1 /** 2 * Compiler implementation of the 3 * $(LINK2 http://www.dlang.org, D programming language). 4 * 5 * Copyright: Copyright (c) 1999-2016 by Digital Mars, All Rights Reserved 6 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright) 7 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 8 * Source: $(DMDSRC _doc.d) 9 */ 10 11 module ddmd.doc; 12 13 import core.stdc.ctype; 14 import core.stdc.stdlib; 15 import core.stdc.stdio; 16 import core.stdc..string; 17 import core.stdc.time; 18 import ddmd.aggregate; 19 import ddmd.arraytypes; 20 import ddmd.attrib; 21 import ddmd.dclass; 22 import ddmd.declaration; 23 import ddmd.denum; 24 import ddmd.dmacro; 25 import ddmd.dmodule; 26 import ddmd.dscope; 27 import ddmd.dstruct; 28 import ddmd.dsymbol; 29 import ddmd.dtemplate; 30 import ddmd.errors; 31 import ddmd.func; 32 import ddmd.globals; 33 import ddmd.hdrgen; 34 import ddmd.id; 35 import ddmd.identifier; 36 import ddmd.lexer; 37 import ddmd.mtype; 38 import ddmd.root.array; 39 import ddmd.root.file; 40 import ddmd.root.filename; 41 import ddmd.root.outbuffer; 42 import ddmd.root.port; 43 import ddmd.root.rmem; 44 import ddmd.tokens; 45 import ddmd.utf; 46 import ddmd.utils; 47 import ddmd.visitor; 48 49 struct Escape 50 { 51 const(char)*[256] strings; 52 53 /*************************************** 54 * Find character string to replace c with. 55 */ 56 extern (C++) const(char)* escapeChar(uint c) 57 { 58 version (all) 59 { 60 assert(c < 256); 61 //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c]); 62 return strings[c]; 63 } 64 else 65 { 66 const(char)* s; 67 switch (c) 68 { 69 case '<': 70 s = "<"; 71 break; 72 case '>': 73 s = ">"; 74 break; 75 case '&': 76 s = "&"; 77 break; 78 default: 79 s = null; 80 break; 81 } 82 return s; 83 } 84 } 85 } 86 87 /*********************************************************** 88 */ 89 extern (C++) class Section 90 { 91 const(char)* name; 92 size_t namelen; 93 const(char)* _body; 94 size_t bodylen; 95 int nooutput; 96 97 void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf) 98 { 99 assert(a.dim); 100 if (namelen) 101 { 102 static __gshared const(char)** table = 103 [ 104 "AUTHORS", 105 "BUGS", 106 "COPYRIGHT", 107 "DATE", 108 "DEPRECATED", 109 "EXAMPLES", 110 "HISTORY", 111 "LICENSE", 112 "RETURNS", 113 "SEE_ALSO", 114 "STANDARDS", 115 "THROWS", 116 "VERSION", 117 null 118 ]; 119 for (size_t i = 0; table[i]; i++) 120 { 121 if (icmp(table[i], name, namelen) == 0) 122 { 123 buf.printf("$(DDOC_%s ", table[i]); 124 goto L1; 125 } 126 } 127 buf.writestring("$(DDOC_SECTION "); 128 // Replace _ characters with spaces 129 buf.writestring("$(DDOC_SECTION_H "); 130 size_t o = buf.offset; 131 for (size_t u = 0; u < namelen; u++) 132 { 133 char c = name[u]; 134 buf.writeByte((c == '_') ? ' ' : c); 135 } 136 escapeStrayParenthesis(loc, buf, o); 137 buf.writestring(":)\n"); 138 } 139 else 140 { 141 buf.writestring("$(DDOC_DESCRIPTION "); 142 } 143 L1: 144 size_t o = buf.offset; 145 buf.write(_body, bodylen); 146 escapeStrayParenthesis(loc, buf, o); 147 highlightText(sc, a, buf, o); 148 buf.writestring(")\n"); 149 } 150 } 151 152 /*********************************************************** 153 */ 154 extern (C++) final class ParamSection : Section 155 { 156 override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf) 157 { 158 assert(a.dim); 159 Dsymbol s = (*a)[0]; // test 160 const(char)* p = _body; 161 size_t len = bodylen; 162 const(char)* pend = p + len; 163 const(char)* tempstart = null; 164 size_t templen = 0; 165 const(char)* namestart = null; 166 size_t namelen = 0; // !=0 if line continuation 167 const(char)* textstart = null; 168 size_t textlen = 0; 169 size_t paramcount = 0; 170 buf.writestring("$(DDOC_PARAMS "); 171 while (p < pend) 172 { 173 // Skip to start of macro 174 while (1) 175 { 176 switch (*p) 177 { 178 case ' ': 179 case '\t': 180 p++; 181 continue; 182 case '\n': 183 p++; 184 goto Lcont; 185 default: 186 if (isIdStart(p) || isCVariadicArg(p, pend - p)) 187 break; 188 if (namelen) 189 goto Ltext; 190 // continuation of prev macro 191 goto Lskipline; 192 } 193 break; 194 } 195 tempstart = p; 196 while (isIdTail(p)) 197 p += utfStride(p); 198 if (isCVariadicArg(p, pend - p)) 199 p += 3; 200 templen = p - tempstart; 201 while (*p == ' ' || *p == '\t') 202 p++; 203 if (*p != '=') 204 { 205 if (namelen) 206 goto Ltext; 207 // continuation of prev macro 208 goto Lskipline; 209 } 210 p++; 211 if (namelen) 212 { 213 // Output existing param 214 L1: 215 //printf("param '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart); 216 ++paramcount; 217 HdrGenState hgs; 218 buf.writestring("$(DDOC_PARAM_ROW "); 219 { 220 buf.writestring("$(DDOC_PARAM_ID "); 221 { 222 size_t o = buf.offset; 223 Parameter fparam = isFunctionParameter(a, namestart, namelen); 224 if (!fparam) 225 { 226 // Comments on a template might refer to function parameters within. 227 // Search the parameters of nested eponymous functions (with the same name.) 228 fparam = isEponymousFunctionParameter(a, namestart, namelen); 229 } 230 bool isCVariadic = isCVariadicParameter(a, namestart, namelen); 231 if (isCVariadic) 232 { 233 buf.writestring("..."); 234 } 235 else if (fparam && fparam.type && fparam.ident) 236 { 237 .toCBuffer(fparam.type, buf, fparam.ident, &hgs); 238 } 239 else 240 { 241 if (isTemplateParameter(a, namestart, namelen)) 242 { 243 // 10236: Don't count template parameters for params check 244 --paramcount; 245 } 246 else if (!fparam) 247 { 248 warning(s.loc, "Ddoc: function declaration has no parameter '%.*s'", namelen, namestart); 249 } 250 buf.write(namestart, namelen); 251 } 252 escapeStrayParenthesis(loc, buf, o); 253 highlightCode(sc, a, buf, o); 254 } 255 buf.writestring(")\n"); 256 buf.writestring("$(DDOC_PARAM_DESC "); 257 { 258 size_t o = buf.offset; 259 buf.write(textstart, textlen); 260 escapeStrayParenthesis(loc, buf, o); 261 highlightText(sc, a, buf, o); 262 } 263 buf.writestring(")"); 264 } 265 buf.writestring(")\n"); 266 namelen = 0; 267 if (p >= pend) 268 break; 269 } 270 namestart = tempstart; 271 namelen = templen; 272 while (*p == ' ' || *p == '\t') 273 p++; 274 textstart = p; 275 Ltext: 276 while (*p != '\n') 277 p++; 278 textlen = p - textstart; 279 p++; 280 Lcont: 281 continue; 282 Lskipline: 283 // Ignore this line 284 while (*p++ != '\n') 285 { 286 } 287 } 288 if (namelen) 289 goto L1; 290 // write out last one 291 buf.writestring(")\n"); 292 TypeFunction tf = a.dim == 1 ? isTypeFunction(s) : null; 293 if (tf) 294 { 295 size_t pcount = (tf.parameters ? tf.parameters.dim : 0) + cast(int)(tf.varargs == 1); 296 if (pcount != paramcount) 297 { 298 warning(s.loc, "Ddoc: parameter count mismatch"); 299 } 300 } 301 } 302 } 303 304 /*********************************************************** 305 */ 306 extern (C++) final class MacroSection : Section 307 { 308 override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf) 309 { 310 //printf("MacroSection::write()\n"); 311 DocComment.parseMacros(dc.pescapetable, dc.pmacrotable, _body, bodylen); 312 } 313 } 314 315 alias Sections = Array!(Section); 316 317 // Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one). 318 extern (C++) bool isCVariadicParameter(Dsymbols* a, const(char)* p, size_t len) 319 { 320 for (size_t i = 0; i < a.dim; i++) 321 { 322 TypeFunction tf = isTypeFunction((*a)[i]); 323 if (tf && tf.varargs == 1 && cmp("...", p, len) == 0) 324 return true; 325 } 326 return false; 327 } 328 329 extern (C++) static Dsymbol getEponymousMember(TemplateDeclaration td) 330 { 331 if (!td.onemember) 332 return null; 333 if (AggregateDeclaration ad = td.onemember.isAggregateDeclaration()) 334 return ad; 335 if (FuncDeclaration fd = td.onemember.isFuncDeclaration()) 336 return fd; 337 if (auto em = td.onemember.isEnumMember()) 338 return null; // Keep backward compatibility. See compilable/ddoc9.d 339 if (VarDeclaration vd = td.onemember.isVarDeclaration()) 340 return td.constraint ? null : vd; 341 return null; 342 } 343 344 extern (C++) static TemplateDeclaration getEponymousParent(Dsymbol s) 345 { 346 if (!s.parent) 347 return null; 348 TemplateDeclaration td = s.parent.isTemplateDeclaration(); 349 return (td && getEponymousMember(td)) ? td : null; 350 } 351 352 extern (C++) __gshared const(char)* ddoc_default = "DDOC = <html><head> 353 $(DDOC_COMMENT Generated by Ddoc from $(SRCFILENAME)) 354 <META http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"> 355 <title>$(TITLE)</title> 356 </head><body> 357 <h1>$(TITLE)</h1> 358 $(BODY) 359 <hr>$(SMALL Page generated by $(LINK2 http://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT)) 360 </body></html> 361 362 B = <b>$0</b> 363 I = <i>$0</i> 364 U = <u>$0</u> 365 P = <p>$0</p> 366 DL = <dl>$0</dl> 367 DT = <dt>$0</dt> 368 DD = <dd>$0</dd> 369 TABLE = <table>$0</table> 370 TR = <tr>$0</tr> 371 TH = <th>$0</th> 372 TD = <td>$0</td> 373 OL = <ol>$0</ol> 374 UL = <ul>$0</ul> 375 LI = <li>$0</li> 376 BIG = <big>$0</big> 377 SMALL = <small>$0</small> 378 BR = <br> 379 LINK = <a href=\"$0\">$0</a> 380 LINK2 = <a href=\"$1\">$+</a> 381 LPAREN= ( 382 RPAREN= ) 383 BACKTICK= ` 384 DOLLAR= $ 385 DEPRECATED= $0 386 387 RED = <font color=red>$0</font> 388 BLUE = <font color=blue>$0</font> 389 GREEN = <font color=green>$0</font> 390 YELLOW =<font color=yellow>$0</font> 391 BLACK = <font color=black>$0</font> 392 WHITE = <font color=white>$0</font> 393 394 D_CODE = <pre class=\"d_code\">$0</pre> 395 DDOC_BACKQUOTED = $(D_INLINECODE $0) 396 D_INLINECODE = <pre style=\"display:inline;\" class=\"d_inline_code\">$0</pre> 397 D_COMMENT = $(GREEN $0) 398 D_STRING = $(RED $0) 399 D_KEYWORD = $(BLUE $0) 400 D_PSYMBOL = $(U $0) 401 D_PARAM = $(I $0) 402 403 DDOC_COMMENT = <!-- $0 --> 404 DDOC_DECL = $(DT $(BIG $0)) 405 DDOC_DECL_DD = $(DD $0) 406 DDOC_DITTO = $(BR)$0 407 DDOC_SECTIONS = $0 408 DDOC_SUMMARY = $0$(BR)$(BR) 409 DDOC_DESCRIPTION = $0$(BR)$(BR) 410 DDOC_AUTHORS = $(B Authors:)$(BR) 411 $0$(BR)$(BR) 412 DDOC_BUGS = $(RED BUGS:)$(BR) 413 $0$(BR)$(BR) 414 DDOC_COPYRIGHT = $(B Copyright:)$(BR) 415 $0$(BR)$(BR) 416 DDOC_DATE = $(B Date:)$(BR) 417 $0$(BR)$(BR) 418 DDOC_DEPRECATED = $(RED Deprecated:)$(BR) 419 $0$(BR)$(BR) 420 DDOC_EXAMPLES = $(B Examples:)$(BR) 421 $0$(BR)$(BR) 422 DDOC_HISTORY = $(B History:)$(BR) 423 $0$(BR)$(BR) 424 DDOC_LICENSE = $(B License:)$(BR) 425 $0$(BR)$(BR) 426 DDOC_RETURNS = $(B Returns:)$(BR) 427 $0$(BR)$(BR) 428 DDOC_SEE_ALSO = $(B See Also:)$(BR) 429 $0$(BR)$(BR) 430 DDOC_STANDARDS = $(B Standards:)$(BR) 431 $0$(BR)$(BR) 432 DDOC_THROWS = $(B Throws:)$(BR) 433 $0$(BR)$(BR) 434 DDOC_VERSION = $(B Version:)$(BR) 435 $0$(BR)$(BR) 436 DDOC_SECTION_H = $(B $0)$(BR) 437 DDOC_SECTION = $0$(BR)$(BR) 438 DDOC_MEMBERS = $(DL $0) 439 DDOC_MODULE_MEMBERS = $(DDOC_MEMBERS $0) 440 DDOC_CLASS_MEMBERS = $(DDOC_MEMBERS $0) 441 DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0) 442 DDOC_ENUM_MEMBERS = $(DDOC_MEMBERS $0) 443 DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0) 444 DDOC_ENUM_BASETYPE = $0 445 DDOC_PARAMS = $(B Params:)$(BR) 446 $(TABLE $0)$(BR) 447 DDOC_PARAM_ROW = $(TR $0) 448 DDOC_PARAM_ID = $(TD $0) 449 DDOC_PARAM_DESC = $(TD $0) 450 DDOC_BLANKLINE = $(BR)$(BR) 451 452 DDOC_ANCHOR = <a name=\"$1\"></a> 453 DDOC_PSYMBOL = $(U $0) 454 DDOC_PSUPER_SYMBOL = $(U $0) 455 DDOC_KEYWORD = $(B $0) 456 DDOC_PARAM = $(I $0) 457 DDOC_CONSTRAINT = $(DDOC_CONSTRAINT) if ($0) 458 DDOC_OVERLOAD_SEPARATOR = 459 DDOC_TEMPLATE_PARAM_LIST = $0 460 DDOC_TEMPLATE_PARAM = $0 461 462 ESCAPES = /</</ 463 />/>/ 464 /&/&/ 465 "; 466 extern (C++) __gshared const(char)* ddoc_decl_s = "$(DDOC_DECL "; 467 extern (C++) __gshared const(char)* ddoc_decl_e = ")\n"; 468 extern (C++) __gshared const(char)* ddoc_decl_dd_s = "$(DDOC_DECL_DD "; 469 extern (C++) __gshared const(char)* ddoc_decl_dd_e = ")\n"; 470 471 /**************************************************** 472 */ 473 extern (C++) void gendocfile(Module m) 474 { 475 static __gshared OutBuffer mbuf; 476 static __gshared int mbuf_done; 477 OutBuffer buf; 478 //printf("Module::gendocfile()\n"); 479 if (!mbuf_done) // if not already read the ddoc files 480 { 481 mbuf_done = 1; 482 // Use our internal default 483 mbuf.write(ddoc_default, strlen(ddoc_default)); 484 // Override with DDOCFILE specified in the sc.ini file 485 char* p = getenv("DDOCFILE"); 486 if (p) 487 global.params.ddocfiles.shift(p); 488 // Override with the ddoc macro files from the command line 489 for (size_t i = 0; i < global.params.ddocfiles.dim; i++) 490 { 491 auto f = FileName((*global.params.ddocfiles)[i]); 492 auto file = File(&f); 493 readFile(m.loc, &file); 494 // BUG: convert file contents to UTF-8 before use 495 //printf("file: '%.*s'\n", file.len, file.buffer); 496 mbuf.write(file.buffer, file.len); 497 } 498 } 499 DocComment.parseMacros(&m.escapetable, &m.macrotable, mbuf.peekSlice().ptr, mbuf.peekSlice().length); 500 Scope* sc = Scope.createGlobal(m); // create root scope 501 DocComment* dc = DocComment.parse(sc, m, m.comment); 502 dc.pmacrotable = &m.macrotable; 503 dc.pescapetable = &m.escapetable; 504 sc.lastdc = dc; 505 // Generate predefined macros 506 // Set the title to be the name of the module 507 { 508 const(char)* p = m.toPrettyChars(); 509 Macro.define(&m.macrotable, "TITLE", p[0 .. strlen(p)]); 510 } 511 // Set time macros 512 { 513 time_t t; 514 time(&t); 515 char* p = ctime(&t); 516 p = mem.xstrdup(p); 517 Macro.define(&m.macrotable, "DATETIME", p[0 .. strlen(p)]); 518 Macro.define(&m.macrotable, "YEAR", p[20 .. 20 + 4]); 519 } 520 const srcfilename = m.srcfile.toChars(); 521 Macro.define(&m.macrotable, "SRCFILENAME", srcfilename[0 .. strlen(srcfilename)]); 522 const docfilename = m.docfile.toChars(); 523 Macro.define(&m.macrotable, "DOCFILENAME", docfilename[0 .. strlen(docfilename)]); 524 if (dc.copyright) 525 { 526 dc.copyright.nooutput = 1; 527 Macro.define(&m.macrotable, "COPYRIGHT", dc.copyright._body[0 .. dc.copyright.bodylen]); 528 } 529 if (m.isDocFile) 530 { 531 Loc loc = m.md ? m.md.loc : m.loc; 532 size_t commentlen = strlen(cast(char*)m.comment); 533 Dsymbols a; 534 // Bugzilla 9764: Don't push m in a, to prevent emphasize ddoc file name. 535 if (dc.macros) 536 { 537 commentlen = dc.macros.name - m.comment; 538 dc.macros.write(loc, dc, sc, &a, &buf); 539 } 540 buf.write(m.comment, commentlen); 541 highlightText(sc, &a, &buf, 0); 542 } 543 else 544 { 545 Dsymbols a; 546 a.push(m); 547 dc.writeSections(sc, &a, &buf); 548 emitMemberComments(m, &buf, sc); 549 } 550 //printf("BODY= '%.*s'\n", buf.offset, buf.data); 551 Macro.define(&m.macrotable, "BODY", buf.peekSlice()); 552 OutBuffer buf2; 553 buf2.writestring("$(DDOC)\n"); 554 size_t end = buf2.offset; 555 m.macrotable.expand(&buf2, 0, &end, null, 0); 556 version (all) 557 { 558 /* Remove all the escape sequences from buf2, 559 * and make CR-LF the newline. 560 */ 561 { 562 const slice = buf2.peekSlice(); 563 buf.setsize(0); 564 buf.reserve(slice.length); 565 auto p = slice.ptr; 566 for (size_t j = 0; j < slice.length; j++) 567 { 568 char c = p[j]; 569 if (c == 0xFF && j + 1 < slice.length) 570 { 571 j++; 572 continue; 573 } 574 if (c == '\n') 575 buf.writeByte('\r'); 576 else if (c == '\r') 577 { 578 buf.writestring("\r\n"); 579 if (j + 1 < slice.length && p[j + 1] == '\n') 580 { 581 j++; 582 } 583 continue; 584 } 585 buf.writeByte(c); 586 } 587 } 588 // Transfer image to file 589 assert(m.docfile); 590 m.docfile.setbuffer(cast(void*)buf.peekSlice().ptr, buf.peekSlice().length); 591 m.docfile._ref = 1; 592 ensurePathToNameExists(Loc(), m.docfile.toChars()); 593 writeFile(m.loc, m.docfile); 594 } 595 else 596 { 597 /* Remove all the escape sequences from buf2 598 */ 599 { 600 size_t i = 0; 601 char* p = buf2.data; 602 for (size_t j = 0; j < buf2.offset; j++) 603 { 604 if (p[j] == 0xFF && j + 1 < buf2.offset) 605 { 606 j++; 607 continue; 608 } 609 p[i] = p[j]; 610 i++; 611 } 612 buf2.setsize(i); 613 } 614 // Transfer image to file 615 m.docfile.setbuffer(buf2.data, buf2.offset); 616 m.docfile._ref = 1; 617 ensurePathToNameExists(Loc(), m.docfile.toChars()); 618 writeFile(m.loc, m.docfile); 619 } 620 } 621 622 /**************************************************** 623 * Having unmatched parentheses can hose the output of Ddoc, 624 * as the macros depend on properly nested parentheses. 625 * This function replaces all ( with $(LPAREN) and ) with $(RPAREN) 626 * to preserve text literally. This also means macros in the 627 * text won't be expanded. 628 */ 629 extern (C++) void escapeDdocString(OutBuffer* buf, size_t start) 630 { 631 for (size_t u = start; u < buf.offset; u++) 632 { 633 char c = buf.data[u]; 634 switch (c) 635 { 636 case '$': 637 buf.remove(u, 1); 638 buf.insert(u, "$(DOLLAR)"); 639 u += 8; 640 break; 641 case '(': 642 buf.remove(u, 1); //remove the ( 643 buf.insert(u, "$(LPAREN)"); //insert this instead 644 u += 8; //skip over newly inserted macro 645 break; 646 case ')': 647 buf.remove(u, 1); //remove the ) 648 buf.insert(u, "$(RPAREN)"); //insert this instead 649 u += 8; //skip over newly inserted macro 650 break; 651 default: 652 break; 653 } 654 } 655 } 656 657 /**************************************************** 658 * Having unmatched parentheses can hose the output of Ddoc, 659 * as the macros depend on properly nested parentheses. 660 * 661 * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN). 662 */ 663 extern (C++) void escapeStrayParenthesis(Loc loc, OutBuffer* buf, size_t start) 664 { 665 uint par_open = 0; 666 bool inCode = 0; 667 for (size_t u = start; u < buf.offset; u++) 668 { 669 char c = buf.data[u]; 670 switch (c) 671 { 672 case '(': 673 if (!inCode) 674 par_open++; 675 break; 676 case ')': 677 if (!inCode) 678 { 679 if (par_open == 0) 680 { 681 //stray ')' 682 warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output. Use $(RPAREN) instead for unpaired right parentheses."); 683 buf.remove(u, 1); //remove the ) 684 buf.insert(u, "$(RPAREN)"); //insert this instead 685 u += 8; //skip over newly inserted macro 686 } 687 else 688 par_open--; 689 } 690 break; 691 version (none) 692 { 693 // For this to work, loc must be set to the beginning of the passed 694 // text which is currently not possible 695 // (loc is set to the Loc of the Dsymbol) 696 case '\n': 697 loc.linnum++; 698 break; 699 } 700 case '-': 701 // Issue 15465: don't try to escape unbalanced parens inside code 702 // blocks. 703 int numdash = 0; 704 while (u < buf.offset && buf.data[u] == '-') 705 { 706 numdash++; 707 u++; 708 } 709 if (numdash >= 3) 710 inCode = !inCode; 711 break; 712 default: 713 break; 714 } 715 } 716 if (par_open) // if any unmatched lparens 717 { 718 par_open = 0; 719 for (size_t u = buf.offset; u > start;) 720 { 721 u--; 722 char c = buf.data[u]; 723 switch (c) 724 { 725 case ')': 726 par_open++; 727 break; 728 case '(': 729 if (par_open == 0) 730 { 731 //stray '(' 732 warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output. Use $(LPAREN) instead for unpaired left parentheses."); 733 buf.remove(u, 1); //remove the ( 734 buf.insert(u, "$(LPAREN)"); //insert this instead 735 } 736 else 737 par_open--; 738 break; 739 default: 740 break; 741 } 742 } 743 } 744 } 745 746 // Basically, this is to skip over things like private{} blocks in a struct or 747 // class definition that don't add any components to the qualified name. 748 extern (C++) static Scope* skipNonQualScopes(Scope* sc) 749 { 750 while (sc && !sc.scopesym) 751 sc = sc.enclosing; 752 return sc; 753 } 754 755 extern (C++) static bool emitAnchorName(OutBuffer* buf, Dsymbol s, Scope* sc) 756 { 757 if (!s || s.isPackage() || s.isModule()) 758 return false; 759 // Add parent names first 760 bool dot = false; 761 if (s.parent) 762 dot = emitAnchorName(buf, s.parent, sc); 763 else if (sc) 764 dot = emitAnchorName(buf, sc.scopesym, skipNonQualScopes(sc.enclosing)); 765 // Eponymous template members can share the parent anchor name 766 if (getEponymousParent(s)) 767 return dot; 768 if (dot) 769 buf.writeByte('.'); 770 // Use "this" not "__ctor" 771 TemplateDeclaration td; 772 if (s.isCtorDeclaration() || ((td = s.isTemplateDeclaration()) !is null && td.onemember && td.onemember.isCtorDeclaration())) 773 { 774 buf.writestring("this"); 775 } 776 else 777 { 778 /* We just want the identifier, not overloads like TemplateDeclaration::toChars. 779 * We don't want the template parameter list and constraints. */ 780 buf.writestring(s.Dsymbol.toChars()); 781 } 782 return true; 783 } 784 785 extern (C++) static void emitAnchor(OutBuffer* buf, Dsymbol s, Scope* sc) 786 { 787 Identifier ident; 788 { 789 OutBuffer anc; 790 emitAnchorName(&anc, s, skipNonQualScopes(sc)); 791 ident = Identifier.idPool(anc.peekSlice()); 792 } 793 794 auto pcount = cast(void*)ident in sc.anchorCounts; 795 typeof(*pcount) count; 796 if (pcount) 797 { 798 // Existing anchor, 799 // don't write an anchor for matching consecutive ditto symbols 800 TemplateDeclaration td = getEponymousParent(s); 801 if (sc.prevAnchor == ident && sc.lastdc && (isDitto(s.comment) || (td && isDitto(td.comment)))) 802 return; 803 804 count = ++*pcount; 805 } 806 else 807 { 808 sc.anchorCounts[cast(void*)ident] = 1; 809 count = 1; 810 } 811 812 // cache anchor name 813 sc.prevAnchor = ident; 814 buf.writestring("$(DDOC_ANCHOR "); 815 buf.writestring(ident.toChars()); 816 // only append count once there's a duplicate 817 if (count > 1) 818 buf.printf(".%u", count); 819 buf.writeByte(')'); 820 } 821 822 /******************************* emitComment **********************************/ 823 824 /** Get leading indentation from 'src' which represents lines of code. */ 825 extern (C++) static size_t getCodeIndent(const(char)* src) 826 { 827 while (src && (*src == '\r' || *src == '\n')) 828 ++src; // skip until we find the first non-empty line 829 size_t codeIndent = 0; 830 while (src && (*src == ' ' || *src == '\t')) 831 { 832 codeIndent++; 833 src++; 834 } 835 return codeIndent; 836 } 837 838 /** Recursively expand template mixin member docs into the scope. */ 839 extern (C++) static void expandTemplateMixinComments(TemplateMixin tm, OutBuffer* buf, Scope* sc) 840 { 841 if (!tm.semanticRun) 842 tm.semantic(sc); 843 TemplateDeclaration td = (tm && tm.tempdecl) ? tm.tempdecl.isTemplateDeclaration() : null; 844 if (td && td.members) 845 { 846 for (size_t i = 0; i < td.members.dim; i++) 847 { 848 Dsymbol sm = (*td.members)[i]; 849 TemplateMixin tmc = sm.isTemplateMixin(); 850 if (tmc && tmc.comment) 851 expandTemplateMixinComments(tmc, buf, sc); 852 else 853 emitComment(sm, buf, sc); 854 } 855 } 856 } 857 858 extern (C++) void emitMemberComments(ScopeDsymbol sds, OutBuffer* buf, Scope* sc) 859 { 860 if (!sds.members) 861 return; 862 //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars()); 863 const(char)* m = "$(DDOC_MEMBERS "; 864 if (sds.isTemplateDeclaration()) 865 m = "$(DDOC_TEMPLATE_MEMBERS "; 866 else if (sds.isClassDeclaration()) 867 m = "$(DDOC_CLASS_MEMBERS "; 868 else if (sds.isStructDeclaration()) 869 m = "$(DDOC_STRUCT_MEMBERS "; 870 else if (sds.isEnumDeclaration()) 871 m = "$(DDOC_ENUM_MEMBERS "; 872 else if (sds.isModule()) 873 m = "$(DDOC_MODULE_MEMBERS "; 874 size_t offset1 = buf.offset; // save starting offset 875 buf.writestring(m); 876 size_t offset2 = buf.offset; // to see if we write anything 877 sc = sc.push(sds); 878 for (size_t i = 0; i < sds.members.dim; i++) 879 { 880 Dsymbol s = (*sds.members)[i]; 881 //printf("\ts = '%s'\n", s->toChars()); 882 // only expand if parent is a non-template (semantic won't work) 883 if (s.comment && s.isTemplateMixin() && s.parent && !s.parent.isTemplateDeclaration()) 884 expandTemplateMixinComments(cast(TemplateMixin)s, buf, sc); 885 emitComment(s, buf, sc); 886 } 887 emitComment(null, buf, sc); 888 sc.pop(); 889 if (buf.offset == offset2) 890 { 891 /* Didn't write out any members, so back out last write 892 */ 893 buf.offset = offset1; 894 } 895 else 896 buf.writestring(")\n"); 897 } 898 899 extern (C++) void emitProtection(OutBuffer* buf, Prot prot) 900 { 901 if (prot.kind != PROTundefined && prot.kind != PROTpublic) 902 { 903 protectionToBuffer(buf, prot); 904 buf.writeByte(' '); 905 } 906 } 907 908 extern (C++) void emitComment(Dsymbol s, OutBuffer* buf, Scope* sc) 909 { 910 extern (C++) final class EmitComment : Visitor 911 { 912 alias visit = super.visit; 913 public: 914 OutBuffer* buf; 915 Scope* sc; 916 917 extern (D) this(OutBuffer* buf, Scope* sc) 918 { 919 this.buf = buf; 920 this.sc = sc; 921 } 922 923 override void visit(Dsymbol) 924 { 925 } 926 927 override void visit(InvariantDeclaration) 928 { 929 } 930 931 override void visit(UnitTestDeclaration) 932 { 933 } 934 935 override void visit(PostBlitDeclaration) 936 { 937 } 938 939 override void visit(DtorDeclaration) 940 { 941 } 942 943 override void visit(StaticCtorDeclaration) 944 { 945 } 946 947 override void visit(StaticDtorDeclaration) 948 { 949 } 950 951 override void visit(TypeInfoDeclaration) 952 { 953 } 954 955 void emit(Scope* sc, Dsymbol s, const(char)* com) 956 { 957 if (s && sc.lastdc && isDitto(com)) 958 { 959 sc.lastdc.a.push(s); 960 return; 961 } 962 // Put previous doc comment if exists 963 if (DocComment* dc = sc.lastdc) 964 { 965 // Put the declaration signatures as the document 'title' 966 buf.writestring(ddoc_decl_s); 967 for (size_t i = 0; i < dc.a.dim; i++) 968 { 969 Dsymbol sx = dc.a[i]; 970 // the added linebreaks in here make looking at multiple 971 // signatures more appealing 972 if (i == 0) 973 { 974 size_t o = buf.offset; 975 toDocBuffer(sx, buf, sc); 976 highlightCode(sc, sx, buf, o); 977 buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)"); 978 continue; 979 } 980 buf.writestring("$(DDOC_DITTO "); 981 { 982 size_t o = buf.offset; 983 toDocBuffer(sx, buf, sc); 984 highlightCode(sc, sx, buf, o); 985 } 986 buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)"); 987 buf.writeByte(')'); 988 } 989 buf.writestring(ddoc_decl_e); 990 // Put the ddoc comment as the document 'description' 991 buf.writestring(ddoc_decl_dd_s); 992 { 993 dc.writeSections(sc, &dc.a, buf); 994 if (ScopeDsymbol sds = dc.a[0].isScopeDsymbol()) 995 emitMemberComments(sds, buf, sc); 996 } 997 buf.writestring(ddoc_decl_dd_e); 998 //printf("buf.2 = [[%.*s]]\n", buf->offset - o0, buf->data + o0); 999 } 1000 if (s) 1001 { 1002 DocComment* dc = DocComment.parse(sc, s, com); 1003 dc.pmacrotable = &sc._module.macrotable; 1004 sc.lastdc = dc; 1005 } 1006 } 1007 1008 override void visit(Declaration d) 1009 { 1010 //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d.toChars(), d.comment); 1011 //printf("type = %p\n", d.type); 1012 const(char)* com = d.comment; 1013 if (TemplateDeclaration td = getEponymousParent(d)) 1014 { 1015 if (isDitto(td.comment)) 1016 com = td.comment; 1017 else 1018 com = Lexer.combineComments(td.comment, com); 1019 } 1020 else 1021 { 1022 if (!d.ident) 1023 return; 1024 if (!d.type) 1025 { 1026 if (!d.isCtorDeclaration() && 1027 !d.isAliasDeclaration() && 1028 !d.isVarDeclaration()) 1029 { 1030 return; 1031 } 1032 } 1033 if (d.protection.kind == PROTprivate || sc.protection.kind == PROTprivate) 1034 return; 1035 } 1036 if (!com) 1037 return; 1038 emit(sc, d, com); 1039 } 1040 1041 override void visit(AggregateDeclaration ad) 1042 { 1043 //printf("AggregateDeclaration::emitComment() '%s'\n", ad->toChars()); 1044 const(char)* com = ad.comment; 1045 if (TemplateDeclaration td = getEponymousParent(ad)) 1046 { 1047 if (isDitto(td.comment)) 1048 com = td.comment; 1049 else 1050 com = Lexer.combineComments(td.comment, com); 1051 } 1052 else 1053 { 1054 if (ad.prot().kind == PROTprivate || sc.protection.kind == PROTprivate) 1055 return; 1056 if (!ad.comment) 1057 return; 1058 } 1059 if (!com) 1060 return; 1061 emit(sc, ad, com); 1062 } 1063 1064 override void visit(TemplateDeclaration td) 1065 { 1066 //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td->toChars(), td->kind()); 1067 if (td.prot().kind == PROTprivate || sc.protection.kind == PROTprivate) 1068 return; 1069 if (!td.comment) 1070 return; 1071 if (Dsymbol ss = getEponymousMember(td)) 1072 { 1073 ss.accept(this); 1074 return; 1075 } 1076 emit(sc, td, td.comment); 1077 } 1078 1079 override void visit(EnumDeclaration ed) 1080 { 1081 if (ed.prot().kind == PROTprivate || sc.protection.kind == PROTprivate) 1082 return; 1083 if (ed.isAnonymous() && ed.members) 1084 { 1085 for (size_t i = 0; i < ed.members.dim; i++) 1086 { 1087 Dsymbol s = (*ed.members)[i]; 1088 emitComment(s, buf, sc); 1089 } 1090 return; 1091 } 1092 if (!ed.comment) 1093 return; 1094 if (ed.isAnonymous()) 1095 return; 1096 emit(sc, ed, ed.comment); 1097 } 1098 1099 override void visit(EnumMember em) 1100 { 1101 //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em->toChars(), em->comment); 1102 if (em.prot().kind == PROTprivate || sc.protection.kind == PROTprivate) 1103 return; 1104 if (!em.comment) 1105 return; 1106 emit(sc, em, em.comment); 1107 } 1108 1109 override void visit(AttribDeclaration ad) 1110 { 1111 //printf("AttribDeclaration::emitComment(sc = %p)\n", sc); 1112 /* A general problem with this, illustrated by BUGZILLA 2516, 1113 * is that attributes are not transmitted through to the underlying 1114 * member declarations for template bodies, because semantic analysis 1115 * is not done for template declaration bodies 1116 * (only template instantiations). 1117 * Hence, Ddoc omits attributes from template members. 1118 */ 1119 Dsymbols* d = ad.include(null, null); 1120 if (d) 1121 { 1122 for (size_t i = 0; i < d.dim; i++) 1123 { 1124 Dsymbol s = (*d)[i]; 1125 //printf("AttribDeclaration::emitComment %s\n", s->toChars()); 1126 emitComment(s, buf, sc); 1127 } 1128 } 1129 } 1130 1131 override void visit(ProtDeclaration pd) 1132 { 1133 if (pd.decl) 1134 { 1135 Scope* scx = sc; 1136 sc = sc.copy(); 1137 sc.protection = pd.protection; 1138 visit(cast(AttribDeclaration)pd); 1139 scx.lastdc = sc.lastdc; 1140 sc = sc.pop(); 1141 } 1142 } 1143 1144 override void visit(ConditionalDeclaration cd) 1145 { 1146 //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc); 1147 if (cd.condition.inc) 1148 { 1149 visit(cast(AttribDeclaration)cd); 1150 return; 1151 } 1152 /* If generating doc comment, be careful because if we're inside 1153 * a template, then include(NULL, NULL) will fail. 1154 */ 1155 Dsymbols* d = cd.decl ? cd.decl : cd.elsedecl; 1156 for (size_t i = 0; i < d.dim; i++) 1157 { 1158 Dsymbol s = (*d)[i]; 1159 emitComment(s, buf, sc); 1160 } 1161 } 1162 } 1163 1164 scope EmitComment v = new EmitComment(buf, sc); 1165 if (!s) 1166 v.emit(sc, null, null); 1167 else 1168 s.accept(v); 1169 } 1170 1171 extern (C++) void toDocBuffer(Dsymbol s, OutBuffer* buf, Scope* sc) 1172 { 1173 extern (C++) final class ToDocBuffer : Visitor 1174 { 1175 alias visit = super.visit; 1176 public: 1177 OutBuffer* buf; 1178 Scope* sc; 1179 1180 extern (D) this(OutBuffer* buf, Scope* sc) 1181 { 1182 this.buf = buf; 1183 this.sc = sc; 1184 } 1185 1186 override void visit(Dsymbol s) 1187 { 1188 //printf("Dsymbol::toDocbuffer() %s\n", s->toChars()); 1189 HdrGenState hgs; 1190 hgs.ddoc = true; 1191 .toCBuffer(s, buf, &hgs); 1192 } 1193 1194 void prefix(Dsymbol s) 1195 { 1196 if (s.isDeprecated()) 1197 buf.writestring("deprecated "); 1198 if (Declaration d = s.isDeclaration()) 1199 { 1200 emitProtection(buf, d.protection); 1201 if (d.isStatic()) 1202 buf.writestring("static "); 1203 else if (d.isFinal()) 1204 buf.writestring("final "); 1205 else if (d.isAbstract()) 1206 buf.writestring("abstract "); 1207 1208 if (d.isFuncDeclaration()) // functionToBufferFull handles this 1209 return; 1210 1211 if (d.isImmutable()) 1212 buf.writestring("immutable "); 1213 if (d.storage_class & STCshared) 1214 buf.writestring("shared "); 1215 if (d.isWild()) 1216 buf.writestring("inout "); 1217 if (d.isConst()) 1218 buf.writestring("const "); 1219 1220 if (d.isSynchronized()) 1221 buf.writestring("synchronized "); 1222 1223 if (d.storage_class & STCmanifest) 1224 buf.writestring("enum "); 1225 1226 // Add "auto" for the untyped variable in template members 1227 if (!d.type && d.isVarDeclaration() && 1228 !d.isImmutable() && !(d.storage_class & STCshared) && !d.isWild() && !d.isConst() && 1229 !d.isSynchronized()) 1230 { 1231 buf.writestring("auto "); 1232 } 1233 } 1234 } 1235 1236 override void visit(Declaration d) 1237 { 1238 if (!d.ident) 1239 return; 1240 TemplateDeclaration td = getEponymousParent(d); 1241 //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d->toChars(), d->originalType ? d->originalType->toChars() : "--", td ? td->toChars() : "--"); 1242 HdrGenState hgs; 1243 hgs.ddoc = true; 1244 if (d.isDeprecated()) 1245 buf.writestring("$(DEPRECATED "); 1246 prefix(d); 1247 if (d.type) 1248 { 1249 Type origType = d.originalType ? d.originalType : d.type; 1250 if (origType.ty == Tfunction) 1251 { 1252 functionToBufferFull(cast(TypeFunction)origType, buf, d.ident, &hgs, td); 1253 } 1254 else 1255 .toCBuffer(origType, buf, d.ident, &hgs); 1256 } 1257 else 1258 buf.writestring(d.ident.toChars()); 1259 if (d.isVarDeclaration() && td) 1260 { 1261 buf.writeByte('('); 1262 if (td.origParameters && td.origParameters.dim) 1263 { 1264 for (size_t i = 0; i < td.origParameters.dim; i++) 1265 { 1266 if (i) 1267 buf.writestring(", "); 1268 toCBuffer((*td.origParameters)[i], buf, &hgs); 1269 } 1270 } 1271 buf.writeByte(')'); 1272 } 1273 // emit constraints if declaration is a templated declaration 1274 if (td && td.constraint) 1275 { 1276 bool noFuncDecl = td.isFuncDeclaration() is null; 1277 if (noFuncDecl) 1278 { 1279 buf.writestring("$(DDOC_CONSTRAINT "); 1280 } 1281 1282 .toCBuffer(td.constraint, buf, &hgs); 1283 1284 if (noFuncDecl) 1285 { 1286 buf.writestring(")"); 1287 } 1288 } 1289 if (d.isDeprecated()) 1290 buf.writestring(")"); 1291 buf.writestring(";\n"); 1292 } 1293 1294 override void visit(AliasDeclaration ad) 1295 { 1296 //printf("AliasDeclaration::toDocbuffer() %s\n", ad->toChars()); 1297 if (!ad.ident) 1298 return; 1299 if (ad.isDeprecated()) 1300 buf.writestring("deprecated "); 1301 emitProtection(buf, ad.protection); 1302 buf.printf("alias %s = ", ad.toChars()); 1303 if (Dsymbol s = ad.aliassym) // ident alias 1304 { 1305 prettyPrintDsymbol(s, ad.parent); 1306 } 1307 else if (Type type = ad.getType()) // type alias 1308 { 1309 if (type.ty == Tclass || type.ty == Tstruct || type.ty == Tenum) 1310 { 1311 if (Dsymbol s = type.toDsymbol(null)) // elaborate type 1312 prettyPrintDsymbol(s, ad.parent); 1313 else 1314 buf.writestring(type.toChars()); 1315 } 1316 else 1317 { 1318 // simple type 1319 buf.writestring(type.toChars()); 1320 } 1321 } 1322 buf.writestring(";\n"); 1323 } 1324 1325 void parentToBuffer(Dsymbol s) 1326 { 1327 if (s && !s.isPackage() && !s.isModule()) 1328 { 1329 parentToBuffer(s.parent); 1330 buf.writestring(s.toChars()); 1331 buf.writestring("."); 1332 } 1333 } 1334 1335 static bool inSameModule(Dsymbol s, Dsymbol p) 1336 { 1337 for (; s; s = s.parent) 1338 { 1339 if (s.isModule()) 1340 break; 1341 } 1342 for (; p; p = p.parent) 1343 { 1344 if (p.isModule()) 1345 break; 1346 } 1347 return s == p; 1348 } 1349 1350 void prettyPrintDsymbol(Dsymbol s, Dsymbol parent) 1351 { 1352 if (s.parent && (s.parent == parent)) // in current scope -> naked name 1353 { 1354 buf.writestring(s.toChars()); 1355 } 1356 else if (!inSameModule(s, parent)) // in another module -> full name 1357 { 1358 buf.writestring(s.toPrettyChars()); 1359 } 1360 else // nested in a type in this module -> full name w/o module name 1361 { 1362 // if alias is nested in a user-type use module-scope lookup 1363 if (!parent.isModule() && !parent.isPackage()) 1364 buf.writestring("."); 1365 parentToBuffer(s.parent); 1366 buf.writestring(s.toChars()); 1367 } 1368 } 1369 1370 override void visit(AggregateDeclaration ad) 1371 { 1372 if (!ad.ident) 1373 return; 1374 version (none) 1375 { 1376 emitProtection(buf, ad.protection); 1377 } 1378 buf.printf("%s %s", ad.kind(), ad.toChars()); 1379 buf.writestring(";\n"); 1380 } 1381 1382 override void visit(StructDeclaration sd) 1383 { 1384 //printf("StructDeclaration::toDocbuffer() %s\n", sd->toChars()); 1385 if (!sd.ident) 1386 return; 1387 version (none) 1388 { 1389 emitProtection(buf, sd.protection); 1390 } 1391 if (TemplateDeclaration td = getEponymousParent(sd)) 1392 { 1393 toDocBuffer(td, buf, sc); 1394 } 1395 else 1396 { 1397 buf.printf("%s %s", sd.kind(), sd.toChars()); 1398 } 1399 buf.writestring(";\n"); 1400 } 1401 1402 override void visit(ClassDeclaration cd) 1403 { 1404 //printf("ClassDeclaration::toDocbuffer() %s\n", cd->toChars()); 1405 if (!cd.ident) 1406 return; 1407 version (none) 1408 { 1409 emitProtection(buf, cd.protection); 1410 } 1411 if (TemplateDeclaration td = getEponymousParent(cd)) 1412 { 1413 toDocBuffer(td, buf, sc); 1414 } 1415 else 1416 { 1417 if (!cd.isInterfaceDeclaration() && cd.isAbstract()) 1418 buf.writestring("abstract "); 1419 buf.printf("%s %s", cd.kind(), cd.toChars()); 1420 } 1421 int any = 0; 1422 for (size_t i = 0; i < cd.baseclasses.dim; i++) 1423 { 1424 BaseClass* bc = (*cd.baseclasses)[i]; 1425 if (bc.sym && bc.sym.ident == Id.Object) 1426 continue; 1427 if (any) 1428 buf.writestring(", "); 1429 else 1430 { 1431 buf.writestring(": "); 1432 any = 1; 1433 } 1434 emitProtection(buf, Prot(PROTpublic)); 1435 if (bc.sym) 1436 { 1437 buf.printf("$(DDOC_PSUPER_SYMBOL %s)", bc.sym.toPrettyChars()); 1438 } 1439 else 1440 { 1441 HdrGenState hgs; 1442 .toCBuffer(bc.type, buf, null, &hgs); 1443 } 1444 } 1445 buf.writestring(";\n"); 1446 } 1447 1448 override void visit(EnumDeclaration ed) 1449 { 1450 if (!ed.ident) 1451 return; 1452 buf.printf("%s %s", ed.kind(), ed.toChars()); 1453 if (ed.memtype) 1454 { 1455 buf.writestring(": $(DDOC_ENUM_BASETYPE "); 1456 HdrGenState hgs; 1457 .toCBuffer(ed.memtype, buf, null, &hgs); 1458 buf.writestring(")"); 1459 } 1460 buf.writestring(";\n"); 1461 } 1462 1463 override void visit(EnumMember em) 1464 { 1465 if (!em.ident) 1466 return; 1467 buf.writestring(em.toChars()); 1468 } 1469 } 1470 1471 scope ToDocBuffer v = new ToDocBuffer(buf, sc); 1472 s.accept(v); 1473 } 1474 1475 /*********************************************************** 1476 */ 1477 struct DocComment 1478 { 1479 Sections sections; // Section*[] 1480 Section summary; 1481 Section copyright; 1482 Section macros; 1483 Macro** pmacrotable; 1484 Escape** pescapetable; 1485 Dsymbols a; 1486 1487 extern (C++) static DocComment* parse(Scope* sc, Dsymbol s, const(char)* comment) 1488 { 1489 //printf("parse(%s): '%s'\n", s->toChars(), comment); 1490 auto dc = new DocComment(); 1491 dc.a.push(s); 1492 if (!comment) 1493 return dc; 1494 dc.parseSections(comment); 1495 for (size_t i = 0; i < dc.sections.dim; i++) 1496 { 1497 Section sec = dc.sections[i]; 1498 if (icmp("copyright", sec.name, sec.namelen) == 0) 1499 { 1500 dc.copyright = sec; 1501 } 1502 if (icmp("macros", sec.name, sec.namelen) == 0) 1503 { 1504 dc.macros = sec; 1505 } 1506 } 1507 return dc; 1508 } 1509 1510 /************************************************ 1511 * Parse macros out of Macros: section. 1512 * Macros are of the form: 1513 * name1 = value1 1514 * 1515 * name2 = value2 1516 */ 1517 extern (C++) static void parseMacros(Escape** pescapetable, Macro** pmacrotable, const(char)* m, size_t mlen) 1518 { 1519 const(char)* p = m; 1520 size_t len = mlen; 1521 const(char)* pend = p + len; 1522 const(char)* tempstart = null; 1523 size_t templen = 0; 1524 const(char)* namestart = null; 1525 size_t namelen = 0; // !=0 if line continuation 1526 const(char)* textstart = null; 1527 size_t textlen = 0; 1528 while (p < pend) 1529 { 1530 // Skip to start of macro 1531 while (1) 1532 { 1533 if (p >= pend) 1534 goto Ldone; 1535 switch (*p) 1536 { 1537 case ' ': 1538 case '\t': 1539 p++; 1540 continue; 1541 case '\r': 1542 case '\n': 1543 p++; 1544 goto Lcont; 1545 default: 1546 if (isIdStart(p)) 1547 break; 1548 if (namelen) 1549 goto Ltext; // continuation of prev macro 1550 goto Lskipline; 1551 } 1552 break; 1553 } 1554 tempstart = p; 1555 while (1) 1556 { 1557 if (p >= pend) 1558 goto Ldone; 1559 if (!isIdTail(p)) 1560 break; 1561 p += utfStride(p); 1562 } 1563 templen = p - tempstart; 1564 while (1) 1565 { 1566 if (p >= pend) 1567 goto Ldone; 1568 if (!(*p == ' ' || *p == '\t')) 1569 break; 1570 p++; 1571 } 1572 if (*p != '=') 1573 { 1574 if (namelen) 1575 goto Ltext; // continuation of prev macro 1576 goto Lskipline; 1577 } 1578 p++; 1579 if (p >= pend) 1580 goto Ldone; 1581 if (namelen) 1582 { 1583 // Output existing macro 1584 L1: 1585 //printf("macro '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart); 1586 if (icmp("ESCAPES", namestart, namelen) == 0) 1587 parseEscapes(pescapetable, textstart, textlen); 1588 else 1589 Macro.define(pmacrotable, namestart[0 ..namelen], textstart[0 .. textlen]); 1590 namelen = 0; 1591 if (p >= pend) 1592 break; 1593 } 1594 namestart = tempstart; 1595 namelen = templen; 1596 while (p < pend && (*p == ' ' || *p == '\t')) 1597 p++; 1598 textstart = p; 1599 Ltext: 1600 while (p < pend && *p != '\r' && *p != '\n') 1601 p++; 1602 textlen = p - textstart; 1603 p++; 1604 //printf("p = %p, pend = %p\n", p, pend); 1605 Lcont: 1606 continue; 1607 Lskipline: 1608 // Ignore this line 1609 while (p < pend && *p != '\r' && *p != '\n') 1610 p++; 1611 } 1612 Ldone: 1613 if (namelen) 1614 goto L1; // write out last one 1615 } 1616 1617 /************************************** 1618 * Parse escapes of the form: 1619 * /c/string/ 1620 * where c is a single character. 1621 * Multiple escapes can be separated 1622 * by whitespace and/or commas. 1623 */ 1624 extern (C++) static void parseEscapes(Escape** pescapetable, const(char)* textstart, size_t textlen) 1625 { 1626 Escape* escapetable = *pescapetable; 1627 if (!escapetable) 1628 { 1629 escapetable = new Escape(); 1630 memset(escapetable, 0, Escape.sizeof); 1631 *pescapetable = escapetable; 1632 } 1633 //printf("parseEscapes('%.*s') pescapetable = %p\n", textlen, textstart, pescapetable); 1634 const(char)* p = textstart; 1635 const(char)* pend = p + textlen; 1636 while (1) 1637 { 1638 while (1) 1639 { 1640 if (p + 4 >= pend) 1641 return; 1642 if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',')) 1643 break; 1644 p++; 1645 } 1646 if (p[0] != '/' || p[2] != '/') 1647 return; 1648 char c = p[1]; 1649 p += 3; 1650 const(char)* start = p; 1651 while (1) 1652 { 1653 if (p >= pend) 1654 return; 1655 if (*p == '/') 1656 break; 1657 p++; 1658 } 1659 size_t len = p - start; 1660 char* s = cast(char*)memcpy(mem.xmalloc(len + 1), start, len); 1661 s[len] = 0; 1662 escapetable.strings[c] = s; 1663 //printf("\t%c = '%s'\n", c, s); 1664 p++; 1665 } 1666 } 1667 1668 /***************************************** 1669 * Parse next paragraph out of *pcomment. 1670 * Update *pcomment to point past paragraph. 1671 * Returns NULL if no more paragraphs. 1672 * If paragraph ends in 'identifier:', 1673 * then (*pcomment)[0 .. idlen] is the identifier. 1674 */ 1675 extern (C++) void parseSections(const(char)* comment) 1676 { 1677 const(char)* p; 1678 const(char)* pstart; 1679 const(char)* pend; 1680 const(char)* idstart = null; // dead-store to prevent spurious warning 1681 size_t idlen; 1682 const(char)* name = null; 1683 size_t namelen = 0; 1684 //printf("parseSections('%s')\n", comment); 1685 p = comment; 1686 while (*p) 1687 { 1688 const(char)* pstart0 = p; 1689 p = skipwhitespace(p); 1690 pstart = p; 1691 pend = p; 1692 /* Find end of section, which is ended by one of: 1693 * 'identifier:' (but not inside a code section) 1694 * '\0' 1695 */ 1696 idlen = 0; 1697 int inCode = 0; 1698 while (1) 1699 { 1700 // Check for start/end of a code section 1701 if (*p == '-') 1702 { 1703 if (!inCode) 1704 { 1705 // restore leading indentation 1706 while (pstart0 < pstart && isIndentWS(pstart - 1)) 1707 --pstart; 1708 } 1709 int numdash = 0; 1710 while (*p == '-') 1711 { 1712 ++numdash; 1713 p++; 1714 } 1715 // BUG: handle UTF PS and LS too 1716 if ((!*p || *p == '\r' || *p == '\n') && numdash >= 3) 1717 inCode ^= 1; 1718 pend = p; 1719 } 1720 if (!inCode && isIdStart(p)) 1721 { 1722 const(char)* q = p + utfStride(p); 1723 while (isIdTail(q)) 1724 q += utfStride(q); 1725 if (*q == ':') // identifier: ends it 1726 { 1727 idlen = q - p; 1728 idstart = p; 1729 for (pend = p; pend > pstart; pend--) 1730 { 1731 if (pend[-1] == '\n') 1732 break; 1733 } 1734 p = q + 1; 1735 break; 1736 } 1737 } 1738 while (1) 1739 { 1740 if (!*p) 1741 goto L1; 1742 if (*p == '\n') 1743 { 1744 p++; 1745 if (*p == '\n' && !summary && !namelen && !inCode) 1746 { 1747 pend = p; 1748 p++; 1749 goto L1; 1750 } 1751 break; 1752 } 1753 p++; 1754 pend = p; 1755 } 1756 p = skipwhitespace(p); 1757 } 1758 L1: 1759 if (namelen || pstart < pend) 1760 { 1761 Section s; 1762 if (icmp("Params", name, namelen) == 0) 1763 s = new ParamSection(); 1764 else if (icmp("Macros", name, namelen) == 0) 1765 s = new MacroSection(); 1766 else 1767 s = new Section(); 1768 s.name = name; 1769 s.namelen = namelen; 1770 s._body = pstart; 1771 s.bodylen = pend - pstart; 1772 s.nooutput = 0; 1773 //printf("Section: '%.*s' = '%.*s'\n", s->namelen, s->name, s->bodylen, s->body); 1774 sections.push(s); 1775 if (!summary && !namelen) 1776 summary = s; 1777 } 1778 if (idlen) 1779 { 1780 name = idstart; 1781 namelen = idlen; 1782 } 1783 else 1784 { 1785 name = null; 1786 namelen = 0; 1787 if (!*p) 1788 break; 1789 } 1790 } 1791 } 1792 1793 extern (C++) void writeSections(Scope* sc, Dsymbols* a, OutBuffer* buf) 1794 { 1795 assert(a.dim); 1796 //printf("DocComment::writeSections()\n"); 1797 Loc loc = (*a)[0].loc; 1798 if (Module m = (*a)[0].isModule()) 1799 { 1800 if (m.md) 1801 loc = m.md.loc; 1802 } 1803 size_t offset1 = buf.offset; 1804 buf.writestring("$(DDOC_SECTIONS "); 1805 size_t offset2 = buf.offset; 1806 for (size_t i = 0; i < sections.dim; i++) 1807 { 1808 Section sec = sections[i]; 1809 if (sec.nooutput) 1810 continue; 1811 //printf("Section: '%.*s' = '%.*s'\n", sec->namelen, sec->name, sec->bodylen, sec->body); 1812 if (!sec.namelen && i == 0) 1813 { 1814 buf.writestring("$(DDOC_SUMMARY "); 1815 size_t o = buf.offset; 1816 buf.write(sec._body, sec.bodylen); 1817 escapeStrayParenthesis(loc, buf, o); 1818 highlightText(sc, a, buf, o); 1819 buf.writestring(")\n"); 1820 } 1821 else 1822 sec.write(loc, &this, sc, a, buf); 1823 } 1824 for (size_t i = 0; i < a.dim; i++) 1825 { 1826 Dsymbol s = (*a)[i]; 1827 if (Dsymbol td = getEponymousParent(s)) 1828 s = td; 1829 for (UnitTestDeclaration utd = s.ddocUnittest; utd; utd = utd.ddocUnittest) 1830 { 1831 if (utd.protection.kind == PROTprivate || !utd.comment || !utd.fbody) 1832 continue; 1833 // Strip whitespaces to avoid showing empty summary 1834 const(char)* c = utd.comment; 1835 while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r') 1836 ++c; 1837 buf.writestring("$(DDOC_EXAMPLES "); 1838 size_t o = buf.offset; 1839 buf.writestring(cast(char*)c); 1840 if (utd.codedoc) 1841 { 1842 size_t n = getCodeIndent(utd.codedoc); 1843 while (n--) 1844 buf.writeByte(' '); 1845 buf.writestring("----\n"); 1846 buf.writestring(utd.codedoc); 1847 buf.writestring("----\n"); 1848 highlightText(sc, a, buf, o); 1849 } 1850 buf.writestring(")"); 1851 } 1852 } 1853 if (buf.offset == offset2) 1854 { 1855 /* Didn't write out any sections, so back out last write 1856 */ 1857 buf.offset = offset1; 1858 buf.writestring("$(DDOC_BLANKLINE)\n"); 1859 } 1860 else 1861 buf.writestring(")\n"); 1862 } 1863 } 1864 1865 /****************************************** 1866 * Compare 0-terminated string with length terminated string. 1867 * Return < 0, ==0, > 0 1868 */ 1869 extern (C++) int cmp(const(char)* stringz, const(void)* s, size_t slen) 1870 { 1871 size_t len1 = strlen(stringz); 1872 if (len1 != slen) 1873 return cast(int)(len1 - slen); 1874 return memcmp(stringz, s, slen); 1875 } 1876 1877 extern (C++) int icmp(const(char)* stringz, const(void)* s, size_t slen) 1878 { 1879 size_t len1 = strlen(stringz); 1880 if (len1 != slen) 1881 return cast(int)(len1 - slen); 1882 return Port.memicmp(stringz, cast(char*)s, slen); 1883 } 1884 1885 /***************************************** 1886 * Return true if comment consists entirely of "ditto". 1887 */ 1888 extern (C++) bool isDitto(const(char)* comment) 1889 { 1890 if (comment) 1891 { 1892 const(char)* p = skipwhitespace(comment); 1893 if (Port.memicmp(p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0) 1894 return true; 1895 } 1896 return false; 1897 } 1898 1899 /********************************************** 1900 * Skip white space. 1901 */ 1902 extern (C++) const(char)* skipwhitespace(const(char)* p) 1903 { 1904 for (; 1; p++) 1905 { 1906 switch (*p) 1907 { 1908 case ' ': 1909 case '\t': 1910 case '\n': 1911 continue; 1912 default: 1913 break; 1914 } 1915 break; 1916 } 1917 return p; 1918 } 1919 1920 /************************************************ 1921 * Scan forward to one of: 1922 * start of identifier 1923 * beginning of next line 1924 * end of buf 1925 */ 1926 extern (C++) size_t skiptoident(OutBuffer* buf, size_t i) 1927 { 1928 const slice = buf.peekSlice(); 1929 while (i < slice.length) 1930 { 1931 dchar c; 1932 size_t oi = i; 1933 if (utf_decodeChar(slice.ptr, slice.length, i, c)) 1934 { 1935 /* Ignore UTF errors, but still consume input 1936 */ 1937 break; 1938 } 1939 if (c >= 0x80) 1940 { 1941 if (!isUniAlpha(c)) 1942 continue; 1943 } 1944 else if (!(isalpha(c) || c == '_' || c == '\n')) 1945 continue; 1946 i = oi; 1947 break; 1948 } 1949 return i; 1950 } 1951 1952 /************************************************ 1953 * Scan forward past end of identifier. 1954 */ 1955 extern (C++) size_t skippastident(OutBuffer* buf, size_t i) 1956 { 1957 const slice = buf.peekSlice(); 1958 while (i < slice.length) 1959 { 1960 dchar c; 1961 size_t oi = i; 1962 if (utf_decodeChar(slice.ptr, slice.length, i, c)) 1963 { 1964 /* Ignore UTF errors, but still consume input 1965 */ 1966 break; 1967 } 1968 if (c >= 0x80) 1969 { 1970 if (isUniAlpha(c)) 1971 continue; 1972 } 1973 else if (isalnum(c) || c == '_') 1974 continue; 1975 i = oi; 1976 break; 1977 } 1978 return i; 1979 } 1980 1981 /************************************************ 1982 * Scan forward past URL starting at i. 1983 * We don't want to highlight parts of a URL. 1984 * Returns: 1985 * i if not a URL 1986 * index just past it if it is a URL 1987 */ 1988 extern (C++) size_t skippastURL(OutBuffer* buf, size_t i) 1989 { 1990 const slice = buf.peekSlice()[i .. $]; 1991 size_t j; 1992 bool sawdot = false; 1993 if (slice.length > 7 && Port.memicmp(slice.ptr, "http://", 7) == 0) 1994 { 1995 j = 7; 1996 } 1997 else if (slice.length > 8 && Port.memicmp(slice.ptr, "https://", 8) == 0) 1998 { 1999 j = 8; 2000 } 2001 else 2002 goto Lno; 2003 for (; j < slice.length; j++) 2004 { 2005 const c = slice[j]; 2006 if (isalnum(c)) 2007 continue; 2008 if (c == '-' || c == '_' || c == '?' || c == '=' || c == '%' || 2009 c == '&' || c == '/' || c == '+' || c == '#' || c == '~') 2010 continue; 2011 if (c == '.') 2012 { 2013 sawdot = true; 2014 continue; 2015 } 2016 break; 2017 } 2018 if (sawdot) 2019 return i + j; 2020 Lno: 2021 return i; 2022 } 2023 2024 /**************************************************** 2025 */ 2026 extern (C++) bool isIdentifier(Dsymbols* a, const(char)* p, size_t len) 2027 { 2028 for (size_t i = 0; i < a.dim; i++) 2029 { 2030 const(char)* s = (*a)[i].ident.toChars(); 2031 if (cmp(s, p, len) == 0) 2032 return true; 2033 } 2034 return false; 2035 } 2036 2037 /**************************************************** 2038 */ 2039 extern (C++) bool isKeyword(const(char)* p, size_t len) 2040 { 2041 immutable string[3] table = ["true", "false", "null"]; 2042 foreach (s; table) 2043 { 2044 if (cmp(s.ptr, p, len) == 0) 2045 return true; 2046 } 2047 return false; 2048 } 2049 2050 /**************************************************** 2051 */ 2052 extern (C++) TypeFunction isTypeFunction(Dsymbol s) 2053 { 2054 FuncDeclaration f = s.isFuncDeclaration(); 2055 /* f->type may be NULL for template members. 2056 */ 2057 if (f && f.type) 2058 { 2059 Type t = f.originalType ? f.originalType : f.type; 2060 if (t.ty == Tfunction) 2061 return cast(TypeFunction)t; 2062 } 2063 return null; 2064 } 2065 2066 /**************************************************** 2067 */ 2068 private Parameter isFunctionParameter(Dsymbol s, const(char)* p, size_t len) 2069 { 2070 TypeFunction tf = isTypeFunction(s); 2071 if (tf && tf.parameters) 2072 { 2073 for (size_t k = 0; k < tf.parameters.dim; k++) 2074 { 2075 Parameter fparam = (*tf.parameters)[k]; 2076 if (fparam.ident && cmp(fparam.ident.toChars(), p, len) == 0) 2077 { 2078 return fparam; 2079 } 2080 } 2081 } 2082 return null; 2083 } 2084 2085 /**************************************************** 2086 */ 2087 extern (C++) Parameter isFunctionParameter(Dsymbols* a, const(char)* p, size_t len) 2088 { 2089 for (size_t i = 0; i < a.dim; i++) 2090 { 2091 Parameter fparam = isFunctionParameter((*a)[i], p, len); 2092 if (fparam) 2093 { 2094 return fparam; 2095 } 2096 } 2097 return null; 2098 } 2099 2100 /**************************************************** 2101 */ 2102 private Parameter isEponymousFunctionParameter(Dsymbols *a, const(char) *p, size_t len) 2103 { 2104 for (size_t i = 0; i < a.dim; i++) 2105 { 2106 TemplateDeclaration td = (*a)[i].isTemplateDeclaration(); 2107 if (td && td.onemember) 2108 { 2109 /* Case 1: we refer to a template declaration inside the template 2110 2111 /// ...ddoc... 2112 template case1(T) { 2113 void case1(R)() {} 2114 } 2115 */ 2116 td = td.onemember.isTemplateDeclaration(); 2117 } 2118 if (!td) 2119 { 2120 /* Case 2: we're an alias to a template declaration 2121 2122 /// ...ddoc... 2123 alias case2 = case1!int; 2124 */ 2125 AliasDeclaration ad = (*a)[i].isAliasDeclaration(); 2126 if (ad && ad.aliassym) 2127 { 2128 td = ad.aliassym.isTemplateDeclaration(); 2129 } 2130 } 2131 while (td) 2132 { 2133 Dsymbol sym = getEponymousMember(td); 2134 if (sym) 2135 { 2136 Parameter fparam = isFunctionParameter(sym, p, len); 2137 if (fparam) 2138 { 2139 return fparam; 2140 } 2141 } 2142 td = td.overnext; 2143 } 2144 } 2145 return null; 2146 } 2147 2148 /**************************************************** 2149 */ 2150 extern (C++) TemplateParameter isTemplateParameter(Dsymbols* a, const(char)* p, size_t len) 2151 { 2152 for (size_t i = 0; i < a.dim; i++) 2153 { 2154 TemplateDeclaration td = (*a)[i].isTemplateDeclaration(); 2155 // Check for the parent, if the current symbol is not a template declaration. 2156 if (!td) 2157 td = getEponymousParent((*a)[i]); 2158 if (td && td.origParameters) 2159 { 2160 for (size_t k = 0; k < td.origParameters.dim; k++) 2161 { 2162 TemplateParameter tp = (*td.origParameters)[k]; 2163 if (tp.ident && cmp(tp.ident.toChars(), p, len) == 0) 2164 { 2165 return tp; 2166 } 2167 } 2168 } 2169 } 2170 return null; 2171 } 2172 2173 /**************************************************** 2174 * Return true if str is a reserved symbol name 2175 * that starts with a double underscore. 2176 */ 2177 extern (C++) bool isReservedName(const(char)* str, size_t len) 2178 { 2179 immutable string[] table = 2180 [ 2181 "__ctor", 2182 "__dtor", 2183 "__postblit", 2184 "__invariant", 2185 "__unitTest", 2186 "__require", 2187 "__ensure", 2188 "__dollar", 2189 "__ctfe", 2190 "__withSym", 2191 "__result", 2192 "__returnLabel", 2193 "__vptr", 2194 "__monitor", 2195 "__gate", 2196 "__xopEquals", 2197 "__xopCmp", 2198 "__LINE__", 2199 "__FILE__", 2200 "__MODULE__", 2201 "__FUNCTION__", 2202 "__PRETTY_FUNCTION__", 2203 "__DATE__", 2204 "__TIME__", 2205 "__TIMESTAMP__", 2206 "__VENDOR__", 2207 "__VERSION__", 2208 "__EOF__", 2209 "__LOCAL_SIZE", 2210 "___tls_get_addr", 2211 "__entrypoint", 2212 ]; 2213 foreach (s; table) 2214 { 2215 if (cmp(s.ptr, str, len) == 0) 2216 return true; 2217 } 2218 return false; 2219 } 2220 2221 /************************************************** 2222 * Highlight text section. 2223 */ 2224 extern (C++) void highlightText(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset) 2225 { 2226 Dsymbol s = a.dim ? (*a)[0] : null; // test 2227 //printf("highlightText()\n"); 2228 int leadingBlank = 1; 2229 int inCode = 0; 2230 int inBacktick = 0; 2231 //int inComment = 0; // in <!-- ... --> comment 2232 size_t iCodeStart = 0; // start of code section 2233 size_t codeIndent = 0; 2234 size_t iLineStart = offset; 2235 for (size_t i = offset; i < buf.offset; i++) 2236 { 2237 char c = buf.data[i]; 2238 Lcont: 2239 switch (c) 2240 { 2241 case ' ': 2242 case '\t': 2243 break; 2244 case '\n': 2245 if (inBacktick) 2246 { 2247 // `inline code` is only valid if contained on a single line 2248 // otherwise, the backticks should be output literally. 2249 // 2250 // This lets things like `output from the linker' display 2251 // unmolested while keeping the feature consistent with GitHub. 2252 inBacktick = false; 2253 inCode = false; // the backtick also assumes we're in code 2254 // Nothing else is necessary since the DDOC_BACKQUOTED macro is 2255 // inserted lazily at the close quote, meaning the rest of the 2256 // text is already OK. 2257 } 2258 if (!sc._module.isDocFile && !inCode && i == iLineStart && i + 1 < buf.offset) // if "\n\n" 2259 { 2260 immutable blankline = "$(DDOC_BLANKLINE)\n"; 2261 i = buf.insert(i, blankline); 2262 } 2263 leadingBlank = 1; 2264 iLineStart = i + 1; 2265 break; 2266 case '<': 2267 { 2268 leadingBlank = 0; 2269 if (inCode) 2270 break; 2271 const slice = buf.peekSlice(); 2272 auto p = &slice[i]; 2273 const se = sc._module.escapetable.escapeChar('<'); 2274 if (se && strcmp(se, "<") == 0) 2275 { 2276 // Generating HTML 2277 // Skip over comments 2278 if (p[1] == '!' && p[2] == '-' && p[3] == '-') 2279 { 2280 size_t j = i + 4; 2281 p += 4; 2282 while (1) 2283 { 2284 if (j == slice.length) 2285 goto L1; 2286 if (p[0] == '-' && p[1] == '-' && p[2] == '>') 2287 { 2288 i = j + 2; // place on closing '>' 2289 break; 2290 } 2291 j++; 2292 p++; 2293 } 2294 break; 2295 } 2296 // Skip over HTML tag 2297 if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2]))) 2298 { 2299 size_t j = i + 2; 2300 p += 2; 2301 while (1) 2302 { 2303 if (j == slice.length) 2304 break; 2305 if (p[0] == '>') 2306 { 2307 i = j; // place on closing '>' 2308 break; 2309 } 2310 j++; 2311 p++; 2312 } 2313 break; 2314 } 2315 } 2316 L1: 2317 // Replace '<' with '<' character entity 2318 if (se) 2319 { 2320 const len = strlen(se); 2321 buf.remove(i, 1); 2322 i = buf.insert(i, se, len); 2323 i--; // point to ';' 2324 } 2325 break; 2326 } 2327 case '>': 2328 { 2329 leadingBlank = 0; 2330 if (inCode) 2331 break; 2332 // Replace '>' with '>' character entity 2333 const(char)* se = sc._module.escapetable.escapeChar('>'); 2334 if (se) 2335 { 2336 size_t len = strlen(se); 2337 buf.remove(i, 1); 2338 i = buf.insert(i, se, len); 2339 i--; // point to ';' 2340 } 2341 break; 2342 } 2343 case '&': 2344 { 2345 leadingBlank = 0; 2346 if (inCode) 2347 break; 2348 char* p = cast(char*)&buf.data[i]; 2349 if (p[1] == '#' || isalpha(p[1])) 2350 break; 2351 // already a character entity 2352 // Replace '&' with '&' character entity 2353 const(char)* se = sc._module.escapetable.escapeChar('&'); 2354 if (se) 2355 { 2356 size_t len = strlen(se); 2357 buf.remove(i, 1); 2358 i = buf.insert(i, se, len); 2359 i--; // point to ';' 2360 } 2361 break; 2362 } 2363 case '`': 2364 { 2365 if (inBacktick) 2366 { 2367 inBacktick = 0; 2368 inCode = 0; 2369 OutBuffer codebuf; 2370 codebuf.write(buf.peekSlice().ptr + iCodeStart + 1, i - (iCodeStart + 1)); 2371 // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL 2372 highlightCode(sc, a, &codebuf, 0); 2373 buf.remove(iCodeStart, i - iCodeStart + 1); // also trimming off the current ` 2374 immutable pre = "$(DDOC_BACKQUOTED "; 2375 i = buf.insert(iCodeStart, pre); 2376 i = buf.insert(i, codebuf.peekSlice()); 2377 i = buf.insert(i, ")"); 2378 i--; // point to the ending ) so when the for loop does i++, it will see the next character 2379 break; 2380 } 2381 if (inCode) 2382 break; 2383 inCode = 1; 2384 inBacktick = 1; 2385 codeIndent = 0; // inline code is not indented 2386 // All we do here is set the code flags and record 2387 // the location. The macro will be inserted lazily 2388 // so we can easily cancel the inBacktick if we come 2389 // across a newline character. 2390 iCodeStart = i; 2391 break; 2392 } 2393 case '-': 2394 /* A line beginning with --- delimits a code section. 2395 * inCode tells us if it is start or end of a code section. 2396 */ 2397 if (leadingBlank) 2398 { 2399 size_t istart = i; 2400 size_t eollen = 0; 2401 leadingBlank = 0; 2402 while (1) 2403 { 2404 ++i; 2405 if (i >= buf.offset) 2406 break; 2407 c = buf.data[i]; 2408 if (c == '\n') 2409 { 2410 eollen = 1; 2411 break; 2412 } 2413 if (c == '\r') 2414 { 2415 eollen = 1; 2416 if (i + 1 >= buf.offset) 2417 break; 2418 if (buf.data[i + 1] == '\n') 2419 { 2420 eollen = 2; 2421 break; 2422 } 2423 } 2424 // BUG: handle UTF PS and LS too 2425 if (c != '-') 2426 goto Lcont; 2427 } 2428 if (i - istart < 3) 2429 goto Lcont; 2430 // We have the start/end of a code section 2431 // Remove the entire --- line, including blanks and \n 2432 buf.remove(iLineStart, i - iLineStart + eollen); 2433 i = iLineStart; 2434 if (inCode && (i <= iCodeStart)) 2435 { 2436 // Empty code section, just remove it completely. 2437 inCode = 0; 2438 break; 2439 } 2440 if (inCode) 2441 { 2442 inCode = 0; 2443 // The code section is from iCodeStart to i 2444 OutBuffer codebuf; 2445 codebuf.write(buf.data + iCodeStart, i - iCodeStart); 2446 codebuf.writeByte(0); 2447 // Remove leading indentations from all lines 2448 bool lineStart = true; 2449 char* endp = cast(char*)codebuf.data + codebuf.offset; 2450 for (char* p = cast(char*)codebuf.data; p < endp;) 2451 { 2452 if (lineStart) 2453 { 2454 size_t j = codeIndent; 2455 char* q = p; 2456 while (j-- > 0 && q < endp && isIndentWS(q)) 2457 ++q; 2458 codebuf.remove(p - cast(char*)codebuf.data, q - p); 2459 assert(cast(char*)codebuf.data <= p); 2460 assert(p < cast(char*)codebuf.data + codebuf.offset); 2461 lineStart = false; 2462 endp = cast(char*)codebuf.data + codebuf.offset; // update 2463 continue; 2464 } 2465 if (*p == '\n') 2466 lineStart = true; 2467 ++p; 2468 } 2469 highlightCode2(sc, a, &codebuf, 0); 2470 buf.remove(iCodeStart, i - iCodeStart); 2471 i = buf.insert(iCodeStart, codebuf.peekSlice()); 2472 i = buf.insert(i, ")\n"); 2473 i -= 2; // in next loop, c should be '\n' 2474 } 2475 else 2476 { 2477 static __gshared const(char)* d_code = "$(D_CODE "; 2478 inCode = 1; 2479 codeIndent = istart - iLineStart; // save indent count 2480 i = buf.insert(i, d_code, strlen(d_code)); 2481 iCodeStart = i; 2482 i--; // place i on > 2483 leadingBlank = true; 2484 } 2485 } 2486 break; 2487 default: 2488 leadingBlank = 0; 2489 if (sc._module.isDocFile || inCode) 2490 break; 2491 const start = cast(char*)buf.data + i; 2492 if (isIdStart(start)) 2493 { 2494 size_t j = skippastident(buf, i); 2495 if (i < j) 2496 { 2497 size_t k = skippastURL(buf, i); 2498 if (i < k) 2499 { 2500 i = k - 1; 2501 break; 2502 } 2503 } 2504 else 2505 break; 2506 size_t len = j - i; 2507 // leading '_' means no highlight unless it's a reserved symbol name 2508 if (c == '_' && (i == 0 || !isdigit(*(start - 1))) && (i == buf.offset - 1 || !isReservedName(start, len))) 2509 { 2510 buf.remove(i, 1); 2511 i = j - 1; 2512 break; 2513 } 2514 if (isIdentifier(a, start, len)) 2515 { 2516 i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; 2517 break; 2518 } 2519 if (isKeyword(start, len)) 2520 { 2521 i = buf.bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1; 2522 break; 2523 } 2524 if (isFunctionParameter(a, start, len)) 2525 { 2526 //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); 2527 i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1; 2528 break; 2529 } 2530 i = j - 1; 2531 } 2532 break; 2533 } 2534 } 2535 if (inCode) 2536 error(s ? s.loc : Loc(), "unmatched --- in DDoc comment"); 2537 } 2538 2539 /************************************************** 2540 * Highlight code for DDOC section. 2541 */ 2542 extern (C++) void highlightCode(Scope* sc, Dsymbol s, OutBuffer* buf, size_t offset) 2543 { 2544 //printf("highlightCode(s = %s '%s')\n", s->kind(), s->toChars()); 2545 OutBuffer ancbuf; 2546 emitAnchor(&ancbuf, s, sc); 2547 buf.insert(offset, ancbuf.peekSlice()); 2548 offset += ancbuf.offset; 2549 Dsymbols a; 2550 a.push(s); 2551 highlightCode(sc, &a, buf, offset); 2552 } 2553 2554 /**************************************************** 2555 */ 2556 extern (C++) void highlightCode(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset) 2557 { 2558 //printf("highlightCode(a = '%s')\n", a->toChars()); 2559 bool resolvedTemplateParameters = false; 2560 2561 for (size_t i = offset; i < buf.offset; i++) 2562 { 2563 char c = buf.data[i]; 2564 const(char)* se = sc._module.escapetable.escapeChar(c); 2565 if (se) 2566 { 2567 size_t len = strlen(se); 2568 buf.remove(i, 1); 2569 i = buf.insert(i, se, len); 2570 i--; // point to ';' 2571 continue; 2572 } 2573 char* start = cast(char*)buf.data + i; 2574 if (isIdStart(start)) 2575 { 2576 size_t j = skippastident(buf, i); 2577 if (i < j) 2578 { 2579 size_t len = j - i; 2580 if (isIdentifier(a, start, len)) 2581 { 2582 i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; 2583 continue; 2584 } 2585 if (isFunctionParameter(a, start, len)) 2586 { 2587 //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); 2588 i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1; 2589 continue; 2590 } 2591 i = j - 1; 2592 } 2593 } 2594 else if (!resolvedTemplateParameters) 2595 { 2596 size_t previ = i; 2597 2598 // hunt for template declarations: 2599 foreach (symi; 0 .. a.dim) 2600 { 2601 FuncDeclaration fd = (*a)[symi].isFuncDeclaration(); 2602 2603 if (!fd || !fd.parent || !fd.parent.isTemplateDeclaration()) 2604 { 2605 continue; 2606 } 2607 2608 TemplateDeclaration td = fd.parent.isTemplateDeclaration(); 2609 2610 // build the template parameters 2611 Array!(size_t) paramLens; 2612 paramLens.reserve(td.parameters.dim); 2613 2614 OutBuffer parametersBuf; 2615 HdrGenState hgs; 2616 2617 parametersBuf.writeByte('('); 2618 2619 foreach (parami; 0 .. td.parameters.dim) 2620 { 2621 TemplateParameter tp = (*td.parameters)[parami]; 2622 2623 if (parami) 2624 parametersBuf.writestring(", "); 2625 2626 size_t lastOffset = parametersBuf.offset; 2627 2628 .toCBuffer(tp, ¶metersBuf, &hgs); 2629 2630 paramLens[parami] = parametersBuf.offset - lastOffset; 2631 } 2632 parametersBuf.writeByte(')'); 2633 2634 const templateParams = parametersBuf.peekString(); 2635 const templateParamsLen = parametersBuf.peekSlice().length; 2636 2637 //printf("templateDecl: %s\ntemplateParams: %s\nstart: %s\n", td.toChars(), templateParams, start); 2638 2639 if (cmp(templateParams, start, templateParamsLen) == 0) 2640 { 2641 immutable templateParamListMacro = "$(DDOC_TEMPLATE_PARAM_LIST "; 2642 buf.bracket(i, templateParamListMacro.ptr, i + templateParamsLen, ")"); 2643 2644 // We have the parameter list. While we're here we might 2645 // as well wrap the parameters themselves as well 2646 2647 // + 1 here to take into account the opening paren of the 2648 // template param list 2649 i += templateParamListMacro.length + 1; 2650 2651 foreach (const len; paramLens) 2652 { 2653 i = buf.bracket(i, "$(DDOC_TEMPLATE_PARAM ", i + len, ")"); 2654 // increment two here for space + comma 2655 i += 2; 2656 } 2657 2658 resolvedTemplateParameters = true; 2659 // reset i to be positioned back before we found the template 2660 // param list this assures that anything within the template 2661 // param list that needs to be escaped or otherwise altered 2662 // has an opportunity for that to happen outside of this context 2663 i = previ; 2664 2665 continue; 2666 } 2667 } 2668 } 2669 } 2670 } 2671 2672 /**************************************** 2673 */ 2674 extern (C++) void highlightCode3(Scope* sc, OutBuffer* buf, const(char)* p, const(char)* pend) 2675 { 2676 for (; p < pend; p++) 2677 { 2678 const(char)* s = sc._module.escapetable.escapeChar(*p); 2679 if (s) 2680 buf.writestring(s); 2681 else 2682 buf.writeByte(*p); 2683 } 2684 } 2685 2686 /************************************************** 2687 * Highlight code for CODE section. 2688 */ 2689 extern (C++) void highlightCode2(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset) 2690 { 2691 uint errorsave = global.errors; 2692 scope Lexer lex = new Lexer(null, cast(char*)buf.data, 0, buf.offset - 1, 0, 1); 2693 OutBuffer res; 2694 const(char)* lastp = cast(char*)buf.data; 2695 //printf("highlightCode2('%.*s')\n", buf->offset - 1, buf->data); 2696 res.reserve(buf.offset); 2697 while (1) 2698 { 2699 Token tok; 2700 lex.scan(&tok); 2701 highlightCode3(sc, &res, lastp, tok.ptr); 2702 const(char)* highlight = null; 2703 switch (tok.value) 2704 { 2705 case TOKidentifier: 2706 { 2707 if (!sc) 2708 break; 2709 size_t len = lex.p - tok.ptr; 2710 if (isIdentifier(a, tok.ptr, len)) 2711 { 2712 highlight = "$(D_PSYMBOL "; 2713 break; 2714 } 2715 if (isFunctionParameter(a, tok.ptr, len)) 2716 { 2717 //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); 2718 highlight = "$(D_PARAM "; 2719 break; 2720 } 2721 break; 2722 } 2723 case TOKcomment: 2724 highlight = "$(D_COMMENT "; 2725 break; 2726 case TOKstring: 2727 highlight = "$(D_STRING "; 2728 break; 2729 default: 2730 if (tok.isKeyword()) 2731 highlight = "$(D_KEYWORD "; 2732 break; 2733 } 2734 if (highlight) 2735 { 2736 res.writestring(highlight); 2737 size_t o = res.offset; 2738 highlightCode3(sc, &res, tok.ptr, lex.p); 2739 if (tok.value == TOKcomment || tok.value == TOKstring) 2740 escapeDdocString(&res, o); // Bugzilla 7656, 7715, and 10519 2741 res.writeByte(')'); 2742 } 2743 else 2744 highlightCode3(sc, &res, tok.ptr, lex.p); 2745 if (tok.value == TOKeof) 2746 break; 2747 lastp = lex.p; 2748 } 2749 buf.setsize(offset); 2750 buf.write(&res); 2751 global.errors = errorsave; 2752 } 2753 2754 /**************************************** 2755 * Determine if p points to the start of a "..." parameter identifier. 2756 */ 2757 extern (C++) bool isCVariadicArg(const(char)* p, size_t len) 2758 { 2759 return len >= 3 && cmp("...", p, 3) == 0; 2760 } 2761 2762 /**************************************** 2763 * Determine if p points to the start of an identifier. 2764 */ 2765 extern (C++) bool isIdStart(const(char)* p) 2766 { 2767 dchar c = *p; 2768 if (isalpha(c) || c == '_') 2769 return true; 2770 if (c >= 0x80) 2771 { 2772 size_t i = 0; 2773 if (utf_decodeChar(p, 4, i, c)) 2774 return false; // ignore errors 2775 if (isUniAlpha(c)) 2776 return true; 2777 } 2778 return false; 2779 } 2780 2781 /**************************************** 2782 * Determine if p points to the rest of an identifier. 2783 */ 2784 extern (C++) bool isIdTail(const(char)* p) 2785 { 2786 dchar c = *p; 2787 if (isalnum(c) || c == '_') 2788 return true; 2789 if (c >= 0x80) 2790 { 2791 size_t i = 0; 2792 if (utf_decodeChar(p, 4, i, c)) 2793 return false; // ignore errors 2794 if (isUniAlpha(c)) 2795 return true; 2796 } 2797 return false; 2798 } 2799 2800 /**************************************** 2801 * Determine if p points to the indentation space. 2802 */ 2803 extern (C++) bool isIndentWS(const(char)* p) 2804 { 2805 return (*p == ' ') || (*p == '\t'); 2806 } 2807 2808 /***************************************** 2809 * Return number of bytes in UTF character. 2810 */ 2811 extern (C++) int utfStride(const(char)* p) 2812 { 2813 dchar c = *p; 2814 if (c < 0x80) 2815 return 1; 2816 size_t i = 0; 2817 utf_decodeChar(p, 4, i, c); // ignore errors, but still consume input 2818 return cast(int)i; 2819 }