1 /**
2  * Compiler implementation of the
3  * $(LINK2 http://www.dlang.org, D programming language).
4  *
5  * Copyright:   Copyright (c) 1999-2016 by Digital Mars, All Rights Reserved
6  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
7  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8  * Source:      $(DMDSRC _doc.d)
9  */
10 
11 module ddmd.doc;
12 
13 import core.stdc.ctype;
14 import core.stdc.stdlib;
15 import core.stdc.stdio;
16 import core.stdc..string;
17 import core.stdc.time;
18 import ddmd.aggregate;
19 import ddmd.arraytypes;
20 import ddmd.attrib;
21 import ddmd.dclass;
22 import ddmd.declaration;
23 import ddmd.denum;
24 import ddmd.dmacro;
25 import ddmd.dmodule;
26 import ddmd.dscope;
27 import ddmd.dstruct;
28 import ddmd.dsymbol;
29 import ddmd.dtemplate;
30 import ddmd.errors;
31 import ddmd.func;
32 import ddmd.globals;
33 import ddmd.hdrgen;
34 import ddmd.id;
35 import ddmd.identifier;
36 import ddmd.lexer;
37 import ddmd.mtype;
38 import ddmd.root.array;
39 import ddmd.root.file;
40 import ddmd.root.filename;
41 import ddmd.root.outbuffer;
42 import ddmd.root.port;
43 import ddmd.root.rmem;
44 import ddmd.tokens;
45 import ddmd.utf;
46 import ddmd.utils;
47 import ddmd.visitor;
48 
49 struct Escape
50 {
51     const(char)*[256] strings;
52 
53     /***************************************
54      * Find character string to replace c with.
55      */
56     extern (C++) const(char)* escapeChar(uint c)
57     {
58         version (all)
59         {
60             assert(c < 256);
61             //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c]);
62             return strings[c];
63         }
64         else
65         {
66             const(char)* s;
67             switch (c)
68             {
69             case '<':
70                 s = "&lt;";
71                 break;
72             case '>':
73                 s = "&gt;";
74                 break;
75             case '&':
76                 s = "&amp;";
77                 break;
78             default:
79                 s = null;
80                 break;
81             }
82             return s;
83         }
84     }
85 }
86 
87 /***********************************************************
88  */
89 extern (C++) class Section
90 {
91     const(char)* name;
92     size_t namelen;
93     const(char)* _body;
94     size_t bodylen;
95     int nooutput;
96 
97     void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
98     {
99         assert(a.dim);
100         if (namelen)
101         {
102             static __gshared const(char)** table =
103             [
104                 "AUTHORS",
105                 "BUGS",
106                 "COPYRIGHT",
107                 "DATE",
108                 "DEPRECATED",
109                 "EXAMPLES",
110                 "HISTORY",
111                 "LICENSE",
112                 "RETURNS",
113                 "SEE_ALSO",
114                 "STANDARDS",
115                 "THROWS",
116                 "VERSION",
117                 null
118             ];
119             for (size_t i = 0; table[i]; i++)
120             {
121                 if (icmp(table[i], name, namelen) == 0)
122                 {
123                     buf.printf("$(DDOC_%s ", table[i]);
124                     goto L1;
125                 }
126             }
127             buf.writestring("$(DDOC_SECTION ");
128             // Replace _ characters with spaces
129             buf.writestring("$(DDOC_SECTION_H ");
130             size_t o = buf.offset;
131             for (size_t u = 0; u < namelen; u++)
132             {
133                 char c = name[u];
134                 buf.writeByte((c == '_') ? ' ' : c);
135             }
136             escapeStrayParenthesis(loc, buf, o);
137             buf.writestring(":)\n");
138         }
139         else
140         {
141             buf.writestring("$(DDOC_DESCRIPTION ");
142         }
143     L1:
144         size_t o = buf.offset;
145         buf.write(_body, bodylen);
146         escapeStrayParenthesis(loc, buf, o);
147         highlightText(sc, a, buf, o);
148         buf.writestring(")\n");
149     }
150 }
151 
152 /***********************************************************
153  */
154 extern (C++) final class ParamSection : Section
155 {
156     override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
157     {
158         assert(a.dim);
159         Dsymbol s = (*a)[0]; // test
160         const(char)* p = _body;
161         size_t len = bodylen;
162         const(char)* pend = p + len;
163         const(char)* tempstart = null;
164         size_t templen = 0;
165         const(char)* namestart = null;
166         size_t namelen = 0; // !=0 if line continuation
167         const(char)* textstart = null;
168         size_t textlen = 0;
169         size_t paramcount = 0;
170         buf.writestring("$(DDOC_PARAMS ");
171         while (p < pend)
172         {
173             // Skip to start of macro
174             while (1)
175             {
176                 switch (*p)
177                 {
178                 case ' ':
179                 case '\t':
180                     p++;
181                     continue;
182                 case '\n':
183                     p++;
184                     goto Lcont;
185                 default:
186                     if (isIdStart(p) || isCVariadicArg(p, pend - p))
187                         break;
188                     if (namelen)
189                         goto Ltext;
190                     // continuation of prev macro
191                     goto Lskipline;
192                 }
193                 break;
194             }
195             tempstart = p;
196             while (isIdTail(p))
197                 p += utfStride(p);
198             if (isCVariadicArg(p, pend - p))
199                 p += 3;
200             templen = p - tempstart;
201             while (*p == ' ' || *p == '\t')
202                 p++;
203             if (*p != '=')
204             {
205                 if (namelen)
206                     goto Ltext;
207                 // continuation of prev macro
208                 goto Lskipline;
209             }
210             p++;
211             if (namelen)
212             {
213                 // Output existing param
214             L1:
215                 //printf("param '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart);
216                 ++paramcount;
217                 HdrGenState hgs;
218                 buf.writestring("$(DDOC_PARAM_ROW ");
219                 {
220                     buf.writestring("$(DDOC_PARAM_ID ");
221                     {
222                         size_t o = buf.offset;
223                         Parameter fparam = isFunctionParameter(a, namestart, namelen);
224                         if (!fparam)
225                         {
226                             // Comments on a template might refer to function parameters within.
227                             // Search the parameters of nested eponymous functions (with the same name.)
228                             fparam = isEponymousFunctionParameter(a, namestart, namelen);
229                         }
230                         bool isCVariadic = isCVariadicParameter(a, namestart, namelen);
231                         if (isCVariadic)
232                         {
233                             buf.writestring("...");
234                         }
235                         else if (fparam && fparam.type && fparam.ident)
236                         {
237                             .toCBuffer(fparam.type, buf, fparam.ident, &hgs);
238                         }
239                         else
240                         {
241                             if (isTemplateParameter(a, namestart, namelen))
242                             {
243                                 // 10236: Don't count template parameters for params check
244                                 --paramcount;
245                             }
246                             else if (!fparam)
247                             {
248                                 warning(s.loc, "Ddoc: function declaration has no parameter '%.*s'", namelen, namestart);
249                             }
250                             buf.write(namestart, namelen);
251                         }
252                         escapeStrayParenthesis(loc, buf, o);
253                         highlightCode(sc, a, buf, o);
254                     }
255                     buf.writestring(")\n");
256                     buf.writestring("$(DDOC_PARAM_DESC ");
257                     {
258                         size_t o = buf.offset;
259                         buf.write(textstart, textlen);
260                         escapeStrayParenthesis(loc, buf, o);
261                         highlightText(sc, a, buf, o);
262                     }
263                     buf.writestring(")");
264                 }
265                 buf.writestring(")\n");
266                 namelen = 0;
267                 if (p >= pend)
268                     break;
269             }
270             namestart = tempstart;
271             namelen = templen;
272             while (*p == ' ' || *p == '\t')
273                 p++;
274             textstart = p;
275         Ltext:
276             while (*p != '\n')
277                 p++;
278             textlen = p - textstart;
279             p++;
280         Lcont:
281             continue;
282         Lskipline:
283             // Ignore this line
284             while (*p++ != '\n')
285             {
286             }
287         }
288         if (namelen)
289             goto L1;
290         // write out last one
291         buf.writestring(")\n");
292         TypeFunction tf = a.dim == 1 ? isTypeFunction(s) : null;
293         if (tf)
294         {
295             size_t pcount = (tf.parameters ? tf.parameters.dim : 0) + cast(int)(tf.varargs == 1);
296             if (pcount != paramcount)
297             {
298                 warning(s.loc, "Ddoc: parameter count mismatch");
299             }
300         }
301     }
302 }
303 
304 /***********************************************************
305  */
306 extern (C++) final class MacroSection : Section
307 {
308     override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
309     {
310         //printf("MacroSection::write()\n");
311         DocComment.parseMacros(dc.pescapetable, dc.pmacrotable, _body, bodylen);
312     }
313 }
314 
315 alias Sections = Array!(Section);
316 
317 // Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one).
318 extern (C++) bool isCVariadicParameter(Dsymbols* a, const(char)* p, size_t len)
319 {
320     for (size_t i = 0; i < a.dim; i++)
321     {
322         TypeFunction tf = isTypeFunction((*a)[i]);
323         if (tf && tf.varargs == 1 && cmp("...", p, len) == 0)
324             return true;
325     }
326     return false;
327 }
328 
329 extern (C++) static Dsymbol getEponymousMember(TemplateDeclaration td)
330 {
331     if (!td.onemember)
332         return null;
333     if (AggregateDeclaration ad = td.onemember.isAggregateDeclaration())
334         return ad;
335     if (FuncDeclaration fd = td.onemember.isFuncDeclaration())
336         return fd;
337     if (auto em = td.onemember.isEnumMember())
338         return null;    // Keep backward compatibility. See compilable/ddoc9.d
339     if (VarDeclaration vd = td.onemember.isVarDeclaration())
340         return td.constraint ? null : vd;
341     return null;
342 }
343 
344 extern (C++) static TemplateDeclaration getEponymousParent(Dsymbol s)
345 {
346     if (!s.parent)
347         return null;
348     TemplateDeclaration td = s.parent.isTemplateDeclaration();
349     return (td && getEponymousMember(td)) ? td : null;
350 }
351 
352 extern (C++) __gshared const(char)* ddoc_default = "DDOC =  <html><head>
353         $(DDOC_COMMENT Generated by Ddoc from $(SRCFILENAME))
354         <META http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">
355         <title>$(TITLE)</title>
356         </head><body>
357         <h1>$(TITLE)</h1>
358 $(BODY)
359         <hr>$(SMALL Page generated by $(LINK2 http://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT))
360         </body></html>
361 
362 B =     <b>$0</b>
363 I =     <i>$0</i>
364 U =     <u>$0</u>
365 P =     <p>$0</p>
366 DL =    <dl>$0</dl>
367 DT =    <dt>$0</dt>
368 DD =    <dd>$0</dd>
369 TABLE = <table>$0</table>
370 TR =    <tr>$0</tr>
371 TH =    <th>$0</th>
372 TD =    <td>$0</td>
373 OL =    <ol>$0</ol>
374 UL =    <ul>$0</ul>
375 LI =    <li>$0</li>
376 BIG =   <big>$0</big>
377 SMALL = <small>$0</small>
378 BR =    <br>
379 LINK =  <a href=\"$0\">$0</a>
380 LINK2 = <a href=\"$1\">$+</a>
381 LPAREN= (
382 RPAREN= )
383 BACKTICK= `
384 DOLLAR= $
385 DEPRECATED= $0
386 
387 RED =   <font color=red>$0</font>
388 BLUE =  <font color=blue>$0</font>
389 GREEN = <font color=green>$0</font>
390 YELLOW =<font color=yellow>$0</font>
391 BLACK = <font color=black>$0</font>
392 WHITE = <font color=white>$0</font>
393 
394 D_CODE = <pre class=\"d_code\">$0</pre>
395 DDOC_BACKQUOTED = $(D_INLINECODE $0)
396 D_INLINECODE = <pre style=\"display:inline;\" class=\"d_inline_code\">$0</pre>
397 D_COMMENT = $(GREEN $0)
398 D_STRING  = $(RED $0)
399 D_KEYWORD = $(BLUE $0)
400 D_PSYMBOL = $(U $0)
401 D_PARAM   = $(I $0)
402 
403 DDOC_COMMENT   = <!-- $0 -->
404 DDOC_DECL      = $(DT $(BIG $0))
405 DDOC_DECL_DD   = $(DD $0)
406 DDOC_DITTO     = $(BR)$0
407 DDOC_SECTIONS  = $0
408 DDOC_SUMMARY   = $0$(BR)$(BR)
409 DDOC_DESCRIPTION = $0$(BR)$(BR)
410 DDOC_AUTHORS   = $(B Authors:)$(BR)
411 $0$(BR)$(BR)
412 DDOC_BUGS      = $(RED BUGS:)$(BR)
413 $0$(BR)$(BR)
414 DDOC_COPYRIGHT = $(B Copyright:)$(BR)
415 $0$(BR)$(BR)
416 DDOC_DATE      = $(B Date:)$(BR)
417 $0$(BR)$(BR)
418 DDOC_DEPRECATED = $(RED Deprecated:)$(BR)
419 $0$(BR)$(BR)
420 DDOC_EXAMPLES  = $(B Examples:)$(BR)
421 $0$(BR)$(BR)
422 DDOC_HISTORY   = $(B History:)$(BR)
423 $0$(BR)$(BR)
424 DDOC_LICENSE   = $(B License:)$(BR)
425 $0$(BR)$(BR)
426 DDOC_RETURNS   = $(B Returns:)$(BR)
427 $0$(BR)$(BR)
428 DDOC_SEE_ALSO  = $(B See Also:)$(BR)
429 $0$(BR)$(BR)
430 DDOC_STANDARDS = $(B Standards:)$(BR)
431 $0$(BR)$(BR)
432 DDOC_THROWS    = $(B Throws:)$(BR)
433 $0$(BR)$(BR)
434 DDOC_VERSION   = $(B Version:)$(BR)
435 $0$(BR)$(BR)
436 DDOC_SECTION_H = $(B $0)$(BR)
437 DDOC_SECTION   = $0$(BR)$(BR)
438 DDOC_MEMBERS   = $(DL $0)
439 DDOC_MODULE_MEMBERS = $(DDOC_MEMBERS $0)
440 DDOC_CLASS_MEMBERS  = $(DDOC_MEMBERS $0)
441 DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0)
442 DDOC_ENUM_MEMBERS   = $(DDOC_MEMBERS $0)
443 DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0)
444 DDOC_ENUM_BASETYPE = $0
445 DDOC_PARAMS    = $(B Params:)$(BR)
446 $(TABLE $0)$(BR)
447 DDOC_PARAM_ROW = $(TR $0)
448 DDOC_PARAM_ID  = $(TD $0)
449 DDOC_PARAM_DESC = $(TD $0)
450 DDOC_BLANKLINE  = $(BR)$(BR)
451 
452 DDOC_ANCHOR     = <a name=\"$1\"></a>
453 DDOC_PSYMBOL    = $(U $0)
454 DDOC_PSUPER_SYMBOL = $(U $0)
455 DDOC_KEYWORD    = $(B $0)
456 DDOC_PARAM      = $(I $0)
457 DDOC_CONSTRAINT      = $(DDOC_CONSTRAINT) if ($0)
458 DDOC_OVERLOAD_SEPARATOR      =
459 DDOC_TEMPLATE_PARAM_LIST = $0
460 DDOC_TEMPLATE_PARAM = $0
461 
462 ESCAPES = /</&lt;/
463           />/&gt;/
464           /&/&amp;/
465 ";
466 extern (C++) __gshared const(char)* ddoc_decl_s = "$(DDOC_DECL ";
467 extern (C++) __gshared const(char)* ddoc_decl_e = ")\n";
468 extern (C++) __gshared const(char)* ddoc_decl_dd_s = "$(DDOC_DECL_DD ";
469 extern (C++) __gshared const(char)* ddoc_decl_dd_e = ")\n";
470 
471 /****************************************************
472  */
473 extern (C++) void gendocfile(Module m)
474 {
475     static __gshared OutBuffer mbuf;
476     static __gshared int mbuf_done;
477     OutBuffer buf;
478     //printf("Module::gendocfile()\n");
479     if (!mbuf_done) // if not already read the ddoc files
480     {
481         mbuf_done = 1;
482         // Use our internal default
483         mbuf.write(ddoc_default, strlen(ddoc_default));
484         // Override with DDOCFILE specified in the sc.ini file
485         char* p = getenv("DDOCFILE");
486         if (p)
487             global.params.ddocfiles.shift(p);
488         // Override with the ddoc macro files from the command line
489         for (size_t i = 0; i < global.params.ddocfiles.dim; i++)
490         {
491             auto f = FileName((*global.params.ddocfiles)[i]);
492             auto file = File(&f);
493             readFile(m.loc, &file);
494             // BUG: convert file contents to UTF-8 before use
495             //printf("file: '%.*s'\n", file.len, file.buffer);
496             mbuf.write(file.buffer, file.len);
497         }
498     }
499     DocComment.parseMacros(&m.escapetable, &m.macrotable, mbuf.peekSlice().ptr, mbuf.peekSlice().length);
500     Scope* sc = Scope.createGlobal(m); // create root scope
501     DocComment* dc = DocComment.parse(sc, m, m.comment);
502     dc.pmacrotable = &m.macrotable;
503     dc.pescapetable = &m.escapetable;
504     sc.lastdc = dc;
505     // Generate predefined macros
506     // Set the title to be the name of the module
507     {
508         const(char)* p = m.toPrettyChars();
509         Macro.define(&m.macrotable, "TITLE", p[0 .. strlen(p)]);
510     }
511     // Set time macros
512     {
513         time_t t;
514         time(&t);
515         char* p = ctime(&t);
516         p = mem.xstrdup(p);
517         Macro.define(&m.macrotable, "DATETIME", p[0 .. strlen(p)]);
518         Macro.define(&m.macrotable, "YEAR", p[20 .. 20 + 4]);
519     }
520     const srcfilename = m.srcfile.toChars();
521     Macro.define(&m.macrotable, "SRCFILENAME", srcfilename[0 .. strlen(srcfilename)]);
522     const docfilename = m.docfile.toChars();
523     Macro.define(&m.macrotable, "DOCFILENAME", docfilename[0 .. strlen(docfilename)]);
524     if (dc.copyright)
525     {
526         dc.copyright.nooutput = 1;
527         Macro.define(&m.macrotable, "COPYRIGHT", dc.copyright._body[0 .. dc.copyright.bodylen]);
528     }
529     if (m.isDocFile)
530     {
531         Loc loc = m.md ? m.md.loc : m.loc;
532         size_t commentlen = strlen(cast(char*)m.comment);
533         Dsymbols a;
534         // Bugzilla 9764: Don't push m in a, to prevent emphasize ddoc file name.
535         if (dc.macros)
536         {
537             commentlen = dc.macros.name - m.comment;
538             dc.macros.write(loc, dc, sc, &a, &buf);
539         }
540         buf.write(m.comment, commentlen);
541         highlightText(sc, &a, &buf, 0);
542     }
543     else
544     {
545         Dsymbols a;
546         a.push(m);
547         dc.writeSections(sc, &a, &buf);
548         emitMemberComments(m, &buf, sc);
549     }
550     //printf("BODY= '%.*s'\n", buf.offset, buf.data);
551     Macro.define(&m.macrotable, "BODY", buf.peekSlice());
552     OutBuffer buf2;
553     buf2.writestring("$(DDOC)\n");
554     size_t end = buf2.offset;
555     m.macrotable.expand(&buf2, 0, &end, null, 0);
556     version (all)
557     {
558         /* Remove all the escape sequences from buf2,
559          * and make CR-LF the newline.
560          */
561         {
562             const slice = buf2.peekSlice();
563             buf.setsize(0);
564             buf.reserve(slice.length);
565             auto p = slice.ptr;
566             for (size_t j = 0; j < slice.length; j++)
567             {
568                 char c = p[j];
569                 if (c == 0xFF && j + 1 < slice.length)
570                 {
571                     j++;
572                     continue;
573                 }
574                 if (c == '\n')
575                     buf.writeByte('\r');
576                 else if (c == '\r')
577                 {
578                     buf.writestring("\r\n");
579                     if (j + 1 < slice.length && p[j + 1] == '\n')
580                     {
581                         j++;
582                     }
583                     continue;
584                 }
585                 buf.writeByte(c);
586             }
587         }
588         // Transfer image to file
589         assert(m.docfile);
590         m.docfile.setbuffer(cast(void*)buf.peekSlice().ptr, buf.peekSlice().length);
591         m.docfile._ref = 1;
592         ensurePathToNameExists(Loc(), m.docfile.toChars());
593         writeFile(m.loc, m.docfile);
594     }
595     else
596     {
597         /* Remove all the escape sequences from buf2
598          */
599         {
600             size_t i = 0;
601             char* p = buf2.data;
602             for (size_t j = 0; j < buf2.offset; j++)
603             {
604                 if (p[j] == 0xFF && j + 1 < buf2.offset)
605                 {
606                     j++;
607                     continue;
608                 }
609                 p[i] = p[j];
610                 i++;
611             }
612             buf2.setsize(i);
613         }
614         // Transfer image to file
615         m.docfile.setbuffer(buf2.data, buf2.offset);
616         m.docfile._ref = 1;
617         ensurePathToNameExists(Loc(), m.docfile.toChars());
618         writeFile(m.loc, m.docfile);
619     }
620 }
621 
622 /****************************************************
623  * Having unmatched parentheses can hose the output of Ddoc,
624  * as the macros depend on properly nested parentheses.
625  * This function replaces all ( with $(LPAREN) and ) with $(RPAREN)
626  * to preserve text literally. This also means macros in the
627  * text won't be expanded.
628  */
629 extern (C++) void escapeDdocString(OutBuffer* buf, size_t start)
630 {
631     for (size_t u = start; u < buf.offset; u++)
632     {
633         char c = buf.data[u];
634         switch (c)
635         {
636         case '$':
637             buf.remove(u, 1);
638             buf.insert(u, "$(DOLLAR)");
639             u += 8;
640             break;
641         case '(':
642             buf.remove(u, 1); //remove the (
643             buf.insert(u, "$(LPAREN)"); //insert this instead
644             u += 8; //skip over newly inserted macro
645             break;
646         case ')':
647             buf.remove(u, 1); //remove the )
648             buf.insert(u, "$(RPAREN)"); //insert this instead
649             u += 8; //skip over newly inserted macro
650             break;
651         default:
652             break;
653         }
654     }
655 }
656 
657 /****************************************************
658  * Having unmatched parentheses can hose the output of Ddoc,
659  * as the macros depend on properly nested parentheses.
660  *
661  * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN).
662  */
663 extern (C++) void escapeStrayParenthesis(Loc loc, OutBuffer* buf, size_t start)
664 {
665     uint par_open = 0;
666     bool inCode = 0;
667     for (size_t u = start; u < buf.offset; u++)
668     {
669         char c = buf.data[u];
670         switch (c)
671         {
672         case '(':
673             if (!inCode)
674                 par_open++;
675             break;
676         case ')':
677             if (!inCode)
678             {
679                 if (par_open == 0)
680                 {
681                     //stray ')'
682                     warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output. Use $(RPAREN) instead for unpaired right parentheses.");
683                     buf.remove(u, 1); //remove the )
684                     buf.insert(u, "$(RPAREN)"); //insert this instead
685                     u += 8; //skip over newly inserted macro
686                 }
687                 else
688                     par_open--;
689             }
690             break;
691             version (none)
692             {
693                 // For this to work, loc must be set to the beginning of the passed
694                 // text which is currently not possible
695                 // (loc is set to the Loc of the Dsymbol)
696             case '\n':
697                 loc.linnum++;
698                 break;
699             }
700         case '-':
701             // Issue 15465: don't try to escape unbalanced parens inside code
702             // blocks.
703             int numdash = 0;
704             while (u < buf.offset && buf.data[u] == '-')
705             {
706                 numdash++;
707                 u++;
708             }
709             if (numdash >= 3)
710                 inCode = !inCode;
711             break;
712         default:
713             break;
714         }
715     }
716     if (par_open) // if any unmatched lparens
717     {
718         par_open = 0;
719         for (size_t u = buf.offset; u > start;)
720         {
721             u--;
722             char c = buf.data[u];
723             switch (c)
724             {
725             case ')':
726                 par_open++;
727                 break;
728             case '(':
729                 if (par_open == 0)
730                 {
731                     //stray '('
732                     warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output. Use $(LPAREN) instead for unpaired left parentheses.");
733                     buf.remove(u, 1); //remove the (
734                     buf.insert(u, "$(LPAREN)"); //insert this instead
735                 }
736                 else
737                     par_open--;
738                 break;
739             default:
740                 break;
741             }
742         }
743     }
744 }
745 
746 // Basically, this is to skip over things like private{} blocks in a struct or
747 // class definition that don't add any components to the qualified name.
748 extern (C++) static Scope* skipNonQualScopes(Scope* sc)
749 {
750     while (sc && !sc.scopesym)
751         sc = sc.enclosing;
752     return sc;
753 }
754 
755 extern (C++) static bool emitAnchorName(OutBuffer* buf, Dsymbol s, Scope* sc)
756 {
757     if (!s || s.isPackage() || s.isModule())
758         return false;
759     // Add parent names first
760     bool dot = false;
761     if (s.parent)
762         dot = emitAnchorName(buf, s.parent, sc);
763     else if (sc)
764         dot = emitAnchorName(buf, sc.scopesym, skipNonQualScopes(sc.enclosing));
765     // Eponymous template members can share the parent anchor name
766     if (getEponymousParent(s))
767         return dot;
768     if (dot)
769         buf.writeByte('.');
770     // Use "this" not "__ctor"
771     TemplateDeclaration td;
772     if (s.isCtorDeclaration() || ((td = s.isTemplateDeclaration()) !is null && td.onemember && td.onemember.isCtorDeclaration()))
773     {
774         buf.writestring("this");
775     }
776     else
777     {
778         /* We just want the identifier, not overloads like TemplateDeclaration::toChars.
779          * We don't want the template parameter list and constraints. */
780         buf.writestring(s.Dsymbol.toChars());
781     }
782     return true;
783 }
784 
785 extern (C++) static void emitAnchor(OutBuffer* buf, Dsymbol s, Scope* sc)
786 {
787     Identifier ident;
788     {
789         OutBuffer anc;
790         emitAnchorName(&anc, s, skipNonQualScopes(sc));
791         ident = Identifier.idPool(anc.peekSlice());
792     }
793 
794     auto pcount = cast(void*)ident in sc.anchorCounts;
795     typeof(*pcount) count;
796     if (pcount)
797     {
798         // Existing anchor,
799         // don't write an anchor for matching consecutive ditto symbols
800         TemplateDeclaration td = getEponymousParent(s);
801         if (sc.prevAnchor == ident && sc.lastdc && (isDitto(s.comment) || (td && isDitto(td.comment))))
802             return;
803 
804         count = ++*pcount;
805     }
806     else
807     {
808         sc.anchorCounts[cast(void*)ident] = 1;
809         count = 1;
810     }
811 
812     // cache anchor name
813     sc.prevAnchor = ident;
814     buf.writestring("$(DDOC_ANCHOR ");
815     buf.writestring(ident.toChars());
816     // only append count once there's a duplicate
817     if (count > 1)
818         buf.printf(".%u", count);
819     buf.writeByte(')');
820 }
821 
822 /******************************* emitComment **********************************/
823 
824 /** Get leading indentation from 'src' which represents lines of code. */
825 extern (C++) static size_t getCodeIndent(const(char)* src)
826 {
827     while (src && (*src == '\r' || *src == '\n'))
828         ++src; // skip until we find the first non-empty line
829     size_t codeIndent = 0;
830     while (src && (*src == ' ' || *src == '\t'))
831     {
832         codeIndent++;
833         src++;
834     }
835     return codeIndent;
836 }
837 
838 /** Recursively expand template mixin member docs into the scope. */
839 extern (C++) static void expandTemplateMixinComments(TemplateMixin tm, OutBuffer* buf, Scope* sc)
840 {
841     if (!tm.semanticRun)
842         tm.semantic(sc);
843     TemplateDeclaration td = (tm && tm.tempdecl) ? tm.tempdecl.isTemplateDeclaration() : null;
844     if (td && td.members)
845     {
846         for (size_t i = 0; i < td.members.dim; i++)
847         {
848             Dsymbol sm = (*td.members)[i];
849             TemplateMixin tmc = sm.isTemplateMixin();
850             if (tmc && tmc.comment)
851                 expandTemplateMixinComments(tmc, buf, sc);
852             else
853                 emitComment(sm, buf, sc);
854         }
855     }
856 }
857 
858 extern (C++) void emitMemberComments(ScopeDsymbol sds, OutBuffer* buf, Scope* sc)
859 {
860     if (!sds.members)
861         return;
862     //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars());
863     const(char)* m = "$(DDOC_MEMBERS ";
864     if (sds.isTemplateDeclaration())
865         m = "$(DDOC_TEMPLATE_MEMBERS ";
866     else if (sds.isClassDeclaration())
867         m = "$(DDOC_CLASS_MEMBERS ";
868     else if (sds.isStructDeclaration())
869         m = "$(DDOC_STRUCT_MEMBERS ";
870     else if (sds.isEnumDeclaration())
871         m = "$(DDOC_ENUM_MEMBERS ";
872     else if (sds.isModule())
873         m = "$(DDOC_MODULE_MEMBERS ";
874     size_t offset1 = buf.offset; // save starting offset
875     buf.writestring(m);
876     size_t offset2 = buf.offset; // to see if we write anything
877     sc = sc.push(sds);
878     for (size_t i = 0; i < sds.members.dim; i++)
879     {
880         Dsymbol s = (*sds.members)[i];
881         //printf("\ts = '%s'\n", s->toChars());
882         // only expand if parent is a non-template (semantic won't work)
883         if (s.comment && s.isTemplateMixin() && s.parent && !s.parent.isTemplateDeclaration())
884             expandTemplateMixinComments(cast(TemplateMixin)s, buf, sc);
885         emitComment(s, buf, sc);
886     }
887     emitComment(null, buf, sc);
888     sc.pop();
889     if (buf.offset == offset2)
890     {
891         /* Didn't write out any members, so back out last write
892          */
893         buf.offset = offset1;
894     }
895     else
896         buf.writestring(")\n");
897 }
898 
899 extern (C++) void emitProtection(OutBuffer* buf, Prot prot)
900 {
901     if (prot.kind != PROTundefined && prot.kind != PROTpublic)
902     {
903         protectionToBuffer(buf, prot);
904         buf.writeByte(' ');
905     }
906 }
907 
908 extern (C++) void emitComment(Dsymbol s, OutBuffer* buf, Scope* sc)
909 {
910     extern (C++) final class EmitComment : Visitor
911     {
912         alias visit = super.visit;
913     public:
914         OutBuffer* buf;
915         Scope* sc;
916 
917         extern (D) this(OutBuffer* buf, Scope* sc)
918         {
919             this.buf = buf;
920             this.sc = sc;
921         }
922 
923         override void visit(Dsymbol)
924         {
925         }
926 
927         override void visit(InvariantDeclaration)
928         {
929         }
930 
931         override void visit(UnitTestDeclaration)
932         {
933         }
934 
935         override void visit(PostBlitDeclaration)
936         {
937         }
938 
939         override void visit(DtorDeclaration)
940         {
941         }
942 
943         override void visit(StaticCtorDeclaration)
944         {
945         }
946 
947         override void visit(StaticDtorDeclaration)
948         {
949         }
950 
951         override void visit(TypeInfoDeclaration)
952         {
953         }
954 
955         void emit(Scope* sc, Dsymbol s, const(char)* com)
956         {
957             if (s && sc.lastdc && isDitto(com))
958             {
959                 sc.lastdc.a.push(s);
960                 return;
961             }
962             // Put previous doc comment if exists
963             if (DocComment* dc = sc.lastdc)
964             {
965                 // Put the declaration signatures as the document 'title'
966                 buf.writestring(ddoc_decl_s);
967                 for (size_t i = 0; i < dc.a.dim; i++)
968                 {
969                     Dsymbol sx = dc.a[i];
970                     // the added linebreaks in here make looking at multiple
971                     // signatures more appealing
972                     if (i == 0)
973                     {
974                         size_t o = buf.offset;
975                         toDocBuffer(sx, buf, sc);
976                         highlightCode(sc, sx, buf, o);
977                         buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
978                         continue;
979                     }
980                     buf.writestring("$(DDOC_DITTO ");
981                     {
982                         size_t o = buf.offset;
983                         toDocBuffer(sx, buf, sc);
984                         highlightCode(sc, sx, buf, o);
985                     }
986                     buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
987                     buf.writeByte(')');
988                 }
989                 buf.writestring(ddoc_decl_e);
990                 // Put the ddoc comment as the document 'description'
991                 buf.writestring(ddoc_decl_dd_s);
992                 {
993                     dc.writeSections(sc, &dc.a, buf);
994                     if (ScopeDsymbol sds = dc.a[0].isScopeDsymbol())
995                         emitMemberComments(sds, buf, sc);
996                 }
997                 buf.writestring(ddoc_decl_dd_e);
998                 //printf("buf.2 = [[%.*s]]\n", buf->offset - o0, buf->data + o0);
999             }
1000             if (s)
1001             {
1002                 DocComment* dc = DocComment.parse(sc, s, com);
1003                 dc.pmacrotable = &sc._module.macrotable;
1004                 sc.lastdc = dc;
1005             }
1006         }
1007 
1008         override void visit(Declaration d)
1009         {
1010             //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d.toChars(), d.comment);
1011             //printf("type = %p\n", d.type);
1012             const(char)* com = d.comment;
1013             if (TemplateDeclaration td = getEponymousParent(d))
1014             {
1015                 if (isDitto(td.comment))
1016                     com = td.comment;
1017                 else
1018                     com = Lexer.combineComments(td.comment, com);
1019             }
1020             else
1021             {
1022                 if (!d.ident)
1023                     return;
1024                 if (!d.type)
1025                 {
1026                     if (!d.isCtorDeclaration() &&
1027                         !d.isAliasDeclaration() &&
1028                         !d.isVarDeclaration())
1029                     {
1030                         return;
1031                     }
1032                 }
1033                 if (d.protection.kind == PROTprivate || sc.protection.kind == PROTprivate)
1034                     return;
1035             }
1036             if (!com)
1037                 return;
1038             emit(sc, d, com);
1039         }
1040 
1041         override void visit(AggregateDeclaration ad)
1042         {
1043             //printf("AggregateDeclaration::emitComment() '%s'\n", ad->toChars());
1044             const(char)* com = ad.comment;
1045             if (TemplateDeclaration td = getEponymousParent(ad))
1046             {
1047                 if (isDitto(td.comment))
1048                     com = td.comment;
1049                 else
1050                     com = Lexer.combineComments(td.comment, com);
1051             }
1052             else
1053             {
1054                 if (ad.prot().kind == PROTprivate || sc.protection.kind == PROTprivate)
1055                     return;
1056                 if (!ad.comment)
1057                     return;
1058             }
1059             if (!com)
1060                 return;
1061             emit(sc, ad, com);
1062         }
1063 
1064         override void visit(TemplateDeclaration td)
1065         {
1066             //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td->toChars(), td->kind());
1067             if (td.prot().kind == PROTprivate || sc.protection.kind == PROTprivate)
1068                 return;
1069             if (!td.comment)
1070                 return;
1071             if (Dsymbol ss = getEponymousMember(td))
1072             {
1073                 ss.accept(this);
1074                 return;
1075             }
1076             emit(sc, td, td.comment);
1077         }
1078 
1079         override void visit(EnumDeclaration ed)
1080         {
1081             if (ed.prot().kind == PROTprivate || sc.protection.kind == PROTprivate)
1082                 return;
1083             if (ed.isAnonymous() && ed.members)
1084             {
1085                 for (size_t i = 0; i < ed.members.dim; i++)
1086                 {
1087                     Dsymbol s = (*ed.members)[i];
1088                     emitComment(s, buf, sc);
1089                 }
1090                 return;
1091             }
1092             if (!ed.comment)
1093                 return;
1094             if (ed.isAnonymous())
1095                 return;
1096             emit(sc, ed, ed.comment);
1097         }
1098 
1099         override void visit(EnumMember em)
1100         {
1101             //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em->toChars(), em->comment);
1102             if (em.prot().kind == PROTprivate || sc.protection.kind == PROTprivate)
1103                 return;
1104             if (!em.comment)
1105                 return;
1106             emit(sc, em, em.comment);
1107         }
1108 
1109         override void visit(AttribDeclaration ad)
1110         {
1111             //printf("AttribDeclaration::emitComment(sc = %p)\n", sc);
1112             /* A general problem with this, illustrated by BUGZILLA 2516,
1113              * is that attributes are not transmitted through to the underlying
1114              * member declarations for template bodies, because semantic analysis
1115              * is not done for template declaration bodies
1116              * (only template instantiations).
1117              * Hence, Ddoc omits attributes from template members.
1118              */
1119             Dsymbols* d = ad.include(null, null);
1120             if (d)
1121             {
1122                 for (size_t i = 0; i < d.dim; i++)
1123                 {
1124                     Dsymbol s = (*d)[i];
1125                     //printf("AttribDeclaration::emitComment %s\n", s->toChars());
1126                     emitComment(s, buf, sc);
1127                 }
1128             }
1129         }
1130 
1131         override void visit(ProtDeclaration pd)
1132         {
1133             if (pd.decl)
1134             {
1135                 Scope* scx = sc;
1136                 sc = sc.copy();
1137                 sc.protection = pd.protection;
1138                 visit(cast(AttribDeclaration)pd);
1139                 scx.lastdc = sc.lastdc;
1140                 sc = sc.pop();
1141             }
1142         }
1143 
1144         override void visit(ConditionalDeclaration cd)
1145         {
1146             //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc);
1147             if (cd.condition.inc)
1148             {
1149                 visit(cast(AttribDeclaration)cd);
1150                 return;
1151             }
1152             /* If generating doc comment, be careful because if we're inside
1153              * a template, then include(NULL, NULL) will fail.
1154              */
1155             Dsymbols* d = cd.decl ? cd.decl : cd.elsedecl;
1156             for (size_t i = 0; i < d.dim; i++)
1157             {
1158                 Dsymbol s = (*d)[i];
1159                 emitComment(s, buf, sc);
1160             }
1161         }
1162     }
1163 
1164     scope EmitComment v = new EmitComment(buf, sc);
1165     if (!s)
1166         v.emit(sc, null, null);
1167     else
1168         s.accept(v);
1169 }
1170 
1171 extern (C++) void toDocBuffer(Dsymbol s, OutBuffer* buf, Scope* sc)
1172 {
1173     extern (C++) final class ToDocBuffer : Visitor
1174     {
1175         alias visit = super.visit;
1176     public:
1177         OutBuffer* buf;
1178         Scope* sc;
1179 
1180         extern (D) this(OutBuffer* buf, Scope* sc)
1181         {
1182             this.buf = buf;
1183             this.sc = sc;
1184         }
1185 
1186         override void visit(Dsymbol s)
1187         {
1188             //printf("Dsymbol::toDocbuffer() %s\n", s->toChars());
1189             HdrGenState hgs;
1190             hgs.ddoc = true;
1191             .toCBuffer(s, buf, &hgs);
1192         }
1193 
1194         void prefix(Dsymbol s)
1195         {
1196             if (s.isDeprecated())
1197                 buf.writestring("deprecated ");
1198             if (Declaration d = s.isDeclaration())
1199             {
1200                 emitProtection(buf, d.protection);
1201                 if (d.isStatic())
1202                     buf.writestring("static ");
1203                 else if (d.isFinal())
1204                     buf.writestring("final ");
1205                 else if (d.isAbstract())
1206                     buf.writestring("abstract ");
1207 
1208                 if (d.isFuncDeclaration())      // functionToBufferFull handles this
1209                     return;
1210 
1211                 if (d.isImmutable())
1212                     buf.writestring("immutable ");
1213                 if (d.storage_class & STCshared)
1214                     buf.writestring("shared ");
1215                 if (d.isWild())
1216                     buf.writestring("inout ");
1217                 if (d.isConst())
1218                     buf.writestring("const ");
1219 
1220                 if (d.isSynchronized())
1221                     buf.writestring("synchronized ");
1222 
1223                 if (d.storage_class & STCmanifest)
1224                     buf.writestring("enum ");
1225 
1226                 // Add "auto" for the untyped variable in template members
1227                 if (!d.type && d.isVarDeclaration() &&
1228                     !d.isImmutable() && !(d.storage_class & STCshared) && !d.isWild() && !d.isConst() &&
1229                     !d.isSynchronized())
1230                 {
1231                     buf.writestring("auto ");
1232                 }
1233             }
1234         }
1235 
1236         override void visit(Declaration d)
1237         {
1238             if (!d.ident)
1239                 return;
1240             TemplateDeclaration td = getEponymousParent(d);
1241             //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d->toChars(), d->originalType ? d->originalType->toChars() : "--", td ? td->toChars() : "--");
1242             HdrGenState hgs;
1243             hgs.ddoc = true;
1244             if (d.isDeprecated())
1245                 buf.writestring("$(DEPRECATED ");
1246             prefix(d);
1247             if (d.type)
1248             {
1249                 Type origType = d.originalType ? d.originalType : d.type;
1250                 if (origType.ty == Tfunction)
1251                 {
1252                     functionToBufferFull(cast(TypeFunction)origType, buf, d.ident, &hgs, td);
1253                 }
1254                 else
1255                     .toCBuffer(origType, buf, d.ident, &hgs);
1256             }
1257             else
1258                 buf.writestring(d.ident.toChars());
1259             if (d.isVarDeclaration() && td)
1260             {
1261                 buf.writeByte('(');
1262                 if (td.origParameters && td.origParameters.dim)
1263                 {
1264                     for (size_t i = 0; i < td.origParameters.dim; i++)
1265                     {
1266                         if (i)
1267                             buf.writestring(", ");
1268                         toCBuffer((*td.origParameters)[i], buf, &hgs);
1269                     }
1270                 }
1271                 buf.writeByte(')');
1272             }
1273             // emit constraints if declaration is a templated declaration
1274             if (td && td.constraint)
1275             {
1276                 bool noFuncDecl = td.isFuncDeclaration() is null;
1277                 if (noFuncDecl)
1278                 {
1279                     buf.writestring("$(DDOC_CONSTRAINT ");
1280                 }
1281 
1282                 .toCBuffer(td.constraint, buf, &hgs);
1283 
1284                 if (noFuncDecl)
1285                 {
1286                     buf.writestring(")");
1287                 }
1288             }
1289             if (d.isDeprecated())
1290                 buf.writestring(")");
1291             buf.writestring(";\n");
1292         }
1293 
1294         override void visit(AliasDeclaration ad)
1295         {
1296             //printf("AliasDeclaration::toDocbuffer() %s\n", ad->toChars());
1297             if (!ad.ident)
1298                 return;
1299             if (ad.isDeprecated())
1300                 buf.writestring("deprecated ");
1301             emitProtection(buf, ad.protection);
1302             buf.printf("alias %s = ", ad.toChars());
1303             if (Dsymbol s = ad.aliassym) // ident alias
1304             {
1305                 prettyPrintDsymbol(s, ad.parent);
1306             }
1307             else if (Type type = ad.getType()) // type alias
1308             {
1309                 if (type.ty == Tclass || type.ty == Tstruct || type.ty == Tenum)
1310                 {
1311                     if (Dsymbol s = type.toDsymbol(null)) // elaborate type
1312                         prettyPrintDsymbol(s, ad.parent);
1313                     else
1314                         buf.writestring(type.toChars());
1315                 }
1316                 else
1317                 {
1318                     // simple type
1319                     buf.writestring(type.toChars());
1320                 }
1321             }
1322             buf.writestring(";\n");
1323         }
1324 
1325         void parentToBuffer(Dsymbol s)
1326         {
1327             if (s && !s.isPackage() && !s.isModule())
1328             {
1329                 parentToBuffer(s.parent);
1330                 buf.writestring(s.toChars());
1331                 buf.writestring(".");
1332             }
1333         }
1334 
1335         static bool inSameModule(Dsymbol s, Dsymbol p)
1336         {
1337             for (; s; s = s.parent)
1338             {
1339                 if (s.isModule())
1340                     break;
1341             }
1342             for (; p; p = p.parent)
1343             {
1344                 if (p.isModule())
1345                     break;
1346             }
1347             return s == p;
1348         }
1349 
1350         void prettyPrintDsymbol(Dsymbol s, Dsymbol parent)
1351         {
1352             if (s.parent && (s.parent == parent)) // in current scope -> naked name
1353             {
1354                 buf.writestring(s.toChars());
1355             }
1356             else if (!inSameModule(s, parent)) // in another module -> full name
1357             {
1358                 buf.writestring(s.toPrettyChars());
1359             }
1360             else // nested in a type in this module -> full name w/o module name
1361             {
1362                 // if alias is nested in a user-type use module-scope lookup
1363                 if (!parent.isModule() && !parent.isPackage())
1364                     buf.writestring(".");
1365                 parentToBuffer(s.parent);
1366                 buf.writestring(s.toChars());
1367             }
1368         }
1369 
1370         override void visit(AggregateDeclaration ad)
1371         {
1372             if (!ad.ident)
1373                 return;
1374             version (none)
1375             {
1376                 emitProtection(buf, ad.protection);
1377             }
1378             buf.printf("%s %s", ad.kind(), ad.toChars());
1379             buf.writestring(";\n");
1380         }
1381 
1382         override void visit(StructDeclaration sd)
1383         {
1384             //printf("StructDeclaration::toDocbuffer() %s\n", sd->toChars());
1385             if (!sd.ident)
1386                 return;
1387             version (none)
1388             {
1389                 emitProtection(buf, sd.protection);
1390             }
1391             if (TemplateDeclaration td = getEponymousParent(sd))
1392             {
1393                 toDocBuffer(td, buf, sc);
1394             }
1395             else
1396             {
1397                 buf.printf("%s %s", sd.kind(), sd.toChars());
1398             }
1399             buf.writestring(";\n");
1400         }
1401 
1402         override void visit(ClassDeclaration cd)
1403         {
1404             //printf("ClassDeclaration::toDocbuffer() %s\n", cd->toChars());
1405             if (!cd.ident)
1406                 return;
1407             version (none)
1408             {
1409                 emitProtection(buf, cd.protection);
1410             }
1411             if (TemplateDeclaration td = getEponymousParent(cd))
1412             {
1413                 toDocBuffer(td, buf, sc);
1414             }
1415             else
1416             {
1417                 if (!cd.isInterfaceDeclaration() && cd.isAbstract())
1418                     buf.writestring("abstract ");
1419                 buf.printf("%s %s", cd.kind(), cd.toChars());
1420             }
1421             int any = 0;
1422             for (size_t i = 0; i < cd.baseclasses.dim; i++)
1423             {
1424                 BaseClass* bc = (*cd.baseclasses)[i];
1425                 if (bc.sym && bc.sym.ident == Id.Object)
1426                     continue;
1427                 if (any)
1428                     buf.writestring(", ");
1429                 else
1430                 {
1431                     buf.writestring(": ");
1432                     any = 1;
1433                 }
1434                 emitProtection(buf, Prot(PROTpublic));
1435                 if (bc.sym)
1436                 {
1437                     buf.printf("$(DDOC_PSUPER_SYMBOL %s)", bc.sym.toPrettyChars());
1438                 }
1439                 else
1440                 {
1441                     HdrGenState hgs;
1442                     .toCBuffer(bc.type, buf, null, &hgs);
1443                 }
1444             }
1445             buf.writestring(";\n");
1446         }
1447 
1448         override void visit(EnumDeclaration ed)
1449         {
1450             if (!ed.ident)
1451                 return;
1452             buf.printf("%s %s", ed.kind(), ed.toChars());
1453             if (ed.memtype)
1454             {
1455                 buf.writestring(": $(DDOC_ENUM_BASETYPE ");
1456                 HdrGenState hgs;
1457                 .toCBuffer(ed.memtype, buf, null, &hgs);
1458                 buf.writestring(")");
1459             }
1460             buf.writestring(";\n");
1461         }
1462 
1463         override void visit(EnumMember em)
1464         {
1465             if (!em.ident)
1466                 return;
1467             buf.writestring(em.toChars());
1468         }
1469     }
1470 
1471     scope ToDocBuffer v = new ToDocBuffer(buf, sc);
1472     s.accept(v);
1473 }
1474 
1475 /***********************************************************
1476  */
1477 struct DocComment
1478 {
1479     Sections sections;      // Section*[]
1480     Section summary;
1481     Section copyright;
1482     Section macros;
1483     Macro** pmacrotable;
1484     Escape** pescapetable;
1485     Dsymbols a;
1486 
1487     extern (C++) static DocComment* parse(Scope* sc, Dsymbol s, const(char)* comment)
1488     {
1489         //printf("parse(%s): '%s'\n", s->toChars(), comment);
1490         auto dc = new DocComment();
1491         dc.a.push(s);
1492         if (!comment)
1493             return dc;
1494         dc.parseSections(comment);
1495         for (size_t i = 0; i < dc.sections.dim; i++)
1496         {
1497             Section sec = dc.sections[i];
1498             if (icmp("copyright", sec.name, sec.namelen) == 0)
1499             {
1500                 dc.copyright = sec;
1501             }
1502             if (icmp("macros", sec.name, sec.namelen) == 0)
1503             {
1504                 dc.macros = sec;
1505             }
1506         }
1507         return dc;
1508     }
1509 
1510     /************************************************
1511      * Parse macros out of Macros: section.
1512      * Macros are of the form:
1513      *      name1 = value1
1514      *
1515      *      name2 = value2
1516      */
1517     extern (C++) static void parseMacros(Escape** pescapetable, Macro** pmacrotable, const(char)* m, size_t mlen)
1518     {
1519         const(char)* p = m;
1520         size_t len = mlen;
1521         const(char)* pend = p + len;
1522         const(char)* tempstart = null;
1523         size_t templen = 0;
1524         const(char)* namestart = null;
1525         size_t namelen = 0; // !=0 if line continuation
1526         const(char)* textstart = null;
1527         size_t textlen = 0;
1528         while (p < pend)
1529         {
1530             // Skip to start of macro
1531             while (1)
1532             {
1533                 if (p >= pend)
1534                     goto Ldone;
1535                 switch (*p)
1536                 {
1537                 case ' ':
1538                 case '\t':
1539                     p++;
1540                     continue;
1541                 case '\r':
1542                 case '\n':
1543                     p++;
1544                     goto Lcont;
1545                 default:
1546                     if (isIdStart(p))
1547                         break;
1548                     if (namelen)
1549                         goto Ltext; // continuation of prev macro
1550                     goto Lskipline;
1551                 }
1552                 break;
1553             }
1554             tempstart = p;
1555             while (1)
1556             {
1557                 if (p >= pend)
1558                     goto Ldone;
1559                 if (!isIdTail(p))
1560                     break;
1561                 p += utfStride(p);
1562             }
1563             templen = p - tempstart;
1564             while (1)
1565             {
1566                 if (p >= pend)
1567                     goto Ldone;
1568                 if (!(*p == ' ' || *p == '\t'))
1569                     break;
1570                 p++;
1571             }
1572             if (*p != '=')
1573             {
1574                 if (namelen)
1575                     goto Ltext; // continuation of prev macro
1576                 goto Lskipline;
1577             }
1578             p++;
1579             if (p >= pend)
1580                 goto Ldone;
1581             if (namelen)
1582             {
1583                 // Output existing macro
1584             L1:
1585                 //printf("macro '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart);
1586                 if (icmp("ESCAPES", namestart, namelen) == 0)
1587                     parseEscapes(pescapetable, textstart, textlen);
1588                 else
1589                     Macro.define(pmacrotable, namestart[0 ..namelen], textstart[0 .. textlen]);
1590                 namelen = 0;
1591                 if (p >= pend)
1592                     break;
1593             }
1594             namestart = tempstart;
1595             namelen = templen;
1596             while (p < pend && (*p == ' ' || *p == '\t'))
1597                 p++;
1598             textstart = p;
1599         Ltext:
1600             while (p < pend && *p != '\r' && *p != '\n')
1601                 p++;
1602             textlen = p - textstart;
1603             p++;
1604             //printf("p = %p, pend = %p\n", p, pend);
1605         Lcont:
1606             continue;
1607         Lskipline:
1608             // Ignore this line
1609             while (p < pend && *p != '\r' && *p != '\n')
1610                 p++;
1611         }
1612     Ldone:
1613         if (namelen)
1614             goto L1; // write out last one
1615     }
1616 
1617     /**************************************
1618      * Parse escapes of the form:
1619      *      /c/string/
1620      * where c is a single character.
1621      * Multiple escapes can be separated
1622      * by whitespace and/or commas.
1623      */
1624     extern (C++) static void parseEscapes(Escape** pescapetable, const(char)* textstart, size_t textlen)
1625     {
1626         Escape* escapetable = *pescapetable;
1627         if (!escapetable)
1628         {
1629             escapetable = new Escape();
1630             memset(escapetable, 0, Escape.sizeof);
1631             *pescapetable = escapetable;
1632         }
1633         //printf("parseEscapes('%.*s') pescapetable = %p\n", textlen, textstart, pescapetable);
1634         const(char)* p = textstart;
1635         const(char)* pend = p + textlen;
1636         while (1)
1637         {
1638             while (1)
1639             {
1640                 if (p + 4 >= pend)
1641                     return;
1642                 if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ','))
1643                     break;
1644                 p++;
1645             }
1646             if (p[0] != '/' || p[2] != '/')
1647                 return;
1648             char c = p[1];
1649             p += 3;
1650             const(char)* start = p;
1651             while (1)
1652             {
1653                 if (p >= pend)
1654                     return;
1655                 if (*p == '/')
1656                     break;
1657                 p++;
1658             }
1659             size_t len = p - start;
1660             char* s = cast(char*)memcpy(mem.xmalloc(len + 1), start, len);
1661             s[len] = 0;
1662             escapetable.strings[c] = s;
1663             //printf("\t%c = '%s'\n", c, s);
1664             p++;
1665         }
1666     }
1667 
1668     /*****************************************
1669      * Parse next paragraph out of *pcomment.
1670      * Update *pcomment to point past paragraph.
1671      * Returns NULL if no more paragraphs.
1672      * If paragraph ends in 'identifier:',
1673      * then (*pcomment)[0 .. idlen] is the identifier.
1674      */
1675     extern (C++) void parseSections(const(char)* comment)
1676     {
1677         const(char)* p;
1678         const(char)* pstart;
1679         const(char)* pend;
1680         const(char)* idstart = null; // dead-store to prevent spurious warning
1681         size_t idlen;
1682         const(char)* name = null;
1683         size_t namelen = 0;
1684         //printf("parseSections('%s')\n", comment);
1685         p = comment;
1686         while (*p)
1687         {
1688             const(char)* pstart0 = p;
1689             p = skipwhitespace(p);
1690             pstart = p;
1691             pend = p;
1692             /* Find end of section, which is ended by one of:
1693              *      'identifier:' (but not inside a code section)
1694              *      '\0'
1695              */
1696             idlen = 0;
1697             int inCode = 0;
1698             while (1)
1699             {
1700                 // Check for start/end of a code section
1701                 if (*p == '-')
1702                 {
1703                     if (!inCode)
1704                     {
1705                         // restore leading indentation
1706                         while (pstart0 < pstart && isIndentWS(pstart - 1))
1707                             --pstart;
1708                     }
1709                     int numdash = 0;
1710                     while (*p == '-')
1711                     {
1712                         ++numdash;
1713                         p++;
1714                     }
1715                     // BUG: handle UTF PS and LS too
1716                     if ((!*p || *p == '\r' || *p == '\n') && numdash >= 3)
1717                         inCode ^= 1;
1718                     pend = p;
1719                 }
1720                 if (!inCode && isIdStart(p))
1721                 {
1722                     const(char)* q = p + utfStride(p);
1723                     while (isIdTail(q))
1724                         q += utfStride(q);
1725                     if (*q == ':') // identifier: ends it
1726                     {
1727                         idlen = q - p;
1728                         idstart = p;
1729                         for (pend = p; pend > pstart; pend--)
1730                         {
1731                             if (pend[-1] == '\n')
1732                                 break;
1733                         }
1734                         p = q + 1;
1735                         break;
1736                     }
1737                 }
1738                 while (1)
1739                 {
1740                     if (!*p)
1741                         goto L1;
1742                     if (*p == '\n')
1743                     {
1744                         p++;
1745                         if (*p == '\n' && !summary && !namelen && !inCode)
1746                         {
1747                             pend = p;
1748                             p++;
1749                             goto L1;
1750                         }
1751                         break;
1752                     }
1753                     p++;
1754                     pend = p;
1755                 }
1756                 p = skipwhitespace(p);
1757             }
1758         L1:
1759             if (namelen || pstart < pend)
1760             {
1761                 Section s;
1762                 if (icmp("Params", name, namelen) == 0)
1763                     s = new ParamSection();
1764                 else if (icmp("Macros", name, namelen) == 0)
1765                     s = new MacroSection();
1766                 else
1767                     s = new Section();
1768                 s.name = name;
1769                 s.namelen = namelen;
1770                 s._body = pstart;
1771                 s.bodylen = pend - pstart;
1772                 s.nooutput = 0;
1773                 //printf("Section: '%.*s' = '%.*s'\n", s->namelen, s->name, s->bodylen, s->body);
1774                 sections.push(s);
1775                 if (!summary && !namelen)
1776                     summary = s;
1777             }
1778             if (idlen)
1779             {
1780                 name = idstart;
1781                 namelen = idlen;
1782             }
1783             else
1784             {
1785                 name = null;
1786                 namelen = 0;
1787                 if (!*p)
1788                     break;
1789             }
1790         }
1791     }
1792 
1793     extern (C++) void writeSections(Scope* sc, Dsymbols* a, OutBuffer* buf)
1794     {
1795         assert(a.dim);
1796         //printf("DocComment::writeSections()\n");
1797         Loc loc = (*a)[0].loc;
1798         if (Module m = (*a)[0].isModule())
1799         {
1800             if (m.md)
1801                 loc = m.md.loc;
1802         }
1803         size_t offset1 = buf.offset;
1804         buf.writestring("$(DDOC_SECTIONS ");
1805         size_t offset2 = buf.offset;
1806         for (size_t i = 0; i < sections.dim; i++)
1807         {
1808             Section sec = sections[i];
1809             if (sec.nooutput)
1810                 continue;
1811             //printf("Section: '%.*s' = '%.*s'\n", sec->namelen, sec->name, sec->bodylen, sec->body);
1812             if (!sec.namelen && i == 0)
1813             {
1814                 buf.writestring("$(DDOC_SUMMARY ");
1815                 size_t o = buf.offset;
1816                 buf.write(sec._body, sec.bodylen);
1817                 escapeStrayParenthesis(loc, buf, o);
1818                 highlightText(sc, a, buf, o);
1819                 buf.writestring(")\n");
1820             }
1821             else
1822                 sec.write(loc, &this, sc, a, buf);
1823         }
1824         for (size_t i = 0; i < a.dim; i++)
1825         {
1826             Dsymbol s = (*a)[i];
1827             if (Dsymbol td = getEponymousParent(s))
1828                 s = td;
1829             for (UnitTestDeclaration utd = s.ddocUnittest; utd; utd = utd.ddocUnittest)
1830             {
1831                 if (utd.protection.kind == PROTprivate || !utd.comment || !utd.fbody)
1832                     continue;
1833                 // Strip whitespaces to avoid showing empty summary
1834                 const(char)* c = utd.comment;
1835                 while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')
1836                     ++c;
1837                 buf.writestring("$(DDOC_EXAMPLES ");
1838                 size_t o = buf.offset;
1839                 buf.writestring(cast(char*)c);
1840                 if (utd.codedoc)
1841                 {
1842                     size_t n = getCodeIndent(utd.codedoc);
1843                     while (n--)
1844                         buf.writeByte(' ');
1845                     buf.writestring("----\n");
1846                     buf.writestring(utd.codedoc);
1847                     buf.writestring("----\n");
1848                     highlightText(sc, a, buf, o);
1849                 }
1850                 buf.writestring(")");
1851             }
1852         }
1853         if (buf.offset == offset2)
1854         {
1855             /* Didn't write out any sections, so back out last write
1856              */
1857             buf.offset = offset1;
1858             buf.writestring("$(DDOC_BLANKLINE)\n");
1859         }
1860         else
1861             buf.writestring(")\n");
1862     }
1863 }
1864 
1865 /******************************************
1866  * Compare 0-terminated string with length terminated string.
1867  * Return < 0, ==0, > 0
1868  */
1869 extern (C++) int cmp(const(char)* stringz, const(void)* s, size_t slen)
1870 {
1871     size_t len1 = strlen(stringz);
1872     if (len1 != slen)
1873         return cast(int)(len1 - slen);
1874     return memcmp(stringz, s, slen);
1875 }
1876 
1877 extern (C++) int icmp(const(char)* stringz, const(void)* s, size_t slen)
1878 {
1879     size_t len1 = strlen(stringz);
1880     if (len1 != slen)
1881         return cast(int)(len1 - slen);
1882     return Port.memicmp(stringz, cast(char*)s, slen);
1883 }
1884 
1885 /*****************************************
1886  * Return true if comment consists entirely of "ditto".
1887  */
1888 extern (C++) bool isDitto(const(char)* comment)
1889 {
1890     if (comment)
1891     {
1892         const(char)* p = skipwhitespace(comment);
1893         if (Port.memicmp(p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
1894             return true;
1895     }
1896     return false;
1897 }
1898 
1899 /**********************************************
1900  * Skip white space.
1901  */
1902 extern (C++) const(char)* skipwhitespace(const(char)* p)
1903 {
1904     for (; 1; p++)
1905     {
1906         switch (*p)
1907         {
1908         case ' ':
1909         case '\t':
1910         case '\n':
1911             continue;
1912         default:
1913             break;
1914         }
1915         break;
1916     }
1917     return p;
1918 }
1919 
1920 /************************************************
1921  * Scan forward to one of:
1922  *      start of identifier
1923  *      beginning of next line
1924  *      end of buf
1925  */
1926 extern (C++) size_t skiptoident(OutBuffer* buf, size_t i)
1927 {
1928     const slice = buf.peekSlice();
1929     while (i < slice.length)
1930     {
1931         dchar c;
1932         size_t oi = i;
1933         if (utf_decodeChar(slice.ptr, slice.length, i, c))
1934         {
1935             /* Ignore UTF errors, but still consume input
1936              */
1937             break;
1938         }
1939         if (c >= 0x80)
1940         {
1941             if (!isUniAlpha(c))
1942                 continue;
1943         }
1944         else if (!(isalpha(c) || c == '_' || c == '\n'))
1945             continue;
1946         i = oi;
1947         break;
1948     }
1949     return i;
1950 }
1951 
1952 /************************************************
1953  * Scan forward past end of identifier.
1954  */
1955 extern (C++) size_t skippastident(OutBuffer* buf, size_t i)
1956 {
1957     const slice = buf.peekSlice();
1958     while (i < slice.length)
1959     {
1960         dchar c;
1961         size_t oi = i;
1962         if (utf_decodeChar(slice.ptr, slice.length, i, c))
1963         {
1964             /* Ignore UTF errors, but still consume input
1965              */
1966             break;
1967         }
1968         if (c >= 0x80)
1969         {
1970             if (isUniAlpha(c))
1971                 continue;
1972         }
1973         else if (isalnum(c) || c == '_')
1974             continue;
1975         i = oi;
1976         break;
1977     }
1978     return i;
1979 }
1980 
1981 /************************************************
1982  * Scan forward past URL starting at i.
1983  * We don't want to highlight parts of a URL.
1984  * Returns:
1985  *      i if not a URL
1986  *      index just past it if it is a URL
1987  */
1988 extern (C++) size_t skippastURL(OutBuffer* buf, size_t i)
1989 {
1990     const slice = buf.peekSlice()[i .. $];
1991     size_t j;
1992     bool sawdot = false;
1993     if (slice.length > 7 && Port.memicmp(slice.ptr, "http://", 7) == 0)
1994     {
1995         j = 7;
1996     }
1997     else if (slice.length > 8 && Port.memicmp(slice.ptr, "https://", 8) == 0)
1998     {
1999         j = 8;
2000     }
2001     else
2002         goto Lno;
2003     for (; j < slice.length; j++)
2004     {
2005         const c = slice[j];
2006         if (isalnum(c))
2007             continue;
2008         if (c == '-' || c == '_' || c == '?' || c == '=' || c == '%' ||
2009             c == '&' || c == '/' || c == '+' || c == '#' || c == '~')
2010             continue;
2011         if (c == '.')
2012         {
2013             sawdot = true;
2014             continue;
2015         }
2016         break;
2017     }
2018     if (sawdot)
2019         return i + j;
2020 Lno:
2021     return i;
2022 }
2023 
2024 /****************************************************
2025  */
2026 extern (C++) bool isIdentifier(Dsymbols* a, const(char)* p, size_t len)
2027 {
2028     for (size_t i = 0; i < a.dim; i++)
2029     {
2030         const(char)* s = (*a)[i].ident.toChars();
2031         if (cmp(s, p, len) == 0)
2032             return true;
2033     }
2034     return false;
2035 }
2036 
2037 /****************************************************
2038  */
2039 extern (C++) bool isKeyword(const(char)* p, size_t len)
2040 {
2041     immutable string[3] table = ["true", "false", "null"];
2042     foreach (s; table)
2043     {
2044         if (cmp(s.ptr, p, len) == 0)
2045             return true;
2046     }
2047     return false;
2048 }
2049 
2050 /****************************************************
2051  */
2052 extern (C++) TypeFunction isTypeFunction(Dsymbol s)
2053 {
2054     FuncDeclaration f = s.isFuncDeclaration();
2055     /* f->type may be NULL for template members.
2056      */
2057     if (f && f.type)
2058     {
2059         Type t = f.originalType ? f.originalType : f.type;
2060         if (t.ty == Tfunction)
2061             return cast(TypeFunction)t;
2062     }
2063     return null;
2064 }
2065 
2066 /****************************************************
2067  */
2068 private Parameter isFunctionParameter(Dsymbol s, const(char)* p, size_t len)
2069 {
2070     TypeFunction tf = isTypeFunction(s);
2071     if (tf && tf.parameters)
2072     {
2073         for (size_t k = 0; k < tf.parameters.dim; k++)
2074         {
2075             Parameter fparam = (*tf.parameters)[k];
2076             if (fparam.ident && cmp(fparam.ident.toChars(), p, len) == 0)
2077             {
2078                 return fparam;
2079             }
2080         }
2081     }
2082     return null;
2083 }
2084 
2085 /****************************************************
2086  */
2087 extern (C++) Parameter isFunctionParameter(Dsymbols* a, const(char)* p, size_t len)
2088 {
2089     for (size_t i = 0; i < a.dim; i++)
2090     {
2091         Parameter fparam = isFunctionParameter((*a)[i], p, len);
2092         if (fparam)
2093         {
2094             return fparam;
2095         }
2096     }
2097     return null;
2098 }
2099 
2100 /****************************************************
2101  */
2102 private Parameter isEponymousFunctionParameter(Dsymbols *a, const(char) *p, size_t len)
2103 {
2104     for (size_t i = 0; i < a.dim; i++)
2105     {
2106         TemplateDeclaration td = (*a)[i].isTemplateDeclaration();
2107         if (td && td.onemember)
2108         {
2109             /* Case 1: we refer to a template declaration inside the template
2110 
2111                /// ...ddoc...
2112                template case1(T) {
2113                  void case1(R)() {}
2114                }
2115              */
2116             td = td.onemember.isTemplateDeclaration();
2117         }
2118         if (!td)
2119         {
2120             /* Case 2: we're an alias to a template declaration
2121 
2122                /// ...ddoc...
2123                alias case2 = case1!int;
2124              */
2125             AliasDeclaration ad = (*a)[i].isAliasDeclaration();
2126             if (ad && ad.aliassym)
2127             {
2128                 td = ad.aliassym.isTemplateDeclaration();
2129             }
2130         }
2131         while (td)
2132         {
2133             Dsymbol sym = getEponymousMember(td);
2134             if (sym)
2135             {
2136                 Parameter fparam = isFunctionParameter(sym, p, len);
2137                 if (fparam)
2138                 {
2139                     return fparam;
2140                 }
2141             }
2142             td = td.overnext;
2143         }
2144     }
2145     return null;
2146 }
2147 
2148 /****************************************************
2149  */
2150 extern (C++) TemplateParameter isTemplateParameter(Dsymbols* a, const(char)* p, size_t len)
2151 {
2152     for (size_t i = 0; i < a.dim; i++)
2153     {
2154         TemplateDeclaration td = (*a)[i].isTemplateDeclaration();
2155         // Check for the parent, if the current symbol is not a template declaration.
2156         if (!td)
2157             td = getEponymousParent((*a)[i]);
2158         if (td && td.origParameters)
2159         {
2160             for (size_t k = 0; k < td.origParameters.dim; k++)
2161             {
2162                 TemplateParameter tp = (*td.origParameters)[k];
2163                 if (tp.ident && cmp(tp.ident.toChars(), p, len) == 0)
2164                 {
2165                     return tp;
2166                 }
2167             }
2168         }
2169     }
2170     return null;
2171 }
2172 
2173 /****************************************************
2174  * Return true if str is a reserved symbol name
2175  * that starts with a double underscore.
2176  */
2177 extern (C++) bool isReservedName(const(char)* str, size_t len)
2178 {
2179     immutable string[] table =
2180     [
2181         "__ctor",
2182         "__dtor",
2183         "__postblit",
2184         "__invariant",
2185         "__unitTest",
2186         "__require",
2187         "__ensure",
2188         "__dollar",
2189         "__ctfe",
2190         "__withSym",
2191         "__result",
2192         "__returnLabel",
2193         "__vptr",
2194         "__monitor",
2195         "__gate",
2196         "__xopEquals",
2197         "__xopCmp",
2198         "__LINE__",
2199         "__FILE__",
2200         "__MODULE__",
2201         "__FUNCTION__",
2202         "__PRETTY_FUNCTION__",
2203         "__DATE__",
2204         "__TIME__",
2205         "__TIMESTAMP__",
2206         "__VENDOR__",
2207         "__VERSION__",
2208         "__EOF__",
2209         "__LOCAL_SIZE",
2210         "___tls_get_addr",
2211         "__entrypoint",
2212     ];
2213     foreach (s; table)
2214     {
2215         if (cmp(s.ptr, str, len) == 0)
2216             return true;
2217     }
2218     return false;
2219 }
2220 
2221 /**************************************************
2222  * Highlight text section.
2223  */
2224 extern (C++) void highlightText(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset)
2225 {
2226     Dsymbol s = a.dim ? (*a)[0] : null; // test
2227     //printf("highlightText()\n");
2228     int leadingBlank = 1;
2229     int inCode = 0;
2230     int inBacktick = 0;
2231     //int inComment = 0;                  // in <!-- ... --> comment
2232     size_t iCodeStart = 0; // start of code section
2233     size_t codeIndent = 0;
2234     size_t iLineStart = offset;
2235     for (size_t i = offset; i < buf.offset; i++)
2236     {
2237         char c = buf.data[i];
2238     Lcont:
2239         switch (c)
2240         {
2241         case ' ':
2242         case '\t':
2243             break;
2244         case '\n':
2245             if (inBacktick)
2246             {
2247                 // `inline code` is only valid if contained on a single line
2248                 // otherwise, the backticks should be output literally.
2249                 //
2250                 // This lets things like `output from the linker' display
2251                 // unmolested while keeping the feature consistent with GitHub.
2252                 inBacktick = false;
2253                 inCode = false; // the backtick also assumes we're in code
2254                 // Nothing else is necessary since the DDOC_BACKQUOTED macro is
2255                 // inserted lazily at the close quote, meaning the rest of the
2256                 // text is already OK.
2257             }
2258             if (!sc._module.isDocFile && !inCode && i == iLineStart && i + 1 < buf.offset) // if "\n\n"
2259             {
2260                 immutable blankline = "$(DDOC_BLANKLINE)\n";
2261                 i = buf.insert(i, blankline);
2262             }
2263             leadingBlank = 1;
2264             iLineStart = i + 1;
2265             break;
2266         case '<':
2267             {
2268                 leadingBlank = 0;
2269                 if (inCode)
2270                     break;
2271                 const slice = buf.peekSlice();
2272                 auto p = &slice[i];
2273                 const se = sc._module.escapetable.escapeChar('<');
2274                 if (se && strcmp(se, "&lt;") == 0)
2275                 {
2276                     // Generating HTML
2277                     // Skip over comments
2278                     if (p[1] == '!' && p[2] == '-' && p[3] == '-')
2279                     {
2280                         size_t j = i + 4;
2281                         p += 4;
2282                         while (1)
2283                         {
2284                             if (j == slice.length)
2285                                 goto L1;
2286                             if (p[0] == '-' && p[1] == '-' && p[2] == '>')
2287                             {
2288                                 i = j + 2; // place on closing '>'
2289                                 break;
2290                             }
2291                             j++;
2292                             p++;
2293                         }
2294                         break;
2295                     }
2296                     // Skip over HTML tag
2297                     if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2])))
2298                     {
2299                         size_t j = i + 2;
2300                         p += 2;
2301                         while (1)
2302                         {
2303                             if (j == slice.length)
2304                                 break;
2305                             if (p[0] == '>')
2306                             {
2307                                 i = j; // place on closing '>'
2308                                 break;
2309                             }
2310                             j++;
2311                             p++;
2312                         }
2313                         break;
2314                     }
2315                 }
2316             L1:
2317                 // Replace '<' with '&lt;' character entity
2318                 if (se)
2319                 {
2320                     const len = strlen(se);
2321                     buf.remove(i, 1);
2322                     i = buf.insert(i, se, len);
2323                     i--; // point to ';'
2324                 }
2325                 break;
2326             }
2327         case '>':
2328             {
2329                 leadingBlank = 0;
2330                 if (inCode)
2331                     break;
2332                 // Replace '>' with '&gt;' character entity
2333                 const(char)* se = sc._module.escapetable.escapeChar('>');
2334                 if (se)
2335                 {
2336                     size_t len = strlen(se);
2337                     buf.remove(i, 1);
2338                     i = buf.insert(i, se, len);
2339                     i--; // point to ';'
2340                 }
2341                 break;
2342             }
2343         case '&':
2344             {
2345                 leadingBlank = 0;
2346                 if (inCode)
2347                     break;
2348                 char* p = cast(char*)&buf.data[i];
2349                 if (p[1] == '#' || isalpha(p[1]))
2350                     break;
2351                 // already a character entity
2352                 // Replace '&' with '&amp;' character entity
2353                 const(char)* se = sc._module.escapetable.escapeChar('&');
2354                 if (se)
2355                 {
2356                     size_t len = strlen(se);
2357                     buf.remove(i, 1);
2358                     i = buf.insert(i, se, len);
2359                     i--; // point to ';'
2360                 }
2361                 break;
2362             }
2363         case '`':
2364             {
2365                 if (inBacktick)
2366                 {
2367                     inBacktick = 0;
2368                     inCode = 0;
2369                     OutBuffer codebuf;
2370                     codebuf.write(buf.peekSlice().ptr + iCodeStart + 1, i - (iCodeStart + 1));
2371                     // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL
2372                     highlightCode(sc, a, &codebuf, 0);
2373                     buf.remove(iCodeStart, i - iCodeStart + 1); // also trimming off the current `
2374                     immutable pre = "$(DDOC_BACKQUOTED ";
2375                     i = buf.insert(iCodeStart, pre);
2376                     i = buf.insert(i, codebuf.peekSlice());
2377                     i = buf.insert(i, ")");
2378                     i--; // point to the ending ) so when the for loop does i++, it will see the next character
2379                     break;
2380                 }
2381                 if (inCode)
2382                     break;
2383                 inCode = 1;
2384                 inBacktick = 1;
2385                 codeIndent = 0; // inline code is not indented
2386                 // All we do here is set the code flags and record
2387                 // the location. The macro will be inserted lazily
2388                 // so we can easily cancel the inBacktick if we come
2389                 // across a newline character.
2390                 iCodeStart = i;
2391                 break;
2392             }
2393         case '-':
2394             /* A line beginning with --- delimits a code section.
2395              * inCode tells us if it is start or end of a code section.
2396              */
2397             if (leadingBlank)
2398             {
2399                 size_t istart = i;
2400                 size_t eollen = 0;
2401                 leadingBlank = 0;
2402                 while (1)
2403                 {
2404                     ++i;
2405                     if (i >= buf.offset)
2406                         break;
2407                     c = buf.data[i];
2408                     if (c == '\n')
2409                     {
2410                         eollen = 1;
2411                         break;
2412                     }
2413                     if (c == '\r')
2414                     {
2415                         eollen = 1;
2416                         if (i + 1 >= buf.offset)
2417                             break;
2418                         if (buf.data[i + 1] == '\n')
2419                         {
2420                             eollen = 2;
2421                             break;
2422                         }
2423                     }
2424                     // BUG: handle UTF PS and LS too
2425                     if (c != '-')
2426                         goto Lcont;
2427                 }
2428                 if (i - istart < 3)
2429                     goto Lcont;
2430                 // We have the start/end of a code section
2431                 // Remove the entire --- line, including blanks and \n
2432                 buf.remove(iLineStart, i - iLineStart + eollen);
2433                 i = iLineStart;
2434                 if (inCode && (i <= iCodeStart))
2435                 {
2436                     // Empty code section, just remove it completely.
2437                     inCode = 0;
2438                     break;
2439                 }
2440                 if (inCode)
2441                 {
2442                     inCode = 0;
2443                     // The code section is from iCodeStart to i
2444                     OutBuffer codebuf;
2445                     codebuf.write(buf.data + iCodeStart, i - iCodeStart);
2446                     codebuf.writeByte(0);
2447                     // Remove leading indentations from all lines
2448                     bool lineStart = true;
2449                     char* endp = cast(char*)codebuf.data + codebuf.offset;
2450                     for (char* p = cast(char*)codebuf.data; p < endp;)
2451                     {
2452                         if (lineStart)
2453                         {
2454                             size_t j = codeIndent;
2455                             char* q = p;
2456                             while (j-- > 0 && q < endp && isIndentWS(q))
2457                                 ++q;
2458                             codebuf.remove(p - cast(char*)codebuf.data, q - p);
2459                             assert(cast(char*)codebuf.data <= p);
2460                             assert(p < cast(char*)codebuf.data + codebuf.offset);
2461                             lineStart = false;
2462                             endp = cast(char*)codebuf.data + codebuf.offset; // update
2463                             continue;
2464                         }
2465                         if (*p == '\n')
2466                             lineStart = true;
2467                         ++p;
2468                     }
2469                     highlightCode2(sc, a, &codebuf, 0);
2470                     buf.remove(iCodeStart, i - iCodeStart);
2471                     i = buf.insert(iCodeStart, codebuf.peekSlice());
2472                     i = buf.insert(i, ")\n");
2473                     i -= 2; // in next loop, c should be '\n'
2474                 }
2475                 else
2476                 {
2477                     static __gshared const(char)* d_code = "$(D_CODE ";
2478                     inCode = 1;
2479                     codeIndent = istart - iLineStart; // save indent count
2480                     i = buf.insert(i, d_code, strlen(d_code));
2481                     iCodeStart = i;
2482                     i--; // place i on >
2483                     leadingBlank = true;
2484                 }
2485             }
2486             break;
2487         default:
2488             leadingBlank = 0;
2489             if (sc._module.isDocFile || inCode)
2490                 break;
2491             const start = cast(char*)buf.data + i;
2492             if (isIdStart(start))
2493             {
2494                 size_t j = skippastident(buf, i);
2495                 if (i < j)
2496                 {
2497                     size_t k = skippastURL(buf, i);
2498                     if (i < k)
2499                     {
2500                         i = k - 1;
2501                         break;
2502                     }
2503                 }
2504                 else
2505                     break;
2506                 size_t len = j - i;
2507                 // leading '_' means no highlight unless it's a reserved symbol name
2508                 if (c == '_' && (i == 0 || !isdigit(*(start - 1))) && (i == buf.offset - 1 || !isReservedName(start, len)))
2509                 {
2510                     buf.remove(i, 1);
2511                     i = j - 1;
2512                     break;
2513                 }
2514                 if (isIdentifier(a, start, len))
2515                 {
2516                     i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
2517                     break;
2518                 }
2519                 if (isKeyword(start, len))
2520                 {
2521                     i = buf.bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1;
2522                     break;
2523                 }
2524                 if (isFunctionParameter(a, start, len))
2525                 {
2526                     //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j);
2527                     i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
2528                     break;
2529                 }
2530                 i = j - 1;
2531             }
2532             break;
2533         }
2534     }
2535     if (inCode)
2536         error(s ? s.loc : Loc(), "unmatched --- in DDoc comment");
2537 }
2538 
2539 /**************************************************
2540  * Highlight code for DDOC section.
2541  */
2542 extern (C++) void highlightCode(Scope* sc, Dsymbol s, OutBuffer* buf, size_t offset)
2543 {
2544     //printf("highlightCode(s = %s '%s')\n", s->kind(), s->toChars());
2545     OutBuffer ancbuf;
2546     emitAnchor(&ancbuf, s, sc);
2547     buf.insert(offset, ancbuf.peekSlice());
2548     offset += ancbuf.offset;
2549     Dsymbols a;
2550     a.push(s);
2551     highlightCode(sc, &a, buf, offset);
2552 }
2553 
2554 /****************************************************
2555  */
2556 extern (C++) void highlightCode(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset)
2557 {
2558     //printf("highlightCode(a = '%s')\n", a->toChars());
2559     bool resolvedTemplateParameters = false;
2560 
2561     for (size_t i = offset; i < buf.offset; i++)
2562     {
2563         char c = buf.data[i];
2564         const(char)* se = sc._module.escapetable.escapeChar(c);
2565         if (se)
2566         {
2567             size_t len = strlen(se);
2568             buf.remove(i, 1);
2569             i = buf.insert(i, se, len);
2570             i--; // point to ';'
2571             continue;
2572         }
2573         char* start = cast(char*)buf.data + i;
2574         if (isIdStart(start))
2575         {
2576             size_t j = skippastident(buf, i);
2577             if (i < j)
2578             {
2579                 size_t len = j - i;
2580                 if (isIdentifier(a, start, len))
2581                 {
2582                     i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
2583                     continue;
2584                 }
2585                 if (isFunctionParameter(a, start, len))
2586                 {
2587                     //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j);
2588                     i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
2589                     continue;
2590                 }
2591                 i = j - 1;
2592             }
2593         }
2594         else if (!resolvedTemplateParameters)
2595         {
2596             size_t previ = i;
2597 
2598             // hunt for template declarations:
2599             foreach (symi; 0 .. a.dim)
2600             {
2601                 FuncDeclaration fd = (*a)[symi].isFuncDeclaration();
2602 
2603                 if (!fd || !fd.parent || !fd.parent.isTemplateDeclaration())
2604                 {
2605                     continue;
2606                 }
2607 
2608                 TemplateDeclaration td = fd.parent.isTemplateDeclaration();
2609 
2610                 // build the template parameters
2611                 Array!(size_t) paramLens;
2612                 paramLens.reserve(td.parameters.dim);
2613 
2614                 OutBuffer parametersBuf;
2615                 HdrGenState hgs;
2616 
2617                 parametersBuf.writeByte('(');
2618 
2619                 foreach (parami; 0 .. td.parameters.dim)
2620                 {
2621                     TemplateParameter tp = (*td.parameters)[parami];
2622 
2623                     if (parami)
2624                         parametersBuf.writestring(", ");
2625 
2626                     size_t lastOffset = parametersBuf.offset;
2627 
2628                     .toCBuffer(tp, &parametersBuf, &hgs);
2629 
2630                     paramLens[parami] = parametersBuf.offset - lastOffset;
2631                 }
2632                 parametersBuf.writeByte(')');
2633 
2634                 const templateParams = parametersBuf.peekString();
2635                 const templateParamsLen = parametersBuf.peekSlice().length;
2636 
2637                 //printf("templateDecl: %s\ntemplateParams: %s\nstart: %s\n", td.toChars(), templateParams, start);
2638 
2639                 if (cmp(templateParams, start, templateParamsLen) == 0)
2640                 {
2641                     immutable templateParamListMacro = "$(DDOC_TEMPLATE_PARAM_LIST ";
2642                     buf.bracket(i, templateParamListMacro.ptr, i + templateParamsLen, ")");
2643 
2644                     // We have the parameter list. While we're here we might
2645                     // as well wrap the parameters themselves as well
2646 
2647                     // + 1 here to take into account the opening paren of the
2648                     // template param list
2649                     i += templateParamListMacro.length + 1;
2650 
2651                     foreach (const len; paramLens)
2652                     {
2653                         i = buf.bracket(i, "$(DDOC_TEMPLATE_PARAM ", i + len, ")");
2654                         // increment two here for space + comma
2655                         i += 2;
2656                     }
2657 
2658                     resolvedTemplateParameters = true;
2659                     // reset i to be positioned back before we found the template
2660                     // param list this assures that anything within the template
2661                     // param list that needs to be escaped or otherwise altered
2662                     // has an opportunity for that to happen outside of this context
2663                     i = previ;
2664 
2665                     continue;
2666                 }
2667             }
2668         }
2669     }
2670 }
2671 
2672 /****************************************
2673  */
2674 extern (C++) void highlightCode3(Scope* sc, OutBuffer* buf, const(char)* p, const(char)* pend)
2675 {
2676     for (; p < pend; p++)
2677     {
2678         const(char)* s = sc._module.escapetable.escapeChar(*p);
2679         if (s)
2680             buf.writestring(s);
2681         else
2682             buf.writeByte(*p);
2683     }
2684 }
2685 
2686 /**************************************************
2687  * Highlight code for CODE section.
2688  */
2689 extern (C++) void highlightCode2(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset)
2690 {
2691     uint errorsave = global.errors;
2692     scope Lexer lex = new Lexer(null, cast(char*)buf.data, 0, buf.offset - 1, 0, 1);
2693     OutBuffer res;
2694     const(char)* lastp = cast(char*)buf.data;
2695     //printf("highlightCode2('%.*s')\n", buf->offset - 1, buf->data);
2696     res.reserve(buf.offset);
2697     while (1)
2698     {
2699         Token tok;
2700         lex.scan(&tok);
2701         highlightCode3(sc, &res, lastp, tok.ptr);
2702         const(char)* highlight = null;
2703         switch (tok.value)
2704         {
2705         case TOKidentifier:
2706             {
2707                 if (!sc)
2708                     break;
2709                 size_t len = lex.p - tok.ptr;
2710                 if (isIdentifier(a, tok.ptr, len))
2711                 {
2712                     highlight = "$(D_PSYMBOL ";
2713                     break;
2714                 }
2715                 if (isFunctionParameter(a, tok.ptr, len))
2716                 {
2717                     //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j);
2718                     highlight = "$(D_PARAM ";
2719                     break;
2720                 }
2721                 break;
2722             }
2723         case TOKcomment:
2724             highlight = "$(D_COMMENT ";
2725             break;
2726         case TOKstring:
2727             highlight = "$(D_STRING ";
2728             break;
2729         default:
2730             if (tok.isKeyword())
2731                 highlight = "$(D_KEYWORD ";
2732             break;
2733         }
2734         if (highlight)
2735         {
2736             res.writestring(highlight);
2737             size_t o = res.offset;
2738             highlightCode3(sc, &res, tok.ptr, lex.p);
2739             if (tok.value == TOKcomment || tok.value == TOKstring)
2740                 escapeDdocString(&res, o); // Bugzilla 7656, 7715, and 10519
2741             res.writeByte(')');
2742         }
2743         else
2744             highlightCode3(sc, &res, tok.ptr, lex.p);
2745         if (tok.value == TOKeof)
2746             break;
2747         lastp = lex.p;
2748     }
2749     buf.setsize(offset);
2750     buf.write(&res);
2751     global.errors = errorsave;
2752 }
2753 
2754 /****************************************
2755  * Determine if p points to the start of a "..." parameter identifier.
2756  */
2757 extern (C++) bool isCVariadicArg(const(char)* p, size_t len)
2758 {
2759     return len >= 3 && cmp("...", p, 3) == 0;
2760 }
2761 
2762 /****************************************
2763  * Determine if p points to the start of an identifier.
2764  */
2765 extern (C++) bool isIdStart(const(char)* p)
2766 {
2767     dchar c = *p;
2768     if (isalpha(c) || c == '_')
2769         return true;
2770     if (c >= 0x80)
2771     {
2772         size_t i = 0;
2773         if (utf_decodeChar(p, 4, i, c))
2774             return false; // ignore errors
2775         if (isUniAlpha(c))
2776             return true;
2777     }
2778     return false;
2779 }
2780 
2781 /****************************************
2782  * Determine if p points to the rest of an identifier.
2783  */
2784 extern (C++) bool isIdTail(const(char)* p)
2785 {
2786     dchar c = *p;
2787     if (isalnum(c) || c == '_')
2788         return true;
2789     if (c >= 0x80)
2790     {
2791         size_t i = 0;
2792         if (utf_decodeChar(p, 4, i, c))
2793             return false; // ignore errors
2794         if (isUniAlpha(c))
2795             return true;
2796     }
2797     return false;
2798 }
2799 
2800 /****************************************
2801  * Determine if p points to the indentation space.
2802  */
2803 extern (C++) bool isIndentWS(const(char)* p)
2804 {
2805     return (*p == ' ') || (*p == '\t');
2806 }
2807 
2808 /*****************************************
2809  * Return number of bytes in UTF character.
2810  */
2811 extern (C++) int utfStride(const(char)* p)
2812 {
2813     dchar c = *p;
2814     if (c < 0x80)
2815         return 1;
2816     size_t i = 0;
2817     utf_decodeChar(p, 4, i, c); // ignore errors, but still consume input
2818     return cast(int)i;
2819 }