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