1 // <details> html tag 2 // FIXME: KEY_VALUE like params 3 module arsd.docgen.comment; 4 5 import adrdox.main; 6 import arsd.dom; 7 8 import dparse.ast; 9 import dparse.lexer; 10 11 import std..string; 12 import std.algorithm; 13 14 import std.conv; 15 16 const(char)[] htmlEncode(const(char)[] s) { 17 return s. 18 replace("&", "&"). 19 replace("<", "<"). 20 replace(">", ">"); 21 } 22 23 24 static struct MyOutputRange { 25 this(string* output) { 26 this.output = output; 27 } 28 29 string* output; 30 void put(T...)(T s) { 31 foreach(i; s) 32 putTag(i.htmlEncode); 33 } 34 35 void putTag(in char[] s) { 36 if(s.length == 0) 37 return; 38 //foreach(ch; s) { 39 assert(s.length); 40 assert(s.indexOf("</body>") == -1); 41 //} 42 (*output) ~= s; 43 } 44 } 45 46 47 /* 48 Params: 49 50 The line, excluding whitespace, must start with 51 identifier = to start a new thing. If a new thing starts, 52 it closes the old. 53 54 The description may be formatted however. Whitespace is stripped. 55 */ 56 57 string getIdent(T)(T t) { 58 if(t is null) 59 return null; 60 if(t.identifier == tok!"") 61 return null; 62 return t.identifier.text; 63 } 64 65 bool hasParam(T)(const T dec, string name, Decl declObject, bool descend = true) { 66 if(dec is null) 67 return false; 68 69 static if(__traits(compiles, dec.parameters)) { 70 if(dec.parameters && dec.parameters.parameters) 71 foreach(parameter; dec.parameters.parameters) 72 if(parameter.name.text == name) 73 return true; 74 } 75 static if(__traits(compiles, dec.templateParameters)) { 76 if(dec.templateParameters && dec.templateParameters.templateParameterList) 77 foreach(parameter; dec.templateParameters.templateParameterList.items) { 78 if(getIdent(parameter.templateTypeParameter) == name) 79 return true; 80 if(getIdent(parameter.templateValueParameter) == name) 81 return true; 82 if(getIdent(parameter.templateAliasParameter) == name) 83 return true; 84 if(getIdent(parameter.templateTupleParameter) == name) 85 return true; 86 if(parameter.templateThisParameter && getIdent(parameter.templateThisParameter.templateTypeParameter) == name) 87 return true; 88 } 89 } 90 91 if(!descend) 92 return false; 93 94 // need to check parent template parameters in case they are 95 // referenced in a child 96 if(declObject.parent) { 97 bool h; 98 if(auto pdec = cast(TemplateDecl) declObject.parent) 99 h = hasParam(pdec.astNode, name, pdec, false); 100 else 101 {} 102 if(h) 103 return true; 104 } 105 106 // and eponymous ones 107 static if(is(T == TemplateDeclaration)) { 108 auto decl = cast(TemplateDecl) declObject; 109 if(decl) { 110 auto e = decl.eponymousMember(); 111 if(e) { 112 if(auto a = cast(ConstructorDecl) e) 113 return hasParam(a.astNode, name, a, false); 114 if(auto a = cast(FunctionDecl) e) 115 return hasParam(a.astNode, name, a, false); 116 // FIXME: add more 117 } 118 } 119 } 120 121 return false; 122 } 123 124 struct LinkReferenceInfo { 125 enum Type { none, text, link, image } 126 Type type; 127 bool isFootnote; 128 string text; // or alt 129 string link; // or src 130 131 string toHtml(string fun, string rt) { 132 if(rt is null) 133 rt = fun; 134 if(rt is fun && text !is null && type != Type.text) 135 rt = text; 136 137 string display = isFootnote ? ("[" ~ rt ~ "]") : rt; 138 139 Element element; 140 final switch(type) { 141 case Type.none: 142 return null; 143 case Type.text: 144 case Type.link: 145 element = isFootnote ? Element.make("sup", "", "footnote-ref") : Element.make("span"); 146 if(type == Type.text) { 147 element.addChild("abbr", display).setAttribute("title", text); 148 } else { 149 element.addChild("a", display, link); 150 } 151 break; 152 case Type.image: 153 element = Element.make("img", link, text); 154 break; 155 } 156 157 return element.toString(); 158 } 159 } 160 161 string[string] globalLinkReferences; 162 163 void loadGlobalLinkReferences(string text) { 164 foreach(line; text.splitLines) { 165 line = line.strip; 166 if(line.length == 0) 167 continue; 168 auto idx = line.indexOf("="); 169 if(idx == -1) 170 continue; 171 auto name = line[0 .. idx].strip; 172 auto value = line[idx + 1 .. $].strip; 173 if(name.length == 0 || value.length == 0) 174 continue; 175 globalLinkReferences[name] = value; 176 } 177 } 178 179 Element getSymbolGroupReference(string name, Decl decl, string rt) { 180 auto pm = decl.isModule() ? decl : decl.parentModule; 181 if(pm is null) 182 return null; 183 if(name in pm.parsedDocComment.symbolGroups) { 184 return Element.make("a", rt.length ? rt : name, pm.fullyQualifiedName ~ ".html#group-" ~ name); 185 } 186 187 return null; 188 } 189 190 LinkReferenceInfo getLinkReference(string name, Decl decl) { 191 bool numeric = (name.all!((ch) => ch >= '0' && ch <= '9')); 192 193 bool onGlobal = false; 194 auto refs = decl.parsedDocComment.linkReferences; 195 196 try_again: 197 198 if(name in refs) { 199 auto txt = refs[name]; 200 201 assert(txt.length); 202 203 LinkReferenceInfo lri; 204 205 import std.uri; 206 auto l = uriLength(txt); 207 if(l != -1) { 208 lri.link = txt; 209 lri.type = LinkReferenceInfo.Type.link; 210 } else if(txt[0] == '$') { 211 // should be an image or plain text 212 if(txt.length > 5 && txt[0 .. 6] == "$(IMG " && txt[$-1] == ')') { 213 txt = txt[6 .. $-1]; 214 auto idx = txt.indexOf(","); 215 if(idx == -1) { 216 lri.link = txt.strip; 217 } else { 218 lri.text = txt[idx + 1 .. $].strip; 219 lri.link = txt[0 .. idx].strip; 220 } 221 lri.type = LinkReferenceInfo.Type.image; 222 } else goto plain_text; 223 } else if(txt[0] == '[' && txt[$-1] == ']') { 224 txt = txt[1 .. $-1]; 225 auto idx = txt.indexOf("|"); 226 if(idx == -1) { 227 lri.link = txt; 228 } else { 229 lri.link = txt[0 .. idx].strip; 230 lri.text = txt[idx + 1 .. $].strip; 231 } 232 233 lri.link = getReferenceLink(lri.link, decl).href; 234 235 lri.type = LinkReferenceInfo.Type.link; 236 } else { 237 plain_text: 238 lri.type = LinkReferenceInfo.Type.text; 239 lri.link = null; 240 lri.text = txt; 241 } 242 243 lri.isFootnote = numeric; 244 return lri; 245 } else if(!onGlobal && !numeric) { 246 if(decl.parent !is null) 247 decl = decl.parent; 248 else { 249 onGlobal = true; 250 refs = globalLinkReferences; 251 // decl = null; // so I kinda want it to be null, but that breaks like everything so I can't. 252 } 253 goto try_again; 254 } 255 256 return LinkReferenceInfo.init; 257 } 258 259 struct DocComment { 260 string ddocSummary; 261 string synopsis; 262 263 string details; 264 265 string[] params; // stored as ident=txt. You can split on first index of =. 266 string returns; 267 string examples; 268 string diagnostics; 269 string throws; 270 string bugs; 271 string see_alsos; 272 273 string[string] symbolGroups; // stored as "name" : "raw text" 274 string[] symbolGroupsOrder; 275 string group; // the group this belongs to, if any 276 277 string[string] otherSections; 278 279 string[string] linkReferences; 280 281 string[string] userDefinedMacros; 282 283 Decl decl; 284 285 bool synopsisEndedOnDoubleBlank; 286 287 void writeSynopsis(MyOutputRange output) { 288 output.putTag("<div class=\"documentation-comment synopsis\">"); 289 output.putTag(formatDocumentationComment(synopsis, decl)); 290 if(details !is synopsis && details.strip.length && details.strip != "<div></div>") 291 output.putTag(` <a id="more-link" href="#details">More...</a>`); 292 output.putTag("</div>"); 293 } 294 295 void writeDetails(T = FunctionDeclaration)(MyOutputRange output) { 296 writeDetails!T(output, cast(T) null, null); 297 } 298 299 void writeDetails(T = FunctionDeclaration)(MyOutputRange output, const T functionDec = null, Decl.ProcessedUnittest[] utInfo = null) { 300 auto f = new MyFormatter!(typeof(output))(output, decl); 301 302 if(params.length) { 303 int count = 0; 304 foreach(param; params) { 305 auto split = param.indexOf("="); 306 auto paramName = param[0 .. split]; 307 308 if(!hasParam(functionDec, paramName, decl)) 309 continue; 310 count++; 311 } 312 313 if(count) { 314 output.putTag("<h2 id=\"parameters\">Parameters</h2>"); 315 output.putTag("<dl class=\"parameter-descriptions\">"); 316 foreach(param; params) { 317 auto split = param.indexOf("="); 318 auto paramName = param[0 .. split]; 319 320 if(!hasParam(functionDec, paramName, decl)) 321 continue; 322 323 output.putTag("<dt id=\"param-"~param[0 .. split]~"\">"); 324 output.putTag("<a href=\"#param-"~param[0 .. split]~"\" class=\"parameter-name\" data-ident=\""); 325 output.put(param[0 .. split]); 326 output.putTag("\">"); 327 output.put(param[0 .. split]); 328 output.putTag("</a>"); 329 330 static if(is(T == FunctionDeclaration) || is(T == Constructor)) 331 if(functionDec !is null) { 332 const(Parameter)* paramAst; 333 foreach(ref p; functionDec.parameters.parameters) { 334 if(p.name.type != tok!"") 335 if(p.name.text == param[0 .. split]) { 336 paramAst = &p; 337 break; 338 } 339 } 340 341 if(paramAst) { 342 output.putTag(" <span class=\"parameter-type\">"); 343 f.format(paramAst.type); 344 output.putTag("</span>"); 345 } 346 347 348 if(paramAst && paramAst.atAttributes.length) { 349 output.putTag("<div class=\"parameter-attributes\">"); 350 output.put("Attributes:"); 351 foreach (attribute; paramAst.atAttributes) { 352 output.putTag("<div class=\"parameter-attribute\">"); 353 f.format(attribute); 354 output.putTag("</div>"); 355 } 356 357 output.putTag("</div>"); 358 } 359 360 361 } 362 363 output.putTag("</dt>"); 364 output.putTag("<dd>"); 365 366 output.putTag("<div class=\"documentation-comment\">"); 367 output.putTag(formatDocumentationComment(param[split + 1 .. $], decl)); 368 output.putTag("</div></dd>"); 369 } 370 output.putTag("</dl>"); 371 } 372 } 373 374 if(returns !is null) { 375 output.putTag("<h2 id=\"returns\">Return Value</h2>"); 376 output.putTag("<div>"); 377 static if(is(T == FunctionDeclaration)) 378 if(functionDec !is null) { 379 output.putTag("<div class=\"return-type-holder\">"); 380 output.putTag("Type: "); 381 output.putTag("<span class=\"return-type\">"); 382 if(functionDec.hasAuto && functionDec.hasRef) 383 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-ref-function-return-prototype">auto ref</a> `); 384 else { 385 if (functionDec.hasAuto) 386 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-function-return-prototype">auto</a> `); 387 if (functionDec.hasRef) 388 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/ref-function-return-prototype">ref</a> `); 389 } 390 391 if (functionDec.returnType !is null) 392 f.format(functionDec.returnType); 393 output.putTag("</span>"); 394 output.putTag("</div>"); 395 } 396 output.putTag("<div class=\"documentation-comment returns-description\">"); 397 output.putTag(formatDocumentationComment(returns, decl)); 398 output.putTag("</div>"); 399 output.putTag("</div>"); 400 } 401 402 if(details.strip.length && details.strip != "<div></div>") { 403 output.putTag("<h2 id=\"details\">Detailed Description</h2>"); 404 output.putTag("<div class=\"documentation-comment detailed-description\">"); 405 output.putTag(formatDocumentationComment(details, decl)); 406 output.putTag("</div>"); 407 } 408 409 if(throws.strip.length) { 410 output.putTag("<h2 id=\"throws\">Throws</h2>"); 411 output.putTag("<div class=\"documentation-comment throws-description\">"); 412 output.putTag(formatDocumentationComment(throws, decl)); 413 output.putTag("</div>"); 414 } 415 416 417 if(diagnostics.strip.length) { 418 output.putTag("<h2 id=\"diagnostics\">Common Problems</h2>"); 419 output.putTag("<div class=\"documentation-comment diagnostics\">"); 420 output.putTag(formatDocumentationComment(diagnostics, decl)); 421 output.putTag("</div>"); 422 } 423 424 if(bugs.strip.length) { 425 output.putTag("<h2 id=\"bugs\">Bugs</h2>"); 426 output.putTag("<div class=\"documentation-comment bugs-description\">"); 427 output.putTag(formatDocumentationComment(bugs, decl)); 428 output.putTag("</div>"); 429 } 430 431 bool hasUt; 432 foreach(ut; utInfo) if(ut.embedded == false){hasUt = true; break;} 433 434 if(examples.length || hasUt) { 435 output.putTag("<h2 id=\"examples\"><a href=\"#examples\" class=\"header-anchor\">Examples</a></h2>"); 436 output.putTag("<div class=\"documentation-comment\">"); 437 output.putTag(formatDocumentationComment(examples, decl)); 438 output.putTag("</div>"); 439 440 foreach(example; utInfo) { 441 if(example.embedded) continue; 442 output.putTag(formatUnittestDocTuple(example, decl).toString()); 443 } 444 } 445 446 if(group) 447 see_alsos ~= "\n\n[" ~ group ~ "]"; 448 449 if(see_alsos.length) { 450 output.putTag("<h2 id=\"see-also\">See Also</h2>"); 451 output.putTag("<div class=\"documentation-comment see-also-section\">"); 452 output.putTag(formatDocumentationComment(see_alsos, decl)); 453 output.putTag("</div>"); 454 } 455 456 if(otherSections.keys.length) { 457 output.putTag("<h2 id=\"meta\">Meta</h2>"); 458 } 459 460 foreach(section, content; otherSections) { 461 output.putTag("<div class=\"documentation-comment " ~ section ~ "-section other-section\">"); 462 output.putTag("<h3>"); 463 output.put(section.capitalize); 464 output.putTag("</h3>"); 465 output.putTag(formatDocumentationComment(content, decl)); 466 output.putTag("</div>"); 467 } 468 } 469 } 470 471 Element formatUnittestDocTuple(Decl.ProcessedUnittest example, Decl decl) { 472 auto holder = Element.make("div").addClass("unittest-example-holder"); 473 474 holder.addChild(formatDocumentationComment2(preprocessComment(example.comment, decl), decl).addClass("documentation-comment")); 475 auto pre = holder.addChild("pre").addClass("d_code highlighted"); 476 477 // trim off leading/trailing newlines since they just clutter output 478 auto codeToWrite = example.code; 479 while(codeToWrite.length && (codeToWrite[0] == '\n' || codeToWrite[0] == '\r')) 480 codeToWrite = codeToWrite[1 .. $]; 481 while(codeToWrite.length && (codeToWrite[$-1] == '\n' || codeToWrite[$-1] == '\r')) 482 codeToWrite = codeToWrite[0 .. $ - 1]; 483 484 pre.innerHTML = highlight(outdent(codeToWrite)); 485 486 return holder; 487 } 488 489 string preprocessComment(string comment, Decl decl) { 490 if(comment.length < 3) 491 return comment; 492 493 comment = comment.replace("\r\n", "\n"); 494 495 comment = comment[1 .. $]; // trim off the / 496 497 auto commentType = comment[0]; 498 499 while(comment.length && comment[0] == commentType) 500 comment = comment[1 .. $]; // trim off other opening spam 501 502 string closingSpam; 503 if(commentType == '*' || commentType == '+') { 504 comment = comment[0 .. $-1]; // trim off the closing / 505 bool closingSpamFound; 506 while(comment.length && comment[$-1] == commentType) { 507 comment = comment[0 .. $-1]; // trim off other closing spam 508 closingSpamFound = true; 509 } 510 511 if(closingSpamFound) { 512 // if there was closing spam we also want to count the spaces before it 513 // to trim off other line spam. The goal here is to clean up 514 /** 515 * Resolve host name. 516 * Returns: false if unable to resolve. 517 */ 518 // into just "Resolve host name.\n Returns: false if unable to resolve." 519 // give or take some irrelevant whitespace. 520 while(comment.length && (comment[$-1] == '\t' || comment[$-1] == ' ')) { 521 closingSpam = comment[$-1] ~ closingSpam; 522 comment = comment[0 .. $-1]; // trim off other closing spam 523 } 524 525 if(closingSpam.length == 0) 526 closingSpam = " "; // some things use the " *" leader, but still end with "*/" on a line of its own 527 } 528 } 529 530 531 string poop; 532 if(commentType == '/') 533 poop = "///"; 534 else 535 poop = closingSpam ~ commentType ~ " "; 536 537 string newComment; 538 foreach(line; comment.splitter("\n")) { 539 // check for " * some text" 540 if(line.length >= poop.length && line.startsWith(poop)) { 541 if(line.length > poop.length && line[poop.length] == ' ') 542 newComment ~= line[poop.length + 1 .. $]; 543 else 544 newComment ~= line[poop.length .. $]; 545 } 546 // check for an empty line with just " *" 547 else if(line.length == poop.length-1 && line[0..poop.length-1] == poop[0..$-1]) 548 {} // this space is intentionally left blank; it is an empty line 549 else if(line.length > 1 && commentType != '/' && line[0] == commentType && line[1] == ' ') 550 newComment ~= line[1 .. $]; // cut off stupid leading * with no space before too 551 else 552 newComment ~= line; 553 newComment ~= "\n"; 554 } 555 556 comment = newComment; 557 return specialPreprocess(comment, decl); 558 } 559 560 DocComment parseDocumentationComment(string comment, Decl decl) { 561 DocComment c; 562 563 if(generatingSource) { 564 if(decl.lineNumber) 565 c.otherSections["source"] ~= "$(LINK2 source/"~decl.parentModule.name~".d.html#L"~to!string(decl.lineNumber)~", See Implementation)$(BR)"; 566 else if(!decl.fakeDecl) 567 c.otherSections["source"] ~= "$(LINK2 source/"~decl.parentModule.name~".d.html, See Source File)$(BR)"; 568 } 569 // FIXME: add links to ddoc and ddox iff std.* or core.* 570 571 c.decl = decl; 572 573 comment = preprocessComment(comment, decl); 574 575 void parseSections(string comment) { 576 string remaining; 577 string section; 578 bool inSynopsis = true; 579 bool justSawBlank = false; 580 bool inCode = false; 581 string inCodeStyle; 582 bool hasAnySynopsis = false; 583 bool inDdocSummary = true; 584 bool synopsisEndedOnDoubleBlank = false; 585 586 string currentMacroName; 587 588 // for symbol_groups 589 string* currentValue; 590 591 bool maybeGroupLine = true; 592 593 594 auto lastLineHelper = comment.strip; 595 auto lastLine = lastLineHelper.lastIndexOf("\n"); 596 if(lastLine != -1) { 597 auto lastLineText = lastLineHelper[lastLine .. $].strip; 598 if(lastLineText.startsWith("Group: ") || lastLineText.startsWith("group: ")) { 599 c.group = lastLineText["Group: ".length .. $]; 600 maybeGroupLine = false; 601 602 comment = lastLineHelper[0 .. lastLine].strip; 603 } 604 } 605 606 foreach(line; comment.splitter("\n")) { 607 608 if(maybeGroupLine) { 609 maybeGroupLine = false; 610 if(line.strip.startsWith("Group: ") || line.strip.startsWith("group: ")) { 611 c.group = line.strip["Group: ".length .. $]; // cut off closing paren 612 continue; 613 } 614 } 615 616 auto maybe = line.strip.toLower; 617 618 if(maybe.startsWith("---") || maybe.startsWith("```")) { 619 justSawBlank = false; 620 621 if(inCode && inCodeStyle == maybe[0 .. 3]) { 622 inCode = false; 623 inCodeStyle = null; 624 } else if(inCode) 625 goto ss; // just still inside code section 626 else { 627 inCodeStyle = maybe[0 .. 3]; 628 inDdocSummary = false; 629 inCode = !inCode; 630 } 631 } 632 if(inCode){ 633 justSawBlank = false; 634 goto ss; // sections never change while in a code example 635 } 636 637 if(inSynopsis && hasAnySynopsis && maybe.length == 0) { 638 // any empty line ends the ddoc summary 639 inDdocSummary = false; 640 // two blank lines in a row ends the synopsis 641 if(justSawBlank) { 642 inSynopsis = false; 643 // synopsis can also end on the presence of another 644 // section, so this is tracked to see how intentional 645 // the break looked (Phobos docs aren't written with 646 // a double-break in mind) 647 synopsisEndedOnDoubleBlank = true; 648 } 649 justSawBlank = true; 650 } else { 651 justSawBlank = false; 652 } 653 654 if(maybe.startsWith("params:")) { 655 section = "params"; 656 inSynopsis = false; 657 } else if(maybe.startsWith("parameters:")) { 658 section = "params"; 659 inSynopsis = false; 660 } else if(maybe.startsWith("returns:")) { 661 section = "returns"; 662 line = line[line.indexOf(":")+1 .. $]; 663 inSynopsis = false; 664 } else if(maybe.startsWith("throws:")) { 665 section = "throws"; 666 inSynopsis = false; 667 line = line[line.indexOf(":")+1 .. $]; 668 } else if(maybe.startsWith("author:")) { 669 section = "authors"; 670 line = line[line.indexOf(":")+1 .. $]; 671 inSynopsis = false; 672 } else if(maybe.startsWith("details:")) { 673 section = "details"; 674 line = line[line.indexOf(":")+1 .. $]; 675 inSynopsis = false; 676 } else if(maybe.startsWith("authors:")) { 677 section = "authors"; 678 line = line[line.indexOf(":")+1 .. $]; 679 inSynopsis = false; 680 } else if(maybe.startsWith("source:")) { 681 section = "source"; 682 line = line[line.indexOf(":")+1 .. $]; 683 inSynopsis = false; 684 } else if(maybe.startsWith("history:")) { 685 section = "history"; 686 line = line[line.indexOf(":")+1 .. $]; 687 inSynopsis = false; 688 } else if(maybe.startsWith("credits:")) { 689 section = "credits"; 690 line = line[line.indexOf(":")+1 .. $]; 691 inSynopsis = false; 692 } else if(maybe.startsWith("version:")) { 693 section = "version"; 694 line = line[line.indexOf(":")+1 .. $]; 695 inSynopsis = false; 696 } else if(maybe.startsWith("since:")) { 697 section = "since"; 698 line = line[line.indexOf(":")+1 .. $]; 699 inSynopsis = false; 700 } else if(maybe.startsWith("license:")) { 701 section = "license"; 702 line = line[line.indexOf(":")+1 .. $]; 703 inSynopsis = false; 704 } else if(maybe.startsWith("copyright:")) { 705 section = "copyright"; 706 line = line[line.indexOf(":")+1 .. $]; 707 inSynopsis = false; 708 } else if(maybe.startsWith("see_also:")) { 709 section = "see_also"; 710 inSynopsis = false; 711 line = line[line.indexOf(":")+1 .. $]; 712 } else if(maybe.startsWith("diagnostics:")) { 713 inSynopsis = false; 714 section = "diagnostics"; 715 line = line[line.indexOf(":")+1 .. $]; 716 } else if(maybe.startsWith("examples:")) { 717 inSynopsis = false; 718 section = "examples"; 719 line = line[line.indexOf(":")+1 .. $]; 720 } else if(maybe.startsWith("example:")) { 721 inSynopsis = false; 722 line = line[line.indexOf(":")+1 .. $]; 723 section = "examples"; // Phobos uses example, the standard is examples. 724 } else if(maybe.startsWith("standards:")) { 725 line = line[line.indexOf(":")+1 .. $]; 726 inSynopsis = false; 727 section = "standards"; 728 } else if(maybe.startsWith("deprecated:")) { 729 inSynopsis = false; 730 section = "deprecated"; 731 } else if(maybe.startsWith("date:")) { 732 inSynopsis = false; 733 section = "date"; 734 } else if(maybe.startsWith("bugs:")) { 735 line = line[line.indexOf(":")+1 .. $]; 736 inSynopsis = false; 737 section = "bugs"; 738 } else if(maybe.startsWith("macros:")) { 739 inSynopsis = false; 740 section = "macros"; 741 currentMacroName = null; 742 } else if(maybe.startsWith("symbol_groups:")) { 743 section = "symbol_groups"; 744 line = line[line.indexOf(":")+1 .. $]; 745 inSynopsis = false; 746 } else if(maybe.startsWith("link_references:")) { 747 inSynopsis = false; 748 section = "link_references"; 749 line = line[line.indexOf(":")+1 .. $].strip; 750 } else if(maybe.isDdocSection()) { 751 inSynopsis = false; 752 753 auto idx2 = line.indexOf(":"); 754 auto name = line[0 .. idx2].replace("_", " "); 755 line = "$(H3 "~name~")\n\n" ~ line[idx2+1 .. $]; 756 } else { 757 // no change to section 758 } 759 760 if(inSynopsis == false) 761 inDdocSummary = false; 762 763 764 ss: switch(section) { 765 case "symbol_groups": 766 // basically a copy/paste of Params but it is coincidental 767 // they might not always share syntax. 768 bool lookingForIdent = true; 769 bool inIdent; 770 bool skippingSpace; 771 size_t space_at; 772 773 auto lol = line.strip; 774 foreach(idx, ch; lol) { 775 import std.uni, std.ascii : isAlphaNum; 776 if(lookingForIdent && !inIdent) { 777 if(!isAlpha(ch) && ch != '_') 778 continue; 779 inIdent = true; 780 lookingForIdent = false; 781 } 782 783 if(inIdent) { 784 if(ch == '_' || isAlphaNum(ch) || isAlpha(ch)) 785 continue; 786 else { 787 skippingSpace = true; 788 inIdent = false; 789 space_at = idx; 790 } 791 } 792 if(skippingSpace) { 793 if(!isWhite(ch)) { 794 if(ch == '=') { 795 // we finally hit a thingy 796 auto currentName = lol[0 .. space_at]; 797 798 c.symbolGroups[currentName] = lol[idx + 1 .. $] ~ "\n"; 799 c.symbolGroupsOrder ~= currentName; 800 currentValue = currentName in c.symbolGroups; 801 break ss; 802 } else 803 // we are expecting whitespace or = and hit 804 // neither.. this can't be ident = desc! 805 break; 806 } 807 } 808 } 809 810 if(currentValue !is null) { 811 *currentValue ~= line ~ "\n"; 812 } 813 break; 814 case "params": 815 bool lookingForIdent = true; 816 bool inIdent; 817 bool skippingSpace; 818 size_t space_at; 819 820 auto lol = line.strip; 821 foreach(idx, ch; lol) { 822 import std.uni, std.ascii : isAlphaNum; 823 if(lookingForIdent && !inIdent) { 824 if(!isAlpha(ch) && ch != '_') 825 continue; 826 inIdent = true; 827 lookingForIdent = false; 828 } 829 830 if(inIdent) { 831 if(ch == '_' || isAlphaNum(ch) || isAlpha(ch)) 832 continue; 833 else { 834 skippingSpace = true; 835 inIdent = false; 836 space_at = idx; 837 } 838 } 839 if(skippingSpace) { 840 if(!isWhite(ch)) { 841 if(ch == '=') { 842 // we finally hit a thingy 843 c.params ~= lol[0 .. space_at] ~ "=" ~ lol[idx + 1 .. $] ~ "\n"; 844 break ss; 845 } else 846 // we are expecting whitespace or = and hit 847 // neither.. this can't be ident = desc! 848 break; 849 } 850 } 851 } 852 853 if(c.params.length) 854 c.params[$-1] ~= line ~ "\n"; 855 break; 856 case "see_also": 857 c.see_alsos ~= line ~ "\n"; 858 break; 859 case "macros": 860 // ignoring for now 861 862 bool lookingForIdent = true; 863 bool inIdent; 864 bool skippingSpace; 865 size_t space_at; 866 867 auto lol = line.strip; 868 foreach(idx, ch; lol) { 869 import std.uni, std.ascii : isAlphaNum; 870 if(lookingForIdent && !inIdent) { 871 if(!isAlpha(ch) && ch != '_') 872 continue; 873 inIdent = true; 874 lookingForIdent = false; 875 } 876 877 if(inIdent) { 878 if(ch == '_' || isAlphaNum(ch) || isAlpha(ch)) 879 continue; 880 else { 881 skippingSpace = true; 882 inIdent = false; 883 space_at = idx; 884 } 885 } 886 if(skippingSpace) { 887 if(!isWhite(ch)) { 888 if(ch == '=') { 889 // we finally hit a thingy 890 currentMacroName = lol[0 .. space_at].strip; 891 auto currentMacroBody = lol[idx + 1 .. $] ~ "\n"; 892 893 c.userDefinedMacros[currentMacroName] = currentMacroBody; 894 break ss; 895 } else 896 // we are expecting whitespace or = and hit 897 // neither.. this can't be ident = desc! 898 break; 899 } 900 } else { 901 if(currentMacroName.length) 902 c.userDefinedMacros[currentMacroName] ~= lol; 903 } 904 } 905 break; 906 case "link_references": 907 auto eql = line.indexOf("="); 908 if(eql != -1) { 909 string name = line[0 .. eql].strip; 910 string value = line[eql + 1 .. $].strip; 911 c.linkReferences[name] = value; 912 } else if(line.strip.length) { 913 section = null; 914 goto case_default; 915 } 916 break; 917 case "returns": 918 c.returns ~= line ~ "\n"; 919 break; 920 case "diagnostics": 921 c.diagnostics ~= line ~ "\n"; 922 break; 923 case "throws": 924 c.throws ~= line ~ "\n"; 925 break; 926 case "bugs": 927 c.bugs ~= line ~ "\n"; 928 break; 929 case "authors": 930 case "license": 931 case "source": 932 case "history": 933 case "credits": 934 case "standards": 935 case "copyright": 936 case "version": 937 case "since": 938 case "date": 939 c.otherSections[section] ~= line ~ "\n"; 940 break; 941 case "details": 942 c.details ~= line ~ "\n"; 943 break; 944 case "examples": 945 c.examples ~= line ~ "\n"; 946 break; 947 default: 948 case_default: 949 if(inSynopsis) { 950 if(inDdocSummary) 951 c.ddocSummary ~= line ~ "\n"; 952 c.synopsis ~= line ~ "\n"; 953 if(line.length) 954 hasAnySynopsis = true; 955 } else 956 remaining ~= line ~ "\n"; 957 } 958 } 959 960 c.details ~= remaining; 961 c.synopsisEndedOnDoubleBlank = synopsisEndedOnDoubleBlank; 962 } 963 964 parseSections(comment); 965 966 return c; 967 } 968 969 bool isDdocSection(string s) { 970 bool hasUnderscore = false; 971 foreach(idx, char c; s) { 972 if(c == '_') 973 hasUnderscore = true; 974 if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) 975 return hasUnderscore && c == ':'; 976 } 977 return false; 978 } 979 980 bool isIdentifierChar(size_t idx, dchar ch) { 981 import std.uni; 982 if(!(isAlpha(ch) || (idx != 0 && isNumber(ch)) || ch == '_')) 983 return false; 984 return true; 985 } 986 987 bool isIdentifierOrUrl(string text) { 988 import std.uri; 989 auto l = uriLength(text); 990 991 if(l != -1) 992 return true; // is url 993 994 if(text.length && text[0] == '#') 995 return true; // is local url link 996 997 bool seenHash; 998 999 import std.uni; 1000 foreach(idx, dchar ch; text) { 1001 if(!isIdentifierChar(idx, ch) && ch != '.') { 1002 if(!seenHash && ch == '#') { 1003 seenHash = true; 1004 continue; 1005 } else if(seenHash && ch == '-') { 1006 continue; 1007 } 1008 return false; 1009 } 1010 } 1011 1012 // passed the ident test... 1013 if(text.length) 1014 return true; 1015 1016 return false; 1017 1018 } 1019 1020 Element getReferenceLink(string text, Decl decl, string realText = null) { 1021 import std.uri; 1022 auto l = uriLength(text); 1023 string hash; 1024 1025 string className; 1026 1027 if(l != -1) { // a url 1028 text = text; 1029 if(realText is null) 1030 realText = text; 1031 } else if(text.length && text[0] == '#') { 1032 // an anchor. add the link so it works when this is copy/pasted on other pages too (such as in search results) 1033 hash = text; 1034 text = decl.link; 1035 1036 if(realText is null) { 1037 realText = hash[1 .. $].replace("-", " "); 1038 } 1039 } else { 1040 if(realText is null) 1041 realText = text; 1042 1043 auto hashIdx = text.indexOf("#"); 1044 if(hashIdx != -1) { 1045 hash = text[hashIdx .. $]; 1046 text = text[0 .. hashIdx]; 1047 } 1048 1049 auto found = decl.lookupName(text); 1050 1051 className = "xref"; 1052 1053 if(found is null) { 1054 auto lastPieceIdx = text.lastIndexOf("."); 1055 if(lastPieceIdx != -1) { 1056 found = decl.lookupName(text[0 .. lastPieceIdx]); 1057 if(found && hash is null) 1058 hash = "#" ~ text[lastPieceIdx + 1 .. $]; 1059 } 1060 } 1061 1062 if(found !is null) 1063 text = found.link; 1064 else if(auto c = text in allClasses) { 1065 // classes may be thrown and as such can be referenced externally without import 1066 // doing this as kinda a hack. 1067 text = (*c).link; 1068 } else 1069 text ~= ".html"; 1070 } 1071 1072 auto element = Element.make("a"); 1073 element.className = className; 1074 element.href = getDirectoryForPackage(text) ~ text ~ hash; 1075 element.innerText = realText; 1076 return element; 1077 } 1078 1079 struct DocCommentTerminationCondition { 1080 string[] terminationStrings; 1081 bool mustBeAtStartOfLine; 1082 string remaining; 1083 string terminatedOn; 1084 } 1085 1086 Element formatDocumentationComment2(string comment, Decl decl, string tagName = "div", DocCommentTerminationCondition* termination = null) { 1087 Element div; 1088 if(tagName is null) 1089 div = new DocumentFragment(null); 1090 else 1091 div = Element.make(tagName); 1092 1093 string currentParagraph; 1094 void putch(char c) { 1095 currentParagraph ~= c; 1096 } 1097 void put(in char[] c) { 1098 currentParagraph ~= c; 1099 } 1100 1101 string currentTag = (tagName is null) ? null : "p"; 1102 string currentClass = null; 1103 1104 void commit() { 1105 auto cp = currentParagraph.strip; 1106 if(cp.length) { 1107 if(currentTag is null) 1108 div.appendHtml(cp); 1109 else { 1110 if(currentTag == "p") { 1111 // check for a markdown style header 1112 auto test = cp.strip; 1113 auto lines = test.splitLines(); 1114 if(lines.length == 2) { 1115 auto hdr = lines[0].strip; 1116 auto equals = lines[1].strip; 1117 if(equals.length >= 4) { 1118 foreach(ch; equals) 1119 if(ch != '=') 1120 goto not_special; 1121 } else { 1122 goto not_special; 1123 } 1124 1125 if(hdr.length == 0) 1126 goto not_special; 1127 1128 // passed tests, I'll allow it: 1129 currentTag = "h3"; 1130 currentClass = "user-header"; 1131 cp = hdr; 1132 } else if(lines.length == 1) { 1133 int hashCount = 0; 1134 foreach(idx, ch; test) { 1135 if(ch == '#') 1136 hashCount++; 1137 else if(ch == ' ' && hashCount > 0) { 1138 // it was special! 1139 // there must be text after btw or else strip would have cut the space too 1140 currentTag = "h" ~ to!string(hashCount); 1141 currentClass = "user-header"; 1142 cp = test[idx + 1 .. $]; 1143 1144 break; 1145 } else 1146 break; // not special 1147 } 1148 } 1149 } 1150 1151 not_special: 1152 div.addChild(currentTag, Html(cp), currentClass); 1153 } 1154 } 1155 currentTag = "p"; 1156 currentParagraph = null; 1157 currentClass = null; 1158 } 1159 1160 bool atStartOfLine = true; 1161 bool earlyTermination; 1162 1163 main_loop: 1164 for(size_t idx = 0; idx < comment.length; idx++) { 1165 auto ch = comment[idx]; 1166 auto remaining = comment[idx .. $]; 1167 1168 if(termination !is null) { 1169 if((termination.mustBeAtStartOfLine && atStartOfLine) || !termination.mustBeAtStartOfLine) { 1170 foreach(ts; termination.terminationStrings) { 1171 if(remaining.startsWith(ts)) { 1172 termination.remaining = remaining[ts.length .. $]; 1173 earlyTermination = true; 1174 termination.terminatedOn = ts; 1175 break main_loop; 1176 } 1177 } 1178 } 1179 } 1180 1181 switch(ch) { 1182 case '\r': 1183 continue; // don't care even a little about Windows vs Unix line endings 1184 case ' ', '\t': 1185 goto useCharWithoutTriggeringStartOfLine; 1186 case '\n': 1187 if(atStartOfLine) 1188 commit(); 1189 atStartOfLine = true; 1190 goto useCharWithoutTriggeringStartOfLine; 1191 break; 1192 // these need to be html encoded 1193 case '"': 1194 put("""); 1195 atStartOfLine = false; 1196 break; 1197 case '<': 1198 // FIXME: support just a wee bit of inline html... 1199 put("<"); 1200 atStartOfLine = false; 1201 break; 1202 case '>': 1203 put(">"); 1204 atStartOfLine = false; 1205 break; 1206 case '&': 1207 put("&"); 1208 atStartOfLine = false; 1209 break; 1210 // special syntax 1211 case '`': 1212 // code. ` is inline, ``` is block. 1213 if(atStartOfLine && remaining.startsWith("```")) { 1214 idx += 3; 1215 remaining = remaining[3 .. $]; 1216 bool braced = false; 1217 if(remaining.length && remaining[0] == '{') { 1218 braced = true; 1219 remaining = remaining[1 .. $]; 1220 } 1221 1222 auto line = remaining.indexOf("\n"); 1223 string language; 1224 if(line != -1) { 1225 language = remaining[0 .. line].strip; 1226 remaining = remaining[line + 1 .. $]; 1227 idx += line + 1; 1228 } 1229 1230 size_t ending; 1231 size_t sliceEnding; 1232 if(braced) { 1233 int count = 1; 1234 while(ending < remaining.length) { 1235 if(remaining[ending] == '{') 1236 count++; 1237 if(remaining[ending] == '}') 1238 count--; 1239 1240 if(count == 0) 1241 break; 1242 ending++; 1243 } 1244 1245 sliceEnding = ending; 1246 1247 while(ending < remaining.length) { 1248 if(remaining[ending] == '\n') { 1249 ending++; 1250 break; 1251 } 1252 ending++; 1253 } 1254 1255 } else { 1256 ending = remaining.indexOf("```\n"); 1257 if(ending != -1) { 1258 sliceEnding = ending; 1259 ending += 4; // skip \n 1260 } else { 1261 ending = remaining.indexOf("```\r"); 1262 1263 if(ending != -1) { 1264 sliceEnding = ending; 1265 ending += 5; // skip \r\n 1266 } else { 1267 ending = remaining.length; 1268 sliceEnding = ending; 1269 } 1270 } 1271 } 1272 1273 if(currentTag == "p") 1274 commit(); 1275 // FIXME: we can prolly do more with languages... 1276 string code = outdent(stripRight(remaining[0 .. sliceEnding])); 1277 Element ele; 1278 // all these languages are close enough for hack good enough. 1279 if(language == "javascript" || language == "c" || language == "c++" || language == "java" || language == "php" || language == "c#" || language == "d" || language == "adrscript" || language == "json") 1280 ele = div.addChild("pre", syntaxHighlightCFamily(code, language)); 1281 else if(language == "css") 1282 ele = div.addChild("pre", syntaxHighlightCss(code)); 1283 else if(language == "html" || language == "xml") 1284 ele = div.addChild("pre", syntaxHighlightHtml(code, language)); 1285 else if(language == "python") 1286 ele = div.addChild("pre", syntaxHighlightPython(code)); 1287 else if(language == "ruby") 1288 ele = div.addChild("pre", syntaxHighlightRuby(code)); 1289 else if(language == "sdlang") 1290 ele = div.addChild("pre", syntaxHighlightSdlang(code)); 1291 else 1292 ele = div.addChild("pre", code); 1293 ele.addClass("block-code"); 1294 ele.dataset.language = language; 1295 idx += ending; 1296 } else { 1297 // check for inline `code` style 1298 bool foundIt = false; 1299 size_t foundItWhere = 0; 1300 foreach(i, scout; remaining[1 .. $]) { 1301 if(scout == '\n') 1302 break; 1303 if(scout == '`') { 1304 foundIt = true; 1305 foundItWhere = i; 1306 break; 1307 } 1308 } 1309 1310 if(!foundIt) 1311 goto ordinary; 1312 1313 atStartOfLine = false; 1314 auto slice = remaining[1 .. foundItWhere + 1]; 1315 idx += 1 + foundItWhere; 1316 1317 if(slice.length == 0) { 1318 // empty code block is `` - doubled backtick. Just 1319 // treat that as a literal (escaped) backtick. 1320 putch('`'); 1321 } else { 1322 auto ele = Element.make("tt").addClass("inline-code"); 1323 ele.innerText = slice; 1324 put(ele.toString()); 1325 } 1326 } 1327 break; 1328 case '$': 1329 // ddoc macro. May be magical. 1330 auto info = macroInformation(remaining); 1331 if(info.linesSpanned == 0) { 1332 goto ordinary; 1333 } 1334 1335 atStartOfLine = false; 1336 1337 auto name = remaining[2 .. info.macroNameEndingOffset]; 1338 1339 auto dzeroAdjustment = (info.textBeginningOffset == info.terminatingOffset) ? 0 : 1; 1340 auto text = remaining[info.textBeginningOffset .. info.terminatingOffset - dzeroAdjustment]; 1341 1342 if(name in ddocMacroBlocks) { 1343 commit(); 1344 1345 auto got = expandDdocMacros(remaining, decl); 1346 div.appendChild(got); 1347 } else { 1348 put(expandDdocMacros(remaining, decl).toString()); 1349 } 1350 1351 idx += info.terminatingOffset - 1; 1352 break; 1353 case '-': 1354 // ddoc style code iff --- at start of line. 1355 if(!atStartOfLine) { 1356 goto ordinary; 1357 } 1358 if(remaining.startsWith("---")) { 1359 // a code sample has started 1360 if(currentTag == "p") 1361 commit(); 1362 div.addChild("pre", extractDdocCodeExample(remaining, idx)).addClass("d_code highlighted"); 1363 atStartOfLine = true; 1364 } else 1365 goto ordinary; 1366 break; 1367 case '!': 1368 // possible markdown-style image 1369 if(remaining.length > 1 && remaining[1] == '[') { 1370 auto inside = remaining[1 .. $]; 1371 auto txt = extractBalanceOnSingleLine(inside); 1372 if(txt is null) 1373 goto ordinary; 1374 if(txt.length < inside.length && inside[txt.length] == '(') { 1375 // possible markdown image 1376 auto parens = extractBalanceOnSingleLine(inside[txt.length .. $]); 1377 if(parens.length) { 1378 auto a = Element.make("img"); 1379 a.alt = txt[1 .. $-1]; 1380 a.src = parens[1 .. $-1]; 1381 put(a.toString()); 1382 1383 idx ++; // skip the ! 1384 idx += parens.length; 1385 idx += txt.length; 1386 idx --; // so the ++ in the for loop brings us back to i 1387 break; 1388 } 1389 } 1390 } 1391 goto ordinary; 1392 break; 1393 case '[': 1394 // wiki-style reference iff [text] or [text|other text] 1395 // or possibly markdown style link: [text](url) 1396 // text MUST be either a valid D identifier chain, optionally with a section hash, 1397 // or a fully-encoded http url. 1398 // text may never include ']' or '|' or whitespace or ',' and must always start with '_' or alphabetic (like a D identifier or http url) 1399 // other text may include anything except the string ']' 1400 // it balances [] inside it. 1401 1402 auto txt = extractBalanceOnSingleLine(remaining); 1403 if(txt is null) 1404 goto ordinary; 1405 if(txt.length < remaining.length && remaining[txt.length] == '(') { 1406 // possible markdown link 1407 auto parens = extractBalanceOnSingleLine(remaining[txt.length .. $]); 1408 if(parens.length) { 1409 auto a = Element.make("a", txt[1 .. $-1], parens[1 .. $-1]); 1410 put(a.toString()); 1411 1412 idx += parens.length; 1413 idx += txt.length; 1414 idx --; // so the ++ in the for loop brings us back to i 1415 break; 1416 } 1417 } 1418 1419 auto fun = txt[1 .. $-1]; 1420 string rt; 1421 auto idx2 = fun.indexOf("|"); 1422 if(idx2 != -1) { 1423 rt = fun[idx2 + 1 .. $].strip; 1424 fun = fun[0 .. idx2].strip; 1425 } 1426 1427 if(fun.all!((ch) => ch >= '0' && ch <= '9')) { 1428 // footnote reference 1429 auto lri = getLinkReference(fun, decl); 1430 if(lri.type == LinkReferenceInfo.Type.none) 1431 goto ordinary; 1432 put(lri.toHtml(fun, rt)); 1433 } else if(!fun.isIdentifierOrUrl()) { 1434 goto ordinary; 1435 } else { 1436 auto lri = getLinkReference(fun, decl); 1437 if(lri.type == LinkReferenceInfo.Type.none) { 1438 if(auto l = getSymbolGroupReference(fun, decl, rt)) { 1439 put(l.toString()); 1440 } else { 1441 put(getReferenceLink(fun, decl, rt).toString); 1442 } 1443 } else 1444 put(lri.toHtml(fun, rt)); 1445 } 1446 1447 idx += txt.length; 1448 idx --; // so the ++ in the for loop brings us back to i 1449 break; 1450 case 'h': 1451 // automatically linkify web URLs pasted in 1452 if(tagName is null) 1453 goto ordinary; 1454 import std.uri; 1455 auto l = uriLength(remaining); 1456 if(l == -1) 1457 goto ordinary; 1458 auto url = remaining[0 .. l]; 1459 put("<a href=\"" ~ url ~ "\">" ~ url ~ "</a>"); 1460 idx += url.length; 1461 idx --; // so the ++ puts us at the end 1462 break; 1463 case '_': 1464 // is it that stupid ddocism where a PSYMBOL is prefixed with _? 1465 // still, really, I want to KILL this completely, I hate it a lot 1466 if(idx && !isIdentifierChar(1, comment[idx-1]) && checkStupidDdocIsm(remaining[1..$], decl)) 1467 break; 1468 else 1469 goto ordinary; 1470 break; 1471 /* 1472 /+ I'm just not happy with this yet because it thinks 1473 1474 Paragraph 1475 * list item 1476 After stuff 1477 1478 the After stuff is part of the list item. Blargh. 1479 1480 +/ 1481 case '*': 1482 // potential list item 1483 if(atStartOfLine) { 1484 commit(); 1485 currentTag = "li"; 1486 atStartOfLine = false; 1487 continue; 1488 } else { 1489 goto ordinary; 1490 } 1491 break; 1492 */ 1493 default: 1494 ordinary: 1495 atStartOfLine = false; 1496 useCharWithoutTriggeringStartOfLine: 1497 putch(ch); 1498 } 1499 } 1500 1501 commit(); 1502 1503 if(termination !is null && !earlyTermination) 1504 termination.remaining = null; 1505 1506 /* 1507 if(div.firstChild !is null && div.firstChild.nodeType == NodeType.Text 1508 && div.firstChild.nextSibling !is null && div.firstChild.nextSibling.tagName == "p") 1509 div.firstChild.wrapIn(Element.make("p")); 1510 */ 1511 1512 return div; 1513 } 1514 1515 bool checkStupidDdocIsm(string remaining, Decl decl) { 1516 size_t i; 1517 foreach(idx, dchar ch; remaining) { 1518 if(!isIdentifierChar(idx, ch)) { 1519 i = idx; 1520 break; 1521 } 1522 } 1523 1524 string ident = remaining[0 .. i]; 1525 if(ident.length == 0) 1526 return false; 1527 1528 // check this name 1529 if(ident == decl.name) { 1530 import std.stdio; writeln("stupid ddocism(1): ", ident); 1531 return true; 1532 } 1533 1534 if(decl.isModule) { 1535 auto name = decl.name; 1536 auto idx = name.lastIndexOf("."); 1537 if(idx != -1) { 1538 name = name[idx + 1 .. $]; 1539 } 1540 1541 if(ident == name) { 1542 import std.stdio; writeln("stupid ddocism(2): ", ident); 1543 return true; 1544 } 1545 } 1546 1547 // check the whole scope too, I think dmd does... maybe 1548 if(decl.parentModule && decl.parentModule.lookupName(ident) !is null) { 1549 import std.stdio; writeln("stupid ddocism(3): ", ident); 1550 return true; 1551 } 1552 1553 // FIXME: params? 1554 1555 return false; 1556 } 1557 1558 string extractBalanceOnSingleLine(string txt) { 1559 if(txt.length == 0) return null; 1560 char starter = txt[0]; 1561 char terminator; 1562 switch(starter) { 1563 case '[': 1564 terminator = ']'; 1565 break; 1566 case '(': 1567 terminator = ')'; 1568 break; 1569 case '{': 1570 terminator = '}'; 1571 break; 1572 default: 1573 return null; 1574 } 1575 1576 int count; 1577 foreach(idx, ch; txt) { 1578 if(ch == '\n') 1579 return null; 1580 if(ch == starter) 1581 count++; 1582 else if(ch == terminator) 1583 count--; 1584 1585 if(count == 0) 1586 return txt[0 .. idx + 1]; 1587 } 1588 1589 return null; // unbalanced prolly 1590 } 1591 1592 Html extractDdocCodeExample(string comment, ref size_t idx) { 1593 assert(comment.startsWith("---")); 1594 auto i = comment.indexOf("\n"); 1595 if(i == -1) 1596 return Html(htmlEncode(comment).idup); 1597 comment = comment[i + 1 .. $]; 1598 idx += i + 1; 1599 1600 LexerConfig config; 1601 StringCache stringCache = StringCache(128); 1602 1603 config.stringBehavior = StringBehavior.source; 1604 config.whitespaceBehavior = WhitespaceBehavior.include; 1605 1606 ubyte[] lies = cast(ubyte[]) comment; 1607 1608 // I'm tokenizing to handle stuff like --- inside strings and 1609 // comments inside the code. If we hit an "operator" -- followed by - 1610 // or -- followed by --, right after some whitespace including a line, 1611 // we're done. 1612 bool justSawNewLine = true; 1613 bool justSawDashDash = false; 1614 size_t terminatingIndex; 1615 lexing: 1616 foreach(token; byToken(lies, config, &stringCache)) { 1617 if(justSawDashDash) { 1618 if(token == tok!"--" || token == tok!"-") { 1619 break lexing; 1620 } 1621 } 1622 1623 if(justSawNewLine) { 1624 if(token == tok!"--") { 1625 justSawDashDash = true; 1626 justSawNewLine = false; 1627 terminatingIndex = token.index; 1628 continue lexing; 1629 } 1630 } 1631 1632 if(token == tok!"whitespace") { 1633 foreach(txt; token.text) 1634 if(txt == '\n') { 1635 justSawNewLine = true; 1636 continue lexing; 1637 } 1638 } 1639 1640 justSawNewLine = false; 1641 } 1642 1643 i = comment.indexOf('\n', terminatingIndex); 1644 if(i == -1) 1645 i = comment.length; 1646 1647 idx += i; 1648 1649 comment = comment[0 .. terminatingIndex]; 1650 1651 return Html(outdent(highlight(comment.stripRight))); 1652 } 1653 1654 string[string] ddocMacros; 1655 int[string] ddocMacroInfo; 1656 int[string] ddocMacroBlocks; 1657 1658 static this() { 1659 ddocMacroBlocks = [ 1660 "ADRDOX_SAMPLE" : 1, 1661 "LIST": 1, 1662 "NUMBERED_LIST": 1, 1663 "SMALL_TABLE" : 1, 1664 "TABLE_ROWS" : 1, 1665 "EMBED_UNITTEST": 1, 1666 1667 "TIP":1, 1668 "NOTE":1, 1669 "WARNING":1, 1670 "PITFALL":1, 1671 "SIDEBAR":1, 1672 "CONSOLE":1, 1673 "H1":1, 1674 "H2":1, 1675 "H3":1, 1676 "H4":1, 1677 "H5":1, 1678 "H5":1, 1679 "HR":1, 1680 "BOOKTABLE":1, 1681 "T2":1, 1682 1683 "TR" : 1, 1684 "TH" : 1, 1685 "TD" : 1, 1686 "TABLE" : 1, 1687 "TDNW" : 1, 1688 "LEADINGROWN" : 1, 1689 1690 "UL" : 1, 1691 "OL" : 1, 1692 "LI" : 1, 1693 1694 "P" : 1, 1695 "PRE" : 1, 1696 "BLOCKQUOTE" : 1, 1697 "DL" : 1, 1698 "DT" : 1, 1699 "DD" : 1, 1700 "POST" : 1, 1701 "DIV" : 1, 1702 "SIDE_BY_SIDE" : 1, 1703 "COLUMN" : 1, 1704 1705 "DIVC" : 1, 1706 1707 // std.regex 1708 "REG_ROW": 1, 1709 "REG_TITLE": 1, 1710 "REG_TABLE": 1, 1711 "REG_START": 1, 1712 "SECTION": 1, 1713 1714 // std.math 1715 "TABLE_SV" : 1, 1716 "TABLE_DOMRG": 2, 1717 "DOMAIN": 1, 1718 "RANGE": 1, 1719 "SVH": 1, 1720 "SV": 1, 1721 1722 "UDA_USES" : 1, 1723 "C_HEADER_DESCRIPTION": 1, 1724 ]; 1725 1726 ddocMacros = [ 1727 "FULLY_QUALIFIED_NAME" : "MAGIC", // decl.fullyQualifiedName, 1728 "MODULE_NAME" : "MAGIC", // decl.parentModule.fullyQualifiedName, 1729 "D" : "<tt class=\"D\">$0</tt>", // this is magical! D is actually handled in code. 1730 "REF" : `<a href="$0.html">$0</a>`, // this is magical! Handles ref in code 1731 1732 "ALWAYS_DOCUMENT" : "", 1733 1734 "UDA_USES" : "", 1735 1736 "TIP" : "<div class=\"tip\">$0</div>", 1737 "NOTE" : "<div class=\"note\">$0</div>", 1738 "WARNING" : "<div class=\"warning\">$0</div>", 1739 "PITFALL" : "<div class=\"pitfall\">$0</div>", 1740 "SIDEBAR" : "<div class=\"sidebar\"><aside>$0</aside></div>", 1741 "CONSOLE" : "<pre class=\"console\">$0</pre>", 1742 1743 // headers have meaning to the table of contents generator 1744 "H1" : "<h1 class=\"user-header\">$0</h1>", 1745 "H2" : "<h2 class=\"user-header\">$0</h2>", 1746 "H3" : "<h3 class=\"user-header\">$0</h3>", 1747 "H4" : "<h4 class=\"user-header\">$0</h4>", 1748 "H5" : "<h5 class=\"user-header\">$0</h5>", 1749 "H6" : "<h6 class=\"user-header\">$0</h6>", 1750 1751 "BLOCKQUOTE" : "<blockquote>$0</blockquote>", // FIXME? 1752 1753 "HR" : "<hr />", 1754 1755 "DASH" : "-", 1756 "NOTHING" : "", // used to escape magic syntax sometimes 1757 1758 // DDoc just expects these. 1759 "AMP" : "&", 1760 "LT" : "<", 1761 "GT" : ">", 1762 "LPAREN" : "(", 1763 "RPAREN" : ")", 1764 "DOLLAR" : "$", 1765 "BACKTICK" : "`", 1766 "COMMA": ",", 1767 "COLON": ":", 1768 "ARGS" : "$0", 1769 1770 // support for my docs' legacy components, will be removed before too long. 1771 "DDOC_ANCHOR" : "<a id=\"$0\" href=\"$(FULLY_QUALIFIED_NAME).html#$0\">$0</a>", 1772 1773 "DDOC_COMMENT" : "", 1774 1775 // this is support for the Phobos docs 1776 1777 // useful parts 1778 "BOOKTABLE" : "<table class=\"phobos-booktable\"><caption>$1</caption>$+</table>", 1779 "T2" : "<tr><td>$(LREF $1)</td><td>$+</td></tr>", 1780 1781 "BIGOH" : "<span class=\"big-o\"><i>O</i>($0)</span>", 1782 1783 "TR" : "<tr>$0</tr>", 1784 "TH" : "<th>$0</th>", 1785 "TD" : "<td>$0</td>", 1786 "TABLE": "<table>$0</table>", 1787 "TDNW" : "<td style=\"white-space: nowrap;\">$0</td>", 1788 "LEADINGROWN":"<tr class=\"leading-row\"><th colspan=\"$1\">$2</th></tr>", 1789 "LEADINGROW":"<tr class=\"leading-row\"><th colspan=\"2\">$0</th></tr>", 1790 1791 "UL" : "<ul>$0</ul>", 1792 "OL" : "<ol>$0</ol>", 1793 "LI" : "<li>$0</li>", 1794 1795 "BR" : "<br />", 1796 "I" : "<i>$0</i>", 1797 "EM" : "<em>$0</em>", 1798 "STRONG" : "<strong>$0</strong>", 1799 "TT" : "<tt>$0</tt>", 1800 "B" : "<b>$0</b>", 1801 "STRIKE" : "<s>$0</s>", 1802 "P" : "<p>$0</p>", 1803 "PRE" : "<pre>$0</pre>", 1804 "DL" : "<dl>$0</dl>", 1805 "DT" : "<dt>$0</dt>", 1806 "DD" : "<dd>$0</dd>", 1807 "LINK2" : "<a href=\"$1\">$2</a>", 1808 "LINK" : `<a href="$0">$0</a>`, 1809 1810 "DDLINK": "<a href=\"http://dlang.org/$1\">$3</a>", 1811 "DDSUBLINK": "<a href=\"http://dlang.org/$1#$2\">$3</a>", 1812 1813 "IMG": "<img src=\"$1\" alt=\"$2\" />", 1814 1815 "BLUE" : "<span style=\"color: blue;\">$0</span>", 1816 "RED" : "<span style=\"color: red;\">$0</span>", 1817 1818 "SCRIPT" : "", 1819 1820 // Useless crap that should just be replaced 1821 "REF_SHORT" : `<a href="$2.$3.$1.html">$1</a>`, 1822 "SHORTREF" : `<a href="$2.$3.$1.html">$1</a>`, 1823 "SUBMODULE" : `<a href="$(FULLY_QUALIFIED_NAME).$2.html">$1</a>`, 1824 "SHORTXREF" : `<a href="std.$1.$2.html">$2</a>`, 1825 "SHORTXREF_PACK" : `<a href="std.$1.$2.$3.html">$3</a>`, 1826 "XREF" : `<a href="std.$1.$2.html">std.$1.$2</a>`, 1827 "CXREF" : `<a href="core.$1.$2.html">core.$1.$2</a>`, 1828 "XREF_PACK" : `<a href="std.$1.$2.$3.html">std.$1.$2.$3</a>`, 1829 "MYREF" : `<a href="$(FULLY_QUALIFIED_NAME).$0.html">$0</a>`, 1830 "LREF" : `<a class="symbol-reference" href="$(MODULE_NAME).$0.html">$0</a>`, 1831 "LREF2" : "MAGIC", 1832 "MREF" : `MAGIC`, 1833 "WEB" : `<a href="http://$1">$2</a>`, 1834 "HTTP" : `<a href="http://$1">$2</a>`, 1835 "BUGZILLA": `<a href="https://issues.dlang.org/show_bug.cgi?id=$1">https://issues.dlang.org/show_bug.cgi?id=$1</a>`, 1836 "XREF_PACK_NAMED" : `<a href="std.$1.$2.$3.html">$4</a>`, 1837 1838 "D_INLINECODE" : `<tt>$1</tt>`, 1839 "D_STRING" : `<tt>$1</tt>`, 1840 "D_CODE_STRING" : `<tt>$1</tt>`, 1841 "D_CODE" : `<pre>$0</pre>`, 1842 "HTTPS" : `<a href="https://$1">$2</a>`, 1843 1844 "RES": `<i>result</i>`, 1845 "POST": `<div class="postcondition">$0</div>`, 1846 "COMMENT" : ``, 1847 1848 "DIV" : `<div>$0</div>`, 1849 "SIDE_BY_SIDE" : `<table class="side-by-side"><tbody><tr>$0</tr></tbody></table>`, 1850 "COLUMN" : `<td>$0</td>`, 1851 "DIVC" : `<div class="$1">$+</div>`, 1852 1853 // std.regex 1854 "REG_ROW":`<tr><td><i>$1</i></td><td>$+</td></tr>`, 1855 "REG_TITLE":`<tr><td><b>$1</b></td><td><b>$2</b></td></tr>`, 1856 "REG_TABLE":`<table border="1" cellspacing="0" cellpadding="5" > $0 </table>`, 1857 "REG_START":`<h3><div align="center"> $0 </div></h3>`, 1858 "SECTION":`<h3><a id="$0">$0</a></h3>`, 1859 "S_LINK":`<a href="#$1">$+</a>`, 1860 1861 // std.math 1862 1863 "TABLE_SV": `<table class="std_math special-values"><caption>Special Values</caption>$0</table>`, 1864 "SVH" : `<tr><th>$1</th><th>$2</th></tr>`, 1865 "SV" : `<tr><td>$1</td><td>$2</td></tr>`, 1866 "TABLE_DOMRG": `<table class="std_math domain-and-range">$1 $2</table>`, 1867 "DOMAIN": `<tr><th>Domain</th><td>$0</td></tr>`, 1868 "RANGE": `<tr><th>Range</th><td>$0</td></tr>`, 1869 1870 "PI": "\u03c0", 1871 "INFIN": "\u221e", 1872 "SUB": "<span>$1<sub>$2</sub></span>", 1873 "SQRT" : "\u221a", 1874 "SUPERSCRIPT": "<sup>$1</sup>", 1875 "POWER": "<span>$1<sup>$2</sup></span>", 1876 "NAN": "<span class=\"nan\">NaN</span>", 1877 "PLUSMN": "\u00b1", 1878 "GLOSSARY": `<a href="http://dlang.org/glosary.html#$0">$0</a>`, 1879 "PHOBOSSRC": `<a href="https://github.com/dlang/phobos/blob/master/$0">$0</a>`, 1880 "DRUNTIMESRC": `<a href="https://github.com/dlang/druntime/blob/master/src/$0">$0</a>`, 1881 "C_HEADER_DESCRIPTION": `<p>This module contains bindings to selected types and functions from the standard C header <a href="http://$1"><$2></a>. Note that this is not automatically generated, and may omit some types/functions from the original C header.</p>`, 1882 ]; 1883 1884 ddocMacroInfo = [ // number of arguments expected, if needed 1885 "EMBED_UNITTEST": 1, 1886 "BOOKTABLE" : 1, 1887 "T2" : 1, 1888 "LEADINGROWN" : 2, 1889 "WEB" : 2, 1890 "HTTP" : 2, 1891 "BUGZILLA" : 1, 1892 "XREF_PACK_NAMED" : 4, 1893 "D_INLINECODE" : 1, 1894 "D_STRING" : 1, 1895 "D_CODE_STRING": 1, 1896 "D_CODE": 1, 1897 "HTTPS": 2, 1898 "DIVC" : 1, 1899 "LINK2" : 2, 1900 "DDLINK" : 3, 1901 "DDSUBLINK" : 3, 1902 "IMG" : 2, 1903 "XREF" : 2, 1904 "CXREF" : 2, 1905 "SHORTXREF" : 2, 1906 "SHORTREF": 3, 1907 "REF_SHORT": 3, 1908 "SUBMODULE" : 2, 1909 "SHORTXREF_PACK" : 3, 1910 "XREF_PACK" : 3, 1911 "MREF" : 1, 1912 "LREF2" : 2, 1913 "C_HEADER_DESCRIPTION": 2, 1914 1915 "REG_ROW" : 1, 1916 "REG_TITLE" : 2, 1917 "S_LINK" : 1, 1918 "PI": 1, 1919 "SUB": 2, 1920 "SQRT" : 1, 1921 "SUPERSCRIPT": 1, 1922 "POWER": 2, 1923 "NAN": 1, 1924 "PLUSMN": 1, 1925 "SVH" : 2, 1926 "SV": 2, 1927 ]; 1928 } 1929 1930 string formatDocumentationComment(string comment, Decl decl) { 1931 // remove that annoying ddocism to suppress its auto-highlight anti-feature 1932 // FIXME 1933 /* 1934 auto psymbols = fullyQualifiedName[$-1].split("."); 1935 // also trim off our .# for overload 1936 import std.uni :isAlpha; 1937 auto psymbol = (psymbols.length && psymbols[$-1].length) ? 1938 ((psymbols[$-1][0].isAlpha || psymbols[$-1][0] == '_') ? psymbols[$-1] : psymbols[$-2]) 1939 : 1940 null; 1941 if(psymbol.length) 1942 comment = comment.replace("_" ~ psymbol, psymbol); 1943 */ 1944 1945 auto data = formatDocumentationComment2(comment, decl).toString(); 1946 1947 return data; 1948 } 1949 1950 1951 // This is kinda deliberately not recursive right now. I might change that later but I want to keep it 1952 // simple to get decent results while keeping the door open to dropping ddoc macros. 1953 Element expandDdocMacros(string txt, Decl decl) { 1954 auto e = expandDdocMacros2(txt, decl); 1955 1956 translateMagicCommands(e); 1957 1958 return e; 1959 } 1960 1961 void translateMagicCommands(Element container, Element applyTo = null) { 1962 foreach(cmd; container.querySelectorAll("magic-command")) { 1963 Element subject; 1964 if(applyTo is null) { 1965 subject = cmd.parentNode; 1966 if(subject is null) 1967 continue; 1968 if(subject.tagName == "caption") 1969 subject = subject.parentNode; 1970 if(subject is null) 1971 continue; 1972 } else { 1973 subject = applyTo; 1974 } 1975 1976 if(cmd.className.length) { 1977 subject.addClass(cmd.className); 1978 } 1979 if(cmd.id.length) 1980 subject.id = cmd.id; 1981 cmd.removeFromTree(); 1982 } 1983 } 1984 1985 Element expandDdocMacros2(string txt, Decl decl) { 1986 auto macros = ddocMacros; 1987 auto numberOfArguments = ddocMacroInfo; 1988 1989 auto userDefinedMacros = decl.parsedDocComment.userDefinedMacros; 1990 1991 string[string] availableMacros; 1992 int[string] availableNumberOfArguments; 1993 1994 if(userDefinedMacros.length) { 1995 foreach(name, value; userDefinedMacros) { 1996 availableMacros[name] = value; 1997 int count; 1998 foreach(idx, ch; value) { 1999 if(ch == '$') { 2000 if(idx + 1 < value.length) { 2001 char n = value[idx + 1]; 2002 if(n >= '0' && n <= '9') { 2003 if(n - '0' > count) 2004 count = n - '0'; 2005 } 2006 } 2007 } 2008 } 2009 if(count) 2010 availableNumberOfArguments[name] = count; 2011 } 2012 2013 foreach(name, value; macros) 2014 availableMacros[name] = value; 2015 foreach(name, n; numberOfArguments) 2016 availableNumberOfArguments[name] = n; 2017 } else { 2018 availableMacros = macros; 2019 availableNumberOfArguments = numberOfArguments; 2020 } 2021 2022 auto idx = 0; 2023 if(txt[idx] == '$') { 2024 auto info = macroInformation(txt[idx .. $]); 2025 if(info.linesSpanned == 0) 2026 assert(0); 2027 2028 if(idx + 2 > txt.length) 2029 assert(0); 2030 if(idx + info.macroNameEndingOffset > txt.length) 2031 assert(0, txt[idx .. $]); 2032 if(info.macroNameEndingOffset < 2) 2033 assert(0, txt[idx..$]); 2034 2035 auto name = txt[idx + 2 .. idx + info.macroNameEndingOffset]; 2036 2037 auto dzeroAdjustment = (info.textBeginningOffset == info.terminatingOffset) ? 0 : 1; 2038 auto stuff = txt[idx + info.textBeginningOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2039 auto stuffNonStripped = txt[idx + info.macroNameEndingOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2040 string replacement; 2041 2042 if(name == "RAW_HTML") { 2043 // magic: user-defined html 2044 auto holder = Element.make("div", "", "user-raw-html"); 2045 holder.innerHTML = stuff; 2046 return holder; 2047 } 2048 2049 if(name == "ID") 2050 return Element.make("magic-command").setAttribute("id", stuff); 2051 if(name == "CLASS") 2052 return Element.make("magic-command").setAttribute("class", stuff); 2053 2054 if(name == "EMBED_UNITTEST") { 2055 auto id = stuff.strip; 2056 foreach(ref ut; decl.getProcessedUnittests()) { 2057 if(ut.comment.canFind("$(ID " ~ id ~ ")")) { 2058 ut.embedded = true; 2059 return formatUnittestDocTuple(ut, decl); 2060 } 2061 } 2062 } 2063 2064 if(name == "SMALL_TABLE") { 2065 return translateSmallTable(stuff, decl); 2066 } 2067 if(name == "TABLE_ROWS") { 2068 return translateListTable(stuff, decl); 2069 } 2070 if(name == "LIST") { 2071 return translateList(stuff, decl, "ul"); 2072 } 2073 if(name == "NUMBERED_LIST") { 2074 return translateList(stuff, decl, "ol"); 2075 } 2076 2077 if(name == "MATH") { 2078 import adrdox.latex; 2079 auto got = mathToImgHtml(stuff); 2080 if(got is null) 2081 return Element.make("span", stuff, "user-math-render-failed"); 2082 return got; 2083 } 2084 2085 if(name == "ADRDOX_SAMPLE") { 2086 // show the original source as code 2087 // then render it for display side-by-side 2088 auto holder = Element.make("div"); 2089 holder.addClass("adrdox-sample"); 2090 2091 auto h = holder.addChild("div"); 2092 2093 h.addChild("pre", outdent(stuffNonStripped)); 2094 h.addChild("div", formatDocumentationComment2(stuff, decl)); 2095 2096 return holder; 2097 } 2098 2099 if(name == "D" || name == "D_PARAM" || name == "D_CODE") { 2100 // this is magic: syntax highlight it 2101 auto holder = Element.make(name == "D_CODE" ? "pre" : "tt"); 2102 holder.className = "D highlighted"; 2103 try { 2104 holder.innerHTML = linkUpHtml(highlight(stuff), decl); 2105 } catch(Throwable t) { 2106 holder.innerText = stuff; 2107 } 2108 return holder; 2109 } 2110 2111 if(name == "UDA_USES") { 2112 Element div = Element.make("dl"); 2113 foreach(d; declsByUda(decl.name, decl.parentModule)) { 2114 if(!d.docsShouldBeOutputted) 2115 continue; 2116 auto dt = div.addChild("dt"); 2117 dt.addChild("a", d.name, d.link); 2118 div.addChild("dd", Html(formatDocumentationComment(d.parsedDocComment.ddocSummary, d))); 2119 } 2120 return div; 2121 } 2122 2123 if(name == "MODULE_NAME") { 2124 return new TextNode(decl.parentModule.fullyQualifiedName); 2125 } 2126 if(name == "FULLY_QUALIFIED_NAME") { 2127 return new TextNode(decl.fullyQualifiedName); 2128 } 2129 if(name == "SUBREF") { 2130 auto cool = stuff.split(","); 2131 if(cool.length == 2) 2132 return getReferenceLink(decl.fullyQualifiedName ~ "." ~ cool[0].strip ~ "." ~ cool[1].strip, decl, cool[1].strip); 2133 } 2134 if(name == "MREF_ALTTEXT") { 2135 auto parts = split(stuff, ","); 2136 string cool; 2137 foreach(indx, p; parts[1 .. $]) { 2138 if(indx) cool ~= "."; 2139 cool ~= p.strip; 2140 } 2141 2142 return getReferenceLink(cool, decl, parts[0].strip); 2143 } 2144 if(name == "LREF2") { 2145 auto parts = split(stuff, ","); 2146 return getReferenceLink(parts[1].strip ~ "." ~ parts[0].strip, decl, parts[0].strip); 2147 } 2148 if(name == "REF_ALTTEXT") { 2149 auto parts = split(stuff, ","); 2150 string cool; 2151 foreach(indx, p; parts[2 .. $]) { 2152 if(indx) cool ~= "."; 2153 cool ~= p.strip; 2154 } 2155 cool ~= "." ~ parts[1].strip; 2156 2157 return getReferenceLink(cool, decl, parts[0].strip); 2158 } 2159 2160 if(name == "NBSP") { 2161 return new TextNode("\ "); 2162 } 2163 2164 if(name == "REF" || name == "MREF" || name == "LREF" || name == "MYREF") { 2165 // this is magic: do commas to dots then link it 2166 string cool; 2167 if(name == "REF") { 2168 auto parts = split(stuff, ","); 2169 parts = parts[1 .. $] ~ parts[0]; 2170 foreach(indx, p; parts) { 2171 if(indx) 2172 cool ~= "."; 2173 cool ~= p.strip; 2174 } 2175 } else 2176 cool = replace(stuff, ",", ".").replace(" ", ""); 2177 return getReferenceLink(cool, decl); 2178 } 2179 2180 2181 if(auto replacementRaw = name in availableMacros) { 2182 if(auto nargsPtr = name in availableNumberOfArguments) { 2183 auto nargs = *nargsPtr; 2184 replacement = *replacementRaw; 2185 2186 auto orig = txt[idx + info.textBeginningOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2187 2188 foreach(i; 1 .. nargs + 1) { 2189 int blargh = -1; 2190 2191 int parens; 2192 foreach(cidx, ch; stuff) { 2193 if(ch == '(') 2194 parens++; 2195 if(ch == ')') 2196 parens--; 2197 if(parens == 0 && ch == ',') { 2198 blargh = cast(int) cidx; 2199 break; 2200 } 2201 } 2202 if(blargh == -1) 2203 blargh = cast(int) stuff.length; 2204 //break; // insufficient args 2205 //blargh = stuff.length - 1; // trims off the closing paren 2206 2207 auto magic = stuff[0 .. blargh].strip; 2208 if(blargh < stuff.length) 2209 stuff = stuff[blargh + 1 .. $]; 2210 else 2211 stuff = stuff[$..$]; 2212 // FIXME: this should probably not be full replace each time. 2213 //if(magic.length) 2214 magic = formatDocumentationComment2(magic, decl, null).innerHTML; 2215 2216 if(name == "T2" && i == 1) 2217 replacement = replace(replacement, "$(LREF $1)", getReferenceLink(magic, decl).toString); 2218 else 2219 replacement = replace(replacement, "$" ~ cast(char)(i + '0'), magic); 2220 } 2221 2222 // FIXME: recursive replacement doesn't work... 2223 2224 auto awesome = stuff.strip; 2225 if(replacement.indexOf("$+") != -1) { 2226 awesome = formatDocumentationComment2(awesome, decl, null).innerHTML; 2227 replacement = replace(replacement, "$+", awesome); 2228 } 2229 2230 if(replacement.indexOf("$0") != -1) { 2231 awesome = formatDocumentationComment2(orig, decl, null).innerHTML; 2232 replacement = replace(*replacementRaw, "$0", awesome); 2233 } 2234 } else { 2235 // if there is any text, we slice off the ), otherwise, keep the empty string 2236 auto awesome = txt[idx + info.textBeginningOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2237 2238 if(name == "CONSOLE") { 2239 awesome = outdent(txt[idx + info.macroNameEndingOffset .. idx + info.terminatingOffset - dzeroAdjustment]).strip; 2240 awesome = awesome.replace("\n---", "\n$(NOTHING)---"); // disable ddoc embedded D code as it breaks for D exception messages 2241 } 2242 2243 2244 awesome = formatDocumentationComment2(awesome, decl, null).innerHTML; 2245 replacement = replace(*replacementRaw, "$0", awesome); 2246 } 2247 2248 Element element; 2249 if(replacement != "<" && replacement.strip.startsWith("<")) { 2250 element = Element.make("placeholder"); 2251 2252 element.innerHTML = replacement; 2253 element = element.childNodes[0]; 2254 element.parentNode = null; 2255 2256 foreach(k, v; element.attributes) 2257 element.attrs[k] = v.replace("$(FULLY_QUALIFIED_NAME)", decl.fullyQualifiedName).replace("$(MODULE_NAME)", decl.parentModule.fullyQualifiedName); 2258 } else { 2259 element = new TextNode(replacement); 2260 } 2261 2262 return element; 2263 } else { 2264 idx = idx + cast(int) info.terminatingOffset; 2265 auto textContent = txt[0 .. info.terminatingOffset]; 2266 return new TextNode(textContent); 2267 } 2268 } else assert(0); 2269 2270 2271 return null; 2272 } 2273 2274 struct MacroInformation { 2275 size_t macroNameEndingOffset; // starting is always 2 if it is actually a macro (check linesSpanned > 0) 2276 size_t textBeginningOffset; 2277 size_t terminatingOffset; 2278 int linesSpanned; 2279 } 2280 2281 // Scans the current macro 2282 MacroInformation macroInformation(in char[] str) { 2283 assert(str[0] == '$'); 2284 2285 MacroInformation info; 2286 info.terminatingOffset = str.length; 2287 2288 if (str.length < 2 || (str[1] != '(' && str[1] != '{')) 2289 return info; 2290 2291 auto open = str[1]; 2292 auto close = str[1] == '(' ? ')' : '}'; 2293 2294 bool readingMacroName = true; 2295 bool readingLeadingWhitespace; 2296 int parensCount = 0; 2297 foreach (idx, char ch; str) 2298 { 2299 if (readingMacroName && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',')) 2300 { 2301 readingMacroName = false; 2302 readingLeadingWhitespace = true; 2303 info.macroNameEndingOffset = idx; 2304 } 2305 2306 if (readingLeadingWhitespace && !(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')) 2307 { 2308 readingLeadingWhitespace = false; 2309 // This is looking past the macro name 2310 // so the offset of $(FOO bar) ought to be 2311 // the index of "bar" 2312 info.textBeginningOffset = idx; 2313 } 2314 2315 // so i want :) and :( not to count 2316 // also prolly 1) and 2) etc. 2317 2318 if (ch == '\n') 2319 info.linesSpanned++; 2320 if (ch == open) 2321 parensCount++; 2322 if (ch == close) 2323 { 2324 parensCount--; 2325 if (parensCount == 0) 2326 { 2327 if(readingMacroName) 2328 info.macroNameEndingOffset = idx; 2329 info.linesSpanned++; // counts the first line it is on 2330 info.terminatingOffset = idx + 1; 2331 if (info.textBeginningOffset == 0) 2332 info.textBeginningOffset = info.terminatingOffset; 2333 break; 2334 } 2335 } 2336 } 2337 2338 if(parensCount) 2339 info.linesSpanned = 0; // unterminated macro, tell upstream it is plain text 2340 2341 return info; 2342 } 2343 2344 2345 2346 string outdent(string s) { 2347 static import std..string; 2348 try { 2349 return join(std..string.outdent(splitLines(s)), "\n"); 2350 } catch(Exception) { 2351 return s; 2352 } 2353 } 2354 2355 2356 // Original code: 2357 // Copyright Brian Schott (Hackerpilot) 2012. 2358 // Distributed under the Boost Software License, Version 1.0. 2359 // (See accompanying file LICENSE_1_0.txt or copy at 2360 // http://www.boost.org/LICENSE_1_0.txt) 2361 // Adam modified 2362 2363 // http://ethanschoonover.com/solarized 2364 string highlight(string sourceCode) 2365 { 2366 import std.array; 2367 import dparse.lexer; 2368 string ret; 2369 2370 StringCache cache = StringCache(StringCache.defaultBucketCount); 2371 LexerConfig config; 2372 config.stringBehavior = StringBehavior.source; 2373 auto tokens = byToken(cast(ubyte[]) sourceCode, config, &cache); 2374 2375 2376 void writeSpan(string cssClass, string value, string dataIdent = "") 2377 { 2378 ret ~= `<span class="` ~ cssClass ~ `" `; 2379 if(dataIdent.length) 2380 ret ~= "data-ident=\""~dataIdent~"\""; 2381 ret ~= `>` ~ value.replace("&", "&").replace("<", "<") ~ `</span>`; 2382 } 2383 2384 2385 while (!tokens.empty) 2386 { 2387 auto t = tokens.front; 2388 tokens.popFront(); 2389 if (isBasicType(t.type)) 2390 writeSpan("type", str(t.type)); 2391 else if(t.text == "string") // string is a de-facto basic type, though it is technically just a user-defined identifier 2392 writeSpan("type", t.text); 2393 else if (isKeyword(t.type)) 2394 writeSpan("kwrd", str(t.type)); 2395 else if (t.type == tok!"comment") { 2396 auto txt = t.text; 2397 if(txt == "/* adrdox_highlight{ */") 2398 ret ~= "<span class=\"specially-highlighted\">"; 2399 else if(txt == "/* }adrdox_highlight */") 2400 ret ~= "</span>"; 2401 else 2402 writeSpan("com", t.text); 2403 } else if (isStringLiteral(t.type) || t.type == tok!"characterLiteral") { 2404 auto txt = t.text; 2405 if(txt.startsWith("q{")) { 2406 ret ~= "<span class=\"token-string-literal\"><span class=\"str\">q{</span>"; 2407 ret ~= highlight(txt[2 .. $ - 1]); 2408 ret ~= "<span class=\"str\">}</span></span>"; 2409 } else { 2410 writeSpan("str", txt); 2411 } 2412 } else if (isNumberLiteral(t.type)) 2413 writeSpan("num", t.text); 2414 else if (isOperator(t.type)) 2415 //writeSpan("op", str(t.type)); 2416 ret ~= htmlEncode(str(t.type)); 2417 else if (t.type == tok!"specialTokenSequence" || t.type == tok!"scriptLine") 2418 writeSpan("cons", t.text); 2419 else if (t.type == tok!"identifier") 2420 writeSpan("hid", t.text); 2421 else 2422 { 2423 ret ~= t.text.replace("<", "<"); 2424 } 2425 2426 } 2427 return ret; 2428 } 2429 2430 2431 2432 2433 import dparse.formatter; 2434 2435 class MyFormatter(Sink) : Formatter!Sink { 2436 Decl context; 2437 this(Sink sink, Decl context = null, bool useTabs = true, IndentStyle style = IndentStyle.otbs, uint indentWidth = 4) 2438 { 2439 this.context = context; 2440 super(sink, useTabs, style, indentWidth); 2441 } 2442 2443 void putTag(in char[] s) { 2444 static if(!__traits(compiles, sink.putTag(""))) 2445 sink.put(s); 2446 else 2447 sink.putTag(s); 2448 } 2449 2450 override void put(string s1) { 2451 auto s = cast(const(char)[]) s1; 2452 static if(!__traits(compiles, sink.putTag(""))) 2453 sink.put(s.htmlEncode); 2454 else 2455 sink.put(s); 2456 } 2457 2458 override void format(const AtAttribute atAttribute) { 2459 if(atAttribute is null || atAttribute.argumentList is null) 2460 super.format(atAttribute); 2461 else { 2462 sink.put("@"); 2463 if(atAttribute.argumentList.items.length == 1) { 2464 format(atAttribute.argumentList.items[0]); 2465 } else { 2466 format(atAttribute.argumentList); 2467 } 2468 } 2469 } 2470 2471 override void format(const TemplateParameterList templateParameterList) 2472 { 2473 putTag("<div class=\"parameters-list\">"); 2474 foreach(i, param; templateParameterList.items) 2475 { 2476 putTag("<div class=\"template-parameter-item parameter-item\">"); 2477 put("\t"); 2478 putTag("<span>"); 2479 format(param); 2480 putTag("</span>"); 2481 putTag("</div>"); 2482 } 2483 putTag("</div>"); 2484 } 2485 2486 override void format(const InStatement inStatement) { 2487 putTag("<a href=\"http://dpldocs.info/in-contract\" class=\"lang-feature\">"); 2488 put("in"); 2489 putTag("</a>"); 2490 //put(" "); 2491 if(inStatement.blockStatement) 2492 format(inStatement.blockStatement); 2493 else if(inStatement.expression) { 2494 put(" ("); 2495 format(inStatement.expression); 2496 put(")"); 2497 } 2498 } 2499 2500 override void format(const OutStatement outStatement) { 2501 putTag("<a href=\"http://dpldocs.info/out-contract\" class=\"lang-feature\">"); 2502 put("out"); 2503 putTag("</a>"); 2504 2505 //put(" "); 2506 if(outStatement.expression) { 2507 put(" ("); 2508 format(outStatement.parameter); 2509 put("; "); 2510 format(outStatement.expression); 2511 put(")"); 2512 } else { 2513 if (outStatement.parameter != tok!"") 2514 { 2515 put(" ("); 2516 format(outStatement.parameter); 2517 put(")"); 2518 } 2519 2520 format(outStatement.blockStatement); 2521 } 2522 } 2523 2524 override void format(const AssertExpression assertExpression) 2525 { 2526 debug(verbose) writeln("AssertExpression"); 2527 2528 /** 2529 AssignExpression assertion; 2530 AssignExpression message; 2531 **/ 2532 2533 with(assertExpression) 2534 { 2535 putTag("<a href=\"http://dpldocs.info/assertion\" class=\"lang-feature\">"); 2536 put("assert"); 2537 putTag("</a> ("); 2538 format(assertion); 2539 if (message) 2540 { 2541 put(", "); 2542 format(message); 2543 } 2544 put(")"); 2545 } 2546 } 2547 2548 2549 override void format(const TemplateAliasParameter templateAliasParameter) 2550 { 2551 debug(verbose) writeln("TemplateAliasParameter"); 2552 2553 /** 2554 Type type; 2555 Token identifier; 2556 Type colonType; 2557 AssignExpression colonExpression; 2558 Type assignType; 2559 AssignExpression assignExpression; 2560 **/ 2561 2562 with(templateAliasParameter) 2563 { 2564 putTag("<a href=\"http://dpldocs.info/template-alias-parameter\" class=\"lang-feature\">"); 2565 put("alias"); 2566 putTag("</a>"); 2567 put(" "); 2568 if (type) 2569 { 2570 format(type); 2571 space(); 2572 } 2573 format(identifier); 2574 if (colonType) 2575 { 2576 put(" : "); 2577 format(colonType); 2578 } 2579 else if (colonExpression) 2580 { 2581 put(" : "); 2582 format(colonExpression); 2583 } 2584 if (assignType) 2585 { 2586 put(" = "); 2587 format(assignType); 2588 } 2589 else if (assignExpression) 2590 { 2591 put(" = "); 2592 format(assignExpression); 2593 } 2594 } 2595 } 2596 2597 2598 override void format(const Constraint constraint) 2599 { 2600 debug(verbose) writeln("Constraint"); 2601 2602 if (constraint.expression) 2603 { 2604 put(" "); 2605 putTag("<a href=\"http://dpldocs.info/template-constraints\" class=\"lang-feature\">"); 2606 put("if"); 2607 putTag("</a>"); 2608 put(" ("); 2609 putTag("<div class=\"template-constraint-expression\">"); 2610 format(constraint.expression); 2611 putTag("</div>"); 2612 put(")"); 2613 } 2614 } 2615 2616 2617 2618 override void format(const PrimaryExpression primaryExpression) 2619 { 2620 debug(verbose) writeln("PrimaryExpression"); 2621 2622 /** 2623 Token dot; 2624 Token primary; 2625 IdentifierOrTemplateInstance identifierOrTemplateInstance; 2626 Token basicType; 2627 TypeofExpression typeofExpression; 2628 TypeidExpression typeidExpression; 2629 ArrayLiteral arrayLiteral; 2630 AssocArrayLiteral assocArrayLiteral; 2631 Expression expression; 2632 IsExpression isExpression; 2633 LambdaExpression lambdaExpression; 2634 FunctionLiteralExpression functionLiteralExpression; 2635 TraitsExpression traitsExpression; 2636 MixinExpression mixinExpression; 2637 ImportExpression importExpression; 2638 Vector vector; 2639 **/ 2640 2641 with(primaryExpression) 2642 { 2643 if (dot != tok!"") put("."); 2644 if (basicType != tok!"") format(basicType); 2645 if (primary != tok!"") 2646 { 2647 if (basicType != tok!"") put("."); // i.e. : uint.max 2648 format(primary); 2649 } 2650 2651 if (expression) 2652 { 2653 putTag("<span class=\"parenthetical-expression\">(<span class=\"parenthetical-expression-contents\">"); 2654 format(expression); 2655 putTag("</span>)</span>"); 2656 } 2657 else if (identifierOrTemplateInstance) 2658 { 2659 format(identifierOrTemplateInstance); 2660 } 2661 else if (typeofExpression) format(typeofExpression); 2662 else if (typeidExpression) format(typeidExpression); 2663 else if (arrayLiteral) format(arrayLiteral); 2664 else if (assocArrayLiteral) format(assocArrayLiteral); 2665 else if (isExpression) format(isExpression); 2666 else if (lambdaExpression) { putTag("<span class=\"lambda-expression\">"); format(lambdaExpression); putTag("</span>"); } 2667 else if (functionLiteralExpression) format(functionLiteralExpression); 2668 else if (traitsExpression) format(traitsExpression); 2669 else if (mixinExpression) format(mixinExpression); 2670 else if (importExpression) format(importExpression); 2671 else if (vector) format(vector); 2672 } 2673 } 2674 2675 override void format(const IsExpression isExpression) { 2676 //Formatter!Sink.format(this); 2677 2678 with(isExpression) { 2679 putTag(`<a href="http://dpldocs.info/is-expression" class="lang-feature">is</a>(`); 2680 if (type) format(type); 2681 if (identifier != tok!"") { 2682 space(); 2683 format(identifier); 2684 } 2685 2686 if (equalsOrColon) { 2687 space(); 2688 put(tokenRep(equalsOrColon)); 2689 space(); 2690 } 2691 2692 if (typeSpecialization) format(typeSpecialization); 2693 if (templateParameterList) { 2694 put(", "); 2695 format(templateParameterList); 2696 } 2697 put(")"); 2698 } 2699 } 2700 2701 override void format(const TypeofExpression typeofExpr) { 2702 debug(verbose) writeln("TypeofExpression"); 2703 2704 /** 2705 Expression expression; 2706 Token return_; 2707 **/ 2708 2709 putTag("<a href=\"http://dpldocs.info/typeof-expression\" class=\"lang-feature\">typeof</a>("); 2710 typeofExpr.expression ? format(typeofExpr.expression) : format(typeofExpr.return_); 2711 put(")"); 2712 } 2713 2714 2715 override void format(const Parameter parameter) 2716 { 2717 debug(verbose) writeln("Parameter"); 2718 2719 /** 2720 IdType[] parameterAttributes; 2721 Type type; 2722 Token name; 2723 bool vararg; 2724 AssignExpression default_; 2725 TypeSuffix[] cstyle; 2726 **/ 2727 2728 putTag("<div class=\"runtime-parameter-item parameter-item\">"); 2729 2730 2731 bool hadAtAttribute; 2732 2733 putTag("<span class=\"parameter-type-holder\">"); 2734 putTag("<span class=\"parameter-type\">"); 2735 foreach (count, attribute; parameter.parameterAttributes) 2736 { 2737 if (count || hadAtAttribute) space(); 2738 putTag("<span class=\"storage-class\">"); 2739 put(tokenRep(attribute)); 2740 putTag("</span>"); 2741 } 2742 2743 if (parameter.parameterAttributes.length > 0) 2744 space(); 2745 2746 if (parameter.type !is null) 2747 format(parameter.type); 2748 2749 putTag(`</span>`); 2750 putTag(`</span>`); 2751 2752 if (parameter.name.type != tok!"") 2753 { 2754 space(); 2755 putTag(`<span class="parameter-name name" data-ident="`~parameter.name.text~`">`); 2756 putTag("<a href=\"#param-" ~ parameter.name.text ~ "\">"); 2757 put(parameter.name.text); 2758 putTag("</a>"); 2759 putTag("</span>"); 2760 } 2761 2762 foreach(suffix; parameter.cstyle) 2763 format(suffix); 2764 2765 if (parameter.default_) 2766 { 2767 putTag(`<span class="parameter-default-value">`); 2768 putTag(" = "); 2769 format(parameter.default_); 2770 putTag(`</span>`); 2771 } 2772 2773 if (parameter.vararg) { 2774 putTag("<a href=\"http://dpldocs.info/typed-variadic-function-arguments\" class=\"lang-feature\">"); 2775 put("..."); 2776 putTag("</a>"); 2777 } 2778 2779 putTag("</div>"); 2780 } 2781 2782 override void format(const Type type) 2783 { 2784 debug(verbose) writeln("Type("); 2785 2786 /** 2787 IdType[] typeConstructors; 2788 TypeSuffix[] typeSuffixes; 2789 Type2 type2; 2790 **/ 2791 2792 foreach (count, constructor; type.typeConstructors) 2793 { 2794 if (count) space(); 2795 put(tokenRep(constructor)); 2796 } 2797 2798 if (type.typeConstructors.length) space(); 2799 2800 //put(`<span class="name" data-ident="`~tokenRep(token)~`">`); 2801 format(type.type2); 2802 //put("</span>"); 2803 2804 foreach (suffix; type.typeSuffixes) 2805 format(suffix); 2806 2807 debug(verbose) writeln(")"); 2808 } 2809 2810 override void format(const Type2 type2) 2811 { 2812 debug(verbose) writeln("Type2"); 2813 2814 /** 2815 IdType builtinType; 2816 Symbol symbol; 2817 TypeofExpression typeofExpression; 2818 IdentifierOrTemplateChain identifierOrTemplateChain; 2819 IdType typeConstructor; 2820 Type type; 2821 **/ 2822 2823 if (type2.symbol !is null) 2824 { 2825 suppressMagic = true; 2826 scope(exit) suppressMagic = false; 2827 2828 Decl link; 2829 if(context) 2830 link = context.lookupName(type2.symbol); 2831 if(link !is null) { 2832 putTag("<a href=\""~link.link~"\" title=\""~link.fullyQualifiedName~"\" class=\"xref\">"); 2833 format(type2.symbol); 2834 putTag("</a>"); 2835 } else if(toText(type2.symbol) == "string") { 2836 putTag("<span class=\"builtin-type\">"); 2837 put("string"); 2838 putTag("</span>"); 2839 } else { 2840 format(type2.symbol); 2841 } 2842 } 2843 else if (type2.typeofExpression !is null) 2844 { 2845 format(type2.typeofExpression); 2846 if (type2.identifierOrTemplateChain) 2847 { 2848 put("."); 2849 format(type2.identifierOrTemplateChain); 2850 } 2851 return; 2852 } 2853 else if (type2.typeConstructor != tok!"") 2854 { 2855 putTag("<span class=\"type-constructor\">"); 2856 put(tokenRep(type2.typeConstructor)); 2857 putTag("</span>"); 2858 put("("); 2859 format(type2.type); 2860 put(")"); 2861 } 2862 else 2863 { 2864 putTag("<span class=\"builtin-type\">"); 2865 put(tokenRep(type2.builtinType)); 2866 putTag("</span>"); 2867 } 2868 } 2869 2870 2871 override void format(const StorageClass storageClass) 2872 { 2873 debug(verbose) writeln("StorageClass"); 2874 2875 /** 2876 AtAttribute atAttribute; 2877 Deprecated deprecated_; 2878 LinkageAttribute linkageAttribute; 2879 Token token; 2880 **/ 2881 2882 with(storageClass) 2883 { 2884 if (atAttribute) format(atAttribute); 2885 else if (deprecated_) format(deprecated_); 2886 else if (linkageAttribute) format(linkageAttribute); 2887 else { 2888 putTag("<span class=\"storage-class\">"); 2889 format(token); 2890 putTag("</span>"); 2891 } 2892 } 2893 } 2894 2895 2896 bool noTag; 2897 override void format(const Token token) 2898 { 2899 debug(verbose) writeln("Token ", tokenRep(token)); 2900 if(!noTag && token == tok!"identifier") { 2901 putTag(`<span class="name" data-ident="`~tokenRep(token)~`">`); 2902 } 2903 auto rep = tokenRep(token); 2904 if(rep == "delegate" || rep == "function") { 2905 putTag(`<span class="lang-feature">`); 2906 put(rep); 2907 putTag(`</span>`); 2908 } else { 2909 put(rep); 2910 } 2911 if(!noTag && token == tok!"identifier") { 2912 putTag("</span>"); 2913 } 2914 } 2915 2916 bool suppressMagic = false; 2917 2918 override void format(const IdentifierOrTemplateInstance identifierOrTemplateInstance) 2919 { 2920 debug(verbose) writeln("IdentifierOrTemplateInstance"); 2921 2922 bool suppressMagicGoingIn = suppressMagic; 2923 2924 //if(!suppressMagicGoingIn) 2925 //putTag("<span class=\"some-ident\">"); 2926 with(identifierOrTemplateInstance) 2927 { 2928 format(identifier); 2929 if (templateInstance) 2930 format(templateInstance); 2931 } 2932 //if(!suppressMagicGoingIn) 2933 //putTag("</span>"); 2934 } 2935 2936 version(none) 2937 override void format(const Symbol symbol) 2938 { 2939 debug(verbose) writeln("Symbol"); 2940 2941 put("GOOD/"); 2942 if (symbol.dot) 2943 put("."); 2944 format(symbol.identifierOrTemplateChain); 2945 put("/GOOD"); 2946 } 2947 2948 override void format(const Parameters parameters) 2949 { 2950 debug(verbose) writeln("Parameters"); 2951 2952 /** 2953 Parameter[] parameters; 2954 bool hasVarargs; 2955 **/ 2956 2957 putTag("<div class=\"parameters-list\">"); 2958 putTag("<span class=\"paren\">(</span>"); 2959 foreach (count, param; parameters.parameters) 2960 { 2961 if (count) putTag("<span class=\"comma\">,</span>"); 2962 format(param); 2963 } 2964 if (parameters.hasVarargs) 2965 { 2966 if (parameters.parameters.length) 2967 putTag("<span class=\"comma\">,</span>"); 2968 putTag("<div class=\"runtime-parameter-item parameter-item\">"); 2969 putTag("<a href=\"http://dpldocs.info/variadic-function-arguments\" class=\"lang-feature\">"); 2970 putTag("..."); 2971 putTag("</a>"); 2972 putTag("</div>"); 2973 } 2974 putTag("<span class=\"paren\">)</span>"); 2975 putTag("</div>"); 2976 } 2977 2978 override void format(const IdentifierChain identifierChain) 2979 { 2980 //put("IDENT"); 2981 foreach(count, ident; identifierChain.identifiers) 2982 { 2983 if (count) put("."); 2984 put(ident.text); 2985 } 2986 //put("/IDENT"); 2987 } 2988 2989 override void format(const AndAndExpression andAndExpression) 2990 { 2991 with(andAndExpression) 2992 { 2993 putTag("<div class=\"andand-left\">"); 2994 format(left); 2995 if (right) 2996 { 2997 putTag(" && </div><div class=\"andand-right\">"); 2998 format(right); 2999 } 3000 putTag("</div>"); 3001 } 3002 } 3003 3004 override void format(const OrOrExpression orOrExpression) 3005 { 3006 with(orOrExpression) 3007 { 3008 putTag("<div class=\"oror-left\">"); 3009 format(left); 3010 if (right) 3011 { 3012 putTag(" || </div><div class=\"oror-right\">"); 3013 format(right); 3014 } 3015 putTag("</div>"); 3016 } 3017 } 3018 3019 alias format = Formatter!Sink.format; 3020 } 3021 3022 3023 /* 3024 The small table looks like this: 3025 3026 3027 Caption 3028 Header | Row 3029 Data | Row 3030 Data | Row 3031 */ 3032 Element translateSmallTable(string text, Decl decl) { 3033 auto holder = Element.make("table"); 3034 holder.addClass("small-table"); 3035 3036 void skipWhitespace() { 3037 while(text.length && (text[0] == ' ' || text[0] == '\t')) 3038 text = text[1 .. $]; 3039 } 3040 3041 string[] extractLine() { 3042 string[] cells; 3043 3044 skipWhitespace(); 3045 size_t i; 3046 3047 while(i < text.length) { 3048 static string lastText; 3049 if(text[i .. $] is lastText) 3050 assert(0, lastText); 3051 3052 lastText = text[i .. $]; 3053 switch(text[i]) { 3054 case '|': 3055 cells ~= text[0 .. i].strip; 3056 text = text[i + 1 .. $]; 3057 i = 0; 3058 break; 3059 case '`': 3060 i++; 3061 while(i < text.length && text[i] != '`') 3062 i++; 3063 if(i < text.length) 3064 i++; // skip closing ` 3065 break; 3066 case '[': 3067 case '(': 3068 case '{': 3069 i++; // FIXME? skip these? 3070 break; 3071 case '$': 3072 auto info = macroInformation(text[i .. $]); 3073 i += info.terminatingOffset + 1; 3074 break; 3075 case '\n': 3076 cells ~= text[0 .. i].strip; 3077 text = text[i + 1 .. $]; 3078 return cells; 3079 break; 3080 default: 3081 i++; 3082 } 3083 } 3084 3085 return cells; 3086 } 3087 3088 bool isDecorative(string[] row) { 3089 foreach(s; row) 3090 foreach(char c; s) 3091 if(c != '+' && c != '-' && c != '|' && c != '=') 3092 return false; 3093 return true; 3094 } 3095 3096 Element tbody; 3097 bool first = true; 3098 bool twodTable; 3099 bool firstLine = true; 3100 string[] headerRow; 3101 while(text.length) { 3102 auto row = extractLine(); 3103 3104 if(row.length == 0) 3105 continue; // should never happen... 3106 3107 if(row.length == 1 && row[0].length == 0) 3108 continue; // blank line 3109 3110 if(isDecorative(row)) 3111 continue; // ascii art is allowed, but ignored 3112 3113 if(firstLine && row.length == 1 && row[0].length) { 3114 firstLine = false; 3115 holder.addChild("caption", row[0]); 3116 continue; 3117 } 3118 3119 firstLine = false; 3120 3121 // empty first and last rows are just surrounding pipes and can be ignored 3122 if(row[0].length == 0) 3123 row = row[1 .. $]; 3124 if(row[$-1].length == 0) 3125 row = row[0 .. $-1]; 3126 3127 if(first) { 3128 auto thead = holder.addChild("thead"); 3129 auto tr = thead.addChild("tr"); 3130 3131 headerRow = row; 3132 3133 if(row[0].length == 0) 3134 twodTable = true; 3135 3136 foreach(cell; row) 3137 tr.addChild("th", formatDocumentationComment2(cell, decl, null)); 3138 first = false; 3139 } else { 3140 if(tbody is null) 3141 tbody = holder.addChild("tbody"); 3142 auto tr = tbody.addChild("tr"); 3143 3144 while(row.length < headerRow.length) 3145 row ~= ""; 3146 3147 foreach(i, cell; row) 3148 tr.addChild((twodTable && i == 0) ? "th" : "td", formatDocumentationComment2(cell, decl, null)); 3149 3150 } 3151 } 3152 3153 // FIXME: format the cells 3154 3155 if(twodTable) 3156 holder.addClass("two-axes"); 3157 3158 3159 return holder; 3160 } 3161 3162 Element translateListTable(string text, Decl decl) { 3163 auto holder = Element.make("table"); 3164 holder.addClass("user-table"); 3165 3166 DocCommentTerminationCondition termination; 3167 termination.terminationStrings = ["*", "-", "+"]; 3168 termination.mustBeAtStartOfLine = true; 3169 3170 string nextTag; 3171 3172 Element row; 3173 3174 do { 3175 auto fmt = formatDocumentationComment2(text, decl, null, &termination); 3176 if(fmt.toString.strip.length) { 3177 if(row is null) 3178 holder.addChild("caption", fmt); 3179 else 3180 row.addChild(nextTag, fmt); 3181 } 3182 text = termination.remaining; 3183 if(termination.terminatedOn == "*") 3184 row = holder.addChild("tr"); 3185 else if(termination.terminatedOn == "+") 3186 nextTag = "th"; 3187 else if(termination.terminatedOn == "-") 3188 nextTag = "td"; 3189 else {} 3190 } while(text.length); 3191 3192 if(holder.querySelector("th:first-child + td")) 3193 holder.addClass("two-axes"); 3194 3195 return holder; 3196 } 3197 3198 Element translateList(string text, Decl decl, string tagName) { 3199 auto holder = Element.make(tagName); 3200 holder.addClass("user-list"); 3201 3202 DocCommentTerminationCondition termination; 3203 termination.terminationStrings = ["*"]; 3204 termination.mustBeAtStartOfLine = true; 3205 3206 auto opening = formatDocumentationComment2(text, decl, null, &termination); 3207 text = termination.remaining; 3208 3209 translateMagicCommands(opening, holder); 3210 3211 while(text.length) { 3212 auto fmt = formatDocumentationComment2(text, decl, null, &termination); 3213 holder.addChild("li", fmt); 3214 text = termination.remaining; 3215 } 3216 3217 return holder; 3218 } 3219 3220 Html syntaxHighlightCss(string code) { 3221 string highlighted; 3222 3223 3224 int pullPiece(string code, bool includeDot) { 3225 int i; 3226 while(i < code.length && ( 3227 (code[i] >= '0' && code[i] <= '9') 3228 || 3229 (code[i] >= 'a' && code[i] <= 'z') 3230 || 3231 (code[i] >= 'A' && code[i] <= 'Z') 3232 || 3233 (includeDot && code[i] == '.') || code[i] == '_' || code[i] == '-' || code[i] == ':' // for css i want to match like :first-letter 3234 )) 3235 { 3236 i++; 3237 } 3238 return i; 3239 } 3240 3241 3242 3243 while(code.length) { 3244 switch(code[0]) { 3245 case '\'': 3246 case '\"': 3247 // string literal 3248 char start = code[0]; 3249 size_t i = 1; 3250 while(i < code.length && code[i] != start && code[i] != '\n') { 3251 if(code[i] == '\\') 3252 i++; // skip escaped char too 3253 i++; 3254 } 3255 3256 i++; // skip closer 3257 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3258 code = code[i .. $]; 3259 break; 3260 case '/': 3261 // check for comment 3262 if(code.length > 1 && code[1] == '*') { 3263 // a star comment 3264 size_t i; 3265 while((i+1) < code.length && !(code[i] == '*' && code[i+1] == '/')) 3266 i++; 3267 3268 if(i < code.length) 3269 i+=2; // skip the */ 3270 3271 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3272 code = code[i .. $]; 3273 } else { 3274 highlighted ~= '/'; 3275 code = code[1 .. $]; 3276 } 3277 break; 3278 case '<': 3279 highlighted ~= "<"; 3280 code = code[1 .. $]; 3281 break; 3282 case '>': 3283 highlighted ~= ">"; 3284 code = code[1 .. $]; 3285 break; 3286 case '&': 3287 highlighted ~= "&"; 3288 code = code[1 .. $]; 3289 break; 3290 case '@': 3291 // @media, @import, @font-face, etc 3292 auto i = pullPiece(code[1 .. $], false); 3293 if(i) 3294 i++; 3295 else 3296 goto plain; 3297 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3298 code = code[i .. $]; 3299 break; 3300 case '\n', ';': 3301 // check for a new rule (imprecise since we don't actually parse, but meh 3302 // will ignore indent, then see if there is a word-like-this followed by : 3303 int i = 1; 3304 while(i < code.length && (code[i] == ' ' || code[i] == '\t')) 3305 i++; 3306 int start = i; 3307 while(i < code.length && (code[i] == '-' || (code[i] >= 'a' && code[i] <= 'z'))) 3308 i++; 3309 if(start == i || i == code.length || code[i] != ':') 3310 goto plain; 3311 3312 highlighted ~= code[0 .. start]; 3313 highlighted ~= "<span class=\"highlighted-named-constant\">" ~ htmlEntitiesEncode(code[start .. i]) ~ "</span>"; 3314 code = code[i .. $]; 3315 break; 3316 case '[': 3317 // attribute match rule 3318 auto i = pullPiece(code[1 .. $], false); 3319 if(i) 3320 i++; 3321 else 3322 goto plain; 3323 highlighted ~= "<span class=\"highlighted-attribute-name\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3324 code = code[i .. $]; 3325 break; 3326 case '#', '.': // id or class selector 3327 auto i = pullPiece(code[1 .. $], false); 3328 if(i) 3329 i++; 3330 else 3331 goto plain; 3332 highlighted ~= "<span class=\"highlighted-identifier\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3333 code = code[i .. $]; 3334 break; 3335 case ':': 3336 // pseudoclass 3337 auto i = pullPiece(code, false); 3338 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3339 code = code[i .. $]; 3340 break; 3341 case '(': 3342 // just skipping stuff in parens... 3343 int parenCount = 0; 3344 int pos = 0; 3345 bool inQuote; 3346 bool escaped; 3347 while(pos < code.length) { 3348 if(code[pos] == '\\') 3349 escaped = true; 3350 if(!escaped && code[pos] == '"') 3351 inQuote = !inQuote; 3352 if(!inQuote && code[pos] == '(') 3353 parenCount++; 3354 if(!inQuote && code[pos] == ')') 3355 parenCount--; 3356 pos++; 3357 if(parenCount == 0) 3358 break; 3359 } 3360 3361 highlighted ~= "<span class=\"highlighted-identifier\">" ~ htmlEntitiesEncode(code[0 .. pos]) ~ "</span>"; 3362 code = code[pos .. $]; 3363 break; 3364 case '0': .. case '9': 3365 // number. including text at the end cuz it is probably a unit 3366 auto i = pullPiece(code, true); 3367 highlighted ~= "<span class=\"highlighted-number\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3368 code = code[i .. $]; 3369 break; 3370 default: 3371 plain: 3372 highlighted ~= code[0]; 3373 code = code[1 .. $]; 3374 } 3375 } 3376 3377 return Html(highlighted); 3378 } 3379 3380 string highlightHtmlTag(string tag) { 3381 string highlighted = "<"; 3382 3383 bool isWhite(char c) { 3384 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 3385 } 3386 3387 tag = tag[1 .. $]; 3388 3389 if(tag[0] == '/') { 3390 highlighted ~= '/'; 3391 tag = tag[1 .. $]; 3392 } 3393 3394 int i = 0; 3395 while(i < tag.length && !isWhite(tag[i]) && tag[i] != '>') { 3396 i++; 3397 } 3398 3399 highlighted ~= "<span class=\"highlighted-tag-name\">" ~ htmlEntitiesEncode(tag[0 .. i]) ~ "</span>"; 3400 tag = tag[i .. $]; 3401 3402 while(tag.length && tag[0] != '>') { 3403 while(isWhite(tag[0])) { 3404 highlighted ~= tag[0]; 3405 tag = tag[1 .. $]; 3406 } 3407 3408 i = 0; 3409 while(i < tag.length && tag[i] != '=' && tag[i] != '>') 3410 i++; 3411 3412 highlighted ~= "<span class=\"highlighted-attribute-name\">" ~ htmlEntitiesEncode(tag[0 .. i]) ~ "</span>"; 3413 tag = tag[i .. $]; 3414 3415 if(tag.length && tag[0] == '=') { 3416 highlighted ~= '='; 3417 tag = tag[1 .. $]; 3418 if(tag.length && (tag[0] == '\'' || tag[0] == '"')) { 3419 char end = tag[0]; 3420 i = 1; 3421 while(i < tag.length && tag[i] != end) 3422 i++; 3423 if(i < tag.length) 3424 i++; 3425 3426 highlighted ~= "<span class=\"highlighted-attribute-value\">" ~ htmlEntitiesEncode(tag[0 .. i]) ~ "</span>"; 3427 tag = tag[i .. $]; 3428 } else if(tag.length) { 3429 i = 0; 3430 while(i < tag.length && !isWhite(tag[i])) 3431 i++; 3432 3433 highlighted ~= "<span class=\"highlighted-attribute-value\">" ~ htmlEntitiesHighlight(tag[0 .. i]) ~ "</span>"; 3434 tag = tag[i .. $]; 3435 } 3436 } 3437 } 3438 3439 3440 highlighted ~= htmlEntitiesEncode(tag); 3441 3442 return highlighted; 3443 } 3444 3445 string htmlEntitiesHighlight(string code) { 3446 string highlighted; 3447 3448 bool isWhite(char c) { 3449 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 3450 } 3451 3452 while(code.length) 3453 switch(code[0]) { 3454 case '&': 3455 // possibly an entity 3456 int i = 0; 3457 while(i < code.length && code[i] != ';' && !isWhite(code[i])) 3458 i++; 3459 if(i == code.length || isWhite(code[i])) { 3460 highlighted ~= "&"; 3461 code = code[1 .. $]; 3462 } else { 3463 i++; 3464 highlighted ~= 3465 "<abbr class=\"highlighted-entity\" title=\"Entity: "~code[0 .. i].replace("\"", """)~"\">" ~ 3466 htmlEntitiesEncode(code[0 .. i]) ~ 3467 "</abbr>"; 3468 code = code[i .. $]; 3469 } 3470 break; 3471 case '<': 3472 highlighted ~= "<"; 3473 code = code[1 .. $]; 3474 break; 3475 case '"': 3476 highlighted ~= """; 3477 code = code[1 .. $]; 3478 break; 3479 case '>': 3480 highlighted ~= ">"; 3481 code = code[1 .. $]; 3482 break; 3483 default: 3484 highlighted ~= code[0]; 3485 code = code[1 .. $]; 3486 } 3487 3488 return highlighted; 3489 } 3490 3491 Html syntaxHighlightRuby(string code) { 3492 // FIXME this is quite minimal and crappy 3493 // ruby just needs to be parsed to be lexed! 3494 // and i didn't implement its myriad of strings 3495 3496 static immutable string[] rubyKeywords = [ 3497 "BEGIN", "ensure", "self", "when", 3498 "END", "not", "super", "while", 3499 "alias", "defined", "for", "or", "then", "yield", 3500 "and", "do", "if", "redo", 3501 "begin", "else", "in", "rescue", "undef", 3502 "break", "elsif", "retry", "unless", 3503 "case", "end", "next", "return", "until", 3504 ]; 3505 3506 static immutable string[] rubyPreprocessors = [ 3507 "require", "include" 3508 ]; 3509 3510 static immutable string[] rubyTypes = [ 3511 "class", "def", "module" 3512 ]; 3513 3514 static immutable string[] rubyConstants = [ 3515 "nil", "false", "true", 3516 ]; 3517 3518 bool lastWasType; 3519 char lastChar; 3520 int indentLevel; 3521 int[] declarationIndentLevels; 3522 3523 string highlighted; 3524 3525 while(code.length) { 3526 bool thisIterWasType = false; 3527 auto ch = code[0]; 3528 switch(ch) { 3529 case '#': 3530 size_t i; 3531 while(i < code.length && code[i] != '\n') 3532 i++; 3533 3534 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3535 code = code[i .. $]; 3536 break; 3537 case '<': 3538 highlighted ~= "<"; 3539 code = code[1 .. $]; 3540 break; 3541 case '>': 3542 highlighted ~= ">"; 3543 code = code[1 .. $]; 3544 break; 3545 case '&': 3546 highlighted ~= "&"; 3547 code = code[1 .. $]; 3548 break; 3549 case '0': .. case '9': 3550 // number literal 3551 size_t i; 3552 while(i < code.length && ( 3553 (code[i] >= '0' && code[i] <= '9') 3554 || 3555 (code[i] >= 'a' && code[i] <= 'z') 3556 || 3557 (code[i] >= 'A' && code[i] <= 'Z') 3558 || 3559 code[i] == '.' || code[i] == '_' 3560 )) 3561 { 3562 i++; 3563 } 3564 3565 highlighted ~= "<span class=\"highlighted-number\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3566 code = code[i .. $]; 3567 break; 3568 case '"', '\'': 3569 // string 3570 // check for triple-quoted string 3571 if(ch == '"') { 3572 3573 // double quote string 3574 auto idx = 1; 3575 bool escaped = false; 3576 int nestedCount = 0; 3577 bool justSawHash = false; 3578 while(!escaped && nestedCount == 0 && code[idx] != '"') { 3579 if(justSawHash && code[idx] == '{') 3580 nestedCount++; 3581 if(nestedCount && code[idx] == '}') 3582 nestedCount--; 3583 3584 if(!escaped && code[idx] == '#') 3585 justSawHash = true; 3586 3587 if(code[idx] == '\\') 3588 escaped = true; 3589 else { 3590 escaped = false; 3591 } 3592 3593 idx++; 3594 } 3595 idx++; 3596 3597 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. idx]) ~ "</span>"; 3598 code = code[idx .. $]; 3599 } else { 3600 int end = 1; 3601 bool escaped; 3602 while(end < code.length && !escaped && code[end] != ch) { 3603 escaped = (code[end] == '\\'); 3604 end++; 3605 } 3606 3607 if(end < code.length) 3608 end++; 3609 3610 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. end]) ~ "</span>"; 3611 code = code[end .. $]; 3612 } 3613 break; 3614 case '\n': 3615 indentLevel = 0; 3616 int i = 1; 3617 while(i < code.length && (code[i] == ' ' || code[i] == '\t')) { 3618 i++; 3619 indentLevel++; 3620 } 3621 highlighted ~= ch; 3622 code = code[1 .. $]; 3623 break; 3624 case ' ', '\t': 3625 thisIterWasType = lastWasType; // don't change the last counter on just whitespace 3626 highlighted ~= ch; 3627 code = code[1 .. $]; 3628 break; 3629 case ':': 3630 // check for ruby symbol 3631 int nameEnd = 1; 3632 while(nameEnd < code.length && ( 3633 (code[nameEnd] >= 'a' && code[nameEnd] <= 'z') || 3634 (code[nameEnd] >= 'A' && code[nameEnd] <= 'Z') || 3635 (code[nameEnd] >= '0' && code[nameEnd] <= '0') || 3636 code[nameEnd] == '_' 3637 )) 3638 { 3639 nameEnd++; 3640 } 3641 3642 if(nameEnd > 1) { 3643 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. nameEnd]) ~ "</span>"; 3644 code = code[nameEnd .. $]; 3645 } else { 3646 highlighted ~= ch; 3647 code = code[1 .. $]; 3648 } 3649 break; 3650 default: 3651 // check for names 3652 int nameEnd = 0; 3653 while(nameEnd < code.length && ( 3654 (code[nameEnd] >= 'a' && code[nameEnd] <= 'z') || 3655 (code[nameEnd] >= 'A' && code[nameEnd] <= 'Z') || 3656 (code[nameEnd] >= '0' && code[nameEnd] <= '0') || 3657 code[nameEnd] == '_' 3658 )) 3659 { 3660 nameEnd++; 3661 } 3662 3663 if(nameEnd) { 3664 auto name = code[0 .. nameEnd]; 3665 code = code[nameEnd .. $]; 3666 3667 if(rubyTypes.canFind(name)) { 3668 highlighted ~= "<span class=\"highlighted-type\">" ~ name ~ "</span>"; 3669 thisIterWasType = true; 3670 declarationIndentLevels ~= indentLevel; 3671 } else if(rubyConstants.canFind(name)) { 3672 highlighted ~= "<span class=\"highlighted-number\">" ~ name ~ "</span>"; 3673 } else if(rubyPreprocessors.canFind(name)) { 3674 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ name ~ "</span>"; 3675 } else if(rubyKeywords.canFind(name)) { 3676 if(name == "end") { 3677 if(declarationIndentLevels.length && indentLevel == declarationIndentLevels[$-1]) { 3678 // cheating on matching ends with declarations: if indentation matches... 3679 highlighted ~= "<span class=\"highlighted-type\">" ~ name ~ "</span>"; 3680 declarationIndentLevels = declarationIndentLevels[0 .. $-1]; 3681 } else { 3682 highlighted ~= "<span class=\"highlighted-keyword\">" ~ name ~ "</span>"; 3683 } 3684 } else { 3685 highlighted ~= "<span class=\"highlighted-keyword\">" ~ name ~ "</span>"; 3686 } 3687 } else { 3688 if(lastWasType) { 3689 highlighted ~= "<span class=\"highlighted-identifier\">" ~ name ~ "</span>"; 3690 } else { 3691 if(name.length && name[0] >= 'A' && name[0] <= 'Z') 3692 highlighted ~= "<span class=\"highlighted-named-constant\">" ~ name ~ "</span>"; 3693 else 3694 highlighted ~= name; 3695 } 3696 } 3697 } else { 3698 highlighted ~= ch; 3699 code = code[1 .. $]; 3700 } 3701 break; 3702 } 3703 lastChar = ch; 3704 lastWasType = thisIterWasType; 3705 } 3706 3707 return Html(highlighted); 3708 } 3709 3710 Html syntaxHighlightPython(string code) { 3711 /* 3712 I just want to support: 3713 numbers 3714 anything starting with 0, then going alphanumeric 3715 keywords 3716 from and import are "preprocessor" 3717 None, True, and False are number literal colored 3718 name 3719 an identifier after the "def" keyword, possibly including : if in there. my vim does it light blue 3720 comments 3721 only # .. \n is allowed. 3722 strings 3723 single quote must end on the same line 3724 triple quote span multiple line. 3725 3726 3727 Extracting python definitions: 3728 class foo: 3729 # method baz, arg: bar, doc string attached 3730 def baz(bar): 3731 """ this is the doc string """ 3732 3733 will just have to follow indentation (BARF) 3734 3735 Note: python has default arguments. 3736 3737 3738 Python implicitly does line continuation if parens, brackets, or braces open 3739 you can also explicitly extend with \ at the end 3740 in these cases, the indentation doesn't count. 3741 3742 If there's only comments on a line, the indentation is also exempt. 3743 */ 3744 static immutable string[] pythonKeywords = [ 3745 "and", "del", "not", "while", 3746 "as", "elif", "global", "or", "with", 3747 "assert", "else", "if", "pass", "yield", 3748 "break", "except", "print", 3749 "exec", "in", "raise", 3750 "continue", "finally", "is", "return", 3751 "for", "lambda", "try", 3752 ]; 3753 3754 static immutable string[] pythonPreprocessors = [ 3755 "from", "import" 3756 ]; 3757 3758 static immutable string[] pythonTypes = [ 3759 "class", "def" 3760 ]; 3761 3762 static immutable string[] pythonConstants = [ 3763 "True", "False", "None" 3764 ]; 3765 3766 string[] indentStack; 3767 int openParenCount = 0; 3768 int openBraceCount = 0; 3769 int openBracketCount = 0; 3770 char lastChar; 3771 bool lastWasType; 3772 3773 string highlighted; 3774 3775 // ensure there is one and exactly one new line at the end 3776 // the highlighter uses the final \n as a chance to clean up the 3777 // indent divs 3778 code = code.stripRight; 3779 code ~= "\n"; 3780 3781 int lineCountSpace = 5; // if python is > 1000 lines it can slightly throw off the indent highlight... but meh. 3782 3783 while(code.length) { 3784 bool thisIterWasType = false; 3785 auto ch = code[0]; 3786 switch(ch) { 3787 case '#': 3788 size_t i; 3789 while(i < code.length && code[i] != '\n') 3790 i++; 3791 3792 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3793 code = code[i .. $]; 3794 break; 3795 case '(': 3796 openParenCount++; 3797 highlighted ~= ch; 3798 code = code[1 .. $]; 3799 break; 3800 case '[': 3801 openBracketCount++; 3802 highlighted ~= ch; 3803 code = code[1 .. $]; 3804 break; 3805 case '{': 3806 openBraceCount++; 3807 highlighted ~= ch; 3808 code = code[1 .. $]; 3809 break; 3810 case ')': 3811 openParenCount--; 3812 highlighted ~= ch; 3813 code = code[1 .. $]; 3814 break; 3815 case ']': 3816 openBracketCount--; 3817 highlighted ~= ch; 3818 code = code[1 .. $]; 3819 break; 3820 case '}': 3821 openBraceCount--; 3822 highlighted ~= ch; 3823 code = code[1 .. $]; 3824 break; 3825 case '<': 3826 highlighted ~= "<"; 3827 code = code[1 .. $]; 3828 break; 3829 case '>': 3830 highlighted ~= ">"; 3831 code = code[1 .. $]; 3832 break; 3833 case '&': 3834 highlighted ~= "&"; 3835 code = code[1 .. $]; 3836 break; 3837 case '0': .. case '9': 3838 // number literal 3839 size_t i; 3840 while(i < code.length && ( 3841 (code[i] >= '0' && code[i] <= '9') 3842 || 3843 (code[i] >= 'a' && code[i] <= 'z') 3844 || 3845 (code[i] >= 'A' && code[i] <= 'Z') 3846 || 3847 code[i] == '.' || code[i] == '_' 3848 )) 3849 { 3850 i++; 3851 } 3852 3853 highlighted ~= "<span class=\"highlighted-number\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3854 code = code[i .. $]; 3855 break; 3856 case '"', '\'': 3857 // string 3858 // check for triple-quoted string 3859 if(ch == '"' && code.length > 2 && code[1] == '"' && code[2] == '"') { 3860 int end = 3; 3861 bool escaped; 3862 while(end + 3 < code.length && !escaped && code[end .. end+ 3] != `"""`) { 3863 escaped = (code[end] == '\\'); 3864 end++; 3865 } 3866 3867 if(end < code.length) 3868 end+= 3; 3869 3870 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. end]) ~ "</span>"; 3871 code = code[end .. $]; 3872 } else { 3873 // otherwise these are limited to one line, though since we 3874 // assume the program is well-formed, I'm not going to bother; 3875 // no need to check for Python errors here, just highlight 3876 int end = 1; 3877 bool escaped; 3878 while(end < code.length && !escaped && code[end] != ch) { 3879 escaped = (code[end] == '\\'); 3880 end++; 3881 } 3882 3883 if(end < code.length) 3884 end++; 3885 3886 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. end]) ~ "</span>"; 3887 code = code[end .. $]; 3888 } 3889 break; 3890 case '\n': 3891 string thisIndent = null; 3892 int thisIndentLength = lineCountSpace; 3893 // figure out indentation and stuff... 3894 if(lastChar == '\\' || openParenCount || openBracketCount || openBraceCount) { 3895 // line continuation, no special processing 3896 } else { 3897 if(code.length == 1) { 3898 // last line in the file, clean up the indentation 3899 int remove; 3900 foreach_reverse(i; indentStack) { 3901 // this isn't actually following the python rule which 3902 // is more like .startsWith but meh 3903 if(i.length <= thisIndent.length) 3904 break; 3905 highlighted ~= "</div>"; 3906 remove++; 3907 } 3908 indentStack = indentStack[0 .. $ - remove]; 3909 // NOT appending the final \n cuz that leads dead space in rendered doc 3910 3911 code = code[1 .. $]; 3912 break; 3913 } else 3914 foreach(idx, cha; code[1 .. $]) { 3915 if(cha == '\n') 3916 break; 3917 if(cha == '#') // comments exempt from indent processing too 3918 break; 3919 if(cha == ' ') 3920 thisIndentLength++; 3921 if(cha == '\t') 3922 thisIndentLength += 8 - (thisIndentLength % 8); 3923 if(cha != ' ' && cha != '\t') { 3924 thisIndent = code[1 .. idx + 1]; 3925 break; 3926 } 3927 } 3928 } 3929 3930 bool changedDiv = false; 3931 3932 if(thisIndent !is null) { // !is null rather than .length is important here. null means skip, "" may need processing 3933 // close open indents if necessary 3934 int remove; 3935 foreach_reverse(i; indentStack) { 3936 // this isn't actually following the python rule which 3937 // is more like .startsWith but meh 3938 if(i.length <= thisIndent.length) 3939 break; 3940 highlighted ~= "</div>"; 3941 changedDiv = true; 3942 remove++; 3943 } 3944 indentStack = indentStack[0 .. $ - remove]; 3945 } 3946 3947 if(thisIndent.length) { // but we only ever open a new one if there was non-zero indent 3948 // open a new one if appropriate 3949 if(indentStack.length == 0 || thisIndent.length > indentStack[$-1].length) { 3950 changedDiv = true; 3951 highlighted ~= "<div class=\"highlighted-python-indent\" style=\"background-position: calc("~to!string(thisIndentLength)~"ch - 2px);\">"; 3952 indentStack ~= thisIndent; 3953 } 3954 } 3955 3956 if(changedDiv) 3957 highlighted ~= "<span style=\"white-space: normal;\">"; 3958 3959 highlighted ~= ch; 3960 3961 if(changedDiv) 3962 highlighted ~= "</span>"; 3963 3964 code = code[1 .. $]; 3965 break; 3966 case ' ', '\t': 3967 thisIterWasType = lastWasType; // don't change the last counter on just whitespace 3968 highlighted ~= ch; 3969 code = code[1 .. $]; 3970 break; 3971 default: 3972 // check for names 3973 int nameEnd = 0; 3974 while(nameEnd < code.length && ( 3975 (code[nameEnd] >= 'a' && code[nameEnd] <= 'z') || 3976 (code[nameEnd] >= 'A' && code[nameEnd] <= 'Z') || 3977 (code[nameEnd] >= '0' && code[nameEnd] <= '0') || 3978 code[nameEnd] == '_' 3979 )) 3980 { 3981 nameEnd++; 3982 } 3983 3984 if(nameEnd) { 3985 auto name = code[0 .. nameEnd]; 3986 code = code[nameEnd .. $]; 3987 3988 if(pythonTypes.canFind(name)) { 3989 highlighted ~= "<span class=\"highlighted-type\">" ~ name ~ "</span>"; 3990 thisIterWasType = true; 3991 } else if(pythonConstants.canFind(name)) { 3992 highlighted ~= "<span class=\"highlighted-number\">" ~ name ~ "</span>"; 3993 } else if(pythonPreprocessors.canFind(name)) { 3994 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ name ~ "</span>"; 3995 } else if(pythonKeywords.canFind(name)) { 3996 highlighted ~= "<span class=\"highlighted-keyword\">" ~ name ~ "</span>"; 3997 } else { 3998 if(lastWasType) { 3999 highlighted ~= "<span class=\"highlighted-identifier\">" ~ name ~ "</span>"; 4000 } else { 4001 highlighted ~= name; 4002 } 4003 } 4004 } else { 4005 highlighted ~= ch; 4006 code = code[1 .. $]; 4007 } 4008 break; 4009 } 4010 lastChar = ch; 4011 lastWasType = thisIterWasType; 4012 } 4013 4014 return Html(highlighted); 4015 } 4016 4017 Html syntaxHighlightHtml(string code, string language) { 4018 string highlighted; 4019 4020 bool isWhite(char c) { 4021 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 4022 } 4023 4024 while(code.length) { 4025 switch(code[0]) { 4026 case '<': 4027 if(code.length > 1 && code[1] == '!') { 4028 // comment or processing instruction 4029 4030 int i = 0; 4031 while(i < code.length && code[i] != '>') { 4032 i++; 4033 } 4034 if(i < code.length) 4035 i++; 4036 4037 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 4038 code = code[i .. $]; 4039 continue; 4040 } else if(code.length > 1 && (code[1] == '/' || (code[1] >= 'a' && code[1] <= 'z') || (code[1] >= 'A' && code[1] <= 'Z'))) { 4041 // possibly a tag 4042 int i = 1; 4043 while(i < code.length && code[i] != '>' && code[i] != '<') 4044 i++; 4045 if(i == code.length || code[i] == '<') 4046 goto plain_lt; 4047 4048 auto tag = code[0 .. i + 1]; 4049 code = code[i + 1 .. $]; 4050 4051 highlighted ~= "<span class=\"highlighted-tag\">" ~ highlightHtmlTag(tag) ~ "</span>"; 4052 4053 if(language == "html" && tag.startsWith("<script")) { 4054 auto end = code.indexOf("</script>"); 4055 if(end == -1) 4056 continue; 4057 4058 auto sCode = code[0 .. end]; 4059 highlighted ~= syntaxHighlightCFamily(sCode, "javascript").source; 4060 code = code[end .. $]; 4061 } else if(language == "html" && tag.startsWith("<style")) { 4062 auto end = code.indexOf("</style>"); 4063 if(end == -1) 4064 continue; 4065 auto sCode = code[0 .. end]; 4066 highlighted ~= syntaxHighlightCss(sCode).source; 4067 code = code[end .. $]; 4068 } 4069 4070 continue; 4071 } 4072 4073 plain_lt: 4074 highlighted ~= "<"; 4075 code = code[1 .. $]; 4076 break; 4077 case '"': 4078 highlighted ~= """; 4079 code = code[1 .. $]; 4080 break; 4081 case '>': 4082 highlighted ~= ">"; 4083 code = code[1 .. $]; 4084 break; 4085 case '&': 4086 // possibly an entity 4087 4088 int i = 0; 4089 while(i < code.length && code[i] != ';' && !isWhite(code[i])) 4090 i++; 4091 if(i == code.length || isWhite(code[i])) { 4092 highlighted ~= "&"; 4093 code = code[1 .. $]; 4094 } else { 4095 i++; 4096 highlighted ~= htmlEntitiesHighlight(code[0 .. i]); 4097 code = code[i .. $]; 4098 } 4099 break; 4100 4101 default: 4102 highlighted ~= code[0]; 4103 code = code[1 .. $]; 4104 } 4105 } 4106 4107 return Html(highlighted); 4108 } 4109 4110 Html syntaxHighlightSdlang(string jsCode) { 4111 string highlighted; 4112 4113 bool isIdentifierChar(char c) { 4114 return 4115 (c >= 'a' && c <= 'z') || 4116 (c >= 'A' && c <= 'Z') || 4117 (c >= '0' && c <= '9') || 4118 (c == '$' || c == '_'); 4119 } 4120 4121 bool startOfLine = true; 4122 4123 while(jsCode.length) { 4124 switch(jsCode[0]) { 4125 case '\'': 4126 case '\"': 4127 // string literal 4128 char start = jsCode[0]; 4129 size_t i = 1; 4130 while(i < jsCode.length && jsCode[i] != start && jsCode[i] != '\n') { 4131 if(jsCode[i] == '\\') 4132 i++; // skip escaped char too 4133 i++; 4134 } 4135 4136 i++; // skip closer 4137 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4138 jsCode = jsCode[i .. $]; 4139 startOfLine = false; 4140 break; 4141 case '-': 4142 if(jsCode.length > 1 && jsCode[1] == '-') { 4143 int i = 0; 4144 while(i < jsCode.length && jsCode[i] != '\n') 4145 i++; 4146 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4147 jsCode = jsCode[i .. $]; 4148 } else { 4149 highlighted ~= '-'; 4150 jsCode = jsCode[1 .. $]; 4151 } 4152 break; 4153 case '#': 4154 int i = 0; 4155 while(i < jsCode.length && jsCode[i] != '\n') 4156 i++; 4157 4158 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4159 jsCode = jsCode[i .. $]; 4160 break; 4161 case '/': 4162 if(jsCode.length > 1 && (jsCode[1] == '*' || jsCode[1] == '/')) { 4163 // it is a comment 4164 if(jsCode[1] == '/') { 4165 size_t i; 4166 while(i < jsCode.length && jsCode[i] != '\n') 4167 i++; 4168 4169 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4170 jsCode = jsCode[i .. $]; 4171 } else { 4172 // a star comment 4173 size_t i; 4174 while((i+1) < jsCode.length && !(jsCode[i] == '*' && jsCode[i+1] == '/')) 4175 i++; 4176 4177 if(i < jsCode.length) 4178 i+=2; // skip the */ 4179 4180 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4181 jsCode = jsCode[i .. $]; 4182 } 4183 } else { 4184 highlighted ~= '/'; 4185 jsCode = jsCode[1 .. $]; 4186 } 4187 break; 4188 case '0': .. case '9': 4189 // number literal 4190 size_t i; 4191 while(i < jsCode.length && ( 4192 (jsCode[i] >= '0' && jsCode[i] <= '9') 4193 || 4194 // really fuzzy but this is meant to highlight hex and exponents too 4195 jsCode[i] == '.' || jsCode[i] == 'e' || jsCode[i] == 'x' || 4196 (jsCode[i] >= 'a' && jsCode[i] <= 'f') || 4197 (jsCode[i] >= 'A' && jsCode[i] <= 'F') 4198 )) 4199 i++; 4200 4201 highlighted ~= "<span class=\"highlighted-number\">" ~ jsCode[0 .. i] ~ "</span>"; 4202 jsCode = jsCode[i .. $]; 4203 startOfLine = false; 4204 break; 4205 case '\t', ' ', '\r': 4206 highlighted ~= jsCode[0]; 4207 jsCode = jsCode[1 .. $]; 4208 break; 4209 case '\n': 4210 startOfLine = true; 4211 highlighted ~= jsCode[0]; 4212 jsCode = jsCode[1 .. $]; 4213 break; 4214 case '\\': 4215 if(jsCode.length > 1 && jsCode[1] == '\n') { 4216 highlighted ~= jsCode[0 .. 1]; 4217 jsCode = jsCode[2 .. $]; 4218 } 4219 break; 4220 case ';': 4221 startOfLine = true; 4222 break; 4223 // escape html chars 4224 case '<': 4225 highlighted ~= "<"; 4226 jsCode = jsCode[1 .. $]; 4227 startOfLine = false; 4228 break; 4229 case '>': 4230 highlighted ~= ">"; 4231 jsCode = jsCode[1 .. $]; 4232 startOfLine = false; 4233 break; 4234 case '&': 4235 highlighted ~= "&"; 4236 jsCode = jsCode[1 .. $]; 4237 startOfLine = false; 4238 break; 4239 default: 4240 if(isIdentifierChar(jsCode[0])) { 4241 size_t i; 4242 while(i < jsCode.length && isIdentifierChar(jsCode[i])) 4243 i++; 4244 auto ident = jsCode[0 .. i]; 4245 jsCode = jsCode[i .. $]; 4246 4247 if(startOfLine) 4248 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4249 else if(ident == "true" || ident == "false") 4250 highlighted ~= "<span class=\"highlighted-number\">" ~ ident ~ "</span>"; 4251 else 4252 highlighted ~= "<span class=\"highlighted-attribute-name\">" ~ ident ~ "</span>"; 4253 } else { 4254 highlighted ~= jsCode[0]; 4255 jsCode = jsCode[1 .. $]; 4256 } 4257 4258 startOfLine = false; 4259 } 4260 } 4261 4262 return Html(highlighted); 4263 4264 } 4265 4266 Html syntaxHighlightCFamily(string jsCode, string language) { 4267 string highlighted; 4268 4269 bool isJsIdentifierChar(char c) { 4270 return 4271 (c >= 'a' && c <= 'z') || 4272 (c >= 'A' && c <= 'Z') || 4273 (c >= '0' && c <= '9') || 4274 (c == '$' || c == '_'); 4275 } 4276 4277 while(jsCode.length) { 4278 4279 4280 switch(jsCode[0]) { 4281 case '\'': 4282 case '\"': 4283 // string literal 4284 char start = jsCode[0]; 4285 size_t i = 1; 4286 while(i < jsCode.length && jsCode[i] != start && jsCode[i] != '\n') { 4287 if(jsCode[i] == '\\') 4288 i++; // skip escaped char too 4289 i++; 4290 } 4291 4292 i++; // skip closer 4293 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4294 jsCode = jsCode[i .. $]; 4295 break; 4296 case '#': 4297 // preprocessor directive / PHP # comment 4298 size_t i; 4299 while(i < jsCode.length && jsCode[i] != '\n') 4300 i++; 4301 4302 if(language == "php") 4303 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4304 else 4305 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4306 jsCode = jsCode[i .. $]; 4307 4308 break; 4309 case '/': 4310 // check for comment 4311 // javascript also has that stupid regex literal, but screw it 4312 if(jsCode.length > 1 && (jsCode[1] == '*' || jsCode[1] == '/')) { 4313 // it is a comment 4314 if(jsCode[1] == '/') { 4315 size_t i; 4316 while(i < jsCode.length && jsCode[i] != '\n') 4317 i++; 4318 4319 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4320 jsCode = jsCode[i .. $]; 4321 } else { 4322 // a star comment 4323 size_t i; 4324 while((i+1) < jsCode.length && !(jsCode[i] == '*' && jsCode[i+1] == '/')) 4325 i++; 4326 4327 if(i < jsCode.length) 4328 i+=2; // skip the */ 4329 4330 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4331 jsCode = jsCode[i .. $]; 4332 } 4333 } else { 4334 highlighted ~= '/'; 4335 jsCode = jsCode[1 .. $]; 4336 } 4337 break; 4338 case '0': .. case '9': 4339 // number literal 4340 // FIXME: negative exponents are not done here 4341 // nor are negative numbers, but I think that needs parsing anyway 4342 // it also doesn't do all things right but the assumption is we're highlighting valid code anyway 4343 size_t i; 4344 while(i < jsCode.length && ( 4345 (jsCode[i] >= '0' && jsCode[i] <= '9') 4346 || 4347 // really fuzzy but this is meant to highlight hex and exponents too 4348 jsCode[i] == '.' || jsCode[i] == 'e' || jsCode[i] == 'x' || 4349 (jsCode[i] >= 'a' && jsCode[i] <= 'f') || 4350 (jsCode[i] >= 'A' && jsCode[i] <= 'F') 4351 )) 4352 i++; 4353 4354 highlighted ~= "<span class=\"highlighted-number\">" ~ jsCode[0 .. i] ~ "</span>"; 4355 jsCode = jsCode[i .. $]; 4356 break; 4357 case '\t', ' ', '\n', '\r': 4358 highlighted ~= jsCode[0]; 4359 jsCode = jsCode[1 .. $]; 4360 break; 4361 case '?': 4362 if(language == "php") { 4363 if(jsCode.length >= 2 && jsCode[0 .. 2] == "?>") { 4364 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">?></span>"; 4365 jsCode = jsCode[2 .. $]; 4366 break; 4367 } 4368 } 4369 highlighted ~= jsCode[0]; 4370 jsCode = jsCode[1 .. $]; 4371 break; 4372 // escape html chars 4373 case '<': 4374 if(language == "php") { 4375 if(jsCode.length > 5 && jsCode[0 .. 5] == "<?php") { 4376 highlighted ~= "<span class=\"highlighted-preprocessor-directive\"><?php</span>"; 4377 jsCode = jsCode[5 .. $]; 4378 break; 4379 } 4380 } 4381 highlighted ~= "<"; 4382 jsCode = jsCode[1 .. $]; 4383 break; 4384 case '>': 4385 highlighted ~= ">"; 4386 jsCode = jsCode[1 .. $]; 4387 break; 4388 case '&': 4389 highlighted ~= "&"; 4390 jsCode = jsCode[1 .. $]; 4391 break; 4392 case '@': 4393 // FIXME highlight UDAs 4394 //goto case; 4395 //break; 4396 default: 4397 if(isJsIdentifierChar(jsCode[0])) { 4398 size_t i; 4399 while(i < jsCode.length && isJsIdentifierChar(jsCode[i])) 4400 i++; 4401 auto ident = jsCode[0 .. i]; 4402 jsCode = jsCode[i .. $]; 4403 4404 if(["function", "for", "in", "while", "new", "if", "else", "switch", "return", "break", "do", "delete", "this", "super", "continue", "goto"].canFind(ident)) 4405 highlighted ~= "<span class=\"highlighted-keyword\">" ~ ident ~ "</span>"; 4406 else if(["enum", "final", "virtual", "explicit", "var", "void", "const", "let", "int", "short", "unsigned", "char", "class", "struct", "float", "double", "typedef", "public", "protected", "private", "static"].canFind(ident)) 4407 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4408 else if(language == "java" && ["extends", "implements"].canFind(ident)) 4409 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4410 // FIXME: do i want to give using this same color? 4411 else if((language == "c#" || language == "c++") && ["using", "namespace"].canFind(ident)) 4412 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4413 else if(language == "java" && ["native", "package", "import"].canFind(ident)) 4414 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ ident ~ "</span>"; 4415 else if(ident[0] == '$') 4416 highlighted ~= "<span class=\"highlighted-identifier\">" ~ ident ~ "</span>"; 4417 else 4418 highlighted ~= ident; // "<span>" ~ ident ~ "</span>"; 4419 4420 } else { 4421 highlighted ~= jsCode[0]; 4422 jsCode = jsCode[1 .. $]; 4423 } 4424 } 4425 } 4426 4427 return Html(highlighted); 4428 }