1 /// Author: Aziz Köksal 2 /// License: GPL3 3 /// $(Maturity high) 4 module dil.doc.DDocEmitter; 5 6 import dil.doc.Parser, 7 dil.doc.Macro, 8 dil.doc.Doc; 9 import dil.ast.DefaultVisitor, 10 dil.ast.ASTPrinter, 11 dil.ast.Node, 12 dil.ast.Declarations, 13 dil.ast.Statements, 14 dil.ast.Expressions, 15 dil.ast.Parameters, 16 dil.ast.Types; 17 import dil.lexer.Token, 18 dil.lexer.Funcs; 19 import dil.semantic.Module; 20 import dil.i18n.Messages; 21 import dil.Highlighter, 22 dil.Diagnostics, 23 dil.SourceText, 24 dil.Enums, 25 dil.String, 26 dil.Array; 27 import common; 28 29 /// Traverses the syntax tree and writes DDoc macros to a string buffer. 30 abstract class DDocEmitter : DefaultVisitor2 31 { 32 CharArray text; /// The buffer that is written to. 33 bool includeUndocumented; /// Include undocumented symbols? 34 bool includePrivate; /// Include symbols with private protection? 35 MacroTable mtable; /// The macro table. 36 Module modul; /// The module. 37 Highlighter tokenHL; /// The token highlighter. 38 Diagnostics reportDiag; /// Collects problem messages. 39 ASTPrinter astPrinter; /// Used to print Types/Expressions/etc. 40 /// Counts code examples in comments. 41 /// This is used to make the code lines targetable in HTML. 42 uint codeExamplesCounter; 43 44 /// Constructs a DDocEmitter object. 45 /// Params: 46 /// modul = The module to generate text for. 47 /// mtable = The macro table. 48 /// includeUndocumented = Whether to include undocumented symbols. 49 /// includePrivate = Whether to include private symbols. 50 /// tokenHL = Used to highlight code sections. 51 this(Module modul, MacroTable mtable, 52 bool includeUndocumented, bool includePrivate, 53 Diagnostics reportDiag, Highlighter tokenHL) 54 { 55 this.mtable = mtable; 56 this.includeUndocumented = includeUndocumented; 57 this.includePrivate = includePrivate; 58 this.modul = modul; 59 this.tokenHL = tokenHL; 60 this.reportDiag = reportDiag; 61 this.astPrinter = new ASTPrinter(true, modul.cc); 62 } 63 64 /// Entry method. 65 char[] emit() 66 { 67 if (isDDocFile(modul)) 68 { // The module is actually a DDoc text file. 69 auto c = DDocUtils.getDDocComment(getDDocText(modul)); 70 foreach (s; c.sections) 71 if (s.Is("macros")) 72 { // Declare the macros in this section. 73 auto ms = new MacrosSection(s.name, s.text); 74 mtable.insert(ms.macroNames, ms.macroTexts); 75 } 76 else 77 write(s.wholeText); 78 return text[]; 79 } 80 81 // Handle as a normal D module with declarations: 82 83 // Initialize the root symbol in the symbol tree. 84 auto lexer = modul.parser.lexer; 85 symbolTree[0] = new DocSymbol(modul.moduleName, modul.getFQN(), K.Module, 86 null, 87 locationOf(lexer.firstToken), 88 locationOf(lexer.lastToken) 89 ); // Root symbol. 90 91 if (auto d = modul.moduleDecl) 92 if (ddoc(d)) 93 { 94 if (auto copyright = cmnt.takeCopyright()) 95 mtable.insert("COPYRIGHT", copyright.text); 96 writeComment(); 97 } 98 // Start traversing the tree and emitting macro text. 99 MEMBERS("MODULE", "", modul.root); 100 return text[]; 101 } 102 103 /// Returns the location of t. 104 Location locationOf(Token* t, cstring filePath = null) 105 { 106 return t.getRealLocation(filePath ? filePath : modul.filePath); 107 } 108 109 /// Reports an undocumented symbol. 110 void reportUndocumented() 111 { 112 if (reportDiag is null) 113 return; 114 auto loc = locationOf(currentDecl.begin, modul.getFQN()); 115 reportDDocProblem(loc, DDocProblem.Kind.UndocumentedSymbol, 116 MID.UndocumentedSymbol); 117 } 118 119 /// Reports an empty comment. 120 void reportEmptyComment() 121 { 122 if (reportDiag is null || !this.cmnt.isEmpty()) 123 return; 124 auto loc = locationOf(currentDecl.begin, modul.getFQN()); 125 reportDDocProblem(loc, DDocProblem.Kind.EmptyComment, MID.EmptyDDocComment); 126 } 127 128 /// Reports a problem. 129 void reportDDocProblem(Location loc, DDocProblem.Kind kind, MID mid, ...) 130 { 131 reportDiag ~= new DDocProblem(loc, kind, 132 reportDiag.formatMsg(mid, _arguments, _argptr)); 133 } 134 135 /// Reports a missing params section or undocumented parameters. 136 void reportParameters(Parameters params) 137 { 138 if (reportDiag is null || cmntIsDitto || params.items.length == 0) 139 return; 140 Token*[] paramNames; 141 foreach (param; params.items) 142 if (param.hasName) 143 paramNames ~= param.name; 144 reportParameters(params.begin, paramNames); 145 } 146 147 /// ditto 148 void reportParameters(TemplateParameters params) 149 { 150 if (reportDiag is null || cmntIsDitto || params.items.length == 0) 151 return; 152 Token*[] paramNames; 153 foreach (param; params.items) 154 paramNames ~= param.name; 155 reportParameters(params.begin, paramNames); 156 } 157 158 /// Params: 159 /// paramsBegin = The left parenthesis of the parameter list. 160 /// params = The identifier tokens of the parameter names. 161 void reportParameters(Token* paramsBegin, Token*[] params) 162 { 163 assert(currentDecl !is null); 164 // TODO: exclude some functions? like "new"? 165 // Search for the params section. 166 Section paramsSection; 167 foreach (s; this.cmnt.sections) 168 if (s.Is("params")) 169 paramsSection = s; 170 if (paramsSection is null) 171 { 172 auto loc = locationOf(paramsBegin, modul.getFQN()); 173 reportDDocProblem(loc, DDocProblem.Kind.NoParamsSection, 174 MID.MissingParamsSection); 175 return; 176 } 177 // Search for undocumented parameters. 178 bool[hash_t] documentedParams; 179 auto ps = new ParamsSection(paramsSection.name, paramsSection.text); 180 foreach (name; ps.paramNames) // Create set of documented parameters. 181 documentedParams[hashOf(name)] = true; 182 foreach (param; params) // Find undocumented parameters. 183 if (!(hashOf(param.ident.str) in documentedParams)) 184 { 185 auto loc = locationOf(param, modul.getFQN()); 186 reportDDocProblem(loc, DDocProblem.Kind.UndocumentedParam, 187 MID.UndocumentedParam, param); 188 } 189 } 190 191 /// Returns true if the source text starts with "Ddoc\n" (ignores letter case.) 192 static bool isDDocFile(Module mod) 193 { 194 auto text = mod.sourceText.text(); 195 if (text.length >= "ddoc\n".length && // Check for minimum length. 196 String(text[0..4]).ieql("ddoc") && // Check first four characters. 197 isNewline(text.ptr + 4)) // Check for a newline. 198 return true; 199 return false; 200 } 201 202 /// Returns the DDoc text of this module. 203 static cstring getDDocText(Module mod) 204 { 205 auto text = mod.sourceText.text(); 206 auto p = text.ptr + "ddoc".length; 207 if (scanNewline(p)) // Skip the newline. 208 // Exclude preceding "Ddoc\n". 209 return text[p-text.ptr .. $]; 210 return null; 211 } 212 /+ 213 char[] textSpan(Token* left, Token* right) 214 { 215 //assert(left && right && (left.end <= right.start || left is right)); 216 //char[] result; 217 //TODO: filter out whitespace tokens. 218 return Token.textSpan(left, right); 219 } 220 +/ 221 /// The current declaration. 222 Node currentDecl; 223 224 /// The template parameters of the current declaration. 225 TemplateParameters currentTParams; 226 227 /// Reflects the fully qualified name of the current symbol's parent. 228 cstring parentFQN; 229 /// Counts symbols with the same FQN. 230 /// This is useful for anchor names that require unique strings. 231 uint[hash_t] fqnCount; 232 233 /// Appends to parentFQN. 234 void pushFQN(cstring name) 235 { 236 if (parentFQN.length) 237 parentFQN ~= "."; 238 parentFQN ~= name; 239 240 auto pfqn = hashOf(parentFQN) in fqnCount; 241 uint count = pfqn ? *pfqn : 0; 242 if (count > 1) // Start adding suffixes with 2. 243 parentFQN ~= ":" ~ itoa(count); 244 } 245 246 /// Returns a unique, identifying string for the current symbol. 247 cstring getSymbolFQN(cstring name) 248 { 249 char[] fqn = parentFQN.dup; 250 if (fqn.length) 251 fqn ~= "."; 252 fqn ~= name; 253 254 uint count; 255 auto hash = hashOf(fqn); 256 auto pfqn = hash in fqnCount; 257 if (pfqn) 258 count = (*pfqn += 1); // Update counter. 259 else 260 fqnCount[hash] = 1; // Start counting with 1. 261 262 if (count > 1) // Start adding suffixes with 2. 263 fqn ~= ":" ~ itoa(count); 264 return fqn; 265 } 266 267 DDocComment cmnt; /// Current comment. 268 DDocComment prevCmnt; /// Previous comment in scope. 269 /// An empty comment. Used for undocumented symbols. 270 static DDocComment emptyCmnt; 271 272 /// Initializes the empty comment. 273 static this() 274 { 275 emptyCmnt = new DDocComment(null, null, null); 276 } 277 278 /// Keeps track of previous comments in each scope. 279 scope class DDocScope 280 { 281 DDocComment saved_prevCmnt; 282 bool saved_cmntIsDitto; 283 size_t saved_prevDeclOffset; 284 cstring saved_parentFQN; 285 /// When constructed, variables are saved. 286 /// Params: 287 /// name = The name of the current symbol. 288 this(cstring name) 289 { // Save the previous comment of the parent scope. 290 saved_prevCmnt = this.outer.prevCmnt; 291 saved_cmntIsDitto = this.outer.cmntIsDitto; 292 saved_prevDeclOffset = this.outer.prevDeclOffset; 293 saved_parentFQN = this.outer.parentFQN; 294 pushFQN(name); 295 // Entering a new scope. Clear variables. 296 this.outer.prevCmnt = null; 297 this.outer.cmntIsDitto = false; 298 this.outer.prevDeclOffset = 0; 299 } 300 /// When destructed, variables are restored. 301 ~this() 302 { // Restore the previous comment of the parent scope. 303 this.outer.prevCmnt = saved_prevCmnt; 304 this.outer.cmntIsDitto = saved_cmntIsDitto; 305 this.outer.prevDeclOffset = saved_prevDeclOffset; 306 this.outer.parentFQN = saved_parentFQN; 307 } 308 } 309 310 bool cmntIsDitto; /// True if current comment is "ditto". 311 312 /// Sets some members and returns true if a comment was found. 313 bool ddoc(Declaration node) 314 { 315 if (!includePrivate && node.prot == Protection.Private) 316 return false; 317 this.currentDecl = node; 318 this.cmnt = DDocUtils.getDDocComment(node); 319 if (this.cmnt) 320 { 321 if (this.cmnt.isDitto) // A ditto comment. 322 (this.cmnt = this.prevCmnt), (this.cmntIsDitto = true); 323 else // A normal comment. 324 { 325 reportEmptyComment(); 326 (this.prevCmnt = this.cmnt), (this.cmntIsDitto = false); 327 } 328 } 329 else 330 { 331 reportUndocumented(); 332 if (includeUndocumented) 333 this.cmnt = this.emptyCmnt; // Assign special empty comment. 334 } 335 return this.cmnt !is null; 336 } 337 338 /// List of predefined, special sections. 339 static string[hash_t] specialSections; 340 static this() 341 { 342 foreach (name; ["AUTHORS", "BUGS", "COPYRIGHT", "DATE", "DEPRECATED", 343 "EXAMPLES", "HISTORY", "LICENSE", "RETURNS", "SEE_ALSO", 344 "STANDARDS", "THROWS", "VERSION"] ~ 345 ["AUTHOR"]) // Additions by DIL. 346 specialSections[hashOf(name)] = name; 347 } 348 349 /// Writes the DDoc comment to the text buffer. 350 void writeComment() 351 { 352 auto c = this.cmnt; 353 assert(c !is null); 354 write("\1DDOC_SECTIONS "); 355 foreach (s; c.sections) 356 { 357 if (s is c.summary) 358 write("\n\1DDOC_SUMMARY "); 359 else if (s is c.description) 360 write("\n\1DDOC_DESCRIPTION "); 361 else if (auto name = 362 String(s.name).toupper().hashOf() in specialSections) 363 write("\n\1DDOC_", *name, " "); 364 else if (s.Is("params")) 365 { // Process parameters section. 366 auto ps = new ParamsSection(s.name, s.text); 367 write("\n\1DDOC_PARAMS "); 368 foreach (i, paramName; ps.paramNames) 369 write("\n\1DDOC_PARAM_ROW \1DDOC_PARAM_ID \1DDOC_PARAM ", 370 paramName, "\2\2\1DDOC_PARAM_DESC "), 371 writeCommentText(ps.paramDescs[i]), 372 write("\2\2"); 373 write("\2"); 374 continue; 375 } 376 else if (s.Is("macros")) 377 { // Declare the macros in this section. 378 auto ms = new MacrosSection(s.name, s.text); 379 mtable.insert(ms.macroNames, ms.macroTexts); 380 continue; 381 } 382 else 383 write("\n\1DDOC_SECTION " 384 "\1DDOC_SECTION_H ", s.name.replace('_', ' '), ":\2"); 385 write("\1DIL_CMT "); 386 writeCommentText(s.text); 387 write("\2\2"); 388 } 389 write("\2"); 390 } 391 392 /// Scans the comment text and: 393 /// $(UL 394 /// $(LI skips HTML tags) 395 /// $(LI escapes '<', '>' and '&' with named HTML entities) 396 /// $(LI inserts $(DDOC_BLANKLINE) in place of '\n\n') 397 /// $(LI highlights the tokens in code sections) 398 /// ) 399 void writeCommentText(cstring comment) 400 { // Init pointers for parsing. 401 auto p = comment.ptr; 402 auto lastLineEnd = p; // The position of the last \n seen. 403 auto end = p + comment.length; 404 // Nesting level of macro invocations and the parentheses inside of them. 405 uint level = 0; 406 char[] parens; // Stack of parentheses and markers. 407 408 while (p < end) 409 { 410 switch (*p) 411 { 412 case '$': 413 auto p2 = p+2; 414 if (p2 < end && p[1] == '(' && isIdentifierStart(p2, end)) // IdStart 415 { 416 parens ~= Macro.Marker.Closing; 417 write(Macro.Marker.Opening); // Relace "$(". 418 p = p2; // Skip "$(". 419 } 420 goto default; 421 case '(': 422 if (parens.length) parens ~= ')'; 423 goto default; 424 case ')': 425 if (!parens.length) goto default; 426 write(parens[$-1]); // Append Macro.Marker.Closing or ")". 427 parens.length--; // Pop one char. 428 break; 429 case '<': 430 auto begin = p; 431 p++; 432 if (p+2 < end && *p == '!' && p[1] == '-' && p[2] == '-') // <!-- 433 { 434 p += 2; // Point to 2nd '-'. 435 // Scan to closing "-->". 436 while (++p < end) 437 if (*p == '-' && p+2 < end && p[1] == '-' && p[2] == '>') 438 { 439 p += 3; // Point one past '>'. 440 break; 441 } 442 write(slice(begin, p)); 443 } // <tag ...> or </tag> 444 else if (p < end && (isalpha(*p) || *p == '/')) 445 { 446 while (++p < end && *p != '>') // Skip to closing '>'. 447 {} 448 if (p == end) 449 { // No closing '>' found. 450 p = begin + 1; 451 write("<"); 452 continue; 453 } 454 p++; // Skip '>'. 455 write(slice(begin, p)); 456 } 457 else 458 write("<"); 459 continue; 460 // case '\'': write("'"); break; // ' 461 // case '"': write("""); break; 462 case '>': write(">"); break; 463 case '&': 464 auto entityBegin = p; 465 if (++p < end && (isalpha(*p) || *p == '#')) 466 { 467 if (*p == '#') 468 while (++p < end && isdigit(*p)){} // Numerical entity. 469 else 470 while (++p < end && isalpha(*p)){} // Named entity. 471 if (p < end && *p == ';') { 472 write(slice(entityBegin, ++p)); // Copy valid entity. 473 continue; 474 } 475 p = entityBegin + 1; // Reset. It's not a valid entity. 476 } 477 write("&"); 478 continue; 479 case '\n': 480 if (!(p+1 < end && p[1] == '\n')) 481 { 482 lastLineEnd = p; 483 goto default; 484 } 485 ++p; 486 write("\n\1DDOC_BLANKLINE\2\n"); 487 lastLineEnd = p; 488 break; 489 case '-': 490 if (p+2 < end && p[1] == '-' && p[2] == '-' && 491 isAllSpace(lastLineEnd + 1, p)) 492 { // Found "---" at start of line. 493 while (p < end && *p == '-') // Skip trailing dashes. 494 p++; 495 auto codeBegin = p; 496 while (p < end && isspace(*p)) // Skip whitespace. 497 p++; 498 if (p < end && *p == '\n') // Skip first newline. 499 codeBegin = ++p; 500 // Find closing dashes. 501 while (p < end && !(*p == '-' && p+2 < end && 502 p[1] == '-' && p[2] == '-' && 503 isAllSpace(lastLineEnd + 1, p))) 504 { 505 if (*p == '\n') 506 lastLineEnd = p; 507 p++; 508 } 509 510 // Remove last newline if present. 511 auto codeEnd = p; 512 while (isspace(*--codeEnd)) 513 {} 514 if (*codeEnd != '\n') // Leaving the pointer on '\n' will exclude it. 515 codeEnd++; // Include the non-newline character. 516 if (codeBegin < codeEnd) 517 { // Highlight the extracted source code. 518 cstring codeText = slice(codeBegin, codeEnd); 519 uint lines; // Number of lines in the code text. 520 521 codeExamplesCounter++; // Found a code section. Increment counter. 522 523 // TODO: use a scratch buffer to unindent and to highlight. 524 codeText = DDocUtils.unindentText(codeText); 525 codeText = tokenHL.highlightTokens(codeText, modul.getFQN(), lines); 526 write("\1D_CODE\n\1DIL_CODELINES "); 527 for (uint num = 1; num <= lines; num++) 528 { 529 auto numtxt = itoa(num); 530 auto id = "L"~numtxt~"_ex"~itoa(codeExamplesCounter); 531 write(`<a href="#`, id, `" name="`, id, `">`, numtxt, "</a>\n"); 532 } 533 write("\2,\1DIL_CODETEXT \4", codeText, "\2\n\2"); 534 } 535 while (p < end && *p == '-') // Skip remaining dashes. 536 p++; 537 continue; 538 } 539 goto default; 540 default: 541 write(*p); 542 } 543 p++; 544 } 545 assert(p is end); 546 foreach (c; parens) 547 if (c == Macro.Marker.Closing) // Unclosed macros? 548 write(Macro.Marker.Unclosed); // Add marker for errors. 549 } 550 551 /// Writes a character to the buffer. 552 void write(char c) 553 { 554 text ~= c; 555 } 556 557 /// Writes an array of strings to the text buffer. 558 void write(cstring[] strings...) 559 { 560 foreach (s; strings) 561 text ~= s; 562 } 563 564 /// Writes a Node tree to the buffer. 565 void write(Node n) 566 { 567 astPrinter.print(n); 568 writeHL(astPrinter.tokens); 569 } 570 571 /// Write highlighted tokens to the buffer. 572 void writeHL(Token[] tokens) 573 { 574 tokenHL.highlightTokens(text, tokens, true); 575 } 576 577 /// Writes params to the text buffer. 578 void writeParams(Parameters params) 579 { 580 reportParameters(params); 581 write("\1DIL_PARAMS "); 582 auto item_count = params.items.length; 583 foreach (param; params.items) 584 { 585 if (param.isCVariadic) 586 write("..."); 587 else 588 { 589 // Write storage class(es). 590 auto lastSTC = param.tokenOfLastSTC(); 591 if (lastSTC) // Write storage classes. 592 writeHL(param.begin[0..lastSTC-param.begin]), write(" "); 593 if (param.type) 594 write(param.type); // Write the type. 595 if (param.hasName) 596 write((param.type?" ":""), "\1DDOC_PARAM ", param.nameStr, "\2"); 597 if (param.defValue) 598 write(" = \1DIL_DEFVAL "), write(param.defValue), write("\2"); 599 if (param.isDVariadic) 600 write("..."); 601 } 602 --item_count && (text ~= ", "); // Skip for last item. 603 } 604 write("\2"); 605 } 606 607 /// Writes the current template parameters to the text buffer. 608 void writeTemplateParams() 609 { 610 if (!currentTParams) 611 return; 612 613 void writeSpecDef(Node spec, Node def) 614 { 615 if (spec) write(" : \1DIL_SPEC "), write(spec), write("\2"); 616 if (def) write(" = \1DIL_DEFVAL "), write(def), write("\2"); 617 } 618 619 reportParameters(currentTParams); 620 write("\1DIL_TEMPLATE_PARAMS "); 621 auto item_count = currentTParams.items.length; 622 foreach (tparam; currentTParams.items) 623 { 624 if (auto p = tparam.Is!(TemplateAliasParam)) 625 write("\1DIL_TPALIAS \1DIL_TPID ", p.nameStr, "\2"), 626 writeSpecDef(p.spec, p.def), 627 write("\2"); 628 else if (auto p = tparam.Is!(TemplateTypeParam)) 629 write("\1DIL_TPTYPE \1DIL_TPID ", p.nameStr, "\2"), 630 writeSpecDef(p.specType, p.defType), 631 write("\2"); 632 else if (auto p = tparam.Is!(TemplateTupleParam)) 633 write("\1DIL_TPTUPLE \1DIL_TPID ", p.nameStr, "\2\2"); 634 else if (auto p = tparam.Is!(TemplateValueParam)) 635 write("\1DIL_TPVALUE "), 636 write(p.valueType), 637 write(" \1DIL_TPID ", p.nameStr, "\2"), 638 writeSpecDef(p.specValue, p.defValue), 639 write("\2"); 640 else if (auto p = tparam.Is!(TemplateThisParam)) 641 write("\1DIL_TPTHIS \1DIL_TPID ", p.nameStr, "\2"), 642 writeSpecDef(p.specType, p.defType), 643 write("\2"); 644 --item_count && write(", "); 645 } 646 write("\2"); 647 currentTParams = null; 648 } 649 650 /// Writes bases to the text buffer. 651 void writeInheritanceList(BaseClassType[] bases) 652 { 653 auto item_count = bases.length; 654 if (item_count == 0) 655 return; 656 write(" \1DIL_BASECLASSES "); 657 foreach (base; bases) 658 write("\1DIL_BASECLASS "), write(base), 659 write(--item_count ? "\2, " : "\2"); 660 write("\2"); 661 } 662 663 /// Offset at which to insert a declaration with a "ditto" comment. 664 size_t prevDeclOffset; 665 666 /// Writes a declaration to the text buffer. 667 void DECL(void delegate() dg, Declaration d, bool writeSemicolon = true) 668 { 669 void writeDECL() 670 { 671 write("\n\1DDOC_DECL "); 672 dg(); 673 writeSemicolon && write("\1DIL_SC\2"); 674 writeAttributes(d); 675 write(" \1DIL_SYMEND ", currentSymbolParams, "\2\2"); 676 } 677 678 if (/+includeUndocumented &&+/ this.cmnt is this.emptyCmnt) 679 // Handle undocumented symbols separately. 680 // This way they don't interrupt consolidated declarations (via 'ditto'.) 681 writeDECL(); 682 else if (cmntIsDitto) 683 { // The declaration has a ditto comment. 684 alias offs = prevDeclOffset; 685 assert(offs != 0); 686 auto savedEnd = text[offs..Neg(0)].dup; // Copy text past offset. 687 text.len = offs; 688 writeDECL(); 689 text ~= savedEnd; // Append the snippet. 690 } 691 else 692 { 693 writeDECL(); 694 // Set the offset. At this offset other declarations with a ditto 695 // comment will be inserted, if present. 696 prevDeclOffset = text.len; 697 } 698 } 699 700 /// Wraps the DDOC_DECL_DD macro around the text written by dg(). 701 /// Writes the comment before dg() is called. 702 void DESC(void delegate() dg = null) 703 { 704 auto isEmpty = this.cmnt is this.emptyCmnt; 705 if (cmntIsDitto && !isEmpty) 706 return; // Don't write a description when we have a ditto-comment. 707 write("\n\1DDOC_DECL_DD "); 708 if (isEmpty) 709 write("\1DIL_NOCMNT\2"); 710 else 711 writeComment(); 712 dg && dg(); 713 write("\2"); 714 } 715 716 /// Saves the attributes of the current symbol. 717 cstring currentSymbolParams; 718 719 /// Writes a symbol to the text buffer. 720 /// E.g: $(DIL_SYMBOL scan, Lexer.scan, func, 229, 646); 721 void SYMBOL(cstring name, K kind, Declaration d) 722 { 723 auto kindStr = DocSymbol.kindIDToStr[kind]; 724 auto fqn = getSymbolFQN(name); 725 auto loc = locationOf(d.begin); 726 auto loc_end = locationOf(d.end); 727 storeAttributes(d); 728 addSymbol(name, fqn, kind, loc, loc_end); 729 currentSymbolParams = Format("{}, {}, {}, {}, {}", 730 name, fqn, kindStr, loc.lineNum, loc_end.lineNum); 731 write("\1DIL_SYMBOL ", currentSymbolParams, "\2"); 732 // write("\1DDOC_PSYMBOL ", name, "\2"); // DMD's macro with no info. 733 } 734 735 /// The symbols that will appear in the result document. 736 DocSymbol[hash_t] symbolTree; 737 738 /// Adds a symbol to the symbol tree. 739 void addSymbol(cstring name, cstring fqn, K kind, 740 Location begin, Location end) 741 { 742 auto attrs = currentAttributes.asArray(); 743 auto symbol = new DocSymbol(name, fqn, kind, attrs, begin, end); 744 symbolTree[hashOf(fqn)] = symbol; // Add the symbol itself. 745 symbolTree[hashOf(parentFQN)].members ~= symbol; // Append as a child to parent. 746 } 747 748 /// Wraps the DDOC_kind_MEMBERS macro around the text 749 /// written by visit(members). 750 void MEMBERS(D)(cstring kind, cstring name, D members) 751 { 752 scope s = new DDocScope(name); 753 write("\n\1DDOC_"~kind~"_MEMBERS "); 754 if (members !is null) 755 super.visit(members); 756 write("\2"); 757 } 758 759 /// Writes a class or interface declaration. 760 void writeClassOrInterface(T)(T d) 761 { 762 if (!ddoc(d)) 763 return; 764 const kind = is(T == ClassDecl) ? "class" : "interface"; 765 const kindID = is(T == ClassDecl) ? K.Class : K.Interface; 766 const KIND = is(T == ClassDecl) ? "CLASS" : "INTERFACE"; 767 DECL({ 768 write("\1DIL_KW ", kind, "\2 "); 769 SYMBOL(d.name.text, kindID, d); 770 writeTemplateParams(); 771 writeInheritanceList(d.bases); 772 }, d); 773 DESC({ MEMBERS(KIND, d.name.text, d.decls); }); 774 } 775 776 /// Writes a struct or union declaration. 777 void writeStructOrUnion(T)(T d) 778 { 779 if (!ddoc(d)) 780 return; 781 const kind = is(T == StructDecl) ? "struct" : "union"; 782 const kindID = is(T == StructDecl) ? K.Struct : K.Union; 783 const KIND = is(T == StructDecl) ? "STRUCT" : "UNION"; 784 cstring name = d.name ? d.name.text : kind; 785 DECL({ 786 d.name && write("\1DIL_KW ", kind, "\2 "); 787 SYMBOL(name, kindID, d); 788 writeTemplateParams(); 789 }, d); 790 DESC({ MEMBERS(KIND, name, d.decls); }); 791 } 792 793 /// Writes an alias or typedef declaration. 794 void writeAliasOrTypedef(T)(T d) 795 { 796 const kind = is(T == AliasDecl) ? "alias" : "typedef"; 797 const kindID = is(T == AliasDecl) ? K.Alias : K.Typedef; 798 if (auto vd = d.vardecl.Is!(VariablesDecl)) 799 foreach (name; vd.names) 800 DECL({ 801 write("\1DIL_KW ", kind, "\2 "); write(vd.type); write(" "); 802 auto saved_begin = vd.begin; 803 // 'vd' instead of 'd' is passed to SYMBOL, because it 804 // has a linkageType member, which has to appear in the docs. 805 vd.begin = d.begin; // Use the begin token of the outer declaration. 806 SYMBOL(name.text, kindID, vd); 807 vd.begin = saved_begin; // Restore to the old value. 808 }, d); 809 else 810 assert(0, "unhandled case"); 811 DESC(); 812 } 813 814 /// All attributes a symbol can have. 815 struct SymbolAttributes 816 { 817 string prot; /// Protection attribute. 818 string[] stcs; /// Storage classes. 819 string link; /// Linkage type. 820 /// Returns all attributes in an array. 821 string[] asArray() 822 { 823 string[] attrs; 824 if (prot.length) 825 attrs = [prot]; 826 attrs ~= stcs; 827 if (link.length) 828 attrs ~= link; 829 return attrs; 830 } 831 } 832 833 /// The attributes of the current symbol. 834 SymbolAttributes currentAttributes; 835 836 /// Stores the attributes of the current symbol. 837 void storeAttributes(Declaration d) 838 { 839 alias attrs = currentAttributes; 840 attrs.prot = (d.prot == Protection.None) ? null : EnumString(d.prot); 841 842 auto stcs = d.stcs; 843 stcs &= ~StorageClass.Auto; // Ignore "auto". 844 attrs.stcs = EnumString.all(stcs); 845 846 LinkageType ltype; 847 if (auto vd = d.Is!(VariablesDecl)) 848 ltype = vd.lnkg; 849 else if (auto fd = d.Is!(FunctionDecl)) 850 ltype = fd.lnkg; 851 852 attrs.link = (ltype == LinkageType.None) ? null : EnumString(ltype); 853 } 854 855 /// Writes the attributes of a declaration in brackets. 856 void writeAttributes(Declaration d) 857 { 858 string[] attributes; 859 860 if (currentAttributes.prot.length) 861 attributes ~= "\1DIL_PROT " ~ currentAttributes.prot ~ "\2"; 862 863 foreach (stcString; currentAttributes.stcs) 864 attributes ~= "\1DIL_STC " ~ stcString ~ "\2"; 865 866 if (currentAttributes.link.length) 867 attributes ~= "\1DIL_LINKAGE extern(" ~ currentAttributes.link ~ ")\2"; 868 869 if (!attributes.length) 870 return; 871 872 write(" \1DIL_ATTRIBUTES ", attributes[0]); 873 foreach (attribute; attributes[1..$]) 874 write(", ", attribute); 875 write("\2"); 876 } 877 878 alias K = DocSymbol.Kind; 879 880 override: 881 //alias visit = super.visit; 882 883 void visit(AliasDecl d) 884 { 885 if (ddoc(d)) 886 writeAliasOrTypedef(d); 887 } 888 889 void visit(TypedefDecl d) 890 { 891 if (ddoc(d)) 892 writeAliasOrTypedef(d); 893 } 894 895 void visit(EnumDecl d) 896 { 897 if (!ddoc(d)) 898 return; 899 cstring name = d.name ? d.name.text : "enum"; 900 DECL({ 901 d.name && write("\1DIL_KW enum\2 "); 902 SYMBOL(name, K.Enum, d); 903 d.baseType && (write(" : "), write(d.baseType)); 904 }, d); 905 DESC({ MEMBERS("ENUM", name, d); }); 906 } 907 908 void visit(EnumMemberDecl d) 909 { 910 if (!ddoc(d)) 911 return; 912 // TODO: emit d.type (D2). 913 DECL({ SYMBOL(d.name.text, K.Enummem, d); }, d, false); 914 DESC(); 915 } 916 917 void visit(TemplateDecl d) 918 { 919 this.currentTParams = d.tparams; 920 if (d.isWrapper) 921 { // This is a templatized class/interface/struct/union/function. 922 super.visit(d.decls); 923 this.currentTParams = null; 924 return; 925 } 926 if (!ddoc(d)) 927 return; 928 DECL({ 929 version(D2) 930 if (d.isMixin) write("\1DIL_KW mixin\2 "); 931 write("\1DIL_KW template\2 "); 932 SYMBOL(d.name.text, K.Template, d); 933 writeTemplateParams(); 934 }, d); 935 DESC({ MEMBERS("TEMPLATE", d.name.text, d.decls); }); 936 } 937 938 void visit(ClassDecl d) 939 { 940 writeClassOrInterface(d); 941 } 942 943 void visit(InterfaceDecl d) 944 { 945 writeClassOrInterface(d); 946 } 947 948 void visit(StructDecl d) 949 { 950 writeStructOrUnion(d); 951 } 952 953 void visit(UnionDecl d) 954 { 955 writeStructOrUnion(d); 956 } 957 958 void visit(ConstructorDecl d) 959 { 960 if (!ddoc(d)) 961 return; 962 DECL({ SYMBOL("this", K.Ctor, d); 963 version(D2) 964 writeTemplateParams(); 965 writeParams(d.params); 966 }, d); 967 DESC(); 968 } 969 970 void visit(StaticCtorDecl d) 971 { 972 if (!ddoc(d)) 973 return; 974 DECL({ write("\1DIL_KW static\2 "); 975 SYMBOL("this", K.Sctor, d); write("()"); }, d); 976 DESC(); 977 } 978 979 void visit(DestructorDecl d) 980 { 981 if (!ddoc(d)) 982 return; 983 DECL({ SYMBOL("~this", K.Dtor, d); write("()"); }, d); 984 DESC(); 985 } 986 987 void visit(StaticDtorDecl d) 988 { 989 if (!ddoc(d)) 990 return; 991 DECL({ write("\1DIL_KW static\2 "); 992 SYMBOL("~this", K.Sdtor, d); write("()"); }, d); 993 DESC(); 994 } 995 996 void visit(FunctionDecl d) 997 { 998 if (!ddoc(d)) 999 return; 1000 DECL({ 1001 if (d.returnType) 1002 write("\1DIL_RETTYPE "), write(d.returnType), write("\2 "); 1003 else write("\1DIL_KW auto\2 "); 1004 SYMBOL(d.name.text, K.Function, d); 1005 writeTemplateParams(); 1006 writeParams(d.params); 1007 }, d); 1008 DESC(); 1009 } 1010 1011 void visit(NewDecl d) 1012 { 1013 if (!ddoc(d)) 1014 return; 1015 DECL({ SYMBOL("new", K.New, d); writeParams(d.params); }, d); 1016 DESC(); 1017 } 1018 1019 void visit(DeleteDecl d) 1020 { 1021 if (!ddoc(d)) 1022 return; 1023 DECL({ SYMBOL("delete", K.Delete, d); writeParams(d.params); }, d); 1024 DESC(); 1025 } 1026 1027 void visit(VariablesDecl d) 1028 { 1029 if (!ddoc(d)) 1030 return; 1031 foreach (name; d.names) 1032 DECL({ 1033 if (d.type) write(d.type); 1034 else write("\1DIL_KW auto\2"); 1035 write(" "); 1036 SYMBOL(name.text, K.Variable, d); 1037 }, d); 1038 DESC(); 1039 } 1040 1041 void visit(InvariantDecl d) 1042 { 1043 if (!ddoc(d)) 1044 return; 1045 DECL({ SYMBOL("invariant", K.Invariant, d); }, d); 1046 DESC(); 1047 } 1048 1049 void visit(UnittestDecl d) 1050 { 1051 if (!ddoc(d)) 1052 return; 1053 DECL({ SYMBOL("unittest", K.Unittest, d); }, d); 1054 DESC(); 1055 } 1056 1057 void visit(DebugDecl d) 1058 { 1059 d.compiledDecls && visitD(d.compiledDecls); 1060 } 1061 1062 void visit(VersionDecl d) 1063 { 1064 d.compiledDecls && visitD(d.compiledDecls); 1065 } 1066 1067 void visit(StaticIfDecl d) 1068 { 1069 d.ifDecls && visitD(d.ifDecls); 1070 } 1071 } 1072 1073 /// A class that holds some info about a symbol. 1074 class DocSymbol 1075 { 1076 /// Enum of symbol kinds. 1077 static enum Kind 1078 { 1079 Package, Module, Template, Class, Interface, Struct, 1080 Union, Alias, Typedef, Enum, Enummem, Variable, Function, 1081 Invariant, New, Delete, Unittest, Ctor, Dtor, Sctor, Sdtor 1082 } 1083 1084 cstring name, fqn; /// Name and fully qualified name. 1085 Kind kind; /// The kind of this symbol. 1086 Location begin, end; /// Beginning and end locations. 1087 DocSymbol[] members; /// All symbol members. 1088 string[] attrs; /// All attributes of this symbol. 1089 1090 /// Constructs a DocSymbol object. 1091 this(cstring name, cstring fqn, Kind kind, string[] attrs, 1092 Location begin, Location end) 1093 { 1094 this.name = name; 1095 this.fqn = fqn; 1096 this.kind = kind; 1097 this.attrs = attrs; 1098 this.begin = begin; 1099 this.end = end; 1100 } 1101 1102 /// Symbol kinds as strings. 1103 static string[] kindIDToStr = ["package", "module", "template", 1104 "class", "interface", "struct", "union", "alias", "typedef", 1105 "enum", "enummem", "variable", "function", "invariant", "new", 1106 "delete", "unittest", "ctor", "dtor", "sctor", "sdtor"]; 1107 1108 /// Maps the kind of a symbol to its ID. 1109 /// Must match the list in "kandil/js/symbols.js". 1110 static uint[hash_t] kindStrToID; 1111 /// Maps the attribute of a symbol to its ID. 1112 /// Must match the list in "kandil/js/symbols.js". 1113 static uint[hash_t] attrToID; 1114 /// Initialize the associative arrays. 1115 static this() 1116 { 1117 foreach (i, kind; kindIDToStr) 1118 kindStrToID[hashOf(kind)] = cast(uint)i; 1119 1120 // Combine attributes and add them to attrToID. 1121 auto attrs = EnumString.prots[1..$] ~ 1122 EnumString.stcs[1..$] ~ EnumString.ltypes[1..$]; 1123 foreach (i, attr; attrs) 1124 attrToID[hashOf(attr)] = cast(uint)i; 1125 } 1126 1127 /// Return the attributes as IDs. E.g.: "[1,9,22]" 1128 cstring formatAttrsAsIDs() 1129 { 1130 if (!attrs.length) 1131 return "[]"; 1132 char[] result = "[".dup; 1133 foreach (attr; attrs) 1134 result ~= itoa(attrToID[hashOf(attr)]) ~ ","; 1135 result[$-1] = ']'; 1136 return result; 1137 } 1138 }