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