"Fossies" - the Fresh Open Source Software Archive

Member "ldc-1.7.0-src/ddmd/doc.d" (5 Jan 2018, 88166 Bytes) of package /linux/misc/ldc-1.7.0-src.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) D source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "doc.d": 1.6.0-src_vs_1.7.0-src.

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