Home | History | Annotate | Line # | Download | only in dmd
      1  1.1  mrg /**
      2  1.1  mrg  * Ddoc documentation generation.
      3  1.1  mrg  *
      4  1.1  mrg  * Specification: $(LINK2 https://dlang.org/spec/ddoc.html, Documentation Generator)
      5  1.1  mrg  *
      6  1.1  mrg  * Copyright:   Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
      7  1.1  mrg  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
      8  1.1  mrg  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
      9  1.1  mrg  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/doc.d, _doc.d)
     10  1.1  mrg  * Documentation:  https://dlang.org/phobos/dmd_doc.html
     11  1.1  mrg  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/doc.d
     12  1.1  mrg  */
     13  1.1  mrg 
     14  1.1  mrg module dmd.doc;
     15  1.1  mrg 
     16  1.1  mrg import core.stdc.ctype;
     17  1.1  mrg import core.stdc.stdlib;
     18  1.1  mrg import core.stdc.stdio;
     19  1.1  mrg import core.stdc.string;
     20  1.1  mrg import core.stdc.time;
     21  1.1  mrg import dmd.aggregate;
     22  1.1  mrg import dmd.arraytypes;
     23  1.1  mrg import dmd.astenums;
     24  1.1  mrg import dmd.attrib;
     25  1.1  mrg import dmd.cond;
     26  1.1  mrg import dmd.dclass;
     27  1.1  mrg import dmd.declaration;
     28  1.1  mrg import dmd.denum;
     29  1.1  mrg import dmd.dimport;
     30  1.1  mrg import dmd.dmacro;
     31  1.1  mrg import dmd.dmodule;
     32  1.1  mrg import dmd.dscope;
     33  1.1  mrg import dmd.dstruct;
     34  1.1  mrg import dmd.dsymbol;
     35  1.1  mrg import dmd.dsymbolsem;
     36  1.1  mrg import dmd.dtemplate;
     37  1.1  mrg import dmd.errors;
     38  1.1  mrg import dmd.func;
     39  1.1  mrg import dmd.globals;
     40  1.1  mrg import dmd.hdrgen;
     41  1.1  mrg import dmd.id;
     42  1.1  mrg import dmd.identifier;
     43  1.1  mrg import dmd.lexer;
     44  1.1  mrg import dmd.mtype;
     45  1.1  mrg import dmd.root.array;
     46  1.1  mrg import dmd.root.file;
     47  1.1  mrg import dmd.root.filename;
     48  1.1  mrg import dmd.common.outbuffer;
     49  1.1  mrg import dmd.root.port;
     50  1.1  mrg import dmd.root.rmem;
     51  1.1  mrg import dmd.root.string;
     52  1.1  mrg import dmd.root.utf;
     53  1.1  mrg import dmd.tokens;
     54  1.1  mrg import dmd.utils;
     55  1.1  mrg import dmd.visitor;
     56  1.1  mrg 
     57  1.1  mrg struct Escape
     58  1.1  mrg {
     59  1.1  mrg     const(char)[][char.max] strings;
     60  1.1  mrg 
     61  1.1  mrg     /***************************************
     62  1.1  mrg      * Find character string to replace c with.
     63  1.1  mrg      */
     64  1.1  mrg     const(char)[] escapeChar(char c)
     65  1.1  mrg     {
     66  1.1  mrg         version (all)
     67  1.1  mrg         {
     68  1.1  mrg             //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c].ptr);
     69  1.1  mrg             return strings[c];
     70  1.1  mrg         }
     71  1.1  mrg         else
     72  1.1  mrg         {
     73  1.1  mrg             const(char)[] s;
     74  1.1  mrg             switch (c)
     75  1.1  mrg             {
     76  1.1  mrg             case '<':
     77  1.1  mrg                 s = "&lt;";
     78  1.1  mrg                 break;
     79  1.1  mrg             case '>':
     80  1.1  mrg                 s = "&gt;";
     81  1.1  mrg                 break;
     82  1.1  mrg             case '&':
     83  1.1  mrg                 s = "&amp;";
     84  1.1  mrg                 break;
     85  1.1  mrg             default:
     86  1.1  mrg                 s = null;
     87  1.1  mrg                 break;
     88  1.1  mrg             }
     89  1.1  mrg             return s;
     90  1.1  mrg         }
     91  1.1  mrg     }
     92  1.1  mrg }
     93  1.1  mrg 
     94  1.1  mrg /***********************************************************
     95  1.1  mrg  */
     96  1.1  mrg private class Section
     97  1.1  mrg {
     98  1.1  mrg     const(char)[] name;
     99  1.1  mrg     const(char)[] body_;
    100  1.1  mrg     int nooutput;
    101  1.1  mrg 
    102  1.1  mrg     override string toString() const
    103  1.1  mrg     {
    104  1.1  mrg         assert(0);
    105  1.1  mrg     }
    106  1.1  mrg 
    107  1.1  mrg     void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
    108  1.1  mrg     {
    109  1.1  mrg         assert(a.dim);
    110  1.1  mrg         if (name.length)
    111  1.1  mrg         {
    112  1.1  mrg             static immutable table =
    113  1.1  mrg             [
    114  1.1  mrg                 "AUTHORS",
    115  1.1  mrg                 "BUGS",
    116  1.1  mrg                 "COPYRIGHT",
    117  1.1  mrg                 "DATE",
    118  1.1  mrg                 "DEPRECATED",
    119  1.1  mrg                 "EXAMPLES",
    120  1.1  mrg                 "HISTORY",
    121  1.1  mrg                 "LICENSE",
    122  1.1  mrg                 "RETURNS",
    123  1.1  mrg                 "SEE_ALSO",
    124  1.1  mrg                 "STANDARDS",
    125  1.1  mrg                 "THROWS",
    126  1.1  mrg                 "VERSION",
    127  1.1  mrg             ];
    128  1.1  mrg             foreach (entry; table)
    129  1.1  mrg             {
    130  1.1  mrg                 if (iequals(entry, name))
    131  1.1  mrg                 {
    132  1.1  mrg                     buf.printf("$(DDOC_%s ", entry.ptr);
    133  1.1  mrg                     goto L1;
    134  1.1  mrg                 }
    135  1.1  mrg             }
    136  1.1  mrg             buf.writestring("$(DDOC_SECTION ");
    137  1.1  mrg             // Replace _ characters with spaces
    138  1.1  mrg             buf.writestring("$(DDOC_SECTION_H ");
    139  1.1  mrg             size_t o = buf.length;
    140  1.1  mrg             foreach (char c; name)
    141  1.1  mrg                 buf.writeByte((c == '_') ? ' ' : c);
    142  1.1  mrg             escapeStrayParenthesis(loc, buf, o, false);
    143  1.1  mrg             buf.writestring(")");
    144  1.1  mrg         }
    145  1.1  mrg         else
    146  1.1  mrg         {
    147  1.1  mrg             buf.writestring("$(DDOC_DESCRIPTION ");
    148  1.1  mrg         }
    149  1.1  mrg     L1:
    150  1.1  mrg         size_t o = buf.length;
    151  1.1  mrg         buf.write(body_);
    152  1.1  mrg         escapeStrayParenthesis(loc, buf, o, true);
    153  1.1  mrg         highlightText(sc, a, loc, *buf, o);
    154  1.1  mrg         buf.writestring(")");
    155  1.1  mrg     }
    156  1.1  mrg }
    157  1.1  mrg 
    158  1.1  mrg /***********************************************************
    159  1.1  mrg  */
    160  1.1  mrg private final class ParamSection : Section
    161  1.1  mrg {
    162  1.1  mrg     override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
    163  1.1  mrg     {
    164  1.1  mrg         assert(a.dim);
    165  1.1  mrg         Dsymbol s = (*a)[0]; // test
    166  1.1  mrg         const(char)* p = body_.ptr;
    167  1.1  mrg         size_t len = body_.length;
    168  1.1  mrg         const(char)* pend = p + len;
    169  1.1  mrg         const(char)* tempstart = null;
    170  1.1  mrg         size_t templen = 0;
    171  1.1  mrg         const(char)* namestart = null;
    172  1.1  mrg         size_t namelen = 0; // !=0 if line continuation
    173  1.1  mrg         const(char)* textstart = null;
    174  1.1  mrg         size_t textlen = 0;
    175  1.1  mrg         size_t paramcount = 0;
    176  1.1  mrg         buf.writestring("$(DDOC_PARAMS ");
    177  1.1  mrg         while (p < pend)
    178  1.1  mrg         {
    179  1.1  mrg             // Skip to start of macro
    180  1.1  mrg             while (1)
    181  1.1  mrg             {
    182  1.1  mrg                 switch (*p)
    183  1.1  mrg                 {
    184  1.1  mrg                 case ' ':
    185  1.1  mrg                 case '\t':
    186  1.1  mrg                     p++;
    187  1.1  mrg                     continue;
    188  1.1  mrg                 case '\n':
    189  1.1  mrg                     p++;
    190  1.1  mrg                     goto Lcont;
    191  1.1  mrg                 default:
    192  1.1  mrg                     if (isIdStart(p) || isCVariadicArg(p[0 .. cast(size_t)(pend - p)]))
    193  1.1  mrg                         break;
    194  1.1  mrg                     if (namelen)
    195  1.1  mrg                         goto Ltext;
    196  1.1  mrg                     // continuation of prev macro
    197  1.1  mrg                     goto Lskipline;
    198  1.1  mrg                 }
    199  1.1  mrg                 break;
    200  1.1  mrg             }
    201  1.1  mrg             tempstart = p;
    202  1.1  mrg             while (isIdTail(p))
    203  1.1  mrg                 p += utfStride(p);
    204  1.1  mrg             if (isCVariadicArg(p[0 .. cast(size_t)(pend - p)]))
    205  1.1  mrg                 p += 3;
    206  1.1  mrg             templen = p - tempstart;
    207  1.1  mrg             while (*p == ' ' || *p == '\t')
    208  1.1  mrg                 p++;
    209  1.1  mrg             if (*p != '=')
    210  1.1  mrg             {
    211  1.1  mrg                 if (namelen)
    212  1.1  mrg                     goto Ltext;
    213  1.1  mrg                 // continuation of prev macro
    214  1.1  mrg                 goto Lskipline;
    215  1.1  mrg             }
    216  1.1  mrg             p++;
    217  1.1  mrg             if (namelen)
    218  1.1  mrg             {
    219  1.1  mrg                 // Output existing param
    220  1.1  mrg             L1:
    221  1.1  mrg                 //printf("param '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart);
    222  1.1  mrg                 ++paramcount;
    223  1.1  mrg                 HdrGenState hgs;
    224  1.1  mrg                 buf.writestring("$(DDOC_PARAM_ROW ");
    225  1.1  mrg                 {
    226  1.1  mrg                     buf.writestring("$(DDOC_PARAM_ID ");
    227  1.1  mrg                     {
    228  1.1  mrg                         size_t o = buf.length;
    229  1.1  mrg                         Parameter fparam = isFunctionParameter(a, namestart, namelen);
    230  1.1  mrg                         if (!fparam)
    231  1.1  mrg                         {
    232  1.1  mrg                             // Comments on a template might refer to function parameters within.
    233  1.1  mrg                             // Search the parameters of nested eponymous functions (with the same name.)
    234  1.1  mrg                             fparam = isEponymousFunctionParameter(a, namestart, namelen);
    235  1.1  mrg                         }
    236  1.1  mrg                         bool isCVariadic = isCVariadicParameter(a, namestart[0 .. namelen]);
    237  1.1  mrg                         if (isCVariadic)
    238  1.1  mrg                         {
    239  1.1  mrg                             buf.writestring("...");
    240  1.1  mrg                         }
    241  1.1  mrg                         else if (fparam && fparam.type && fparam.ident)
    242  1.1  mrg                         {
    243  1.1  mrg                             .toCBuffer(fparam.type, buf, fparam.ident, &hgs);
    244  1.1  mrg                         }
    245  1.1  mrg                         else
    246  1.1  mrg                         {
    247  1.1  mrg                             if (isTemplateParameter(a, namestart, namelen))
    248  1.1  mrg                             {
    249  1.1  mrg                                 // 10236: Don't count template parameters for params check
    250  1.1  mrg                                 --paramcount;
    251  1.1  mrg                             }
    252  1.1  mrg                             else if (!fparam)
    253  1.1  mrg                             {
    254  1.1  mrg                                 warning(s.loc, "Ddoc: function declaration has no parameter '%.*s'", cast(int)namelen, namestart);
    255  1.1  mrg                             }
    256  1.1  mrg                             buf.write(namestart[0 .. namelen]);
    257  1.1  mrg                         }
    258  1.1  mrg                         escapeStrayParenthesis(loc, buf, o, true);
    259  1.1  mrg                         highlightCode(sc, a, *buf, o);
    260  1.1  mrg                     }
    261  1.1  mrg                     buf.writestring(")");
    262  1.1  mrg                     buf.writestring("$(DDOC_PARAM_DESC ");
    263  1.1  mrg                     {
    264  1.1  mrg                         size_t o = buf.length;
    265  1.1  mrg                         buf.write(textstart[0 .. textlen]);
    266  1.1  mrg                         escapeStrayParenthesis(loc, buf, o, true);
    267  1.1  mrg                         highlightText(sc, a, loc, *buf, o);
    268  1.1  mrg                     }
    269  1.1  mrg                     buf.writestring(")");
    270  1.1  mrg                 }
    271  1.1  mrg                 buf.writestring(")");
    272  1.1  mrg                 namelen = 0;
    273  1.1  mrg                 if (p >= pend)
    274  1.1  mrg                     break;
    275  1.1  mrg             }
    276  1.1  mrg             namestart = tempstart;
    277  1.1  mrg             namelen = templen;
    278  1.1  mrg             while (*p == ' ' || *p == '\t')
    279  1.1  mrg                 p++;
    280  1.1  mrg             textstart = p;
    281  1.1  mrg         Ltext:
    282  1.1  mrg             while (*p != '\n')
    283  1.1  mrg                 p++;
    284  1.1  mrg             textlen = p - textstart;
    285  1.1  mrg             p++;
    286  1.1  mrg         Lcont:
    287  1.1  mrg             continue;
    288  1.1  mrg         Lskipline:
    289  1.1  mrg             // Ignore this line
    290  1.1  mrg             while (*p++ != '\n')
    291  1.1  mrg             {
    292  1.1  mrg             }
    293  1.1  mrg         }
    294  1.1  mrg         if (namelen)
    295  1.1  mrg             goto L1;
    296  1.1  mrg         // write out last one
    297  1.1  mrg         buf.writestring(")");
    298  1.1  mrg         TypeFunction tf = a.dim == 1 ? isTypeFunction(s) : null;
    299  1.1  mrg         if (tf)
    300  1.1  mrg         {
    301  1.1  mrg             size_t pcount = (tf.parameterList.parameters ? tf.parameterList.parameters.dim : 0) +
    302  1.1  mrg                             cast(int)(tf.parameterList.varargs == VarArg.variadic);
    303  1.1  mrg             if (pcount != paramcount)
    304  1.1  mrg             {
    305  1.1  mrg                 warning(s.loc, "Ddoc: parameter count mismatch, expected %llu, got %llu",
    306  1.1  mrg                         cast(ulong) pcount, cast(ulong) paramcount);
    307  1.1  mrg                 if (paramcount == 0)
    308  1.1  mrg                 {
    309  1.1  mrg                     // Chances are someone messed up the format
    310  1.1  mrg                     warningSupplemental(s.loc, "Note that the format is `param = description`");
    311  1.1  mrg                 }
    312  1.1  mrg             }
    313  1.1  mrg         }
    314  1.1  mrg     }
    315  1.1  mrg }
    316  1.1  mrg 
    317  1.1  mrg /***********************************************************
    318  1.1  mrg  */
    319  1.1  mrg private final class MacroSection : Section
    320  1.1  mrg {
    321  1.1  mrg     override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
    322  1.1  mrg     {
    323  1.1  mrg         //printf("MacroSection::write()\n");
    324  1.1  mrg         DocComment.parseMacros(dc.escapetable, *dc.pmacrotable, body_);
    325  1.1  mrg     }
    326  1.1  mrg }
    327  1.1  mrg 
    328  1.1  mrg private alias Sections = Array!(Section);
    329  1.1  mrg 
    330  1.1  mrg // Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one).
    331  1.1  mrg private bool isCVariadicParameter(Dsymbols* a, const(char)[] p)
    332  1.1  mrg {
    333  1.1  mrg     foreach (member; *a)
    334  1.1  mrg     {
    335  1.1  mrg         TypeFunction tf = isTypeFunction(member);
    336  1.1  mrg         if (tf && tf.parameterList.varargs == VarArg.variadic && p == "...")
    337  1.1  mrg             return true;
    338  1.1  mrg     }
    339  1.1  mrg     return false;
    340  1.1  mrg }
    341  1.1  mrg 
    342  1.1  mrg private Dsymbol getEponymousMember(TemplateDeclaration td)
    343  1.1  mrg {
    344  1.1  mrg     if (!td.onemember)
    345  1.1  mrg         return null;
    346  1.1  mrg     if (AggregateDeclaration ad = td.onemember.isAggregateDeclaration())
    347  1.1  mrg         return ad;
    348  1.1  mrg     if (FuncDeclaration fd = td.onemember.isFuncDeclaration())
    349  1.1  mrg         return fd;
    350  1.1  mrg     if (auto em = td.onemember.isEnumMember())
    351  1.1  mrg         return null;    // Keep backward compatibility. See compilable/ddoc9.d
    352  1.1  mrg     if (VarDeclaration vd = td.onemember.isVarDeclaration())
    353  1.1  mrg         return td.constraint ? null : vd;
    354  1.1  mrg     return null;
    355  1.1  mrg }
    356  1.1  mrg 
    357  1.1  mrg private TemplateDeclaration getEponymousParent(Dsymbol s)
    358  1.1  mrg {
    359  1.1  mrg     if (!s.parent)
    360  1.1  mrg         return null;
    361  1.1  mrg     TemplateDeclaration td = s.parent.isTemplateDeclaration();
    362  1.1  mrg     return (td && getEponymousMember(td)) ? td : null;
    363  1.1  mrg }
    364  1.1  mrg 
    365  1.1  mrg private immutable ddoc_default = import("default_ddoc_theme." ~ ddoc_ext);
    366  1.1  mrg private immutable ddoc_decl_s = "$(DDOC_DECL ";
    367  1.1  mrg private immutable ddoc_decl_e = ")\n";
    368  1.1  mrg private immutable ddoc_decl_dd_s = "$(DDOC_DECL_DD ";
    369  1.1  mrg private immutable ddoc_decl_dd_e = ")\n";
    370  1.1  mrg 
    371  1.1  mrg /****************************************************
    372  1.1  mrg  */
    373  1.1  mrg extern(C++) void gendocfile(Module m)
    374  1.1  mrg {
    375  1.1  mrg     __gshared OutBuffer mbuf;
    376  1.1  mrg     __gshared int mbuf_done;
    377  1.1  mrg     OutBuffer buf;
    378  1.1  mrg     //printf("Module::gendocfile()\n");
    379  1.1  mrg     if (!mbuf_done) // if not already read the ddoc files
    380  1.1  mrg     {
    381  1.1  mrg         mbuf_done = 1;
    382  1.1  mrg         // Use our internal default
    383  1.1  mrg         mbuf.writestring(ddoc_default);
    384  1.1  mrg         // Override with DDOCFILE specified in the sc.ini file
    385  1.1  mrg         char* p = getenv("DDOCFILE");
    386  1.1  mrg         if (p)
    387  1.1  mrg             global.params.ddocfiles.shift(p);
    388  1.1  mrg         // Override with the ddoc macro files from the command line
    389  1.1  mrg         for (size_t i = 0; i < global.params.ddocfiles.dim; i++)
    390  1.1  mrg         {
    391  1.1  mrg             auto buffer = readFile(m.loc, global.params.ddocfiles[i]);
    392  1.1  mrg             // BUG: convert file contents to UTF-8 before use
    393  1.1  mrg             const data = buffer.data;
    394  1.1  mrg             //printf("file: '%.*s'\n", cast(int)data.length, data.ptr);
    395  1.1  mrg             mbuf.write(data);
    396  1.1  mrg         }
    397  1.1  mrg     }
    398  1.1  mrg     DocComment.parseMacros(m.escapetable, m.macrotable, mbuf[]);
    399  1.1  mrg     Scope* sc = Scope.createGlobal(m); // create root scope
    400  1.1  mrg     DocComment* dc = DocComment.parse(m, m.comment);
    401  1.1  mrg     dc.pmacrotable = &m.macrotable;
    402  1.1  mrg     dc.escapetable = m.escapetable;
    403  1.1  mrg     sc.lastdc = dc;
    404  1.1  mrg     // Generate predefined macros
    405  1.1  mrg     // Set the title to be the name of the module
    406  1.1  mrg     {
    407  1.1  mrg         const p = m.toPrettyChars().toDString;
    408  1.1  mrg         m.macrotable.define("TITLE", p);
    409  1.1  mrg     }
    410  1.1  mrg     // Set time macros
    411  1.1  mrg     {
    412  1.1  mrg         time_t t;
    413  1.1  mrg         time(&t);
    414  1.1  mrg         char* p = ctime(&t);
    415  1.1  mrg         p = mem.xstrdup(p);
    416  1.1  mrg         m.macrotable.define("DATETIME", p.toDString());
    417  1.1  mrg         m.macrotable.define("YEAR", p[20 .. 20 + 4]);
    418  1.1  mrg     }
    419  1.1  mrg     const srcfilename = m.srcfile.toString();
    420  1.1  mrg     m.macrotable.define("SRCFILENAME", srcfilename);
    421  1.1  mrg     const docfilename = m.docfile.toString();
    422  1.1  mrg     m.macrotable.define("DOCFILENAME", docfilename);
    423  1.1  mrg     if (dc.copyright)
    424  1.1  mrg     {
    425  1.1  mrg         dc.copyright.nooutput = 1;
    426  1.1  mrg         m.macrotable.define("COPYRIGHT", dc.copyright.body_);
    427  1.1  mrg     }
    428  1.1  mrg     if (m.filetype == FileType.ddoc)
    429  1.1  mrg     {
    430  1.1  mrg         const ploc = m.md ? &m.md.loc : &m.loc;
    431  1.1  mrg         const loc = Loc(ploc.filename ? ploc.filename : srcfilename.ptr,
    432  1.1  mrg                         ploc.linnum,
    433  1.1  mrg                         ploc.charnum);
    434  1.1  mrg 
    435  1.1  mrg         size_t commentlen = strlen(cast(char*)m.comment);
    436  1.1  mrg         Dsymbols a;
    437  1.1  mrg         // https://issues.dlang.org/show_bug.cgi?id=9764
    438  1.1  mrg         // Don't push m in a, to prevent emphasize ddoc file name.
    439  1.1  mrg         if (dc.macros)
    440  1.1  mrg         {
    441  1.1  mrg             commentlen = dc.macros.name.ptr - m.comment;
    442  1.1  mrg             dc.macros.write(loc, dc, sc, &a, &buf);
    443  1.1  mrg         }
    444  1.1  mrg         buf.write(m.comment[0 .. commentlen]);
    445  1.1  mrg         highlightText(sc, &a, loc, buf, 0);
    446  1.1  mrg     }
    447  1.1  mrg     else
    448  1.1  mrg     {
    449  1.1  mrg         Dsymbols a;
    450  1.1  mrg         a.push(m);
    451  1.1  mrg         dc.writeSections(sc, &a, &buf);
    452  1.1  mrg         emitMemberComments(m, buf, sc);
    453  1.1  mrg     }
    454  1.1  mrg     //printf("BODY= '%.*s'\n", cast(int)buf.length, buf.data);
    455  1.1  mrg     m.macrotable.define("BODY", buf[]);
    456  1.1  mrg     OutBuffer buf2;
    457  1.1  mrg     buf2.writestring("$(DDOC)");
    458  1.1  mrg     size_t end = buf2.length;
    459  1.1  mrg     m.macrotable.expand(buf2, 0, end, null);
    460  1.1  mrg     version (all)
    461  1.1  mrg     {
    462  1.1  mrg         /* Remove all the escape sequences from buf2,
    463  1.1  mrg          * and make CR-LF the newline.
    464  1.1  mrg          */
    465  1.1  mrg         {
    466  1.1  mrg             const slice = buf2[];
    467  1.1  mrg             buf.setsize(0);
    468  1.1  mrg             buf.reserve(slice.length);
    469  1.1  mrg             auto p = slice.ptr;
    470  1.1  mrg             for (size_t j = 0; j < slice.length; j++)
    471  1.1  mrg             {
    472  1.1  mrg                 char c = p[j];
    473  1.1  mrg                 if (c == 0xFF && j + 1 < slice.length)
    474  1.1  mrg                 {
    475  1.1  mrg                     j++;
    476  1.1  mrg                     continue;
    477  1.1  mrg                 }
    478  1.1  mrg                 if (c == '\n')
    479  1.1  mrg                     buf.writeByte('\r');
    480  1.1  mrg                 else if (c == '\r')
    481  1.1  mrg                 {
    482  1.1  mrg                     buf.writestring("\r\n");
    483  1.1  mrg                     if (j + 1 < slice.length && p[j + 1] == '\n')
    484  1.1  mrg                     {
    485  1.1  mrg                         j++;
    486  1.1  mrg                     }
    487  1.1  mrg                     continue;
    488  1.1  mrg                 }
    489  1.1  mrg                 buf.writeByte(c);
    490  1.1  mrg             }
    491  1.1  mrg         }
    492  1.1  mrg         writeFile(m.loc, m.docfile.toString(), buf[]);
    493  1.1  mrg     }
    494  1.1  mrg     else
    495  1.1  mrg     {
    496  1.1  mrg         /* Remove all the escape sequences from buf2
    497  1.1  mrg          */
    498  1.1  mrg         {
    499  1.1  mrg             size_t i = 0;
    500  1.1  mrg             char* p = buf2.data;
    501  1.1  mrg             for (size_t j = 0; j < buf2.length; j++)
    502  1.1  mrg             {
    503  1.1  mrg                 if (p[j] == 0xFF && j + 1 < buf2.length)
    504  1.1  mrg                 {
    505  1.1  mrg                     j++;
    506  1.1  mrg                     continue;
    507  1.1  mrg                 }
    508  1.1  mrg                 p[i] = p[j];
    509  1.1  mrg                 i++;
    510  1.1  mrg             }
    511  1.1  mrg             buf2.setsize(i);
    512  1.1  mrg         }
    513  1.1  mrg         writeFile(m.loc, m.docfile.toString(), buf2[]);
    514  1.1  mrg     }
    515  1.1  mrg }
    516  1.1  mrg 
    517  1.1  mrg /****************************************************
    518  1.1  mrg  * Having unmatched parentheses can hose the output of Ddoc,
    519  1.1  mrg  * as the macros depend on properly nested parentheses.
    520  1.1  mrg  * This function replaces all ( with $(LPAREN) and ) with $(RPAREN)
    521  1.1  mrg  * to preserve text literally. This also means macros in the
    522  1.1  mrg  * text won't be expanded.
    523  1.1  mrg  */
    524  1.1  mrg void escapeDdocString(OutBuffer* buf, size_t start)
    525  1.1  mrg {
    526  1.1  mrg     for (size_t u = start; u < buf.length; u++)
    527  1.1  mrg     {
    528  1.1  mrg         char c = (*buf)[u];
    529  1.1  mrg         switch (c)
    530  1.1  mrg         {
    531  1.1  mrg         case '$':
    532  1.1  mrg             buf.remove(u, 1);
    533  1.1  mrg             buf.insert(u, "$(DOLLAR)");
    534  1.1  mrg             u += 8;
    535  1.1  mrg             break;
    536  1.1  mrg         case '(':
    537  1.1  mrg             buf.remove(u, 1); //remove the (
    538  1.1  mrg             buf.insert(u, "$(LPAREN)"); //insert this instead
    539  1.1  mrg             u += 8; //skip over newly inserted macro
    540  1.1  mrg             break;
    541  1.1  mrg         case ')':
    542  1.1  mrg             buf.remove(u, 1); //remove the )
    543  1.1  mrg             buf.insert(u, "$(RPAREN)"); //insert this instead
    544  1.1  mrg             u += 8; //skip over newly inserted macro
    545  1.1  mrg             break;
    546  1.1  mrg         default:
    547  1.1  mrg             break;
    548  1.1  mrg         }
    549  1.1  mrg     }
    550  1.1  mrg }
    551  1.1  mrg 
    552  1.1  mrg /****************************************************
    553  1.1  mrg  * Having unmatched parentheses can hose the output of Ddoc,
    554  1.1  mrg  * as the macros depend on properly nested parentheses.
    555  1.1  mrg  *
    556  1.1  mrg  * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN).
    557  1.1  mrg  *
    558  1.1  mrg  * Params:
    559  1.1  mrg  *  loc   = source location of start of text. It is a mutable copy to allow incrementing its linenum, for printing the correct line number when an error is encountered in a multiline block of ddoc.
    560  1.1  mrg  *  buf   = an OutBuffer containing the DDoc
    561  1.1  mrg  *  start = the index within buf to start replacing unmatched parentheses
    562  1.1  mrg  *  respectBackslashEscapes = if true, always replace parentheses that are
    563  1.1  mrg  *    directly preceeded by a backslash with $(LPAREN) or $(RPAREN) instead of
    564  1.1  mrg  *    counting them as stray parentheses
    565  1.1  mrg  */
    566  1.1  mrg private void escapeStrayParenthesis(Loc loc, OutBuffer* buf, size_t start, bool respectBackslashEscapes)
    567  1.1  mrg {
    568  1.1  mrg     uint par_open = 0;
    569  1.1  mrg     char inCode = 0;
    570  1.1  mrg     bool atLineStart = true;
    571  1.1  mrg     for (size_t u = start; u < buf.length; u++)
    572  1.1  mrg     {
    573  1.1  mrg         char c = (*buf)[u];
    574  1.1  mrg         switch (c)
    575  1.1  mrg         {
    576  1.1  mrg         case '(':
    577  1.1  mrg             if (!inCode)
    578  1.1  mrg                 par_open++;
    579  1.1  mrg             atLineStart = false;
    580  1.1  mrg             break;
    581  1.1  mrg         case ')':
    582  1.1  mrg             if (!inCode)
    583  1.1  mrg             {
    584  1.1  mrg                 if (par_open == 0)
    585  1.1  mrg                 {
    586  1.1  mrg                     //stray ')'
    587  1.1  mrg                     warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output. Use $(RPAREN) instead for unpaired right parentheses.");
    588  1.1  mrg                     buf.remove(u, 1); //remove the )
    589  1.1  mrg                     buf.insert(u, "$(RPAREN)"); //insert this instead
    590  1.1  mrg                     u += 8; //skip over newly inserted macro
    591  1.1  mrg                 }
    592  1.1  mrg                 else
    593  1.1  mrg                     par_open--;
    594  1.1  mrg             }
    595  1.1  mrg             atLineStart = false;
    596  1.1  mrg             break;
    597  1.1  mrg         case '\n':
    598  1.1  mrg             atLineStart = true;
    599  1.1  mrg             version (none)
    600  1.1  mrg             {
    601  1.1  mrg                 // For this to work, loc must be set to the beginning of the passed
    602  1.1  mrg                 // text which is currently not possible
    603  1.1  mrg                 // (loc is set to the Loc of the Dsymbol)
    604  1.1  mrg                 loc.linnum++;
    605  1.1  mrg             }
    606  1.1  mrg             break;
    607  1.1  mrg         case ' ':
    608  1.1  mrg         case '\r':
    609  1.1  mrg         case '\t':
    610  1.1  mrg             break;
    611  1.1  mrg         case '-':
    612  1.1  mrg         case '`':
    613  1.1  mrg         case '~':
    614  1.1  mrg             // Issue 15465: don't try to escape unbalanced parens inside code
    615  1.1  mrg             // blocks.
    616  1.1  mrg             int numdash = 1;
    617  1.1  mrg             for (++u; u < buf.length && (*buf)[u] == c; ++u)
    618  1.1  mrg                 ++numdash;
    619  1.1  mrg             --u;
    620  1.1  mrg             if (c == '`' || (atLineStart && numdash >= 3))
    621  1.1  mrg             {
    622  1.1  mrg                 if (inCode == c)
    623  1.1  mrg                     inCode = 0;
    624  1.1  mrg                 else if (!inCode)
    625  1.1  mrg                     inCode = c;
    626  1.1  mrg             }
    627  1.1  mrg             atLineStart = false;
    628  1.1  mrg             break;
    629  1.1  mrg         case '\\':
    630  1.1  mrg             // replace backslash-escaped parens with their macros
    631  1.1  mrg             if (!inCode && respectBackslashEscapes && u+1 < buf.length && global.params.markdown)
    632  1.1  mrg             {
    633  1.1  mrg                 if ((*buf)[u+1] == '(' || (*buf)[u+1] == ')')
    634  1.1  mrg                 {
    635  1.1  mrg                     const paren = (*buf)[u+1] == '(' ? "$(LPAREN)" : "$(RPAREN)";
    636  1.1  mrg                     buf.remove(u, 2); //remove the \)
    637  1.1  mrg                     buf.insert(u, paren); //insert this instead
    638  1.1  mrg                     u += 8; //skip over newly inserted macro
    639  1.1  mrg                 }
    640  1.1  mrg                 else if ((*buf)[u+1] == '\\')
    641  1.1  mrg                     ++u;
    642  1.1  mrg             }
    643  1.1  mrg             break;
    644  1.1  mrg         default:
    645  1.1  mrg             atLineStart = false;
    646  1.1  mrg             break;
    647  1.1  mrg         }
    648  1.1  mrg     }
    649  1.1  mrg     if (par_open) // if any unmatched lparens
    650  1.1  mrg     {
    651  1.1  mrg         par_open = 0;
    652  1.1  mrg         for (size_t u = buf.length; u > start;)
    653  1.1  mrg         {
    654  1.1  mrg             u--;
    655  1.1  mrg             char c = (*buf)[u];
    656  1.1  mrg             switch (c)
    657  1.1  mrg             {
    658  1.1  mrg             case ')':
    659  1.1  mrg                 par_open++;
    660  1.1  mrg                 break;
    661  1.1  mrg             case '(':
    662  1.1  mrg                 if (par_open == 0)
    663  1.1  mrg                 {
    664  1.1  mrg                     //stray '('
    665  1.1  mrg                     warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output. Use $(LPAREN) instead for unpaired left parentheses.");
    666  1.1  mrg                     buf.remove(u, 1); //remove the (
    667  1.1  mrg                     buf.insert(u, "$(LPAREN)"); //insert this instead
    668  1.1  mrg                 }
    669  1.1  mrg                 else
    670  1.1  mrg                     par_open--;
    671  1.1  mrg                 break;
    672  1.1  mrg             default:
    673  1.1  mrg                 break;
    674  1.1  mrg             }
    675  1.1  mrg         }
    676  1.1  mrg     }
    677  1.1  mrg }
    678  1.1  mrg 
    679  1.1  mrg // Basically, this is to skip over things like private{} blocks in a struct or
    680  1.1  mrg // class definition that don't add any components to the qualified name.
    681  1.1  mrg private Scope* skipNonQualScopes(Scope* sc)
    682  1.1  mrg {
    683  1.1  mrg     while (sc && !sc.scopesym)
    684  1.1  mrg         sc = sc.enclosing;
    685  1.1  mrg     return sc;
    686  1.1  mrg }
    687  1.1  mrg 
    688  1.1  mrg private bool emitAnchorName(ref OutBuffer buf, Dsymbol s, Scope* sc, bool includeParent)
    689  1.1  mrg {
    690  1.1  mrg     if (!s || s.isPackage() || s.isModule())
    691  1.1  mrg         return false;
    692  1.1  mrg     // Add parent names first
    693  1.1  mrg     bool dot = false;
    694  1.1  mrg     auto eponymousParent = getEponymousParent(s);
    695  1.1  mrg     if (includeParent && s.parent || eponymousParent)
    696  1.1  mrg         dot = emitAnchorName(buf, s.parent, sc, includeParent);
    697  1.1  mrg     else if (includeParent && sc)
    698  1.1  mrg         dot = emitAnchorName(buf, sc.scopesym, skipNonQualScopes(sc.enclosing), includeParent);
    699  1.1  mrg     // Eponymous template members can share the parent anchor name
    700  1.1  mrg     if (eponymousParent)
    701  1.1  mrg         return dot;
    702  1.1  mrg     if (dot)
    703  1.1  mrg         buf.writeByte('.');
    704  1.1  mrg     // Use "this" not "__ctor"
    705  1.1  mrg     TemplateDeclaration td;
    706  1.1  mrg     if (s.isCtorDeclaration() || ((td = s.isTemplateDeclaration()) !is null && td.onemember && td.onemember.isCtorDeclaration()))
    707  1.1  mrg     {
    708  1.1  mrg         buf.writestring("this");
    709  1.1  mrg     }
    710  1.1  mrg     else
    711  1.1  mrg     {
    712  1.1  mrg         /* We just want the identifier, not overloads like TemplateDeclaration::toChars.
    713  1.1  mrg          * We don't want the template parameter list and constraints. */
    714  1.1  mrg         buf.writestring(s.Dsymbol.toChars());
    715  1.1  mrg     }
    716  1.1  mrg     return true;
    717  1.1  mrg }
    718  1.1  mrg 
    719  1.1  mrg private void emitAnchor(ref OutBuffer buf, Dsymbol s, Scope* sc, bool forHeader = false)
    720  1.1  mrg {
    721  1.1  mrg     Identifier ident;
    722  1.1  mrg     {
    723  1.1  mrg         OutBuffer anc;
    724  1.1  mrg         emitAnchorName(anc, s, skipNonQualScopes(sc), true);
    725  1.1  mrg         ident = Identifier.idPool(anc[]);
    726  1.1  mrg     }
    727  1.1  mrg 
    728  1.1  mrg     auto pcount = cast(void*)ident in sc.anchorCounts;
    729  1.1  mrg     typeof(*pcount) count;
    730  1.1  mrg     if (!forHeader)
    731  1.1  mrg     {
    732  1.1  mrg         if (pcount)
    733  1.1  mrg         {
    734  1.1  mrg             // Existing anchor,
    735  1.1  mrg             // don't write an anchor for matching consecutive ditto symbols
    736  1.1  mrg             TemplateDeclaration td = getEponymousParent(s);
    737  1.1  mrg             if (sc.prevAnchor == ident && sc.lastdc && (isDitto(s.comment) || (td && isDitto(td.comment))))
    738  1.1  mrg                 return;
    739  1.1  mrg 
    740  1.1  mrg             count = ++*pcount;
    741  1.1  mrg         }
    742  1.1  mrg         else
    743  1.1  mrg         {
    744  1.1  mrg             sc.anchorCounts[cast(void*)ident] = 1;
    745  1.1  mrg             count = 1;
    746  1.1  mrg         }
    747  1.1  mrg     }
    748  1.1  mrg 
    749  1.1  mrg     // cache anchor name
    750  1.1  mrg     sc.prevAnchor = ident;
    751  1.1  mrg     auto macroName = forHeader ? "DDOC_HEADER_ANCHOR" : "DDOC_ANCHOR";
    752  1.1  mrg 
    753  1.1  mrg     if (auto imp = s.isImport())
    754  1.1  mrg     {
    755  1.1  mrg         // For example: `public import core.stdc.string : memcpy, memcmp;`
    756  1.1  mrg         if (imp.aliases.dim > 0)
    757  1.1  mrg         {
    758  1.1  mrg             for(int i = 0; i < imp.aliases.dim; i++)
    759  1.1  mrg             {
    760  1.1  mrg                 // Need to distinguish between
    761  1.1  mrg                 // `public import core.stdc.string : memcpy, memcmp;` and
    762  1.1  mrg                 // `public import core.stdc.string : copy = memcpy, compare = memcmp;`
    763  1.1  mrg                 auto a = imp.aliases[i];
    764  1.1  mrg                 auto id = a ? a : imp.names[i];
    765  1.1  mrg                 auto loc = Loc.init;
    766  1.1  mrg                 if (auto symFromId = sc.search(loc, id, null))
    767  1.1  mrg                 {
    768  1.1  mrg                     emitAnchor(buf, symFromId, sc, forHeader);
    769  1.1  mrg                 }
    770  1.1  mrg             }
    771  1.1  mrg         }
    772  1.1  mrg         else
    773  1.1  mrg         {
    774  1.1  mrg             // For example: `public import str = core.stdc.string;`
    775  1.1  mrg             if (imp.aliasId)
    776  1.1  mrg             {
    777  1.1  mrg                 auto symbolName = imp.aliasId.toString();
    778  1.1  mrg 
    779  1.1  mrg                 buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr,
    780  1.1  mrg                     cast(int) symbolName.length, symbolName.ptr);
    781  1.1  mrg 
    782  1.1  mrg                 if (forHeader)
    783  1.1  mrg                 {
    784  1.1  mrg                     buf.printf(", %.*s", cast(int) symbolName.length, symbolName.ptr);
    785  1.1  mrg                 }
    786  1.1  mrg             }
    787  1.1  mrg             else
    788  1.1  mrg             {
    789  1.1  mrg                 // The general case:  `public import core.stdc.string;`
    790  1.1  mrg 
    791  1.1  mrg                 // fully qualify imports so `core.stdc.string` doesn't appear as `core`
    792  1.1  mrg                 void printFullyQualifiedImport()
    793  1.1  mrg                 {
    794  1.1  mrg                     foreach (const pid; imp.packages)
    795  1.1  mrg                     {
    796  1.1  mrg                         buf.printf("%s.", pid.toChars());
    797  1.1  mrg                     }
    798  1.1  mrg                     buf.writestring(imp.id.toString());
    799  1.1  mrg                 }
    800  1.1  mrg 
    801  1.1  mrg                 buf.printf("$(%.*s ", cast(int) macroName.length, macroName.ptr);
    802  1.1  mrg                 printFullyQualifiedImport();
    803  1.1  mrg 
    804  1.1  mrg                 if (forHeader)
    805  1.1  mrg                 {
    806  1.1  mrg                     buf.printf(", ");
    807  1.1  mrg                     printFullyQualifiedImport();
    808  1.1  mrg                 }
    809  1.1  mrg             }
    810  1.1  mrg 
    811  1.1  mrg             buf.writeByte(')');
    812  1.1  mrg         }
    813  1.1  mrg     }
    814  1.1  mrg     else
    815  1.1  mrg     {
    816  1.1  mrg         auto symbolName = ident.toString();
    817  1.1  mrg         buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr,
    818  1.1  mrg             cast(int) symbolName.length, symbolName.ptr);
    819  1.1  mrg 
    820  1.1  mrg         // only append count once there's a duplicate
    821  1.1  mrg         if (count > 1)
    822  1.1  mrg             buf.printf(".%u", count);
    823  1.1  mrg 
    824  1.1  mrg         if (forHeader)
    825  1.1  mrg         {
    826  1.1  mrg             Identifier shortIdent;
    827  1.1  mrg             {
    828  1.1  mrg                 OutBuffer anc;
    829  1.1  mrg                 emitAnchorName(anc, s, skipNonQualScopes(sc), false);
    830  1.1  mrg                 shortIdent = Identifier.idPool(anc[]);
    831  1.1  mrg             }
    832  1.1  mrg 
    833  1.1  mrg             auto shortName = shortIdent.toString();
    834  1.1  mrg             buf.printf(", %.*s", cast(int) shortName.length, shortName.ptr);
    835  1.1  mrg         }
    836  1.1  mrg 
    837  1.1  mrg         buf.writeByte(')');
    838  1.1  mrg     }
    839  1.1  mrg }
    840  1.1  mrg 
    841  1.1  mrg /******************************* emitComment **********************************/
    842  1.1  mrg 
    843  1.1  mrg /** Get leading indentation from 'src' which represents lines of code. */
    844  1.1  mrg private size_t getCodeIndent(const(char)* src)
    845  1.1  mrg {
    846  1.1  mrg     while (src && (*src == '\r' || *src == '\n'))
    847  1.1  mrg         ++src; // skip until we find the first non-empty line
    848  1.1  mrg     size_t codeIndent = 0;
    849  1.1  mrg     while (src && (*src == ' ' || *src == '\t'))
    850  1.1  mrg     {
    851  1.1  mrg         codeIndent++;
    852  1.1  mrg         src++;
    853  1.1  mrg     }
    854  1.1  mrg     return codeIndent;
    855  1.1  mrg }
    856  1.1  mrg 
    857  1.1  mrg /** Recursively expand template mixin member docs into the scope. */
    858  1.1  mrg private void expandTemplateMixinComments(TemplateMixin tm, ref OutBuffer buf, Scope* sc)
    859  1.1  mrg {
    860  1.1  mrg     if (!tm.semanticRun)
    861  1.1  mrg         tm.dsymbolSemantic(sc);
    862  1.1  mrg     TemplateDeclaration td = (tm && tm.tempdecl) ? tm.tempdecl.isTemplateDeclaration() : null;
    863  1.1  mrg     if (td && td.members)
    864  1.1  mrg     {
    865  1.1  mrg         for (size_t i = 0; i < td.members.dim; i++)
    866  1.1  mrg         {
    867  1.1  mrg             Dsymbol sm = (*td.members)[i];
    868  1.1  mrg             TemplateMixin tmc = sm.isTemplateMixin();
    869  1.1  mrg             if (tmc && tmc.comment)
    870  1.1  mrg                 expandTemplateMixinComments(tmc, buf, sc);
    871  1.1  mrg             else
    872  1.1  mrg                 emitComment(sm, buf, sc);
    873  1.1  mrg         }
    874  1.1  mrg     }
    875  1.1  mrg }
    876  1.1  mrg 
    877  1.1  mrg private void emitMemberComments(ScopeDsymbol sds, ref OutBuffer buf, Scope* sc)
    878  1.1  mrg {
    879  1.1  mrg     if (!sds.members)
    880  1.1  mrg         return;
    881  1.1  mrg     //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars());
    882  1.1  mrg     const(char)[] m = "$(DDOC_MEMBERS ";
    883  1.1  mrg     if (sds.isTemplateDeclaration())
    884  1.1  mrg         m = "$(DDOC_TEMPLATE_MEMBERS ";
    885  1.1  mrg     else if (sds.isClassDeclaration())
    886  1.1  mrg         m = "$(DDOC_CLASS_MEMBERS ";
    887  1.1  mrg     else if (sds.isStructDeclaration())
    888  1.1  mrg         m = "$(DDOC_STRUCT_MEMBERS ";
    889  1.1  mrg     else if (sds.isEnumDeclaration())
    890  1.1  mrg         m = "$(DDOC_ENUM_MEMBERS ";
    891  1.1  mrg     else if (sds.isModule())
    892  1.1  mrg         m = "$(DDOC_MODULE_MEMBERS ";
    893  1.1  mrg     size_t offset1 = buf.length; // save starting offset
    894  1.1  mrg     buf.writestring(m);
    895  1.1  mrg     size_t offset2 = buf.length; // to see if we write anything
    896  1.1  mrg     sc = sc.push(sds);
    897  1.1  mrg     for (size_t i = 0; i < sds.members.dim; i++)
    898  1.1  mrg     {
    899  1.1  mrg         Dsymbol s = (*sds.members)[i];
    900  1.1  mrg         //printf("\ts = '%s'\n", s.toChars());
    901  1.1  mrg         // only expand if parent is a non-template (semantic won't work)
    902  1.1  mrg         if (s.comment && s.isTemplateMixin() && s.parent && !s.parent.isTemplateDeclaration())
    903  1.1  mrg             expandTemplateMixinComments(cast(TemplateMixin)s, buf, sc);
    904  1.1  mrg         emitComment(s, buf, sc);
    905  1.1  mrg     }
    906  1.1  mrg     emitComment(null, buf, sc);
    907  1.1  mrg     sc.pop();
    908  1.1  mrg     if (buf.length == offset2)
    909  1.1  mrg     {
    910  1.1  mrg         /* Didn't write out any members, so back out last write
    911  1.1  mrg          */
    912  1.1  mrg         buf.setsize(offset1);
    913  1.1  mrg     }
    914  1.1  mrg     else
    915  1.1  mrg         buf.writestring(")");
    916  1.1  mrg }
    917  1.1  mrg 
    918  1.1  mrg private void emitVisibility(ref OutBuffer buf, Import i)
    919  1.1  mrg {
    920  1.1  mrg     // imports are private by default, which is different from other declarations
    921  1.1  mrg     // so they should explicitly show their visibility
    922  1.1  mrg     emitVisibility(buf, i.visibility);
    923  1.1  mrg }
    924  1.1  mrg 
    925  1.1  mrg private void emitVisibility(ref OutBuffer buf, Declaration d)
    926  1.1  mrg {
    927  1.1  mrg     auto vis = d.visibility;
    928  1.1  mrg     if (vis.kind != Visibility.Kind.undefined && vis.kind != Visibility.Kind.public_)
    929  1.1  mrg     {
    930  1.1  mrg         emitVisibility(buf, vis);
    931  1.1  mrg     }
    932  1.1  mrg }
    933  1.1  mrg 
    934  1.1  mrg private void emitVisibility(ref OutBuffer buf, Visibility vis)
    935  1.1  mrg {
    936  1.1  mrg     visibilityToBuffer(&buf, vis);
    937  1.1  mrg     buf.writeByte(' ');
    938  1.1  mrg }
    939  1.1  mrg 
    940  1.1  mrg private void emitComment(Dsymbol s, ref OutBuffer buf, Scope* sc)
    941  1.1  mrg {
    942  1.1  mrg     extern (C++) final class EmitComment : Visitor
    943  1.1  mrg     {
    944  1.1  mrg         alias visit = Visitor.visit;
    945  1.1  mrg     public:
    946  1.1  mrg         OutBuffer* buf;
    947  1.1  mrg         Scope* sc;
    948  1.1  mrg 
    949  1.1  mrg         extern (D) this(ref OutBuffer buf, Scope* sc)
    950  1.1  mrg         {
    951  1.1  mrg             this.buf = &buf;
    952  1.1  mrg             this.sc = sc;
    953  1.1  mrg         }
    954  1.1  mrg 
    955  1.1  mrg         override void visit(Dsymbol)
    956  1.1  mrg         {
    957  1.1  mrg         }
    958  1.1  mrg 
    959  1.1  mrg         override void visit(InvariantDeclaration)
    960  1.1  mrg         {
    961  1.1  mrg         }
    962  1.1  mrg 
    963  1.1  mrg         override void visit(UnitTestDeclaration)
    964  1.1  mrg         {
    965  1.1  mrg         }
    966  1.1  mrg 
    967  1.1  mrg         override void visit(PostBlitDeclaration)
    968  1.1  mrg         {
    969  1.1  mrg         }
    970  1.1  mrg 
    971  1.1  mrg         override void visit(DtorDeclaration)
    972  1.1  mrg         {
    973  1.1  mrg         }
    974  1.1  mrg 
    975  1.1  mrg         override void visit(StaticCtorDeclaration)
    976  1.1  mrg         {
    977  1.1  mrg         }
    978  1.1  mrg 
    979  1.1  mrg         override void visit(StaticDtorDeclaration)
    980  1.1  mrg         {
    981  1.1  mrg         }
    982  1.1  mrg 
    983  1.1  mrg         override void visit(TypeInfoDeclaration)
    984  1.1  mrg         {
    985  1.1  mrg         }
    986  1.1  mrg 
    987  1.1  mrg         void emit(Scope* sc, Dsymbol s, const(char)* com)
    988  1.1  mrg         {
    989  1.1  mrg             if (s && sc.lastdc && isDitto(com))
    990  1.1  mrg             {
    991  1.1  mrg                 sc.lastdc.a.push(s);
    992  1.1  mrg                 return;
    993  1.1  mrg             }
    994  1.1  mrg             // Put previous doc comment if exists
    995  1.1  mrg             if (DocComment* dc = sc.lastdc)
    996  1.1  mrg             {
    997  1.1  mrg                 assert(dc.a.dim > 0, "Expects at least one declaration for a" ~
    998  1.1  mrg                     "documentation comment");
    999  1.1  mrg 
   1000  1.1  mrg                 auto symbol = dc.a[0];
   1001  1.1  mrg 
   1002  1.1  mrg                 buf.writestring("$(DDOC_MEMBER");
   1003  1.1  mrg                 buf.writestring("$(DDOC_MEMBER_HEADER");
   1004  1.1  mrg                 emitAnchor(*buf, symbol, sc, true);
   1005  1.1  mrg                 buf.writeByte(')');
   1006  1.1  mrg 
   1007  1.1  mrg                 // Put the declaration signatures as the document 'title'
   1008  1.1  mrg                 buf.writestring(ddoc_decl_s);
   1009  1.1  mrg                 for (size_t i = 0; i < dc.a.dim; i++)
   1010  1.1  mrg                 {
   1011  1.1  mrg                     Dsymbol sx = dc.a[i];
   1012  1.1  mrg                     // the added linebreaks in here make looking at multiple
   1013  1.1  mrg                     // signatures more appealing
   1014  1.1  mrg                     if (i == 0)
   1015  1.1  mrg                     {
   1016  1.1  mrg                         size_t o = buf.length;
   1017  1.1  mrg                         toDocBuffer(sx, *buf, sc);
   1018  1.1  mrg                         highlightCode(sc, sx, *buf, o);
   1019  1.1  mrg                         buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
   1020  1.1  mrg                         continue;
   1021  1.1  mrg                     }
   1022  1.1  mrg                     buf.writestring("$(DDOC_DITTO ");
   1023  1.1  mrg                     {
   1024  1.1  mrg                         size_t o = buf.length;
   1025  1.1  mrg                         toDocBuffer(sx, *buf, sc);
   1026  1.1  mrg                         highlightCode(sc, sx, *buf, o);
   1027  1.1  mrg                     }
   1028  1.1  mrg                     buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
   1029  1.1  mrg                     buf.writeByte(')');
   1030  1.1  mrg                 }
   1031  1.1  mrg                 buf.writestring(ddoc_decl_e);
   1032  1.1  mrg                 // Put the ddoc comment as the document 'description'
   1033  1.1  mrg                 buf.writestring(ddoc_decl_dd_s);
   1034  1.1  mrg                 {
   1035  1.1  mrg                     dc.writeSections(sc, &dc.a, buf);
   1036  1.1  mrg                     if (ScopeDsymbol sds = dc.a[0].isScopeDsymbol())
   1037  1.1  mrg                         emitMemberComments(sds, *buf, sc);
   1038  1.1  mrg                 }
   1039  1.1  mrg                 buf.writestring(ddoc_decl_dd_e);
   1040  1.1  mrg                 buf.writeByte(')');
   1041  1.1  mrg                 //printf("buf.2 = [[%.*s]]\n", cast(int)(buf.length - o0), buf.data + o0);
   1042  1.1  mrg             }
   1043  1.1  mrg             if (s)
   1044  1.1  mrg             {
   1045  1.1  mrg                 DocComment* dc = DocComment.parse(s, com);
   1046  1.1  mrg                 dc.pmacrotable = &sc._module.macrotable;
   1047  1.1  mrg                 sc.lastdc = dc;
   1048  1.1  mrg             }
   1049  1.1  mrg         }
   1050  1.1  mrg 
   1051  1.1  mrg         override void visit(Import imp)
   1052  1.1  mrg         {
   1053  1.1  mrg             if (imp.visible().kind != Visibility.Kind.public_ && sc.visibility.kind != Visibility.Kind.export_)
   1054  1.1  mrg                 return;
   1055  1.1  mrg 
   1056  1.1  mrg             if (imp.comment)
   1057  1.1  mrg                 emit(sc, imp, imp.comment);
   1058  1.1  mrg         }
   1059  1.1  mrg 
   1060  1.1  mrg         override void visit(Declaration d)
   1061  1.1  mrg         {
   1062  1.1  mrg             //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d.toChars(), d.comment);
   1063  1.1  mrg             //printf("type = %p\n", d.type);
   1064  1.1  mrg             const(char)* com = d.comment;
   1065  1.1  mrg             if (TemplateDeclaration td = getEponymousParent(d))
   1066  1.1  mrg             {
   1067  1.1  mrg                 if (isDitto(td.comment))
   1068  1.1  mrg                     com = td.comment;
   1069  1.1  mrg                 else
   1070  1.1  mrg                     com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true);
   1071  1.1  mrg             }
   1072  1.1  mrg             else
   1073  1.1  mrg             {
   1074  1.1  mrg                 if (!d.ident)
   1075  1.1  mrg                     return;
   1076  1.1  mrg                 if (!d.type)
   1077  1.1  mrg                 {
   1078  1.1  mrg                     if (!d.isCtorDeclaration() &&
   1079  1.1  mrg                         !d.isAliasDeclaration() &&
   1080  1.1  mrg                         !d.isVarDeclaration())
   1081  1.1  mrg                     {
   1082  1.1  mrg                         return;
   1083  1.1  mrg                     }
   1084  1.1  mrg                 }
   1085  1.1  mrg                 if (d.visibility.kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
   1086  1.1  mrg                     return;
   1087  1.1  mrg             }
   1088  1.1  mrg             if (!com)
   1089  1.1  mrg                 return;
   1090  1.1  mrg             emit(sc, d, com);
   1091  1.1  mrg         }
   1092  1.1  mrg 
   1093  1.1  mrg         override void visit(AggregateDeclaration ad)
   1094  1.1  mrg         {
   1095  1.1  mrg             //printf("AggregateDeclaration::emitComment() '%s'\n", ad.toChars());
   1096  1.1  mrg             const(char)* com = ad.comment;
   1097  1.1  mrg             if (TemplateDeclaration td = getEponymousParent(ad))
   1098  1.1  mrg             {
   1099  1.1  mrg                 if (isDitto(td.comment))
   1100  1.1  mrg                     com = td.comment;
   1101  1.1  mrg                 else
   1102  1.1  mrg                     com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true);
   1103  1.1  mrg             }
   1104  1.1  mrg             else
   1105  1.1  mrg             {
   1106  1.1  mrg                 if (ad.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
   1107  1.1  mrg                     return;
   1108  1.1  mrg                 if (!ad.comment)
   1109  1.1  mrg                     return;
   1110  1.1  mrg             }
   1111  1.1  mrg             if (!com)
   1112  1.1  mrg                 return;
   1113  1.1  mrg             emit(sc, ad, com);
   1114  1.1  mrg         }
   1115  1.1  mrg 
   1116  1.1  mrg         override void visit(TemplateDeclaration td)
   1117  1.1  mrg         {
   1118  1.1  mrg             //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td.toChars(), td.kind());
   1119  1.1  mrg             if (td.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
   1120  1.1  mrg                 return;
   1121  1.1  mrg             if (!td.comment)
   1122  1.1  mrg                 return;
   1123  1.1  mrg             if (Dsymbol ss = getEponymousMember(td))
   1124  1.1  mrg             {
   1125  1.1  mrg                 ss.accept(this);
   1126  1.1  mrg                 return;
   1127  1.1  mrg             }
   1128  1.1  mrg             emit(sc, td, td.comment);
   1129  1.1  mrg         }
   1130  1.1  mrg 
   1131  1.1  mrg         override void visit(EnumDeclaration ed)
   1132  1.1  mrg         {
   1133  1.1  mrg             if (ed.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
   1134  1.1  mrg                 return;
   1135  1.1  mrg             if (ed.isAnonymous() && ed.members)
   1136  1.1  mrg             {
   1137  1.1  mrg                 for (size_t i = 0; i < ed.members.dim; i++)
   1138  1.1  mrg                 {
   1139  1.1  mrg                     Dsymbol s = (*ed.members)[i];
   1140  1.1  mrg                     emitComment(s, *buf, sc);
   1141  1.1  mrg                 }
   1142  1.1  mrg                 return;
   1143  1.1  mrg             }
   1144  1.1  mrg             if (!ed.comment)
   1145  1.1  mrg                 return;
   1146  1.1  mrg             if (ed.isAnonymous())
   1147  1.1  mrg                 return;
   1148  1.1  mrg             emit(sc, ed, ed.comment);
   1149  1.1  mrg         }
   1150  1.1  mrg 
   1151  1.1  mrg         override void visit(EnumMember em)
   1152  1.1  mrg         {
   1153  1.1  mrg             //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em.toChars(), em.comment);
   1154  1.1  mrg             if (em.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_)
   1155  1.1  mrg                 return;
   1156  1.1  mrg             if (!em.comment)
   1157  1.1  mrg                 return;
   1158  1.1  mrg             emit(sc, em, em.comment);
   1159  1.1  mrg         }
   1160  1.1  mrg 
   1161  1.1  mrg         override void visit(AttribDeclaration ad)
   1162  1.1  mrg         {
   1163  1.1  mrg             //printf("AttribDeclaration::emitComment(sc = %p)\n", sc);
   1164  1.1  mrg             /* A general problem with this,
   1165  1.1  mrg              * illustrated by https://issues.dlang.org/show_bug.cgi?id=2516
   1166  1.1  mrg              * is that attributes are not transmitted through to the underlying
   1167  1.1  mrg              * member declarations for template bodies, because semantic analysis
   1168  1.1  mrg              * is not done for template declaration bodies
   1169  1.1  mrg              * (only template instantiations).
   1170  1.1  mrg              * Hence, Ddoc omits attributes from template members.
   1171  1.1  mrg              */
   1172  1.1  mrg             Dsymbols* d = ad.include(null);
   1173  1.1  mrg             if (d)
   1174  1.1  mrg             {
   1175  1.1  mrg                 for (size_t i = 0; i < d.dim; i++)
   1176  1.1  mrg                 {
   1177  1.1  mrg                     Dsymbol s = (*d)[i];
   1178  1.1  mrg                     //printf("AttribDeclaration::emitComment %s\n", s.toChars());
   1179  1.1  mrg                     emitComment(s, *buf, sc);
   1180  1.1  mrg                 }
   1181  1.1  mrg             }
   1182  1.1  mrg         }
   1183  1.1  mrg 
   1184  1.1  mrg         override void visit(VisibilityDeclaration pd)
   1185  1.1  mrg         {
   1186  1.1  mrg             if (pd.decl)
   1187  1.1  mrg             {
   1188  1.1  mrg                 Scope* scx = sc;
   1189  1.1  mrg                 sc = sc.copy();
   1190  1.1  mrg                 sc.visibility = pd.visibility;
   1191  1.1  mrg                 visit(cast(AttribDeclaration)pd);
   1192  1.1  mrg                 scx.lastdc = sc.lastdc;
   1193  1.1  mrg                 sc = sc.pop();
   1194  1.1  mrg             }
   1195  1.1  mrg         }
   1196  1.1  mrg 
   1197  1.1  mrg         override void visit(ConditionalDeclaration cd)
   1198  1.1  mrg         {
   1199  1.1  mrg             //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc);
   1200  1.1  mrg             if (cd.condition.inc != Include.notComputed)
   1201  1.1  mrg             {
   1202  1.1  mrg                 visit(cast(AttribDeclaration)cd);
   1203  1.1  mrg                 return;
   1204  1.1  mrg             }
   1205  1.1  mrg             /* If generating doc comment, be careful because if we're inside
   1206  1.1  mrg              * a template, then include(null) will fail.
   1207  1.1  mrg              */
   1208  1.1  mrg             Dsymbols* d = cd.decl ? cd.decl : cd.elsedecl;
   1209  1.1  mrg             for (size_t i = 0; i < d.dim; i++)
   1210  1.1  mrg             {
   1211  1.1  mrg                 Dsymbol s = (*d)[i];
   1212  1.1  mrg                 emitComment(s, *buf, sc);
   1213  1.1  mrg             }
   1214  1.1  mrg         }
   1215  1.1  mrg     }
   1216  1.1  mrg 
   1217  1.1  mrg     scope EmitComment v = new EmitComment(buf, sc);
   1218  1.1  mrg     if (!s)
   1219  1.1  mrg         v.emit(sc, null, null);
   1220  1.1  mrg     else
   1221  1.1  mrg         s.accept(v);
   1222  1.1  mrg }
   1223  1.1  mrg 
   1224  1.1  mrg private void toDocBuffer(Dsymbol s, ref OutBuffer buf, Scope* sc)
   1225  1.1  mrg {
   1226  1.1  mrg     extern (C++) final class ToDocBuffer : Visitor
   1227  1.1  mrg     {
   1228  1.1  mrg         alias visit = Visitor.visit;
   1229  1.1  mrg     public:
   1230  1.1  mrg         OutBuffer* buf;
   1231  1.1  mrg         Scope* sc;
   1232  1.1  mrg 
   1233  1.1  mrg         extern (D) this(ref OutBuffer buf, Scope* sc)
   1234  1.1  mrg         {
   1235  1.1  mrg             this.buf = &buf;
   1236  1.1  mrg             this.sc = sc;
   1237  1.1  mrg         }
   1238  1.1  mrg 
   1239  1.1  mrg         override void visit(Dsymbol s)
   1240  1.1  mrg         {
   1241  1.1  mrg             //printf("Dsymbol::toDocbuffer() %s\n", s.toChars());
   1242  1.1  mrg             HdrGenState hgs;
   1243  1.1  mrg             hgs.ddoc = true;
   1244  1.1  mrg             .toCBuffer(s, buf, &hgs);
   1245  1.1  mrg         }
   1246  1.1  mrg 
   1247  1.1  mrg         void prefix(Dsymbol s)
   1248  1.1  mrg         {
   1249  1.1  mrg             if (s.isDeprecated())
   1250  1.1  mrg                 buf.writestring("deprecated ");
   1251  1.1  mrg             if (Declaration d = s.isDeclaration())
   1252  1.1  mrg             {
   1253  1.1  mrg                 emitVisibility(*buf, d);
   1254  1.1  mrg                 if (d.isStatic())
   1255  1.1  mrg                     buf.writestring("static ");
   1256  1.1  mrg                 else if (d.isFinal())
   1257  1.1  mrg                     buf.writestring("final ");
   1258  1.1  mrg                 else if (d.isAbstract())
   1259  1.1  mrg                     buf.writestring("abstract ");
   1260  1.1  mrg 
   1261  1.1  mrg                 if (d.isFuncDeclaration())      // functionToBufferFull handles this
   1262  1.1  mrg                     return;
   1263  1.1  mrg 
   1264  1.1  mrg                 if (d.isImmutable())
   1265  1.1  mrg                     buf.writestring("immutable ");
   1266  1.1  mrg                 if (d.storage_class & STC.shared_)
   1267  1.1  mrg                     buf.writestring("shared ");
   1268  1.1  mrg                 if (d.isWild())
   1269  1.1  mrg                     buf.writestring("inout ");
   1270  1.1  mrg                 if (d.isConst())
   1271  1.1  mrg                     buf.writestring("const ");
   1272  1.1  mrg 
   1273  1.1  mrg                 if (d.isSynchronized())
   1274  1.1  mrg                     buf.writestring("synchronized ");
   1275  1.1  mrg 
   1276  1.1  mrg                 if (d.storage_class & STC.manifest)
   1277  1.1  mrg                     buf.writestring("enum ");
   1278  1.1  mrg 
   1279  1.1  mrg                 // Add "auto" for the untyped variable in template members
   1280  1.1  mrg                 if (!d.type && d.isVarDeclaration() &&
   1281  1.1  mrg                     !d.isImmutable() && !(d.storage_class & STC.shared_) && !d.isWild() && !d.isConst() &&
   1282  1.1  mrg                     !d.isSynchronized())
   1283  1.1  mrg                 {
   1284  1.1  mrg                     buf.writestring("auto ");
   1285  1.1  mrg                 }
   1286  1.1  mrg             }
   1287  1.1  mrg         }
   1288  1.1  mrg 
   1289  1.1  mrg         override void visit(Import i)
   1290  1.1  mrg         {
   1291  1.1  mrg             HdrGenState hgs;
   1292  1.1  mrg             hgs.ddoc = true;
   1293  1.1  mrg             emitVisibility(*buf, i);
   1294  1.1  mrg             .toCBuffer(i, buf, &hgs);
   1295  1.1  mrg         }
   1296  1.1  mrg 
   1297  1.1  mrg         override void visit(Declaration d)
   1298  1.1  mrg         {
   1299  1.1  mrg             if (!d.ident)
   1300  1.1  mrg                 return;
   1301  1.1  mrg             TemplateDeclaration td = getEponymousParent(d);
   1302  1.1  mrg             //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d.toChars(), d.originalType ? d.originalType.toChars() : "--", td ? td.toChars() : "--");
   1303  1.1  mrg             HdrGenState hgs;
   1304  1.1  mrg             hgs.ddoc = true;
   1305  1.1  mrg             if (d.isDeprecated())
   1306  1.1  mrg                 buf.writestring("$(DEPRECATED ");
   1307  1.1  mrg             prefix(d);
   1308  1.1  mrg             if (d.type)
   1309  1.1  mrg             {
   1310  1.1  mrg                 Type origType = d.originalType ? d.originalType : d.type;
   1311  1.1  mrg                 if (origType.ty == Tfunction)
   1312  1.1  mrg                 {
   1313  1.1  mrg                     functionToBufferFull(cast(TypeFunction)origType, buf, d.ident, &hgs, td);
   1314  1.1  mrg                 }
   1315  1.1  mrg                 else
   1316  1.1  mrg                     .toCBuffer(origType, buf, d.ident, &hgs);
   1317  1.1  mrg             }
   1318  1.1  mrg             else
   1319  1.1  mrg                 buf.writestring(d.ident.toString());
   1320  1.1  mrg             if (d.isVarDeclaration() && td)
   1321  1.1  mrg             {
   1322  1.1  mrg                 buf.writeByte('(');
   1323  1.1  mrg                 if (td.origParameters && td.origParameters.dim)
   1324  1.1  mrg                 {
   1325  1.1  mrg                     for (size_t i = 0; i < td.origParameters.dim; i++)
   1326  1.1  mrg                     {
   1327  1.1  mrg                         if (i)
   1328  1.1  mrg                             buf.writestring(", ");
   1329  1.1  mrg                         toCBuffer((*td.origParameters)[i], buf, &hgs);
   1330  1.1  mrg                     }
   1331  1.1  mrg                 }
   1332  1.1  mrg                 buf.writeByte(')');
   1333  1.1  mrg             }
   1334  1.1  mrg             // emit constraints if declaration is a templated declaration
   1335  1.1  mrg             if (td && td.constraint)
   1336  1.1  mrg             {
   1337  1.1  mrg                 bool noFuncDecl = td.isFuncDeclaration() is null;
   1338  1.1  mrg                 if (noFuncDecl)
   1339  1.1  mrg                 {
   1340  1.1  mrg                     buf.writestring("$(DDOC_CONSTRAINT ");
   1341  1.1  mrg                 }
   1342  1.1  mrg 
   1343  1.1  mrg                 .toCBuffer(td.constraint, buf, &hgs);
   1344  1.1  mrg 
   1345  1.1  mrg                 if (noFuncDecl)
   1346  1.1  mrg                 {
   1347  1.1  mrg                     buf.writestring(")");
   1348  1.1  mrg                 }
   1349  1.1  mrg             }
   1350  1.1  mrg             if (d.isDeprecated())
   1351  1.1  mrg                 buf.writestring(")");
   1352  1.1  mrg             buf.writestring(";\n");
   1353  1.1  mrg         }
   1354  1.1  mrg 
   1355  1.1  mrg         override void visit(AliasDeclaration ad)
   1356  1.1  mrg         {
   1357  1.1  mrg             //printf("AliasDeclaration::toDocbuffer() %s\n", ad.toChars());
   1358  1.1  mrg             if (!ad.ident)
   1359  1.1  mrg                 return;
   1360  1.1  mrg             if (ad.isDeprecated())
   1361  1.1  mrg                 buf.writestring("deprecated ");
   1362  1.1  mrg             emitVisibility(*buf, ad);
   1363  1.1  mrg             buf.printf("alias %s = ", ad.toChars());
   1364  1.1  mrg             if (Dsymbol s = ad.aliassym) // ident alias
   1365  1.1  mrg             {
   1366  1.1  mrg                 prettyPrintDsymbol(s, ad.parent);
   1367  1.1  mrg             }
   1368  1.1  mrg             else if (Type type = ad.getType()) // type alias
   1369  1.1  mrg             {
   1370  1.1  mrg                 if (type.ty == Tclass || type.ty == Tstruct || type.ty == Tenum)
   1371  1.1  mrg                 {
   1372  1.1  mrg                     if (Dsymbol s = type.toDsymbol(null)) // elaborate type
   1373  1.1  mrg                         prettyPrintDsymbol(s, ad.parent);
   1374  1.1  mrg                     else
   1375  1.1  mrg                         buf.writestring(type.toChars());
   1376  1.1  mrg                 }
   1377  1.1  mrg                 else
   1378  1.1  mrg                 {
   1379  1.1  mrg                     // simple type
   1380  1.1  mrg                     buf.writestring(type.toChars());
   1381  1.1  mrg                 }
   1382  1.1  mrg             }
   1383  1.1  mrg             buf.writestring(";\n");
   1384  1.1  mrg         }
   1385  1.1  mrg 
   1386  1.1  mrg         void parentToBuffer(Dsymbol s)
   1387  1.1  mrg         {
   1388  1.1  mrg             if (s && !s.isPackage() && !s.isModule())
   1389  1.1  mrg             {
   1390  1.1  mrg                 parentToBuffer(s.parent);
   1391  1.1  mrg                 buf.writestring(s.toChars());
   1392  1.1  mrg                 buf.writestring(".");
   1393  1.1  mrg             }
   1394  1.1  mrg         }
   1395  1.1  mrg 
   1396  1.1  mrg         static bool inSameModule(Dsymbol s, Dsymbol p)
   1397  1.1  mrg         {
   1398  1.1  mrg             for (; s; s = s.parent)
   1399  1.1  mrg             {
   1400  1.1  mrg                 if (s.isModule())
   1401  1.1  mrg                     break;
   1402  1.1  mrg             }
   1403  1.1  mrg             for (; p; p = p.parent)
   1404  1.1  mrg             {
   1405  1.1  mrg                 if (p.isModule())
   1406  1.1  mrg                     break;
   1407  1.1  mrg             }
   1408  1.1  mrg             return s == p;
   1409  1.1  mrg         }
   1410  1.1  mrg 
   1411  1.1  mrg         void prettyPrintDsymbol(Dsymbol s, Dsymbol parent)
   1412  1.1  mrg         {
   1413  1.1  mrg             if (s.parent && (s.parent == parent)) // in current scope -> naked name
   1414  1.1  mrg             {
   1415  1.1  mrg                 buf.writestring(s.toChars());
   1416  1.1  mrg             }
   1417  1.1  mrg             else if (!inSameModule(s, parent)) // in another module -> full name
   1418  1.1  mrg             {
   1419  1.1  mrg                 buf.writestring(s.toPrettyChars());
   1420  1.1  mrg             }
   1421  1.1  mrg             else // nested in a type in this module -> full name w/o module name
   1422  1.1  mrg             {
   1423  1.1  mrg                 // if alias is nested in a user-type use module-scope lookup
   1424  1.1  mrg                 if (!parent.isModule() && !parent.isPackage())
   1425  1.1  mrg                     buf.writestring(".");
   1426  1.1  mrg                 parentToBuffer(s.parent);
   1427  1.1  mrg                 buf.writestring(s.toChars());
   1428  1.1  mrg             }
   1429  1.1  mrg         }
   1430  1.1  mrg 
   1431  1.1  mrg         override void visit(AggregateDeclaration ad)
   1432  1.1  mrg         {
   1433  1.1  mrg             if (!ad.ident)
   1434  1.1  mrg                 return;
   1435  1.1  mrg             version (none)
   1436  1.1  mrg             {
   1437  1.1  mrg                 emitVisibility(buf, ad);
   1438  1.1  mrg             }
   1439  1.1  mrg             buf.printf("%s %s", ad.kind(), ad.toChars());
   1440  1.1  mrg             buf.writestring(";\n");
   1441  1.1  mrg         }
   1442  1.1  mrg 
   1443  1.1  mrg         override void visit(StructDeclaration sd)
   1444  1.1  mrg         {
   1445  1.1  mrg             //printf("StructDeclaration::toDocbuffer() %s\n", sd.toChars());
   1446  1.1  mrg             if (!sd.ident)
   1447  1.1  mrg                 return;
   1448  1.1  mrg             version (none)
   1449  1.1  mrg             {
   1450  1.1  mrg                 emitVisibility(buf, sd);
   1451  1.1  mrg             }
   1452  1.1  mrg             if (TemplateDeclaration td = getEponymousParent(sd))
   1453  1.1  mrg             {
   1454  1.1  mrg                 toDocBuffer(td, *buf, sc);
   1455  1.1  mrg             }
   1456  1.1  mrg             else
   1457  1.1  mrg             {
   1458  1.1  mrg                 buf.printf("%s %s", sd.kind(), sd.toChars());
   1459  1.1  mrg             }
   1460  1.1  mrg             buf.writestring(";\n");
   1461  1.1  mrg         }
   1462  1.1  mrg 
   1463  1.1  mrg         override void visit(ClassDeclaration cd)
   1464  1.1  mrg         {
   1465  1.1  mrg             //printf("ClassDeclaration::toDocbuffer() %s\n", cd.toChars());
   1466  1.1  mrg             if (!cd.ident)
   1467  1.1  mrg                 return;
   1468  1.1  mrg             version (none)
   1469  1.1  mrg             {
   1470  1.1  mrg                 emitVisibility(*buf, cd);
   1471  1.1  mrg             }
   1472  1.1  mrg             if (TemplateDeclaration td = getEponymousParent(cd))
   1473  1.1  mrg             {
   1474  1.1  mrg                 toDocBuffer(td, *buf, sc);
   1475  1.1  mrg             }
   1476  1.1  mrg             else
   1477  1.1  mrg             {
   1478  1.1  mrg                 if (!cd.isInterfaceDeclaration() && cd.isAbstract())
   1479  1.1  mrg                     buf.writestring("abstract ");
   1480  1.1  mrg                 buf.printf("%s %s", cd.kind(), cd.toChars());
   1481  1.1  mrg             }
   1482  1.1  mrg             int any = 0;
   1483  1.1  mrg             for (size_t i = 0; i < cd.baseclasses.dim; i++)
   1484  1.1  mrg             {
   1485  1.1  mrg                 BaseClass* bc = (*cd.baseclasses)[i];
   1486  1.1  mrg                 if (bc.sym && bc.sym.ident == Id.Object)
   1487  1.1  mrg                     continue;
   1488  1.1  mrg                 if (any)
   1489  1.1  mrg                     buf.writestring(", ");
   1490  1.1  mrg                 else
   1491  1.1  mrg                 {
   1492  1.1  mrg                     buf.writestring(": ");
   1493  1.1  mrg                     any = 1;
   1494  1.1  mrg                 }
   1495  1.1  mrg 
   1496  1.1  mrg                 if (bc.sym)
   1497  1.1  mrg                 {
   1498  1.1  mrg                     buf.printf("$(DDOC_PSUPER_SYMBOL %s)", bc.sym.toPrettyChars());
   1499  1.1  mrg                 }
   1500  1.1  mrg                 else
   1501  1.1  mrg                 {
   1502  1.1  mrg                     HdrGenState hgs;
   1503  1.1  mrg                     .toCBuffer(bc.type, buf, null, &hgs);
   1504  1.1  mrg                 }
   1505  1.1  mrg             }
   1506  1.1  mrg             buf.writestring(";\n");
   1507  1.1  mrg         }
   1508  1.1  mrg 
   1509  1.1  mrg         override void visit(EnumDeclaration ed)
   1510  1.1  mrg         {
   1511  1.1  mrg             if (!ed.ident)
   1512  1.1  mrg                 return;
   1513  1.1  mrg             buf.printf("%s %s", ed.kind(), ed.toChars());
   1514  1.1  mrg             if (ed.memtype)
   1515  1.1  mrg             {
   1516  1.1  mrg                 buf.writestring(": $(DDOC_ENUM_BASETYPE ");
   1517  1.1  mrg                 HdrGenState hgs;
   1518  1.1  mrg                 .toCBuffer(ed.memtype, buf, null, &hgs);
   1519  1.1  mrg                 buf.writestring(")");
   1520  1.1  mrg             }
   1521  1.1  mrg             buf.writestring(";\n");
   1522  1.1  mrg         }
   1523  1.1  mrg 
   1524  1.1  mrg         override void visit(EnumMember em)
   1525  1.1  mrg         {
   1526  1.1  mrg             if (!em.ident)
   1527  1.1  mrg                 return;
   1528  1.1  mrg             buf.writestring(em.toChars());
   1529  1.1  mrg         }
   1530  1.1  mrg     }
   1531  1.1  mrg 
   1532  1.1  mrg     scope ToDocBuffer v = new ToDocBuffer(buf, sc);
   1533  1.1  mrg     s.accept(v);
   1534  1.1  mrg }
   1535  1.1  mrg 
   1536  1.1  mrg /***********************************************************
   1537  1.1  mrg  */
   1538  1.1  mrg struct DocComment
   1539  1.1  mrg {
   1540  1.1  mrg     Sections sections;      // Section*[]
   1541  1.1  mrg     Section summary;
   1542  1.1  mrg     Section copyright;
   1543  1.1  mrg     Section macros;
   1544  1.1  mrg     MacroTable* pmacrotable;
   1545  1.1  mrg     Escape* escapetable;
   1546  1.1  mrg     Dsymbols a;
   1547  1.1  mrg 
   1548  1.1  mrg     static DocComment* parse(Dsymbol s, const(char)* comment)
   1549  1.1  mrg     {
   1550  1.1  mrg         //printf("parse(%s): '%s'\n", s.toChars(), comment);
   1551  1.1  mrg         auto dc = new DocComment();
   1552  1.1  mrg         dc.a.push(s);
   1553  1.1  mrg         if (!comment)
   1554  1.1  mrg             return dc;
   1555  1.1  mrg         dc.parseSections(comment);
   1556  1.1  mrg         for (size_t i = 0; i < dc.sections.dim; i++)
   1557  1.1  mrg         {
   1558  1.1  mrg             Section sec = dc.sections[i];
   1559  1.1  mrg             if (iequals("copyright", sec.name))
   1560  1.1  mrg             {
   1561  1.1  mrg                 dc.copyright = sec;
   1562  1.1  mrg             }
   1563  1.1  mrg             if (iequals("macros", sec.name))
   1564  1.1  mrg             {
   1565  1.1  mrg                 dc.macros = sec;
   1566  1.1  mrg             }
   1567  1.1  mrg         }
   1568  1.1  mrg         return dc;
   1569  1.1  mrg     }
   1570  1.1  mrg 
   1571  1.1  mrg     /************************************************
   1572  1.1  mrg      * Parse macros out of Macros: section.
   1573  1.1  mrg      * Macros are of the form:
   1574  1.1  mrg      *      name1 = value1
   1575  1.1  mrg      *
   1576  1.1  mrg      *      name2 = value2
   1577  1.1  mrg      */
   1578  1.1  mrg     extern(D) static void parseMacros(
   1579  1.1  mrg         Escape* escapetable, ref MacroTable pmacrotable, const(char)[] m)
   1580  1.1  mrg     {
   1581  1.1  mrg         const(char)* p = m.ptr;
   1582  1.1  mrg         size_t len = m.length;
   1583  1.1  mrg         const(char)* pend = p + len;
   1584  1.1  mrg         const(char)* tempstart = null;
   1585  1.1  mrg         size_t templen = 0;
   1586  1.1  mrg         const(char)* namestart = null;
   1587  1.1  mrg         size_t namelen = 0; // !=0 if line continuation
   1588  1.1  mrg         const(char)* textstart = null;
   1589  1.1  mrg         size_t textlen = 0;
   1590  1.1  mrg         while (p < pend)
   1591  1.1  mrg         {
   1592  1.1  mrg             // Skip to start of macro
   1593  1.1  mrg             while (1)
   1594  1.1  mrg             {
   1595  1.1  mrg                 if (p >= pend)
   1596  1.1  mrg                     goto Ldone;
   1597  1.1  mrg                 switch (*p)
   1598  1.1  mrg                 {
   1599  1.1  mrg                 case ' ':
   1600  1.1  mrg                 case '\t':
   1601  1.1  mrg                     p++;
   1602  1.1  mrg                     continue;
   1603  1.1  mrg                 case '\r':
   1604  1.1  mrg                 case '\n':
   1605  1.1  mrg                     p++;
   1606  1.1  mrg                     goto Lcont;
   1607  1.1  mrg                 default:
   1608  1.1  mrg                     if (isIdStart(p))
   1609  1.1  mrg                         break;
   1610  1.1  mrg                     if (namelen)
   1611  1.1  mrg                         goto Ltext; // continuation of prev macro
   1612  1.1  mrg                     goto Lskipline;
   1613  1.1  mrg                 }
   1614  1.1  mrg                 break;
   1615  1.1  mrg             }
   1616  1.1  mrg             tempstart = p;
   1617  1.1  mrg             while (1)
   1618  1.1  mrg             {
   1619  1.1  mrg                 if (p >= pend)
   1620  1.1  mrg                     goto Ldone;
   1621  1.1  mrg                 if (!isIdTail(p))
   1622  1.1  mrg                     break;
   1623  1.1  mrg                 p += utfStride(p);
   1624  1.1  mrg             }
   1625  1.1  mrg             templen = p - tempstart;
   1626  1.1  mrg             while (1)
   1627  1.1  mrg             {
   1628  1.1  mrg                 if (p >= pend)
   1629  1.1  mrg                     goto Ldone;
   1630  1.1  mrg                 if (!(*p == ' ' || *p == '\t'))
   1631  1.1  mrg                     break;
   1632  1.1  mrg                 p++;
   1633  1.1  mrg             }
   1634  1.1  mrg             if (*p != '=')
   1635  1.1  mrg             {
   1636  1.1  mrg                 if (namelen)
   1637  1.1  mrg                     goto Ltext; // continuation of prev macro
   1638  1.1  mrg                 goto Lskipline;
   1639  1.1  mrg             }
   1640  1.1  mrg             p++;
   1641  1.1  mrg             if (p >= pend)
   1642  1.1  mrg                 goto Ldone;
   1643  1.1  mrg             if (namelen)
   1644  1.1  mrg             {
   1645  1.1  mrg                 // Output existing macro
   1646  1.1  mrg             L1:
   1647  1.1  mrg                 //printf("macro '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart);
   1648  1.1  mrg                 if (iequals("ESCAPES", namestart[0 .. namelen]))
   1649  1.1  mrg                     parseEscapes(escapetable, textstart[0 .. textlen]);
   1650  1.1  mrg                 else
   1651  1.1  mrg                     pmacrotable.define(namestart[0 .. namelen], textstart[0 .. textlen]);
   1652  1.1  mrg                 namelen = 0;
   1653  1.1  mrg                 if (p >= pend)
   1654  1.1  mrg                     break;
   1655  1.1  mrg             }
   1656  1.1  mrg             namestart = tempstart;
   1657  1.1  mrg             namelen = templen;
   1658  1.1  mrg             while (p < pend && (*p == ' ' || *p == '\t'))
   1659  1.1  mrg                 p++;
   1660  1.1  mrg             textstart = p;
   1661  1.1  mrg         Ltext:
   1662  1.1  mrg             while (p < pend && *p != '\r' && *p != '\n')
   1663  1.1  mrg                 p++;
   1664  1.1  mrg             textlen = p - textstart;
   1665  1.1  mrg             p++;
   1666  1.1  mrg             //printf("p = %p, pend = %p\n", p, pend);
   1667  1.1  mrg         Lcont:
   1668  1.1  mrg             continue;
   1669  1.1  mrg         Lskipline:
   1670  1.1  mrg             // Ignore this line
   1671  1.1  mrg             while (p < pend && *p != '\r' && *p != '\n')
   1672  1.1  mrg                 p++;
   1673  1.1  mrg         }
   1674  1.1  mrg     Ldone:
   1675  1.1  mrg         if (namelen)
   1676  1.1  mrg             goto L1; // write out last one
   1677  1.1  mrg     }
   1678  1.1  mrg 
   1679  1.1  mrg     /**************************************
   1680  1.1  mrg      * Parse escapes of the form:
   1681  1.1  mrg      *      /c/string/
   1682  1.1  mrg      * where c is a single character.
   1683  1.1  mrg      * Multiple escapes can be separated
   1684  1.1  mrg      * by whitespace and/or commas.
   1685  1.1  mrg      */
   1686  1.1  mrg     static void parseEscapes(Escape* escapetable, const(char)[] text)
   1687  1.1  mrg     {
   1688  1.1  mrg         if (!escapetable)
   1689  1.1  mrg         {
   1690  1.1  mrg             escapetable = new Escape();
   1691  1.1  mrg             memset(escapetable, 0, Escape.sizeof);
   1692  1.1  mrg         }
   1693  1.1  mrg         //printf("parseEscapes('%.*s') pescapetable = %p\n", cast(int)text.length, text.ptr, escapetable);
   1694  1.1  mrg         const(char)* p = text.ptr;
   1695  1.1  mrg         const(char)* pend = p + text.length;
   1696  1.1  mrg         while (1)
   1697  1.1  mrg         {
   1698  1.1  mrg             while (1)
   1699  1.1  mrg             {
   1700  1.1  mrg                 if (p + 4 >= pend)
   1701  1.1  mrg                     return;
   1702  1.1  mrg                 if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ','))
   1703  1.1  mrg                     break;
   1704  1.1  mrg                 p++;
   1705  1.1  mrg             }
   1706  1.1  mrg             if (p[0] != '/' || p[2] != '/')
   1707  1.1  mrg                 return;
   1708  1.1  mrg             char c = p[1];
   1709  1.1  mrg             p += 3;
   1710  1.1  mrg             const(char)* start = p;
   1711  1.1  mrg             while (1)
   1712  1.1  mrg             {
   1713  1.1  mrg                 if (p >= pend)
   1714  1.1  mrg                     return;
   1715  1.1  mrg                 if (*p == '/')
   1716  1.1  mrg                     break;
   1717  1.1  mrg                 p++;
   1718  1.1  mrg             }
   1719  1.1  mrg             size_t len = p - start;
   1720  1.1  mrg             char* s = cast(char*)memcpy(mem.xmalloc(len + 1), start, len);
   1721  1.1  mrg             s[len] = 0;
   1722  1.1  mrg             escapetable.strings[c] = s[0 .. len];
   1723  1.1  mrg             //printf("\t%c = '%s'\n", c, s);
   1724  1.1  mrg             p++;
   1725  1.1  mrg         }
   1726  1.1  mrg     }
   1727  1.1  mrg 
   1728  1.1  mrg     /*****************************************
   1729  1.1  mrg      * Parse next paragraph out of *pcomment.
   1730  1.1  mrg      * Update *pcomment to point past paragraph.
   1731  1.1  mrg      * Returns NULL if no more paragraphs.
   1732  1.1  mrg      * If paragraph ends in 'identifier:',
   1733  1.1  mrg      * then (*pcomment)[0 .. idlen] is the identifier.
   1734  1.1  mrg      */
   1735  1.1  mrg     void parseSections(const(char)* comment)
   1736  1.1  mrg     {
   1737  1.1  mrg         const(char)* p;
   1738  1.1  mrg         const(char)* pstart;
   1739  1.1  mrg         const(char)* pend;
   1740  1.1  mrg         const(char)* idstart = null; // dead-store to prevent spurious warning
   1741  1.1  mrg         size_t idlen;
   1742  1.1  mrg         const(char)* name = null;
   1743  1.1  mrg         size_t namelen = 0;
   1744  1.1  mrg         //printf("parseSections('%s')\n", comment);
   1745  1.1  mrg         p = comment;
   1746  1.1  mrg         while (*p)
   1747  1.1  mrg         {
   1748  1.1  mrg             const(char)* pstart0 = p;
   1749  1.1  mrg             p = skipwhitespace(p);
   1750  1.1  mrg             pstart = p;
   1751  1.1  mrg             pend = p;
   1752  1.1  mrg 
   1753  1.1  mrg             // Undo indent if starting with a list item
   1754  1.1  mrg             if ((*p == '-' || *p == '+' || *p == '*') && (*(p+1) == ' ' || *(p+1) == '\t'))
   1755  1.1  mrg                 pstart = pstart0;
   1756  1.1  mrg             else
   1757  1.1  mrg             {
   1758  1.1  mrg                 const(char)* pitem = p;
   1759  1.1  mrg                 while (*pitem >= '0' && *pitem <= '9')
   1760  1.1  mrg                     ++pitem;
   1761  1.1  mrg                 if (pitem > p && *pitem == '.' && (*(pitem+1) == ' ' || *(pitem+1) == '\t'))
   1762  1.1  mrg                     pstart = pstart0;
   1763  1.1  mrg             }
   1764  1.1  mrg 
   1765  1.1  mrg             /* Find end of section, which is ended by one of:
   1766  1.1  mrg              *      'identifier:' (but not inside a code section)
   1767  1.1  mrg              *      '\0'
   1768  1.1  mrg              */
   1769  1.1  mrg             idlen = 0;
   1770  1.1  mrg             int inCode = 0;
   1771  1.1  mrg             while (1)
   1772  1.1  mrg             {
   1773  1.1  mrg                 // Check for start/end of a code section
   1774  1.1  mrg                 if (*p == '-' || *p == '`' || *p == '~')
   1775  1.1  mrg                 {
   1776  1.1  mrg                     char c = *p;
   1777  1.1  mrg                     int numdash = 0;
   1778  1.1  mrg                     while (*p == c)
   1779  1.1  mrg                     {
   1780  1.1  mrg                         ++numdash;
   1781  1.1  mrg                         p++;
   1782  1.1  mrg                     }
   1783  1.1  mrg                     // BUG: handle UTF PS and LS too
   1784  1.1  mrg                     if ((!*p || *p == '\r' || *p == '\n' || (!inCode && c != '-')) && numdash >= 3)
   1785  1.1  mrg                     {
   1786  1.1  mrg                         inCode = inCode == c ? false : c;
   1787  1.1  mrg                         if (inCode)
   1788  1.1  mrg                         {
   1789  1.1  mrg                             // restore leading indentation
   1790  1.1  mrg                             while (pstart0 < pstart && isIndentWS(pstart - 1))
   1791  1.1  mrg                                 --pstart;
   1792  1.1  mrg                         }
   1793  1.1  mrg                     }
   1794  1.1  mrg                     pend = p;
   1795  1.1  mrg                 }
   1796  1.1  mrg                 if (!inCode && isIdStart(p))
   1797  1.1  mrg                 {
   1798  1.1  mrg                     const(char)* q = p + utfStride(p);
   1799  1.1  mrg                     while (isIdTail(q))
   1800  1.1  mrg                         q += utfStride(q);
   1801  1.1  mrg 
   1802  1.1  mrg                     // Detected tag ends it
   1803  1.1  mrg                     if (*q == ':' && isupper(*p)
   1804  1.1  mrg                             && (isspace(q[1]) || q[1] == 0))
   1805  1.1  mrg                     {
   1806  1.1  mrg                         idlen = q - p;
   1807  1.1  mrg                         idstart = p;
   1808  1.1  mrg                         for (pend = p; pend > pstart; pend--)
   1809  1.1  mrg                         {
   1810  1.1  mrg                             if (pend[-1] == '\n')
   1811  1.1  mrg                                 break;
   1812  1.1  mrg                         }
   1813  1.1  mrg                         p = q + 1;
   1814  1.1  mrg                         break;
   1815  1.1  mrg                     }
   1816  1.1  mrg                 }
   1817  1.1  mrg                 while (1)
   1818  1.1  mrg                 {
   1819  1.1  mrg                     if (!*p)
   1820  1.1  mrg                         goto L1;
   1821  1.1  mrg                     if (*p == '\n')
   1822  1.1  mrg                     {
   1823  1.1  mrg                         p++;
   1824  1.1  mrg                         if (*p == '\n' && !summary && !namelen && !inCode)
   1825  1.1  mrg                         {
   1826  1.1  mrg                             pend = p;
   1827  1.1  mrg                             p++;
   1828  1.1  mrg                             goto L1;
   1829  1.1  mrg                         }
   1830  1.1  mrg                         break;
   1831  1.1  mrg                     }
   1832  1.1  mrg                     p++;
   1833  1.1  mrg                     pend = p;
   1834  1.1  mrg                 }
   1835  1.1  mrg                 p = skipwhitespace(p);
   1836  1.1  mrg             }
   1837  1.1  mrg         L1:
   1838  1.1  mrg             if (namelen || pstart < pend)
   1839  1.1  mrg             {
   1840  1.1  mrg                 Section s;
   1841  1.1  mrg                 if (iequals("Params", name[0 .. namelen]))
   1842  1.1  mrg                     s = new ParamSection();
   1843  1.1  mrg                 else if (iequals("Macros", name[0 .. namelen]))
   1844  1.1  mrg                     s = new MacroSection();
   1845  1.1  mrg                 else
   1846  1.1  mrg                     s = new Section();
   1847  1.1  mrg                 s.name = name[0 .. namelen];
   1848  1.1  mrg                 s.body_ = pstart[0 .. pend - pstart];
   1849  1.1  mrg                 s.nooutput = 0;
   1850  1.1  mrg                 //printf("Section: '%.*s' = '%.*s'\n", cast(int)s.namelen, s.name, cast(int)s.bodylen, s.body);
   1851  1.1  mrg                 sections.push(s);
   1852  1.1  mrg                 if (!summary && !namelen)
   1853  1.1  mrg                     summary = s;
   1854  1.1  mrg             }
   1855  1.1  mrg             if (idlen)
   1856  1.1  mrg             {
   1857  1.1  mrg                 name = idstart;
   1858  1.1  mrg                 namelen = idlen;
   1859  1.1  mrg             }
   1860  1.1  mrg             else
   1861  1.1  mrg             {
   1862  1.1  mrg                 name = null;
   1863  1.1  mrg                 namelen = 0;
   1864  1.1  mrg                 if (!*p)
   1865  1.1  mrg                     break;
   1866  1.1  mrg             }
   1867  1.1  mrg         }
   1868  1.1  mrg     }
   1869  1.1  mrg 
   1870  1.1  mrg     void writeSections(Scope* sc, Dsymbols* a, OutBuffer* buf)
   1871  1.1  mrg     {
   1872  1.1  mrg         assert(a.dim);
   1873  1.1  mrg         //printf("DocComment::writeSections()\n");
   1874  1.1  mrg         Loc loc = (*a)[0].loc;
   1875  1.1  mrg         if (Module m = (*a)[0].isModule())
   1876  1.1  mrg         {
   1877  1.1  mrg             if (m.md)
   1878  1.1  mrg                 loc = m.md.loc;
   1879  1.1  mrg         }
   1880  1.1  mrg         size_t offset1 = buf.length;
   1881  1.1  mrg         buf.writestring("$(DDOC_SECTIONS ");
   1882  1.1  mrg         size_t offset2 = buf.length;
   1883  1.1  mrg         for (size_t i = 0; i < sections.dim; i++)
   1884  1.1  mrg         {
   1885  1.1  mrg             Section sec = sections[i];
   1886  1.1  mrg             if (sec.nooutput)
   1887  1.1  mrg                 continue;
   1888  1.1  mrg             //printf("Section: '%.*s' = '%.*s'\n", cast(int)sec.namelen, sec.name, cast(int)sec.bodylen, sec.body);
   1889  1.1  mrg             if (!sec.name.length && i == 0)
   1890  1.1  mrg             {
   1891  1.1  mrg                 buf.writestring("$(DDOC_SUMMARY ");
   1892  1.1  mrg                 size_t o = buf.length;
   1893  1.1  mrg                 buf.write(sec.body_);
   1894  1.1  mrg                 escapeStrayParenthesis(loc, buf, o, true);
   1895  1.1  mrg                 highlightText(sc, a, loc, *buf, o);
   1896  1.1  mrg                 buf.writestring(")");
   1897  1.1  mrg             }
   1898  1.1  mrg             else
   1899  1.1  mrg                 sec.write(loc, &this, sc, a, buf);
   1900  1.1  mrg         }
   1901  1.1  mrg         for (size_t i = 0; i < a.dim; i++)
   1902  1.1  mrg         {
   1903  1.1  mrg             Dsymbol s = (*a)[i];
   1904  1.1  mrg             if (Dsymbol td = getEponymousParent(s))
   1905  1.1  mrg                 s = td;
   1906  1.1  mrg             for (UnitTestDeclaration utd = s.ddocUnittest; utd; utd = utd.ddocUnittest)
   1907  1.1  mrg             {
   1908  1.1  mrg                 if (utd.visibility.kind == Visibility.Kind.private_ || !utd.comment || !utd.fbody)
   1909  1.1  mrg                     continue;
   1910  1.1  mrg                 // Strip whitespaces to avoid showing empty summary
   1911  1.1  mrg                 const(char)* c = utd.comment;
   1912  1.1  mrg                 while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')
   1913  1.1  mrg                     ++c;
   1914  1.1  mrg                 buf.writestring("$(DDOC_EXAMPLES ");
   1915  1.1  mrg                 size_t o = buf.length;
   1916  1.1  mrg                 buf.writestring(cast(char*)c);
   1917  1.1  mrg                 if (utd.codedoc)
   1918  1.1  mrg                 {
   1919  1.1  mrg                     auto codedoc = utd.codedoc.stripLeadingNewlines;
   1920  1.1  mrg                     size_t n = getCodeIndent(codedoc);
   1921  1.1  mrg                     while (n--)
   1922  1.1  mrg                         buf.writeByte(' ');
   1923  1.1  mrg                     buf.writestring("----\n");
   1924  1.1  mrg                     buf.writestring(codedoc);
   1925  1.1  mrg                     buf.writestring("----\n");
   1926  1.1  mrg                     highlightText(sc, a, loc, *buf, o);
   1927  1.1  mrg                 }
   1928  1.1  mrg                 buf.writestring(")");
   1929  1.1  mrg             }
   1930  1.1  mrg         }
   1931  1.1  mrg         if (buf.length == offset2)
   1932  1.1  mrg         {
   1933  1.1  mrg             /* Didn't write out any sections, so back out last write
   1934  1.1  mrg              */
   1935  1.1  mrg             buf.setsize(offset1);
   1936  1.1  mrg             buf.writestring("\n");
   1937  1.1  mrg         }
   1938  1.1  mrg         else
   1939  1.1  mrg             buf.writestring(")");
   1940  1.1  mrg     }
   1941  1.1  mrg }
   1942  1.1  mrg 
   1943  1.1  mrg /*****************************************
   1944  1.1  mrg  * Return true if comment consists entirely of "ditto".
   1945  1.1  mrg  */
   1946  1.1  mrg private bool isDitto(const(char)* comment)
   1947  1.1  mrg {
   1948  1.1  mrg     if (comment)
   1949  1.1  mrg     {
   1950  1.1  mrg         const(char)* p = skipwhitespace(comment);
   1951  1.1  mrg         if (Port.memicmp(p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
   1952  1.1  mrg             return true;
   1953  1.1  mrg     }
   1954  1.1  mrg     return false;
   1955  1.1  mrg }
   1956  1.1  mrg 
   1957  1.1  mrg /**********************************************
   1958  1.1  mrg  * Skip white space.
   1959  1.1  mrg  */
   1960  1.1  mrg private const(char)* skipwhitespace(const(char)* p)
   1961  1.1  mrg {
   1962  1.1  mrg     return skipwhitespace(p.toDString).ptr;
   1963  1.1  mrg }
   1964  1.1  mrg 
   1965  1.1  mrg /// Ditto
   1966  1.1  mrg private const(char)[] skipwhitespace(const(char)[] p)
   1967  1.1  mrg {
   1968  1.1  mrg     foreach (idx, char c; p)
   1969  1.1  mrg     {
   1970  1.1  mrg         switch (c)
   1971  1.1  mrg         {
   1972  1.1  mrg         case ' ':
   1973  1.1  mrg         case '\t':
   1974  1.1  mrg         case '\n':
   1975  1.1  mrg             continue;
   1976  1.1  mrg         default:
   1977  1.1  mrg             return p[idx .. $];
   1978  1.1  mrg         }
   1979  1.1  mrg     }
   1980  1.1  mrg     return p[$ .. $];
   1981  1.1  mrg }
   1982  1.1  mrg 
   1983  1.1  mrg /************************************************
   1984  1.1  mrg  * Scan past all instances of the given characters.
   1985  1.1  mrg  * Params:
   1986  1.1  mrg  *  buf           = an OutBuffer containing the DDoc
   1987  1.1  mrg  *  i             = the index within `buf` to start scanning from
   1988  1.1  mrg  *  chars         = the characters to skip; order is unimportant
   1989  1.1  mrg  * Returns: the index after skipping characters.
   1990  1.1  mrg  */
   1991  1.1  mrg private size_t skipChars(ref OutBuffer buf, size_t i, string chars)
   1992  1.1  mrg {
   1993  1.1  mrg     Outer:
   1994  1.1  mrg     foreach (j, c; buf[][i..$])
   1995  1.1  mrg     {
   1996  1.1  mrg         foreach (d; chars)
   1997  1.1  mrg         {
   1998  1.1  mrg             if (d == c)
   1999  1.1  mrg                 continue Outer;
   2000  1.1  mrg         }
   2001  1.1  mrg         return i + j;
   2002  1.1  mrg     }
   2003  1.1  mrg     return buf.length;
   2004  1.1  mrg }
   2005  1.1  mrg 
   2006  1.1  mrg unittest {
   2007  1.1  mrg     OutBuffer buf;
   2008  1.1  mrg     string data = "test ---\r\n\r\nend";
   2009  1.1  mrg     buf.write(data);
   2010  1.1  mrg 
   2011  1.1  mrg     assert(skipChars(buf, 0, "-") == 0);
   2012  1.1  mrg     assert(skipChars(buf, 4, "-") == 4);
   2013  1.1  mrg     assert(skipChars(buf, 4, " -") == 8);
   2014  1.1  mrg     assert(skipChars(buf, 8, "\r\n") == 12);
   2015  1.1  mrg     assert(skipChars(buf, 12, "dne") == 15);
   2016  1.1  mrg }
   2017  1.1  mrg 
   2018  1.1  mrg /****************************************************
   2019  1.1  mrg  * Replace all instances of `c` with `r` in the given string
   2020  1.1  mrg  * Params:
   2021  1.1  mrg  *  s = the string to do replacements in
   2022  1.1  mrg  *  c = the character to look for
   2023  1.1  mrg  *  r = the string to replace `c` with
   2024  1.1  mrg  * Returns: `s` with `c` replaced with `r`
   2025  1.1  mrg  */
   2026  1.1  mrg private inout(char)[] replaceChar(inout(char)[] s, char c, string r) pure
   2027  1.1  mrg {
   2028  1.1  mrg     int count = 0;
   2029  1.1  mrg     foreach (char sc; s)
   2030  1.1  mrg         if (sc == c)
   2031  1.1  mrg             ++count;
   2032  1.1  mrg     if (count == 0)
   2033  1.1  mrg         return s;
   2034  1.1  mrg 
   2035  1.1  mrg     char[] result;
   2036  1.1  mrg     result.reserve(s.length - count + (r.length * count));
   2037  1.1  mrg     size_t start = 0;
   2038  1.1  mrg     foreach (i, char sc; s)
   2039  1.1  mrg     {
   2040  1.1  mrg         if (sc == c)
   2041  1.1  mrg         {
   2042  1.1  mrg             result ~= s[start..i];
   2043  1.1  mrg             result ~= r;
   2044  1.1  mrg             start = i+1;
   2045  1.1  mrg         }
   2046  1.1  mrg     }
   2047  1.1  mrg     result ~= s[start..$];
   2048  1.1  mrg     return result;
   2049  1.1  mrg }
   2050  1.1  mrg 
   2051  1.1  mrg ///
   2052  1.1  mrg unittest
   2053  1.1  mrg {
   2054  1.1  mrg     assert("".replaceChar(',', "$(COMMA)") == "");
   2055  1.1  mrg     assert("ab".replaceChar(',', "$(COMMA)") == "ab");
   2056  1.1  mrg     assert("a,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)b");
   2057  1.1  mrg     assert("a,,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)$(COMMA)b");
   2058  1.1  mrg     assert(",ab".replaceChar(',', "$(COMMA)") == "$(COMMA)ab");
   2059  1.1  mrg     assert("ab,".replaceChar(',', "$(COMMA)") == "ab$(COMMA)");
   2060  1.1  mrg }
   2061  1.1  mrg 
   2062  1.1  mrg /**
   2063  1.1  mrg  * Return a lowercased copy of a string.
   2064  1.1  mrg  * Params:
   2065  1.1  mrg  *  s = the string to lowercase
   2066  1.1  mrg  * Returns: the lowercase version of the string or the original if already lowercase
   2067  1.1  mrg  */
   2068  1.1  mrg private string toLowercase(string s) pure
   2069  1.1  mrg {
   2070  1.1  mrg     string lower;
   2071  1.1  mrg     foreach (size_t i; 0..s.length)
   2072  1.1  mrg     {
   2073  1.1  mrg         char c = s[i];
   2074  1.1  mrg // TODO: maybe unicode lowercase, somehow
   2075  1.1  mrg         if (c >= 'A' && c <= 'Z')
   2076  1.1  mrg         {
   2077  1.1  mrg             if (!lower.length) {
   2078  1.1  mrg                 lower.reserve(s.length);
   2079  1.1  mrg             }
   2080  1.1  mrg             lower ~= s[lower.length..i];
   2081  1.1  mrg             c += 'a' - 'A';
   2082  1.1  mrg             lower ~= c;
   2083  1.1  mrg         }
   2084  1.1  mrg     }
   2085  1.1  mrg     if (lower.length)
   2086  1.1  mrg         lower ~= s[lower.length..$];
   2087  1.1  mrg     else
   2088  1.1  mrg         lower = s;
   2089  1.1  mrg     return lower;
   2090  1.1  mrg }
   2091  1.1  mrg 
   2092  1.1  mrg ///
   2093  1.1  mrg unittest
   2094  1.1  mrg {
   2095  1.1  mrg     assert("".toLowercase == "");
   2096  1.1  mrg     assert("abc".toLowercase == "abc");
   2097  1.1  mrg     assert("ABC".toLowercase == "abc");
   2098  1.1  mrg     assert("aBc".toLowercase == "abc");
   2099  1.1  mrg }
   2100  1.1  mrg 
   2101  1.1  mrg /************************************************
   2102  1.1  mrg  * Get the indent from one index to another, counting tab stops as four spaces wide
   2103  1.1  mrg  * per the Markdown spec.
   2104  1.1  mrg  * Params:
   2105  1.1  mrg  *  buf   = an OutBuffer containing the DDoc
   2106  1.1  mrg  *  from  = the index within `buf` to start counting from, inclusive
   2107  1.1  mrg  *  to    = the index within `buf` to stop counting at, exclusive
   2108  1.1  mrg  * Returns: the indent
   2109  1.1  mrg  */
   2110  1.1  mrg private int getMarkdownIndent(ref OutBuffer buf, size_t from, size_t to)
   2111  1.1  mrg {
   2112  1.1  mrg     const slice = buf[];
   2113  1.1  mrg     if (to > slice.length)
   2114  1.1  mrg         to = slice.length;
   2115  1.1  mrg     int indent = 0;
   2116  1.1  mrg     foreach (const c; slice[from..to])
   2117  1.1  mrg         indent += (c == '\t') ? 4 - (indent % 4) : 1;
   2118  1.1  mrg     return indent;
   2119  1.1  mrg }
   2120  1.1  mrg 
   2121  1.1  mrg /************************************************
   2122  1.1  mrg  * Scan forward to one of:
   2123  1.1  mrg  *      start of identifier
   2124  1.1  mrg  *      beginning of next line
   2125  1.1  mrg  *      end of buf
   2126  1.1  mrg  */
   2127  1.1  mrg size_t skiptoident(ref OutBuffer buf, size_t i)
   2128  1.1  mrg {
   2129  1.1  mrg     const slice = buf[];
   2130  1.1  mrg     while (i < slice.length)
   2131  1.1  mrg     {
   2132  1.1  mrg         dchar c;
   2133  1.1  mrg         size_t oi = i;
   2134  1.1  mrg         if (utf_decodeChar(slice, i, c))
   2135  1.1  mrg         {
   2136  1.1  mrg             /* Ignore UTF errors, but still consume input
   2137  1.1  mrg              */
   2138  1.1  mrg             break;
   2139  1.1  mrg         }
   2140  1.1  mrg         if (c >= 0x80)
   2141  1.1  mrg         {
   2142  1.1  mrg             if (!isUniAlpha(c))
   2143  1.1  mrg                 continue;
   2144  1.1  mrg         }
   2145  1.1  mrg         else if (!(isalpha(c) || c == '_' || c == '\n'))
   2146  1.1  mrg             continue;
   2147  1.1  mrg         i = oi;
   2148  1.1  mrg         break;
   2149  1.1  mrg     }
   2150  1.1  mrg     return i;
   2151  1.1  mrg }
   2152  1.1  mrg 
   2153  1.1  mrg /************************************************
   2154  1.1  mrg  * Scan forward past end of identifier.
   2155  1.1  mrg  */
   2156  1.1  mrg private size_t skippastident(ref OutBuffer buf, size_t i)
   2157  1.1  mrg {
   2158  1.1  mrg     const slice = buf[];
   2159  1.1  mrg     while (i < slice.length)
   2160  1.1  mrg     {
   2161  1.1  mrg         dchar c;
   2162  1.1  mrg         size_t oi = i;
   2163  1.1  mrg         if (utf_decodeChar(slice, i, c))
   2164  1.1  mrg         {
   2165  1.1  mrg             /* Ignore UTF errors, but still consume input
   2166  1.1  mrg              */
   2167  1.1  mrg             break;
   2168  1.1  mrg         }
   2169  1.1  mrg         if (c >= 0x80)
   2170  1.1  mrg         {
   2171  1.1  mrg             if (isUniAlpha(c))
   2172  1.1  mrg                 continue;
   2173  1.1  mrg         }
   2174  1.1  mrg         else if (isalnum(c) || c == '_')
   2175  1.1  mrg             continue;
   2176  1.1  mrg         i = oi;
   2177  1.1  mrg         break;
   2178  1.1  mrg     }
   2179  1.1  mrg     return i;
   2180  1.1  mrg }
   2181  1.1  mrg 
   2182  1.1  mrg /************************************************
   2183  1.1  mrg  * Scan forward past end of an identifier that might
   2184  1.1  mrg  * contain dots (e.g. `abc.def`)
   2185  1.1  mrg  */
   2186  1.1  mrg private size_t skipPastIdentWithDots(ref OutBuffer buf, size_t i)
   2187  1.1  mrg {
   2188  1.1  mrg     const slice = buf[];
   2189  1.1  mrg     bool lastCharWasDot;
   2190  1.1  mrg     while (i < slice.length)
   2191  1.1  mrg     {
   2192  1.1  mrg         dchar c;
   2193  1.1  mrg         size_t oi = i;
   2194  1.1  mrg         if (utf_decodeChar(slice, i, c))
   2195  1.1  mrg         {
   2196  1.1  mrg             /* Ignore UTF errors, but still consume input
   2197  1.1  mrg              */
   2198  1.1  mrg             break;
   2199  1.1  mrg         }
   2200  1.1  mrg         if (c == '.')
   2201  1.1  mrg         {
   2202  1.1  mrg             // We need to distinguish between `abc.def`, abc..def`, and `abc.`
   2203  1.1  mrg             // Only `abc.def` is a valid identifier
   2204  1.1  mrg 
   2205  1.1  mrg             if (lastCharWasDot)
   2206  1.1  mrg             {
   2207  1.1  mrg                 i = oi;
   2208  1.1  mrg                 break;
   2209  1.1  mrg             }
   2210  1.1  mrg 
   2211  1.1  mrg             lastCharWasDot = true;
   2212  1.1  mrg             continue;
   2213  1.1  mrg         }
   2214  1.1  mrg         else
   2215  1.1  mrg         {
   2216  1.1  mrg             if (c >= 0x80)
   2217  1.1  mrg             {
   2218  1.1  mrg                 if (isUniAlpha(c))
   2219  1.1  mrg                 {
   2220  1.1  mrg                     lastCharWasDot = false;
   2221  1.1  mrg                     continue;
   2222  1.1  mrg                 }
   2223  1.1  mrg             }
   2224  1.1  mrg             else if (isalnum(c) || c == '_')
   2225  1.1  mrg             {
   2226  1.1  mrg                 lastCharWasDot = false;
   2227  1.1  mrg                 continue;
   2228  1.1  mrg             }
   2229  1.1  mrg             i = oi;
   2230  1.1  mrg             break;
   2231  1.1  mrg         }
   2232  1.1  mrg     }
   2233  1.1  mrg 
   2234  1.1  mrg     // if `abc.`
   2235  1.1  mrg     if (lastCharWasDot)
   2236  1.1  mrg         return i - 1;
   2237  1.1  mrg 
   2238  1.1  mrg     return i;
   2239  1.1  mrg }
   2240  1.1  mrg 
   2241  1.1  mrg /************************************************
   2242  1.1  mrg  * Scan forward past URL starting at i.
   2243  1.1  mrg  * We don't want to highlight parts of a URL.
   2244  1.1  mrg  * Returns:
   2245  1.1  mrg  *      i if not a URL
   2246  1.1  mrg  *      index just past it if it is a URL
   2247  1.1  mrg  */
   2248  1.1  mrg private size_t skippastURL(ref OutBuffer buf, size_t i)
   2249  1.1  mrg {
   2250  1.1  mrg     const slice = buf[][i .. $];
   2251  1.1  mrg     size_t j;
   2252  1.1  mrg     bool sawdot = false;
   2253  1.1  mrg     if (slice.length > 7 && Port.memicmp(slice.ptr, "http://", 7) == 0)
   2254  1.1  mrg     {
   2255  1.1  mrg         j = 7;
   2256  1.1  mrg     }
   2257  1.1  mrg     else if (slice.length > 8 && Port.memicmp(slice.ptr, "https://", 8) == 0)
   2258  1.1  mrg     {
   2259  1.1  mrg         j = 8;
   2260  1.1  mrg     }
   2261  1.1  mrg     else
   2262  1.1  mrg         goto Lno;
   2263  1.1  mrg     for (; j < slice.length; j++)
   2264  1.1  mrg     {
   2265  1.1  mrg         const c = slice[j];
   2266  1.1  mrg         if (isalnum(c))
   2267  1.1  mrg             continue;
   2268  1.1  mrg         if (c == '-' || c == '_' || c == '?' || c == '=' || c == '%' ||
   2269  1.1  mrg             c == '&' || c == '/' || c == '+' || c == '#' || c == '~')
   2270  1.1  mrg             continue;
   2271  1.1  mrg         if (c == '.')
   2272  1.1  mrg         {
   2273  1.1  mrg             sawdot = true;
   2274  1.1  mrg             continue;
   2275  1.1  mrg         }
   2276  1.1  mrg         break;
   2277  1.1  mrg     }
   2278  1.1  mrg     if (sawdot)
   2279  1.1  mrg         return i + j;
   2280  1.1  mrg Lno:
   2281  1.1  mrg     return i;
   2282  1.1  mrg }
   2283  1.1  mrg 
   2284  1.1  mrg /****************************************************
   2285  1.1  mrg  * Remove a previously-inserted blank line macro.
   2286  1.1  mrg  * Params:
   2287  1.1  mrg  *  buf           = an OutBuffer containing the DDoc
   2288  1.1  mrg  *  iAt           = the index within `buf` of the start of the `$(DDOC_BLANKLINE)`
   2289  1.1  mrg  *                  macro. Upon function return its value is set to `0`.
   2290  1.1  mrg  *  i             = an index within `buf`. If `i` is after `iAt` then it gets
   2291  1.1  mrg  *                  reduced by the length of the removed macro.
   2292  1.1  mrg  */
   2293  1.1  mrg private void removeBlankLineMacro(ref OutBuffer buf, ref size_t iAt, ref size_t i)
   2294  1.1  mrg {
   2295  1.1  mrg     if (!iAt)
   2296  1.1  mrg         return;
   2297  1.1  mrg 
   2298  1.1  mrg     enum macroLength = "$(DDOC_BLANKLINE)".length;
   2299  1.1  mrg     buf.remove(iAt, macroLength);
   2300  1.1  mrg     if (i > iAt)
   2301  1.1  mrg         i -= macroLength;
   2302  1.1  mrg     iAt = 0;
   2303  1.1  mrg }
   2304  1.1  mrg 
   2305  1.1  mrg /****************************************************
   2306  1.1  mrg  * Attempt to detect and replace a Markdown thematic break (HR). These are three
   2307  1.1  mrg  * or more of the same delimiter, optionally with spaces or tabs between any of
   2308  1.1  mrg  * them, e.g. `\n- - -\n` becomes `\n$(HR)\n`
   2309  1.1  mrg  * Params:
   2310  1.1  mrg  *  buf         = an OutBuffer containing the DDoc
   2311  1.1  mrg  *  i           = the index within `buf` of the first character of a potential
   2312  1.1  mrg  *                thematic break. If the replacement is made `i` changes to
   2313  1.1  mrg  *                point to the closing parenthesis of the `$(HR)` macro.
   2314  1.1  mrg  *  iLineStart  = the index within `buf` that the thematic break's line starts at
   2315  1.1  mrg  *  loc         = the current location within the file
   2316  1.1  mrg  * Returns: whether a thematic break was replaced
   2317  1.1  mrg  */
   2318  1.1  mrg private bool replaceMarkdownThematicBreak(ref OutBuffer buf, ref size_t i, size_t iLineStart, const ref Loc loc)
   2319  1.1  mrg {
   2320  1.1  mrg     if (!global.params.markdown)
   2321  1.1  mrg         return false;
   2322  1.1  mrg 
   2323  1.1  mrg     const slice = buf[];
   2324  1.1  mrg     const c = buf[i];
   2325  1.1  mrg     size_t j = i + 1;
   2326  1.1  mrg     int repeat = 1;
   2327  1.1  mrg     for (; j < slice.length; j++)
   2328  1.1  mrg     {
   2329  1.1  mrg         if (buf[j] == c)
   2330  1.1  mrg             ++repeat;
   2331  1.1  mrg         else if (buf[j] != ' ' && buf[j] != '\t')
   2332  1.1  mrg             break;
   2333  1.1  mrg     }
   2334  1.1  mrg     if (repeat >= 3)
   2335  1.1  mrg     {
   2336  1.1  mrg         if (j >= buf.length || buf[j] == '\n' || buf[j] == '\r')
   2337  1.1  mrg         {
   2338  1.1  mrg             if (global.params.vmarkdown)
   2339  1.1  mrg             {
   2340  1.1  mrg                 const s = buf[][i..j];
   2341  1.1  mrg                 message(loc, "Ddoc: converted '%.*s' to a thematic break", cast(int)s.length, s.ptr);
   2342  1.1  mrg             }
   2343  1.1  mrg 
   2344  1.1  mrg             buf.remove(iLineStart, j - iLineStart);
   2345  1.1  mrg             i = buf.insert(iLineStart, "$(HR)") - 1;
   2346  1.1  mrg             return true;
   2347  1.1  mrg         }
   2348  1.1  mrg     }
   2349  1.1  mrg     return false;
   2350  1.1  mrg }
   2351  1.1  mrg 
   2352  1.1  mrg /****************************************************
   2353  1.1  mrg  * Detect the level of an ATX-style heading, e.g. `## This is a heading` would
   2354  1.1  mrg  * have a level of `2`.
   2355  1.1  mrg  * Params:
   2356  1.1  mrg  *  buf   = an OutBuffer containing the DDoc
   2357  1.1  mrg  *  i     = the index within `buf` of the first `#` character
   2358  1.1  mrg  * Returns:
   2359  1.1  mrg  *          the detected heading level from 1 to 6, or
   2360  1.1  mrg  *          0 if not at an ATX heading
   2361  1.1  mrg  */
   2362  1.1  mrg private int detectAtxHeadingLevel(ref OutBuffer buf, const size_t i)
   2363  1.1  mrg {
   2364  1.1  mrg     if (!global.params.markdown)
   2365  1.1  mrg         return 0;
   2366  1.1  mrg 
   2367  1.1  mrg     const iHeadingStart = i;
   2368  1.1  mrg     const iAfterHashes = skipChars(buf, i, "#");
   2369  1.1  mrg     const headingLevel = cast(int) (iAfterHashes - iHeadingStart);
   2370  1.1  mrg     if (headingLevel > 6)
   2371  1.1  mrg         return 0;
   2372  1.1  mrg 
   2373  1.1  mrg     const iTextStart = skipChars(buf, iAfterHashes, " \t");
   2374  1.1  mrg     const emptyHeading = buf[iTextStart] == '\r' || buf[iTextStart] == '\n';
   2375  1.1  mrg 
   2376  1.1  mrg     // require whitespace
   2377  1.1  mrg     if (!emptyHeading && iTextStart == iAfterHashes)
   2378  1.1  mrg         return 0;
   2379  1.1  mrg 
   2380  1.1  mrg     return headingLevel;
   2381  1.1  mrg }
   2382  1.1  mrg 
   2383  1.1  mrg /****************************************************
   2384  1.1  mrg  * Remove any trailing `##` suffix from an ATX-style heading.
   2385  1.1  mrg  * Params:
   2386  1.1  mrg  *  buf   = an OutBuffer containing the DDoc
   2387  1.1  mrg  *  i     = the index within `buf` to start looking for a suffix at
   2388  1.1  mrg  */
   2389  1.1  mrg private void removeAnyAtxHeadingSuffix(ref OutBuffer buf, size_t i)
   2390  1.1  mrg {
   2391  1.1  mrg     size_t j = i;
   2392  1.1  mrg     size_t iSuffixStart = 0;
   2393  1.1  mrg     size_t iWhitespaceStart = j;
   2394  1.1  mrg     const slice = buf[];
   2395  1.1  mrg     for (; j < slice.length; j++)
   2396  1.1  mrg     {
   2397  1.1  mrg         switch (slice[j])
   2398  1.1  mrg         {
   2399  1.1  mrg         case '#':
   2400  1.1  mrg             if (iWhitespaceStart && !iSuffixStart)
   2401  1.1  mrg                 iSuffixStart = j;
   2402  1.1  mrg             continue;
   2403  1.1  mrg         case ' ':
   2404  1.1  mrg         case '\t':
   2405  1.1  mrg             if (!iWhitespaceStart)
   2406  1.1  mrg                 iWhitespaceStart = j;
   2407  1.1  mrg             continue;
   2408  1.1  mrg         case '\r':
   2409  1.1  mrg         case '\n':
   2410  1.1  mrg             break;
   2411  1.1  mrg         default:
   2412  1.1  mrg             iSuffixStart = 0;
   2413  1.1  mrg             iWhitespaceStart = 0;
   2414  1.1  mrg             continue;
   2415  1.1  mrg         }
   2416  1.1  mrg         break;
   2417  1.1  mrg     }
   2418  1.1  mrg     if (iSuffixStart)
   2419  1.1  mrg         buf.remove(iWhitespaceStart, j - iWhitespaceStart);
   2420  1.1  mrg }
   2421  1.1  mrg 
   2422  1.1  mrg /****************************************************
   2423  1.1  mrg  * Wrap text in a Markdown heading macro, e.g. `$(H2 heading text`).
   2424  1.1  mrg  * Params:
   2425  1.1  mrg  *  buf           = an OutBuffer containing the DDoc
   2426  1.1  mrg  *  iStart        = the index within `buf` that the Markdown heading starts at
   2427  1.1  mrg  *  iEnd          = the index within `buf` of the character after the last
   2428  1.1  mrg  *                  heading character. Is incremented by the length of the
   2429  1.1  mrg  *                  inserted heading macro when this function ends.
   2430  1.1  mrg  *  loc           = the location of the Ddoc within the file
   2431  1.1  mrg  *  headingLevel  = the level (1-6) of heading to end. Is set to `0` when this
   2432  1.1  mrg  *                  function ends.
   2433  1.1  mrg  */
   2434  1.1  mrg private void endMarkdownHeading(ref OutBuffer buf, size_t iStart, ref size_t iEnd, const ref Loc loc, ref int headingLevel)
   2435  1.1  mrg {
   2436  1.1  mrg     if (!global.params.markdown)
   2437  1.1  mrg         return;
   2438  1.1  mrg     if (global.params.vmarkdown)
   2439  1.1  mrg     {
   2440  1.1  mrg         const s = buf[][iStart..iEnd];
   2441  1.1  mrg         message(loc, "Ddoc: added heading '%.*s'", cast(int)s.length, s.ptr);
   2442  1.1  mrg     }
   2443  1.1  mrg 
   2444  1.1  mrg     char[5] heading = "$(H0 ";
   2445  1.1  mrg     heading[3] = cast(char) ('0' + headingLevel);
   2446  1.1  mrg     buf.insert(iStart, heading);
   2447  1.1  mrg     iEnd += 5;
   2448  1.1  mrg     size_t iBeforeNewline = iEnd;
   2449  1.1  mrg     while (buf[iBeforeNewline-1] == '\r' || buf[iBeforeNewline-1] == '\n')
   2450  1.1  mrg         --iBeforeNewline;
   2451  1.1  mrg     buf.insert(iBeforeNewline, ")");
   2452  1.1  mrg     headingLevel = 0;
   2453  1.1  mrg }
   2454  1.1  mrg 
   2455  1.1  mrg /****************************************************
   2456  1.1  mrg  * End all nested Markdown quotes, if inside any.
   2457  1.1  mrg  * Params:
   2458  1.1  mrg  *  buf         = an OutBuffer containing the DDoc
   2459  1.1  mrg  *  i           = the index within `buf` of the character after the quote text.
   2460  1.1  mrg  *  quoteLevel  = the current quote level. Is set to `0` when this function ends.
   2461  1.1  mrg  * Returns: the amount that `i` was moved
   2462  1.1  mrg  */
   2463  1.1  mrg private size_t endAllMarkdownQuotes(ref OutBuffer buf, size_t i, ref int quoteLevel)
   2464  1.1  mrg {
   2465  1.1  mrg     const length = quoteLevel;
   2466  1.1  mrg     for (; quoteLevel > 0; --quoteLevel)
   2467  1.1  mrg         i = buf.insert(i, ")");
   2468  1.1  mrg     return length;
   2469  1.1  mrg }
   2470  1.1  mrg 
   2471  1.1  mrg /****************************************************
   2472  1.1  mrg  * Convenience function to end all Markdown lists and quotes, if inside any, and
   2473  1.1  mrg  * set `quoteMacroLevel` to `0`.
   2474  1.1  mrg  * Params:
   2475  1.1  mrg  *  buf         = an OutBuffer containing the DDoc
   2476  1.1  mrg  *  i           = the index within `buf` of the character after the list and/or
   2477  1.1  mrg  *                quote text. Is adjusted when this function ends if any lists
   2478  1.1  mrg  *                and/or quotes were ended.
   2479  1.1  mrg  *  nestedLists = a set of nested lists. Upon return it will be empty.
   2480  1.1  mrg  *  quoteLevel  = the current quote level. Is set to `0` when this function ends.
   2481  1.1  mrg  *  quoteMacroLevel   = the macro level that the quote was started at. Is set to
   2482  1.1  mrg  *                      `0` when this function ends.
   2483  1.1  mrg  * Returns: the amount that `i` was moved
   2484  1.1  mrg  */
   2485  1.1  mrg private size_t endAllListsAndQuotes(ref OutBuffer buf, ref size_t i, ref MarkdownList[] nestedLists, ref int quoteLevel, out int quoteMacroLevel)
   2486  1.1  mrg {
   2487  1.1  mrg     quoteMacroLevel = 0;
   2488  1.1  mrg     const i0 = i;
   2489  1.1  mrg     i += MarkdownList.endAllNestedLists(buf, i, nestedLists);
   2490  1.1  mrg     i += endAllMarkdownQuotes(buf, i, quoteLevel);
   2491  1.1  mrg     return i - i0;
   2492  1.1  mrg }
   2493  1.1  mrg 
   2494  1.1  mrg /****************************************************
   2495  1.1  mrg  * Replace Markdown emphasis with the appropriate macro,
   2496  1.1  mrg  * e.g. `*very* **nice**` becomes `$(EM very) $(STRONG nice)`.
   2497  1.1  mrg  * Params:
   2498  1.1  mrg  *  buf               = an OutBuffer containing the DDoc
   2499  1.1  mrg  *  loc               = the current location within the file
   2500  1.1  mrg  *  inlineDelimiters  = the collection of delimiters found within a paragraph. When this function returns its length will be reduced to `downToLevel`.
   2501  1.1  mrg  *  downToLevel       = the length within `inlineDelimiters`` to reduce emphasis to
   2502  1.1  mrg  * Returns: the number of characters added to the buffer by the replacements
   2503  1.1  mrg  */
   2504  1.1  mrg private size_t replaceMarkdownEmphasis(ref OutBuffer buf, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, int downToLevel = 0)
   2505  1.1  mrg {
   2506  1.1  mrg     if (!global.params.markdown)
   2507  1.1  mrg         return 0;
   2508  1.1  mrg 
   2509  1.1  mrg     size_t replaceEmphasisPair(ref MarkdownDelimiter start, ref MarkdownDelimiter end)
   2510  1.1  mrg     {
   2511  1.1  mrg         immutable count = start.count == 1 || end.count == 1 ? 1 : 2;
   2512  1.1  mrg 
   2513  1.1  mrg         size_t iStart = start.iStart;
   2514  1.1  mrg         size_t iEnd = end.iStart;
   2515  1.1  mrg         end.count -= count;
   2516  1.1  mrg         start.count -= count;
   2517  1.1  mrg         iStart += start.count;
   2518  1.1  mrg 
   2519  1.1  mrg         if (!start.count)
   2520  1.1  mrg             start.type = 0;
   2521  1.1  mrg         if (!end.count)
   2522  1.1  mrg             end.type = 0;
   2523  1.1  mrg 
   2524  1.1  mrg         if (global.params.vmarkdown)
   2525  1.1  mrg         {
   2526  1.1  mrg             const s = buf[][iStart + count..iEnd];
   2527  1.1  mrg             message(loc, "Ddoc: emphasized text '%.*s'", cast(int)s.length, s.ptr);
   2528  1.1  mrg         }
   2529  1.1  mrg 
   2530  1.1  mrg         buf.remove(iStart, count);
   2531  1.1  mrg         iEnd -= count;
   2532  1.1  mrg         buf.remove(iEnd, count);
   2533  1.1  mrg 
   2534  1.1  mrg         string macroName = count >= 2 ? "$(STRONG " : "$(EM ";
   2535  1.1  mrg         buf.insert(iEnd, ")");
   2536  1.1  mrg         buf.insert(iStart, macroName);
   2537  1.1  mrg 
   2538  1.1  mrg         const delta = 1 + macroName.length - (count + count);
   2539  1.1  mrg         end.iStart += count;
   2540  1.1  mrg         return delta;
   2541  1.1  mrg     }
   2542  1.1  mrg 
   2543  1.1  mrg     size_t delta = 0;
   2544  1.1  mrg     int start = (cast(int) inlineDelimiters.length) - 1;
   2545  1.1  mrg     while (start >= downToLevel)
   2546  1.1  mrg     {
   2547  1.1  mrg         // find start emphasis
   2548  1.1  mrg         while (start >= downToLevel &&
   2549  1.1  mrg             (inlineDelimiters[start].type != '*' || !inlineDelimiters[start].leftFlanking))
   2550  1.1  mrg             --start;
   2551  1.1  mrg         if (start < downToLevel)
   2552  1.1  mrg             break;
   2553  1.1  mrg 
   2554  1.1  mrg         // find the nearest end emphasis
   2555  1.1  mrg         int end = start + 1;
   2556  1.1  mrg         while (end < inlineDelimiters.length &&
   2557  1.1  mrg             (inlineDelimiters[end].type != inlineDelimiters[start].type ||
   2558  1.1  mrg                 inlineDelimiters[end].macroLevel != inlineDelimiters[start].macroLevel ||
   2559  1.1  mrg                 !inlineDelimiters[end].rightFlanking))
   2560  1.1  mrg             ++end;
   2561  1.1  mrg         if (end == inlineDelimiters.length)
   2562  1.1  mrg         {
   2563  1.1  mrg             // the start emphasis has no matching end; if it isn't an end itself then kill it
   2564  1.1  mrg             if (!inlineDelimiters[start].rightFlanking)
   2565  1.1  mrg                 inlineDelimiters[start].type = 0;
   2566  1.1  mrg             --start;
   2567  1.1  mrg             continue;
   2568  1.1  mrg         }
   2569  1.1  mrg 
   2570  1.1  mrg         // multiple-of-3 rule
   2571  1.1  mrg         if (((inlineDelimiters[start].leftFlanking && inlineDelimiters[start].rightFlanking) ||
   2572  1.1  mrg                 (inlineDelimiters[end].leftFlanking && inlineDelimiters[end].rightFlanking)) &&
   2573  1.1  mrg             (inlineDelimiters[start].count + inlineDelimiters[end].count) % 3 == 0)
   2574  1.1  mrg         {
   2575  1.1  mrg             --start;
   2576  1.1  mrg             continue;
   2577  1.1  mrg         }
   2578  1.1  mrg 
   2579  1.1  mrg         immutable delta0 = replaceEmphasisPair(inlineDelimiters[start], inlineDelimiters[end]);
   2580  1.1  mrg 
   2581  1.1  mrg         for (; end < inlineDelimiters.length; ++end)
   2582  1.1  mrg             inlineDelimiters[end].iStart += delta0;
   2583  1.1  mrg         delta += delta0;
   2584  1.1  mrg     }
   2585  1.1  mrg 
   2586  1.1  mrg     inlineDelimiters.length = downToLevel;
   2587  1.1  mrg     return delta;
   2588  1.1  mrg }
   2589  1.1  mrg 
   2590  1.1  mrg /****************************************************
   2591  1.1  mrg  */
   2592  1.1  mrg private bool isIdentifier(Dsymbols* a, const(char)* p, size_t len)
   2593  1.1  mrg {
   2594  1.1  mrg     foreach (member; *a)
   2595  1.1  mrg     {
   2596  1.1  mrg         if (auto imp = member.isImport())
   2597  1.1  mrg         {
   2598  1.1  mrg             // For example: `public import str = core.stdc.string;`
   2599  1.1  mrg             // This checks if `p` is equal to `str`
   2600  1.1  mrg             if (imp.aliasId)
   2601  1.1  mrg             {
   2602  1.1  mrg                 if (p[0 .. len] == imp.aliasId.toString())
   2603  1.1  mrg                     return true;
   2604  1.1  mrg             }
   2605  1.1  mrg             else
   2606  1.1  mrg             {
   2607  1.1  mrg                 // The general case:  `public import core.stdc.string;`
   2608  1.1  mrg 
   2609  1.1  mrg                 // fully qualify imports so `core.stdc.string` doesn't appear as `core`
   2610  1.1  mrg                 string fullyQualifiedImport;
   2611  1.1  mrg                 foreach (const pid; imp.packages)
   2612  1.1  mrg                 {
   2613  1.1  mrg                     fullyQualifiedImport ~= pid.toString() ~ ".";
   2614  1.1  mrg                 }
   2615  1.1  mrg                 fullyQualifiedImport ~= imp.id.toString();
   2616  1.1  mrg 
   2617  1.1  mrg                 // Check if `p` == `core.stdc.string`
   2618  1.1  mrg                 if (p[0 .. len] == fullyQualifiedImport)
   2619  1.1  mrg                     return true;
   2620  1.1  mrg             }
   2621  1.1  mrg         }
   2622  1.1  mrg         else if (member.ident)
   2623  1.1  mrg         {
   2624  1.1  mrg             if (p[0 .. len] == member.ident.toString())
   2625  1.1  mrg                 return true;
   2626  1.1  mrg         }
   2627  1.1  mrg 
   2628  1.1  mrg     }
   2629  1.1  mrg     return false;
   2630  1.1  mrg }
   2631  1.1  mrg 
   2632  1.1  mrg /****************************************************
   2633  1.1  mrg  */
   2634  1.1  mrg private bool isKeyword(const(char)* p, size_t len)
   2635  1.1  mrg {
   2636  1.1  mrg     immutable string[3] table = ["true", "false", "null"];
   2637  1.1  mrg     foreach (s; table)
   2638  1.1  mrg     {
   2639  1.1  mrg         if (p[0 .. len] == s)
   2640  1.1  mrg             return true;
   2641  1.1  mrg     }
   2642  1.1  mrg     return false;
   2643  1.1  mrg }
   2644  1.1  mrg 
   2645  1.1  mrg /****************************************************
   2646  1.1  mrg  */
   2647  1.1  mrg private TypeFunction isTypeFunction(Dsymbol s)
   2648  1.1  mrg {
   2649  1.1  mrg     FuncDeclaration f = s.isFuncDeclaration();
   2650  1.1  mrg     /* f.type may be NULL for template members.
   2651  1.1  mrg      */
   2652  1.1  mrg     if (f && f.type)
   2653  1.1  mrg     {
   2654  1.1  mrg         Type t = f.originalType ? f.originalType : f.type;
   2655  1.1  mrg         if (t.ty == Tfunction)
   2656  1.1  mrg             return cast(TypeFunction)t;
   2657  1.1  mrg     }
   2658  1.1  mrg     return null;
   2659  1.1  mrg }
   2660  1.1  mrg 
   2661  1.1  mrg /****************************************************
   2662  1.1  mrg  */
   2663  1.1  mrg private Parameter isFunctionParameter(Dsymbol s, const(char)* p, size_t len)
   2664  1.1  mrg {
   2665  1.1  mrg     TypeFunction tf = isTypeFunction(s);
   2666  1.1  mrg     if (tf && tf.parameterList.parameters)
   2667  1.1  mrg     {
   2668  1.1  mrg         foreach (fparam; *tf.parameterList.parameters)
   2669  1.1  mrg         {
   2670  1.1  mrg             if (fparam.ident && p[0 .. len] == fparam.ident.toString())
   2671  1.1  mrg             {
   2672  1.1  mrg                 return fparam;
   2673  1.1  mrg             }
   2674  1.1  mrg         }
   2675  1.1  mrg     }
   2676  1.1  mrg     return null;
   2677  1.1  mrg }
   2678  1.1  mrg 
   2679  1.1  mrg /****************************************************
   2680  1.1  mrg  */
   2681  1.1  mrg private Parameter isFunctionParameter(Dsymbols* a, const(char)* p, size_t len)
   2682  1.1  mrg {
   2683  1.1  mrg     for (size_t i = 0; i < a.dim; i++)
   2684  1.1  mrg     {
   2685  1.1  mrg         Parameter fparam = isFunctionParameter((*a)[i], p, len);
   2686  1.1  mrg         if (fparam)
   2687  1.1  mrg         {
   2688  1.1  mrg             return fparam;
   2689  1.1  mrg         }
   2690  1.1  mrg     }
   2691  1.1  mrg     return null;
   2692  1.1  mrg }
   2693  1.1  mrg 
   2694  1.1  mrg /****************************************************
   2695  1.1  mrg  */
   2696  1.1  mrg private Parameter isEponymousFunctionParameter(Dsymbols *a, const(char) *p, size_t len)
   2697  1.1  mrg {
   2698  1.1  mrg     for (size_t i = 0; i < a.dim; i++)
   2699  1.1  mrg     {
   2700  1.1  mrg         TemplateDeclaration td = (*a)[i].isTemplateDeclaration();
   2701  1.1  mrg         if (td && td.onemember)
   2702  1.1  mrg         {
   2703  1.1  mrg             /* Case 1: we refer to a template declaration inside the template
   2704  1.1  mrg 
   2705  1.1  mrg                /// ...ddoc...
   2706  1.1  mrg                template case1(T) {
   2707  1.1  mrg                  void case1(R)() {}
   2708  1.1  mrg                }
   2709  1.1  mrg              */
   2710  1.1  mrg             td = td.onemember.isTemplateDeclaration();
   2711  1.1  mrg         }
   2712  1.1  mrg         if (!td)
   2713  1.1  mrg         {
   2714  1.1  mrg             /* Case 2: we're an alias to a template declaration
   2715  1.1  mrg 
   2716  1.1  mrg                /// ...ddoc...
   2717  1.1  mrg                alias case2 = case1!int;
   2718  1.1  mrg              */
   2719  1.1  mrg             AliasDeclaration ad = (*a)[i].isAliasDeclaration();
   2720  1.1  mrg             if (ad && ad.aliassym)
   2721  1.1  mrg             {
   2722  1.1  mrg                 td = ad.aliassym.isTemplateDeclaration();
   2723  1.1  mrg             }
   2724  1.1  mrg         }
   2725  1.1  mrg         while (td)
   2726  1.1  mrg         {
   2727  1.1  mrg             Dsymbol sym = getEponymousMember(td);
   2728  1.1  mrg             if (sym)
   2729  1.1  mrg             {
   2730  1.1  mrg                 Parameter fparam = isFunctionParameter(sym, p, len);
   2731  1.1  mrg                 if (fparam)
   2732  1.1  mrg                 {
   2733  1.1  mrg                     return fparam;
   2734  1.1  mrg                 }
   2735  1.1  mrg             }
   2736  1.1  mrg             td = td.overnext;
   2737  1.1  mrg         }
   2738  1.1  mrg     }
   2739  1.1  mrg     return null;
   2740  1.1  mrg }
   2741  1.1  mrg 
   2742  1.1  mrg /****************************************************
   2743  1.1  mrg  */
   2744  1.1  mrg private TemplateParameter isTemplateParameter(Dsymbols* a, const(char)* p, size_t len)
   2745  1.1  mrg {
   2746  1.1  mrg     for (size_t i = 0; i < a.dim; i++)
   2747  1.1  mrg     {
   2748  1.1  mrg         TemplateDeclaration td = (*a)[i].isTemplateDeclaration();
   2749  1.1  mrg         // Check for the parent, if the current symbol is not a template declaration.
   2750  1.1  mrg         if (!td)
   2751  1.1  mrg             td = getEponymousParent((*a)[i]);
   2752  1.1  mrg         if (td && td.origParameters)
   2753  1.1  mrg         {
   2754  1.1  mrg             foreach (tp; *td.origParameters)
   2755  1.1  mrg             {
   2756  1.1  mrg                 if (tp.ident && p[0 .. len] == tp.ident.toString())
   2757  1.1  mrg                 {
   2758  1.1  mrg                     return tp;
   2759  1.1  mrg                 }
   2760  1.1  mrg             }
   2761  1.1  mrg         }
   2762  1.1  mrg     }
   2763  1.1  mrg     return null;
   2764  1.1  mrg }
   2765  1.1  mrg 
   2766  1.1  mrg /****************************************************
   2767  1.1  mrg  * Return true if str is a reserved symbol name
   2768  1.1  mrg  * that starts with a double underscore.
   2769  1.1  mrg  */
   2770  1.1  mrg private bool isReservedName(const(char)[] str)
   2771  1.1  mrg {
   2772  1.1  mrg     immutable string[] table =
   2773  1.1  mrg     [
   2774  1.1  mrg         "__ctor",
   2775  1.1  mrg         "__dtor",
   2776  1.1  mrg         "__postblit",
   2777  1.1  mrg         "__invariant",
   2778  1.1  mrg         "__unitTest",
   2779  1.1  mrg         "__require",
   2780  1.1  mrg         "__ensure",
   2781  1.1  mrg         "__dollar",
   2782  1.1  mrg         "__ctfe",
   2783  1.1  mrg         "__withSym",
   2784  1.1  mrg         "__result",
   2785  1.1  mrg         "__returnLabel",
   2786  1.1  mrg         "__vptr",
   2787  1.1  mrg         "__monitor",
   2788  1.1  mrg         "__gate",
   2789  1.1  mrg         "__xopEquals",
   2790  1.1  mrg         "__xopCmp",
   2791  1.1  mrg         "__LINE__",
   2792  1.1  mrg         "__FILE__",
   2793  1.1  mrg         "__MODULE__",
   2794  1.1  mrg         "__FUNCTION__",
   2795  1.1  mrg         "__PRETTY_FUNCTION__",
   2796  1.1  mrg         "__DATE__",
   2797  1.1  mrg         "__TIME__",
   2798  1.1  mrg         "__TIMESTAMP__",
   2799  1.1  mrg         "__VENDOR__",
   2800  1.1  mrg         "__VERSION__",
   2801  1.1  mrg         "__EOF__",
   2802  1.1  mrg         "__CXXLIB__",
   2803  1.1  mrg         "__LOCAL_SIZE",
   2804  1.1  mrg         "__entrypoint",
   2805  1.1  mrg     ];
   2806  1.1  mrg     foreach (s; table)
   2807  1.1  mrg     {
   2808  1.1  mrg         if (str == s)
   2809  1.1  mrg             return true;
   2810  1.1  mrg     }
   2811  1.1  mrg     return false;
   2812  1.1  mrg }
   2813  1.1  mrg 
   2814  1.1  mrg /****************************************************
   2815  1.1  mrg  * A delimiter for Markdown inline content like emphasis and links.
   2816  1.1  mrg  */
   2817  1.1  mrg private struct MarkdownDelimiter
   2818  1.1  mrg {
   2819  1.1  mrg     size_t iStart;  /// the index where this delimiter starts
   2820  1.1  mrg     int count;      /// the length of this delimeter's start sequence
   2821  1.1  mrg     int macroLevel; /// the count of nested DDoc macros when the delimiter is started
   2822  1.1  mrg     bool leftFlanking;  /// whether the delimiter is left-flanking, as defined by the CommonMark spec
   2823  1.1  mrg     bool rightFlanking; /// whether the delimiter is right-flanking, as defined by the CommonMark spec
   2824  1.1  mrg     bool atParagraphStart;  /// whether the delimiter is at the start of a paragraph
   2825  1.1  mrg     char type;      /// the type of delimiter, defined by its starting character
   2826  1.1  mrg 
   2827  1.1  mrg     /// whether this describes a valid delimiter
   2828  1.1  mrg     @property bool isValid() const { return count != 0; }
   2829  1.1  mrg 
   2830  1.1  mrg     /// flag this delimiter as invalid
   2831  1.1  mrg     void invalidate() { count = 0; }
   2832  1.1  mrg }
   2833  1.1  mrg 
   2834  1.1  mrg /****************************************************
   2835  1.1  mrg  * Info about a Markdown list.
   2836  1.1  mrg  */
   2837  1.1  mrg private struct MarkdownList
   2838  1.1  mrg {
   2839  1.1  mrg     string orderedStart;    /// an optional start number--if present then the list starts at this number
   2840  1.1  mrg     size_t iStart;          /// the index where the list item starts
   2841  1.1  mrg     size_t iContentStart;   /// the index where the content starts after the list delimiter
   2842  1.1  mrg     int delimiterIndent;    /// the level of indent the list delimiter starts at
   2843  1.1  mrg     int contentIndent;      /// the level of indent the content starts at
   2844  1.1  mrg     int macroLevel;         /// the count of nested DDoc macros when the list is started
   2845  1.1  mrg     char type;              /// the type of list, defined by its starting character
   2846  1.1  mrg 
   2847  1.1  mrg     /// whether this describes a valid list
   2848  1.1  mrg     @property bool isValid() const { return type != type.init; }
   2849  1.1  mrg 
   2850  1.1  mrg     /****************************************************
   2851  1.1  mrg      * Try to parse a list item, returning whether successful.
   2852  1.1  mrg      * Params:
   2853  1.1  mrg      *  buf           = an OutBuffer containing the DDoc
   2854  1.1  mrg      *  iLineStart    = the index within `buf` of the first character of the line
   2855  1.1  mrg      *  i             = the index within `buf` of the potential list item
   2856  1.1  mrg      * Returns: the parsed list item. Its `isValid` property describes whether parsing succeeded.
   2857  1.1  mrg      */
   2858  1.1  mrg     static MarkdownList parseItem(ref OutBuffer buf, size_t iLineStart, size_t i)
   2859  1.1  mrg     {
   2860  1.1  mrg         if (!global.params.markdown)
   2861  1.1  mrg             return MarkdownList();
   2862  1.1  mrg 
   2863  1.1  mrg         if (buf[i] == '+' || buf[i] == '-' || buf[i] == '*')
   2864  1.1  mrg             return parseUnorderedListItem(buf, iLineStart, i);
   2865  1.1  mrg         else
   2866  1.1  mrg             return parseOrderedListItem(buf, iLineStart, i);
   2867  1.1  mrg     }
   2868  1.1  mrg 
   2869  1.1  mrg     /****************************************************
   2870  1.1  mrg      * Return whether the context is at a list item of the same type as this list.
   2871  1.1  mrg      * Params:
   2872  1.1  mrg      *  buf           = an OutBuffer containing the DDoc
   2873  1.1  mrg      *  iLineStart    = the index within `buf` of the first character of the line
   2874  1.1  mrg      *  i             = the index within `buf` of the list item
   2875  1.1  mrg      * Returns: whether `i` is at a list item of the same type as this list
   2876  1.1  mrg      */
   2877  1.1  mrg     private bool isAtItemInThisList(ref OutBuffer buf, size_t iLineStart, size_t i)
   2878  1.1  mrg     {
   2879  1.1  mrg         MarkdownList item = (type == '.' || type == ')') ?
   2880  1.1  mrg             parseOrderedListItem(buf, iLineStart, i) :
   2881  1.1  mrg             parseUnorderedListItem(buf, iLineStart, i);
   2882  1.1  mrg         if (item.type == type)
   2883  1.1  mrg             return item.delimiterIndent < contentIndent && item.contentIndent > delimiterIndent;
   2884  1.1  mrg         return false;
   2885  1.1  mrg     }
   2886  1.1  mrg 
   2887  1.1  mrg     /****************************************************
   2888  1.1  mrg      * Start a Markdown list item by creating/deleting nested lists and starting the item.
   2889  1.1  mrg      * Params:
   2890  1.1  mrg      *  buf           = an OutBuffer containing the DDoc
   2891  1.1  mrg      *  iLineStart    = the index within `buf` of the first character of the line. If this function succeeds it will be adjuested to equal `i`.
   2892  1.1  mrg      *  i             = the index within `buf` of the list item. If this function succeeds `i` will be adjusted to fit the inserted macro.
   2893  1.1  mrg      *  iPrecedingBlankLine = the index within `buf` of the preceeding blank line. If non-zero and a new list was started, the preceeding blank line is removed and this value is set to `0`.
   2894  1.1  mrg      *  nestedLists   = a set of nested lists. If this function succeeds it may contain a new nested list.
   2895  1.1  mrg      *  loc           = the location of the Ddoc within the file
   2896  1.1  mrg      * Returns: `true` if a list was created
   2897  1.1  mrg      */
   2898  1.1  mrg     bool startItem(ref OutBuffer buf, ref size_t iLineStart, ref size_t i, ref size_t iPrecedingBlankLine, ref MarkdownList[] nestedLists, const ref Loc loc)
   2899  1.1  mrg     {
   2900  1.1  mrg         buf.remove(iStart, iContentStart - iStart);
   2901  1.1  mrg 
   2902  1.1  mrg         if (!nestedLists.length ||
   2903  1.1  mrg             delimiterIndent >= nestedLists[$-1].contentIndent ||
   2904  1.1  mrg             buf[iLineStart - 4..iLineStart] == "$(LI")
   2905  1.1  mrg         {
   2906  1.1  mrg             // start a list macro
   2907  1.1  mrg             nestedLists ~= this;
   2908  1.1  mrg             if (type == '.')
   2909  1.1  mrg             {
   2910  1.1  mrg                 if (orderedStart.length)
   2911  1.1  mrg                 {
   2912  1.1  mrg                     iStart = buf.insert(iStart, "$(OL_START ");
   2913  1.1  mrg                     iStart = buf.insert(iStart, orderedStart);
   2914  1.1  mrg                     iStart = buf.insert(iStart, ",\n");
   2915  1.1  mrg                 }
   2916  1.1  mrg                 else
   2917  1.1  mrg                     iStart = buf.insert(iStart, "$(OL\n");
   2918  1.1  mrg             }
   2919  1.1  mrg             else
   2920  1.1  mrg                 iStart = buf.insert(iStart, "$(UL\n");
   2921  1.1  mrg 
   2922  1.1  mrg             removeBlankLineMacro(buf, iPrecedingBlankLine, iStart);
   2923  1.1  mrg         }
   2924  1.1  mrg         else if (nestedLists.length)
   2925  1.1  mrg         {
   2926  1.1  mrg             nestedLists[$-1].delimiterIndent = delimiterIndent;
   2927  1.1  mrg             nestedLists[$-1].contentIndent = contentIndent;
   2928  1.1  mrg         }
   2929  1.1  mrg 
   2930  1.1  mrg         iStart = buf.insert(iStart, "$(LI\n");
   2931  1.1  mrg         i = iStart - 1;
   2932  1.1  mrg         iLineStart = i;
   2933  1.1  mrg 
   2934  1.1  mrg         if (global.params.vmarkdown)
   2935  1.1  mrg         {
   2936  1.1  mrg             size_t iEnd = iStart;
   2937  1.1  mrg             while (iEnd < buf.length && buf[iEnd] != '\r' && buf[iEnd] != '\n')
   2938  1.1  mrg                 ++iEnd;
   2939  1.1  mrg             const s = buf[][iStart..iEnd];
   2940  1.1  mrg             message(loc, "Ddoc: starting list item '%.*s'", cast(int)s.length, s.ptr);
   2941  1.1  mrg         }
   2942  1.1  mrg 
   2943  1.1  mrg         return true;
   2944  1.1  mrg     }
   2945  1.1  mrg 
   2946  1.1  mrg     /****************************************************
   2947  1.1  mrg      * End all nested Markdown lists.
   2948  1.1  mrg      * Params:
   2949  1.1  mrg      *  buf           = an OutBuffer containing the DDoc
   2950  1.1  mrg      *  i             = the index within `buf` to end lists at.
   2951  1.1  mrg      *  nestedLists   = a set of nested lists. Upon return it will be empty.
   2952  1.1  mrg      * Returns: the amount that `i` changed
   2953  1.1  mrg      */
   2954  1.1  mrg     static size_t endAllNestedLists(ref OutBuffer buf, size_t i, ref MarkdownList[] nestedLists)
   2955  1.1  mrg     {
   2956  1.1  mrg         const iStart = i;
   2957  1.1  mrg         for (; nestedLists.length; --nestedLists.length)
   2958  1.1  mrg             i = buf.insert(i, ")\n)");
   2959  1.1  mrg         return i - iStart;
   2960  1.1  mrg     }
   2961  1.1  mrg 
   2962  1.1  mrg     /****************************************************
   2963  1.1  mrg      * Look for a sibling list item or the end of nested list(s).
   2964  1.1  mrg      * Params:
   2965  1.1  mrg      *  buf               = an OutBuffer containing the DDoc
   2966  1.1  mrg      *  i                 = the index within `buf` to end lists at. If there was a sibling or ending lists `i` will be adjusted to fit the macro endings.
   2967  1.1  mrg      *  iParagraphStart   = the index within `buf` to start the next paragraph at at. May be adjusted upon return.
   2968  1.1  mrg      *  nestedLists       = a set of nested lists. Some nested lists may have been removed from it upon return.
   2969  1.1  mrg      */
   2970  1.1  mrg     static void handleSiblingOrEndingList(ref OutBuffer buf, ref size_t i, ref size_t iParagraphStart, ref MarkdownList[] nestedLists)
   2971  1.1  mrg     {
   2972  1.1  mrg         size_t iAfterSpaces = skipChars(buf, i + 1, " \t");
   2973  1.1  mrg 
   2974  1.1  mrg         if (nestedLists[$-1].isAtItemInThisList(buf, i + 1, iAfterSpaces))
   2975  1.1  mrg         {
   2976  1.1  mrg             // end a sibling list item
   2977  1.1  mrg             i = buf.insert(i, ")");
   2978  1.1  mrg             iParagraphStart = skipChars(buf, i, " \t\r\n");
   2979  1.1  mrg         }
   2980  1.1  mrg         else if (iAfterSpaces >= buf.length || (buf[iAfterSpaces] != '\r' && buf[iAfterSpaces] != '\n'))
   2981  1.1  mrg         {
   2982  1.1  mrg             // end nested lists that are indented more than this content
   2983  1.1  mrg             const indent = getMarkdownIndent(buf, i + 1, iAfterSpaces);
   2984  1.1  mrg             while (nestedLists.length && nestedLists[$-1].contentIndent > indent)
   2985  1.1  mrg             {
   2986  1.1  mrg                 i = buf.insert(i, ")\n)");
   2987  1.1  mrg                 --nestedLists.length;
   2988  1.1  mrg                 iParagraphStart = skipChars(buf, i, " \t\r\n");
   2989  1.1  mrg 
   2990  1.1  mrg                 if (nestedLists.length && nestedLists[$-1].isAtItemInThisList(buf, i + 1, iParagraphStart))
   2991  1.1  mrg                 {
   2992  1.1  mrg                     i = buf.insert(i, ")");
   2993  1.1  mrg                     ++iParagraphStart;
   2994  1.1  mrg                     break;
   2995  1.1  mrg                 }
   2996  1.1  mrg             }
   2997  1.1  mrg         }
   2998  1.1  mrg     }
   2999  1.1  mrg 
   3000  1.1  mrg     /****************************************************
   3001  1.1  mrg      * Parse an unordered list item at the current position
   3002  1.1  mrg      * Params:
   3003  1.1  mrg      *  buf           = an OutBuffer containing the DDoc
   3004  1.1  mrg      *  iLineStart    = the index within `buf` of the first character of the line
   3005  1.1  mrg      *  i             = the index within `buf` of the list item
   3006  1.1  mrg      * Returns: the parsed list item, or a list item with type `.init` if no list item is available
   3007  1.1  mrg      */
   3008  1.1  mrg     private static MarkdownList parseUnorderedListItem(ref OutBuffer buf, size_t iLineStart, size_t i)
   3009  1.1  mrg     {
   3010  1.1  mrg         if (i+1 < buf.length &&
   3011  1.1  mrg                 (buf[i] == '-' ||
   3012  1.1  mrg                 buf[i] == '*' ||
   3013  1.1  mrg                 buf[i] == '+') &&
   3014  1.1  mrg             (buf[i+1] == ' ' ||
   3015  1.1  mrg                 buf[i+1] == '\t' ||
   3016  1.1  mrg                 buf[i+1] == '\r' ||
   3017  1.1  mrg                 buf[i+1] == '\n'))
   3018  1.1  mrg         {
   3019  1.1  mrg             const iContentStart = skipChars(buf, i + 1, " \t");
   3020  1.1  mrg             const delimiterIndent = getMarkdownIndent(buf, iLineStart, i);
   3021  1.1  mrg             const contentIndent = getMarkdownIndent(buf, iLineStart, iContentStart);
   3022  1.1  mrg             auto list = MarkdownList(null, iLineStart, iContentStart, delimiterIndent, contentIndent, 0, buf[i]);
   3023  1.1  mrg             return list;
   3024  1.1  mrg         }
   3025  1.1  mrg         return MarkdownList();
   3026  1.1  mrg     }
   3027  1.1  mrg 
   3028  1.1  mrg     /****************************************************
   3029  1.1  mrg      * Parse an ordered list item at the current position
   3030  1.1  mrg      * Params:
   3031  1.1  mrg      *  buf           = an OutBuffer containing the DDoc
   3032  1.1  mrg      *  iLineStart    = the index within `buf` of the first character of the line
   3033  1.1  mrg      *  i             = the index within `buf` of the list item
   3034  1.1  mrg      * Returns: the parsed list item, or a list item with type `.init` if no list item is available
   3035  1.1  mrg      */
   3036  1.1  mrg     private static MarkdownList parseOrderedListItem(ref OutBuffer buf, size_t iLineStart, size_t i)
   3037  1.1  mrg     {
   3038  1.1  mrg         size_t iAfterNumbers = skipChars(buf, i, "0123456789");
   3039  1.1  mrg         if (iAfterNumbers - i > 0 &&
   3040  1.1  mrg             iAfterNumbers - i <= 9 &&
   3041  1.1  mrg             iAfterNumbers + 1 < buf.length &&
   3042  1.1  mrg             buf[iAfterNumbers] == '.' &&
   3043  1.1  mrg             (buf[iAfterNumbers+1] == ' ' ||
   3044  1.1  mrg                 buf[iAfterNumbers+1] == '\t' ||
   3045  1.1  mrg                 buf[iAfterNumbers+1] == '\r' ||
   3046  1.1  mrg                 buf[iAfterNumbers+1] == '\n'))
   3047  1.1  mrg         {
   3048  1.1  mrg             const iContentStart = skipChars(buf, iAfterNumbers + 1, " \t");
   3049  1.1  mrg             const delimiterIndent = getMarkdownIndent(buf, iLineStart, i);
   3050  1.1  mrg             const contentIndent = getMarkdownIndent(buf, iLineStart, iContentStart);
   3051  1.1  mrg             size_t iNumberStart = skipChars(buf, i, "0");
   3052  1.1  mrg             if (iNumberStart == iAfterNumbers)
   3053  1.1  mrg                 --iNumberStart;
   3054  1.1  mrg             auto orderedStart = buf[][iNumberStart .. iAfterNumbers];
   3055  1.1  mrg             if (orderedStart == "1")
   3056  1.1  mrg                 orderedStart = null;
   3057  1.1  mrg             return MarkdownList(orderedStart.idup, iLineStart, iContentStart, delimiterIndent, contentIndent, 0, buf[iAfterNumbers]);
   3058  1.1  mrg         }
   3059  1.1  mrg         return MarkdownList();
   3060  1.1  mrg     }
   3061  1.1  mrg }
   3062  1.1  mrg 
   3063  1.1  mrg /****************************************************
   3064  1.1  mrg  * A Markdown link.
   3065  1.1  mrg  */
   3066  1.1  mrg private struct MarkdownLink
   3067  1.1  mrg {
   3068  1.1  mrg     string href;    /// the link destination
   3069  1.1  mrg     string title;   /// an optional title for the link
   3070  1.1  mrg     string label;   /// an optional label for the link
   3071  1.1  mrg     Dsymbol symbol; /// an optional symbol to link to
   3072  1.1  mrg 
   3073  1.1  mrg     /****************************************************
   3074  1.1  mrg      * Replace a Markdown link or link definition in the form of:
   3075  1.1  mrg      * - Inline link: `[foo](url/ 'optional title')`
   3076  1.1  mrg      * - Reference link: `[foo][bar]`, `[foo][]` or `[foo]`
   3077  1.1  mrg      * - Link reference definition: `[bar]: url/ 'optional title'`
   3078  1.1  mrg      * Params:
   3079  1.1  mrg      *  buf               = an OutBuffer containing the DDoc
   3080  1.1  mrg      *  i                 = the index within `buf` that points to the `]` character of the potential link.
   3081  1.1  mrg      *                      If this function succeeds it will be adjusted to fit the inserted link macro.
   3082  1.1  mrg      *  loc               = the current location within the file
   3083  1.1  mrg      *  inlineDelimiters  = previously parsed Markdown delimiters, including emphasis and link/image starts
   3084  1.1  mrg      *  delimiterIndex    = the index within `inlineDelimiters` of the nearest link/image starting delimiter
   3085  1.1  mrg      *  linkReferences    = previously parsed link references. When this function returns it may contain
   3086  1.1  mrg      *                      additional previously unparsed references.
   3087  1.1  mrg      * Returns: whether a reference link was found and replaced at `i`
   3088  1.1  mrg      */
   3089  1.1  mrg     static bool replaceLink(ref OutBuffer buf, ref size_t i, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, int delimiterIndex, ref MarkdownLinkReferences linkReferences)
   3090  1.1  mrg     {
   3091  1.1  mrg         const delimiter = inlineDelimiters[delimiterIndex];
   3092  1.1  mrg         MarkdownLink link;
   3093  1.1  mrg 
   3094  1.1  mrg         size_t iEnd = link.parseReferenceDefinition(buf, i, delimiter);
   3095  1.1  mrg         if (iEnd > i)
   3096  1.1  mrg         {
   3097  1.1  mrg             i = delimiter.iStart;
   3098  1.1  mrg             link.storeAndReplaceDefinition(buf, i, iEnd, linkReferences, loc);
   3099  1.1  mrg             inlineDelimiters.length = delimiterIndex;
   3100  1.1  mrg             return true;
   3101  1.1  mrg         }
   3102  1.1  mrg 
   3103  1.1  mrg         iEnd = link.parseInlineLink(buf, i);
   3104  1.1  mrg         if (iEnd == i)
   3105  1.1  mrg         {
   3106  1.1  mrg             iEnd = link.parseReferenceLink(buf, i, delimiter);
   3107  1.1  mrg             if (iEnd > i)
   3108  1.1  mrg             {
   3109  1.1  mrg                 const label = link.label;
   3110  1.1  mrg                 link = linkReferences.lookupReference(label, buf, i, loc);
   3111  1.1  mrg                 // check rightFlanking to avoid replacing things like int[string]
   3112  1.1  mrg                 if (!link.href.length && !delimiter.rightFlanking)
   3113  1.1  mrg                     link = linkReferences.lookupSymbol(label);
   3114  1.1  mrg                 if (!link.href.length)
   3115  1.1  mrg                     return false;
   3116  1.1  mrg             }
   3117  1.1  mrg         }
   3118  1.1  mrg 
   3119  1.1  mrg         if (iEnd == i)
   3120  1.1  mrg             return false;
   3121  1.1  mrg 
   3122  1.1  mrg         immutable delta = replaceMarkdownEmphasis(buf, loc, inlineDelimiters, delimiterIndex);
   3123  1.1  mrg         iEnd += delta;
   3124  1.1  mrg         i += delta;
   3125  1.1  mrg 
   3126  1.1  mrg         if (global.params.vmarkdown)
   3127  1.1  mrg         {
   3128  1.1  mrg             const s = buf[][delimiter.iStart..iEnd];
   3129  1.1  mrg             message(loc, "Ddoc: linking '%.*s' to '%.*s'", cast(int)s.length, s.ptr, cast(int)link.href.length, link.href.ptr);
   3130  1.1  mrg         }
   3131  1.1  mrg 
   3132  1.1  mrg         link.replaceLink(buf, i, iEnd, delimiter);
   3133  1.1  mrg         return true;
   3134  1.1  mrg     }
   3135  1.1  mrg 
   3136  1.1  mrg     /****************************************************
   3137  1.1  mrg      * Replace a Markdown link definition in the form of `[bar]: url/ 'optional title'`
   3138  1.1  mrg      * Params:
   3139  1.1  mrg      *  buf               = an OutBuffer containing the DDoc
   3140  1.1  mrg      *  i                 = the index within `buf` that points to the `]` character of the potential link.
   3141  1.1  mrg      *                      If this function succeeds it will be adjusted to fit the inserted link macro.
   3142  1.1  mrg      *  inlineDelimiters  = previously parsed Markdown delimiters, including emphasis and link/image starts
   3143  1.1  mrg      *  delimiterIndex    = the index within `inlineDelimiters` of the nearest link/image starting delimiter
   3144  1.1  mrg      *  linkReferences    = previously parsed link references. When this function returns it may contain
   3145  1.1  mrg      *                      additional previously unparsed references.
   3146  1.1  mrg      *  loc               = the current location in the file
   3147  1.1  mrg      * Returns: whether a reference link was found and replaced at `i`
   3148  1.1  mrg      */
   3149  1.1  mrg     static bool replaceReferenceDefinition(ref OutBuffer buf, ref size_t i, ref MarkdownDelimiter[] inlineDelimiters, int delimiterIndex, ref MarkdownLinkReferences linkReferences, const ref Loc loc)
   3150  1.1  mrg     {
   3151  1.1  mrg         const delimiter = inlineDelimiters[delimiterIndex];
   3152  1.1  mrg         MarkdownLink link;
   3153  1.1  mrg         size_t iEnd = link.parseReferenceDefinition(buf, i, delimiter);
   3154  1.1  mrg         if (iEnd == i)
   3155  1.1  mrg             return false;
   3156  1.1  mrg 
   3157  1.1  mrg         i = delimiter.iStart;
   3158  1.1  mrg         link.storeAndReplaceDefinition(buf, i, iEnd, linkReferences, loc);
   3159  1.1  mrg         inlineDelimiters.length = delimiterIndex;
   3160  1.1  mrg         return true;
   3161  1.1  mrg     }
   3162  1.1  mrg 
   3163  1.1  mrg     /****************************************************
   3164  1.1  mrg      * Parse a Markdown inline link in the form of `[foo](url/ 'optional title')`
   3165  1.1  mrg      * Params:
   3166  1.1  mrg      *  buf   = an OutBuffer containing the DDoc
   3167  1.1  mrg      *  i     = the index within `buf` that points to the `]` character of the inline link.
   3168  1.1  mrg      * Returns: the index at the end of parsing the link, or `i` if parsing failed.
   3169  1.1  mrg      */
   3170  1.1  mrg     private size_t parseInlineLink(ref OutBuffer buf, size_t i)
   3171  1.1  mrg     {
   3172  1.1  mrg         size_t iEnd = i + 1;
   3173  1.1  mrg         if (iEnd >= buf.length || buf[iEnd] != '(')
   3174  1.1  mrg             return i;
   3175  1.1  mrg         ++iEnd;
   3176  1.1  mrg 
   3177  1.1  mrg         if (!parseHref(buf, iEnd))
   3178  1.1  mrg             return i;
   3179  1.1  mrg 
   3180  1.1  mrg         iEnd = skipChars(buf, iEnd, " \t\r\n");
   3181  1.1  mrg         if (buf[iEnd] != ')')
   3182  1.1  mrg         {
   3183  1.1  mrg             if (parseTitle(buf, iEnd))
   3184  1.1  mrg                 iEnd = skipChars(buf, iEnd, " \t\r\n");
   3185  1.1  mrg         }
   3186  1.1  mrg 
   3187  1.1  mrg         if (buf[iEnd] != ')')
   3188  1.1  mrg             return i;
   3189  1.1  mrg 
   3190  1.1  mrg         return iEnd + 1;
   3191  1.1  mrg     }
   3192  1.1  mrg 
   3193  1.1  mrg     /****************************************************
   3194  1.1  mrg      * Parse a Markdown reference link in the form of `[foo][bar]`, `[foo][]` or `[foo]`
   3195  1.1  mrg      * Params:
   3196  1.1  mrg      *  buf       = an OutBuffer containing the DDoc
   3197  1.1  mrg      *  i         = the index within `buf` that points to the `]` character of the inline link.
   3198  1.1  mrg      *  delimiter = the delimiter that starts this link
   3199  1.1  mrg      * Returns: the index at the end of parsing the link, or `i` if parsing failed.
   3200  1.1  mrg      */
   3201  1.1  mrg     private size_t parseReferenceLink(ref OutBuffer buf, size_t i, MarkdownDelimiter delimiter)
   3202  1.1  mrg     {
   3203  1.1  mrg         size_t iStart = i + 1;
   3204  1.1  mrg         size_t iEnd = iStart;
   3205  1.1  mrg         if (iEnd >= buf.length || buf[iEnd] != '[' || (iEnd+1 < buf.length && buf[iEnd+1] == ']'))
   3206  1.1  mrg         {
   3207  1.1  mrg             // collapsed reference [foo][] or shortcut reference [foo]
   3208  1.1  mrg             iStart = delimiter.iStart + delimiter.count - 1;
   3209  1.1  mrg             if (buf[iEnd] == '[')
   3210  1.1  mrg                 iEnd += 2;
   3211  1.1  mrg         }
   3212  1.1  mrg 
   3213  1.1  mrg         parseLabel(buf, iStart);
   3214  1.1  mrg         if (!label.length)
   3215  1.1  mrg             return i;
   3216  1.1  mrg 
   3217  1.1  mrg         if (iEnd < iStart)
   3218  1.1  mrg             iEnd = iStart;
   3219  1.1  mrg         return iEnd;
   3220  1.1  mrg     }
   3221  1.1  mrg 
   3222  1.1  mrg     /****************************************************
   3223  1.1  mrg      * Parse a Markdown reference definition in the form of `[bar]: url/ 'optional title'`
   3224  1.1  mrg      * Params:
   3225  1.1  mrg      *  buf               = an OutBuffer containing the DDoc
   3226  1.1  mrg      *  i                 = the index within `buf` that points to the `]` character of the inline link.
   3227  1.1  mrg      *  delimiter = the delimiter that starts this link
   3228  1.1  mrg      * Returns: the index at the end of parsing the link, or `i` if parsing failed.
   3229  1.1  mrg      */
   3230  1.1  mrg     private size_t parseReferenceDefinition(ref OutBuffer buf, size_t i, MarkdownDelimiter delimiter)
   3231  1.1  mrg     {
   3232  1.1  mrg         if (!delimiter.atParagraphStart || delimiter.type != '[' ||
   3233  1.1  mrg             i+1 >= buf.length || buf[i+1] != ':')
   3234  1.1  mrg             return i;
   3235  1.1  mrg 
   3236  1.1  mrg         size_t iEnd = delimiter.iStart;
   3237  1.1  mrg         parseLabel(buf, iEnd);
   3238  1.1  mrg         if (label.length == 0 || iEnd != i + 1)
   3239  1.1  mrg             return i;
   3240  1.1  mrg 
   3241  1.1  mrg         ++iEnd;
   3242  1.1  mrg         iEnd = skipChars(buf, iEnd, " \t");
   3243  1.1  mrg         skipOneNewline(buf, iEnd);
   3244  1.1  mrg 
   3245  1.1  mrg         if (!parseHref(buf, iEnd) || href.length == 0)
   3246  1.1  mrg             return i;
   3247  1.1  mrg 
   3248  1.1  mrg         iEnd = skipChars(buf, iEnd, " \t");
   3249  1.1  mrg         const requireNewline = !skipOneNewline(buf, iEnd);
   3250  1.1  mrg         const iBeforeTitle = iEnd;
   3251  1.1  mrg 
   3252  1.1  mrg         if (parseTitle(buf, iEnd))
   3253  1.1  mrg         {
   3254  1.1  mrg             iEnd = skipChars(buf, iEnd, " \t");
   3255  1.1  mrg             if (iEnd < buf.length && buf[iEnd] != '\r' && buf[iEnd] != '\n')
   3256  1.1  mrg             {
   3257  1.1  mrg                 // the title must end with a newline
   3258  1.1  mrg                 title.length = 0;
   3259  1.1  mrg                 iEnd = iBeforeTitle;
   3260  1.1  mrg             }
   3261  1.1  mrg         }
   3262  1.1  mrg 
   3263  1.1  mrg         iEnd = skipChars(buf, iEnd, " \t");
   3264  1.1  mrg         if (requireNewline && iEnd < buf.length-1 && buf[iEnd] != '\r' && buf[iEnd] != '\n')
   3265  1.1  mrg             return i;
   3266  1.1  mrg 
   3267  1.1  mrg         return iEnd;
   3268  1.1  mrg     }
   3269  1.1  mrg 
   3270  1.1  mrg     /****************************************************
   3271  1.1  mrg      * Parse and normalize a Markdown reference label
   3272  1.1  mrg      * Params:
   3273  1.1  mrg      *  buf   = an OutBuffer containing the DDoc
   3274  1.1  mrg      *  i     = the index within `buf` that points to the `[` character at the start of the label.
   3275  1.1  mrg      *          If this function returns a non-empty label then `i` will point just after the ']' at the end of the label.
   3276  1.1  mrg      * Returns: the parsed and normalized label, possibly empty
   3277  1.1  mrg      */
   3278  1.1  mrg     private bool parseLabel(ref OutBuffer buf, ref size_t i)
   3279  1.1  mrg     {
   3280  1.1  mrg         if (buf[i] != '[')
   3281  1.1  mrg             return false;
   3282  1.1  mrg 
   3283  1.1  mrg         const slice = buf[];
   3284  1.1  mrg         size_t j = i + 1;
   3285  1.1  mrg 
   3286  1.1  mrg         // Some labels have already been en-symboled; handle that
   3287  1.1  mrg         const inSymbol = j+15 < slice.length && slice[j..j+15] == "$(DDOC_PSYMBOL ";
   3288  1.1  mrg         if (inSymbol)
   3289  1.1  mrg             j += 15;
   3290  1.1  mrg 
   3291  1.1  mrg         for (; j < slice.length; ++j)
   3292  1.1  mrg         {
   3293  1.1  mrg             const c = slice[j];
   3294  1.1  mrg             switch (c)
   3295  1.1  mrg             {
   3296  1.1  mrg             case ' ':
   3297  1.1  mrg             case '\t':
   3298  1.1  mrg             case '\r':
   3299  1.1  mrg             case '\n':
   3300  1.1  mrg                 if (label.length && label[$-1] != ' ')
   3301  1.1  mrg                     label ~= ' ';
   3302  1.1  mrg                 break;
   3303  1.1  mrg             case ')':
   3304  1.1  mrg                 if (inSymbol && j+1 < slice.length && slice[j+1] == ']')
   3305  1.1  mrg                 {
   3306  1.1  mrg                     ++j;
   3307  1.1  mrg                     goto case ']';
   3308  1.1  mrg                 }
   3309  1.1  mrg                 goto default;
   3310  1.1  mrg             case '[':
   3311  1.1  mrg                 if (slice[j-1] != '\\')
   3312  1.1  mrg                 {
   3313  1.1  mrg                     label.length = 0;
   3314  1.1  mrg                     return false;
   3315  1.1  mrg                 }
   3316  1.1  mrg                 break;
   3317  1.1  mrg             case ']':
   3318  1.1  mrg                 if (label.length && label[$-1] == ' ')
   3319  1.1  mrg                     --label.length;
   3320  1.1  mrg                 if (label.length)
   3321  1.1  mrg                 {
   3322  1.1  mrg                     i = j + 1;
   3323  1.1  mrg                     return true;
   3324  1.1  mrg                 }
   3325  1.1  mrg                 return false;
   3326  1.1  mrg             default:
   3327  1.1  mrg                 label ~= c;
   3328  1.1  mrg                 break;
   3329  1.1  mrg             }
   3330  1.1  mrg         }
   3331  1.1  mrg         label.length = 0;
   3332  1.1  mrg         return false;
   3333  1.1  mrg     }
   3334  1.1  mrg 
   3335  1.1  mrg     /****************************************************
   3336  1.1  mrg      * Parse and store a Markdown link URL, optionally enclosed in `<>` brackets
   3337  1.1  mrg      * Params:
   3338  1.1  mrg      *  buf   = an OutBuffer containing the DDoc
   3339  1.1  mrg      *  i     = the index within `buf` that points to the first character of the URL.
   3340  1.1  mrg      *          If this function succeeds `i` will point just after the the end of the URL.
   3341  1.1  mrg      * Returns: whether a URL was found and parsed
   3342  1.1  mrg      */
   3343  1.1  mrg     private bool parseHref(ref OutBuffer buf, ref size_t i)
   3344  1.1  mrg     {
   3345  1.1  mrg         size_t j = skipChars(buf, i, " \t");
   3346  1.1  mrg 
   3347  1.1  mrg         size_t iHrefStart = j;
   3348  1.1  mrg         size_t parenDepth = 1;
   3349  1.1  mrg         bool inPointy = false;
   3350  1.1  mrg         const slice = buf[];
   3351  1.1  mrg         for (; j < slice.length; j++)
   3352  1.1  mrg         {
   3353  1.1  mrg             switch (slice[j])
   3354  1.1  mrg             {
   3355  1.1  mrg             case '<':
   3356  1.1  mrg                 if (!inPointy && j == iHrefStart)
   3357  1.1  mrg                 {
   3358  1.1  mrg                     inPointy = true;
   3359  1.1  mrg                     ++iHrefStart;
   3360  1.1  mrg                 }
   3361  1.1  mrg                 break;
   3362  1.1  mrg             case '>':
   3363  1.1  mrg                 if (inPointy && slice[j-1] != '\\')
   3364  1.1  mrg                     goto LReturnHref;
   3365  1.1  mrg                 break;
   3366  1.1  mrg             case '(':
   3367  1.1  mrg                 if (!inPointy && slice[j-1] != '\\')
   3368  1.1  mrg                     ++parenDepth;
   3369  1.1  mrg                 break;
   3370  1.1  mrg             case ')':
   3371  1.1  mrg                 if (!inPointy && slice[j-1] != '\\')
   3372  1.1  mrg                 {
   3373  1.1  mrg                     --parenDepth;
   3374  1.1  mrg                     if (!parenDepth)
   3375  1.1  mrg                         goto LReturnHref;
   3376  1.1  mrg                 }
   3377  1.1  mrg                 break;
   3378  1.1  mrg             case ' ':
   3379  1.1  mrg             case '\t':
   3380  1.1  mrg             case '\r':
   3381  1.1  mrg             case '\n':
   3382  1.1  mrg                 if (inPointy)
   3383  1.1  mrg                 {
   3384  1.1  mrg                     // invalid link
   3385  1.1  mrg                     return false;
   3386  1.1  mrg                 }
   3387  1.1  mrg                 goto LReturnHref;
   3388  1.1  mrg             default:
   3389  1.1  mrg                 break;
   3390  1.1  mrg             }
   3391  1.1  mrg         }
   3392  1.1  mrg         if (inPointy)
   3393  1.1  mrg             return false;
   3394  1.1  mrg     LReturnHref:
   3395  1.1  mrg         auto href = slice[iHrefStart .. j].dup;
   3396  1.1  mrg         this.href = cast(string) percentEncode(removeEscapeBackslashes(href)).replaceChar(',', "$(COMMA)");
   3397  1.1  mrg         i = j;
   3398  1.1  mrg         if (inPointy)
   3399  1.1  mrg             ++i;
   3400  1.1  mrg         return true;
   3401  1.1  mrg     }
   3402  1.1  mrg 
   3403  1.1  mrg     /****************************************************
   3404  1.1  mrg      * Parse and store a Markdown link title, enclosed in parentheses or `'` or `"` quotes
   3405  1.1  mrg      * Params:
   3406  1.1  mrg      *  buf   = an OutBuffer containing the DDoc
   3407  1.1  mrg      *  i     = the index within `buf` that points to the first character of the title.
   3408  1.1  mrg      *          If this function succeeds `i` will point just after the the end of the title.
   3409  1.1  mrg      * Returns: whether a title was found and parsed
   3410  1.1  mrg      */
   3411  1.1  mrg     private bool parseTitle(ref OutBuffer buf, ref size_t i)
   3412  1.1  mrg     {
   3413  1.1  mrg         size_t j = skipChars(buf, i, " \t");
   3414  1.1  mrg         if (j >= buf.length)
   3415  1.1  mrg             return false;
   3416  1.1  mrg 
   3417  1.1  mrg         char type = buf[j];
   3418  1.1  mrg         if (type != '"' && type != '\'' && type != '(')
   3419  1.1  mrg             return false;
   3420  1.1  mrg         if (type == '(')
   3421  1.1  mrg             type = ')';
   3422  1.1  mrg 
   3423  1.1  mrg         const iTitleStart = j + 1;
   3424  1.1  mrg         size_t iNewline = 0;
   3425  1.1  mrg         const slice = buf[];
   3426  1.1  mrg         for (j = iTitleStart; j < slice.length; j++)
   3427  1.1  mrg         {
   3428  1.1  mrg             const c = slice[j];
   3429  1.1  mrg             switch (c)
   3430  1.1  mrg             {
   3431  1.1  mrg             case ')':
   3432  1.1  mrg             case '"':
   3433  1.1  mrg             case '\'':
   3434  1.1  mrg                 if (type == c && slice[j-1] != '\\')
   3435  1.1  mrg                     goto LEndTitle;
   3436  1.1  mrg                 iNewline = 0;
   3437  1.1  mrg                 break;
   3438  1.1  mrg             case ' ':
   3439  1.1  mrg             case '\t':
   3440  1.1  mrg             case '\r':
   3441  1.1  mrg                 break;
   3442  1.1  mrg             case '\n':
   3443  1.1  mrg                 if (iNewline)
   3444  1.1  mrg                 {
   3445  1.1  mrg                     // no blank lines in titles
   3446  1.1  mrg                     return false;
   3447  1.1  mrg                 }
   3448  1.1  mrg                 iNewline = j;
   3449  1.1  mrg                 break;
   3450  1.1  mrg             default:
   3451  1.1  mrg                 iNewline = 0;
   3452  1.1  mrg                 break;
   3453  1.1  mrg             }
   3454  1.1  mrg         }
   3455  1.1  mrg         return false;
   3456  1.1  mrg     LEndTitle:
   3457  1.1  mrg         auto title = slice[iTitleStart .. j].dup;
   3458  1.1  mrg         this.title = cast(string) removeEscapeBackslashes(title).
   3459  1.1  mrg             replaceChar(',', "$(COMMA)").
   3460  1.1  mrg             replaceChar('"', "$(QUOTE)");
   3461  1.1  mrg         i = j + 1;
   3462  1.1  mrg         return true;
   3463  1.1  mrg     }
   3464  1.1  mrg 
   3465  1.1  mrg     /****************************************************
   3466  1.1  mrg      * Replace a Markdown link or image with the appropriate macro
   3467  1.1  mrg      * Params:
   3468  1.1  mrg      *  buf       = an OutBuffer containing the DDoc
   3469  1.1  mrg      *  i         = the index within `buf` that points to the `]` character of the inline link.
   3470  1.1  mrg      *              When this function returns it will be adjusted to the end of the inserted macro.
   3471  1.1  mrg      *  iLinkEnd  = the index within `buf` that points just after the last character of the link
   3472  1.1  mrg      *  delimiter = the Markdown delimiter that started the link or image
   3473  1.1  mrg      */
   3474  1.1  mrg     private void replaceLink(ref OutBuffer buf, ref size_t i, size_t iLinkEnd, MarkdownDelimiter delimiter)
   3475  1.1  mrg     {
   3476  1.1  mrg         size_t iAfterLink = i - delimiter.count;
   3477  1.1  mrg         string macroName;
   3478  1.1  mrg         if (symbol)
   3479  1.1  mrg         {
   3480  1.1  mrg             macroName = "$(SYMBOL_LINK ";
   3481  1.1  mrg         }
   3482  1.1  mrg         else if (title.length)
   3483  1.1  mrg         {
   3484  1.1  mrg             if (delimiter.type == '[')
   3485  1.1  mrg                 macroName = "$(LINK_TITLE ";
   3486  1.1  mrg             else
   3487  1.1  mrg                 macroName = "$(IMAGE_TITLE ";
   3488  1.1  mrg         }
   3489  1.1  mrg         else
   3490  1.1  mrg         {
   3491  1.1  mrg             if (delimiter.type == '[')
   3492  1.1  mrg                 macroName = "$(LINK2 ";
   3493  1.1  mrg             else
   3494  1.1  mrg                 macroName = "$(IMAGE ";
   3495  1.1  mrg         }
   3496  1.1  mrg         buf.remove(delimiter.iStart, delimiter.count);
   3497  1.1  mrg         buf.remove(i - delimiter.count, iLinkEnd - i);
   3498  1.1  mrg         iLinkEnd = buf.insert(delimiter.iStart, macroName);
   3499  1.1  mrg         iLinkEnd = buf.insert(iLinkEnd, href);
   3500  1.1  mrg         iLinkEnd = buf.insert(iLinkEnd, ", ");
   3501  1.1  mrg         iAfterLink += macroName.length + href.length + 2;
   3502  1.1  mrg         if (title.length)
   3503  1.1  mrg         {
   3504  1.1  mrg             iLinkEnd = buf.insert(iLinkEnd, title);
   3505  1.1  mrg             iLinkEnd = buf.insert(iLinkEnd, ", ");
   3506  1.1  mrg             iAfterLink += title.length + 2;
   3507  1.1  mrg 
   3508  1.1  mrg             // Link macros with titles require escaping commas
   3509  1.1  mrg             for (size_t j = iLinkEnd; j < iAfterLink; ++j)
   3510  1.1  mrg                 if (buf[j] == ',')
   3511  1.1  mrg                 {
   3512  1.1  mrg                     buf.remove(j, 1);
   3513  1.1  mrg                     j = buf.insert(j, "$(COMMA)") - 1;
   3514  1.1  mrg                     iAfterLink += 7;
   3515  1.1  mrg                 }
   3516  1.1  mrg         }
   3517  1.1  mrg // TODO: if image, remove internal macros, leaving only text
   3518  1.1  mrg         buf.insert(iAfterLink, ")");
   3519  1.1  mrg         i = iAfterLink;
   3520  1.1  mrg     }
   3521  1.1  mrg 
   3522  1.1  mrg     /****************************************************
   3523  1.1  mrg      * Store the Markdown link definition and remove it from `buf`
   3524  1.1  mrg      * Params:
   3525  1.1  mrg      *  buf               = an OutBuffer containing the DDoc
   3526  1.1  mrg      *  i                 = the index within `buf` that points to the `[` character at the start of the link definition.
   3527  1.1  mrg      *                      When this function returns it will be adjusted to exclude the link definition.
   3528  1.1  mrg      *  iEnd              = the index within `buf` that points just after the end of the definition
   3529  1.1  mrg      *  linkReferences    = previously parsed link references. When this function returns it may contain
   3530  1.1  mrg      *                      an additional reference.
   3531  1.1  mrg      *  loc               = the current location in the file
   3532  1.1  mrg      */
   3533  1.1  mrg     private void storeAndReplaceDefinition(ref OutBuffer buf, ref size_t i, size_t iEnd, ref MarkdownLinkReferences linkReferences, const ref Loc loc)
   3534  1.1  mrg     {
   3535  1.1  mrg         if (global.params.vmarkdown)
   3536  1.1  mrg             message(loc, "Ddoc: found link reference '%.*s' to '%.*s'", cast(int)label.length, label.ptr, cast(int)href.length, href.ptr);
   3537  1.1  mrg 
   3538  1.1  mrg         // Remove the definition and trailing whitespace
   3539  1.1  mrg         iEnd = skipChars(buf, iEnd, " \t\r\n");
   3540  1.1  mrg         buf.remove(i, iEnd - i);
   3541  1.1  mrg         i -= 2;
   3542  1.1  mrg 
   3543  1.1  mrg         string lowercaseLabel = label.toLowercase();
   3544  1.1  mrg         if (lowercaseLabel !in linkReferences.references)
   3545  1.1  mrg             linkReferences.references[lowercaseLabel] = this;
   3546  1.1  mrg     }
   3547  1.1  mrg 
   3548  1.1  mrg     /****************************************************
   3549  1.1  mrg      * Remove Markdown escaping backslashes from the given string
   3550  1.1  mrg      * Params:
   3551  1.1  mrg      *  s = the string to remove escaping backslashes from
   3552  1.1  mrg      * Returns: `s` without escaping backslashes in it
   3553  1.1  mrg      */
   3554  1.1  mrg     private static char[] removeEscapeBackslashes(char[] s)
   3555  1.1  mrg     {
   3556  1.1  mrg         if (!s.length)
   3557  1.1  mrg             return s;
   3558  1.1  mrg 
   3559  1.1  mrg         // avoid doing anything if there isn't anything to escape
   3560  1.1  mrg         size_t i;
   3561  1.1  mrg         for (i = 0; i < s.length-1; ++i)
   3562  1.1  mrg             if (s[i] == '\\' && ispunct(s[i+1]))
   3563  1.1  mrg                 break;
   3564  1.1  mrg         if (i == s.length-1)
   3565  1.1  mrg             return s;
   3566  1.1  mrg 
   3567  1.1  mrg         // copy characters backwards, then truncate
   3568  1.1  mrg         size_t j = i + 1;
   3569  1.1  mrg         s[i] = s[j];
   3570  1.1  mrg         for (++i, ++j; j < s.length; ++i, ++j)
   3571  1.1  mrg         {
   3572  1.1  mrg             if (j < s.length-1 && s[j] == '\\' && ispunct(s[j+1]))
   3573  1.1  mrg                 ++j;
   3574  1.1  mrg             s[i] = s[j];
   3575  1.1  mrg         }
   3576  1.1  mrg         s.length -= (j - i);
   3577  1.1  mrg         return s;
   3578  1.1  mrg     }
   3579  1.1  mrg 
   3580  1.1  mrg     ///
   3581  1.1  mrg     unittest
   3582  1.1  mrg     {
   3583  1.1  mrg         assert(removeEscapeBackslashes("".dup) == "");
   3584  1.1  mrg         assert(removeEscapeBackslashes(`\a`.dup) == `\a`);
   3585  1.1  mrg         assert(removeEscapeBackslashes(`.\`.dup) == `.\`);
   3586  1.1  mrg         assert(removeEscapeBackslashes(`\.\`.dup) == `.\`);
   3587  1.1  mrg         assert(removeEscapeBackslashes(`\.`.dup) == `.`);
   3588  1.1  mrg         assert(removeEscapeBackslashes(`\.\.`.dup) == `..`);
   3589  1.1  mrg         assert(removeEscapeBackslashes(`a\.b\.c`.dup) == `a.b.c`);
   3590  1.1  mrg     }
   3591  1.1  mrg 
   3592  1.1  mrg     /****************************************************
   3593  1.1  mrg      * Percent-encode (AKA URL-encode) the given string
   3594  1.1  mrg      * Params:
   3595  1.1  mrg      *  s = the string to percent-encode
   3596  1.1  mrg      * Returns: `s` with special characters percent-encoded
   3597  1.1  mrg      */
   3598  1.1  mrg     private static inout(char)[] percentEncode(inout(char)[] s) pure
   3599  1.1  mrg     {
   3600  1.1  mrg         static bool shouldEncode(char c)
   3601  1.1  mrg         {
   3602  1.1  mrg             return ((c < '0' && c != '!' && c != '#' && c != '$' && c != '%' && c != '&' && c != '\'' && c != '(' &&
   3603  1.1  mrg                     c != ')' && c != '*' && c != '+' && c != ',' && c != '-' && c != '.' && c != '/')
   3604  1.1  mrg                 || (c > '9' && c < 'A' && c != ':' && c != ';' && c != '=' && c != '?' && c != '@')
   3605  1.1  mrg                 || (c > 'Z' && c < 'a' && c != '[' && c != ']' && c != '_')
   3606  1.1  mrg                 || (c > 'z' && c != '~'));
   3607  1.1  mrg         }
   3608  1.1  mrg 
   3609  1.1  mrg         for (size_t i = 0; i < s.length; ++i)
   3610  1.1  mrg         {
   3611  1.1  mrg             if (shouldEncode(s[i]))
   3612  1.1  mrg             {
   3613  1.1  mrg                 immutable static hexDigits = "0123456789ABCDEF";
   3614  1.1  mrg                 immutable encoded1 = hexDigits[s[i] >> 4];
   3615  1.1  mrg                 immutable encoded2 = hexDigits[s[i] & 0x0F];
   3616  1.1  mrg                 s = s[0..i] ~ '%' ~ encoded1 ~ encoded2 ~ s[i+1..$];
   3617  1.1  mrg                 i += 2;
   3618  1.1  mrg             }
   3619  1.1  mrg         }
   3620  1.1  mrg         return s;
   3621  1.1  mrg     }
   3622  1.1  mrg 
   3623  1.1  mrg     ///
   3624  1.1  mrg     unittest
   3625  1.1  mrg     {
   3626  1.1  mrg         assert(percentEncode("") == "");
   3627  1.1  mrg         assert(percentEncode("aB12-._~/?") == "aB12-._~/?");
   3628  1.1  mrg         assert(percentEncode("<\n>") == "%3C%0A%3E");
   3629  1.1  mrg     }
   3630  1.1  mrg 
   3631  1.1  mrg     /**************************************************
   3632  1.1  mrg      * Skip a single newline at `i`
   3633  1.1  mrg      * Params:
   3634  1.1  mrg      *  buf   = an OutBuffer containing the DDoc
   3635  1.1  mrg      *  i     = the index within `buf` to start looking at.
   3636  1.1  mrg      *          If this function succeeds `i` will point after the newline.
   3637  1.1  mrg      * Returns: whether a newline was skipped
   3638  1.1  mrg      */
   3639  1.1  mrg     private static bool skipOneNewline(ref OutBuffer buf, ref size_t i) pure
   3640  1.1  mrg     {
   3641  1.1  mrg         if (i < buf.length && buf[i] == '\r')
   3642  1.1  mrg             ++i;
   3643  1.1  mrg         if (i < buf.length && buf[i] == '\n')
   3644  1.1  mrg         {
   3645  1.1  mrg             ++i;
   3646  1.1  mrg             return true;
   3647  1.1  mrg         }
   3648  1.1  mrg         return false;
   3649  1.1  mrg     }
   3650  1.1  mrg }
   3651  1.1  mrg 
   3652  1.1  mrg /**************************************************
   3653  1.1  mrg  * A set of Markdown link references.
   3654  1.1  mrg  */
   3655  1.1  mrg private struct MarkdownLinkReferences
   3656  1.1  mrg {
   3657  1.1  mrg     MarkdownLink[string] references;    // link references keyed by normalized label
   3658  1.1  mrg     MarkdownLink[string] symbols;       // link symbols keyed by name
   3659  1.1  mrg     Scope* _scope;      // the current scope
   3660  1.1  mrg     bool extractedAll;  // the index into the buffer of the last-parsed reference
   3661  1.1  mrg 
   3662  1.1  mrg     /**************************************************
   3663  1.1  mrg      * Look up a reference by label, searching through the rest of the buffer if needed.
   3664  1.1  mrg      * Symbols in the current scope are searched for if the DDoc doesn't define the reference.
   3665  1.1  mrg      * Params:
   3666  1.1  mrg      *  label = the label to find the reference for
   3667  1.1  mrg      *  buf   = an OutBuffer containing the DDoc
   3668  1.1  mrg      *  i     = the index within `buf` to start searching for references at
   3669  1.1  mrg      *  loc   = the current location in the file
   3670  1.1  mrg      * Returns: a link. If the `href` member has a value then the reference is valid.
   3671  1.1  mrg      */
   3672  1.1  mrg     MarkdownLink lookupReference(string label, ref OutBuffer buf, size_t i, const ref Loc loc)
   3673  1.1  mrg     {
   3674  1.1  mrg         const lowercaseLabel = label.toLowercase();
   3675  1.1  mrg         if (lowercaseLabel !in references)
   3676  1.1  mrg             extractReferences(buf, i, loc);
   3677  1.1  mrg 
   3678  1.1  mrg         if (lowercaseLabel in references)
   3679  1.1  mrg             return references[lowercaseLabel];
   3680  1.1  mrg 
   3681  1.1  mrg         return MarkdownLink();
   3682  1.1  mrg     }
   3683  1.1  mrg 
   3684  1.1  mrg     /**
   3685  1.1  mrg      * Look up the link for the D symbol with the given name.
   3686  1.1  mrg      * If found, the link is cached in the `symbols` member.
   3687  1.1  mrg      * Params:
   3688  1.1  mrg      *  name  = the name of the symbol
   3689  1.1  mrg      * Returns: the link for the symbol or a link with a `null` href
   3690  1.1  mrg      */
   3691  1.1  mrg     MarkdownLink lookupSymbol(string name)
   3692  1.1  mrg     {
   3693  1.1  mrg         if (name in symbols)
   3694  1.1  mrg             return symbols[name];
   3695  1.1  mrg 
   3696  1.1  mrg         const ids = split(name, '.');
   3697  1.1  mrg 
   3698  1.1  mrg         MarkdownLink link;
   3699  1.1  mrg         auto id = Identifier.lookup(ids[0].ptr, ids[0].length);
   3700  1.1  mrg         if (id)
   3701  1.1  mrg         {
   3702  1.1  mrg             auto loc = Loc();
   3703  1.1  mrg             auto symbol = _scope.search(loc, id, null, IgnoreErrors);
   3704  1.1  mrg             for (size_t i = 1; symbol && i < ids.length; ++i)
   3705  1.1  mrg             {
   3706  1.1  mrg                 id = Identifier.lookup(ids[i].ptr, ids[i].length);
   3707  1.1  mrg                 symbol = id !is null ? symbol.search(loc, id, IgnoreErrors) : null;
   3708  1.1  mrg             }
   3709  1.1  mrg             if (symbol)
   3710  1.1  mrg                 link = MarkdownLink(createHref(symbol), null, name, symbol);
   3711  1.1  mrg         }
   3712  1.1  mrg 
   3713  1.1  mrg         symbols[name] = link;
   3714  1.1  mrg         return link;
   3715  1.1  mrg     }
   3716  1.1  mrg 
   3717  1.1  mrg     /**************************************************
   3718  1.1  mrg      * Remove and store all link references from the document, in the form of
   3719  1.1  mrg      * `[label]: href "optional title"`
   3720  1.1  mrg      * Params:
   3721  1.1  mrg      *  buf   = an OutBuffer containing the DDoc
   3722  1.1  mrg      *  i     = the index within `buf` to start looking at
   3723  1.1  mrg      *  loc   = the current location in the file
   3724  1.1  mrg      * Returns: whether a reference was extracted
   3725  1.1  mrg      */
   3726  1.1  mrg     private void extractReferences(ref OutBuffer buf, size_t i, const ref Loc loc)
   3727  1.1  mrg     {
   3728  1.1  mrg         static bool isFollowedBySpace(ref OutBuffer buf, size_t i)
   3729  1.1  mrg         {
   3730  1.1  mrg             return i+1 < buf.length && (buf[i+1] == ' ' || buf[i+1] == '\t');
   3731  1.1  mrg         }
   3732  1.1  mrg 
   3733  1.1  mrg         if (extractedAll)
   3734  1.1  mrg             return;
   3735  1.1  mrg 
   3736  1.1  mrg         bool leadingBlank = false;
   3737  1.1  mrg         int inCode = false;
   3738  1.1  mrg         bool newParagraph = true;
   3739  1.1  mrg         MarkdownDelimiter[] delimiters;
   3740  1.1  mrg         for (; i < buf.length; ++i)
   3741  1.1  mrg         {
   3742  1.1  mrg             const c = buf[i];
   3743  1.1  mrg             switch (c)
   3744  1.1  mrg             {
   3745  1.1  mrg             case ' ':
   3746  1.1  mrg             case '\t':
   3747  1.1  mrg                 break;
   3748  1.1  mrg             case '\n':
   3749  1.1  mrg                 if (leadingBlank && !inCode)
   3750  1.1  mrg                     newParagraph = true;
   3751  1.1  mrg                 leadingBlank = true;
   3752  1.1  mrg                 break;
   3753  1.1  mrg             case '\\':
   3754  1.1  mrg                 ++i;
   3755  1.1  mrg                 break;
   3756  1.1  mrg             case '#':
   3757  1.1  mrg                 if (leadingBlank && !inCode)
   3758  1.1  mrg                     newParagraph = true;
   3759  1.1  mrg                 leadingBlank = false;
   3760  1.1  mrg                 break;
   3761  1.1  mrg             case '>':
   3762  1.1  mrg                 if (leadingBlank && !inCode)
   3763  1.1  mrg                     newParagraph = true;
   3764  1.1  mrg                 break;
   3765  1.1  mrg             case '+':
   3766  1.1  mrg                 if (leadingBlank && !inCode && isFollowedBySpace(buf, i))
   3767  1.1  mrg                     newParagraph = true;
   3768  1.1  mrg                 else
   3769  1.1  mrg                     leadingBlank = false;
   3770  1.1  mrg                 break;
   3771  1.1  mrg             case '0':
   3772  1.1  mrg             ..
   3773  1.1  mrg             case '9':
   3774  1.1  mrg                 if (leadingBlank && !inCode)
   3775  1.1  mrg                 {
   3776  1.1  mrg                     i = skipChars(buf, i, "0123456789");
   3777  1.1  mrg                     if (i < buf.length &&
   3778  1.1  mrg                         (buf[i] == '.' || buf[i] == ')') &&
   3779  1.1  mrg                         isFollowedBySpace(buf, i))
   3780  1.1  mrg                         newParagraph = true;
   3781  1.1  mrg                     else
   3782  1.1  mrg                         leadingBlank = false;
   3783  1.1  mrg                 }
   3784  1.1  mrg                 break;
   3785  1.1  mrg             case '*':
   3786  1.1  mrg                 if (leadingBlank && !inCode)
   3787  1.1  mrg                 {
   3788  1.1  mrg                     newParagraph = true;
   3789  1.1  mrg                     if (!isFollowedBySpace(buf, i))
   3790  1.1  mrg                         leadingBlank = false;
   3791  1.1  mrg                 }
   3792  1.1  mrg                 break;
   3793  1.1  mrg             case '`':
   3794  1.1  mrg             case '~':
   3795  1.1  mrg                 if (leadingBlank && i+2 < buf.length && buf[i+1] == c && buf[i+2] == c)
   3796  1.1  mrg                 {
   3797  1.1  mrg                     inCode = inCode == c ? false : c;
   3798  1.1  mrg                     i = skipChars(buf, i, [c]) - 1;
   3799  1.1  mrg                     newParagraph = true;
   3800  1.1  mrg                 }
   3801  1.1  mrg                 leadingBlank = false;
   3802  1.1  mrg                 break;
   3803  1.1  mrg             case '-':
   3804  1.1  mrg                 if (leadingBlank && !inCode && isFollowedBySpace(buf, i))
   3805  1.1  mrg                     goto case '+';
   3806  1.1  mrg                 else
   3807  1.1  mrg                     goto case '`';
   3808  1.1  mrg             case '[':
   3809  1.1  mrg                 if (leadingBlank && !inCode && newParagraph)
   3810  1.1  mrg                     delimiters ~= MarkdownDelimiter(i, 1, 0, false, false, true, c);
   3811  1.1  mrg                 break;
   3812  1.1  mrg             case ']':
   3813  1.1  mrg                 if (delimiters.length && !inCode &&
   3814  1.1  mrg                     MarkdownLink.replaceReferenceDefinition(buf, i, delimiters, cast(int) delimiters.length - 1, this, loc))
   3815  1.1  mrg                     --i;
   3816  1.1  mrg                 break;
   3817  1.1  mrg             default:
   3818  1.1  mrg                 if (leadingBlank)
   3819  1.1  mrg                     newParagraph = false;
   3820  1.1  mrg                 leadingBlank = false;
   3821  1.1  mrg                 break;
   3822  1.1  mrg             }
   3823  1.1  mrg         }
   3824  1.1  mrg         extractedAll = true;
   3825  1.1  mrg     }
   3826  1.1  mrg 
   3827  1.1  mrg     /**
   3828  1.1  mrg      * Split a string by a delimiter, excluding the delimiter.
   3829  1.1  mrg      * Params:
   3830  1.1  mrg      *  s         = the string to split
   3831  1.1  mrg      *  delimiter = the character to split by
   3832  1.1  mrg      * Returns: the resulting array of strings
   3833  1.1  mrg      */
   3834  1.1  mrg     private static string[] split(string s, char delimiter) pure
   3835  1.1  mrg     {
   3836  1.1  mrg         string[] result;
   3837  1.1  mrg         size_t iStart = 0;
   3838  1.1  mrg         foreach (size_t i; 0..s.length)
   3839  1.1  mrg             if (s[i] == delimiter)
   3840  1.1  mrg             {
   3841  1.1  mrg                 result ~= s[iStart..i];
   3842  1.1  mrg                 iStart = i + 1;
   3843  1.1  mrg             }
   3844  1.1  mrg         result ~= s[iStart..$];
   3845  1.1  mrg         return result;
   3846  1.1  mrg     }
   3847  1.1  mrg 
   3848  1.1  mrg     ///
   3849  1.1  mrg     unittest
   3850  1.1  mrg     {
   3851  1.1  mrg         assert(split("", ',') == [""]);
   3852  1.1  mrg         assert(split("ab", ',') == ["ab"]);
   3853  1.1  mrg         assert(split("a,b", ',') == ["a", "b"]);
   3854  1.1  mrg         assert(split("a,,b", ',') == ["a", "", "b"]);
   3855  1.1  mrg         assert(split(",ab", ',') == ["", "ab"]);
   3856  1.1  mrg         assert(split("ab,", ',') == ["ab", ""]);
   3857  1.1  mrg     }
   3858  1.1  mrg 
   3859  1.1  mrg     /**
   3860  1.1  mrg      * Create a HREF for the given D symbol.
   3861  1.1  mrg      * The HREF is relative to the current location if possible.
   3862  1.1  mrg      * Params:
   3863  1.1  mrg      *  symbol    = the symbol to create a HREF for.
   3864  1.1  mrg      * Returns: the resulting href
   3865  1.1  mrg      */
   3866  1.1  mrg     private string createHref(Dsymbol symbol)
   3867  1.1  mrg     {
   3868  1.1  mrg         Dsymbol root = symbol;
   3869  1.1  mrg 
   3870  1.1  mrg         const(char)[] lref;
   3871  1.1  mrg         while (symbol && symbol.ident && !symbol.isModule())
   3872  1.1  mrg         {
   3873  1.1  mrg             if (lref.length)
   3874  1.1  mrg                 lref = '.' ~ lref;
   3875  1.1  mrg             lref = symbol.ident.toString() ~ lref;
   3876  1.1  mrg             symbol = symbol.parent;
   3877  1.1  mrg         }
   3878  1.1  mrg 
   3879  1.1  mrg         const(char)[] path;
   3880  1.1  mrg         if (symbol && symbol.ident && symbol.isModule() != _scope._module)
   3881  1.1  mrg         {
   3882  1.1  mrg             do
   3883  1.1  mrg             {
   3884  1.1  mrg                 root = symbol;
   3885  1.1  mrg 
   3886  1.1  mrg                 // If the module has a file name, we're done
   3887  1.1  mrg                 if (const m = symbol.isModule())
   3888  1.1  mrg                     if (m.docfile)
   3889  1.1  mrg                     {
   3890  1.1  mrg                         path = m.docfile.toString();
   3891  1.1  mrg                         break;
   3892  1.1  mrg                     }
   3893  1.1  mrg 
   3894  1.1  mrg                 if (path.length)
   3895  1.1  mrg                     path = '_' ~ path;
   3896  1.1  mrg                 path = symbol.ident.toString() ~ path;
   3897  1.1  mrg                 symbol = symbol.parent;
   3898  1.1  mrg             } while (symbol && symbol.ident);
   3899  1.1  mrg 
   3900  1.1  mrg             if (!symbol && path.length)
   3901  1.1  mrg                 path ~= "$(DOC_EXTENSION)";
   3902  1.1  mrg         }
   3903  1.1  mrg 
   3904  1.1  mrg         // Attempt an absolute URL if not in the same package
   3905  1.1  mrg         while (root.parent)
   3906  1.1  mrg             root = root.parent;
   3907  1.1  mrg         Dsymbol scopeRoot = _scope._module;
   3908  1.1  mrg         while (scopeRoot.parent)
   3909  1.1  mrg             scopeRoot = scopeRoot.parent;
   3910  1.1  mrg         if (scopeRoot != root)
   3911  1.1  mrg         {
   3912  1.1  mrg             path = "$(DOC_ROOT_" ~ root.ident.toString() ~ ')' ~ path;
   3913  1.1  mrg             lref = '.' ~ lref;  // remote URIs like Phobos and Mir use .prefixes
   3914  1.1  mrg         }
   3915  1.1  mrg 
   3916  1.1  mrg         return cast(string) (path ~ '#' ~ lref);
   3917  1.1  mrg     }
   3918  1.1  mrg }
   3919  1.1  mrg 
   3920  1.1  mrg private enum TableColumnAlignment
   3921  1.1  mrg {
   3922  1.1  mrg     none,
   3923  1.1  mrg     left,
   3924  1.1  mrg     center,
   3925  1.1  mrg     right
   3926  1.1  mrg }
   3927  1.1  mrg 
   3928  1.1  mrg /****************************************************
   3929  1.1  mrg  * Parse a Markdown table delimiter row in the form of `| -- | :-- | :--: | --: |`
   3930  1.1  mrg  * where the example text has four columns with the following alignments:
   3931  1.1  mrg  * default, left, center, and right. The first and last pipes are optional. If a
   3932  1.1  mrg  * delimiter row is found it will be removed from `buf`.
   3933  1.1  mrg  *
   3934  1.1  mrg  * Params:
   3935  1.1  mrg  *  buf     = an OutBuffer containing the DDoc
   3936  1.1  mrg  *  iStart  = the index within `buf` that the delimiter row starts at
   3937  1.1  mrg  *  inQuote   = whether the table is inside a quote
   3938  1.1  mrg  *  columnAlignments = alignments to populate for each column
   3939  1.1  mrg  * Returns: the index of the end of the parsed delimiter, or `0` if not found
   3940  1.1  mrg  */
   3941  1.1  mrg private size_t parseTableDelimiterRow(ref OutBuffer buf, const size_t iStart, bool inQuote, ref TableColumnAlignment[] columnAlignments)
   3942  1.1  mrg {
   3943  1.1  mrg     size_t i = skipChars(buf, iStart, inQuote ? ">| \t" : "| \t");
   3944  1.1  mrg     while (i < buf.length && buf[i] != '\r' && buf[i] != '\n')
   3945  1.1  mrg     {
   3946  1.1  mrg         const leftColon = buf[i] == ':';
   3947  1.1  mrg         if (leftColon)
   3948  1.1  mrg             ++i;
   3949  1.1  mrg 
   3950  1.1  mrg         if (i >= buf.length || buf[i] != '-')
   3951  1.1  mrg             break;
   3952  1.1  mrg         i = skipChars(buf, i, "-");
   3953  1.1  mrg 
   3954  1.1  mrg         const rightColon = i < buf.length && buf[i] == ':';
   3955  1.1  mrg         i = skipChars(buf, i, ": \t");
   3956  1.1  mrg 
   3957  1.1  mrg         if (i >= buf.length || (buf[i] != '|' && buf[i] != '\r' && buf[i] != '\n'))
   3958  1.1  mrg             break;
   3959  1.1  mrg         i = skipChars(buf, i, "| \t");
   3960  1.1  mrg 
   3961  1.1  mrg         columnAlignments ~= (leftColon && rightColon) ? TableColumnAlignment.center :
   3962  1.1  mrg                 leftColon ? TableColumnAlignment.left :
   3963  1.1  mrg                 rightColon ? TableColumnAlignment.right :
   3964  1.1  mrg                 TableColumnAlignment.none;
   3965  1.1  mrg     }
   3966  1.1  mrg 
   3967  1.1  mrg     if (i < buf.length && buf[i] != '\r' && buf[i] != '\n' && buf[i] != ')')
   3968  1.1  mrg     {
   3969  1.1  mrg         columnAlignments.length = 0;
   3970  1.1  mrg         return 0;
   3971  1.1  mrg     }
   3972  1.1  mrg 
   3973  1.1  mrg     if (i < buf.length && buf[i] == '\r') ++i;
   3974  1.1  mrg     if (i < buf.length && buf[i] == '\n') ++i;
   3975  1.1  mrg     return i;
   3976  1.1  mrg }
   3977  1.1  mrg 
   3978  1.1  mrg /****************************************************
   3979  1.1  mrg  * Look for a table delimiter row, and if found parse the previous row as a
   3980  1.1  mrg  * table header row. If both exist with a matching number of columns, start a
   3981  1.1  mrg  * table.
   3982  1.1  mrg  *
   3983  1.1  mrg  * Params:
   3984  1.1  mrg  *  buf       = an OutBuffer containing the DDoc
   3985  1.1  mrg  *  iStart    = the index within `buf` that the table header row starts at, inclusive
   3986  1.1  mrg  *  iEnd      = the index within `buf` that the table header row ends at, exclusive
   3987  1.1  mrg  *  loc       = the current location in the file
   3988  1.1  mrg  *  inQuote   = whether the table is inside a quote
   3989  1.1  mrg  *  inlineDelimiters = delimiters containing columns separators and any inline emphasis
   3990  1.1  mrg  *  columnAlignments = the parsed alignments for each column
   3991  1.1  mrg  * Returns: the number of characters added by starting the table, or `0` if unchanged
   3992  1.1  mrg  */
   3993  1.1  mrg private size_t startTable(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, bool inQuote, ref MarkdownDelimiter[] inlineDelimiters, out TableColumnAlignment[] columnAlignments)
   3994  1.1  mrg {
   3995  1.1  mrg     const iDelimiterRowEnd = parseTableDelimiterRow(buf, iEnd + 1, inQuote, columnAlignments);
   3996  1.1  mrg     if (iDelimiterRowEnd)
   3997  1.1  mrg     {
   3998  1.1  mrg         size_t delta;
   3999  1.1  mrg         if (replaceTableRow(buf, iStart, iEnd, loc, inlineDelimiters, columnAlignments, true, delta))
   4000  1.1  mrg         {
   4001  1.1  mrg             buf.remove(iEnd + delta, iDelimiterRowEnd - iEnd);
   4002  1.1  mrg             buf.insert(iEnd + delta, "$(TBODY ");
   4003  1.1  mrg             buf.insert(iStart, "$(TABLE ");
   4004  1.1  mrg             return delta + 15;
   4005  1.1  mrg         }
   4006  1.1  mrg     }
   4007  1.1  mrg 
   4008  1.1  mrg     columnAlignments.length = 0;
   4009  1.1  mrg     return 0;
   4010  1.1  mrg }
   4011  1.1  mrg 
   4012  1.1  mrg /****************************************************
   4013  1.1  mrg  * Replace a Markdown table row in the form of table cells delimited by pipes:
   4014  1.1  mrg  * `| cell | cell | cell`. The first and last pipes are optional.
   4015  1.1  mrg  *
   4016  1.1  mrg  * Params:
   4017  1.1  mrg  *  buf       = an OutBuffer containing the DDoc
   4018  1.1  mrg  *  iStart    = the index within `buf` that the table row starts at, inclusive
   4019  1.1  mrg  *  iEnd      = the index within `buf` that the table row ends at, exclusive
   4020  1.1  mrg  *  loc       = the current location in the file
   4021  1.1  mrg  *  inlineDelimiters = delimiters containing columns separators and any inline emphasis
   4022  1.1  mrg  *  columnAlignments = alignments for each column
   4023  1.1  mrg  *  headerRow = if `true` then the number of columns will be enforced to match
   4024  1.1  mrg  *              `columnAlignments.length` and the row will be surrounded by a
   4025  1.1  mrg  *              `THEAD` macro
   4026  1.1  mrg  *  delta     = the number of characters added by replacing the row, or `0` if unchanged
   4027  1.1  mrg  * Returns: `true` if a table row was found and replaced
   4028  1.1  mrg  */
   4029  1.1  mrg private bool replaceTableRow(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, TableColumnAlignment[] columnAlignments, bool headerRow, out size_t delta)
   4030  1.1  mrg {
   4031  1.1  mrg     delta = 0;
   4032  1.1  mrg 
   4033  1.1  mrg     if (!columnAlignments.length || iStart == iEnd)
   4034  1.1  mrg         return false;
   4035  1.1  mrg 
   4036  1.1  mrg     iStart = skipChars(buf, iStart, " \t");
   4037  1.1  mrg     int cellCount = 0;
   4038  1.1  mrg     foreach (delimiter; inlineDelimiters)
   4039  1.1  mrg         if (delimiter.type == '|' && !delimiter.leftFlanking)
   4040  1.1  mrg             ++cellCount;
   4041  1.1  mrg     bool ignoreLast = inlineDelimiters.length > 0 && inlineDelimiters[$-1].type == '|';
   4042  1.1  mrg     if (ignoreLast)
   4043  1.1  mrg     {
   4044  1.1  mrg         const iLast = skipChars(buf, inlineDelimiters[$-1].iStart + inlineDelimiters[$-1].count, " \t");
   4045  1.1  mrg         ignoreLast = iLast >= iEnd;
   4046  1.1  mrg     }
   4047  1.1  mrg     if (!ignoreLast)
   4048  1.1  mrg         ++cellCount;
   4049  1.1  mrg 
   4050  1.1  mrg     if (headerRow && cellCount != columnAlignments.length)
   4051  1.1  mrg         return false;
   4052  1.1  mrg 
   4053  1.1  mrg     if (headerRow && global.params.vmarkdown)
   4054  1.1  mrg     {
   4055  1.1  mrg         const s = buf[][iStart..iEnd];
   4056  1.1  mrg         message(loc, "Ddoc: formatting table '%.*s'", cast(int)s.length, s.ptr);
   4057  1.1  mrg     }
   4058  1.1  mrg 
   4059  1.1  mrg     void replaceTableCell(size_t iCellStart, size_t iCellEnd, int cellIndex, int di)
   4060  1.1  mrg     {
   4061  1.1  mrg         const eDelta = replaceMarkdownEmphasis(buf, loc, inlineDelimiters, di);
   4062  1.1  mrg         delta += eDelta;
   4063  1.1  mrg         iCellEnd += eDelta;
   4064  1.1  mrg 
   4065  1.1  mrg         // strip trailing whitespace and delimiter
   4066  1.1  mrg         size_t i = iCellEnd - 1;
   4067  1.1  mrg         while (i > iCellStart && (buf[i] == '|' || buf[i] == ' ' || buf[i] == '\t'))
   4068  1.1  mrg             --i;
   4069  1.1  mrg         ++i;
   4070  1.1  mrg         buf.remove(i, iCellEnd - i);
   4071  1.1  mrg         delta -= iCellEnd - i;
   4072  1.1  mrg         iCellEnd = i;
   4073  1.1  mrg 
   4074  1.1  mrg         buf.insert(iCellEnd, ")");
   4075  1.1  mrg         ++delta;
   4076  1.1  mrg 
   4077  1.1  mrg         // strip initial whitespace and delimiter
   4078  1.1  mrg         i = skipChars(buf, iCellStart, "| \t");
   4079  1.1  mrg         buf.remove(iCellStart, i - iCellStart);
   4080  1.1  mrg         delta -= i - iCellStart;
   4081  1.1  mrg 
   4082  1.1  mrg         switch (columnAlignments[cellIndex])
   4083  1.1  mrg         {
   4084  1.1  mrg         case TableColumnAlignment.none:
   4085  1.1  mrg             buf.insert(iCellStart, headerRow ? "$(TH " : "$(TD ");
   4086  1.1  mrg             delta += 5;
   4087  1.1  mrg             break;
   4088  1.1  mrg         case TableColumnAlignment.left:
   4089  1.1  mrg             buf.insert(iCellStart, "left, ");
   4090  1.1  mrg             delta += 6;
   4091  1.1  mrg             goto default;
   4092  1.1  mrg         case TableColumnAlignment.center:
   4093  1.1  mrg             buf.insert(iCellStart, "center, ");
   4094  1.1  mrg             delta += 8;
   4095  1.1  mrg             goto default;
   4096  1.1  mrg         case TableColumnAlignment.right:
   4097  1.1  mrg             buf.insert(iCellStart, "right, ");
   4098  1.1  mrg             delta += 7;
   4099  1.1  mrg             goto default;
   4100  1.1  mrg         default:
   4101  1.1  mrg             buf.insert(iCellStart, headerRow ? "$(TH_ALIGN " : "$(TD_ALIGN ");
   4102  1.1  mrg             delta += 11;
   4103  1.1  mrg             break;
   4104  1.1  mrg         }
   4105  1.1  mrg     }
   4106  1.1  mrg 
   4107  1.1  mrg     int cellIndex = cellCount - 1;
   4108  1.1  mrg     size_t iCellEnd = iEnd;
   4109  1.1  mrg     foreach_reverse (di, delimiter; inlineDelimiters)
   4110  1.1  mrg     {
   4111  1.1  mrg         if (delimiter.type == '|')
   4112  1.1  mrg         {
   4113  1.1  mrg             if (ignoreLast && di == inlineDelimiters.length-1)
   4114  1.1  mrg             {
   4115  1.1  mrg                 ignoreLast = false;
   4116  1.1  mrg                 continue;
   4117  1.1  mrg             }
   4118  1.1  mrg 
   4119  1.1  mrg             if (cellIndex >= columnAlignments.length)
   4120  1.1  mrg             {
   4121  1.1  mrg                 // kill any extra cells
   4122  1.1  mrg                 buf.remove(delimiter.iStart, iEnd + delta - delimiter.iStart);
   4123  1.1  mrg                 delta -= iEnd + delta - delimiter.iStart;
   4124  1.1  mrg                 iCellEnd = iEnd + delta;
   4125  1.1  mrg                 --cellIndex;
   4126  1.1  mrg                 continue;
   4127  1.1  mrg             }
   4128  1.1  mrg 
   4129  1.1  mrg             replaceTableCell(delimiter.iStart, iCellEnd, cellIndex, cast(int) di);
   4130  1.1  mrg             iCellEnd = delimiter.iStart;
   4131  1.1  mrg             --cellIndex;
   4132  1.1  mrg         }
   4133  1.1  mrg     }
   4134  1.1  mrg 
   4135  1.1  mrg     // if no starting pipe, replace from the start
   4136  1.1  mrg     if (cellIndex >= 0)
   4137  1.1  mrg         replaceTableCell(iStart, iCellEnd, cellIndex, 0);
   4138  1.1  mrg 
   4139  1.1  mrg     buf.insert(iEnd + delta, ")");
   4140  1.1  mrg     buf.insert(iStart, "$(TR ");
   4141  1.1  mrg     delta += 6;
   4142  1.1  mrg 
   4143  1.1  mrg     if (headerRow)
   4144  1.1  mrg     {
   4145  1.1  mrg         buf.insert(iEnd + delta, ")");
   4146  1.1  mrg         buf.insert(iStart, "$(THEAD ");
   4147  1.1  mrg         delta += 9;
   4148  1.1  mrg     }
   4149  1.1  mrg 
   4150  1.1  mrg     return true;
   4151  1.1  mrg }
   4152  1.1  mrg 
   4153  1.1  mrg /****************************************************
   4154  1.1  mrg  * End a table, if in one.
   4155  1.1  mrg  *
   4156  1.1  mrg  * Params:
   4157  1.1  mrg  *  buf = an OutBuffer containing the DDoc
   4158  1.1  mrg  *  i   = the index within `buf` to end the table at
   4159  1.1  mrg  *  columnAlignments = alignments for each column; upon return is set to length `0`
   4160  1.1  mrg  * Returns: the number of characters added by ending the table, or `0` if unchanged
   4161  1.1  mrg  */
   4162  1.1  mrg private size_t endTable(ref OutBuffer buf, size_t i, ref TableColumnAlignment[] columnAlignments)
   4163  1.1  mrg {
   4164  1.1  mrg     if (!columnAlignments.length)
   4165  1.1  mrg         return 0;
   4166  1.1  mrg 
   4167  1.1  mrg     buf.insert(i, "))");
   4168  1.1  mrg     columnAlignments.length = 0;
   4169  1.1  mrg     return 2;
   4170  1.1  mrg }
   4171  1.1  mrg 
   4172  1.1  mrg /****************************************************
   4173  1.1  mrg  * End a table row and then the table itself.
   4174  1.1  mrg  *
   4175  1.1  mrg  * Params:
   4176  1.1  mrg  *  buf       = an OutBuffer containing the DDoc
   4177  1.1  mrg  *  iStart    = the index within `buf` that the table row starts at, inclusive
   4178  1.1  mrg  *  iEnd      = the index within `buf` that the table row ends at, exclusive
   4179  1.1  mrg  *  loc       = the current location in the file
   4180  1.1  mrg  *  inlineDelimiters = delimiters containing columns separators and any inline emphasis
   4181  1.1  mrg  *  columnAlignments = alignments for each column; upon return is set to length `0`
   4182  1.1  mrg  * Returns: the number of characters added by replacing the row, or `0` if unchanged
   4183  1.1  mrg  */
   4184  1.1  mrg private size_t endRowAndTable(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, ref TableColumnAlignment[] columnAlignments)
   4185  1.1  mrg {
   4186  1.1  mrg     size_t delta;
   4187  1.1  mrg     replaceTableRow(buf, iStart, iEnd, loc, inlineDelimiters, columnAlignments, false, delta);
   4188  1.1  mrg     delta += endTable(buf, iEnd + delta, columnAlignments);
   4189  1.1  mrg     return delta;
   4190  1.1  mrg }
   4191  1.1  mrg 
   4192  1.1  mrg /**************************************************
   4193  1.1  mrg  * Highlight text section.
   4194  1.1  mrg  *
   4195  1.1  mrg  * Params:
   4196  1.1  mrg  *  scope = the current parse scope
   4197  1.1  mrg  *  a     = an array of D symbols at the current scope
   4198  1.1  mrg  *  loc   = source location of start of text. It is a mutable copy to allow incrementing its linenum, for printing the correct line number when an error is encountered in a multiline block of ddoc.
   4199  1.1  mrg  *  buf   = an OutBuffer containing the DDoc
   4200  1.1  mrg  *  offset = the index within buf to start highlighting
   4201  1.1  mrg  */
   4202  1.1  mrg private void highlightText(Scope* sc, Dsymbols* a, Loc loc, ref OutBuffer buf, size_t offset)
   4203  1.1  mrg {
   4204  1.1  mrg     const incrementLoc = loc.linnum == 0 ? 1 : 0;
   4205  1.1  mrg     loc.linnum += incrementLoc;
   4206  1.1  mrg     loc.charnum = 0;
   4207  1.1  mrg     //printf("highlightText()\n");
   4208  1.1  mrg     bool leadingBlank = true;
   4209  1.1  mrg     size_t iParagraphStart = offset;
   4210  1.1  mrg     size_t iPrecedingBlankLine = 0;
   4211  1.1  mrg     int headingLevel = 0;
   4212  1.1  mrg     int headingMacroLevel = 0;
   4213  1.1  mrg     int quoteLevel = 0;
   4214  1.1  mrg     bool lineQuoted = false;
   4215  1.1  mrg     int quoteMacroLevel = 0;
   4216  1.1  mrg     MarkdownList[] nestedLists;
   4217  1.1  mrg     MarkdownDelimiter[] inlineDelimiters;
   4218  1.1  mrg     MarkdownLinkReferences linkReferences;
   4219  1.1  mrg     TableColumnAlignment[] columnAlignments;
   4220  1.1  mrg     bool tableRowDetected = false;
   4221  1.1  mrg     int inCode = 0;
   4222  1.1  mrg     int inBacktick = 0;
   4223  1.1  mrg     int macroLevel = 0;
   4224  1.1  mrg     int previousMacroLevel = 0;
   4225  1.1  mrg     int parenLevel = 0;
   4226  1.1  mrg     size_t iCodeStart = 0; // start of code section
   4227  1.1  mrg     size_t codeFenceLength = 0;
   4228  1.1  mrg     size_t codeIndent = 0;
   4229  1.1  mrg     string codeLanguage;
   4230  1.1  mrg     size_t iLineStart = offset;
   4231  1.1  mrg     linkReferences._scope = sc;
   4232  1.1  mrg     for (size_t i = offset; i < buf.length; i++)
   4233  1.1  mrg     {
   4234  1.1  mrg         char c = buf[i];
   4235  1.1  mrg     Lcont:
   4236  1.1  mrg         switch (c)
   4237  1.1  mrg         {
   4238  1.1  mrg         case ' ':
   4239  1.1  mrg         case '\t':
   4240  1.1  mrg             break;
   4241  1.1  mrg         case '\n':
   4242  1.1  mrg             if (inBacktick)
   4243  1.1  mrg             {
   4244  1.1  mrg                 // `inline code` is only valid if contained on a single line
   4245  1.1  mrg                 // otherwise, the backticks should be output literally.
   4246  1.1  mrg                 //
   4247  1.1  mrg                 // This lets things like `output from the linker' display
   4248  1.1  mrg                 // unmolested while keeping the feature consistent with GitHub.
   4249  1.1  mrg                 inBacktick = false;
   4250  1.1  mrg                 inCode = false; // the backtick also assumes we're in code
   4251  1.1  mrg                 // Nothing else is necessary since the DDOC_BACKQUOTED macro is
   4252  1.1  mrg                 // inserted lazily at the close quote, meaning the rest of the
   4253  1.1  mrg                 // text is already OK.
   4254  1.1  mrg             }
   4255  1.1  mrg             if (headingLevel)
   4256  1.1  mrg             {
   4257  1.1  mrg                 i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters);
   4258  1.1  mrg                 endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel);
   4259  1.1  mrg                 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
   4260  1.1  mrg                 ++i;
   4261  1.1  mrg                 iParagraphStart = skipChars(buf, i, " \t\r\n");
   4262  1.1  mrg             }
   4263  1.1  mrg 
   4264  1.1  mrg             if (tableRowDetected && !columnAlignments.length)
   4265  1.1  mrg                 i += startTable(buf, iLineStart, i, loc, lineQuoted, inlineDelimiters, columnAlignments);
   4266  1.1  mrg             else if (columnAlignments.length)
   4267  1.1  mrg             {
   4268  1.1  mrg                 size_t delta;
   4269  1.1  mrg                 if (replaceTableRow(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments, false, delta))
   4270  1.1  mrg                     i += delta;
   4271  1.1  mrg                 else
   4272  1.1  mrg                     i += endTable(buf, i, columnAlignments);
   4273  1.1  mrg             }
   4274  1.1  mrg 
   4275  1.1  mrg             if (!inCode && nestedLists.length && !quoteLevel)
   4276  1.1  mrg                 MarkdownList.handleSiblingOrEndingList(buf, i, iParagraphStart, nestedLists);
   4277  1.1  mrg 
   4278  1.1  mrg             iPrecedingBlankLine = 0;
   4279  1.1  mrg             if (!inCode && i == iLineStart && i + 1 < buf.length) // if "\n\n"
   4280  1.1  mrg             {
   4281  1.1  mrg                 i += endTable(buf, i, columnAlignments);
   4282  1.1  mrg                 if (!lineQuoted && quoteLevel)
   4283  1.1  mrg                     endAllListsAndQuotes(buf, i, nestedLists, quoteLevel, quoteMacroLevel);
   4284  1.1  mrg                 i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters);
   4285  1.1  mrg 
   4286  1.1  mrg                 // if we don't already know about this paragraph break then
   4287  1.1  mrg                 // insert a blank line and record the paragraph break
   4288  1.1  mrg                 if (iParagraphStart <= i)
   4289  1.1  mrg                 {
   4290  1.1  mrg                     iPrecedingBlankLine = i;
   4291  1.1  mrg                     i = buf.insert(i, "$(DDOC_BLANKLINE)");
   4292  1.1  mrg                     iParagraphStart = i + 1;
   4293  1.1  mrg                 }
   4294  1.1  mrg             }
   4295  1.1  mrg             else if (inCode &&
   4296  1.1  mrg                 i == iLineStart &&
   4297  1.1  mrg                 i + 1 < buf.length &&
   4298  1.1  mrg                 !lineQuoted &&
   4299  1.1  mrg                 quoteLevel) // if "\n\n" in quoted code
   4300  1.1  mrg             {
   4301  1.1  mrg                 inCode = false;
   4302  1.1  mrg                 i = buf.insert(i, ")");
   4303  1.1  mrg                 i += endAllMarkdownQuotes(buf, i, quoteLevel);
   4304  1.1  mrg                 quoteMacroLevel = 0;
   4305  1.1  mrg             }
   4306  1.1  mrg             leadingBlank = true;
   4307  1.1  mrg             lineQuoted = false;
   4308  1.1  mrg             tableRowDetected = false;
   4309  1.1  mrg             iLineStart = i + 1;
   4310  1.1  mrg             loc.linnum += incrementLoc;
   4311  1.1  mrg 
   4312  1.1  mrg             // update the paragraph start if we just entered a macro
   4313  1.1  mrg             if (previousMacroLevel < macroLevel && iParagraphStart < iLineStart)
   4314  1.1  mrg                 iParagraphStart = iLineStart;
   4315  1.1  mrg             previousMacroLevel = macroLevel;
   4316  1.1  mrg             break;
   4317  1.1  mrg 
   4318  1.1  mrg         case '<':
   4319  1.1  mrg             {
   4320  1.1  mrg                 leadingBlank = false;
   4321  1.1  mrg                 if (inCode)
   4322  1.1  mrg                     break;
   4323  1.1  mrg                 const slice = buf[];
   4324  1.1  mrg                 auto p = &slice[i];
   4325  1.1  mrg                 const se = sc._module.escapetable.escapeChar('<');
   4326  1.1  mrg                 if (se == "&lt;")
   4327  1.1  mrg                 {
   4328  1.1  mrg                     // Generating HTML
   4329  1.1  mrg                     // Skip over comments
   4330  1.1  mrg                     if (p[1] == '!' && p[2] == '-' && p[3] == '-')
   4331  1.1  mrg                     {
   4332  1.1  mrg                         size_t j = i + 4;
   4333  1.1  mrg                         p += 4;
   4334  1.1  mrg                         while (1)
   4335  1.1  mrg                         {
   4336  1.1  mrg                             if (j == slice.length)
   4337  1.1  mrg                                 goto L1;
   4338  1.1  mrg                             if (p[0] == '-' && p[1] == '-' && p[2] == '>')
   4339  1.1  mrg                             {
   4340  1.1  mrg                                 i = j + 2; // place on closing '>'
   4341  1.1  mrg                                 break;
   4342  1.1  mrg                             }
   4343  1.1  mrg                             j++;
   4344  1.1  mrg                             p++;
   4345  1.1  mrg                         }
   4346  1.1  mrg                         break;
   4347  1.1  mrg                     }
   4348  1.1  mrg                     // Skip over HTML tag
   4349  1.1  mrg                     if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2])))
   4350  1.1  mrg                     {
   4351  1.1  mrg                         size_t j = i + 2;
   4352  1.1  mrg                         p += 2;
   4353  1.1  mrg                         while (1)
   4354  1.1  mrg                         {
   4355  1.1  mrg                             if (j == slice.length)
   4356  1.1  mrg                                 break;
   4357  1.1  mrg                             if (p[0] == '>')
   4358  1.1  mrg                             {
   4359  1.1  mrg                                 i = j; // place on closing '>'
   4360  1.1  mrg                                 break;
   4361  1.1  mrg                             }
   4362  1.1  mrg                             j++;
   4363  1.1  mrg                             p++;
   4364  1.1  mrg                         }
   4365  1.1  mrg                         break;
   4366  1.1  mrg                     }
   4367  1.1  mrg                 }
   4368  1.1  mrg             L1:
   4369  1.1  mrg                 // Replace '<' with '&lt;' character entity
   4370  1.1  mrg                 if (se.length)
   4371  1.1  mrg                 {
   4372  1.1  mrg                     buf.remove(i, 1);
   4373  1.1  mrg                     i = buf.insert(i, se);
   4374  1.1  mrg                     i--; // point to ';'
   4375  1.1  mrg                 }
   4376  1.1  mrg                 break;
   4377  1.1  mrg             }
   4378  1.1  mrg 
   4379  1.1  mrg         case '>':
   4380  1.1  mrg             {
   4381  1.1  mrg                 if (leadingBlank && (!inCode || quoteLevel) && global.params.markdown)
   4382  1.1  mrg                 {
   4383  1.1  mrg                     if (!quoteLevel && global.params.vmarkdown)
   4384  1.1  mrg                     {
   4385  1.1  mrg                         size_t iEnd = i + 1;
   4386  1.1  mrg                         while (iEnd < buf.length && buf[iEnd] != '\n')
   4387  1.1  mrg                             ++iEnd;
   4388  1.1  mrg                         const s = buf[][i .. iEnd];
   4389  1.1  mrg                         message(loc, "Ddoc: starting quote block with '%.*s'", cast(int)s.length, s.ptr);
   4390  1.1  mrg                     }
   4391  1.1  mrg 
   4392  1.1  mrg                     lineQuoted = true;
   4393  1.1  mrg                     int lineQuoteLevel = 1;
   4394  1.1  mrg                     size_t iAfterDelimiters = i + 1;
   4395  1.1  mrg                     for (; iAfterDelimiters < buf.length; ++iAfterDelimiters)
   4396  1.1  mrg                     {
   4397  1.1  mrg                         const c0 = buf[iAfterDelimiters];
   4398  1.1  mrg                         if (c0 == '>')
   4399  1.1  mrg                             ++lineQuoteLevel;
   4400  1.1  mrg                         else if (c0 != ' ' && c0 != '\t')
   4401  1.1  mrg                             break;
   4402  1.1  mrg                     }
   4403  1.1  mrg                     if (!quoteMacroLevel)
   4404  1.1  mrg                         quoteMacroLevel = macroLevel;
   4405  1.1  mrg                     buf.remove(i, iAfterDelimiters - i);
   4406  1.1  mrg 
   4407  1.1  mrg                     if (quoteLevel < lineQuoteLevel)
   4408  1.1  mrg                     {
   4409  1.1  mrg                         i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   4410  1.1  mrg                         if (nestedLists.length)
   4411  1.1  mrg                         {
   4412  1.1  mrg                             const indent = getMarkdownIndent(buf, iLineStart, i);
   4413  1.1  mrg                             if (indent < nestedLists[$-1].contentIndent)
   4414  1.1  mrg                                 i += MarkdownList.endAllNestedLists(buf, i, nestedLists);
   4415  1.1  mrg                         }
   4416  1.1  mrg 
   4417  1.1  mrg                         for (; quoteLevel < lineQuoteLevel; ++quoteLevel)
   4418  1.1  mrg                         {
   4419  1.1  mrg                             i = buf.insert(i, "$(BLOCKQUOTE\n");
   4420  1.1  mrg                             iLineStart = iParagraphStart = i;
   4421  1.1  mrg                         }
   4422  1.1  mrg                         --i;
   4423  1.1  mrg                     }
   4424  1.1  mrg                     else
   4425  1.1  mrg                     {
   4426  1.1  mrg                         --i;
   4427  1.1  mrg                         if (nestedLists.length)
   4428  1.1  mrg                             MarkdownList.handleSiblingOrEndingList(buf, i, iParagraphStart, nestedLists);
   4429  1.1  mrg                     }
   4430  1.1  mrg                     break;
   4431  1.1  mrg                 }
   4432  1.1  mrg 
   4433  1.1  mrg                 leadingBlank = false;
   4434  1.1  mrg                 if (inCode)
   4435  1.1  mrg                     break;
   4436  1.1  mrg                 // Replace '>' with '&gt;' character entity
   4437  1.1  mrg                 const se = sc._module.escapetable.escapeChar('>');
   4438  1.1  mrg                 if (se.length)
   4439  1.1  mrg                 {
   4440  1.1  mrg                     buf.remove(i, 1);
   4441  1.1  mrg                     i = buf.insert(i, se);
   4442  1.1  mrg                     i--; // point to ';'
   4443  1.1  mrg                 }
   4444  1.1  mrg                 break;
   4445  1.1  mrg             }
   4446  1.1  mrg 
   4447  1.1  mrg         case '&':
   4448  1.1  mrg             {
   4449  1.1  mrg                 leadingBlank = false;
   4450  1.1  mrg                 if (inCode)
   4451  1.1  mrg                     break;
   4452  1.1  mrg                 char* p = cast(char*)&buf[].ptr[i];
   4453  1.1  mrg                 if (p[1] == '#' || isalpha(p[1]))
   4454  1.1  mrg                     break;
   4455  1.1  mrg                 // already a character entity
   4456  1.1  mrg                 // Replace '&' with '&amp;' character entity
   4457  1.1  mrg                 const se = sc._module.escapetable.escapeChar('&');
   4458  1.1  mrg                 if (se)
   4459  1.1  mrg                 {
   4460  1.1  mrg                     buf.remove(i, 1);
   4461  1.1  mrg                     i = buf.insert(i, se);
   4462  1.1  mrg                     i--; // point to ';'
   4463  1.1  mrg                 }
   4464  1.1  mrg                 break;
   4465  1.1  mrg             }
   4466  1.1  mrg 
   4467  1.1  mrg         case '`':
   4468  1.1  mrg             {
   4469  1.1  mrg                 const iAfterDelimiter = skipChars(buf, i, "`");
   4470  1.1  mrg                 const count = iAfterDelimiter - i;
   4471  1.1  mrg 
   4472  1.1  mrg                 if (inBacktick == count)
   4473  1.1  mrg                 {
   4474  1.1  mrg                     inBacktick = 0;
   4475  1.1  mrg                     inCode = 0;
   4476  1.1  mrg                     OutBuffer codebuf;
   4477  1.1  mrg                     codebuf.write(buf[iCodeStart + count .. i]);
   4478  1.1  mrg                     // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL
   4479  1.1  mrg                     highlightCode(sc, a, codebuf, 0);
   4480  1.1  mrg                     escapeStrayParenthesis(loc, &codebuf, 0, false);
   4481  1.1  mrg                     buf.remove(iCodeStart, i - iCodeStart + count); // also trimming off the current `
   4482  1.1  mrg                     immutable pre = "$(DDOC_BACKQUOTED ";
   4483  1.1  mrg                     i = buf.insert(iCodeStart, pre);
   4484  1.1  mrg                     i = buf.insert(i, codebuf[]);
   4485  1.1  mrg                     i = buf.insert(i, ")");
   4486  1.1  mrg                     i--; // point to the ending ) so when the for loop does i++, it will see the next character
   4487  1.1  mrg                     break;
   4488  1.1  mrg                 }
   4489  1.1  mrg 
   4490  1.1  mrg                 // Perhaps we're starting or ending a Markdown code block
   4491  1.1  mrg                 if (leadingBlank && global.params.markdown && count >= 3)
   4492  1.1  mrg                 {
   4493  1.1  mrg                     bool moreBackticks = false;
   4494  1.1  mrg                     for (size_t j = iAfterDelimiter; !moreBackticks && j < buf.length; ++j)
   4495  1.1  mrg                         if (buf[j] == '`')
   4496  1.1  mrg                             moreBackticks = true;
   4497  1.1  mrg                         else if (buf[j] == '\r' || buf[j] == '\n')
   4498  1.1  mrg                             break;
   4499  1.1  mrg                     if (!moreBackticks)
   4500  1.1  mrg                         goto case '-';
   4501  1.1  mrg                 }
   4502  1.1  mrg 
   4503  1.1  mrg                 if (inCode)
   4504  1.1  mrg                 {
   4505  1.1  mrg                     if (inBacktick)
   4506  1.1  mrg                         i = iAfterDelimiter - 1;
   4507  1.1  mrg                     break;
   4508  1.1  mrg                 }
   4509  1.1  mrg                 inCode = c;
   4510  1.1  mrg                 inBacktick = cast(int) count;
   4511  1.1  mrg                 codeIndent = 0; // inline code is not indented
   4512  1.1  mrg                 // All we do here is set the code flags and record
   4513  1.1  mrg                 // the location. The macro will be inserted lazily
   4514  1.1  mrg                 // so we can easily cancel the inBacktick if we come
   4515  1.1  mrg                 // across a newline character.
   4516  1.1  mrg                 iCodeStart = i;
   4517  1.1  mrg                 i = iAfterDelimiter - 1;
   4518  1.1  mrg                 break;
   4519  1.1  mrg             }
   4520  1.1  mrg 
   4521  1.1  mrg         case '#':
   4522  1.1  mrg         {
   4523  1.1  mrg             /* A line beginning with # indicates an ATX-style heading. */
   4524  1.1  mrg             if (leadingBlank && !inCode)
   4525  1.1  mrg             {
   4526  1.1  mrg                 leadingBlank = false;
   4527  1.1  mrg 
   4528  1.1  mrg                 headingLevel = detectAtxHeadingLevel(buf, i);
   4529  1.1  mrg                 if (!headingLevel)
   4530  1.1  mrg                     break;
   4531  1.1  mrg 
   4532  1.1  mrg                 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   4533  1.1  mrg                 if (!lineQuoted && quoteLevel)
   4534  1.1  mrg                     i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
   4535  1.1  mrg 
   4536  1.1  mrg                 // remove the ### prefix, including whitespace
   4537  1.1  mrg                 i = skipChars(buf, i + headingLevel, " \t");
   4538  1.1  mrg                 buf.remove(iLineStart, i - iLineStart);
   4539  1.1  mrg                 i = iParagraphStart = iLineStart;
   4540  1.1  mrg 
   4541  1.1  mrg                 removeAnyAtxHeadingSuffix(buf, i);
   4542  1.1  mrg                 --i;
   4543  1.1  mrg 
   4544  1.1  mrg                 headingMacroLevel = macroLevel;
   4545  1.1  mrg             }
   4546  1.1  mrg             break;
   4547  1.1  mrg         }
   4548  1.1  mrg 
   4549  1.1  mrg         case '~':
   4550  1.1  mrg             {
   4551  1.1  mrg                 if (leadingBlank && global.params.markdown)
   4552  1.1  mrg                 {
   4553  1.1  mrg                     // Perhaps we're starting or ending a Markdown code block
   4554  1.1  mrg                     const iAfterDelimiter = skipChars(buf, i, "~");
   4555  1.1  mrg                     if (iAfterDelimiter - i >= 3)
   4556  1.1  mrg                         goto case '-';
   4557  1.1  mrg                 }
   4558  1.1  mrg                 leadingBlank = false;
   4559  1.1  mrg                 break;
   4560  1.1  mrg             }
   4561  1.1  mrg 
   4562  1.1  mrg         case '-':
   4563  1.1  mrg             /* A line beginning with --- delimits a code section.
   4564  1.1  mrg              * inCode tells us if it is start or end of a code section.
   4565  1.1  mrg              */
   4566  1.1  mrg             if (leadingBlank)
   4567  1.1  mrg             {
   4568  1.1  mrg                 if (!inCode && c == '-')
   4569  1.1  mrg                 {
   4570  1.1  mrg                     const list = MarkdownList.parseItem(buf, iLineStart, i);
   4571  1.1  mrg                     if (list.isValid)
   4572  1.1  mrg                     {
   4573  1.1  mrg                         if (replaceMarkdownThematicBreak(buf, i, iLineStart, loc))
   4574  1.1  mrg                         {
   4575  1.1  mrg                             removeBlankLineMacro(buf, iPrecedingBlankLine, i);
   4576  1.1  mrg                             iParagraphStart = skipChars(buf, i+1, " \t\r\n");
   4577  1.1  mrg                             break;
   4578  1.1  mrg                         }
   4579  1.1  mrg                         else
   4580  1.1  mrg                             goto case '+';
   4581  1.1  mrg                     }
   4582  1.1  mrg                 }
   4583  1.1  mrg 
   4584  1.1  mrg                 size_t istart = i;
   4585  1.1  mrg                 size_t eollen = 0;
   4586  1.1  mrg                 leadingBlank = false;
   4587  1.1  mrg                 const c0 = c; // if we jumped here from case '`' or case '~'
   4588  1.1  mrg                 size_t iInfoString = 0;
   4589  1.1  mrg                 if (!inCode)
   4590  1.1  mrg                     codeLanguage.length = 0;
   4591  1.1  mrg                 while (1)
   4592  1.1  mrg                 {
   4593  1.1  mrg                     ++i;
   4594  1.1  mrg                     if (i >= buf.length)
   4595  1.1  mrg                         break;
   4596  1.1  mrg                     c = buf[i];
   4597  1.1  mrg                     if (c == '\n')
   4598  1.1  mrg                     {
   4599  1.1  mrg                         eollen = 1;
   4600  1.1  mrg                         break;
   4601  1.1  mrg                     }
   4602  1.1  mrg                     if (c == '\r')
   4603  1.1  mrg                     {
   4604  1.1  mrg                         eollen = 1;
   4605  1.1  mrg                         if (i + 1 >= buf.length)
   4606  1.1  mrg                             break;
   4607  1.1  mrg                         if (buf[i + 1] == '\n')
   4608  1.1  mrg                         {
   4609  1.1  mrg                             eollen = 2;
   4610  1.1  mrg                             break;
   4611  1.1  mrg                         }
   4612  1.1  mrg                     }
   4613  1.1  mrg                     // BUG: handle UTF PS and LS too
   4614  1.1  mrg                     if (c != c0 || iInfoString)
   4615  1.1  mrg                     {
   4616  1.1  mrg                         if (global.params.markdown && !iInfoString && !inCode && i - istart >= 3)
   4617  1.1  mrg                         {
   4618  1.1  mrg                             // Start a Markdown info string, like ```ruby
   4619  1.1  mrg                             codeFenceLength = i - istart;
   4620  1.1  mrg                             i = iInfoString = skipChars(buf, i, " \t");
   4621  1.1  mrg                         }
   4622  1.1  mrg                         else if (iInfoString && c != '`')
   4623  1.1  mrg                         {
   4624  1.1  mrg                             if (!codeLanguage.length && (c == ' ' || c == '\t'))
   4625  1.1  mrg                                 codeLanguage = cast(string) buf[iInfoString..i].idup;
   4626  1.1  mrg                         }
   4627  1.1  mrg                         else
   4628  1.1  mrg                         {
   4629  1.1  mrg                             iInfoString = 0;
   4630  1.1  mrg                             goto Lcont;
   4631  1.1  mrg                         }
   4632  1.1  mrg                     }
   4633  1.1  mrg                 }
   4634  1.1  mrg                 if (i - istart < 3 || (inCode && (inCode != c0 || (inCode != '-' && i - istart < codeFenceLength))))
   4635  1.1  mrg                     goto Lcont;
   4636  1.1  mrg                 if (iInfoString)
   4637  1.1  mrg                 {
   4638  1.1  mrg                     if (!codeLanguage.length)
   4639  1.1  mrg                         codeLanguage = cast(string) buf[iInfoString..i].idup;
   4640  1.1  mrg                 }
   4641  1.1  mrg                 else
   4642  1.1  mrg                     codeFenceLength = i - istart;
   4643  1.1  mrg 
   4644  1.1  mrg                 // We have the start/end of a code section
   4645  1.1  mrg                 // Remove the entire --- line, including blanks and \n
   4646  1.1  mrg                 buf.remove(iLineStart, i - iLineStart + eollen);
   4647  1.1  mrg                 i = iLineStart;
   4648  1.1  mrg                 if (eollen)
   4649  1.1  mrg                     leadingBlank = true;
   4650  1.1  mrg                 if (inCode && (i <= iCodeStart))
   4651  1.1  mrg                 {
   4652  1.1  mrg                     // Empty code section, just remove it completely.
   4653  1.1  mrg                     inCode = 0;
   4654  1.1  mrg                     break;
   4655  1.1  mrg                 }
   4656  1.1  mrg                 if (inCode)
   4657  1.1  mrg                 {
   4658  1.1  mrg                     inCode = 0;
   4659  1.1  mrg                     // The code section is from iCodeStart to i
   4660  1.1  mrg                     OutBuffer codebuf;
   4661  1.1  mrg                     codebuf.write(buf[iCodeStart .. i]);
   4662  1.1  mrg                     codebuf.writeByte(0);
   4663  1.1  mrg                     // Remove leading indentations from all lines
   4664  1.1  mrg                     bool lineStart = true;
   4665  1.1  mrg                     char* endp = cast(char*)codebuf[].ptr + codebuf.length;
   4666  1.1  mrg                     for (char* p = cast(char*)codebuf[].ptr; p < endp;)
   4667  1.1  mrg                     {
   4668  1.1  mrg                         if (lineStart)
   4669  1.1  mrg                         {
   4670  1.1  mrg                             size_t j = codeIndent;
   4671  1.1  mrg                             char* q = p;
   4672  1.1  mrg                             while (j-- > 0 && q < endp && isIndentWS(q))
   4673  1.1  mrg                                 ++q;
   4674  1.1  mrg                             codebuf.remove(p - cast(char*)codebuf[].ptr, q - p);
   4675  1.1  mrg                             assert(cast(char*)codebuf[].ptr <= p);
   4676  1.1  mrg                             assert(p < cast(char*)codebuf[].ptr + codebuf.length);
   4677  1.1  mrg                             lineStart = false;
   4678  1.1  mrg                             endp = cast(char*)codebuf[].ptr + codebuf.length; // update
   4679  1.1  mrg                             continue;
   4680  1.1  mrg                         }
   4681  1.1  mrg                         if (*p == '\n')
   4682  1.1  mrg                             lineStart = true;
   4683  1.1  mrg                         ++p;
   4684  1.1  mrg                     }
   4685  1.1  mrg                     if (!codeLanguage.length || codeLanguage == "dlang" || codeLanguage == "d")
   4686  1.1  mrg                         highlightCode2(sc, a, codebuf, 0);
   4687  1.1  mrg                     else
   4688  1.1  mrg                         codebuf.remove(codebuf.length-1, 1);    // remove the trailing 0 byte
   4689  1.1  mrg                     escapeStrayParenthesis(loc, &codebuf, 0, false);
   4690  1.1  mrg                     buf.remove(iCodeStart, i - iCodeStart);
   4691  1.1  mrg                     i = buf.insert(iCodeStart, codebuf[]);
   4692  1.1  mrg                     i = buf.insert(i, ")\n");
   4693  1.1  mrg                     i -= 2; // in next loop, c should be '\n'
   4694  1.1  mrg                 }
   4695  1.1  mrg                 else
   4696  1.1  mrg                 {
   4697  1.1  mrg                     i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   4698  1.1  mrg                     if (!lineQuoted && quoteLevel)
   4699  1.1  mrg                     {
   4700  1.1  mrg                         const delta = endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
   4701  1.1  mrg                         i += delta;
   4702  1.1  mrg                         istart += delta;
   4703  1.1  mrg                     }
   4704  1.1  mrg 
   4705  1.1  mrg                     inCode = c0;
   4706  1.1  mrg                     codeIndent = istart - iLineStart; // save indent count
   4707  1.1  mrg                     if (codeLanguage.length && codeLanguage != "dlang" && codeLanguage != "d")
   4708  1.1  mrg                     {
   4709  1.1  mrg                         // backslash-escape
   4710  1.1  mrg                         for (size_t j; j < codeLanguage.length - 1; ++j)
   4711  1.1  mrg                             if (codeLanguage[j] == '\\' && ispunct(codeLanguage[j + 1]))
   4712  1.1  mrg                                 codeLanguage = codeLanguage[0..j] ~ codeLanguage[j + 1..$];
   4713  1.1  mrg 
   4714  1.1  mrg                         if (global.params.vmarkdown)
   4715  1.1  mrg                             message(loc, "Ddoc: adding code block for language '%.*s'", cast(int)codeLanguage.length, codeLanguage.ptr);
   4716  1.1  mrg 
   4717  1.1  mrg                         i = buf.insert(i, "$(OTHER_CODE ");
   4718  1.1  mrg                         i = buf.insert(i, codeLanguage);
   4719  1.1  mrg                         i = buf.insert(i, ",");
   4720  1.1  mrg                     }
   4721  1.1  mrg                     else
   4722  1.1  mrg                         i = buf.insert(i, "$(D_CODE ");
   4723  1.1  mrg                     iCodeStart = i;
   4724  1.1  mrg                     i--; // place i on >
   4725  1.1  mrg                     leadingBlank = true;
   4726  1.1  mrg                 }
   4727  1.1  mrg             }
   4728  1.1  mrg             break;
   4729  1.1  mrg 
   4730  1.1  mrg         case '_':
   4731  1.1  mrg         {
   4732  1.1  mrg             if (leadingBlank && !inCode && replaceMarkdownThematicBreak(buf, i, iLineStart, loc))
   4733  1.1  mrg             {
   4734  1.1  mrg                 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   4735  1.1  mrg                 if (!lineQuoted && quoteLevel)
   4736  1.1  mrg                     i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
   4737  1.1  mrg                 removeBlankLineMacro(buf, iPrecedingBlankLine, i);
   4738  1.1  mrg                 iParagraphStart = skipChars(buf, i+1, " \t\r\n");
   4739  1.1  mrg                 break;
   4740  1.1  mrg             }
   4741  1.1  mrg             goto default;
   4742  1.1  mrg         }
   4743  1.1  mrg 
   4744  1.1  mrg         case '+':
   4745  1.1  mrg         case '0':
   4746  1.1  mrg         ..
   4747  1.1  mrg         case '9':
   4748  1.1  mrg         {
   4749  1.1  mrg             if (leadingBlank && !inCode)
   4750  1.1  mrg             {
   4751  1.1  mrg                 MarkdownList list = MarkdownList.parseItem(buf, iLineStart, i);
   4752  1.1  mrg                 if (list.isValid)
   4753  1.1  mrg                 {
   4754  1.1  mrg                     // Avoid starting a numbered list in the middle of a paragraph
   4755  1.1  mrg                     if (!nestedLists.length && list.orderedStart.length &&
   4756  1.1  mrg                         iParagraphStart < iLineStart)
   4757  1.1  mrg                     {
   4758  1.1  mrg                         i += list.orderedStart.length - 1;
   4759  1.1  mrg                         break;
   4760  1.1  mrg                     }
   4761  1.1  mrg 
   4762  1.1  mrg                     i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   4763  1.1  mrg                     if (!lineQuoted && quoteLevel)
   4764  1.1  mrg                     {
   4765  1.1  mrg                         const delta = endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
   4766  1.1  mrg                         i += delta;
   4767  1.1  mrg                         list.iStart += delta;
   4768  1.1  mrg                         list.iContentStart += delta;
   4769  1.1  mrg                     }
   4770  1.1  mrg 
   4771  1.1  mrg                     list.macroLevel = macroLevel;
   4772  1.1  mrg                     list.startItem(buf, iLineStart, i, iPrecedingBlankLine, nestedLists, loc);
   4773  1.1  mrg                     break;
   4774  1.1  mrg                 }
   4775  1.1  mrg             }
   4776  1.1  mrg             leadingBlank = false;
   4777  1.1  mrg             break;
   4778  1.1  mrg         }
   4779  1.1  mrg 
   4780  1.1  mrg         case '*':
   4781  1.1  mrg         {
   4782  1.1  mrg             if (inCode || inBacktick || !global.params.markdown)
   4783  1.1  mrg             {
   4784  1.1  mrg                 leadingBlank = false;
   4785  1.1  mrg                 break;
   4786  1.1  mrg             }
   4787  1.1  mrg 
   4788  1.1  mrg             if (leadingBlank)
   4789  1.1  mrg             {
   4790  1.1  mrg                 // Check for a thematic break
   4791  1.1  mrg                 if (replaceMarkdownThematicBreak(buf, i, iLineStart, loc))
   4792  1.1  mrg                 {
   4793  1.1  mrg                     i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   4794  1.1  mrg                     if (!lineQuoted && quoteLevel)
   4795  1.1  mrg                         i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel);
   4796  1.1  mrg                     removeBlankLineMacro(buf, iPrecedingBlankLine, i);
   4797  1.1  mrg                     iParagraphStart = skipChars(buf, i+1, " \t\r\n");
   4798  1.1  mrg                     break;
   4799  1.1  mrg                 }
   4800  1.1  mrg 
   4801  1.1  mrg                 // An initial * indicates a Markdown list item
   4802  1.1  mrg                 const list = MarkdownList.parseItem(buf, iLineStart, i);
   4803  1.1  mrg                 if (list.isValid)
   4804  1.1  mrg                     goto case '+';
   4805  1.1  mrg             }
   4806  1.1  mrg 
   4807  1.1  mrg             // Markdown emphasis
   4808  1.1  mrg             const leftC = i > offset ? buf[i-1] : '\0';
   4809  1.1  mrg             size_t iAfterEmphasis = skipChars(buf, i+1, "*");
   4810  1.1  mrg             const rightC = iAfterEmphasis < buf.length ? buf[iAfterEmphasis] : '\0';
   4811  1.1  mrg             int count = cast(int) (iAfterEmphasis - i);
   4812  1.1  mrg             const leftFlanking = (rightC != '\0' && !isspace(rightC)) && (!ispunct(rightC) || leftC == '\0' || isspace(leftC) || ispunct(leftC));
   4813  1.1  mrg             const rightFlanking = (leftC != '\0' && !isspace(leftC)) && (!ispunct(leftC) || rightC == '\0' || isspace(rightC) || ispunct(rightC));
   4814  1.1  mrg             auto emphasis = MarkdownDelimiter(i, count, macroLevel, leftFlanking, rightFlanking, false, c);
   4815  1.1  mrg 
   4816  1.1  mrg             if (!emphasis.leftFlanking && !emphasis.rightFlanking)
   4817  1.1  mrg             {
   4818  1.1  mrg                 i = iAfterEmphasis - 1;
   4819  1.1  mrg                 break;
   4820  1.1  mrg             }
   4821  1.1  mrg 
   4822  1.1  mrg             inlineDelimiters ~= emphasis;
   4823  1.1  mrg             i += emphasis.count;
   4824  1.1  mrg             --i;
   4825  1.1  mrg             break;
   4826  1.1  mrg         }
   4827  1.1  mrg 
   4828  1.1  mrg         case '!':
   4829  1.1  mrg         {
   4830  1.1  mrg             leadingBlank = false;
   4831  1.1  mrg 
   4832  1.1  mrg             if (inCode || !global.params.markdown)
   4833  1.1  mrg                 break;
   4834  1.1  mrg 
   4835  1.1  mrg             if (i < buf.length-1 && buf[i+1] == '[')
   4836  1.1  mrg             {
   4837  1.1  mrg                 const imageStart = MarkdownDelimiter(i, 2, macroLevel, false, false, false, c);
   4838  1.1  mrg                 inlineDelimiters ~= imageStart;
   4839  1.1  mrg                 ++i;
   4840  1.1  mrg             }
   4841  1.1  mrg             break;
   4842  1.1  mrg         }
   4843  1.1  mrg         case '[':
   4844  1.1  mrg         {
   4845  1.1  mrg             if (inCode || !global.params.markdown)
   4846  1.1  mrg             {
   4847  1.1  mrg                 leadingBlank = false;
   4848  1.1  mrg                 break;
   4849  1.1  mrg             }
   4850  1.1  mrg 
   4851  1.1  mrg             const leftC = i > offset ? buf[i-1] : '\0';
   4852  1.1  mrg             const rightFlanking = leftC != '\0' && !isspace(leftC) && !ispunct(leftC);
   4853  1.1  mrg             const atParagraphStart = leadingBlank && iParagraphStart >= iLineStart;
   4854  1.1  mrg             const linkStart = MarkdownDelimiter(i, 1, macroLevel, false, rightFlanking, atParagraphStart, c);
   4855  1.1  mrg             inlineDelimiters ~= linkStart;
   4856  1.1  mrg             leadingBlank = false;
   4857  1.1  mrg             break;
   4858  1.1  mrg         }
   4859  1.1  mrg         case ']':
   4860  1.1  mrg         {
   4861  1.1  mrg             leadingBlank = false;
   4862  1.1  mrg 
   4863  1.1  mrg             if (inCode || !global.params.markdown)
   4864  1.1  mrg                 break;
   4865  1.1  mrg 
   4866  1.1  mrg             for (int d = cast(int) inlineDelimiters.length - 1; d >= 0; --d)
   4867  1.1  mrg             {
   4868  1.1  mrg                 const delimiter = inlineDelimiters[d];
   4869  1.1  mrg                 if (delimiter.type == '[' || delimiter.type == '!')
   4870  1.1  mrg                 {
   4871  1.1  mrg                     if (delimiter.isValid &&
   4872  1.1  mrg                         MarkdownLink.replaceLink(buf, i, loc, inlineDelimiters, d, linkReferences))
   4873  1.1  mrg                     {
   4874  1.1  mrg                         // if we removed a reference link then we're at line start
   4875  1.1  mrg                         if (i <= delimiter.iStart)
   4876  1.1  mrg                             leadingBlank = true;
   4877  1.1  mrg 
   4878  1.1  mrg                         // don't nest links
   4879  1.1  mrg                         if (delimiter.type == '[')
   4880  1.1  mrg                             for (--d; d >= 0; --d)
   4881  1.1  mrg                                 if (inlineDelimiters[d].type == '[')
   4882  1.1  mrg                                     inlineDelimiters[d].invalidate();
   4883  1.1  mrg                     }
   4884  1.1  mrg                     else
   4885  1.1  mrg                     {
   4886  1.1  mrg                         // nothing found, so kill the delimiter
   4887  1.1  mrg                         inlineDelimiters = inlineDelimiters[0..d] ~ inlineDelimiters[d+1..$];
   4888  1.1  mrg                     }
   4889  1.1  mrg                     break;
   4890  1.1  mrg                 }
   4891  1.1  mrg             }
   4892  1.1  mrg             break;
   4893  1.1  mrg         }
   4894  1.1  mrg 
   4895  1.1  mrg         case '|':
   4896  1.1  mrg         {
   4897  1.1  mrg             if (inCode || !global.params.markdown)
   4898  1.1  mrg             {
   4899  1.1  mrg                 leadingBlank = false;
   4900  1.1  mrg                 break;
   4901  1.1  mrg             }
   4902  1.1  mrg 
   4903  1.1  mrg             tableRowDetected = true;
   4904  1.1  mrg             inlineDelimiters ~= MarkdownDelimiter(i, 1, macroLevel, leadingBlank, false, false, c);
   4905  1.1  mrg             leadingBlank = false;
   4906  1.1  mrg             break;
   4907  1.1  mrg         }
   4908  1.1  mrg 
   4909  1.1  mrg         case '\\':
   4910  1.1  mrg         {
   4911  1.1  mrg             leadingBlank = false;
   4912  1.1  mrg             if (inCode || i+1 >= buf.length || !global.params.markdown)
   4913  1.1  mrg                 break;
   4914  1.1  mrg 
   4915  1.1  mrg             /* Escape Markdown special characters */
   4916  1.1  mrg             char c1 = buf[i+1];
   4917  1.1  mrg             if (ispunct(c1))
   4918  1.1  mrg             {
   4919  1.1  mrg                 if (global.params.vmarkdown)
   4920  1.1  mrg                     message(loc, "Ddoc: backslash-escaped %c", c1);
   4921  1.1  mrg 
   4922  1.1  mrg                 buf.remove(i, 1);
   4923  1.1  mrg 
   4924  1.1  mrg                 auto se = sc._module.escapetable.escapeChar(c1);
   4925  1.1  mrg                 if (!se)
   4926  1.1  mrg                     se = c1 == '$' ? "$(DOLLAR)" : c1 == ',' ? "$(COMMA)" : null;
   4927  1.1  mrg                 if (se)
   4928  1.1  mrg                 {
   4929  1.1  mrg                     buf.remove(i, 1);
   4930  1.1  mrg                     i = buf.insert(i, se);
   4931  1.1  mrg                     i--; // point to escaped char
   4932  1.1  mrg                 }
   4933  1.1  mrg             }
   4934  1.1  mrg             break;
   4935  1.1  mrg         }
   4936  1.1  mrg 
   4937  1.1  mrg         case '$':
   4938  1.1  mrg         {
   4939  1.1  mrg             /* Look for the start of a macro, '$(Identifier'
   4940  1.1  mrg              */
   4941  1.1  mrg             leadingBlank = false;
   4942  1.1  mrg             if (inCode || inBacktick)
   4943  1.1  mrg                 break;
   4944  1.1  mrg             const slice = buf[];
   4945  1.1  mrg             auto p = &slice[i];
   4946  1.1  mrg             if (p[1] == '(' && isIdStart(&p[2]))
   4947  1.1  mrg                 ++macroLevel;
   4948  1.1  mrg             break;
   4949  1.1  mrg         }
   4950  1.1  mrg 
   4951  1.1  mrg         case '(':
   4952  1.1  mrg         {
   4953  1.1  mrg             if (!inCode && i > offset && buf[i-1] != '$')
   4954  1.1  mrg                 ++parenLevel;
   4955  1.1  mrg             break;
   4956  1.1  mrg         }
   4957  1.1  mrg 
   4958  1.1  mrg         case ')':
   4959  1.1  mrg         {   /* End of macro
   4960  1.1  mrg              */
   4961  1.1  mrg             leadingBlank = false;
   4962  1.1  mrg             if (inCode || inBacktick)
   4963  1.1  mrg                 break;
   4964  1.1  mrg             if (parenLevel > 0)
   4965  1.1  mrg                 --parenLevel;
   4966  1.1  mrg             else if (macroLevel)
   4967  1.1  mrg             {
   4968  1.1  mrg                 int downToLevel = cast(int) inlineDelimiters.length;
   4969  1.1  mrg                 while (downToLevel > 0 && inlineDelimiters[downToLevel - 1].macroLevel >= macroLevel)
   4970  1.1  mrg                     --downToLevel;
   4971  1.1  mrg                 if (headingLevel && headingMacroLevel >= macroLevel)
   4972  1.1  mrg                 {
   4973  1.1  mrg                     endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel);
   4974  1.1  mrg                     removeBlankLineMacro(buf, iPrecedingBlankLine, i);
   4975  1.1  mrg                 }
   4976  1.1  mrg                 i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   4977  1.1  mrg                 while (nestedLists.length && nestedLists[$-1].macroLevel >= macroLevel)
   4978  1.1  mrg                 {
   4979  1.1  mrg                     i = buf.insert(i, ")\n)");
   4980  1.1  mrg                     --nestedLists.length;
   4981  1.1  mrg                 }
   4982  1.1  mrg                 if (quoteLevel && quoteMacroLevel >= macroLevel)
   4983  1.1  mrg                     i += endAllMarkdownQuotes(buf, i, quoteLevel);
   4984  1.1  mrg                 i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters, downToLevel);
   4985  1.1  mrg 
   4986  1.1  mrg                 --macroLevel;
   4987  1.1  mrg                 quoteMacroLevel = 0;
   4988  1.1  mrg             }
   4989  1.1  mrg             break;
   4990  1.1  mrg         }
   4991  1.1  mrg 
   4992  1.1  mrg         default:
   4993  1.1  mrg             leadingBlank = false;
   4994  1.1  mrg             if (sc._module.filetype == FileType.ddoc || inCode)
   4995  1.1  mrg                 break;
   4996  1.1  mrg             const start = cast(char*)buf[].ptr + i;
   4997  1.1  mrg             if (isIdStart(start))
   4998  1.1  mrg             {
   4999  1.1  mrg                 size_t j = skippastident(buf, i);
   5000  1.1  mrg                 if (i < j)
   5001  1.1  mrg                 {
   5002  1.1  mrg                     size_t k = skippastURL(buf, i);
   5003  1.1  mrg                     if (i < k)
   5004  1.1  mrg                     {
   5005  1.1  mrg                         /* The URL is buf[i..k]
   5006  1.1  mrg                          */
   5007  1.1  mrg                         if (macroLevel)
   5008  1.1  mrg                             /* Leave alone if already in a macro
   5009  1.1  mrg                              */
   5010  1.1  mrg                             i = k - 1;
   5011  1.1  mrg                         else
   5012  1.1  mrg                         {
   5013  1.1  mrg                             /* Replace URL with '$(DDOC_LINK_AUTODETECT URL)'
   5014  1.1  mrg                              */
   5015  1.1  mrg                             i = buf.bracket(i, "$(DDOC_LINK_AUTODETECT ", k, ")") - 1;
   5016  1.1  mrg                         }
   5017  1.1  mrg                         break;
   5018  1.1  mrg                     }
   5019  1.1  mrg                 }
   5020  1.1  mrg                 else
   5021  1.1  mrg                     break;
   5022  1.1  mrg                 size_t len = j - i;
   5023  1.1  mrg                 // leading '_' means no highlight unless it's a reserved symbol name
   5024  1.1  mrg                 if (c == '_' && (i == 0 || !isdigit(*(start - 1))) && (i == buf.length - 1 || !isReservedName(start[0 .. len])))
   5025  1.1  mrg                 {
   5026  1.1  mrg                     buf.remove(i, 1);
   5027  1.1  mrg                     i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL_SUPPRESS ", j - 1, ")") - 1;
   5028  1.1  mrg                     break;
   5029  1.1  mrg                 }
   5030  1.1  mrg                 if (isIdentifier(a, start, len))
   5031  1.1  mrg                 {
   5032  1.1  mrg                     i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL ", j, ")") - 1;
   5033  1.1  mrg                     break;
   5034  1.1  mrg                 }
   5035  1.1  mrg                 if (isKeyword(start, len))
   5036  1.1  mrg                 {
   5037  1.1  mrg                     i = buf.bracket(i, "$(DDOC_AUTO_KEYWORD ", j, ")") - 1;
   5038  1.1  mrg                     break;
   5039  1.1  mrg                 }
   5040  1.1  mrg                 if (isFunctionParameter(a, start, len))
   5041  1.1  mrg                 {
   5042  1.1  mrg                     //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j);
   5043  1.1  mrg                     i = buf.bracket(i, "$(DDOC_AUTO_PARAM ", j, ")") - 1;
   5044  1.1  mrg                     break;
   5045  1.1  mrg                 }
   5046  1.1  mrg                 i = j - 1;
   5047  1.1  mrg             }
   5048  1.1  mrg             break;
   5049  1.1  mrg         }
   5050  1.1  mrg     }
   5051  1.1  mrg 
   5052  1.1  mrg     if (inCode == '-')
   5053  1.1  mrg         error(loc, "unmatched `---` in DDoc comment");
   5054  1.1  mrg     else if (inCode)
   5055  1.1  mrg         buf.insert(buf.length, ")");
   5056  1.1  mrg 
   5057  1.1  mrg     size_t i = buf.length;
   5058  1.1  mrg     if (headingLevel)
   5059  1.1  mrg     {
   5060  1.1  mrg         endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel);
   5061  1.1  mrg         removeBlankLineMacro(buf, iPrecedingBlankLine, i);
   5062  1.1  mrg     }
   5063  1.1  mrg     i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments);
   5064  1.1  mrg     i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters);
   5065  1.1  mrg     endAllListsAndQuotes(buf, i, nestedLists, quoteLevel, quoteMacroLevel);
   5066  1.1  mrg }
   5067  1.1  mrg 
   5068  1.1  mrg /**************************************************
   5069  1.1  mrg  * Highlight code for DDOC section.
   5070  1.1  mrg  */
   5071  1.1  mrg private void highlightCode(Scope* sc, Dsymbol s, ref OutBuffer buf, size_t offset)
   5072  1.1  mrg {
   5073  1.1  mrg     auto imp = s.isImport();
   5074  1.1  mrg     if (imp && imp.aliases.dim > 0)
   5075  1.1  mrg     {
   5076  1.1  mrg         // For example: `public import core.stdc.string : memcpy, memcmp;`
   5077  1.1  mrg         for(int i = 0; i < imp.aliases.dim; i++)
   5078  1.1  mrg         {
   5079  1.1  mrg             // Need to distinguish between
   5080  1.1  mrg             // `public import core.stdc.string : memcpy, memcmp;` and
   5081  1.1  mrg             // `public import core.stdc.string : copy = memcpy, compare = memcmp;`
   5082  1.1  mrg             auto a = imp.aliases[i];
   5083  1.1  mrg             auto id = a ? a : imp.names[i];
   5084  1.1  mrg             auto loc = Loc.init;
   5085  1.1  mrg             if (auto symFromId = sc.search(loc, id, null))
   5086  1.1  mrg             {
   5087  1.1  mrg                 highlightCode(sc, symFromId, buf, offset);
   5088  1.1  mrg             }
   5089  1.1  mrg         }
   5090  1.1  mrg     }
   5091  1.1  mrg     else
   5092  1.1  mrg     {
   5093  1.1  mrg         OutBuffer ancbuf;
   5094  1.1  mrg         emitAnchor(ancbuf, s, sc);
   5095  1.1  mrg         buf.insert(offset, ancbuf[]);
   5096  1.1  mrg         offset += ancbuf.length;
   5097  1.1  mrg 
   5098  1.1  mrg         Dsymbols a;
   5099  1.1  mrg         a.push(s);
   5100  1.1  mrg         highlightCode(sc, &a, buf, offset);
   5101  1.1  mrg     }
   5102  1.1  mrg }
   5103  1.1  mrg 
   5104  1.1  mrg /****************************************************
   5105  1.1  mrg  */
   5106  1.1  mrg private void highlightCode(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
   5107  1.1  mrg {
   5108  1.1  mrg     //printf("highlightCode(a = '%s')\n", a.toChars());
   5109  1.1  mrg     bool resolvedTemplateParameters = false;
   5110  1.1  mrg 
   5111  1.1  mrg     for (size_t i = offset; i < buf.length; i++)
   5112  1.1  mrg     {
   5113  1.1  mrg         char c = buf[i];
   5114  1.1  mrg         const se = sc._module.escapetable.escapeChar(c);
   5115  1.1  mrg         if (se.length)
   5116  1.1  mrg         {
   5117  1.1  mrg             buf.remove(i, 1);
   5118  1.1  mrg             i = buf.insert(i, se);
   5119  1.1  mrg             i--; // point to ';'
   5120  1.1  mrg             continue;
   5121  1.1  mrg         }
   5122  1.1  mrg         char* start = cast(char*)buf[].ptr + i;
   5123  1.1  mrg         if (isIdStart(start))
   5124  1.1  mrg         {
   5125  1.1  mrg             size_t j = skipPastIdentWithDots(buf, i);
   5126  1.1  mrg             if (i < j)
   5127  1.1  mrg             {
   5128  1.1  mrg                 size_t len = j - i;
   5129  1.1  mrg                 if (isIdentifier(a, start, len))
   5130  1.1  mrg                 {
   5131  1.1  mrg                     i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
   5132  1.1  mrg                     continue;
   5133  1.1  mrg                 }
   5134  1.1  mrg             }
   5135  1.1  mrg 
   5136  1.1  mrg             j = skippastident(buf, i);
   5137  1.1  mrg             if (i < j)
   5138  1.1  mrg             {
   5139  1.1  mrg                 size_t len = j - i;
   5140  1.1  mrg                 if (isIdentifier(a, start, len))
   5141  1.1  mrg                 {
   5142  1.1  mrg                     i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
   5143  1.1  mrg                     continue;
   5144  1.1  mrg                 }
   5145  1.1  mrg                 if (isFunctionParameter(a, start, len))
   5146  1.1  mrg                 {
   5147  1.1  mrg                     //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j);
   5148  1.1  mrg                     i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
   5149  1.1  mrg                     continue;
   5150  1.1  mrg                 }
   5151  1.1  mrg                 i = j - 1;
   5152  1.1  mrg             }
   5153  1.1  mrg         }
   5154  1.1  mrg         else if (!resolvedTemplateParameters)
   5155  1.1  mrg         {
   5156  1.1  mrg             size_t previ = i;
   5157  1.1  mrg 
   5158  1.1  mrg             // hunt for template declarations:
   5159  1.1  mrg             foreach (symi; 0 .. a.dim)
   5160  1.1  mrg             {
   5161  1.1  mrg                 FuncDeclaration fd = (*a)[symi].isFuncDeclaration();
   5162  1.1  mrg 
   5163  1.1  mrg                 if (!fd || !fd.parent || !fd.parent.isTemplateDeclaration())
   5164  1.1  mrg                 {
   5165  1.1  mrg                     continue;
   5166  1.1  mrg                 }
   5167  1.1  mrg 
   5168  1.1  mrg                 TemplateDeclaration td = fd.parent.isTemplateDeclaration();
   5169  1.1  mrg 
   5170  1.1  mrg                 // build the template parameters
   5171  1.1  mrg                 Array!(size_t) paramLens;
   5172  1.1  mrg                 paramLens.reserve(td.parameters.dim);
   5173  1.1  mrg 
   5174  1.1  mrg                 OutBuffer parametersBuf;
   5175  1.1  mrg                 HdrGenState hgs;
   5176  1.1  mrg 
   5177  1.1  mrg                 parametersBuf.writeByte('(');
   5178  1.1  mrg 
   5179  1.1  mrg                 foreach (parami; 0 .. td.parameters.dim)
   5180  1.1  mrg                 {
   5181  1.1  mrg                     TemplateParameter tp = (*td.parameters)[parami];
   5182  1.1  mrg 
   5183  1.1  mrg                     if (parami)
   5184  1.1  mrg                         parametersBuf.writestring(", ");
   5185  1.1  mrg 
   5186  1.1  mrg                     size_t lastOffset = parametersBuf.length;
   5187  1.1  mrg 
   5188  1.1  mrg                     .toCBuffer(tp, &parametersBuf, &hgs);
   5189  1.1  mrg 
   5190  1.1  mrg                     paramLens[parami] = parametersBuf.length - lastOffset;
   5191  1.1  mrg                 }
   5192  1.1  mrg                 parametersBuf.writeByte(')');
   5193  1.1  mrg 
   5194  1.1  mrg                 const templateParams = parametersBuf[];
   5195  1.1  mrg 
   5196  1.1  mrg                 //printf("templateDecl: %s\ntemplateParams: %s\nstart: %s\n", td.toChars(), templateParams, start);
   5197  1.1  mrg                 if (start[0 .. templateParams.length] == templateParams)
   5198  1.1  mrg                 {
   5199  1.1  mrg                     immutable templateParamListMacro = "$(DDOC_TEMPLATE_PARAM_LIST ";
   5200  1.1  mrg                     buf.bracket(i, templateParamListMacro.ptr, i + templateParams.length, ")");
   5201  1.1  mrg 
   5202  1.1  mrg                     // We have the parameter list. While we're here we might
   5203  1.1  mrg                     // as well wrap the parameters themselves as well
   5204  1.1  mrg 
   5205  1.1  mrg                     // + 1 here to take into account the opening paren of the
   5206  1.1  mrg                     // template param list
   5207  1.1  mrg                     i += templateParamListMacro.length + 1;
   5208  1.1  mrg 
   5209  1.1  mrg                     foreach (const len; paramLens)
   5210  1.1  mrg                     {
   5211  1.1  mrg                         i = buf.bracket(i, "$(DDOC_TEMPLATE_PARAM ", i + len, ")");
   5212  1.1  mrg                         // increment two here for space + comma
   5213  1.1  mrg                         i += 2;
   5214  1.1  mrg                     }
   5215  1.1  mrg 
   5216  1.1  mrg                     resolvedTemplateParameters = true;
   5217  1.1  mrg                     // reset i to be positioned back before we found the template
   5218  1.1  mrg                     // param list this assures that anything within the template
   5219  1.1  mrg                     // param list that needs to be escaped or otherwise altered
   5220  1.1  mrg                     // has an opportunity for that to happen outside of this context
   5221  1.1  mrg                     i = previ;
   5222  1.1  mrg 
   5223  1.1  mrg                     continue;
   5224  1.1  mrg                 }
   5225  1.1  mrg             }
   5226  1.1  mrg         }
   5227  1.1  mrg     }
   5228  1.1  mrg }
   5229  1.1  mrg 
   5230  1.1  mrg /****************************************
   5231  1.1  mrg  */
   5232  1.1  mrg private void highlightCode3(Scope* sc, ref OutBuffer buf, const(char)* p, const(char)* pend)
   5233  1.1  mrg {
   5234  1.1  mrg     for (; p < pend; p++)
   5235  1.1  mrg     {
   5236  1.1  mrg         const se = sc._module.escapetable.escapeChar(*p);
   5237  1.1  mrg         if (se.length)
   5238  1.1  mrg             buf.writestring(se);
   5239  1.1  mrg         else
   5240  1.1  mrg             buf.writeByte(*p);
   5241  1.1  mrg     }
   5242  1.1  mrg }
   5243  1.1  mrg 
   5244  1.1  mrg /**************************************************
   5245  1.1  mrg  * Highlight code for CODE section.
   5246  1.1  mrg  */
   5247  1.1  mrg private void highlightCode2(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
   5248  1.1  mrg {
   5249  1.1  mrg     uint errorsave = global.startGagging();
   5250  1.1  mrg 
   5251  1.1  mrg     scope Lexer lex = new Lexer(null, cast(char*)buf[].ptr, 0, buf.length - 1, 0, 1);
   5252  1.1  mrg     OutBuffer res;
   5253  1.1  mrg     const(char)* lastp = cast(char*)buf[].ptr;
   5254  1.1  mrg     //printf("highlightCode2('%.*s')\n", cast(int)(buf.length - 1), buf[].ptr);
   5255  1.1  mrg     res.reserve(buf.length);
   5256  1.1  mrg     while (1)
   5257  1.1  mrg     {
   5258  1.1  mrg         Token tok;
   5259  1.1  mrg         lex.scan(&tok);
   5260  1.1  mrg         highlightCode3(sc, res, lastp, tok.ptr);
   5261  1.1  mrg         string highlight = null;
   5262  1.1  mrg         switch (tok.value)
   5263  1.1  mrg         {
   5264  1.1  mrg         case TOK.identifier:
   5265  1.1  mrg             {
   5266  1.1  mrg                 if (!sc)
   5267  1.1  mrg                     break;
   5268  1.1  mrg                 size_t len = lex.p - tok.ptr;
   5269  1.1  mrg                 if (isIdentifier(a, tok.ptr, len))
   5270  1.1  mrg                 {
   5271  1.1  mrg                     highlight = "$(D_PSYMBOL ";
   5272  1.1  mrg                     break;
   5273  1.1  mrg                 }
   5274  1.1  mrg                 if (isFunctionParameter(a, tok.ptr, len))
   5275  1.1  mrg                 {
   5276  1.1  mrg                     //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j);
   5277  1.1  mrg                     highlight = "$(D_PARAM ";
   5278  1.1  mrg                     break;
   5279  1.1  mrg                 }
   5280  1.1  mrg                 break;
   5281  1.1  mrg             }
   5282  1.1  mrg         case TOK.comment:
   5283  1.1  mrg             highlight = "$(D_COMMENT ";
   5284  1.1  mrg             break;
   5285  1.1  mrg         case TOK.string_:
   5286  1.1  mrg             highlight = "$(D_STRING ";
   5287  1.1  mrg             break;
   5288  1.1  mrg         default:
   5289  1.1  mrg             if (tok.isKeyword())
   5290  1.1  mrg                 highlight = "$(D_KEYWORD ";
   5291  1.1  mrg             break;
   5292  1.1  mrg         }
   5293  1.1  mrg         if (highlight)
   5294  1.1  mrg         {
   5295  1.1  mrg             res.writestring(highlight);
   5296  1.1  mrg             size_t o = res.length;
   5297  1.1  mrg             highlightCode3(sc, res, tok.ptr, lex.p);
   5298  1.1  mrg             if (tok.value == TOK.comment || tok.value == TOK.string_)
   5299  1.1  mrg                 /* https://issues.dlang.org/show_bug.cgi?id=7656
   5300  1.1  mrg                  * https://issues.dlang.org/show_bug.cgi?id=7715
   5301  1.1  mrg                  * https://issues.dlang.org/show_bug.cgi?id=10519
   5302  1.1  mrg                  */
   5303  1.1  mrg                 escapeDdocString(&res, o);
   5304  1.1  mrg             res.writeByte(')');
   5305  1.1  mrg         }
   5306  1.1  mrg         else
   5307  1.1  mrg             highlightCode3(sc, res, tok.ptr, lex.p);
   5308  1.1  mrg         if (tok.value == TOK.endOfFile)
   5309  1.1  mrg             break;
   5310  1.1  mrg         lastp = lex.p;
   5311  1.1  mrg     }
   5312  1.1  mrg     buf.setsize(offset);
   5313  1.1  mrg     buf.write(&res);
   5314  1.1  mrg     global.endGagging(errorsave);
   5315  1.1  mrg }
   5316  1.1  mrg 
   5317  1.1  mrg /****************************************
   5318  1.1  mrg  * Determine if p points to the start of a "..." parameter identifier.
   5319  1.1  mrg  */
   5320  1.1  mrg private bool isCVariadicArg(const(char)[] p)
   5321  1.1  mrg {
   5322  1.1  mrg     return p.length >= 3 && p[0 .. 3] == "...";
   5323  1.1  mrg }
   5324  1.1  mrg 
   5325  1.1  mrg /****************************************
   5326  1.1  mrg  * Determine if p points to the start of an identifier.
   5327  1.1  mrg  */
   5328  1.1  mrg bool isIdStart(const(char)* p)
   5329  1.1  mrg {
   5330  1.1  mrg     dchar c = *p;
   5331  1.1  mrg     if (isalpha(c) || c == '_')
   5332  1.1  mrg         return true;
   5333  1.1  mrg     if (c >= 0x80)
   5334  1.1  mrg     {
   5335  1.1  mrg         size_t i = 0;
   5336  1.1  mrg         if (utf_decodeChar(p[0 .. 4], i, c))
   5337  1.1  mrg             return false; // ignore errors
   5338  1.1  mrg         if (isUniAlpha(c))
   5339  1.1  mrg             return true;
   5340  1.1  mrg     }
   5341  1.1  mrg     return false;
   5342  1.1  mrg }
   5343  1.1  mrg 
   5344  1.1  mrg /****************************************
   5345  1.1  mrg  * Determine if p points to the rest of an identifier.
   5346  1.1  mrg  */
   5347  1.1  mrg bool isIdTail(const(char)* p)
   5348  1.1  mrg {
   5349  1.1  mrg     dchar c = *p;
   5350  1.1  mrg     if (isalnum(c) || c == '_')
   5351  1.1  mrg         return true;
   5352  1.1  mrg     if (c >= 0x80)
   5353  1.1  mrg     {
   5354  1.1  mrg         size_t i = 0;
   5355  1.1  mrg         if (utf_decodeChar(p[0 .. 4], i, c))
   5356  1.1  mrg             return false; // ignore errors
   5357  1.1  mrg         if (isUniAlpha(c))
   5358  1.1  mrg             return true;
   5359  1.1  mrg     }
   5360  1.1  mrg     return false;
   5361  1.1  mrg }
   5362  1.1  mrg 
   5363  1.1  mrg /****************************************
   5364  1.1  mrg  * Determine if p points to the indentation space.
   5365  1.1  mrg  */
   5366  1.1  mrg private bool isIndentWS(const(char)* p)
   5367  1.1  mrg {
   5368  1.1  mrg     return (*p == ' ') || (*p == '\t');
   5369  1.1  mrg }
   5370  1.1  mrg 
   5371  1.1  mrg /*****************************************
   5372  1.1  mrg  * Return number of bytes in UTF character.
   5373  1.1  mrg  */
   5374  1.1  mrg int utfStride(const(char)* p)
   5375  1.1  mrg {
   5376  1.1  mrg     dchar c = *p;
   5377  1.1  mrg     if (c < 0x80)
   5378  1.1  mrg         return 1;
   5379  1.1  mrg     size_t i = 0;
   5380  1.1  mrg     utf_decodeChar(p[0 .. 4], i, c); // ignore errors, but still consume input
   5381  1.1  mrg     return cast(int)i;
   5382  1.1  mrg }
   5383  1.1  mrg 
   5384  1.1  mrg private inout(char)* stripLeadingNewlines(inout(char)* s)
   5385  1.1  mrg {
   5386  1.1  mrg     while (s && *s == '\n' || *s == '\r')
   5387  1.1  mrg         s++;
   5388  1.1  mrg 
   5389  1.1  mrg     return s;
   5390  1.1  mrg }
   5391