d: Merge upstream dmd, druntime 4c18eed967, phobos d945686a4.
[official-gcc.git] / gcc / d / dmd / doc.d
blobb1a4c2fe504b708ad648de2adf3a84cd0e016af7
1 /**
2 * Ddoc documentation generation.
4 * Specification: $(LINK2 https://dlang.org/spec/ddoc.html, Documentation Generator)
6 * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
7 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
8 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/doc.d, _doc.d)
10 * Documentation: https://dlang.org/phobos/dmd_doc.html
11 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/doc.d
14 module dmd.doc;
16 import core.stdc.ctype;
17 import core.stdc.stdlib;
18 import core.stdc.stdio;
19 import core.stdc.string;
20 import core.stdc.time;
21 import dmd.aggregate;
22 import dmd.arraytypes;
23 import dmd.astenums;
24 import dmd.attrib;
25 import dmd.cond;
26 import dmd.dclass;
27 import dmd.declaration;
28 import dmd.denum;
29 import dmd.dimport;
30 import dmd.dmacro;
31 import dmd.dmodule;
32 import dmd.dscope;
33 import dmd.dstruct;
34 import dmd.dsymbol;
35 import dmd.dsymbolsem;
36 import dmd.dtemplate;
37 import dmd.errorsink;
38 import dmd.func;
39 import dmd.globals;
40 import dmd.hdrgen;
41 import dmd.id;
42 import dmd.identifier;
43 import dmd.lexer;
44 import dmd.location;
45 import dmd.mtype;
46 import dmd.root.array;
47 import dmd.root.file;
48 import dmd.root.filename;
49 import dmd.common.outbuffer;
50 import dmd.root.port;
51 import dmd.root.rmem;
52 import dmd.root.string;
53 import dmd.root.utf;
54 import dmd.tokens;
55 import dmd.visitor;
57 private:
59 public
60 struct Escape
62 const(char)[][char.max] strings;
64 /***************************************
65 * Find character string to replace c with.
67 const(char)[] escapeChar(char c) @safe
69 version (all)
71 //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c].ptr);
72 return strings[c];
74 else
76 const(char)[] s;
77 switch (c)
79 case '<':
80 s = "&lt;";
81 break;
82 case '>':
83 s = "&gt;";
84 break;
85 case '&':
86 s = "&amp;";
87 break;
88 default:
89 s = null;
90 break;
92 return s;
97 /***********************************************************
99 class Section
101 const(char)[] name;
102 const(char)[] body_;
103 int nooutput;
105 override string toString() const
107 assert(0);
110 void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, ref OutBuffer buf)
112 assert(a.length);
113 if (name.length)
115 static immutable table =
117 "AUTHORS",
118 "BUGS",
119 "COPYRIGHT",
120 "DATE",
121 "DEPRECATED",
122 "EXAMPLES",
123 "HISTORY",
124 "LICENSE",
125 "RETURNS",
126 "SEE_ALSO",
127 "STANDARDS",
128 "THROWS",
129 "VERSION",
131 foreach (entry; table)
133 if (iequals(entry, name))
135 buf.printf("$(DDOC_%s ", entry.ptr);
136 goto L1;
139 buf.writestring("$(DDOC_SECTION ");
140 // Replace _ characters with spaces
141 buf.writestring("$(DDOC_SECTION_H ");
142 size_t o = buf.length;
143 foreach (char c; name)
144 buf.writeByte((c == '_') ? ' ' : c);
145 escapeStrayParenthesis(loc, buf, o, false, sc.eSink);
146 buf.writestring(")");
148 else
150 buf.writestring("$(DDOC_DESCRIPTION ");
153 size_t o = buf.length;
154 buf.write(body_);
155 escapeStrayParenthesis(loc, buf, o, true, sc.eSink);
156 highlightText(sc, a, loc, buf, o);
157 buf.writestring(")");
161 /***********************************************************
163 final class ParamSection : Section
165 override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, ref OutBuffer buf)
167 assert(a.length);
168 Dsymbol s = (*a)[0]; // test
169 const(char)* p = body_.ptr;
170 size_t len = body_.length;
171 const(char)* pend = p + len;
172 const(char)* tempstart = null;
173 size_t templen = 0;
174 const(char)* namestart = null;
175 size_t namelen = 0; // !=0 if line continuation
176 const(char)* textstart = null;
177 size_t textlen = 0;
178 size_t paramcount = 0;
179 buf.writestring("$(DDOC_PARAMS ");
180 while (p < pend)
182 // Skip to start of macro
183 while (1)
185 switch (*p)
187 case ' ':
188 case '\t':
189 p++;
190 continue;
191 case '\n':
192 p++;
193 goto Lcont;
194 default:
195 if (isIdStart(p) || isCVariadicArg(p[0 .. cast(size_t)(pend - p)]))
196 break;
197 if (namelen)
198 goto Ltext;
199 // continuation of prev macro
200 goto Lskipline;
202 break;
204 tempstart = p;
205 while (isIdTail(p))
206 p += utfStride(p);
207 if (isCVariadicArg(p[0 .. cast(size_t)(pend - p)]))
208 p += 3;
209 templen = p - tempstart;
210 while (*p == ' ' || *p == '\t')
211 p++;
212 if (*p != '=')
214 if (namelen)
215 goto Ltext;
216 // continuation of prev macro
217 goto Lskipline;
219 p++;
220 if (namelen)
222 // Output existing param
224 //printf("param '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart);
225 ++paramcount;
226 HdrGenState hgs;
227 buf.writestring("$(DDOC_PARAM_ROW ");
229 buf.writestring("$(DDOC_PARAM_ID ");
231 size_t o = buf.length;
232 Parameter fparam = isFunctionParameter(a, namestart[0 .. namelen]);
233 if (!fparam)
235 // Comments on a template might refer to function parameters within.
236 // Search the parameters of nested eponymous functions (with the same name.)
237 fparam = isEponymousFunctionParameter(a, namestart[0 .. namelen]);
239 bool isCVariadic = isCVariadicParameter(a, namestart[0 .. namelen]);
240 if (isCVariadic)
242 buf.writestring("...");
244 else if (fparam && fparam.type && fparam.ident)
246 toCBuffer(fparam.type, buf, fparam.ident, hgs);
248 else
250 if (isTemplateParameter(a, namestart, namelen))
252 // 10236: Don't count template parameters for params check
253 --paramcount;
255 else if (!fparam)
257 sc.eSink.warning(s.loc, "Ddoc: function declaration has no parameter '%.*s'", cast(int)namelen, namestart);
259 buf.write(namestart[0 .. namelen]);
261 escapeStrayParenthesis(loc, buf, o, true, sc.eSink);
262 highlightCode(sc, a, buf, o);
264 buf.writestring(")");
265 buf.writestring("$(DDOC_PARAM_DESC ");
267 size_t o = buf.length;
268 buf.write(textstart[0 .. textlen]);
269 escapeStrayParenthesis(loc, buf, o, true, sc.eSink);
270 highlightText(sc, a, loc, buf, o);
272 buf.writestring(")");
274 buf.writestring(")");
275 namelen = 0;
276 if (p >= pend)
277 break;
279 namestart = tempstart;
280 namelen = templen;
281 while (*p == ' ' || *p == '\t')
282 p++;
283 textstart = p;
284 Ltext:
285 while (*p != '\n')
286 p++;
287 textlen = p - textstart;
288 p++;
289 Lcont:
290 continue;
291 Lskipline:
292 // Ignore this line
293 while (*p++ != '\n')
297 if (namelen)
298 goto L1;
299 // write out last one
300 buf.writestring(")");
301 TypeFunction tf = a.length == 1 ? isTypeFunction(s) : null;
302 if (tf)
304 size_t pcount = (tf.parameterList.parameters ? tf.parameterList.parameters.length : 0) +
305 cast(int)(tf.parameterList.varargs == VarArg.variadic);
306 if (pcount != paramcount)
308 sc.eSink.warning(s.loc, "Ddoc: parameter count mismatch, expected %llu, got %llu",
309 cast(ulong) pcount, cast(ulong) paramcount);
310 if (paramcount == 0)
312 // Chances are someone messed up the format
313 sc.eSink.warningSupplemental(s.loc, "Note that the format is `param = description`");
320 /***********************************************************
322 final class MacroSection : Section
324 override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, ref OutBuffer buf)
326 //printf("MacroSection::write()\n");
327 DocComment.parseMacros(dc.escapetable, *dc.pmacrotable, body_);
331 alias Sections = Array!(Section);
333 // Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one).
334 bool isCVariadicParameter(Dsymbols* a, const(char)[] p) @safe
336 foreach (member; *a)
338 TypeFunction tf = isTypeFunction(member);
339 if (tf && tf.parameterList.varargs == VarArg.variadic && p == "...")
340 return true;
342 return false;
345 Dsymbol getEponymousMember(TemplateDeclaration td) @safe
347 if (!td.onemember)
348 return null;
349 if (AggregateDeclaration ad = td.onemember.isAggregateDeclaration())
350 return ad;
351 if (FuncDeclaration fd = td.onemember.isFuncDeclaration())
352 return fd;
353 if (auto em = td.onemember.isEnumMember())
354 return null; // Keep backward compatibility. See compilable/ddoc9.d
355 if (VarDeclaration vd = td.onemember.isVarDeclaration())
356 return td.constraint ? null : vd;
357 return null;
360 TemplateDeclaration getEponymousParent(Dsymbol s) @safe
362 if (!s.parent)
363 return null;
364 TemplateDeclaration td = s.parent.isTemplateDeclaration();
365 return (td && getEponymousMember(td)) ? td : null;
368 immutable ddoc_default = import("default_ddoc_theme." ~ ddoc_ext);
369 immutable ddoc_decl_s = "$(DDOC_DECL ";
370 immutable ddoc_decl_e = ")\n";
371 immutable ddoc_decl_dd_s = "$(DDOC_DECL_DD ";
372 immutable ddoc_decl_dd_e = ")\n";
374 /****************************************************
375 * Generate Ddoc file for Module m.
376 * Params:
377 * m = Module
378 * ddoctext_ptr = combined text of .ddoc files for macro definitions
379 * ddoctext_length = extant of ddoctext_ptr
380 * datetime = charz returned by ctime()
381 * eSink = send error messages to eSink
382 * outbuf = append the Ddoc text to this
384 public
385 extern(C++) void gendocfile(Module m, const char* ddoctext_ptr, size_t ddoctext_length, const char* datetime, ErrorSink eSink, ref OutBuffer outbuf)
387 gendocfile(m, ddoctext_ptr[0 .. ddoctext_length], datetime, eSink, outbuf);
390 /****************************************************
391 * Generate Ddoc text for Module `m` and append it to `outbuf`.
392 * Params:
393 * m = Module
394 * ddoctext = combined text of .ddoc files for macro definitions
395 * datetime = charz returned by ctime()
396 * eSink = send error messages to eSink
397 * outbuf = append the Ddoc text to this
399 public
400 void gendocfile(Module m, const char[] ddoctext, const char* datetime, ErrorSink eSink, ref OutBuffer outbuf)
402 // Load internal default macros first
403 DocComment.parseMacros(m.escapetable, m.macrotable, ddoc_default[]);
405 // Ddoc files override default macros
406 DocComment.parseMacros(m.escapetable, m.macrotable, ddoctext);
408 Scope* sc = Scope.createGlobal(m, eSink); // create root scope
409 DocComment* dc = DocComment.parse(m, m.comment);
410 dc.pmacrotable = &m.macrotable;
411 dc.escapetable = m.escapetable;
412 sc.lastdc = dc;
413 // Generate predefined macros
414 // Set the title to be the name of the module
416 const p = m.toPrettyChars().toDString;
417 m.macrotable.define("TITLE", p);
419 // Set time macros
420 m.macrotable.define("DATETIME", datetime[0 .. 26]);
421 m.macrotable.define("YEAR", datetime[20 .. 20 + 4]);
423 const srcfilename = m.srcfile.toString();
424 m.macrotable.define("SRCFILENAME", srcfilename);
425 const docfilename = m.docfile.toString();
426 m.macrotable.define("DOCFILENAME", docfilename);
427 if (dc.copyright)
429 dc.copyright.nooutput = 1;
430 m.macrotable.define("COPYRIGHT", dc.copyright.body_);
433 OutBuffer buf;
434 if (m.filetype == FileType.ddoc)
436 const ploc = m.md ? &m.md.loc : &m.loc;
437 Loc loc = *ploc;
438 if (!loc.filename)
439 loc.filename = srcfilename.ptr;
441 size_t commentlen = strlen(cast(char*)m.comment);
442 Dsymbols a;
443 // https://issues.dlang.org/show_bug.cgi?id=9764
444 // Don't push m in a, to prevent emphasize ddoc file name.
445 if (dc.macros)
447 commentlen = dc.macros.name.ptr - m.comment;
448 dc.macros.write(loc, dc, sc, &a, buf);
450 buf.write(m.comment[0 .. commentlen]);
451 highlightText(sc, &a, loc, buf, 0);
453 else
455 Dsymbols a;
456 a.push(m);
457 dc.writeSections(sc, &a, buf);
458 emitMemberComments(m, buf, sc);
460 //printf("BODY= '%.*s'\n", cast(int)buf.length, buf.data);
461 m.macrotable.define("BODY", buf[]);
463 OutBuffer buf2;
464 buf2.writestring("$(DDOC)");
465 size_t end = buf2.length;
467 // Expand buf in place with macro expansions
468 const success = m.macrotable.expand(buf2, 0, end, null, global.recursionLimit, &isIdStart, &isIdTail);
469 if (!success)
470 eSink.error(Loc.initial, "DDoc macro expansion limit exceeded; more than %d expansions.", global.recursionLimit);
472 /* Remove all the escape sequences from buf,
473 * and make CR-LF the newline.
475 const slice = buf2[];
476 outbuf.reserve(slice.length);
477 auto p = slice.ptr;
478 for (size_t j = 0; j < slice.length; j++)
480 char c = p[j];
481 if (c == 0xFF && j + 1 < slice.length)
483 j++;
484 continue;
486 if (c == '\n')
487 outbuf.writeByte('\r');
488 else if (c == '\r')
490 outbuf.writestring("\r\n");
491 if (j + 1 < slice.length && p[j + 1] == '\n')
493 j++;
495 continue;
497 outbuf.writeByte(c);
501 /****************************************************
502 * Having unmatched parentheses can hose the output of Ddoc,
503 * as the macros depend on properly nested parentheses.
504 * This function replaces all ( with $(LPAREN) and ) with $(RPAREN)
505 * to preserve text literally. This also means macros in the
506 * text won't be expanded.
508 public
509 void escapeDdocString(ref OutBuffer buf, size_t start)
511 for (size_t u = start; u < buf.length; u++)
513 char c = buf[u];
514 switch (c)
516 case '$':
517 buf.remove(u, 1);
518 buf.insert(u, "$(DOLLAR)");
519 u += 8;
520 break;
521 case '(':
522 buf.remove(u, 1); //remove the (
523 buf.insert(u, "$(LPAREN)"); //insert this instead
524 u += 8; //skip over newly inserted macro
525 break;
526 case ')':
527 buf.remove(u, 1); //remove the )
528 buf.insert(u, "$(RPAREN)"); //insert this instead
529 u += 8; //skip over newly inserted macro
530 break;
531 default:
532 break;
537 /****************************************************
538 * Having unmatched parentheses can hose the output of Ddoc,
539 * as the macros depend on properly nested parentheses.
541 * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN).
543 * Params:
544 * loc = source location of start of text. It is a mutable copy to allow incrementing its linenum, for printing the correct line number when an error is encountered in a multiline block of ddoc.
545 * buf = an OutBuffer containing the DDoc
546 * start = the index within buf to start replacing unmatched parentheses
547 * respectBackslashEscapes = if true, always replace parentheses that are
548 * directly preceeded by a backslash with $(LPAREN) or $(RPAREN) instead of
549 * counting them as stray parentheses
551 private void escapeStrayParenthesis(Loc loc, ref OutBuffer buf, size_t start, bool respectBackslashEscapes, ErrorSink eSink)
553 uint par_open = 0;
554 char inCode = 0;
555 bool atLineStart = true;
556 for (size_t u = start; u < buf.length; u++)
558 char c = buf[u];
559 switch (c)
561 case '(':
562 if (!inCode)
563 par_open++;
564 atLineStart = false;
565 break;
566 case ')':
567 if (!inCode)
569 if (par_open == 0)
571 //stray ')'
572 eSink.warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output. Use $(RPAREN) instead for unpaired right parentheses.");
573 buf.remove(u, 1); //remove the )
574 buf.insert(u, "$(RPAREN)"); //insert this instead
575 u += 8; //skip over newly inserted macro
577 else
578 par_open--;
580 atLineStart = false;
581 break;
582 case '\n':
583 atLineStart = true;
584 version (none)
586 // For this to work, loc must be set to the beginning of the passed
587 // text which is currently not possible
588 // (loc is set to the Loc of the Dsymbol)
589 loc.linnum++;
591 break;
592 case ' ':
593 case '\r':
594 case '\t':
595 break;
596 case '-':
597 case '`':
598 case '~':
599 // Issue 15465: don't try to escape unbalanced parens inside code
600 // blocks.
601 int numdash = 1;
602 for (++u; u < buf.length && buf[u] == c; ++u)
603 ++numdash;
604 --u;
605 if (c == '`' || (atLineStart && numdash >= 3))
607 if (inCode == c)
608 inCode = 0;
609 else if (!inCode)
610 inCode = c;
612 atLineStart = false;
613 break;
614 case '\\':
615 // replace backslash-escaped parens with their macros
616 if (!inCode && respectBackslashEscapes && u+1 < buf.length)
618 if (buf[u+1] == '(' || buf[u+1] == ')')
620 const paren = buf[u+1] == '(' ? "$(LPAREN)" : "$(RPAREN)";
621 buf.remove(u, 2); //remove the \)
622 buf.insert(u, paren); //insert this instead
623 u += 8; //skip over newly inserted macro
625 else if (buf[u+1] == '\\')
626 ++u;
628 break;
629 default:
630 atLineStart = false;
631 break;
634 if (par_open) // if any unmatched lparens
636 par_open = 0;
637 for (size_t u = buf.length; u > start;)
639 u--;
640 char c = buf[u];
641 switch (c)
643 case ')':
644 par_open++;
645 break;
646 case '(':
647 if (par_open == 0)
649 //stray '('
650 eSink.warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output. Use $(LPAREN) instead for unpaired left parentheses.");
651 buf.remove(u, 1); //remove the (
652 buf.insert(u, "$(LPAREN)"); //insert this instead
654 else
655 par_open--;
656 break;
657 default:
658 break;
664 // Basically, this is to skip over things like private{} blocks in a struct or
665 // class definition that don't add any components to the qualified name.
666 Scope* skipNonQualScopes(Scope* sc) @safe
668 while (sc && !sc.scopesym)
669 sc = sc.enclosing;
670 return sc;
673 bool emitAnchorName(ref OutBuffer buf, Dsymbol s, Scope* sc, bool includeParent)
675 if (!s || s.isPackage() || s.isModule())
676 return false;
677 // Add parent names first
678 bool dot = false;
679 auto eponymousParent = getEponymousParent(s);
680 if (includeParent && s.parent || eponymousParent)
681 dot = emitAnchorName(buf, s.parent, sc, includeParent);
682 else if (includeParent && sc)
683 dot = emitAnchorName(buf, sc.scopesym, skipNonQualScopes(sc.enclosing), includeParent);
684 // Eponymous template members can share the parent anchor name
685 if (eponymousParent)
686 return dot;
687 if (dot)
688 buf.writeByte('.');
689 // Use "this" not "__ctor"
690 TemplateDeclaration td;
691 if (s.isCtorDeclaration() || ((td = s.isTemplateDeclaration()) !is null && td.onemember && td.onemember.isCtorDeclaration()))
693 buf.writestring("this");
695 else
697 /* We just want the identifier, not overloads like TemplateDeclaration::toChars.
698 * We don't want the template parameter list and constraints. */
699 buf.writestring(s.Dsymbol.toChars());
701 return true;
704 void emitAnchor(ref OutBuffer buf, Dsymbol s, Scope* sc, bool forHeader = false)
706 Identifier ident;
708 OutBuffer anc;
709 emitAnchorName(anc, s, skipNonQualScopes(sc), true);
710 ident = Identifier.idPool(anc[]);
713 auto pcount = cast(void*)ident in sc.anchorCounts;
714 typeof(*pcount) count;
715 if (!forHeader)
717 if (pcount)
719 // Existing anchor,
720 // don't write an anchor for matching consecutive ditto symbols
721 TemplateDeclaration td = getEponymousParent(s);
722 if (sc.prevAnchor == ident && sc.lastdc && (isDitto(s.comment) || (td && isDitto(td.comment))))
723 return;
725 count = ++*pcount;
727 else
729 sc.anchorCounts[cast(void*)ident] = 1;
730 count = 1;
734 // cache anchor name
735 sc.prevAnchor = ident;
736 auto macroName = forHeader ? "DDOC_HEADER_ANCHOR" : "DDOC_ANCHOR";
738 if (auto imp = s.isImport())
740 // For example: `public import core.stdc.string : memcpy, memcmp;`
741 if (imp.aliases.length > 0)
743 for(int i = 0; i < imp.aliases.length; i++)
745 // Need to distinguish between
746 // `public import core.stdc.string : memcpy, memcmp;` and
747 // `public import core.stdc.string : copy = memcpy, compare = memcmp;`
748 auto a = imp.aliases[i];
749 auto id = a ? a : imp.names[i];
750 auto loc = Loc.init;
751 if (auto symFromId = sc.search(loc, id, null))
753 emitAnchor(buf, symFromId, sc, forHeader);
757 else
759 // For example: `public import str = core.stdc.string;`
760 if (imp.aliasId)
762 auto symbolName = imp.aliasId.toString();
764 buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr,
765 cast(int) symbolName.length, symbolName.ptr);
767 if (forHeader)
769 buf.printf(", %.*s", cast(int) symbolName.length, symbolName.ptr);
772 else
774 // The general case: `public import core.stdc.string;`
776 // fully qualify imports so `core.stdc.string` doesn't appear as `core`
777 void printFullyQualifiedImport()
779 foreach (const pid; imp.packages)
781 buf.printf("%s.", pid.toChars());
783 buf.writestring(imp.id.toString());
786 buf.printf("$(%.*s ", cast(int) macroName.length, macroName.ptr);
787 printFullyQualifiedImport();
789 if (forHeader)
791 buf.printf(", ");
792 printFullyQualifiedImport();
796 buf.writeByte(')');
799 else
801 auto symbolName = ident.toString();
802 buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr,
803 cast(int) symbolName.length, symbolName.ptr);
805 // only append count once there's a duplicate
806 if (count > 1)
807 buf.printf(".%u", count);
809 if (forHeader)
811 Identifier shortIdent;
813 OutBuffer anc;
814 emitAnchorName(anc, s, skipNonQualScopes(sc), false);
815 shortIdent = Identifier.idPool(anc[]);
818 auto shortName = shortIdent.toString();
819 buf.printf(", %.*s", cast(int) shortName.length, shortName.ptr);
822 buf.writeByte(')');
826 /******************************* emitComment **********************************/
828 /** Get leading indentation from 'src' which represents lines of code. */
829 size_t getCodeIndent(const(char)* src)
831 while (src && (*src == '\r' || *src == '\n'))
832 ++src; // skip until we find the first non-empty line
833 size_t codeIndent = 0;
834 while (src && (*src == ' ' || *src == '\t'))
836 codeIndent++;
837 src++;
839 return codeIndent;
842 /** Recursively expand template mixin member docs into the scope. */
843 void expandTemplateMixinComments(TemplateMixin tm, ref OutBuffer buf, Scope* sc)
845 if (!tm.semanticRun)
846 tm.dsymbolSemantic(sc);
847 TemplateDeclaration td = (tm && tm.tempdecl) ? tm.tempdecl.isTemplateDeclaration() : null;
848 if (td && td.members)
850 for (size_t i = 0; i < td.members.length; i++)
852 Dsymbol sm = (*td.members)[i];
853 TemplateMixin tmc = sm.isTemplateMixin();
854 if (tmc && tmc.comment)
855 expandTemplateMixinComments(tmc, buf, sc);
856 else
857 emitComment(sm, buf, sc);
862 void emitMemberComments(ScopeDsymbol sds, ref OutBuffer buf, Scope* sc)
864 if (!sds.members)
865 return;
866 //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars());
867 const(char)[] m = "$(DDOC_MEMBERS ";
868 if (sds.isTemplateDeclaration())
869 m = "$(DDOC_TEMPLATE_MEMBERS ";
870 else if (sds.isClassDeclaration())
871 m = "$(DDOC_CLASS_MEMBERS ";
872 else if (sds.isStructDeclaration())
873 m = "$(DDOC_STRUCT_MEMBERS ";
874 else if (sds.isEnumDeclaration())
875 m = "$(DDOC_ENUM_MEMBERS ";
876 else if (sds.isModule())
877 m = "$(DDOC_MODULE_MEMBERS ";
878 size_t offset1 = buf.length; // save starting offset
879 buf.writestring(m);
880 size_t offset2 = buf.length; // to see if we write anything
881 sc = sc.push(sds);
882 for (size_t i = 0; i < sds.members.length; i++)
884 Dsymbol s = (*sds.members)[i];
885 //printf("\ts = '%s'\n", s.toChars());
886 // only expand if parent is a non-template (semantic won't work)
887 if (s.comment && s.isTemplateMixin() && s.parent && !s.parent.isTemplateDeclaration())
888 expandTemplateMixinComments(cast(TemplateMixin)s, buf, sc);
889 emitComment(s, buf, sc);
891 emitComment(null, buf, sc);
892 sc.pop();
893 if (buf.length == offset2)
895 /* Didn't write out any members, so back out last write
897 buf.setsize(offset1);
899 else
900 buf.writestring(")");
903 void emitVisibility(ref OutBuffer buf, Import i)
905 // imports are private by default, which is different from other declarations
906 // so they should explicitly show their visibility
907 emitVisibility(buf, i.visibility);
910 void emitVisibility(ref OutBuffer buf, Declaration d)
912 auto vis = d.visibility;
913 if (vis.kind != Visibility.Kind.undefined && vis.kind != Visibility.Kind.public_)
915 emitVisibility(buf, vis);
919 void emitVisibility(ref OutBuffer buf, Visibility vis)
921 visibilityToBuffer(buf, vis);
922 buf.writeByte(' ');
925 void emitComment(Dsymbol s, ref OutBuffer buf, Scope* sc)
927 extern (C++) final class EmitComment : Visitor
929 alias visit = Visitor.visit;
930 public:
931 OutBuffer* buf;
932 Scope* sc;
934 extern (D) this(ref OutBuffer buf, Scope* sc) scope
936 this.buf = &buf;
937 this.sc = sc;
940 override void visit(Dsymbol)
944 override void visit(InvariantDeclaration)
948 override void visit(UnitTestDeclaration)
952 override void visit(PostBlitDeclaration)
956 override void visit(DtorDeclaration)
960 override void visit(StaticCtorDeclaration)
964 override void visit(StaticDtorDeclaration)
968 override void visit(TypeInfoDeclaration)
972 void emit(Scope* sc, Dsymbol s, const(char)* com)
974 if (s && sc.lastdc && isDitto(com))
976 sc.lastdc.a.push(s);
977 return;
979 // Put previous doc comment if exists
980 if (DocComment* dc = sc.lastdc)
982 assert(dc.a.length > 0, "Expects at least one declaration for a" ~
983 "documentation comment");
985 auto symbol = dc.a[0];
987 buf.writestring("$(DDOC_MEMBER");
988 buf.writestring("$(DDOC_MEMBER_HEADER");
989 emitAnchor(*buf, symbol, sc, true);
990 buf.writeByte(')');
992 // Put the declaration signatures as the document 'title'
993 buf.writestring(ddoc_decl_s);
994 for (size_t i = 0; i < dc.a.length; i++)
996 Dsymbol sx = dc.a[i];
997 // the added linebreaks in here make looking at multiple
998 // signatures more appealing
999 if (i == 0)
1001 size_t o = buf.length;
1002 toDocBuffer(sx, *buf, sc);
1003 highlightCode(sc, sx, *buf, o);
1004 buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
1005 continue;
1007 buf.writestring("$(DDOC_DITTO ");
1009 size_t o = buf.length;
1010 toDocBuffer(sx, *buf, sc);
1011 highlightCode(sc, sx, *buf, o);
1013 buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
1014 buf.writeByte(')');
1016 buf.writestring(ddoc_decl_e);
1017 // Put the ddoc comment as the document 'description'
1018 buf.writestring(ddoc_decl_dd_s);
1020 dc.writeSections(sc, &dc.a, *buf);
1021 if (ScopeDsymbol sds = dc.a[0].isScopeDsymbol())
1022 emitMemberComments(sds, *buf, sc);
1024 buf.writestring(ddoc_decl_dd_e);
1025 buf.writeByte(')');
1026 //printf("buf.2 = [[%.*s]]\n", cast(int)(buf.length - o0), buf.data + o0);
1028 if (s)
1030 DocComment* dc = DocComment.parse(s, com);
1031 dc.pmacrotable = &sc._module.macrotable;
1032 sc.lastdc = dc;
1036 override void visit(Import imp)
1038 if (imp.visible().kind != Visibility.Kind.public_ && sc.visibility.kind != Visibility.Kind.export_)
1039 return;
1041 if (imp.comment)
1042 emit(sc, imp, imp.comment);
1045 override void visit(Declaration d)
1047 //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d.toChars(), d.comment);
1048 //printf("type = %p\n", d.type);
1049 const(char)* com = d.comment;
1050 if (TemplateDeclaration td = getEponymousParent(d))
1052 if (isDitto(td.comment))
1053 com = td.comment;
1054 else
1055 com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true);
1057 else
1059 if (!d.ident)
1060 return;
1061 if (!d.type)
1063 if (!d.isCtorDeclaration() &&
1064 !d.isAliasDeclaration() &&
1065 !d.isVarDeclaration())
1067 return;
1070 if (d.visibility.kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
1071 return;
1073 if (!com)
1074 return;
1075 emit(sc, d, com);
1078 override void visit(AggregateDeclaration ad)
1080 //printf("AggregateDeclaration::emitComment() '%s'\n", ad.toChars());
1081 const(char)* com = ad.comment;
1082 if (TemplateDeclaration td = getEponymousParent(ad))
1084 if (isDitto(td.comment))
1085 com = td.comment;
1086 else
1087 com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true);
1089 else
1091 if (ad.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
1092 return;
1093 if (!ad.comment)
1094 return;
1096 if (!com)
1097 return;
1098 emit(sc, ad, com);
1101 override void visit(TemplateDeclaration td)
1103 //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td.toChars(), td.kind());
1104 if (td.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
1105 return;
1106 if (!td.comment)
1107 return;
1108 if (Dsymbol ss = getEponymousMember(td))
1110 ss.accept(this);
1111 return;
1113 emit(sc, td, td.comment);
1116 override void visit(EnumDeclaration ed)
1118 if (ed.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
1119 return;
1120 if (ed.isAnonymous() && ed.members)
1122 for (size_t i = 0; i < ed.members.length; i++)
1124 Dsymbol s = (*ed.members)[i];
1125 emitComment(s, *buf, sc);
1127 return;
1129 if (!ed.comment)
1130 return;
1131 if (ed.isAnonymous())
1132 return;
1133 emit(sc, ed, ed.comment);
1136 override void visit(EnumMember em)
1138 //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em.toChars(), em.comment);
1139 if (em.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
1140 return;
1141 if (!em.comment)
1142 return;
1143 emit(sc, em, em.comment);
1146 override void visit(AttribDeclaration ad)
1148 //printf("AttribDeclaration::emitComment(sc = %p)\n", sc);
1149 /* A general problem with this,
1150 * illustrated by https://issues.dlang.org/show_bug.cgi?id=2516
1151 * is that attributes are not transmitted through to the underlying
1152 * member declarations for template bodies, because semantic analysis
1153 * is not done for template declaration bodies
1154 * (only template instantiations).
1155 * Hence, Ddoc omits attributes from template members.
1157 Dsymbols* d = ad.include(null);
1158 if (d)
1160 for (size_t i = 0; i < d.length; i++)
1162 Dsymbol s = (*d)[i];
1163 //printf("AttribDeclaration::emitComment %s\n", s.toChars());
1164 emitComment(s, *buf, sc);
1169 override void visit(VisibilityDeclaration pd)
1171 if (pd.decl)
1173 Scope* scx = sc;
1174 sc = sc.copy();
1175 sc.visibility = pd.visibility;
1176 visit(cast(AttribDeclaration)pd);
1177 scx.lastdc = sc.lastdc;
1178 sc = sc.pop();
1182 override void visit(ConditionalDeclaration cd)
1184 //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc);
1185 if (cd.condition.inc != Include.notComputed)
1187 visit(cast(AttribDeclaration)cd);
1188 return;
1190 /* If generating doc comment, be careful because if we're inside
1191 * a template, then include(null) will fail.
1193 Dsymbols* d = cd.decl ? cd.decl : cd.elsedecl;
1194 for (size_t i = 0; i < d.length; i++)
1196 Dsymbol s = (*d)[i];
1197 emitComment(s, *buf, sc);
1202 scope EmitComment v = new EmitComment(buf, sc);
1203 if (!s)
1204 v.emit(sc, null, null);
1205 else
1206 s.accept(v);
1209 void toDocBuffer(Dsymbol s, ref OutBuffer buf, Scope* sc)
1211 extern (C++) final class ToDocBuffer : Visitor
1213 alias visit = Visitor.visit;
1214 public:
1215 OutBuffer* buf;
1216 Scope* sc;
1218 extern (D) this(ref OutBuffer buf, Scope* sc) scope
1220 this.buf = &buf;
1221 this.sc = sc;
1224 override void visit(Dsymbol s)
1226 //printf("Dsymbol::toDocbuffer() %s\n", s.toChars());
1227 HdrGenState hgs;
1228 hgs.ddoc = true;
1229 toCBuffer(s, *buf, hgs);
1232 void prefix(Dsymbol s)
1234 if (s.isDeprecated())
1235 buf.writestring("deprecated ");
1236 if (Declaration d = s.isDeclaration())
1238 emitVisibility(*buf, d);
1239 if (d.isStatic())
1240 buf.writestring("static ");
1241 else if (d.isFinal())
1242 buf.writestring("final ");
1243 else if (d.isAbstract())
1244 buf.writestring("abstract ");
1246 if (d.isFuncDeclaration()) // functionToBufferFull handles this
1247 return;
1249 if (d.isImmutable())
1250 buf.writestring("immutable ");
1251 if (d.storage_class & STC.shared_)
1252 buf.writestring("shared ");
1253 if (d.isWild())
1254 buf.writestring("inout ");
1255 if (d.isConst())
1256 buf.writestring("const ");
1258 if (d.isSynchronized())
1259 buf.writestring("synchronized ");
1261 if (d.storage_class & STC.manifest)
1262 buf.writestring("enum ");
1264 // Add "auto" for the untyped variable in template members
1265 if (!d.type && d.isVarDeclaration() &&
1266 !d.isImmutable() && !(d.storage_class & STC.shared_) && !d.isWild() && !d.isConst() &&
1267 !d.isSynchronized())
1269 buf.writestring("auto ");
1274 override void visit(Import i)
1276 HdrGenState hgs;
1277 hgs.ddoc = true;
1278 emitVisibility(*buf, i);
1279 toCBuffer(i, *buf, hgs);
1282 override void visit(Declaration d)
1284 if (!d.ident)
1285 return;
1286 TemplateDeclaration td = getEponymousParent(d);
1287 //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d.toChars(), d.originalType ? d.originalType.toChars() : "--", td ? td.toChars() : "--");
1288 HdrGenState hgs;
1289 hgs.ddoc = true;
1290 if (d.isDeprecated())
1291 buf.writestring("$(DEPRECATED ");
1292 prefix(d);
1293 if (d.type)
1295 Type origType = d.originalType ? d.originalType : d.type;
1296 if (origType.ty == Tfunction)
1298 functionToBufferFull(cast(TypeFunction)origType, *buf, d.ident, &hgs, td);
1300 else
1301 toCBuffer(origType, *buf, d.ident, hgs);
1303 else
1304 buf.writestring(d.ident.toString());
1305 if (d.isVarDeclaration() && td)
1307 buf.writeByte('(');
1308 if (td.origParameters && td.origParameters.length)
1310 for (size_t i = 0; i < td.origParameters.length; i++)
1312 if (i)
1313 buf.writestring(", ");
1314 toCBuffer((*td.origParameters)[i], *buf, hgs);
1317 buf.writeByte(')');
1319 // emit constraints if declaration is a templated declaration
1320 if (td && td.constraint)
1322 bool noFuncDecl = td.isFuncDeclaration() is null;
1323 if (noFuncDecl)
1325 buf.writestring("$(DDOC_CONSTRAINT ");
1328 toCBuffer(td.constraint, *buf, hgs);
1330 if (noFuncDecl)
1332 buf.writestring(")");
1335 if (d.isDeprecated())
1336 buf.writestring(")");
1337 buf.writestring(";\n");
1340 override void visit(AliasDeclaration ad)
1342 //printf("AliasDeclaration::toDocbuffer() %s\n", ad.toChars());
1343 if (!ad.ident)
1344 return;
1345 if (ad.isDeprecated())
1346 buf.writestring("deprecated ");
1347 emitVisibility(*buf, ad);
1348 buf.printf("alias %s = ", ad.toChars());
1349 if (Dsymbol s = ad.aliassym) // ident alias
1351 prettyPrintDsymbol(s, ad.parent);
1353 else if (Type type = ad.getType()) // type alias
1355 if (type.ty == Tclass || type.ty == Tstruct || type.ty == Tenum)
1357 if (Dsymbol s = type.toDsymbol(null)) // elaborate type
1358 prettyPrintDsymbol(s, ad.parent);
1359 else
1360 buf.writestring(type.toChars());
1362 else
1364 // simple type
1365 buf.writestring(type.toChars());
1368 buf.writestring(";\n");
1371 void parentToBuffer(Dsymbol s)
1373 if (s && !s.isPackage() && !s.isModule())
1375 parentToBuffer(s.parent);
1376 buf.writestring(s.toChars());
1377 buf.writestring(".");
1381 static bool inSameModule(Dsymbol s, Dsymbol p) @safe
1383 for (; s; s = s.parent)
1385 if (s.isModule())
1386 break;
1388 for (; p; p = p.parent)
1390 if (p.isModule())
1391 break;
1393 return s == p;
1396 void prettyPrintDsymbol(Dsymbol s, Dsymbol parent)
1398 if (s.parent && (s.parent == parent)) // in current scope -> naked name
1400 buf.writestring(s.toChars());
1402 else if (!inSameModule(s, parent)) // in another module -> full name
1404 buf.writestring(s.toPrettyChars());
1406 else // nested in a type in this module -> full name w/o module name
1408 // if alias is nested in a user-type use module-scope lookup
1409 if (!parent.isModule() && !parent.isPackage())
1410 buf.writestring(".");
1411 parentToBuffer(s.parent);
1412 buf.writestring(s.toChars());
1416 override void visit(AggregateDeclaration ad)
1418 if (!ad.ident)
1419 return;
1420 version (none)
1422 emitVisibility(buf, ad);
1424 buf.printf("%s %s", ad.kind(), ad.toChars());
1425 buf.writestring(";\n");
1428 override void visit(StructDeclaration sd)
1430 //printf("StructDeclaration::toDocbuffer() %s\n", sd.toChars());
1431 if (!sd.ident)
1432 return;
1433 version (none)
1435 emitVisibility(buf, sd);
1437 if (TemplateDeclaration td = getEponymousParent(sd))
1439 toDocBuffer(td, *buf, sc);
1441 else
1443 buf.printf("%s %s", sd.kind(), sd.toChars());
1445 buf.writestring(";\n");
1448 override void visit(ClassDeclaration cd)
1450 //printf("ClassDeclaration::toDocbuffer() %s\n", cd.toChars());
1451 if (!cd.ident)
1452 return;
1453 version (none)
1455 emitVisibility(*buf, cd);
1457 if (TemplateDeclaration td = getEponymousParent(cd))
1459 toDocBuffer(td, *buf, sc);
1461 else
1463 if (!cd.isInterfaceDeclaration() && cd.isAbstract())
1464 buf.writestring("abstract ");
1465 buf.printf("%s %s", cd.kind(), cd.toChars());
1467 int any = 0;
1468 for (size_t i = 0; i < cd.baseclasses.length; i++)
1470 BaseClass* bc = (*cd.baseclasses)[i];
1471 if (bc.sym && bc.sym.ident == Id.Object)
1472 continue;
1473 if (any)
1474 buf.writestring(", ");
1475 else
1477 buf.writestring(": ");
1478 any = 1;
1481 if (bc.sym)
1483 buf.printf("$(DDOC_PSUPER_SYMBOL %s)", bc.sym.toPrettyChars());
1485 else
1487 HdrGenState hgs;
1488 toCBuffer(bc.type, *buf, null, hgs);
1491 buf.writestring(";\n");
1494 override void visit(EnumDeclaration ed)
1496 if (!ed.ident)
1497 return;
1498 buf.printf("%s %s", ed.kind(), ed.toChars());
1499 if (ed.memtype)
1501 buf.writestring(": $(DDOC_ENUM_BASETYPE ");
1502 HdrGenState hgs;
1503 toCBuffer(ed.memtype, *buf, null, hgs);
1504 buf.writestring(")");
1506 buf.writestring(";\n");
1509 override void visit(EnumMember em)
1511 if (!em.ident)
1512 return;
1513 buf.writestring(em.toChars());
1517 scope ToDocBuffer v = new ToDocBuffer(buf, sc);
1518 s.accept(v);
1521 /***********************************************************
1523 public
1524 struct DocComment
1526 Sections sections; // Section*[]
1527 Section summary;
1528 Section copyright;
1529 Section macros;
1530 MacroTable* pmacrotable;
1531 Escape* escapetable;
1532 Dsymbols a;
1534 static DocComment* parse(Dsymbol s, const(char)* comment)
1536 //printf("parse(%s): '%s'\n", s.toChars(), comment);
1537 auto dc = new DocComment();
1538 dc.a.push(s);
1539 if (!comment)
1540 return dc;
1541 dc.parseSections(comment);
1542 for (size_t i = 0; i < dc.sections.length; i++)
1544 Section sec = dc.sections[i];
1545 if (iequals("copyright", sec.name))
1547 dc.copyright = sec;
1549 if (iequals("macros", sec.name))
1551 dc.macros = sec;
1554 return dc;
1557 /************************************************
1558 * Parse macros out of Macros: section.
1559 * Macros are of the form:
1560 * name1 = value1
1562 * name2 = value2
1564 extern(D) static void parseMacros(
1565 Escape* escapetable, ref MacroTable pmacrotable, const(char)[] m)
1567 const(char)* p = m.ptr;
1568 size_t len = m.length;
1569 const(char)* pend = p + len;
1570 const(char)* tempstart = null;
1571 size_t templen = 0;
1572 const(char)* namestart = null;
1573 size_t namelen = 0; // !=0 if line continuation
1574 const(char)* textstart = null;
1575 size_t textlen = 0;
1576 while (p < pend)
1578 // Skip to start of macro
1579 while (1)
1581 if (p >= pend)
1582 goto Ldone;
1583 switch (*p)
1585 case ' ':
1586 case '\t':
1587 p++;
1588 continue;
1589 case '\r':
1590 case '\n':
1591 p++;
1592 goto Lcont;
1593 default:
1594 if (isIdStart(p))
1595 break;
1596 if (namelen)
1597 goto Ltext; // continuation of prev macro
1598 goto Lskipline;
1600 break;
1602 tempstart = p;
1603 while (1)
1605 if (p >= pend)
1606 goto Ldone;
1607 if (!isIdTail(p))
1608 break;
1609 p += utfStride(p);
1611 templen = p - tempstart;
1612 while (1)
1614 if (p >= pend)
1615 goto Ldone;
1616 if (!(*p == ' ' || *p == '\t'))
1617 break;
1618 p++;
1620 if (*p != '=')
1622 if (namelen)
1623 goto Ltext; // continuation of prev macro
1624 goto Lskipline;
1626 p++;
1627 if (p >= pend)
1628 goto Ldone;
1629 if (namelen)
1631 // Output existing macro
1633 //printf("macro '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart);
1634 if (iequals("ESCAPES", namestart[0 .. namelen]))
1635 parseEscapes(escapetable, textstart[0 .. textlen]);
1636 else
1637 pmacrotable.define(namestart[0 .. namelen], textstart[0 .. textlen]);
1638 namelen = 0;
1639 if (p >= pend)
1640 break;
1642 namestart = tempstart;
1643 namelen = templen;
1644 while (p < pend && (*p == ' ' || *p == '\t'))
1645 p++;
1646 textstart = p;
1647 Ltext:
1648 while (p < pend && *p != '\r' && *p != '\n')
1649 p++;
1650 textlen = p - textstart;
1651 p++;
1652 //printf("p = %p, pend = %p\n", p, pend);
1653 Lcont:
1654 continue;
1655 Lskipline:
1656 // Ignore this line
1657 while (p < pend && *p != '\r' && *p != '\n')
1658 p++;
1660 Ldone:
1661 if (namelen)
1662 goto L1; // write out last one
1665 /**************************************
1666 * Parse escapes of the form:
1667 * /c/string/
1668 * where c is a single character.
1669 * Multiple escapes can be separated
1670 * by whitespace and/or commas.
1672 static void parseEscapes(Escape* escapetable, const(char)[] text)
1674 if (!escapetable)
1676 escapetable = new Escape();
1677 memset(escapetable, 0, Escape.sizeof);
1679 //printf("parseEscapes('%.*s') pescapetable = %p\n", cast(int)text.length, text.ptr, escapetable);
1680 const(char)* p = text.ptr;
1681 const(char)* pend = p + text.length;
1682 while (1)
1684 while (1)
1686 if (p + 4 >= pend)
1687 return;
1688 if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ','))
1689 break;
1690 p++;
1692 if (p[0] != '/' || p[2] != '/')
1693 return;
1694 char c = p[1];
1695 p += 3;
1696 const(char)* start = p;
1697 while (1)
1699 if (p >= pend)
1700 return;
1701 if (*p == '/')
1702 break;
1703 p++;
1705 size_t len = p - start;
1706 char* s = cast(char*)memcpy(mem.xmalloc(len + 1), start, len);
1707 s[len] = 0;
1708 escapetable.strings[c] = s[0 .. len];
1709 //printf("\t%c = '%s'\n", c, s);
1710 p++;
1714 /*****************************************
1715 * Parse next paragraph out of *pcomment.
1716 * Update *pcomment to point past paragraph.
1717 * Returns NULL if no more paragraphs.
1718 * If paragraph ends in 'identifier:',
1719 * then (*pcomment)[0 .. idlen] is the identifier.
1721 void parseSections(const(char)* comment)
1723 const(char)* p;
1724 const(char)* pstart;
1725 const(char)* pend;
1726 const(char)* idstart = null; // dead-store to prevent spurious warning
1727 size_t idlen;
1728 const(char)* name = null;
1729 size_t namelen = 0;
1730 //printf("parseSections('%s')\n", comment);
1731 p = comment;
1732 while (*p)
1734 const(char)* pstart0 = p;
1735 p = skipwhitespace(p);
1736 pstart = p;
1737 pend = p;
1739 // Undo indent if starting with a list item
1740 if ((*p == '-' || *p == '+' || *p == '*') && (*(p+1) == ' ' || *(p+1) == '\t'))
1741 pstart = pstart0;
1742 else
1744 const(char)* pitem = p;
1745 while (*pitem >= '0' && *pitem <= '9')
1746 ++pitem;
1747 if (pitem > p && *pitem == '.' && (*(pitem+1) == ' ' || *(pitem+1) == '\t'))
1748 pstart = pstart0;
1751 /* Find end of section, which is ended by one of:
1752 * 'identifier:' (but not inside a code section)
1753 * '\0'
1755 idlen = 0;
1756 int inCode = 0;
1757 while (1)
1759 // Check for start/end of a code section
1760 if (*p == '-' || *p == '`' || *p == '~')
1762 char c = *p;
1763 int numdash = 0;
1764 while (*p == c)
1766 ++numdash;
1767 p++;
1769 // BUG: handle UTF PS and LS too
1770 if ((!*p || *p == '\r' || *p == '\n' || (!inCode && c != '-')) && numdash >= 3)
1772 inCode = inCode == c ? false : c;
1773 if (inCode)
1775 // restore leading indentation
1776 while (pstart0 < pstart && isIndentWS(pstart - 1))
1777 --pstart;
1780 pend = p;
1782 if (!inCode && isIdStart(p))
1784 const(char)* q = p + utfStride(p);
1785 while (isIdTail(q))
1786 q += utfStride(q);
1788 // Detected tag ends it
1789 if (*q == ':' && isupper(*p)
1790 && (isspace(q[1]) || q[1] == 0))
1792 idlen = q - p;
1793 idstart = p;
1794 for (pend = p; pend > pstart; pend--)
1796 if (pend[-1] == '\n')
1797 break;
1799 p = q + 1;
1800 break;
1803 while (1)
1805 if (!*p)
1806 goto L1;
1807 if (*p == '\n')
1809 p++;
1810 if (*p == '\n' && !summary && !namelen && !inCode)
1812 pend = p;
1813 p++;
1814 goto L1;
1816 break;
1818 p++;
1819 pend = p;
1821 p = skipwhitespace(p);
1824 if (namelen || pstart < pend)
1826 Section s;
1827 if (iequals("Params", name[0 .. namelen]))
1828 s = new ParamSection();
1829 else if (iequals("Macros", name[0 .. namelen]))
1830 s = new MacroSection();
1831 else
1832 s = new Section();
1833 s.name = name[0 .. namelen];
1834 s.body_ = pstart[0 .. pend - pstart];
1835 s.nooutput = 0;
1836 //printf("Section: '%.*s' = '%.*s'\n", cast(int)s.namelen, s.name, cast(int)s.bodylen, s.body);
1837 sections.push(s);
1838 if (!summary && !namelen)
1839 summary = s;
1841 if (idlen)
1843 name = idstart;
1844 namelen = idlen;
1846 else
1848 name = null;
1849 namelen = 0;
1850 if (!*p)
1851 break;
1856 void writeSections(Scope* sc, Dsymbols* a, ref OutBuffer buf)
1858 assert(a.length);
1859 //printf("DocComment::writeSections()\n");
1860 Loc loc = (*a)[0].loc;
1861 if (Module m = (*a)[0].isModule())
1863 if (m.md)
1864 loc = m.md.loc;
1866 size_t offset1 = buf.length;
1867 buf.writestring("$(DDOC_SECTIONS ");
1868 size_t offset2 = buf.length;
1869 for (size_t i = 0; i < sections.length; i++)
1871 Section sec = sections[i];
1872 if (sec.nooutput)
1873 continue;
1874 //printf("Section: '%.*s' = '%.*s'\n", cast(int)sec.namelen, sec.name, cast(int)sec.bodylen, sec.body);
1875 if (!sec.name.length && i == 0)
1877 buf.writestring("$(DDOC_SUMMARY ");
1878 size_t o = buf.length;
1879 buf.write(sec.body_);
1880 escapeStrayParenthesis(loc, buf, o, true, sc.eSink);
1881 highlightText(sc, a, loc, buf, o);
1882 buf.writestring(")");
1884 else
1885 sec.write(loc, &this, sc, a, buf);
1887 for (size_t i = 0; i < a.length; i++)
1889 Dsymbol s = (*a)[i];
1890 if (Dsymbol td = getEponymousParent(s))
1891 s = td;
1892 for (UnitTestDeclaration utd = s.ddocUnittest; utd; utd = utd.ddocUnittest)
1894 if (utd.visibility.kind == Visibility.Kind.private_ || !utd.comment || !utd.fbody)
1895 continue;
1896 // Strip whitespaces to avoid showing empty summary
1897 const(char)* c = utd.comment;
1898 while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')
1899 ++c;
1900 buf.writestring("$(DDOC_EXAMPLES ");
1901 size_t o = buf.length;
1902 buf.writestring(cast(char*)c);
1903 if (utd.codedoc)
1905 auto codedoc = utd.codedoc.stripLeadingNewlines;
1906 size_t n = getCodeIndent(codedoc);
1907 while (n--)
1908 buf.writeByte(' ');
1909 buf.writestring("----\n");
1910 buf.writestring(codedoc);
1911 buf.writestring("----\n");
1912 highlightText(sc, a, loc, buf, o);
1914 buf.writestring(")");
1917 if (buf.length == offset2)
1919 /* Didn't write out any sections, so back out last write
1921 buf.setsize(offset1);
1922 buf.writestring("\n");
1924 else
1925 buf.writestring(")");
1929 /*****************************************
1930 * Return true if comment consists entirely of "ditto".
1932 bool isDitto(const(char)* comment)
1934 if (comment)
1936 const(char)* p = skipwhitespace(comment);
1937 if (Port.memicmp(p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
1938 return true;
1940 return false;
1943 /**********************************************
1944 * Skip white space.
1946 const(char)* skipwhitespace(const(char)* p)
1948 return skipwhitespace(p.toDString).ptr;
1951 /// Ditto
1952 const(char)[] skipwhitespace(const(char)[] p) @safe
1954 foreach (idx, char c; p)
1956 switch (c)
1958 case ' ':
1959 case '\t':
1960 case '\n':
1961 continue;
1962 default:
1963 return p[idx .. $];
1966 return p[$ .. $];
1969 /************************************************
1970 * Scan past all instances of the given characters.
1971 * Params:
1972 * buf = an OutBuffer containing the DDoc
1973 * i = the index within `buf` to start scanning from
1974 * chars = the characters to skip; order is unimportant
1975 * Returns: the index after skipping characters.
1977 size_t skipChars(ref OutBuffer buf, size_t i, string chars) @safe
1979 Outer:
1980 foreach (j, c; buf[][i..$])
1982 foreach (d; chars)
1984 if (d == c)
1985 continue Outer;
1987 return i + j;
1989 return buf.length;
1992 unittest {
1993 OutBuffer buf;
1994 string data = "test ---\r\n\r\nend";
1995 buf.write(data);
1997 assert(skipChars(buf, 0, "-") == 0);
1998 assert(skipChars(buf, 4, "-") == 4);
1999 assert(skipChars(buf, 4, " -") == 8);
2000 assert(skipChars(buf, 8, "\r\n") == 12);
2001 assert(skipChars(buf, 12, "dne") == 15);
2004 /****************************************************
2005 * Replace all instances of `c` with `r` in the given string
2006 * Params:
2007 * s = the string to do replacements in
2008 * c = the character to look for
2009 * r = the string to replace `c` with
2010 * Returns: `s` with `c` replaced with `r`
2012 inout(char)[] replaceChar(inout(char)[] s, char c, string r) pure @safe
2014 int count = 0;
2015 foreach (char sc; s)
2016 if (sc == c)
2017 ++count;
2018 if (count == 0)
2019 return s;
2021 char[] result;
2022 result.reserve(s.length - count + (r.length * count));
2023 size_t start = 0;
2024 foreach (i, char sc; s)
2026 if (sc == c)
2028 result ~= s[start..i];
2029 result ~= r;
2030 start = i+1;
2033 result ~= s[start..$];
2034 return result;
2038 unittest
2040 assert("".replaceChar(',', "$(COMMA)") == "");
2041 assert("ab".replaceChar(',', "$(COMMA)") == "ab");
2042 assert("a,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)b");
2043 assert("a,,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)$(COMMA)b");
2044 assert(",ab".replaceChar(',', "$(COMMA)") == "$(COMMA)ab");
2045 assert("ab,".replaceChar(',', "$(COMMA)") == "ab$(COMMA)");
2049 * Return a lowercased copy of a string.
2050 * Params:
2051 * s = the string to lowercase
2052 * Returns: the lowercase version of the string or the original if already lowercase
2054 string toLowercase(string s) pure @safe
2056 string lower;
2057 foreach (size_t i; 0..s.length)
2059 char c = s[i];
2060 // TODO: maybe unicode lowercase, somehow
2061 if (c >= 'A' && c <= 'Z')
2063 if (!lower.length) {
2064 lower.reserve(s.length);
2066 lower ~= s[lower.length..i];
2067 c += 'a' - 'A';
2068 lower ~= c;
2071 if (lower.length)
2072 lower ~= s[lower.length..$];
2073 else
2074 lower = s;
2075 return lower;
2079 unittest
2081 assert("".toLowercase == "");
2082 assert("abc".toLowercase == "abc");
2083 assert("ABC".toLowercase == "abc");
2084 assert("aBc".toLowercase == "abc");
2087 /************************************************
2088 * Get the indent from one index to another, counting tab stops as four spaces wide
2089 * per the Markdown spec.
2090 * Params:
2091 * buf = an OutBuffer containing the DDoc
2092 * from = the index within `buf` to start counting from, inclusive
2093 * to = the index within `buf` to stop counting at, exclusive
2094 * Returns: the indent
2096 int getMarkdownIndent(ref OutBuffer buf, size_t from, size_t to) @safe
2098 const slice = buf[];
2099 if (to > slice.length)
2100 to = slice.length;
2101 int indent = 0;
2102 foreach (const c; slice[from..to])
2103 indent += (c == '\t') ? 4 - (indent % 4) : 1;
2104 return indent;
2107 /************************************************
2108 * Scan forward to one of:
2109 * start of identifier
2110 * beginning of next line
2111 * end of buf
2113 size_t skiptoident(ref OutBuffer buf, size_t i) @safe
2115 const slice = buf[];
2116 while (i < slice.length)
2118 dchar c;
2119 size_t oi = i;
2120 if (utf_decodeChar(slice, i, c))
2122 /* Ignore UTF errors, but still consume input
2124 break;
2126 if (c >= 0x80)
2128 if (!isUniAlpha(c))
2129 continue;
2131 else if (!(isalpha(c) || c == '_' || c == '\n'))
2132 continue;
2133 i = oi;
2134 break;
2136 return i;
2139 /************************************************
2140 * Scan forward past end of identifier.
2142 size_t skippastident(ref OutBuffer buf, size_t i) @safe
2144 const slice = buf[];
2145 while (i < slice.length)
2147 dchar c;
2148 size_t oi = i;
2149 if (utf_decodeChar(slice, i, c))
2151 /* Ignore UTF errors, but still consume input
2153 break;
2155 if (c >= 0x80)
2157 if (isUniAlpha(c))
2158 continue;
2160 else if (isalnum(c) || c == '_')
2161 continue;
2162 i = oi;
2163 break;
2165 return i;
2168 /************************************************
2169 * Scan forward past end of an identifier that might
2170 * contain dots (e.g. `abc.def`)
2172 size_t skipPastIdentWithDots(ref OutBuffer buf, size_t i) @safe
2174 const slice = buf[];
2175 bool lastCharWasDot;
2176 while (i < slice.length)
2178 dchar c;
2179 size_t oi = i;
2180 if (utf_decodeChar(slice, i, c))
2182 /* Ignore UTF errors, but still consume input
2184 break;
2186 if (c == '.')
2188 // We need to distinguish between `abc.def`, abc..def`, and `abc.`
2189 // Only `abc.def` is a valid identifier
2191 if (lastCharWasDot)
2193 i = oi;
2194 break;
2197 lastCharWasDot = true;
2198 continue;
2200 else
2202 if (c >= 0x80)
2204 if (isUniAlpha(c))
2206 lastCharWasDot = false;
2207 continue;
2210 else if (isalnum(c) || c == '_')
2212 lastCharWasDot = false;
2213 continue;
2215 i = oi;
2216 break;
2220 // if `abc.`
2221 if (lastCharWasDot)
2222 return i - 1;
2224 return i;
2227 /************************************************
2228 * Scan forward past URL starting at i.
2229 * We don't want to highlight parts of a URL.
2230 * Returns:
2231 * i if not a URL
2232 * index just past it if it is a URL
2234 size_t skippastURL(ref OutBuffer buf, size_t i)
2236 const slice = buf[][i .. $];
2237 size_t j;
2238 bool sawdot = false;
2239 if (slice.length > 7 && Port.memicmp(slice.ptr, "http://", 7) == 0)
2241 j = 7;
2243 else if (slice.length > 8 && Port.memicmp(slice.ptr, "https://", 8) == 0)
2245 j = 8;
2247 else
2248 goto Lno;
2249 for (; j < slice.length; j++)
2251 const c = slice[j];
2252 if (isalnum(c))
2253 continue;
2254 if (c == '-' || c == '_' || c == '?' || c == '=' || c == '%' ||
2255 c == '&' || c == '/' || c == '+' || c == '#' || c == '~')
2256 continue;
2257 if (c == '.')
2259 sawdot = true;
2260 continue;
2262 break;
2264 if (sawdot)
2265 return i + j;
2266 Lno:
2267 return i;
2270 /****************************************************
2271 * Remove a previously-inserted blank line macro.
2272 * Params:
2273 * buf = an OutBuffer containing the DDoc
2274 * iAt = the index within `buf` of the start of the `$(DDOC_BLANKLINE)`
2275 * macro. Upon function return its value is set to `0`.
2276 * i = an index within `buf`. If `i` is after `iAt` then it gets
2277 * reduced by the length of the removed macro.
2279 void removeBlankLineMacro(ref OutBuffer buf, ref size_t iAt, ref size_t i)
2281 if (!iAt)
2282 return;
2284 enum macroLength = "$(DDOC_BLANKLINE)".length;
2285 buf.remove(iAt, macroLength);
2286 if (i > iAt)
2287 i -= macroLength;
2288 iAt = 0;
2291 /****************************************************
2292 * Attempt to detect and replace a Markdown thematic break (HR). These are three
2293 * or more of the same delimiter, optionally with spaces or tabs between any of
2294 * them, e.g. `\n- - -\n` becomes `\n$(HR)\n`
2295 * Params:
2296 * buf = an OutBuffer containing the DDoc
2297 * i = the index within `buf` of the first character of a potential
2298 * thematic break. If the replacement is made `i` changes to
2299 * point to the closing parenthesis of the `$(HR)` macro.
2300 * iLineStart = the index within `buf` that the thematic break's line starts at
2301 * loc = the current location within the file
2302 * Returns: whether a thematic break was replaced
2304 bool replaceMarkdownThematicBreak(ref OutBuffer buf, ref size_t i, size_t iLineStart, const ref Loc loc)
2307 const slice = buf[];
2308 const c = buf[i];
2309 size_t j = i + 1;
2310 int repeat = 1;
2311 for (; j < slice.length; j++)
2313 if (buf[j] == c)
2314 ++repeat;
2315 else if (buf[j] != ' ' && buf[j] != '\t')
2316 break;
2318 if (repeat >= 3)
2320 if (j >= buf.length || buf[j] == '\n' || buf[j] == '\r')
2322 buf.remove(iLineStart, j - iLineStart);
2323 i = buf.insert(iLineStart, "$(HR)") - 1;
2324 return true;
2327 return false;
2330 /****************************************************
2331 * Detect the level of an ATX-style heading, e.g. `## This is a heading` would
2332 * have a level of `2`.
2333 * Params:
2334 * buf = an OutBuffer containing the DDoc
2335 * i = the index within `buf` of the first `#` character
2336 * Returns:
2337 * the detected heading level from 1 to 6, or
2338 * 0 if not at an ATX heading
2340 int detectAtxHeadingLevel(ref OutBuffer buf, const size_t i) @safe
2342 const iHeadingStart = i;
2343 const iAfterHashes = skipChars(buf, i, "#");
2344 const headingLevel = cast(int) (iAfterHashes - iHeadingStart);
2345 if (headingLevel > 6)
2346 return 0;
2348 const iTextStart = skipChars(buf, iAfterHashes, " \t");
2349 const emptyHeading = buf[iTextStart] == '\r' || buf[iTextStart] == '\n';
2351 // require whitespace
2352 if (!emptyHeading && iTextStart == iAfterHashes)
2353 return 0;
2355 return headingLevel;
2358 /****************************************************
2359 * Remove any trailing `##` suffix from an ATX-style heading.
2360 * Params:
2361 * buf = an OutBuffer containing the DDoc
2362 * i = the index within `buf` to start looking for a suffix at
2364 void removeAnyAtxHeadingSuffix(ref OutBuffer buf, size_t i)
2366 size_t j = i;
2367 size_t iSuffixStart = 0;
2368 size_t iWhitespaceStart = j;
2369 const slice = buf[];
2370 for (; j < slice.length; j++)
2372 switch (slice[j])
2374 case '#':
2375 if (iWhitespaceStart && !iSuffixStart)
2376 iSuffixStart = j;
2377 continue;
2378 case ' ':
2379 case '\t':
2380 if (!iWhitespaceStart)
2381 iWhitespaceStart = j;
2382 continue;
2383 case '\r':
2384 case '\n':
2385 break;
2386 default:
2387 iSuffixStart = 0;
2388 iWhitespaceStart = 0;
2389 continue;
2391 break;
2393 if (iSuffixStart)
2394 buf.remove(iWhitespaceStart, j - iWhitespaceStart);
2397 /****************************************************
2398 * Wrap text in a Markdown heading macro, e.g. `$(H2 heading text`).
2399 * Params:
2400 * buf = an OutBuffer containing the DDoc
2401 * iStart = the index within `buf` that the Markdown heading starts at
2402 * iEnd = the index within `buf` of the character after the last
2403 * heading character. Is incremented by the length of the
2404 * inserted heading macro when this function ends.
2405 * loc = the location of the Ddoc within the file
2406 * headingLevel = the level (1-6) of heading to end. Is set to `0` when this
2407 * function ends.
2409 void endMarkdownHeading(ref OutBuffer buf, size_t iStart, ref size_t iEnd, const ref Loc loc, ref int headingLevel)
2411 char[5] heading = "$(H0 ";
2412 heading[3] = cast(char) ('0' + headingLevel);
2413 buf.insert(iStart, heading);
2414 iEnd += 5;
2415 size_t iBeforeNewline = iEnd;
2416 while (buf[iBeforeNewline-1] == '\r' || buf[iBeforeNewline-1] == '\n')
2417 --iBeforeNewline;
2418 buf.insert(iBeforeNewline, ")");
2419 headingLevel = 0;
2422 /****************************************************
2423 * End all nested Markdown quotes, if inside any.
2424 * Params:
2425 * buf = an OutBuffer containing the DDoc
2426 * i = the index within `buf` of the character after the quote text.
2427 * quoteLevel = the current quote level. Is set to `0` when this function ends.
2428 * Returns: the amount that `i` was moved
2430 size_t endAllMarkdownQuotes(ref OutBuffer buf, size_t i, ref int quoteLevel)
2432 const length = quoteLevel;
2433 for (; quoteLevel > 0; --quoteLevel)
2434 i = buf.insert(i, ")");
2435 return length;
2438 /****************************************************
2439 * Convenience function to end all Markdown lists and quotes, if inside any, and
2440 * set `quoteMacroLevel` to `0`.
2441 * Params:
2442 * buf = an OutBuffer containing the DDoc
2443 * i = the index within `buf` of the character after the list and/or
2444 * quote text. Is adjusted when this function ends if any lists
2445 * and/or quotes were ended.
2446 * nestedLists = a set of nested lists. Upon return it will be empty.
2447 * quoteLevel = the current quote level. Is set to `0` when this function ends.
2448 * quoteMacroLevel = the macro level that the quote was started at. Is set to
2449 * `0` when this function ends.
2450 * Returns: the amount that `i` was moved
2452 size_t endAllListsAndQuotes(ref OutBuffer buf, ref size_t i, ref MarkdownList[] nestedLists, ref int quoteLevel, out int quoteMacroLevel)
2454 quoteMacroLevel = 0;
2455 const i0 = i;
2456 i += MarkdownList.endAllNestedLists(buf, i, nestedLists);
2457 i += endAllMarkdownQuotes(buf, i, quoteLevel);
2458 return i - i0;
2461 /****************************************************
2462 * Replace Markdown emphasis with the appropriate macro,
2463 * e.g. `*very* **nice**` becomes `$(EM very) $(STRONG nice)`.
2464 * Params:
2465 * buf = an OutBuffer containing the DDoc
2466 * loc = the current location within the file
2467 * inlineDelimiters = the collection of delimiters found within a paragraph. When this function returns its length will be reduced to `downToLevel`.
2468 * downToLevel = the length within `inlineDelimiters`` to reduce emphasis to
2469 * Returns: the number of characters added to the buffer by the replacements
2471 size_t replaceMarkdownEmphasis(ref OutBuffer buf, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, int downToLevel = 0)
2473 size_t replaceEmphasisPair(ref MarkdownDelimiter start, ref MarkdownDelimiter end)
2475 immutable count = start.count == 1 || end.count == 1 ? 1 : 2;
2477 size_t iStart = start.iStart;
2478 size_t iEnd = end.iStart;
2479 end.count -= count;
2480 start.count -= count;
2481 iStart += start.count;
2483 if (!start.count)
2484 start.type = 0;
2485 if (!end.count)
2486 end.type = 0;
2488 buf.remove(iStart, count);
2489 iEnd -= count;
2490 buf.remove(iEnd, count);
2492 string macroName = count >= 2 ? "$(STRONG " : "$(EM ";
2493 buf.insert(iEnd, ")");
2494 buf.insert(iStart, macroName);
2496 const delta = 1 + macroName.length - (count + count);
2497 end.iStart += count;
2498 return delta;
2501 size_t delta = 0;
2502 int start = (cast(int) inlineDelimiters.length) - 1;
2503 while (start >= downToLevel)
2505 // find start emphasis
2506 while (start >= downToLevel &&
2507 (inlineDelimiters[start].type != '*' || !inlineDelimiters[start].leftFlanking))
2508 --start;
2509 if (start < downToLevel)
2510 break;
2512 // find the nearest end emphasis
2513 int end = start + 1;
2514 while (end < inlineDelimiters.length &&
2515 (inlineDelimiters[end].type != inlineDelimiters[start].type ||
2516 inlineDelimiters[end].macroLevel != inlineDelimiters[start].macroLevel ||
2517 !inlineDelimiters[end].rightFlanking))
2518 ++end;
2519 if (end == inlineDelimiters.length)
2521 // the start emphasis has no matching end; if it isn't an end itself then kill it
2522 if (!inlineDelimiters[start].rightFlanking)
2523 inlineDelimiters[start].type = 0;
2524 --start;
2525 continue;
2528 // multiple-of-3 rule
2529 if (((inlineDelimiters[start].leftFlanking && inlineDelimiters[start].rightFlanking) ||
2530 (inlineDelimiters[end].leftFlanking && inlineDelimiters[end].rightFlanking)) &&
2531 (inlineDelimiters[start].count + inlineDelimiters[end].count) % 3 == 0)
2533 --start;
2534 continue;
2537 immutable delta0 = replaceEmphasisPair(inlineDelimiters[start], inlineDelimiters[end]);
2539 for (; end < inlineDelimiters.length; ++end)
2540 inlineDelimiters[end].iStart += delta0;
2541 delta += delta0;
2544 inlineDelimiters.length = downToLevel;
2545 return delta;
2548 /****************************************************
2550 bool isIdentifier(Dsymbols* a, const(char)[] s) @safe
2552 foreach (member; *a)
2554 if (auto imp = member.isImport())
2556 // For example: `public import str = core.stdc.string;`
2557 // This checks if `s` is equal to `str`
2558 if (imp.aliasId)
2560 if (s == imp.aliasId.toString())
2561 return true;
2563 else
2565 // The general case: `public import core.stdc.string;`
2567 // fully qualify imports so `core.stdc.string` doesn't appear as `core`
2568 string fullyQualifiedImport;
2569 foreach (const pid; imp.packages)
2571 fullyQualifiedImport ~= pid.toString() ~ ".";
2573 fullyQualifiedImport ~= imp.id.toString();
2575 // Check if `s` == `core.stdc.string`
2576 if (s == fullyQualifiedImport)
2577 return true;
2580 else if (member.ident)
2582 if (s == member.ident.toString())
2583 return true;
2587 return false;
2590 /****************************************************
2592 bool isKeyword(const(char)[] str) @safe
2594 immutable string[3] table = ["true", "false", "null"];
2595 foreach (s; table)
2597 if (str == s)
2598 return true;
2600 return false;
2603 /****************************************************
2605 TypeFunction isTypeFunction(Dsymbol s) @safe
2607 FuncDeclaration f = s.isFuncDeclaration();
2608 /* f.type may be NULL for template members.
2610 if (f && f.type)
2612 Type t = f.originalType ? f.originalType : f.type;
2613 if (t.ty == Tfunction)
2614 return cast(TypeFunction)t;
2616 return null;
2619 /****************************************************
2621 Parameter isFunctionParameter(Dsymbol s, const(char)[] str) @safe
2623 TypeFunction tf = isTypeFunction(s);
2624 if (tf && tf.parameterList.parameters)
2626 foreach (fparam; *tf.parameterList.parameters)
2628 if (fparam.ident && str == fparam.ident.toString())
2630 return fparam;
2634 return null;
2637 /****************************************************
2639 Parameter isFunctionParameter(Dsymbols* a, const(char)[] p) @safe
2641 foreach (Dsymbol sym; *a)
2643 Parameter fparam = isFunctionParameter(sym, p);
2644 if (fparam)
2646 return fparam;
2649 return null;
2652 /****************************************************
2654 Parameter isEponymousFunctionParameter(Dsymbols *a, const(char)[] p) @safe
2656 foreach (Dsymbol dsym; *a)
2658 TemplateDeclaration td = dsym.isTemplateDeclaration();
2659 if (td && td.onemember)
2661 /* Case 1: we refer to a template declaration inside the template
2663 /// ...ddoc...
2664 template case1(T) {
2665 void case1(R)() {}
2668 td = td.onemember.isTemplateDeclaration();
2670 if (!td)
2672 /* Case 2: we're an alias to a template declaration
2674 /// ...ddoc...
2675 alias case2 = case1!int;
2677 AliasDeclaration ad = dsym.isAliasDeclaration();
2678 if (ad && ad.aliassym)
2680 td = ad.aliassym.isTemplateDeclaration();
2683 while (td)
2685 Dsymbol sym = getEponymousMember(td);
2686 if (sym)
2688 Parameter fparam = isFunctionParameter(sym, p);
2689 if (fparam)
2691 return fparam;
2694 td = td.overnext;
2697 return null;
2700 /****************************************************
2702 TemplateParameter isTemplateParameter(Dsymbols* a, const(char)* p, size_t len)
2704 for (size_t i = 0; i < a.length; i++)
2706 TemplateDeclaration td = (*a)[i].isTemplateDeclaration();
2707 // Check for the parent, if the current symbol is not a template declaration.
2708 if (!td)
2709 td = getEponymousParent((*a)[i]);
2710 if (td && td.origParameters)
2712 foreach (tp; *td.origParameters)
2714 if (tp.ident && p[0 .. len] == tp.ident.toString())
2716 return tp;
2721 return null;
2724 /****************************************************
2725 * Return true if str is a reserved symbol name
2726 * that starts with a double underscore.
2728 bool isReservedName(const(char)[] str) @safe
2730 immutable string[] table =
2732 "__ctor",
2733 "__dtor",
2734 "__postblit",
2735 "__invariant",
2736 "__unitTest",
2737 "__require",
2738 "__ensure",
2739 "__dollar",
2740 "__ctfe",
2741 "__withSym",
2742 "__result",
2743 "__returnLabel",
2744 "__vptr",
2745 "__monitor",
2746 "__gate",
2747 "__xopEquals",
2748 "__xopCmp",
2749 "__LINE__",
2750 "__FILE__",
2751 "__MODULE__",
2752 "__FUNCTION__",
2753 "__PRETTY_FUNCTION__",
2754 "__DATE__",
2755 "__TIME__",
2756 "__TIMESTAMP__",
2757 "__VENDOR__",
2758 "__VERSION__",
2759 "__EOF__",
2760 "__CXXLIB__",
2761 "__LOCAL_SIZE",
2762 "__entrypoint",
2764 foreach (s; table)
2766 if (str == s)
2767 return true;
2769 return false;
2772 /****************************************************
2773 * A delimiter for Markdown inline content like emphasis and links.
2775 struct MarkdownDelimiter
2777 size_t iStart; /// the index where this delimiter starts
2778 int count; /// the length of this delimeter's start sequence
2779 int macroLevel; /// the count of nested DDoc macros when the delimiter is started
2780 bool leftFlanking; /// whether the delimiter is left-flanking, as defined by the CommonMark spec
2781 bool rightFlanking; /// whether the delimiter is right-flanking, as defined by the CommonMark spec
2782 bool atParagraphStart; /// whether the delimiter is at the start of a paragraph
2783 char type; /// the type of delimiter, defined by its starting character
2785 /// whether this describes a valid delimiter
2786 @property bool isValid() const @safe { return count != 0; }
2788 /// flag this delimiter as invalid
2789 void invalidate() @safe { count = 0; }
2792 /****************************************************
2793 * Info about a Markdown list.
2795 struct MarkdownList
2797 string orderedStart; /// an optional start number--if present then the list starts at this number
2798 size_t iStart; /// the index where the list item starts
2799 size_t iContentStart; /// the index where the content starts after the list delimiter
2800 int delimiterIndent; /// the level of indent the list delimiter starts at
2801 int contentIndent; /// the level of indent the content starts at
2802 int macroLevel; /// the count of nested DDoc macros when the list is started
2803 char type; /// the type of list, defined by its starting character
2805 /// whether this describes a valid list
2806 @property bool isValid() const @safe { return type != type.init; }
2808 /****************************************************
2809 * Try to parse a list item, returning whether successful.
2810 * Params:
2811 * buf = an OutBuffer containing the DDoc
2812 * iLineStart = the index within `buf` of the first character of the line
2813 * i = the index within `buf` of the potential list item
2814 * Returns: the parsed list item. Its `isValid` property describes whether parsing succeeded.
2816 static MarkdownList parseItem(ref OutBuffer buf, size_t iLineStart, size_t i) @safe
2818 if (buf[i] == '+' || buf[i] == '-' || buf[i] == '*')
2819 return parseUnorderedListItem(buf, iLineStart, i);
2820 else
2821 return parseOrderedListItem(buf, iLineStart, i);
2824 /****************************************************
2825 * Return whether the context is at a list item of the same type as this list.
2826 * Params:
2827 * buf = an OutBuffer containing the DDoc
2828 * iLineStart = the index within `buf` of the first character of the line
2829 * i = the index within `buf` of the list item
2830 * Returns: whether `i` is at a list item of the same type as this list
2832 private bool isAtItemInThisList(ref OutBuffer buf, size_t iLineStart, size_t i) @safe
2834 MarkdownList item = (type == '.' || type == ')') ?
2835 parseOrderedListItem(buf, iLineStart, i) :
2836 parseUnorderedListItem(buf, iLineStart, i);
2837 if (item.type == type)
2838 return item.delimiterIndent < contentIndent && item.contentIndent > delimiterIndent;
2839 return false;
2842 /****************************************************
2843 * Start a Markdown list item by creating/deleting nested lists and starting the item.
2844 * Params:
2845 * buf = an OutBuffer containing the DDoc
2846 * iLineStart = the index within `buf` of the first character of the line. If this function succeeds it will be adjuested to equal `i`.
2847 * i = the index within `buf` of the list item. If this function succeeds `i` will be adjusted to fit the inserted macro.
2848 * iPrecedingBlankLine = the index within `buf` of the preceeding blank line. If non-zero and a new list was started, the preceeding blank line is removed and this value is set to `0`.
2849 * nestedLists = a set of nested lists. If this function succeeds it may contain a new nested list.
2850 * loc = the location of the Ddoc within the file
2851 * Returns: `true` if a list was created
2853 bool startItem(ref OutBuffer buf, ref size_t iLineStart, ref size_t i, ref size_t iPrecedingBlankLine, ref MarkdownList[] nestedLists, const ref Loc loc)
2855 buf.remove(iStart, iContentStart - iStart);
2857 if (!nestedLists.length ||
2858 delimiterIndent >= nestedLists[$-1].contentIndent ||
2859 buf[iLineStart - 4..iLineStart] == "$(LI")
2861 // start a list macro
2862 nestedLists ~= this;
2863 if (type == '.')
2865 if (orderedStart.length)
2867 iStart = buf.insert(iStart, "$(OL_START ");
2868 iStart = buf.insert(iStart, orderedStart);
2869 iStart = buf.insert(iStart, ",\n");
2871 else
2872 iStart = buf.insert(iStart, "$(OL\n");
2874 else
2875 iStart = buf.insert(iStart, "$(UL\n");
2877 removeBlankLineMacro(buf, iPrecedingBlankLine, iStart);
2879 else if (nestedLists.length)
2881 nestedLists[$-1].delimiterIndent = delimiterIndent;
2882 nestedLists[$-1].contentIndent = contentIndent;
2885 iStart = buf.insert(iStart, "$(LI\n");
2886 i = iStart - 1;
2887 iLineStart = i;
2889 return true;
2892 /****************************************************
2893 * End all nested Markdown lists.
2894 * Params:
2895 * buf = an OutBuffer containing the DDoc
2896 * i = the index within `buf` to end lists at.
2897 * nestedLists = a set of nested lists. Upon return it will be empty.
2898 * Returns: the amount that `i` changed
2900 static size_t endAllNestedLists(ref OutBuffer buf, size_t i, ref MarkdownList[] nestedLists)
2902 const iStart = i;
2903 for (; nestedLists.length; --nestedLists.length)
2904 i = buf.insert(i, ")\n)");
2905 return i - iStart;
2908 /****************************************************
2909 * Look for a sibling list item or the end of nested list(s).
2910 * Params:
2911 * buf = an OutBuffer containing the DDoc
2912 * i = the index within `buf` to end lists at. If there was a sibling or ending lists `i` will be adjusted to fit the macro endings.
2913 * iParagraphStart = the index within `buf` to start the next paragraph at at. May be adjusted upon return.
2914 * nestedLists = a set of nested lists. Some nested lists may have been removed from it upon return.
2916 static void handleSiblingOrEndingList(ref OutBuffer buf, ref size_t i, ref size_t iParagraphStart, ref MarkdownList[] nestedLists)
2918 size_t iAfterSpaces = skipChars(buf, i + 1, " \t");
2920 if (nestedLists[$-1].isAtItemInThisList(buf, i + 1, iAfterSpaces))
2922 // end a sibling list item
2923 i = buf.insert(i, ")");
2924 iParagraphStart = skipChars(buf, i, " \t\r\n");
2926 else if (iAfterSpaces >= buf.length || (buf[iAfterSpaces] != '\r' && buf[iAfterSpaces] != '\n'))
2928 // end nested lists that are indented more than this content
2929 const indent = getMarkdownIndent(buf, i + 1, iAfterSpaces);
2930 while (nestedLists.length && nestedLists[$-1].contentIndent > indent)
2932 i = buf.insert(i, ")\n)");
2933 --nestedLists.length;
2934 iParagraphStart = skipChars(buf, i, " \t\r\n");
2936 if (nestedLists.length && nestedLists[$-1].isAtItemInThisList(buf, i + 1, iParagraphStart))
2938 i = buf.insert(i, ")");
2939 ++iParagraphStart;
2940 break;
2946 /****************************************************
2947 * Parse an unordered list item at the current position
2948 * Params:
2949 * buf = an OutBuffer containing the DDoc
2950 * iLineStart = the index within `buf` of the first character of the line
2951 * i = the index within `buf` of the list item
2952 * Returns: the parsed list item, or a list item with type `.init` if no list item is available
2954 private static MarkdownList parseUnorderedListItem(ref OutBuffer buf, size_t iLineStart, size_t i) @safe
2956 if (i+1 < buf.length &&
2957 (buf[i] == '-' ||
2958 buf[i] == '*' ||
2959 buf[i] == '+') &&
2960 (buf[i+1] == ' ' ||
2961 buf[i+1] == '\t' ||
2962 buf[i+1] == '\r' ||
2963 buf[i+1] == '\n'))
2965 const iContentStart = skipChars(buf, i + 1, " \t");
2966 const delimiterIndent = getMarkdownIndent(buf, iLineStart, i);
2967 const contentIndent = getMarkdownIndent(buf, iLineStart, iContentStart);
2968 auto list = MarkdownList(null, iLineStart, iContentStart, delimiterIndent, contentIndent, 0, buf[i]);
2969 return list;
2971 return MarkdownList();
2974 /****************************************************
2975 * Parse an ordered list item at the current position
2976 * Params:
2977 * buf = an OutBuffer containing the DDoc
2978 * iLineStart = the index within `buf` of the first character of the line
2979 * i = the index within `buf` of the list item
2980 * Returns: the parsed list item, or a list item with type `.init` if no list item is available
2982 private static MarkdownList parseOrderedListItem(ref OutBuffer buf, size_t iLineStart, size_t i) @safe
2984 size_t iAfterNumbers = skipChars(buf, i, "0123456789");
2985 if (iAfterNumbers - i > 0 &&
2986 iAfterNumbers - i <= 9 &&
2987 iAfterNumbers + 1 < buf.length &&
2988 buf[iAfterNumbers] == '.' &&
2989 (buf[iAfterNumbers+1] == ' ' ||
2990 buf[iAfterNumbers+1] == '\t' ||
2991 buf[iAfterNumbers+1] == '\r' ||
2992 buf[iAfterNumbers+1] == '\n'))
2994 const iContentStart = skipChars(buf, iAfterNumbers + 1, " \t");
2995 const delimiterIndent = getMarkdownIndent(buf, iLineStart, i);
2996 const contentIndent = getMarkdownIndent(buf, iLineStart, iContentStart);
2997 size_t iNumberStart = skipChars(buf, i, "0");
2998 if (iNumberStart == iAfterNumbers)
2999 --iNumberStart;
3000 auto orderedStart = buf[][iNumberStart .. iAfterNumbers];
3001 if (orderedStart == "1")
3002 orderedStart = null;
3003 return MarkdownList(orderedStart.idup, iLineStart, iContentStart, delimiterIndent, contentIndent, 0, buf[iAfterNumbers]);
3005 return MarkdownList();
3009 /****************************************************
3010 * A Markdown link.
3012 struct MarkdownLink
3014 string href; /// the link destination
3015 string title; /// an optional title for the link
3016 string label; /// an optional label for the link
3017 Dsymbol symbol; /// an optional symbol to link to
3019 /****************************************************
3020 * Replace a Markdown link or link definition in the form of:
3021 * - Inline link: `[foo](url/ 'optional title')`
3022 * - Reference link: `[foo][bar]`, `[foo][]` or `[foo]`
3023 * - Link reference definition: `[bar]: url/ 'optional title'`
3024 * Params:
3025 * buf = an OutBuffer containing the DDoc
3026 * i = the index within `buf` that points to the `]` character of the potential link.
3027 * If this function succeeds it will be adjusted to fit the inserted link macro.
3028 * loc = the current location within the file
3029 * inlineDelimiters = previously parsed Markdown delimiters, including emphasis and link/image starts
3030 * delimiterIndex = the index within `inlineDelimiters` of the nearest link/image starting delimiter
3031 * linkReferences = previously parsed link references. When this function returns it may contain
3032 * additional previously unparsed references.
3033 * Returns: whether a reference link was found and replaced at `i`
3035 static bool replaceLink(ref OutBuffer buf, ref size_t i, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, int delimiterIndex, ref MarkdownLinkReferences linkReferences)
3037 const delimiter = inlineDelimiters[delimiterIndex];
3038 MarkdownLink link;
3040 size_t iEnd = link.parseReferenceDefinition(buf, i, delimiter);
3041 if (iEnd > i)
3043 i = delimiter.iStart;
3044 link.storeAndReplaceDefinition(buf, i, iEnd, linkReferences, loc);
3045 inlineDelimiters.length = delimiterIndex;
3046 return true;
3049 iEnd = link.parseInlineLink(buf, i);
3050 if (iEnd == i)
3052 iEnd = link.parseReferenceLink(buf, i, delimiter);
3053 if (iEnd > i)
3055 const label = link.label;
3056 link = linkReferences.lookupReference(label, buf, i, loc);
3057 // check rightFlanking to avoid replacing things like int[string]
3058 if (!link.href.length && !delimiter.rightFlanking)
3059 link = linkReferences.lookupSymbol(label);
3060 if (!link.href.length)
3061 return false;
3065 if (iEnd == i)
3066 return false;
3068 immutable delta = replaceMarkdownEmphasis(buf, loc, inlineDelimiters, delimiterIndex);
3069 iEnd += delta;
3070 i += delta;
3071 link.replaceLink(buf, i, iEnd, delimiter);
3072 return true;
3075 /****************************************************
3076 * Replace a Markdown link definition in the form of `[bar]: url/ 'optional title'`
3077 * Params:
3078 * buf = an OutBuffer containing the DDoc
3079 * i = the index within `buf` that points to the `]` character of the potential link.
3080 * If this function succeeds it will be adjusted to fit the inserted link macro.
3081 * inlineDelimiters = previously parsed Markdown delimiters, including emphasis and link/image starts
3082 * delimiterIndex = the index within `inlineDelimiters` of the nearest link/image starting delimiter
3083 * linkReferences = previously parsed link references. When this function returns it may contain
3084 * additional previously unparsed references.
3085 * loc = the current location in the file
3086 * Returns: whether a reference link was found and replaced at `i`
3088 static bool replaceReferenceDefinition(ref OutBuffer buf, ref size_t i, ref MarkdownDelimiter[] inlineDelimiters, int delimiterIndex, ref MarkdownLinkReferences linkReferences, const ref Loc loc)
3090 const delimiter = inlineDelimiters[delimiterIndex];
3091 MarkdownLink link;
3092 size_t iEnd = link.parseReferenceDefinition(buf, i, delimiter);
3093 if (iEnd == i)
3094 return false;
3096 i = delimiter.iStart;
3097 link.storeAndReplaceDefinition(buf, i, iEnd, linkReferences, loc);
3098 inlineDelimiters.length = delimiterIndex;
3099 return true;
3102 /****************************************************
3103 * Parse a Markdown inline link in the form of `[foo](url/ 'optional title')`
3104 * Params:
3105 * buf = an OutBuffer containing the DDoc
3106 * i = the index within `buf` that points to the `]` character of the inline link.
3107 * Returns: the index at the end of parsing the link, or `i` if parsing failed.
3109 private size_t parseInlineLink(ref OutBuffer buf, size_t i)
3111 size_t iEnd = i + 1;
3112 if (iEnd >= buf.length || buf[iEnd] != '(')
3113 return i;
3114 ++iEnd;
3116 if (!parseHref(buf, iEnd))
3117 return i;
3119 iEnd = skipChars(buf, iEnd, " \t\r\n");
3120 if (buf[iEnd] != ')')
3122 if (parseTitle(buf, iEnd))
3123 iEnd = skipChars(buf, iEnd, " \t\r\n");
3126 if (buf[iEnd] != ')')
3127 return i;
3129 return iEnd + 1;
3132 /****************************************************
3133 * Parse a Markdown reference link in the form of `[foo][bar]`, `[foo][]` or `[foo]`
3134 * Params:
3135 * buf = an OutBuffer containing the DDoc
3136 * i = the index within `buf` that points to the `]` character of the inline link.
3137 * delimiter = the delimiter that starts this link
3138 * Returns: the index at the end of parsing the link, or `i` if parsing failed.
3140 private size_t parseReferenceLink(ref OutBuffer buf, size_t i, MarkdownDelimiter delimiter) @safe
3142 size_t iStart = i + 1;
3143 size_t iEnd = iStart;
3144 if (iEnd >= buf.length || buf[iEnd] != '[' || (iEnd+1 < buf.length && buf[iEnd+1] == ']'))
3146 // collapsed reference [foo][] or shortcut reference [foo]
3147 iStart = delimiter.iStart + delimiter.count - 1;
3148 if (buf[iEnd] == '[')
3149 iEnd += 2;
3152 parseLabel(buf, iStart);
3153 if (!label.length)
3154 return i;
3156 if (iEnd < iStart)
3157 iEnd = iStart;
3158 return iEnd;
3161 /****************************************************
3162 * Parse a Markdown reference definition in the form of `[bar]: url/ 'optional title'`
3163 * Params:
3164 * buf = an OutBuffer containing the DDoc
3165 * i = the index within `buf` that points to the `]` character of the inline link.
3166 * delimiter = the delimiter that starts this link
3167 * Returns: the index at the end of parsing the link, or `i` if parsing failed.
3169 private size_t parseReferenceDefinition(ref OutBuffer buf, size_t i, MarkdownDelimiter delimiter)
3171 if (!delimiter.atParagraphStart || delimiter.type != '[' ||
3172 i+1 >= buf.length || buf[i+1] != ':')
3173 return i;
3175 size_t iEnd = delimiter.iStart;
3176 parseLabel(buf, iEnd);
3177 if (label.length == 0 || iEnd != i + 1)
3178 return i;
3180 ++iEnd;
3181 iEnd = skipChars(buf, iEnd, " \t");
3182 skipOneNewline(buf, iEnd);
3184 if (!parseHref(buf, iEnd) || href.length == 0)
3185 return i;
3187 iEnd = skipChars(buf, iEnd, " \t");
3188 const requireNewline = !skipOneNewline(buf, iEnd);
3189 const iBeforeTitle = iEnd;
3191 if (parseTitle(buf, iEnd))
3193 iEnd = skipChars(buf, iEnd, " \t");
3194 if (iEnd < buf.length && buf[iEnd] != '\r' && buf[iEnd] != '\n')
3196 // the title must end with a newline
3197 title.length = 0;
3198 iEnd = iBeforeTitle;
3202 iEnd = skipChars(buf, iEnd, " \t");
3203 if (requireNewline && iEnd < buf.length-1 && buf[iEnd] != '\r' && buf[iEnd] != '\n')
3204 return i;
3206 return iEnd;
3209 /****************************************************
3210 * Parse and normalize a Markdown reference label
3211 * Params:
3212 * buf = an OutBuffer containing the DDoc
3213 * i = the index within `buf` that points to the `[` character at the start of the label.
3214 * If this function returns a non-empty label then `i` will point just after the ']' at the end of the label.
3215 * Returns: the parsed and normalized label, possibly empty
3217 private bool parseLabel(ref OutBuffer buf, ref size_t i) @safe
3219 if (buf[i] != '[')
3220 return false;
3222 const slice = buf[];
3223 size_t j = i + 1;
3225 // Some labels have already been en-symboled; handle that
3226 const inSymbol = j+15 < slice.length && slice[j..j+15] == "$(DDOC_PSYMBOL ";
3227 if (inSymbol)
3228 j += 15;
3230 for (; j < slice.length; ++j)
3232 const c = slice[j];
3233 switch (c)
3235 case ' ':
3236 case '\t':
3237 case '\r':
3238 case '\n':
3239 if (label.length && label[$-1] != ' ')
3240 label ~= ' ';
3241 break;
3242 case ')':
3243 if (inSymbol && j+1 < slice.length && slice[j+1] == ']')
3245 ++j;
3246 goto case ']';
3248 goto default;
3249 case '[':
3250 if (slice[j-1] != '\\')
3252 label.length = 0;
3253 return false;
3255 break;
3256 case ']':
3257 if (label.length && label[$-1] == ' ')
3258 --label.length;
3259 if (label.length)
3261 i = j + 1;
3262 return true;
3264 return false;
3265 default:
3266 label ~= c;
3267 break;
3270 label.length = 0;
3271 return false;
3274 /****************************************************
3275 * Parse and store a Markdown link URL, optionally enclosed in `<>` brackets
3276 * Params:
3277 * buf = an OutBuffer containing the DDoc
3278 * i = the index within `buf` that points to the first character of the URL.
3279 * If this function succeeds `i` will point just after the end of the URL.
3280 * Returns: whether a URL was found and parsed
3282 private bool parseHref(ref OutBuffer buf, ref size_t i)
3284 size_t j = skipChars(buf, i, " \t");
3286 size_t iHrefStart = j;
3287 size_t parenDepth = 1;
3288 bool inPointy = false;
3289 const slice = buf[];
3290 for (; j < slice.length; j++)
3292 switch (slice[j])
3294 case '<':
3295 if (!inPointy && j == iHrefStart)
3297 inPointy = true;
3298 ++iHrefStart;
3300 break;
3301 case '>':
3302 if (inPointy && slice[j-1] != '\\')
3303 goto LReturnHref;
3304 break;
3305 case '(':
3306 if (!inPointy && slice[j-1] != '\\')
3307 ++parenDepth;
3308 break;
3309 case ')':
3310 if (!inPointy && slice[j-1] != '\\')
3312 --parenDepth;
3313 if (!parenDepth)
3314 goto LReturnHref;
3316 break;
3317 case ' ':
3318 case '\t':
3319 case '\r':
3320 case '\n':
3321 if (inPointy)
3323 // invalid link
3324 return false;
3326 goto LReturnHref;
3327 default:
3328 break;
3331 if (inPointy)
3332 return false;
3333 LReturnHref:
3334 auto href = slice[iHrefStart .. j].dup;
3335 this.href = cast(string) percentEncode(removeEscapeBackslashes(href)).replaceChar(',', "$(COMMA)");
3336 i = j;
3337 if (inPointy)
3338 ++i;
3339 return true;
3342 /****************************************************
3343 * Parse and store a Markdown link title, enclosed in parentheses or `'` or `"` quotes
3344 * Params:
3345 * buf = an OutBuffer containing the DDoc
3346 * i = the index within `buf` that points to the first character of the title.
3347 * If this function succeeds `i` will point just after the end of the title.
3348 * Returns: whether a title was found and parsed
3350 private bool parseTitle(ref OutBuffer buf, ref size_t i)
3352 size_t j = skipChars(buf, i, " \t");
3353 if (j >= buf.length)
3354 return false;
3356 char type = buf[j];
3357 if (type != '"' && type != '\'' && type != '(')
3358 return false;
3359 if (type == '(')
3360 type = ')';
3362 const iTitleStart = j + 1;
3363 size_t iNewline = 0;
3364 const slice = buf[];
3365 for (j = iTitleStart; j < slice.length; j++)
3367 const c = slice[j];
3368 switch (c)
3370 case ')':
3371 case '"':
3372 case '\'':
3373 if (type == c && slice[j-1] != '\\')
3374 goto LEndTitle;
3375 iNewline = 0;
3376 break;
3377 case ' ':
3378 case '\t':
3379 case '\r':
3380 break;
3381 case '\n':
3382 if (iNewline)
3384 // no blank lines in titles
3385 return false;
3387 iNewline = j;
3388 break;
3389 default:
3390 iNewline = 0;
3391 break;
3394 return false;
3395 LEndTitle:
3396 auto title = slice[iTitleStart .. j].dup;
3397 this.title = cast(string) removeEscapeBackslashes(title).
3398 replaceChar(',', "$(COMMA)").
3399 replaceChar('"', "$(QUOTE)");
3400 i = j + 1;
3401 return true;
3404 /****************************************************
3405 * Replace a Markdown link or image with the appropriate macro
3406 * Params:
3407 * buf = an OutBuffer containing the DDoc
3408 * i = the index within `buf` that points to the `]` character of the inline link.
3409 * When this function returns it will be adjusted to the end of the inserted macro.
3410 * iLinkEnd = the index within `buf` that points just after the last character of the link
3411 * delimiter = the Markdown delimiter that started the link or image
3413 private void replaceLink(ref OutBuffer buf, ref size_t i, size_t iLinkEnd, MarkdownDelimiter delimiter)
3415 size_t iAfterLink = i - delimiter.count;
3416 string macroName;
3417 if (symbol)
3419 macroName = "$(SYMBOL_LINK ";
3421 else if (title.length)
3423 if (delimiter.type == '[')
3424 macroName = "$(LINK_TITLE ";
3425 else
3426 macroName = "$(IMAGE_TITLE ";
3428 else
3430 if (delimiter.type == '[')
3431 macroName = "$(LINK2 ";
3432 else
3433 macroName = "$(IMAGE ";
3435 buf.remove(delimiter.iStart, delimiter.count);
3436 buf.remove(i - delimiter.count, iLinkEnd - i);
3437 iLinkEnd = buf.insert(delimiter.iStart, macroName);
3438 iLinkEnd = buf.insert(iLinkEnd, href);
3439 iLinkEnd = buf.insert(iLinkEnd, ", ");
3440 iAfterLink += macroName.length + href.length + 2;
3441 if (title.length)
3443 iLinkEnd = buf.insert(iLinkEnd, title);
3444 iLinkEnd = buf.insert(iLinkEnd, ", ");
3445 iAfterLink += title.length + 2;
3447 // Link macros with titles require escaping commas
3448 for (size_t j = iLinkEnd; j < iAfterLink; ++j)
3449 if (buf[j] == ',')
3451 buf.remove(j, 1);
3452 j = buf.insert(j, "$(COMMA)") - 1;
3453 iAfterLink += 7;
3456 // TODO: if image, remove internal macros, leaving only text
3457 buf.insert(iAfterLink, ")");
3458 i = iAfterLink;
3461 /****************************************************
3462 * Store the Markdown link definition and remove it from `buf`
3463 * Params:
3464 * buf = an OutBuffer containing the DDoc
3465 * i = the index within `buf` that points to the `[` character at the start of the link definition.
3466 * When this function returns it will be adjusted to exclude the link definition.
3467 * iEnd = the index within `buf` that points just after the end of the definition
3468 * linkReferences = previously parsed link references. When this function returns it may contain
3469 * an additional reference.
3470 * loc = the current location in the file
3472 private void storeAndReplaceDefinition(ref OutBuffer buf, ref size_t i, size_t iEnd, ref MarkdownLinkReferences linkReferences, const ref Loc loc)
3474 // Remove the definition and trailing whitespace
3475 iEnd = skipChars(buf, iEnd, " \t\r\n");
3476 buf.remove(i, iEnd - i);
3477 i -= 2;
3479 string lowercaseLabel = label.toLowercase();
3480 if (lowercaseLabel !in linkReferences.references)
3481 linkReferences.references[lowercaseLabel] = this;
3484 /****************************************************
3485 * Remove Markdown escaping backslashes from the given string
3486 * Params:
3487 * s = the string to remove escaping backslashes from
3488 * Returns: `s` without escaping backslashes in it
3490 private static char[] removeEscapeBackslashes(char[] s) @safe
3492 if (!s.length)
3493 return s;
3495 // avoid doing anything if there isn't anything to escape
3496 size_t i;
3497 for (i = 0; i < s.length-1; ++i)
3498 if (s[i] == '\\' && ispunct(s[i+1]))
3499 break;
3500 if (i == s.length-1)
3501 return s;
3503 // copy characters backwards, then truncate
3504 size_t j = i + 1;
3505 s[i] = s[j];
3506 for (++i, ++j; j < s.length; ++i, ++j)
3508 if (j < s.length-1 && s[j] == '\\' && ispunct(s[j+1]))
3509 ++j;
3510 s[i] = s[j];
3512 s.length -= (j - i);
3513 return s;
3517 unittest
3519 assert(removeEscapeBackslashes("".dup) == "");
3520 assert(removeEscapeBackslashes(`\a`.dup) == `\a`);
3521 assert(removeEscapeBackslashes(`.\`.dup) == `.\`);
3522 assert(removeEscapeBackslashes(`\.\`.dup) == `.\`);
3523 assert(removeEscapeBackslashes(`\.`.dup) == `.`);
3524 assert(removeEscapeBackslashes(`\.\.`.dup) == `..`);
3525 assert(removeEscapeBackslashes(`a\.b\.c`.dup) == `a.b.c`);
3528 /****************************************************
3529 * Percent-encode (AKA URL-encode) the given string
3530 * Params:
3531 * s = the string to percent-encode
3532 * Returns: `s` with special characters percent-encoded
3534 private static inout(char)[] percentEncode(inout(char)[] s) pure @safe
3536 static bool shouldEncode(char c)
3538 return ((c < '0' && c != '!' && c != '#' && c != '$' && c != '%' && c != '&' && c != '\'' && c != '(' &&
3539 c != ')' && c != '*' && c != '+' && c != ',' && c != '-' && c != '.' && c != '/')
3540 || (c > '9' && c < 'A' && c != ':' && c != ';' && c != '=' && c != '?' && c != '@')
3541 || (c > 'Z' && c < 'a' && c != '[' && c != ']' && c != '_')
3542 || (c > 'z' && c != '~'));
3545 for (size_t i = 0; i < s.length; ++i)
3547 if (shouldEncode(s[i]))
3549 immutable static hexDigits = "0123456789ABCDEF";
3550 immutable encoded1 = hexDigits[s[i] >> 4];
3551 immutable encoded2 = hexDigits[s[i] & 0x0F];
3552 s = s[0..i] ~ '%' ~ encoded1 ~ encoded2 ~ s[i+1..$];
3553 i += 2;
3556 return s;
3560 unittest
3562 assert(percentEncode("") == "");
3563 assert(percentEncode("aB12-._~/?") == "aB12-._~/?");
3564 assert(percentEncode("<\n>") == "%3C%0A%3E");
3567 /**************************************************
3568 * Skip a single newline at `i`
3569 * Params:
3570 * buf = an OutBuffer containing the DDoc
3571 * i = the index within `buf` to start looking at.
3572 * If this function succeeds `i` will point after the newline.
3573 * Returns: whether a newline was skipped
3575 private static bool skipOneNewline(ref OutBuffer buf, ref size_t i) pure @safe
3577 if (i < buf.length && buf[i] == '\r')
3578 ++i;
3579 if (i < buf.length && buf[i] == '\n')
3581 ++i;
3582 return true;
3584 return false;
3588 /**************************************************
3589 * A set of Markdown link references.
3591 struct MarkdownLinkReferences
3593 MarkdownLink[string] references; // link references keyed by normalized label
3594 MarkdownLink[string] symbols; // link symbols keyed by name
3595 Scope* _scope; // the current scope
3596 bool extractedAll; // the index into the buffer of the last-parsed reference
3598 /**************************************************
3599 * Look up a reference by label, searching through the rest of the buffer if needed.
3600 * Symbols in the current scope are searched for if the DDoc doesn't define the reference.
3601 * Params:
3602 * label = the label to find the reference for
3603 * buf = an OutBuffer containing the DDoc
3604 * i = the index within `buf` to start searching for references at
3605 * loc = the current location in the file
3606 * Returns: a link. If the `href` member has a value then the reference is valid.
3608 MarkdownLink lookupReference(string label, ref OutBuffer buf, size_t i, const ref Loc loc)
3610 const lowercaseLabel = label.toLowercase();
3611 if (lowercaseLabel !in references)
3612 extractReferences(buf, i, loc);
3614 if (lowercaseLabel in references)
3615 return references[lowercaseLabel];
3617 return MarkdownLink();
3621 * Look up the link for the D symbol with the given name.
3622 * If found, the link is cached in the `symbols` member.
3623 * Params:
3624 * name = the name of the symbol
3625 * Returns: the link for the symbol or a link with a `null` href
3627 MarkdownLink lookupSymbol(string name)
3629 if (name in symbols)
3630 return symbols[name];
3632 const ids = split(name, '.');
3634 MarkdownLink link;
3635 auto id = Identifier.lookup(ids[0].ptr, ids[0].length);
3636 if (id)
3638 auto loc = Loc();
3639 auto symbol = _scope.search(loc, id, null, IgnoreErrors);
3640 for (size_t i = 1; symbol && i < ids.length; ++i)
3642 id = Identifier.lookup(ids[i].ptr, ids[i].length);
3643 symbol = id !is null ? symbol.search(loc, id, IgnoreErrors) : null;
3645 if (symbol)
3646 link = MarkdownLink(createHref(symbol), null, name, symbol);
3649 symbols[name] = link;
3650 return link;
3653 /**************************************************
3654 * Remove and store all link references from the document, in the form of
3655 * `[label]: href "optional title"`
3656 * Params:
3657 * buf = an OutBuffer containing the DDoc
3658 * i = the index within `buf` to start looking at
3659 * loc = the current location in the file
3660 * Returns: whether a reference was extracted
3662 private void extractReferences(ref OutBuffer buf, size_t i, const ref Loc loc)
3664 static bool isFollowedBySpace(ref OutBuffer buf, size_t i)
3666 return i+1 < buf.length && (buf[i+1] == ' ' || buf[i+1] == '\t');
3669 if (extractedAll)
3670 return;
3672 bool leadingBlank = false;
3673 int inCode = false;
3674 bool newParagraph = true;
3675 MarkdownDelimiter[] delimiters;
3676 for (; i < buf.length; ++i)
3678 const c = buf[i];
3679 switch (c)
3681 case ' ':
3682 case '\t':
3683 break;
3684 case '\n':
3685 if (leadingBlank && !inCode)
3686 newParagraph = true;
3687 leadingBlank = true;
3688 break;
3689 case '\\':
3690 ++i;
3691 break;
3692 case '#':
3693 if (leadingBlank && !inCode)
3694 newParagraph = true;
3695 leadingBlank = false;
3696 break;
3697 case '>':
3698 if (leadingBlank && !inCode)
3699 newParagraph = true;
3700 break;
3701 case '+':
3702 if (leadingBlank && !inCode && isFollowedBySpace(buf, i))
3703 newParagraph = true;
3704 else
3705 leadingBlank = false;
3706 break;
3707 case '0':
3709 case '9':
3710 if (leadingBlank && !inCode)
3712 i = skipChars(buf, i, "0123456789");
3713 if (i < buf.length &&
3714 (buf[i] == '.' || buf[i] == ')') &&
3715 isFollowedBySpace(buf, i))
3716 newParagraph = true;
3717 else
3718 leadingBlank = false;
3720 break;
3721 case '*':
3722 if (leadingBlank && !inCode)
3724 newParagraph = true;
3725 if (!isFollowedBySpace(buf, i))
3726 leadingBlank = false;
3728 break;
3729 case '`':
3730 case '~':
3731 if (leadingBlank && i+2 < buf.length && buf[i+1] == c && buf[i+2] == c)
3733 inCode = inCode == c ? false : c;
3734 i = skipChars(buf, i, [c]) - 1;
3735 newParagraph = true;
3737 leadingBlank = false;
3738 break;
3739 case '-':
3740 if (leadingBlank && !inCode && isFollowedBySpace(buf, i))
3741 goto case '+';
3742 else
3743 goto case '`';
3744 case '[':
3745 if (leadingBlank && !inCode && newParagraph)
3746 delimiters ~= MarkdownDelimiter(i, 1, 0, false, false, true, c);
3747 break;
3748 case ']':
3749 if (delimiters.length && !inCode &&
3750 MarkdownLink.replaceReferenceDefinition(buf, i, delimiters, cast(int) delimiters.length - 1, this, loc))
3751 --i;
3752 break;
3753 default:
3754 if (leadingBlank)
3755 newParagraph = false;
3756 leadingBlank = false;
3757 break;
3760 extractedAll = true;
3764 * Split a string by a delimiter, excluding the delimiter.
3765 * Params:
3766 * s = the string to split
3767 * delimiter = the character to split by
3768 * Returns: the resulting array of strings
3770 private static string[] split(string s, char delimiter) pure @safe
3772 string[] result;
3773 size_t iStart = 0;
3774 foreach (size_t i; 0..s.length)
3775 if (s[i] == delimiter)
3777 result ~= s[iStart..i];
3778 iStart = i + 1;
3780 result ~= s[iStart..$];
3781 return result;
3785 unittest
3787 assert(split("", ',') == [""]);
3788 assert(split("ab", ',') == ["ab"]);
3789 assert(split("a,b", ',') == ["a", "b"]);
3790 assert(split("a,,b", ',') == ["a", "", "b"]);
3791 assert(split(",ab", ',') == ["", "ab"]);
3792 assert(split("ab,", ',') == ["ab", ""]);
3796 * Create a HREF for the given D symbol.
3797 * The HREF is relative to the current location if possible.
3798 * Params:
3799 * symbol = the symbol to create a HREF for.
3800 * Returns: the resulting href
3802 private string createHref(Dsymbol symbol)
3804 Dsymbol root = symbol;
3806 const(char)[] lref;
3807 while (symbol && symbol.ident && !symbol.isModule())
3809 if (lref.length)
3810 lref = '.' ~ lref;
3811 lref = symbol.ident.toString() ~ lref;
3812 symbol = symbol.parent;
3815 const(char)[] path;
3816 if (symbol && symbol.ident && symbol.isModule() != _scope._module)
3820 root = symbol;
3822 // If the module has a file name, we're done
3823 if (const m = symbol.isModule())
3824 if (m.docfile)
3826 path = m.docfile.toString();
3827 break;
3830 if (path.length)
3831 path = '_' ~ path;
3832 path = symbol.ident.toString() ~ path;
3833 symbol = symbol.parent;
3834 } while (symbol && symbol.ident);
3836 if (!symbol && path.length)
3837 path ~= "$(DOC_EXTENSION)";
3840 // Attempt an absolute URL if not in the same package
3841 while (root.parent)
3842 root = root.parent;
3843 Dsymbol scopeRoot = _scope._module;
3844 while (scopeRoot.parent)
3845 scopeRoot = scopeRoot.parent;
3846 if (scopeRoot != root)
3848 path = "$(DOC_ROOT_" ~ root.ident.toString() ~ ')' ~ path;
3849 lref = '.' ~ lref; // remote URIs like Phobos and Mir use .prefixes
3852 return cast(string) (path ~ '#' ~ lref);
3856 enum TableColumnAlignment
3858 none,
3859 left,
3860 center,
3861 right
3864 /****************************************************
3865 * Parse a Markdown table delimiter row in the form of `| -- | :-- | :--: | --: |`
3866 * where the example text has four columns with the following alignments:
3867 * default, left, center, and right. The first and last pipes are optional. If a
3868 * delimiter row is found it will be removed from `buf`.
3870 * Params:
3871 * buf = an OutBuffer containing the DDoc
3872 * iStart = the index within `buf` that the delimiter row starts at
3873 * inQuote = whether the table is inside a quote
3874 * columnAlignments = alignments to populate for each column
3875 * Returns: the index of the end of the parsed delimiter, or `0` if not found
3877 size_t parseTableDelimiterRow(ref OutBuffer buf, const size_t iStart, bool inQuote, ref TableColumnAlignment[] columnAlignments) @safe
3879 size_t i = skipChars(buf, iStart, inQuote ? ">| \t" : "| \t");
3880 while (i < buf.length && buf[i] != '\r' && buf[i] != '\n')
3882 const leftColon = buf[i] == ':';
3883 if (leftColon)
3884 ++i;
3886 if (i >= buf.length || buf[i] != '-')
3887 break;
3888 i = skipChars(buf, i, "-");
3890 const rightColon = i < buf.length && buf[i] == ':';
3891 i = skipChars(buf, i, ": \t");
3893 if (i >= buf.length || (buf[i] != '|' && buf[i] != '\r' && buf[i] != '\n'))
3894 break;
3895 i = skipChars(buf, i, "| \t");
3897 columnAlignments ~= (leftColon && rightColon) ? TableColumnAlignment.center :
3898 leftColon ? TableColumnAlignment.left :
3899 rightColon ? TableColumnAlignment.right :
3900 TableColumnAlignment.none;
3903 if (i < buf.length && buf[i] != '\r' && buf[i] != '\n' && buf[i] != ')')
3905 columnAlignments.length = 0;
3906 return 0;
3909 if (i < buf.length && buf[i] == '\r') ++i;
3910 if (i < buf.length && buf[i] == '\n') ++i;
3911 return i;
3914 /****************************************************
3915 * Look for a table delimiter row, and if found parse the previous row as a
3916 * table header row. If both exist with a matching number of columns, start a
3917 * table.
3919 * Params:
3920 * buf = an OutBuffer containing the DDoc
3921 * iStart = the index within `buf` that the table header row starts at, inclusive
3922 * iEnd = the index within `buf` that the table header row ends at, exclusive
3923 * loc = the current location in the file
3924 * inQuote = whether the table is inside a quote
3925 * inlineDelimiters = delimiters containing columns separators and any inline emphasis
3926 * columnAlignments = the parsed alignments for each column
3927 * Returns: the number of characters added by starting the table, or `0` if unchanged
3929 size_t startTable(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, bool inQuote, ref MarkdownDelimiter[] inlineDelimiters, out TableColumnAlignment[] columnAlignments)
3931 const iDelimiterRowEnd = parseTableDelimiterRow(buf, iEnd + 1, inQuote, columnAlignments);
3932 if (iDelimiterRowEnd)
3934 size_t delta;
3935 if (replaceTableRow(buf, iStart, iEnd, loc, inlineDelimiters, columnAlignments, true, delta))
3937 buf.remove(iEnd + delta, iDelimiterRowEnd - iEnd);
3938 buf.insert(iEnd + delta, "$(TBODY ");
3939 buf.insert(iStart, "$(TABLE ");
3940 return delta + 15;
3944 columnAlignments.length = 0;
3945 return 0;
3948 /****************************************************
3949 * Replace a Markdown table row in the form of table cells delimited by pipes:
3950 * `| cell | cell | cell`. The first and last pipes are optional.
3952 * Params:
3953 * buf = an OutBuffer containing the DDoc
3954 * iStart = the index within `buf` that the table row starts at, inclusive
3955 * iEnd = the index within `buf` that the table row ends at, exclusive
3956 * loc = the current location in the file
3957 * inlineDelimiters = delimiters containing columns separators and any inline emphasis
3958 * columnAlignments = alignments for each column
3959 * headerRow = if `true` then the number of columns will be enforced to match
3960 * `columnAlignments.length` and the row will be surrounded by a
3961 * `THEAD` macro
3962 * delta = the number of characters added by replacing the row, or `0` if unchanged
3963 * Returns: `true` if a table row was found and replaced
3965 bool replaceTableRow(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, TableColumnAlignment[] columnAlignments, bool headerRow, out size_t delta)
3967 delta = 0;
3969 if (!columnAlignments.length || iStart == iEnd)
3970 return false;
3972 iStart = skipChars(buf, iStart, " \t");
3973 int cellCount = 0;
3974 foreach (delimiter; inlineDelimiters)
3975 if (delimiter.type == '|' && !delimiter.leftFlanking)
3976 ++cellCount;
3977 bool ignoreLast = inlineDelimiters.length > 0 && inlineDelimiters[$-1].type == '|';
3978 if (ignoreLast)
3980 const iLast = skipChars(buf, inlineDelimiters[$-1].iStart + inlineDelimiters[$-1].count, " \t");
3981 ignoreLast = iLast >= iEnd;
3983 if (!ignoreLast)
3984 ++cellCount;
3986 if (headerRow && cellCount != columnAlignments.length)
3987 return false;
3989 void replaceTableCell(size_t iCellStart, size_t iCellEnd, int cellIndex, int di)
3991 const eDelta = replaceMarkdownEmphasis(buf, loc, inlineDelimiters, di);
3992 delta += eDelta;
3993 iCellEnd += eDelta;
3995 // strip trailing whitespace and delimiter
3996 size_t i = iCellEnd - 1;
3997 while (i > iCellStart && (buf[i] == '|' || buf[i] == ' ' || buf[i] == '\t'))
3998 --i;
3999 ++i;
4000 buf.remove(i, iCellEnd - i);
4001 delta -= iCellEnd - i;
4002 iCellEnd = i;
4004 buf.insert(iCellEnd, ")");
4005 ++delta;
4007 // strip initial whitespace and delimiter
4008 i = skipChars(buf, iCellStart, "| \t");
4009 buf.remove(iCellStart, i - iCellStart);
4010 delta -= i - iCellStart;
4012 switch (columnAlignments[cellIndex])
4014 case TableColumnAlignment.none:
4015 buf.insert(iCellStart, headerRow ? "$(TH " : "$(TD ");
4016 delta += 5;
4017 break;
4018 case TableColumnAlignment.left:
4019 buf.insert(iCellStart, "left, ");
4020 delta += 6;
4021 goto default;
4022 case TableColumnAlignment.center:
4023 buf.insert(iCellStart, "center, ");
4024 delta += 8;
4025 goto default;
4026 case TableColumnAlignment.right:
4027 buf.insert(iCellStart, "right, ");
4028 delta += 7;
4029 goto default;
4030 default:
4031 buf.insert(iCellStart, headerRow ? "$(TH_ALIGN " : "$(TD_ALIGN ");
4032 delta += 11;
4033 break;
4037 int cellIndex = cellCount - 1;
4038 size_t iCellEnd = iEnd;
4039 foreach_reverse (di, delimiter; inlineDelimiters)
4041 if (delimiter.type == '|')
4043 if (ignoreLast && di == inlineDelimiters.length-1)
4045 ignoreLast = false;
4046 continue;
4049 if (cellIndex >= columnAlignments.length)
4051 // kill any extra cells
4052 buf.remove(delimiter.iStart, iEnd + delta - delimiter.iStart);
4053 delta -= iEnd + delta - delimiter.iStart;
4054 iCellEnd = iEnd + delta;
4055 --cellIndex;
4056 continue;
4059 replaceTableCell(delimiter.iStart, iCellEnd, cellIndex, cast(int) di);
4060 iCellEnd = delimiter.iStart;
4061 --cellIndex;
4065 // if no starting pipe, replace from the start
4066 if (cellIndex >= 0)
4067 replaceTableCell(iStart, iCellEnd, cellIndex, 0);
4069 buf.insert(iEnd + delta, ")");
4070 buf.insert(iStart, "$(TR ");
4071 delta += 6;
4073 if (headerRow)
4075 buf.insert(iEnd + delta, ")");
4076 buf.insert(iStart, "$(THEAD ");
4077 delta += 9;
4080 return true;
4083 /****************************************************
4084 * End a table, if in one.
4086 * Params:
4087 * buf = an OutBuffer containing the DDoc
4088 * i = the index within `buf` to end the table at
4089 * columnAlignments = alignments for each column; upon return is set to length `0`
4090 * Returns: the number of characters added by ending the table, or `0` if unchanged
4092 size_t endTable(ref OutBuffer buf, size_t i, ref TableColumnAlignment[] columnAlignments)
4094 if (!columnAlignments.length)
4095 return 0;
4097 buf.insert(i, "))");
4098 columnAlignments.length = 0;
4099 return 2;
4102 /****************************************************
4103 * End a table row and then the table itself.
4105 * Params:
4106 * buf = an OutBuffer containing the DDoc
4107 * iStart = the index within `buf` that the table row starts at, inclusive
4108 * iEnd = the index within `buf` that the table row ends at, exclusive
4109 * loc = the current location in the file
4110 * inlineDelimiters = delimiters containing columns separators and any inline emphasis
4111 * columnAlignments = alignments for each column; upon return is set to length `0`
4112 * Returns: the number of characters added by replacing the row, or `0` if unchanged
4114 size_t endRowAndTable(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, ref TableColumnAlignment[] columnAlignments)
4116 size_t delta;
4117 replaceTableRow(buf, iStart, iEnd, loc, inlineDelimiters, columnAlignments, false, delta);
4118 delta += endTable(buf, iEnd + delta, columnAlignments);
4119 return delta;
4122 /**************************************************
4123 * Highlight text section.
4125 * Params:
4126 * scope = the current parse scope
4127 * a = an array of D symbols at the current scope
4128 * loc = source location of start of text. It is a mutable copy to allow incrementing its linenum, for printing the correct line number when an error is encountered in a multiline block of ddoc.
4129 * buf = an OutBuffer containing the DDoc
4130 * offset = the index within buf to start highlighting
4132 void highlightText(Scope* sc, Dsymbols* a, Loc loc, ref OutBuffer buf, size_t offset)
4134 const incrementLoc = loc.linnum == 0 ? 1 : 0;
4135 loc.linnum = loc.linnum + incrementLoc;
4136 loc.charnum = 0;
4137 //printf("highlightText()\n");
4138 bool leadingBlank = true;
4139 size_t iParagraphStart = offset;
4140 size_t iPrecedingBlankLine = 0;
4141 int headingLevel = 0;
4142 int headingMacroLevel = 0;
4143 int quoteLevel = 0;
4144 bool lineQuoted = false;
4145 int quoteMacroLevel = 0;
4146 MarkdownList[] nestedLists;
4147 MarkdownDelimiter[] inlineDelimiters;
4148 MarkdownLinkReferences linkReferences;
4149 TableColumnAlignment[] columnAlignments;
4150 bool tableRowDetected = false;
4151 int inCode = 0;
4152 int inBacktick = 0;
4153 int macroLevel = 0;
4154 int previousMacroLevel = 0;
4155 int parenLevel = 0;
4156 size_t iCodeStart = 0; // start of code section
4157 size_t codeFenceLength = 0;
4158 size_t codeIndent = 0;
4159 string codeLanguage;
4160 size_t iLineStart = offset;
4161 linkReferences._scope = sc;
4162 for (size_t i = offset; i < buf.length; i++)
4164 char c = buf[i];
4165 Lcont:
4166 switch (c)
4168 case ' ':
4169 case '\t':
4170 break;
4171 case '\n':
4172 if (inBacktick)
4174 // `inline code` is only valid if contained on a single line
4175 // otherwise, the backticks should be output literally.
4177 // This lets things like `output from the linker' display
4178 // unmolested while keeping the feature consistent with GitHub.
4179 inBacktick = false;
4180 inCode = false; // the backtick also assumes we're in code
4181 // Nothing else is necessary since the DDOC_BACKQUOTED macro is
4182 // inserted lazily at the close quote, meaning the rest of the
4183 // text is already OK.
4185 if (headingLevel)
4187 i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters);
4188 endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel);
4189 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
4190 ++i;
4191 iParagraphStart = skipChars(buf, i, " \t\r\n");
4194 if (tableRowDetected && !columnAlignments.length)
4195 i += startTable(buf, iLineStart, i, loc, lineQuoted, inlineDelimiters, columnAlignments);
4196 else if (columnAlignments.length)
4198 size_t delta;
4199 if (replaceTableRow(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments, false, delta))
4200 i += delta;
4201 else
4202 i += endTable(buf, i, columnAlignments);
4205 if (!inCode && nestedLists.length && !quoteLevel)
4206 MarkdownList.handleSiblingOrEndingList(buf, i, iParagraphStart, nestedLists);
4208 iPrecedingBlankLine = 0;
4209 if (!inCode && i == iLineStart && i + 1 < buf.length) // if "\n\n"
4211 i += endTable(buf, i, columnAlignments);
4212 if (!lineQuoted && quoteLevel)
4213 endAllListsAndQuotes(buf, i, nestedLists, quoteLevel, quoteMacroLevel);
4214 i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters);
4216 // if we don't already know about this paragraph break then
4217 // insert a blank line and record the paragraph break
4218 if (iParagraphStart <= i)
4220 iPrecedingBlankLine = i;
4221 i = buf.insert(i, "$(DDOC_BLANKLINE)");
4222 iParagraphStart = i + 1;
4225 else if (inCode &&
4226 i == iLineStart &&
4227 i + 1 < buf.length &&
4228 !lineQuoted &&
4229 quoteLevel) // if "\n\n" in quoted code
4231 inCode = false;
4232 i = buf.insert(i, ")");
4233 i += endAllMarkdownQuotes(buf, i, quoteLevel);
4234 quoteMacroLevel = 0;
4236 leadingBlank = true;
4237 lineQuoted = false;
4238 tableRowDetected = false;
4239 iLineStart = i + 1;
4240 loc.linnum = loc.linnum + incrementLoc;
4242 // update the paragraph start if we just entered a macro
4243 if (previousMacroLevel < macroLevel && iParagraphStart < iLineStart)
4244 iParagraphStart = iLineStart;
4245 previousMacroLevel = macroLevel;
4246 break;
4248 case '<':
4250 leadingBlank = false;
4251 if (inCode)
4252 break;
4253 const slice = buf[];
4254 auto p = &slice[i];
4255 const se = sc._module.escapetable.escapeChar('<');
4256 if (se == "&lt;")
4258 // Generating HTML
4259 // Skip over comments
4260 if (p[1] == '!' && p[2] == '-' && p[3] == '-')
4262 size_t j = i + 4;
4263 p += 4;
4264 while (1)
4266 if (j == slice.length)
4267 goto L1;
4268 if (p[0] == '-' && p[1] == '-' && p[2] == '>')
4270 i = j + 2; // place on closing '>'
4271 break;
4273 j++;
4274 p++;
4276 break;
4278 // Skip over HTML tag
4279 if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2])))
4281 size_t j = i + 2;
4282 p += 2;
4283 while (1)
4285 if (j == slice.length)
4286 break;
4287 if (p[0] == '>')
4289 i = j; // place on closing '>'
4290 break;
4292 j++;
4293 p++;
4295 break;
4299 // Replace '<' with '&lt;' character entity
4300 if (se.length)
4302 buf.remove(i, 1);
4303 i = buf.insert(i, se);
4304 i--; // point to ';'
4306 break;
4309 case '>':
4311 if (leadingBlank && (!inCode || quoteLevel))
4313 lineQuoted = true;
4314 int lineQuoteLevel = 1;
4315 size_t iAfterDelimiters = i + 1;
4316 for (; iAfterDelimiters < buf.length; ++iAfterDelimiters)
4318 const c0 = buf[iAfterDelimiters];
4319 if (c0 == '>')
4320 ++lineQuoteLevel;
4321 else if (c0 != ' ' && c0 != '\t')
4322 break;
4324 if (!quoteMacroLevel)
4325 quoteMacroLevel = macroLevel;
4326 buf.remove(i, iAfterDelimiters - i);
4328 if (quoteLevel < lineQuoteLevel)
4330 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4331 if (nestedLists.length)
4333 const indent = getMarkdownIndent(buf, iLineStart, i);
4334 if (indent < nestedLists[$-1].contentIndent)
4335 i += MarkdownList.endAllNestedLists(buf, i, nestedLists);
4338 for (; quoteLevel < lineQuoteLevel; ++quoteLevel)
4340 i = buf.insert(i, "$(BLOCKQUOTE\n");
4341 iLineStart = iParagraphStart = i;
4343 --i;
4345 else
4347 --i;
4348 if (nestedLists.length)
4349 MarkdownList.handleSiblingOrEndingList(buf, i, iParagraphStart, nestedLists);
4351 break;
4354 leadingBlank = false;
4355 if (inCode)
4356 break;
4357 // Replace '>' with '&gt;' character entity
4358 const se = sc._module.escapetable.escapeChar('>');
4359 if (se.length)
4361 buf.remove(i, 1);
4362 i = buf.insert(i, se);
4363 i--; // point to ';'
4365 break;
4368 case '&':
4370 leadingBlank = false;
4371 if (inCode)
4372 break;
4373 char* p = cast(char*)&buf[].ptr[i];
4374 if (p[1] == '#' || isalpha(p[1]))
4375 break;
4376 // already a character entity
4377 // Replace '&' with '&amp;' character entity
4378 const se = sc._module.escapetable.escapeChar('&');
4379 if (se)
4381 buf.remove(i, 1);
4382 i = buf.insert(i, se);
4383 i--; // point to ';'
4385 break;
4388 case '`':
4390 const iAfterDelimiter = skipChars(buf, i, "`");
4391 const count = iAfterDelimiter - i;
4393 if (inBacktick == count)
4395 inBacktick = 0;
4396 inCode = 0;
4397 OutBuffer codebuf;
4398 codebuf.write(buf[iCodeStart + count .. i]);
4399 // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL
4400 highlightCode(sc, a, codebuf, 0);
4401 escapeStrayParenthesis(loc, codebuf, 0, false, sc.eSink);
4402 buf.remove(iCodeStart, i - iCodeStart + count); // also trimming off the current `
4403 immutable pre = "$(DDOC_BACKQUOTED ";
4404 i = buf.insert(iCodeStart, pre);
4405 i = buf.insert(i, codebuf[]);
4406 i = buf.insert(i, ")");
4407 i--; // point to the ending ) so when the for loop does i++, it will see the next character
4408 break;
4411 // Perhaps we're starting or ending a Markdown code block
4412 if (leadingBlank && count >= 3)
4414 bool moreBackticks = false;
4415 for (size_t j = iAfterDelimiter; !moreBackticks && j < buf.length; ++j)
4416 if (buf[j] == '`')
4417 moreBackticks = true;
4418 else if (buf[j] == '\r' || buf[j] == '\n')
4419 break;
4420 if (!moreBackticks)
4421 goto case '-';
4424 if (inCode)
4426 if (inBacktick)
4427 i = iAfterDelimiter - 1;
4428 break;
4430 inCode = c;
4431 inBacktick = cast(int) count;
4432 codeIndent = 0; // inline code is not indented
4433 // All we do here is set the code flags and record
4434 // the location. The macro will be inserted lazily
4435 // so we can easily cancel the inBacktick if we come
4436 // across a newline character.
4437 iCodeStart = i;
4438 i = iAfterDelimiter - 1;
4439 break;
4442 case '#':
4444 /* A line beginning with # indicates an ATX-style heading. */
4445 if (leadingBlank && !inCode)
4447 leadingBlank = false;
4449 headingLevel = detectAtxHeadingLevel(buf, i);
4450 if (!headingLevel)
4451 break;
4453 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4454 if (!lineQuoted && quoteLevel)
4455 i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
4457 // remove the ### prefix, including whitespace
4458 i = skipChars(buf, i + headingLevel, " \t");
4459 buf.remove(iLineStart, i - iLineStart);
4460 i = iParagraphStart = iLineStart;
4462 removeAnyAtxHeadingSuffix(buf, i);
4463 --i;
4465 headingMacroLevel = macroLevel;
4467 break;
4470 case '~':
4472 if (leadingBlank)
4474 // Perhaps we're starting or ending a Markdown code block
4475 const iAfterDelimiter = skipChars(buf, i, "~");
4476 if (iAfterDelimiter - i >= 3)
4477 goto case '-';
4479 leadingBlank = false;
4480 break;
4483 case '-':
4484 /* A line beginning with --- delimits a code section.
4485 * inCode tells us if it is start or end of a code section.
4487 if (leadingBlank)
4489 if (!inCode && c == '-')
4491 const list = MarkdownList.parseItem(buf, iLineStart, i);
4492 if (list.isValid)
4494 if (replaceMarkdownThematicBreak(buf, i, iLineStart, loc))
4496 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
4497 iParagraphStart = skipChars(buf, i+1, " \t\r\n");
4498 break;
4500 else
4501 goto case '+';
4505 size_t istart = i;
4506 size_t eollen = 0;
4507 leadingBlank = false;
4508 const c0 = c; // if we jumped here from case '`' or case '~'
4509 size_t iInfoString = 0;
4510 if (!inCode)
4511 codeLanguage.length = 0;
4512 while (1)
4514 ++i;
4515 if (i >= buf.length)
4516 break;
4517 c = buf[i];
4518 if (c == '\n')
4520 eollen = 1;
4521 break;
4523 if (c == '\r')
4525 eollen = 1;
4526 if (i + 1 >= buf.length)
4527 break;
4528 if (buf[i + 1] == '\n')
4530 eollen = 2;
4531 break;
4534 // BUG: handle UTF PS and LS too
4535 if (c != c0 || iInfoString)
4537 if (!iInfoString && !inCode && i - istart >= 3)
4539 // Start a Markdown info string, like ```ruby
4540 codeFenceLength = i - istart;
4541 i = iInfoString = skipChars(buf, i, " \t");
4543 else if (iInfoString && c != '`')
4545 if (!codeLanguage.length && (c == ' ' || c == '\t'))
4546 codeLanguage = cast(string) buf[iInfoString..i].idup;
4548 else
4550 iInfoString = 0;
4551 goto Lcont;
4555 if (i - istart < 3 || (inCode && (inCode != c0 || (inCode != '-' && i - istart < codeFenceLength))))
4556 goto Lcont;
4557 if (iInfoString)
4559 if (!codeLanguage.length)
4560 codeLanguage = cast(string) buf[iInfoString..i].idup;
4562 else
4563 codeFenceLength = i - istart;
4565 // We have the start/end of a code section
4566 // Remove the entire --- line, including blanks and \n
4567 buf.remove(iLineStart, i - iLineStart + eollen);
4568 i = iLineStart;
4569 if (eollen)
4570 leadingBlank = true;
4571 if (inCode && (i <= iCodeStart))
4573 // Empty code section, just remove it completely.
4574 inCode = 0;
4575 break;
4577 if (inCode)
4579 inCode = 0;
4580 // The code section is from iCodeStart to i
4581 OutBuffer codebuf;
4582 codebuf.write(buf[iCodeStart .. i]);
4583 codebuf.writeByte(0);
4584 // Remove leading indentations from all lines
4585 bool lineStart = true;
4586 char* endp = cast(char*)codebuf[].ptr + codebuf.length;
4587 for (char* p = cast(char*)codebuf[].ptr; p < endp;)
4589 if (lineStart)
4591 size_t j = codeIndent;
4592 char* q = p;
4593 while (j-- > 0 && q < endp && isIndentWS(q))
4594 ++q;
4595 codebuf.remove(p - cast(char*)codebuf[].ptr, q - p);
4596 assert(cast(char*)codebuf[].ptr <= p);
4597 assert(p < cast(char*)codebuf[].ptr + codebuf.length);
4598 lineStart = false;
4599 endp = cast(char*)codebuf[].ptr + codebuf.length; // update
4600 continue;
4602 if (*p == '\n')
4603 lineStart = true;
4604 ++p;
4606 if (!codeLanguage.length || codeLanguage == "dlang" || codeLanguage == "d")
4607 highlightCode2(sc, a, codebuf, 0);
4608 else
4609 codebuf.remove(codebuf.length-1, 1); // remove the trailing 0 byte
4610 escapeStrayParenthesis(loc, codebuf, 0, false, sc.eSink);
4611 buf.remove(iCodeStart, i - iCodeStart);
4612 i = buf.insert(iCodeStart, codebuf[]);
4613 i = buf.insert(i, ")\n");
4614 i -= 2; // in next loop, c should be '\n'
4616 else
4618 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4619 if (!lineQuoted && quoteLevel)
4621 const delta = endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
4622 i += delta;
4623 istart += delta;
4626 inCode = c0;
4627 codeIndent = istart - iLineStart; // save indent count
4628 if (codeLanguage.length && codeLanguage != "dlang" && codeLanguage != "d")
4630 // backslash-escape
4631 for (size_t j; j < codeLanguage.length - 1; ++j)
4632 if (codeLanguage[j] == '\\' && ispunct(codeLanguage[j + 1]))
4633 codeLanguage = codeLanguage[0..j] ~ codeLanguage[j + 1..$];
4635 i = buf.insert(i, "$(OTHER_CODE ");
4636 i = buf.insert(i, codeLanguage);
4637 i = buf.insert(i, ",");
4639 else
4640 i = buf.insert(i, "$(D_CODE ");
4641 iCodeStart = i;
4642 i--; // place i on >
4643 leadingBlank = true;
4646 break;
4648 case '_':
4650 if (leadingBlank && !inCode && replaceMarkdownThematicBreak(buf, i, iLineStart, loc))
4652 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4653 if (!lineQuoted && quoteLevel)
4654 i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
4655 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
4656 iParagraphStart = skipChars(buf, i+1, " \t\r\n");
4657 break;
4659 goto default;
4662 case '+':
4663 case '0':
4665 case '9':
4667 if (leadingBlank && !inCode)
4669 MarkdownList list = MarkdownList.parseItem(buf, iLineStart, i);
4670 if (list.isValid)
4672 // Avoid starting a numbered list in the middle of a paragraph
4673 if (!nestedLists.length && list.orderedStart.length &&
4674 iParagraphStart < iLineStart)
4676 i += list.orderedStart.length - 1;
4677 break;
4680 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4681 if (!lineQuoted && quoteLevel)
4683 const delta = endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
4684 i += delta;
4685 list.iStart += delta;
4686 list.iContentStart += delta;
4689 list.macroLevel = macroLevel;
4690 list.startItem(buf, iLineStart, i, iPrecedingBlankLine, nestedLists, loc);
4691 break;
4694 leadingBlank = false;
4695 break;
4698 case '*':
4700 if (inCode || inBacktick)
4702 leadingBlank = false;
4703 break;
4706 if (leadingBlank)
4708 // Check for a thematic break
4709 if (replaceMarkdownThematicBreak(buf, i, iLineStart, loc))
4711 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4712 if (!lineQuoted && quoteLevel)
4713 i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
4714 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
4715 iParagraphStart = skipChars(buf, i+1, " \t\r\n");
4716 break;
4719 // An initial * indicates a Markdown list item
4720 const list = MarkdownList.parseItem(buf, iLineStart, i);
4721 if (list.isValid)
4722 goto case '+';
4725 // Markdown emphasis
4726 const leftC = i > offset ? buf[i-1] : '\0';
4727 size_t iAfterEmphasis = skipChars(buf, i+1, "*");
4728 const rightC = iAfterEmphasis < buf.length ? buf[iAfterEmphasis] : '\0';
4729 int count = cast(int) (iAfterEmphasis - i);
4730 const leftFlanking = (rightC != '\0' && !isspace(rightC)) && (!ispunct(rightC) || leftC == '\0' || isspace(leftC) || ispunct(leftC));
4731 const rightFlanking = (leftC != '\0' && !isspace(leftC)) && (!ispunct(leftC) || rightC == '\0' || isspace(rightC) || ispunct(rightC));
4732 auto emphasis = MarkdownDelimiter(i, count, macroLevel, leftFlanking, rightFlanking, false, c);
4734 if (!emphasis.leftFlanking && !emphasis.rightFlanking)
4736 i = iAfterEmphasis - 1;
4737 break;
4740 inlineDelimiters ~= emphasis;
4741 i += emphasis.count;
4742 --i;
4743 break;
4746 case '!':
4748 leadingBlank = false;
4750 if (inCode)
4751 break;
4753 if (i < buf.length-1 && buf[i+1] == '[')
4755 const imageStart = MarkdownDelimiter(i, 2, macroLevel, false, false, false, c);
4756 inlineDelimiters ~= imageStart;
4757 ++i;
4759 break;
4761 case '[':
4763 if (inCode)
4765 leadingBlank = false;
4766 break;
4769 const leftC = i > offset ? buf[i-1] : '\0';
4770 const rightFlanking = leftC != '\0' && !isspace(leftC) && !ispunct(leftC);
4771 const atParagraphStart = leadingBlank && iParagraphStart >= iLineStart;
4772 const linkStart = MarkdownDelimiter(i, 1, macroLevel, false, rightFlanking, atParagraphStart, c);
4773 inlineDelimiters ~= linkStart;
4774 leadingBlank = false;
4775 break;
4777 case ']':
4779 leadingBlank = false;
4781 if (inCode)
4782 break;
4784 for (int d = cast(int) inlineDelimiters.length - 1; d >= 0; --d)
4786 const delimiter = inlineDelimiters[d];
4787 if (delimiter.type == '[' || delimiter.type == '!')
4789 if (delimiter.isValid &&
4790 MarkdownLink.replaceLink(buf, i, loc, inlineDelimiters, d, linkReferences))
4792 // if we removed a reference link then we're at line start
4793 if (i <= delimiter.iStart)
4794 leadingBlank = true;
4796 // don't nest links
4797 if (delimiter.type == '[')
4798 for (--d; d >= 0; --d)
4799 if (inlineDelimiters[d].type == '[')
4800 inlineDelimiters[d].invalidate();
4802 else
4804 // nothing found, so kill the delimiter
4805 inlineDelimiters = inlineDelimiters[0..d] ~ inlineDelimiters[d+1..$];
4807 break;
4810 break;
4813 case '|':
4815 if (inCode)
4817 leadingBlank = false;
4818 break;
4821 tableRowDetected = true;
4822 inlineDelimiters ~= MarkdownDelimiter(i, 1, macroLevel, leadingBlank, false, false, c);
4823 leadingBlank = false;
4824 break;
4827 case '\\':
4829 leadingBlank = false;
4830 if (inCode || i+1 >= buf.length)
4831 break;
4833 /* Escape Markdown special characters */
4834 char c1 = buf[i+1];
4835 if (ispunct(c1))
4837 buf.remove(i, 1);
4839 auto se = sc._module.escapetable.escapeChar(c1);
4840 if (!se)
4841 se = c1 == '$' ? "$(DOLLAR)" : c1 == ',' ? "$(COMMA)" : null;
4842 if (se)
4844 buf.remove(i, 1);
4845 i = buf.insert(i, se);
4846 i--; // point to escaped char
4849 break;
4852 case '$':
4854 /* Look for the start of a macro, '$(Identifier'
4856 leadingBlank = false;
4857 if (inCode || inBacktick)
4858 break;
4859 const slice = buf[];
4860 auto p = &slice[i];
4861 if (p[1] == '(' && isIdStart(&p[2]))
4862 ++macroLevel;
4863 break;
4866 case '(':
4868 if (!inCode && i > offset && buf[i-1] != '$')
4869 ++parenLevel;
4870 break;
4873 case ')':
4874 { /* End of macro
4876 leadingBlank = false;
4877 if (inCode || inBacktick)
4878 break;
4879 if (parenLevel > 0)
4880 --parenLevel;
4881 else if (macroLevel)
4883 int downToLevel = cast(int) inlineDelimiters.length;
4884 while (downToLevel > 0 && inlineDelimiters[downToLevel - 1].macroLevel >= macroLevel)
4885 --downToLevel;
4886 if (headingLevel && headingMacroLevel >= macroLevel)
4888 endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel);
4889 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
4891 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4892 while (nestedLists.length && nestedLists[$-1].macroLevel >= macroLevel)
4894 i = buf.insert(i, ")\n)");
4895 --nestedLists.length;
4897 if (quoteLevel && quoteMacroLevel >= macroLevel)
4898 i += endAllMarkdownQuotes(buf, i, quoteLevel);
4899 i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters, downToLevel);
4901 --macroLevel;
4902 quoteMacroLevel = 0;
4904 break;
4907 default:
4908 leadingBlank = false;
4909 if (sc._module.filetype == FileType.ddoc || inCode)
4910 break;
4911 const start = cast(char*)buf[].ptr + i;
4912 if (isIdStart(start))
4914 size_t j = skippastident(buf, i);
4915 if (i < j)
4917 size_t k = skippastURL(buf, i);
4918 if (i < k)
4920 /* The URL is buf[i..k]
4922 if (macroLevel)
4923 /* Leave alone if already in a macro
4925 i = k - 1;
4926 else
4928 /* Replace URL with '$(DDOC_LINK_AUTODETECT URL)'
4930 i = buf.bracket(i, "$(DDOC_LINK_AUTODETECT ", k, ")") - 1;
4932 break;
4935 else
4936 break;
4937 size_t len = j - i;
4938 // leading '_' means no highlight unless it's a reserved symbol name
4939 if (c == '_' && (i == 0 || !isdigit(*(start - 1))) && (i == buf.length - 1 || !isReservedName(start[0 .. len])))
4941 buf.remove(i, 1);
4942 i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL_SUPPRESS ", j - 1, ")") - 1;
4943 break;
4945 if (isIdentifier(a, start[0 .. len]))
4947 i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL ", j, ")") - 1;
4948 break;
4950 if (isKeyword(start[0 .. len]))
4952 i = buf.bracket(i, "$(DDOC_AUTO_KEYWORD ", j, ")") - 1;
4953 break;
4955 if (isFunctionParameter(a, start[0 .. len]))
4957 //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j);
4958 i = buf.bracket(i, "$(DDOC_AUTO_PARAM ", j, ")") - 1;
4959 break;
4961 i = j - 1;
4963 break;
4967 if (inCode == '-')
4968 sc.eSink.error(loc, "unmatched `---` in DDoc comment");
4969 else if (inCode)
4970 buf.insert(buf.length, ")");
4972 size_t i = buf.length;
4973 if (headingLevel)
4975 endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel);
4976 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
4978 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
4979 i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters);
4980 endAllListsAndQuotes(buf, i, nestedLists, quoteLevel, quoteMacroLevel);
4983 /**************************************************
4984 * Highlight code for DDOC section.
4986 void highlightCode(Scope* sc, Dsymbol s, ref OutBuffer buf, size_t offset)
4988 auto imp = s.isImport();
4989 if (imp && imp.aliases.length > 0)
4991 // For example: `public import core.stdc.string : memcpy, memcmp;`
4992 for(int i = 0; i < imp.aliases.length; i++)
4994 // Need to distinguish between
4995 // `public import core.stdc.string : memcpy, memcmp;` and
4996 // `public import core.stdc.string : copy = memcpy, compare = memcmp;`
4997 auto a = imp.aliases[i];
4998 auto id = a ? a : imp.names[i];
4999 auto loc = Loc.init;
5000 if (auto symFromId = sc.search(loc, id, null))
5002 highlightCode(sc, symFromId, buf, offset);
5006 else
5008 OutBuffer ancbuf;
5009 emitAnchor(ancbuf, s, sc);
5010 buf.insert(offset, ancbuf[]);
5011 offset += ancbuf.length;
5013 Dsymbols a;
5014 a.push(s);
5015 highlightCode(sc, &a, buf, offset);
5019 /****************************************************
5021 void highlightCode(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
5023 //printf("highlightCode(a = '%s')\n", a.toChars());
5024 bool resolvedTemplateParameters = false;
5026 for (size_t i = offset; i < buf.length; i++)
5028 char c = buf[i];
5029 const se = sc._module.escapetable.escapeChar(c);
5030 if (se.length)
5032 buf.remove(i, 1);
5033 i = buf.insert(i, se);
5034 i--; // point to ';'
5035 continue;
5037 char* start = cast(char*)buf[].ptr + i;
5038 if (isIdStart(start))
5040 size_t j = skipPastIdentWithDots(buf, i);
5041 if (i < j)
5043 size_t len = j - i;
5044 if (isIdentifier(a, start[0 .. len]))
5046 i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
5047 continue;
5051 j = skippastident(buf, i);
5052 if (i < j)
5054 size_t len = j - i;
5055 if (isIdentifier(a, start[0 .. len]))
5057 i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
5058 continue;
5060 if (isFunctionParameter(a, start[0 .. len]))
5062 //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j);
5063 i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
5064 continue;
5066 i = j - 1;
5069 else if (!resolvedTemplateParameters)
5071 size_t previ = i;
5073 // hunt for template declarations:
5074 foreach (symi; 0 .. a.length)
5076 FuncDeclaration fd = (*a)[symi].isFuncDeclaration();
5078 if (!fd || !fd.parent || !fd.parent.isTemplateDeclaration())
5080 continue;
5083 TemplateDeclaration td = fd.parent.isTemplateDeclaration();
5085 // build the template parameters
5086 Array!(size_t) paramLens;
5087 paramLens.reserve(td.parameters.length);
5089 OutBuffer parametersBuf;
5090 HdrGenState hgs;
5092 parametersBuf.writeByte('(');
5094 foreach (parami; 0 .. td.parameters.length)
5096 TemplateParameter tp = (*td.parameters)[parami];
5098 if (parami)
5099 parametersBuf.writestring(", ");
5101 size_t lastOffset = parametersBuf.length;
5103 toCBuffer(tp, parametersBuf, hgs);
5105 paramLens[parami] = parametersBuf.length - lastOffset;
5107 parametersBuf.writeByte(')');
5109 const templateParams = parametersBuf[];
5111 //printf("templateDecl: %s\ntemplateParams: %s\nstart: %s\n", td.toChars(), templateParams, start);
5112 if (start[0 .. templateParams.length] == templateParams)
5114 immutable templateParamListMacro = "$(DDOC_TEMPLATE_PARAM_LIST ";
5115 buf.bracket(i, templateParamListMacro.ptr, i + templateParams.length, ")");
5117 // We have the parameter list. While we're here we might
5118 // as well wrap the parameters themselves as well
5120 // + 1 here to take into account the opening paren of the
5121 // template param list
5122 i += templateParamListMacro.length + 1;
5124 foreach (const len; paramLens)
5126 i = buf.bracket(i, "$(DDOC_TEMPLATE_PARAM ", i + len, ")");
5127 // increment two here for space + comma
5128 i += 2;
5131 resolvedTemplateParameters = true;
5132 // reset i to be positioned back before we found the template
5133 // param list this assures that anything within the template
5134 // param list that needs to be escaped or otherwise altered
5135 // has an opportunity for that to happen outside of this context
5136 i = previ;
5138 continue;
5145 /****************************************
5147 void highlightCode3(Scope* sc, ref OutBuffer buf, const(char)* p, const(char)* pend)
5149 for (; p < pend; p++)
5151 const se = sc._module.escapetable.escapeChar(*p);
5152 if (se.length)
5153 buf.writestring(se);
5154 else
5155 buf.writeByte(*p);
5159 /**************************************************
5160 * Highlight code for CODE section.
5162 void highlightCode2(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
5164 scope eSinkNull = new ErrorSinkNull();
5166 scope Lexer lex = new Lexer(null, cast(char*)buf[].ptr, 0, buf.length - 1, 0, 1,
5167 eSinkNull, // ignore errors
5168 &global.compileEnv);
5169 OutBuffer res;
5170 const(char)* lastp = cast(char*)buf[].ptr;
5171 //printf("highlightCode2('%.*s')\n", cast(int)(buf.length - 1), buf[].ptr);
5172 res.reserve(buf.length);
5173 while (1)
5175 Token tok;
5176 lex.scan(&tok);
5177 highlightCode3(sc, res, lastp, tok.ptr);
5178 string highlight = null;
5179 switch (tok.value)
5181 case TOK.identifier:
5183 if (!sc)
5184 break;
5185 size_t len = lex.p - tok.ptr;
5186 if (isIdentifier(a, tok.ptr[0 .. len]))
5188 highlight = "$(D_PSYMBOL ";
5189 break;
5191 if (isFunctionParameter(a, tok.ptr[0 .. len]))
5193 //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j);
5194 highlight = "$(D_PARAM ";
5195 break;
5197 break;
5199 case TOK.comment:
5200 highlight = "$(D_COMMENT ";
5201 break;
5202 case TOK.string_:
5203 highlight = "$(D_STRING ";
5204 break;
5205 default:
5206 if (tok.isKeyword())
5207 highlight = "$(D_KEYWORD ";
5208 break;
5210 if (highlight)
5212 res.writestring(highlight);
5213 size_t o = res.length;
5214 highlightCode3(sc, res, tok.ptr, lex.p);
5215 if (tok.value == TOK.comment || tok.value == TOK.string_)
5216 /* https://issues.dlang.org/show_bug.cgi?id=7656
5217 * https://issues.dlang.org/show_bug.cgi?id=7715
5218 * https://issues.dlang.org/show_bug.cgi?id=10519
5220 escapeDdocString(res, o);
5221 res.writeByte(')');
5223 else
5224 highlightCode3(sc, res, tok.ptr, lex.p);
5225 if (tok.value == TOK.endOfFile)
5226 break;
5227 lastp = lex.p;
5229 buf.setsize(offset);
5230 buf.write(&res);
5233 /****************************************
5234 * Determine if p points to the start of a "..." parameter identifier.
5236 bool isCVariadicArg(const(char)[] p) @nogc nothrow pure @safe
5238 return p.length >= 3 && p[0 .. 3] == "...";
5241 /****************************************
5242 * Determine if p points to the start of an identifier.
5244 @trusted
5245 bool isIdStart(const(char)* p) @nogc nothrow pure
5247 dchar c = *p;
5248 if (isalpha(c) || c == '_')
5249 return true;
5250 if (c >= 0x80)
5252 size_t i = 0;
5253 if (utf_decodeChar(p[0 .. 4], i, c))
5254 return false; // ignore errors
5255 if (isUniAlpha(c))
5256 return true;
5258 return false;
5261 /****************************************
5262 * Determine if p points to the rest of an identifier.
5264 @trusted
5265 bool isIdTail(const(char)* p) @nogc nothrow pure
5267 dchar c = *p;
5268 if (isalnum(c) || c == '_')
5269 return true;
5270 if (c >= 0x80)
5272 size_t i = 0;
5273 if (utf_decodeChar(p[0 .. 4], i, c))
5274 return false; // ignore errors
5275 if (isUniAlpha(c))
5276 return true;
5278 return false;
5281 /****************************************
5282 * Determine if p points to the indentation space.
5284 bool isIndentWS(const(char)* p) @nogc nothrow pure @safe
5286 return (*p == ' ') || (*p == '\t');
5289 /*****************************************
5290 * Return number of bytes in UTF character.
5292 int utfStride(const(char)* p) @nogc nothrow pure
5294 dchar c = *p;
5295 if (c < 0x80)
5296 return 1;
5297 size_t i = 0;
5298 utf_decodeChar(p[0 .. 4], i, c); // ignore errors, but still consume input
5299 return cast(int)i;
5302 inout(char)* stripLeadingNewlines(inout(char)* s) @nogc nothrow pure
5304 while (s && *s == '\n' || *s == '\r')
5305 s++;
5307 return s;