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