1
/**
2
 * Ddoc documentation generation.
3
 *
4
 * Specification: $(LINK2 https://dlang.org/spec/ddoc.html, Documentation Generator)
5
 *
6
 * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
7
 * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
8
 * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/doc.d, _doc.d)
10
 * Documentation:  https://dlang.org/phobos/dmd_doc.html
11
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/doc.d
12
 */
13

14
module dmd.doc;
15

16
import core.stdc.ctype;
17
import core.stdc.stdlib;
18
import core.stdc.stdio;
19
import core.stdc.string;
20
import core.stdc.time;
21
import dmd.aggregate;
22
import dmd.arraytypes;
23
import dmd.attrib;
24
import dmd.cond;
25
import dmd.dclass;
26
import dmd.declaration;
27
import dmd.denum;
28
import dmd.dimport;
29
import dmd.dmacro;
30
import dmd.dmodule;
31
import dmd.dscope;
32
import dmd.dstruct;
33
import dmd.dsymbol;
34
import dmd.dsymbolsem;
35
import dmd.dtemplate;
36
import dmd.errors;
37
import dmd.func;
38
import dmd.globals;
39
import dmd.hdrgen;
40
import dmd.id;
41
import dmd.identifier;
42
import dmd.lexer;
43
import dmd.mtype;
44
import dmd.root.array;
45
import dmd.root.file;
46
import dmd.root.filename;
47
import dmd.root.outbuffer;
48
import dmd.root.port;
49
import dmd.root.rmem;
50
import dmd.root.string;
51
import dmd.tokens;
52
import dmd.utf;
53
import dmd.utils;
54
import dmd.visitor;
55

56
struct Escape
57
{
58
    const(char)[][char.max] strings;
59

60
    /***************************************
61
     * Find character string to replace c with.
62
     */
63
    const(char)[] escapeChar(char c)
64
    {
65
        version (all)
66
        {
67
            //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c].ptr);
68 1
            return strings[c];
69
        }
70
        else
71
        {
72
            const(char)[] s;
73
            switch (c)
74
            {
75
            case '<':
76
                s = "&lt;";
77
                break;
78
            case '>':
79
                s = "&gt;";
80
                break;
81
            case '&':
82
                s = "&amp;";
83
                break;
84
            default:
85
                s = null;
86
                break;
87
            }
88
            return s;
89
        }
90
    }
91
}
92

93
/***********************************************************
94
 */
95
private class Section
96
{
97
    const(char)[] name;
98
    const(char)[] body_;
99
    int nooutput;
100

101
    override string toString() const
102
    {
103 0
        assert(0);
104
    }
105

106
    void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
107
    {
108 1
        assert(a.dim);
109 1
        if (name.length)
110
        {
111
            static immutable table =
112
            [
113
                "AUTHORS",
114
                "BUGS",
115
                "COPYRIGHT",
116
                "DATE",
117
                "DEPRECATED",
118
                "EXAMPLES",
119
                "HISTORY",
120
                "LICENSE",
121
                "RETURNS",
122
                "SEE_ALSO",
123
                "STANDARDS",
124
                "THROWS",
125
                "VERSION",
126
            ];
127 1
            foreach (entry; table)
128
            {
129 1
                if (iequals(entry, name))
130
                {
131 1
                    buf.printf("$(DDOC_%s ", entry.ptr);
132 1
                    goto L1;
133
                }
134
            }
135 1
            buf.writestring("$(DDOC_SECTION ");
136
            // Replace _ characters with spaces
137 1
            buf.writestring("$(DDOC_SECTION_H ");
138 1
            size_t o = buf.length;
139 1
            foreach (char c; name)
140 1
                buf.writeByte((c == '_') ? ' ' : c);
141 1
            escapeStrayParenthesis(loc, buf, o, false);
142 1
            buf.writestring(")");
143
        }
144
        else
145
        {
146 1
            buf.writestring("$(DDOC_DESCRIPTION ");
147
        }
148
    L1:
149 1
        size_t o = buf.length;
150 1
        buf.write(body_);
151 1
        escapeStrayParenthesis(loc, buf, o, true);
152 1
        highlightText(sc, a, loc, *buf, o);
153 1
        buf.writestring(")");
154
    }
155
}
156

157
/***********************************************************
158
 */
159
private final class ParamSection : Section
160
{
161
    override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
162
    {
163 1
        assert(a.dim);
164 1
        Dsymbol s = (*a)[0]; // test
165 1
        const(char)* p = body_.ptr;
166 1
        size_t len = body_.length;
167 1
        const(char)* pend = p + len;
168 1
        const(char)* tempstart = null;
169 1
        size_t templen = 0;
170 1
        const(char)* namestart = null;
171 1
        size_t namelen = 0; // !=0 if line continuation
172 1
        const(char)* textstart = null;
173 1
        size_t textlen = 0;
174 1
        size_t paramcount = 0;
175 1
        buf.writestring("$(DDOC_PARAMS ");
176 1
        while (p < pend)
177
        {
178
            // Skip to start of macro
179 1
            while (1)
180
            {
181 1
                switch (*p)
182
                {
183 1
                case ' ':
184 1
                case '\t':
185 1
                    p++;
186 1
                    continue;
187 0
                case '\n':
188 0
                    p++;
189 0
                    goto Lcont;
190 1
                default:
191 1
                    if (isIdStart(p) || isCVariadicArg(p[0 .. cast(size_t)(pend - p)]))
192 1
                        break;
193 0
                    if (namelen)
194 0
                        goto Ltext;
195
                    // continuation of prev macro
196 0
                    goto Lskipline;
197
                }
198 1
                break;
199
            }
200 1
            tempstart = p;
201 1
            while (isIdTail(p))
202 1
                p += utfStride(p);
203 1
            if (isCVariadicArg(p[0 .. cast(size_t)(pend - p)]))
204 1
                p += 3;
205 1
            templen = p - tempstart;
206 1
            while (*p == ' ' || *p == '\t')
207 1
                p++;
208 1
            if (*p != '=')
209
            {
210 1
                if (namelen)
211 1
                    goto Ltext;
212
                // continuation of prev macro
213 1
                goto Lskipline;
214
            }
215 1
            p++;
216 1
            if (namelen)
217
            {
218
                // Output existing param
219
            L1:
220
                //printf("param '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart);
221 1
                ++paramcount;
222 1
                HdrGenState hgs;
223 1
                buf.writestring("$(DDOC_PARAM_ROW ");
224
                {
225 1
                    buf.writestring("$(DDOC_PARAM_ID ");
226
                    {
227 1
                        size_t o = buf.length;
228 1
                        Parameter fparam = isFunctionParameter(a, namestart, namelen);
229 1
                        if (!fparam)
230
                        {
231
                            // Comments on a template might refer to function parameters within.
232
                            // Search the parameters of nested eponymous functions (with the same name.)
233 1
                            fparam = isEponymousFunctionParameter(a, namestart, namelen);
234
                        }
235 1
                        bool isCVariadic = isCVariadicParameter(a, namestart[0 .. namelen]);
236 1
                        if (isCVariadic)
237
                        {
238 1
                            buf.writestring("...");
239
                        }
240 1
                        else if (fparam && fparam.type && fparam.ident)
241
                        {
242 1
                            .toCBuffer(fparam.type, buf, fparam.ident, &hgs);
243
                        }
244
                        else
245
                        {
246 1
                            if (isTemplateParameter(a, namestart, namelen))
247
                            {
248
                                // 10236: Don't count template parameters for params check
249 1
                                --paramcount;
250
                            }
251 1
                            else if (!fparam)
252
                            {
253 1
                                warning(s.loc, "Ddoc: function declaration has no parameter '%.*s'", cast(int)namelen, namestart);
254
                            }
255 1
                            buf.write(namestart[0 .. namelen]);
256
                        }
257 1
                        escapeStrayParenthesis(loc, buf, o, true);
258 1
                        highlightCode(sc, a, *buf, o);
259
                    }
260 1
                    buf.writestring(")");
261 1
                    buf.writestring("$(DDOC_PARAM_DESC ");
262
                    {
263 1
                        size_t o = buf.length;
264 1
                        buf.write(textstart[0 .. textlen]);
265 1
                        escapeStrayParenthesis(loc, buf, o, true);
266 1
                        highlightText(sc, a, loc, *buf, o);
267
                    }
268 1
                    buf.writestring(")");
269
                }
270 1
                buf.writestring(")");
271 1
                namelen = 0;
272 1
                if (p >= pend)
273 1
                    break;
274
            }
275 1
            namestart = tempstart;
276 1
            namelen = templen;
277 1
            while (*p == ' ' || *p == '\t')
278 1
                p++;
279 1
            textstart = p;
280
        Ltext:
281 1
            while (*p != '\n')
282 1
                p++;
283 1
            textlen = p - textstart;
284 1
            p++;
285
        Lcont:
286 1
            continue;
287
        Lskipline:
288
            // Ignore this line
289 1
            while (*p++ != '\n')
290
            {
291
            }
292
        }
293 1
        if (namelen)
294 1
            goto L1;
295
        // write out last one
296 1
        buf.writestring(")");
297 1
        TypeFunction tf = a.dim == 1 ? isTypeFunction(s) : null;
298 1
        if (tf)
299
        {
300 1
            size_t pcount = (tf.parameterList.parameters ? tf.parameterList.parameters.dim : 0) +
301
                            cast(int)(tf.parameterList.varargs == VarArg.variadic);
302 1
            if (pcount != paramcount)
303
            {
304 1
                warning(s.loc, "Ddoc: parameter count mismatch, expected %llu, got %llu",
305
                        cast(ulong) pcount, cast(ulong) paramcount);
306 1
                if (paramcount == 0)
307
                {
308
                    // Chances are someone messed up the format
309 1
                    warningSupplemental(s.loc, "Note that the format is `param = description`");
310
                }
311
            }
312
        }
313
    }
314
}
315

316
/***********************************************************
317
 */
318
private final class MacroSection : Section
319
{
320
    override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
321
    {
322
        //printf("MacroSection::write()\n");
323 1
        DocComment.parseMacros(dc.escapetable, *dc.pmacrotable, body_);
324
    }
325
}
326

327
private alias Sections = Array!(Section);
328

329
// Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one).
330
private bool isCVariadicParameter(Dsymbols* a, const(char)[] p)
331
{
332 1
    foreach (member; *a)
333
    {
334 1
        TypeFunction tf = isTypeFunction(member);
335 1
        if (tf && tf.parameterList.varargs == VarArg.variadic && p == "...")
336 1
            return true;
337
    }
338 1
    return false;
339
}
340

341
private Dsymbol getEponymousMember(TemplateDeclaration td)
342
{
343 1
    if (!td.onemember)
344 1
        return null;
345 1
    if (AggregateDeclaration ad = td.onemember.isAggregateDeclaration())
346 1
        return ad;
347 1
    if (FuncDeclaration fd = td.onemember.isFuncDeclaration())
348 1
        return fd;
349 1
    if (auto em = td.onemember.isEnumMember())
350 1
        return null;    // Keep backward compatibility. See compilable/ddoc9.d
351 1
    if (VarDeclaration vd = td.onemember.isVarDeclaration())
352 1
        return td.constraint ? null : vd;
353 1
    return null;
354
}
355

356
private TemplateDeclaration getEponymousParent(Dsymbol s)
357
{
358 1
    if (!s.parent)
359 1
        return null;
360 1
    TemplateDeclaration td = s.parent.isTemplateDeclaration();
361 1
    return (td && getEponymousMember(td)) ? td : null;
362
}
363

364
private immutable ddoc_default = import("default_ddoc_theme.ddoc");
365
private immutable ddoc_decl_s = "$(DDOC_DECL ";
366
private immutable ddoc_decl_e = ")\n";
367
private immutable ddoc_decl_dd_s = "$(DDOC_DECL_DD ";
368
private immutable ddoc_decl_dd_e = ")\n";
369

370
/****************************************************
371
 */
372
extern(C++) void gendocfile(Module m)
373
{
374 1
    __gshared OutBuffer mbuf;
375 1
    __gshared int mbuf_done;
376 1
    OutBuffer buf;
377
    //printf("Module::gendocfile()\n");
378 1
    if (!mbuf_done) // if not already read the ddoc files
379
    {
380 1
        mbuf_done = 1;
381
        // Use our internal default
382 1
        mbuf.writestring(ddoc_default);
383
        // Override with DDOCFILE specified in the sc.ini file
384 1
        char* p = getenv("DDOCFILE");
385 1
        if (p)
386 0
            global.params.ddocfiles.shift(p);
387
        // Override with the ddoc macro files from the command line
388 1
        for (size_t i = 0; i < global.params.ddocfiles.dim; i++)
389
        {
390 1
            auto buffer = readFile(m.loc, global.params.ddocfiles[i]);
391
            // BUG: convert file contents to UTF-8 before use
392 1
            const data = buffer.data;
393
            //printf("file: '%.*s'\n", cast(int)data.length, data.ptr);
394 1
            mbuf.write(data);
395
        }
396
    }
397 1
    DocComment.parseMacros(m.escapetable, m.macrotable, mbuf[]);
398 1
    Scope* sc = Scope.createGlobal(m); // create root scope
399 1
    DocComment* dc = DocComment.parse(m, m.comment);
400 1
    dc.pmacrotable = &m.macrotable;
401 1
    dc.escapetable = m.escapetable;
402 1
    sc.lastdc = dc;
403
    // Generate predefined macros
404
    // Set the title to be the name of the module
405
    {
406 1
        const p = m.toPrettyChars().toDString;
407 1
        m.macrotable.define("TITLE", p);
408
    }
409
    // Set time macros
410
    {
411 1
        time_t t;
412 1
        time(&t);
413 1
        char* p = ctime(&t);
414 1
        p = mem.xstrdup(p);
415 1
        m.macrotable.define("DATETIME", p.toDString());
416 1
        m.macrotable.define("YEAR", p[20 .. 20 + 4]);
417
    }
418 1
    const srcfilename = m.srcfile.toString();
419 1
    m.macrotable.define("SRCFILENAME", srcfilename);
420 1
    const docfilename = m.docfile.toString();
421 1
    m.macrotable.define("DOCFILENAME", docfilename);
422 1
    if (dc.copyright)
423
    {
424 1
        dc.copyright.nooutput = 1;
425 1
        m.macrotable.define("COPYRIGHT", dc.copyright.body_);
426
    }
427 1
    if (m.isDocFile)
428
    {
429 1
        const ploc = m.md ? &m.md.loc : &m.loc;
430 1
        const loc = Loc(ploc.filename ? ploc.filename : srcfilename.ptr,
431
                        ploc.linnum,
432
                        ploc.charnum);
433

434 1
        size_t commentlen = strlen(cast(char*)m.comment);
435 1
        Dsymbols a;
436
        // https://issues.dlang.org/show_bug.cgi?id=9764
437
        // Don't push m in a, to prevent emphasize ddoc file name.
438 1
        if (dc.macros)
439
        {
440 0
            commentlen = dc.macros.name.ptr - m.comment;
441 0
            dc.macros.write(loc, dc, sc, &a, &buf);
442
        }
443 1
        buf.write(m.comment[0 .. commentlen]);
444 1
        highlightText(sc, &a, loc, buf, 0);
445
    }
446
    else
447
    {
448 1
        Dsymbols a;
449 1
        a.push(m);
450 1
        dc.writeSections(sc, &a, &buf);
451 1
        emitMemberComments(m, buf, sc);
452
    }
453
    //printf("BODY= '%.*s'\n", cast(int)buf.length, buf.data);
454 1
    m.macrotable.define("BODY", buf[]);
455 1
    OutBuffer buf2;
456 1
    buf2.writestring("$(DDOC)");
457 1
    size_t end = buf2.length;
458 1
    m.macrotable.expand(buf2, 0, end, null);
459
    version (all)
460
    {
461
        /* Remove all the escape sequences from buf2,
462
         * and make CR-LF the newline.
463
         */
464
        {
465 1
            const slice = buf2[];
466 1
            buf.setsize(0);
467 1
            buf.reserve(slice.length);
468 1
            auto p = slice.ptr;
469 1
            for (size_t j = 0; j < slice.length; j++)
470
            {
471 1
                char c = p[j];
472 1
                if (c == 0xFF && j + 1 < slice.length)
473
                {
474 1
                    j++;
475 1
                    continue;
476
                }
477 1
                if (c == '\n')
478 1
                    buf.writeByte('\r');
479 1
                else if (c == '\r')
480
                {
481 0
                    buf.writestring("\r\n");
482 0
                    if (j + 1 < slice.length && p[j + 1] == '\n')
483
                    {
484 0
                        j++;
485
                    }
486 0
                    continue;
487
                }
488 1
                buf.writeByte(c);
489
            }
490
        }
491 1
        writeFile(m.loc, m.docfile.toString(), buf[]);
492
    }
493
    else
494
    {
495
        /* Remove all the escape sequences from buf2
496
         */
497
        {
498
            size_t i = 0;
499
            char* p = buf2.data;
500
            for (size_t j = 0; j < buf2.length; j++)
501
            {
502
                if (p[j] == 0xFF && j + 1 < buf2.length)
503
                {
504
                    j++;
505
                    continue;
506
                }
507
                p[i] = p[j];
508
                i++;
509
            }
510
            buf2.setsize(i);
511
        }
512
        writeFile(m.loc, m.docfile.toString(), buf2[]);
513
    }
514
}
515

516
/****************************************************
517
 * Having unmatched parentheses can hose the output of Ddoc,
518
 * as the macros depend on properly nested parentheses.
519
 * This function replaces all ( with $(LPAREN) and ) with $(RPAREN)
520
 * to preserve text literally. This also means macros in the
521
 * text won't be expanded.
522
 */
523
void escapeDdocString(OutBuffer* buf, size_t start)
524
{
525 1
    for (size_t u = start; u < buf.length; u++)
526
    {
527 1
        char c = (*buf)[u];
528 1
        switch (c)
529
        {
530 1
        case '$':
531 1
            buf.remove(u, 1);
532 1
            buf.insert(u, "$(DOLLAR)");
533 1
            u += 8;
534 1
            break;
535 1
        case '(':
536 1
            buf.remove(u, 1); //remove the (
537 1
            buf.insert(u, "$(LPAREN)"); //insert this instead
538 1
            u += 8; //skip over newly inserted macro
539 1
            break;
540 1
        case ')':
541 1
            buf.remove(u, 1); //remove the )
542 1
            buf.insert(u, "$(RPAREN)"); //insert this instead
543 1
            u += 8; //skip over newly inserted macro
544 1
            break;
545 1
        default:
546 1
            break;
547
        }
548
    }
549
}
550

551
/****************************************************
552
 * Having unmatched parentheses can hose the output of Ddoc,
553
 * as the macros depend on properly nested parentheses.
554
 *
555
 * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN).
556
 *
557
 * Params:
558
 *  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.
559
 *  buf   = an OutBuffer containing the DDoc
560
 *  start = the index within buf to start replacing unmatched parentheses
561
 *  respectBackslashEscapes = if true, always replace parentheses that are
562
 *    directly preceeded by a backslash with $(LPAREN) or $(RPAREN) instead of
563
 *    counting them as stray parentheses
564
 */
565
private void escapeStrayParenthesis(Loc loc, OutBuffer* buf, size_t start, bool respectBackslashEscapes)
566
{
567 1
    uint par_open = 0;
568 1
    char inCode = 0;
569 1
    bool atLineStart = true;
570 1
    for (size_t u = start; u < buf.length; u++)
571
    {
572 1
        char c = (*buf)[u];
573 1
        switch (c)
574
        {
575 1
        case '(':
576 1
            if (!inCode)
577 1
                par_open++;
578 1
            atLineStart = false;
579 1
            break;
580 1
        case ')':
581 1
            if (!inCode)
582
            {
583 1
                if (par_open == 0)
584
                {
585
                    //stray ')'
586 1
                    warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output. Use $(RPAREN) instead for unpaired right parentheses.");
587 1
                    buf.remove(u, 1); //remove the )
588 1
                    buf.insert(u, "$(RPAREN)"); //insert this instead
589 1
                    u += 8; //skip over newly inserted macro
590
                }
591
                else
592 1
                    par_open--;
593
            }
594 1
            atLineStart = false;
595 1
            break;
596 1
        case '\n':
597 1
            atLineStart = true;
598
            version (none)
599
            {
600
                // For this to work, loc must be set to the beginning of the passed
601
                // text which is currently not possible
602
                // (loc is set to the Loc of the Dsymbol)
603
                loc.linnum++;
604
            }
605 1
            break;
606 1
        case ' ':
607 1
        case '\r':
608 1
        case '\t':
609 1
            break;
610 1
        case '-':
611 1
        case '`':
612 1
        case '~':
613
            // Issue 15465: don't try to escape unbalanced parens inside code
614
            // blocks.
615 1
            int numdash = 1;
616 1
            for (++u; u < buf.length && (*buf)[u] == c; ++u)
617 1
                ++numdash;
618 1
            --u;
619 1
            if (c == '`' || (atLineStart && numdash >= 3))
620
            {
621 1
                if (inCode == c)
622 1
                    inCode = 0;
623 1
                else if (!inCode)
624 1
                    inCode = c;
625
            }
626 1
            atLineStart = false;
627 1
            break;
628 1
        case '\\':
629
            // replace backslash-escaped parens with their macros
630 1
            if (!inCode && respectBackslashEscapes && u+1 < buf.length && global.params.markdown)
631
            {
632 1
                if ((*buf)[u+1] == '(' || (*buf)[u+1] == ')')
633
                {
634 1
                    const paren = (*buf)[u+1] == '(' ? "$(LPAREN)" : "$(RPAREN)";
635 1
                    buf.remove(u, 2); //remove the \)
636 1
                    buf.insert(u, paren); //insert this instead
637 1
                    u += 8; //skip over newly inserted macro
638
                }
639 1
                else if ((*buf)[u+1] == '\\')
640 1
                    ++u;
641
            }
642 1
            break;
643 1
        default:
644 1
            atLineStart = false;
645 1
            break;
646
        }
647
    }
648 1
    if (par_open) // if any unmatched lparens
649
    {
650 1
        par_open = 0;
651 1
        for (size_t u = buf.length; u > start;)
652
        {
653 1
            u--;
654 1
            char c = (*buf)[u];
655 1
            switch (c)
656
            {
657 0
            case ')':
658 0
                par_open++;
659 0
                break;
660 1
            case '(':
661 1
                if (par_open == 0)
662
                {
663
                    //stray '('
664 1
                    warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output. Use $(LPAREN) instead for unpaired left parentheses.");
665 1
                    buf.remove(u, 1); //remove the (
666 1
                    buf.insert(u, "$(LPAREN)"); //insert this instead
667
                }
668
                else
669 0
                    par_open--;
670 1
                break;
671 1
            default:
672 1
                break;
673
            }
674
        }
675
    }
676
}
677

678
// Basically, this is to skip over things like private{} blocks in a struct or
679
// class definition that don't add any components to the qualified name.
680
private Scope* skipNonQualScopes(Scope* sc)
681
{
682 1
    while (sc && !sc.scopesym)
683 0
        sc = sc.enclosing;
684 1
    return sc;
685
}
686

687
private bool emitAnchorName(ref OutBuffer buf, Dsymbol s, Scope* sc, bool includeParent)
688
{
689 1
    if (!s || s.isPackage() || s.isModule())
690 1
        return false;
691
    // Add parent names first
692 1
    bool dot = false;
693 1
    auto eponymousParent = getEponymousParent(s);
694 1
    if (includeParent && s.parent || eponymousParent)
695 1
        dot = emitAnchorName(buf, s.parent, sc, includeParent);
696 1
    else if (includeParent && sc)
697 1
        dot = emitAnchorName(buf, sc.scopesym, skipNonQualScopes(sc.enclosing), includeParent);
698
    // Eponymous template members can share the parent anchor name
699 1
    if (eponymousParent)
700 1
        return dot;
701 1
    if (dot)
702 1
        buf.writeByte('.');
703
    // Use "this" not "__ctor"
704 1
    TemplateDeclaration td;
705 1
    if (s.isCtorDeclaration() || ((td = s.isTemplateDeclaration()) !is null && td.onemember && td.onemember.isCtorDeclaration()))
706
    {
707 1
        buf.writestring("this");
708
    }
709
    else
710
    {
711
        /* We just want the identifier, not overloads like TemplateDeclaration::toChars.
712
         * We don't want the template parameter list and constraints. */
713 1
        buf.writestring(s.Dsymbol.toChars());
714
    }
715 1
    return true;
716
}
717

718
private void emitAnchor(ref OutBuffer buf, Dsymbol s, Scope* sc, bool forHeader = false)
719
{
720 1
    Identifier ident;
721
    {
722 1
        OutBuffer anc;
723 1
        emitAnchorName(anc, s, skipNonQualScopes(sc), true);
724 1
        ident = Identifier.idPool(anc[]);
725
    }
726

727 1
    auto pcount = cast(void*)ident in sc.anchorCounts;
728 1
    typeof(*pcount) count;
729 1
    if (!forHeader)
730
    {
731 1
        if (pcount)
732
        {
733
            // Existing anchor,
734
            // don't write an anchor for matching consecutive ditto symbols
735 1
            TemplateDeclaration td = getEponymousParent(s);
736 1
            if (sc.prevAnchor == ident && sc.lastdc && (isDitto(s.comment) || (td && isDitto(td.comment))))
737 1
                return;
738

739 1
            count = ++*pcount;
740
        }
741
        else
742
        {
743 1
            sc.anchorCounts[cast(void*)ident] = 1;
744 1
            count = 1;
745
        }
746
    }
747

748
    // cache anchor name
749 1
    sc.prevAnchor = ident;
750 1
    auto macroName = forHeader ? "DDOC_HEADER_ANCHOR" : "DDOC_ANCHOR";
751

752 1
    if (auto imp = s.isImport())
753
    {
754
        // For example: `public import core.stdc.string : memcpy, memcmp;`
755 1
        if (imp.aliases.dim > 0)
756
        {
757 1
            for(int i = 0; i < imp.aliases.dim; i++)
758
            {
759
                // Need to distinguish between
760
                // `public import core.stdc.string : memcpy, memcmp;` and
761
                // `public import core.stdc.string : copy = memcpy, compare = memcmp;`
762 1
                auto a = imp.aliases[i];
763 1
                auto id = a ? a : imp.names[i];
764 1
                auto loc = Loc.init;
765 1
                if (auto symFromId = sc.search(loc, id, null))
766
                {
767 1
                    emitAnchor(buf, symFromId, sc, forHeader);
768
                }
769
            }
770
        }
771
        else
772
        {
773
            // For example: `public import str = core.stdc.string;`
774 1
            if (imp.aliasId)
775
            {
776 1
                auto symbolName = imp.aliasId.toString();
777

778 1
                buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr,
779
                    cast(int) symbolName.length, symbolName.ptr);
780

781 1
                if (forHeader)
782
                {
783 1
                    buf.printf(", %.*s", cast(int) symbolName.length, symbolName.ptr);
784
                }
785
            }
786
            else
787
            {
788
                // The general case:  `public import core.stdc.string;`
789

790
                // fully qualify imports so `core.stdc.string` doesn't appear as `core`
791
                void printFullyQualifiedImport()
792
                {
793 1
                    if (imp.packages && imp.packages.dim)
794
                    {
795 1
                        foreach (const pid; *imp.packages)
796
                        {
797 1
                            buf.printf("%s.", pid.toChars());
798
                        }
799
                    }
800 1
                    buf.writestring(imp.id.toString());
801
                }
802

803 1
                buf.printf("$(%.*s ", cast(int) macroName.length, macroName.ptr);
804 1
                printFullyQualifiedImport();
805

806 1
                if (forHeader)
807
                {
808 1
                    buf.printf(", ");
809 1
                    printFullyQualifiedImport();
810
                }
811
            }
812

813 1
            buf.writeByte(')');
814
        }
815
    }
816
    else
817
    {
818 1
        auto symbolName = ident.toString();
819 1
        buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr,
820
            cast(int) symbolName.length, symbolName.ptr);
821

822
        // only append count once there's a duplicate
823 1
        if (count > 1)
824 1
            buf.printf(".%u", count);
825

826 1
        if (forHeader)
827
        {
828 1
            Identifier shortIdent;
829
            {
830 1
                OutBuffer anc;
831 1
                emitAnchorName(anc, s, skipNonQualScopes(sc), false);
832 1
                shortIdent = Identifier.idPool(anc[]);
833
            }
834

835 1
            auto shortName = shortIdent.toString();
836 1
            buf.printf(", %.*s", cast(int) shortName.length, shortName.ptr);
837
        }
838

839 1
        buf.writeByte(')');
840
    }
841
}
842

843
/******************************* emitComment **********************************/
844

845
/** Get leading indentation from 'src' which represents lines of code. */
846
private size_t getCodeIndent(const(char)* src)
847
{
848 1
    while (src && (*src == '\r' || *src == '\n'))
849 0
        ++src; // skip until we find the first non-empty line
850 1
    size_t codeIndent = 0;
851 1
    while (src && (*src == ' ' || *src == '\t'))
852
    {
853 1
        codeIndent++;
854 1
        src++;
855
    }
856 1
    return codeIndent;
857
}
858

859
/** Recursively expand template mixin member docs into the scope. */
860
private void expandTemplateMixinComments(TemplateMixin tm, ref OutBuffer buf, Scope* sc)
861
{
862 1
    if (!tm.semanticRun)
863 1
        tm.dsymbolSemantic(sc);
864 1
    TemplateDeclaration td = (tm && tm.tempdecl) ? tm.tempdecl.isTemplateDeclaration() : null;
865 1
    if (td && td.members)
866
    {
867 1
        for (size_t i = 0; i < td.members.dim; i++)
868
        {
869 1
            Dsymbol sm = (*td.members)[i];
870 1
            TemplateMixin tmc = sm.isTemplateMixin();
871 1
            if (tmc && tmc.comment)
872 1
                expandTemplateMixinComments(tmc, buf, sc);
873
            else
874 1
                emitComment(sm, buf, sc);
875
        }
876
    }
877
}
878

879
private void emitMemberComments(ScopeDsymbol sds, ref OutBuffer buf, Scope* sc)
880
{
881 1
    if (!sds.members)
882 1
        return;
883
    //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars());
884 1
    const(char)[] m = "$(DDOC_MEMBERS ";
885 1
    if (sds.isTemplateDeclaration())
886 1
        m = "$(DDOC_TEMPLATE_MEMBERS ";
887 1
    else if (sds.isClassDeclaration())
888 1
        m = "$(DDOC_CLASS_MEMBERS ";
889 1
    else if (sds.isStructDeclaration())
890 1
        m = "$(DDOC_STRUCT_MEMBERS ";
891 1
    else if (sds.isEnumDeclaration())
892 1
        m = "$(DDOC_ENUM_MEMBERS ";
893 1
    else if (sds.isModule())
894 1
        m = "$(DDOC_MODULE_MEMBERS ";
895 1
    size_t offset1 = buf.length; // save starting offset
896 1
    buf.writestring(m);
897 1
    size_t offset2 = buf.length; // to see if we write anything
898 1
    sc = sc.push(sds);
899 1
    for (size_t i = 0; i < sds.members.dim; i++)
900
    {
901 1
        Dsymbol s = (*sds.members)[i];
902
        //printf("\ts = '%s'\n", s.toChars());
903
        // only expand if parent is a non-template (semantic won't work)
904 1
        if (s.comment && s.isTemplateMixin() && s.parent && !s.parent.isTemplateDeclaration())
905 1
            expandTemplateMixinComments(cast(TemplateMixin)s, buf, sc);
906 1
        emitComment(s, buf, sc);
907
    }
908 1
    emitComment(null, buf, sc);
909 1
    sc.pop();
910 1
    if (buf.length == offset2)
911
    {
912
        /* Didn't write out any members, so back out last write
913
         */
914 1
        buf.setsize(offset1);
915
    }
916
    else
917 1
        buf.writestring(")");
918
}
919

920
private void emitProtection(ref OutBuffer buf, Import i)
921
{
922
    // imports are private by default, which is different from other declarations
923
    // so they should explicitly show their protection
924 1
    emitProtection(buf, i.protection);
925
}
926

927
private void emitProtection(ref OutBuffer buf, Declaration d)
928
{
929 1
    auto prot = d.protection;
930 1
    if (prot.kind != Prot.Kind.undefined && prot.kind != Prot.Kind.public_)
931
    {
932 1
        emitProtection(buf, prot);
933
    }
934
}
935

936
private void emitProtection(ref OutBuffer buf, Prot prot)
937
{
938 1
    protectionToBuffer(&buf, prot);
939 1
    buf.writeByte(' ');
940
}
941

942
private void emitComment(Dsymbol s, ref OutBuffer buf, Scope* sc)
943
{
944
    extern (C++) final class EmitComment : Visitor
945
    {
946
        alias visit = Visitor.visit;
947
    public:
948
        OutBuffer* buf;
949
        Scope* sc;
950

951 1
        extern (D) this(ref OutBuffer buf, Scope* sc)
952
        {
953 1
            this.buf = &buf;
954 1
            this.sc = sc;
955
        }
956

957
        override void visit(Dsymbol)
958
        {
959
        }
960

961
        override void visit(InvariantDeclaration)
962
        {
963
        }
964

965
        override void visit(UnitTestDeclaration)
966
        {
967
        }
968

969
        override void visit(PostBlitDeclaration)
970
        {
971
        }
972

973
        override void visit(DtorDeclaration)
974
        {
975
        }
976

977
        override void visit(StaticCtorDeclaration)
978
        {
979
        }
980

981
        override void visit(StaticDtorDeclaration)
982
        {
983
        }
984

985
        override void visit(TypeInfoDeclaration)
986
        {
987
        }
988

989
        void emit(Scope* sc, Dsymbol s, const(char)* com)
990
        {
991 1
            if (s && sc.lastdc && isDitto(com))
992
            {
993 1
                sc.lastdc.a.push(s);
994 1
                return;
995
            }
996
            // Put previous doc comment if exists
997 1
            if (DocComment* dc = sc.lastdc)
998
            {
999 1
                assert(dc.a.dim > 0, "Expects at least one declaration for a" ~
1000
                    "documentation comment");
1001

1002 1
                auto symbol = dc.a[0];
1003

1004 1
                buf.writestring("$(DDOC_MEMBER");
1005 1
                buf.writestring("$(DDOC_MEMBER_HEADER");
1006 1
                emitAnchor(*buf, symbol, sc, true);
1007 1
                buf.writeByte(')');
1008

1009
                // Put the declaration signatures as the document 'title'
1010 1
                buf.writestring(ddoc_decl_s);
1011 1
                for (size_t i = 0; i < dc.a.dim; i++)
1012
                {
1013 1
                    Dsymbol sx = dc.a[i];
1014
                    // the added linebreaks in here make looking at multiple
1015
                    // signatures more appealing
1016 1
                    if (i == 0)
1017
                    {
1018 1
                        size_t o = buf.length;
1019 1
                        toDocBuffer(sx, *buf, sc);
1020 1
                        highlightCode(sc, sx, *buf, o);
1021 1
                        buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
1022 1
                        continue;
1023
                    }
1024 1
                    buf.writestring("$(DDOC_DITTO ");
1025
                    {
1026 1
                        size_t o = buf.length;
1027 1
                        toDocBuffer(sx, *buf, sc);
1028 1
                        highlightCode(sc, sx, *buf, o);
1029
                    }
1030 1
                    buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
1031 1
                    buf.writeByte(')');
1032
                }
1033 1
                buf.writestring(ddoc_decl_e);
1034
                // Put the ddoc comment as the document 'description'
1035 1
                buf.writestring(ddoc_decl_dd_s);
1036
                {
1037 1
                    dc.writeSections(sc, &dc.a, buf);
1038 1
                    if (ScopeDsymbol sds = dc.a[0].isScopeDsymbol())
1039 1
                        emitMemberComments(sds, *buf, sc);
1040
                }
1041 1
                buf.writestring(ddoc_decl_dd_e);
1042 1
                buf.writeByte(')');
1043
                //printf("buf.2 = [[%.*s]]\n", cast(int)(buf.length - o0), buf.data + o0);
1044
            }
1045 1
            if (s)
1046
            {
1047 1
                DocComment* dc = DocComment.parse(s, com);
1048 1
                dc.pmacrotable = &sc._module.macrotable;
1049 1
                sc.lastdc = dc;
1050
            }
1051
        }
1052

1053
        override void visit(Import imp)
1054
        {
1055 1
            if (imp.prot().kind != Prot.Kind.public_ && sc.protection.kind != Prot.Kind.export_)
1056 1
                return;
1057

1058 1
            if (imp.comment)
1059 1
                emit(sc, imp, imp.comment);
1060
        }
1061

1062
        override void visit(Declaration d)
1063
        {
1064
            //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d.toChars(), d.comment);
1065
            //printf("type = %p\n", d.type);
1066 1
            const(char)* com = d.comment;
1067 1
            if (TemplateDeclaration td = getEponymousParent(d))
1068
            {
1069 1
                if (isDitto(td.comment))
1070 1
                    com = td.comment;
1071
                else
1072 1
                    com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true);
1073
            }
1074
            else
1075
            {
1076 1
                if (!d.ident)
1077 0
                    return;
1078 1
                if (!d.type)
1079
                {
1080 1
                    if (!d.isCtorDeclaration() &&
1081 1
                        !d.isAliasDeclaration() &&
1082 1
                        !d.isVarDeclaration())
1083
                    {
1084 0
                        return;
1085
                    }
1086
                }
1087 1
                if (d.protection.kind == Prot.Kind.private_ || sc.protection.kind == Prot.Kind.private_)
1088 1
                    return;
1089
            }
1090 1
            if (!com)
1091 1
                return;
1092 1
            emit(sc, d, com);
1093
        }
1094

1095
        override void visit(AggregateDeclaration ad)
1096
        {
1097
            //printf("AggregateDeclaration::emitComment() '%s'\n", ad.toChars());
1098 1
            const(char)* com = ad.comment;
1099 1
            if (TemplateDeclaration td = getEponymousParent(ad))
1100
            {
1101 1
                if (isDitto(td.comment))
1102 1
                    com = td.comment;
1103
                else
1104 1
                    com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true);
1105
            }
1106
            else
1107
            {
1108 1
                if (ad.prot().kind == Prot.Kind.private_ || sc.protection.kind == Prot.Kind.private_)
1109 0
                    return;
1110 1
                if (!ad.comment)
1111 1
                    return;
1112
            }
1113 1
            if (!com)
1114 0
                return;
1115 1
            emit(sc, ad, com);
1116
        }
1117

1118
        override void visit(TemplateDeclaration td)
1119
        {
1120
            //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td.toChars(), td.kind());
1121 1
            if (td.prot().kind == Prot.Kind.private_ || sc.protection.kind == Prot.Kind.private_)
1122 0
                return;
1123 1
            if (!td.comment)
1124 1
                return;
1125 1
            if (Dsymbol ss = getEponymousMember(td))
1126
            {
1127 1
                ss.accept(this);
1128 1
                return;
1129
            }
1130 1
            emit(sc, td, td.comment);
1131
        }
1132

1133
        override void visit(EnumDeclaration ed)
1134
        {
1135 1
            if (ed.prot().kind == Prot.Kind.private_ || sc.protection.kind == Prot.Kind.private_)
1136 0
                return;
1137 1
            if (ed.isAnonymous() && ed.members)
1138
            {
1139 1
                for (size_t i = 0; i < ed.members.dim; i++)
1140
                {
1141 1
                    Dsymbol s = (*ed.members)[i];
1142 1
                    emitComment(s, *buf, sc);
1143
                }
1144 1
                return;
1145
            }
1146 1
            if (!ed.comment)
1147 0
                return;
1148 1
            if (ed.isAnonymous())
1149 0
                return;
1150 1
            emit(sc, ed, ed.comment);
1151
        }
1152

1153
        override void visit(EnumMember em)
1154
        {
1155
            //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em.toChars(), em.comment);
1156 1
            if (em.prot().kind == Prot.Kind.private_ || sc.protection.kind == Prot.Kind.private_)
1157 0
                return;
1158 1
            if (!em.comment)
1159 1
                return;
1160 1
            emit(sc, em, em.comment);
1161
        }
1162

1163
        override void visit(AttribDeclaration ad)
1164
        {
1165
            //printf("AttribDeclaration::emitComment(sc = %p)\n", sc);
1166
            /* A general problem with this,
1167
             * illustrated by https://issues.dlang.org/show_bug.cgi?id=2516
1168
             * is that attributes are not transmitted through to the underlying
1169
             * member declarations for template bodies, because semantic analysis
1170
             * is not done for template declaration bodies
1171
             * (only template instantiations).
1172
             * Hence, Ddoc omits attributes from template members.
1173
             */
1174 1
            Dsymbols* d = ad.include(null);
1175 1
            if (d)
1176
            {
1177 1
                for (size_t i = 0; i < d.dim; i++)
1178
                {
1179 1
                    Dsymbol s = (*d)[i];
1180
                    //printf("AttribDeclaration::emitComment %s\n", s.toChars());
1181 1
                    emitComment(s, *buf, sc);
1182
                }
1183
            }
1184
        }
1185

1186
        override void visit(ProtDeclaration pd)
1187
        {
1188 1
            if (pd.decl)
1189
            {
1190 1
                Scope* scx = sc;
1191 1
                sc = sc.copy();
1192 1
                sc.protection = pd.protection;
1193 1
                visit(cast(AttribDeclaration)pd);
1194 1
                scx.lastdc = sc.lastdc;
1195 1
                sc = sc.pop();
1196
            }
1197
        }
1198

1199
        override void visit(ConditionalDeclaration cd)
1200
        {
1201
            //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc);
1202 1
            if (cd.condition.inc != Include.notComputed)
1203
            {
1204 1
                visit(cast(AttribDeclaration)cd);
1205 1
                return;
1206
            }
1207
            /* If generating doc comment, be careful because if we're inside
1208
             * a template, then include(null) will fail.
1209
             */
1210 1
            Dsymbols* d = cd.decl ? cd.decl : cd.elsedecl;
1211 1
            for (size_t i = 0; i < d.dim; i++)
1212
            {
1213 1
                Dsymbol s = (*d)[i];
1214 1
                emitComment(s, *buf, sc);
1215
            }
1216
        }
1217
    }
1218

1219 1
    scope EmitComment v = new EmitComment(buf, sc);
1220 1
    if (!s)
1221 1
        v.emit(sc, null, null);
1222
    else
1223 1
        s.accept(v);
1224
}
1225

1226
private void toDocBuffer(Dsymbol s, ref OutBuffer buf, Scope* sc)
1227
{
1228
    extern (C++) final class ToDocBuffer : Visitor
1229
    {
1230
        alias visit = Visitor.visit;
1231
    public:
1232
        OutBuffer* buf;
1233
        Scope* sc;
1234

1235 1
        extern (D) this(ref OutBuffer buf, Scope* sc)
1236
        {
1237 1
            this.buf = &buf;
1238 1
            this.sc = sc;
1239
        }
1240

1241
        override void visit(Dsymbol s)
1242
        {
1243
            //printf("Dsymbol::toDocbuffer() %s\n", s.toChars());
1244 1
            HdrGenState hgs;
1245 1
            hgs.ddoc = true;
1246 1
            .toCBuffer(s, buf, &hgs);
1247
        }
1248

1249
        void prefix(Dsymbol s)
1250
        {
1251 1
            if (s.isDeprecated())
1252 1
                buf.writestring("deprecated ");
1253 1
            if (Declaration d = s.isDeclaration())
1254
            {
1255 1
                emitProtection(*buf, d);
1256 1
                if (d.isStatic())
1257 1
                    buf.writestring("static ");
1258 1
                else if (d.isFinal())
1259 1
                    buf.writestring("final ");
1260 1
                else if (d.isAbstract())
1261 1
                    buf.writestring("abstract ");
1262

1263 1
                if (d.isFuncDeclaration())      // functionToBufferFull handles this
1264 1
                    return;
1265

1266 1
                if (d.isImmutable())
1267 1
                    buf.writestring("immutable ");
1268 1
                if (d.storage_class & STC.shared_)
1269 1
                    buf.writestring("shared ");
1270 1
                if (d.isWild())
1271 1
                    buf.writestring("inout ");
1272 1
                if (d.isConst())
1273 1
                    buf.writestring("const ");
1274

1275 1
                if (d.isSynchronized())
1276 0
                    buf.writestring("synchronized ");
1277

1278 1
                if (d.storage_class & STC.manifest)
1279 1
                    buf.writestring("enum ");
1280

1281
                // Add "auto" for the untyped variable in template members
1282 1
                if (!d.type && d.isVarDeclaration() &&
1283 1
                    !d.isImmutable() && !(d.storage_class & STC.shared_) && !d.isWild() && !d.isConst() &&
1284 1
                    !d.isSynchronized())
1285
                {
1286 1
                    buf.writestring("auto ");
1287
                }
1288
            }
1289
        }
1290

1291
        override void visit(Import i)
1292
        {
1293 1
            HdrGenState hgs;
1294 1
            hgs.ddoc = true;
1295 1
            emitProtection(*buf, i);
1296 1
            .toCBuffer(i, buf, &hgs);
1297
        }
1298

1299
        override void visit(Declaration d)
1300
        {
1301 1
            if (!d.ident)
1302 0
                return;
1303 1
            TemplateDeclaration td = getEponymousParent(d);
1304
            //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d.toChars(), d.originalType ? d.originalType.toChars() : "--", td ? td.toChars() : "--");
1305 1
            HdrGenState hgs;
1306 1
            hgs.ddoc = true;
1307 1
            if (d.isDeprecated())
1308 1
                buf.writestring("$(DEPRECATED ");
1309 1
            prefix(d);
1310 1
            if (d.type)
1311
            {
1312 1
                Type origType = d.originalType ? d.originalType : d.type;
1313 1
                if (origType.ty == Tfunction)
1314
                {
1315 1
                    functionToBufferFull(cast(TypeFunction)origType, buf, d.ident, &hgs, td);
1316
                }
1317
                else
1318 1
                    .toCBuffer(origType, buf, d.ident, &hgs);
1319
            }
1320
            else
1321 1
                buf.writestring(d.ident.toString());
1322 1
            if (d.isVarDeclaration() && td)
1323
            {
1324 1
                buf.writeByte('(');
1325 1
                if (td.origParameters && td.origParameters.dim)
1326
                {
1327 1
                    for (size_t i = 0; i < td.origParameters.dim; i++)
1328
                    {
1329 1
                        if (i)
1330 0
                            buf.writestring(", ");
1331 1
                        toCBuffer((*td.origParameters)[i], buf, &hgs);
1332
                    }
1333
                }
1334 1
                buf.writeByte(')');
1335
            }
1336
            // emit constraints if declaration is a templated declaration
1337 1
            if (td && td.constraint)
1338
            {
1339 1
                bool noFuncDecl = td.isFuncDeclaration() is null;
1340 1
                if (noFuncDecl)
1341
                {
1342 1
                    buf.writestring("$(DDOC_CONSTRAINT ");
1343
                }
1344

1345 1
                .toCBuffer(td.constraint, buf, &hgs);
1346

1347 1
                if (noFuncDecl)
1348
                {
1349 1
                    buf.writestring(")");
1350
                }
1351
            }
1352 1
            if (d.isDeprecated())
1353 1
                buf.writestring(")");
1354 1
            buf.writestring(";\n");
1355
        }
1356

1357
        override void visit(AliasDeclaration ad)
1358
        {
1359
            //printf("AliasDeclaration::toDocbuffer() %s\n", ad.toChars());
1360 1
            if (!ad.ident)
1361 0
                return;
1362 1
            if (ad.isDeprecated())
1363 1
                buf.writestring("deprecated ");
1364 1
            emitProtection(*buf, ad);
1365 1
            buf.printf("alias %s = ", ad.toChars());
1366 1
            if (Dsymbol s = ad.aliassym) // ident alias
1367
            {
1368 1
                prettyPrintDsymbol(s, ad.parent);
1369
            }
1370 1
            else if (Type type = ad.getType()) // type alias
1371
            {
1372 1
                if (type.ty == Tclass || type.ty == Tstruct || type.ty == Tenum)
1373
                {
1374 1
                    if (Dsymbol s = type.toDsymbol(null)) // elaborate type
1375 1
                        prettyPrintDsymbol(s, ad.parent);
1376
                    else
1377 0
                        buf.writestring(type.toChars());
1378
                }
1379
                else
1380
                {
1381
                    // simple type
1382 1
                    buf.writestring(type.toChars());
1383
                }
1384
            }
1385 1
            buf.writestring(";\n");
1386
        }
1387

1388
        void parentToBuffer(Dsymbol s)
1389
        {
1390 1
            if (s && !s.isPackage() && !s.isModule())
1391
            {
1392 1
                parentToBuffer(s.parent);
1393 1
                buf.writestring(s.toChars());
1394 1
                buf.writestring(".");
1395
            }
1396
        }
1397

1398
        static bool inSameModule(Dsymbol s, Dsymbol p)
1399
        {
1400 1
            for (; s; s = s.parent)
1401
            {
1402 1
                if (s.isModule())
1403 1
                    break;
1404
            }
1405 1
            for (; p; p = p.parent)
1406
            {
1407 1
                if (p.isModule())
1408 1
                    break;
1409
            }
1410 1
            return s == p;
1411
        }
1412

1413
        void prettyPrintDsymbol(Dsymbol s, Dsymbol parent)
1414
        {
1415 1
            if (s.parent && (s.parent == parent)) // in current scope -> naked name
1416
            {
1417 1
                buf.writestring(s.toChars());
1418
            }
1419 1
            else if (!inSameModule(s, parent)) // in another module -> full name
1420
            {
1421 1
                buf.writestring(s.toPrettyChars());
1422
            }
1423
            else // nested in a type in this module -> full name w/o module name
1424
            {
1425
                // if alias is nested in a user-type use module-scope lookup
1426 1
                if (!parent.isModule() && !parent.isPackage())
1427 1
                    buf.writestring(".");
1428 1
                parentToBuffer(s.parent);
1429 1
                buf.writestring(s.toChars());
1430
            }
1431
        }
1432

1433
        override void visit(AggregateDeclaration ad)
1434
        {
1435 0
            if (!ad.ident)
1436 0
                return;
1437
            version (none)
1438
            {
1439
                emitProtection(buf, ad);
1440
            }
1441 0
            buf.printf("%s %s", ad.kind(), ad.toChars());
1442 0
            buf.writestring(";\n");
1443
        }
1444

1445
        override void visit(StructDeclaration sd)
1446
        {
1447
            //printf("StructDeclaration::toDocbuffer() %s\n", sd.toChars());
1448 1
            if (!sd.ident)
1449 0
                return;
1450
            version (none)
1451
            {
1452
                emitProtection(buf, sd);
1453
            }
1454 1
            if (TemplateDeclaration td = getEponymousParent(sd))
1455
            {
1456 1
                toDocBuffer(td, *buf, sc);
1457
            }
1458
            else
1459
            {
1460 1
                buf.printf("%s %s", sd.kind(), sd.toChars());
1461
            }
1462 1
            buf.writestring(";\n");
1463
        }
1464

1465
        override void visit(ClassDeclaration cd)
1466
        {
1467
            //printf("ClassDeclaration::toDocbuffer() %s\n", cd.toChars());
1468 1
            if (!cd.ident)
1469 0
                return;
1470
            version (none)
1471
            {
1472
                emitProtection(*buf, cd);
1473
            }
1474 1
            if (TemplateDeclaration td = getEponymousParent(cd))
1475
            {
1476 1
                toDocBuffer(td, *buf, sc);
1477
            }
1478
            else
1479
            {
1480 1
                if (!cd.isInterfaceDeclaration() && cd.isAbstract())
1481 1
                    buf.writestring("abstract ");
1482 1
                buf.printf("%s %s", cd.kind(), cd.toChars());
1483
            }
1484 1
            int any = 0;
1485 1
            for (size_t i = 0; i < cd.baseclasses.dim; i++)
1486
            {
1487 1
                BaseClass* bc = (*cd.baseclasses)[i];
1488 1
                if (bc.sym && bc.sym.ident == Id.Object)
1489 1
                    continue;
1490 1
                if (any)
1491 1
                    buf.writestring(", ");
1492
                else
1493
                {
1494 1
                    buf.writestring(": ");
1495 1
                    any = 1;
1496
                }
1497

1498 1
                if (bc.sym)
1499
                {
1500 1
                    buf.printf("$(DDOC_PSUPER_SYMBOL %s)", bc.sym.toPrettyChars());
1501
                }
1502
                else
1503
                {
1504 1
                    HdrGenState hgs;
1505 1
                    .toCBuffer(bc.type, buf, null, &hgs);
1506
                }
1507
            }
1508 1
            buf.writestring(";\n");
1509
        }
1510

1511
        override void visit(EnumDeclaration ed)
1512
        {
1513 1
            if (!ed.ident)
1514 0
                return;
1515 1
            buf.printf("%s %s", ed.kind(), ed.toChars());
1516 1
            if (ed.memtype)
1517
            {
1518 1
                buf.writestring(": $(DDOC_ENUM_BASETYPE ");
1519 1
                HdrGenState hgs;
1520 1
                .toCBuffer(ed.memtype, buf, null, &hgs);
1521 1
                buf.writestring(")");
1522
            }
1523 1
            buf.writestring(";\n");
1524
        }
1525

1526
        override void visit(EnumMember em)
1527
        {
1528 1
            if (!em.ident)
1529 0
                return;
1530 1
            buf.writestring(em.toChars());
1531
        }
1532
    }
1533

1534 1
    scope ToDocBuffer v = new ToDocBuffer(buf, sc);
1535 1
    s.accept(v);
1536
}
1537

1538
/***********************************************************
1539
 */
1540
struct DocComment
1541
{
1542
    Sections sections;      // Section*[]
1543
    Section summary;
1544
    Section copyright;
1545
    Section macros;
1546
    MacroTable* pmacrotable;
1547
    Escape* escapetable;
1548
    Dsymbols a;
1549

1550
    static DocComment* parse(Dsymbol s, const(char)* comment)
1551
    {
1552
        //printf("parse(%s): '%s'\n", s.toChars(), comment);
1553 1
        auto dc = new DocComment();
1554 1
        dc.a.push(s);
1555 1
        if (!comment)
1556 1
            return dc;
1557 1
        dc.parseSections(comment);
1558 1
        for (size_t i = 0; i < dc.sections.dim; i++)
1559
        {
1560 1
            Section sec = dc.sections[i];
1561 1
            if (iequals("copyright", sec.name))
1562
            {
1563 1
                dc.copyright = sec;
1564
            }
1565 1
            if (iequals("macros", sec.name))
1566
            {
1567 1
                dc.macros = sec;
1568
            }
1569
        }
1570 1
        return dc;
1571
    }
1572

1573
    /************************************************
1574
     * Parse macros out of Macros: section.
1575
     * Macros are of the form:
1576
     *      name1 = value1
1577
     *
1578
     *      name2 = value2
1579
     */
1580
    extern(D) static void parseMacros(
1581
        Escape* escapetable, ref MacroTable pmacrotable, const(char)[] m)
1582
    {
1583 1
        const(char)* p = m.ptr;
1584 1
        size_t len = m.length;
1585 1
        const(char)* pend = p + len;
1586 1
        const(char)* tempstart = null;
1587 1
        size_t templen = 0;
1588 1
        const(char)* namestart = null;
1589 1
        size_t namelen = 0; // !=0 if line continuation
1590 1
        const(char)* textstart = null;
1591 1
        size_t textlen = 0;
1592 1
        while (p < pend)
1593
        {
1594
            // Skip to start of macro
1595 1
            while (1)
1596
            {
1597 1
                if (p >= pend)
1598 0
                    goto Ldone;
1599 1
                switch (*p)
1600
                {
1601 1
                case ' ':
1602 1
                case '\t':
1603 1
                    p++;
1604 1
                    continue;
1605 0
                case '\r':
1606 1
                case '\n':
1607 1
                    p++;
1608 1
                    goto Lcont;
1609 1
                default:
1610 1
                    if (isIdStart(p))
1611 1
                        break;
1612 1
                    if (namelen)
1613 1
                        goto Ltext; // continuation of prev macro
1614 0
                    goto Lskipline;
1615
                }
1616 1
                break;
1617
            }
1618 1
            tempstart = p;
1619 1
            while (1)
1620
            {
1621 1
                if (p >= pend)
1622 0
                    goto Ldone;
1623 1
                if (!isIdTail(p))
1624 1
                    break;
1625 1
                p += utfStride(p);
1626
            }
1627 1
            templen = p - tempstart;
1628 1
            while (1)
1629
            {
1630 1
                if (p >= pend)
1631 0
                    goto Ldone;
1632 1
                if (!(*p == ' ' || *p == '\t'))
1633 1
                    break;
1634 1
                p++;
1635
            }
1636 1
            if (*p != '=')
1637
            {
1638 1
                if (namelen)
1639 1
                    goto Ltext; // continuation of prev macro
1640 0
                goto Lskipline;
1641
            }
1642 1
            p++;
1643 1
            if (p >= pend)
1644 0
                goto Ldone;
1645 1
            if (namelen)
1646
            {
1647
                // Output existing macro
1648
            L1:
1649
                //printf("macro '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart);
1650 1
                if (iequals("ESCAPES", namestart[0 .. namelen]))
1651 1
                    parseEscapes(escapetable, textstart[0 .. textlen]);
1652
                else
1653 1
                    pmacrotable.define(namestart[0 .. namelen], textstart[0 .. textlen]);
1654 1
                namelen = 0;
1655 1
                if (p >= pend)
1656 1
                    break;
1657
            }
1658 1
            namestart = tempstart;
1659 1
            namelen = templen;
1660 1
            while (p < pend && (*p == ' ' || *p == '\t'))
1661 1
                p++;
1662 1
            textstart = p;
1663
        Ltext:
1664 1
            while (p < pend && *p != '\r' && *p != '\n')
1665 1
                p++;
1666 1
            textlen = p - textstart;
1667 1
            p++;
1668
            //printf("p = %p, pend = %p\n", p, pend);
1669
        Lcont:
1670 1
            continue;
1671
        Lskipline:
1672
            // Ignore this line
1673 0
            while (p < pend && *p != '\r' && *p != '\n')
1674 0
                p++;
1675
        }
1676
    Ldone:
1677 1
        if (namelen)
1678 1
            goto L1; // write out last one
1679
    }
1680

1681
    /**************************************
1682
     * Parse escapes of the form:
1683
     *      /c/string/
1684
     * where c is a single character.
1685
     * Multiple escapes can be separated
1686
     * by whitespace and/or commas.
1687
     */
1688
    static void parseEscapes(Escape* escapetable, const(char)[] text)
1689
    {
1690 1
        if (!escapetable)
1691
        {
1692 1
            escapetable = new Escape();
1693 1
            memset(escapetable, 0, Escape.sizeof);
1694
        }
1695
        //printf("parseEscapes('%.*s') pescapetable = %p\n", cast(int)text.length, text.ptr, escapetable);
1696 1
        const(char)* p = text.ptr;
1697 1
        const(char)* pend = p + text.length;
1698 1
        while (1)
1699
        {
1700 1
            while (1)
1701
            {
1702 1
                if (p + 4 >= pend)
1703 1
                    return;
1704 1
                if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ','))
1705 1
                    break;
1706 1
                p++;
1707
            }
1708 1
            if (p[0] != '/' || p[2] != '/')
1709 0
                return;
1710 1
            char c = p[1];
1711 1
            p += 3;
1712 1
            const(char)* start = p;
1713 1
            while (1)
1714
            {
1715 1
                if (p >= pend)
1716 0
                    return;
1717 1
                if (*p == '/')
1718 1
                    break;
1719 1
                p++;
1720
            }
1721 1
            size_t len = p - start;
1722 1
            char* s = cast(char*)memcpy(mem.xmalloc(len + 1), start, len);
1723 1
            s[len] = 0;
1724 1
            escapetable.strings[c] = s[0 .. len];
1725
            //printf("\t%c = '%s'\n", c, s);
1726 1
            p++;
1727
        }
1728
    }
1729

1730
    /*****************************************
1731
     * Parse next paragraph out of *pcomment.
1732
     * Update *pcomment to point past paragraph.
1733
     * Returns NULL if no more paragraphs.
1734
     * If paragraph ends in 'identifier:',
1735
     * then (*pcomment)[0 .. idlen] is the identifier.
1736
     */
1737
    void parseSections(const(char)* comment)
1738
    {
1739 1
        const(char)* p;
1740 1
        const(char)* pstart;
1741 1
        const(char)* pend;
1742 1
        const(char)* idstart = null; // dead-store to prevent spurious warning
1743 1
        size_t idlen;
1744 1
        const(char)* name = null;
1745 1
        size_t namelen = 0;
1746
        //printf("parseSections('%s')\n", comment);
1747 1
        p = comment;
1748 1
        while (*p)
1749
        {
1750 1
            const(char)* pstart0 = p;
1751 1
            p = skipwhitespace(p);
1752 1
            pstart = p;
1753 1
            pend = p;
1754

1755
            // Undo indent if starting with a list item
1756 1
            if ((*p == '-' || *p == '+' || *p == '*') && (*(p+1) == ' ' || *(p+1) == '\t'))
1757 1
                pstart = pstart0;
1758
            else
1759
            {
1760 1
                const(char)* pitem = p;
1761 1
                while (*pitem >= '0' && *pitem <= '9')
1762 1
                    ++pitem;
1763 1
                if (pitem > p && *pitem == '.' && (*(pitem+1) == ' ' || *(pitem+1) == '\t'))
1764 0
                    pstart = pstart0;
1765
            }
1766

1767
            /* Find end of section, which is ended by one of:
1768
             *      'identifier:' (but not inside a code section)
1769
             *      '\0'
1770
             */
1771 1
            idlen = 0;
1772 1
            int inCode = 0;
1773 1
            while (1)
1774
            {
1775
                // Check for start/end of a code section
1776 1
                if (*p == '-' || *p == '`' || *p == '~')
1777
                {
1778 1
                    char c = *p;
1779 1
                    int numdash = 0;
1780 1
                    while (*p == c)
1781
                    {
1782 1
                        ++numdash;
1783 1
                        p++;
1784
                    }
1785
                    // BUG: handle UTF PS and LS too
1786 1
                    if ((!*p || *p == '\r' || *p == '\n' || (!inCode && c != '-')) && numdash >= 3)
1787
                    {
1788 1
                        inCode = inCode == c ? false : c;
1789 1
                        if (inCode)
1790
                        {
1791
                            // restore leading indentation
1792 1
                            while (pstart0 < pstart && isIndentWS(pstart - 1))
1793 1
                                --pstart;
1794
                        }
1795
                    }
1796 1
                    pend = p;
1797
                }
1798 1
                if (!inCode && isIdStart(p))
1799
                {
1800 1
                    const(char)* q = p + utfStride(p);
1801 1
                    while (isIdTail(q))
1802 1
                        q += utfStride(q);
1803

1804
                    // Detected tag ends it
1805 1
                    if (*q == ':' && isupper(*p)
1806 1
                            && (isspace(q[1]) || q[1] == 0))
1807
                    {
1808 1
                        idlen = q - p;
1809 1
                        idstart = p;
1810 1
                        for (pend = p; pend > pstart; pend--)
1811
                        {
1812 1
                            if (pend[-1] == '\n')
1813 1
                                break;
1814
                        }
1815 1
                        p = q + 1;
1816 1
                        break;
1817
                    }
1818
                }
1819 1
                while (1)
1820
                {
1821 1
                    if (!*p)
1822 1
                        goto L1;
1823 1
                    if (*p == '\n')
1824
                    {
1825 1
                        p++;
1826 1
                        if (*p == '\n' && !summary && !namelen && !inCode)
1827
                        {
1828 1
                            pend = p;
1829 1
                            p++;
1830 1
                            goto L1;
1831
                        }
1832 1
                        break;
1833
                    }
1834 1
                    p++;
1835 1
                    pend = p;
1836
                }
1837 1
                p = skipwhitespace(p);
1838
            }
1839
        L1:
1840 1
            if (namelen || pstart < pend)
1841
            {
1842 1
                Section s;
1843 1
                if (iequals("Params", name[0 .. namelen]))
1844 1
                    s = new ParamSection();
1845 1
                else if (iequals("Macros", name[0 .. namelen]))
1846 1
                    s = new MacroSection();
1847
                else
1848 1
                    s = new Section();
1849 1
                s.name = name[0 .. namelen];
1850 1
                s.body_ = pstart[0 .. pend - pstart];
1851 1
                s.nooutput = 0;
1852
                //printf("Section: '%.*s' = '%.*s'\n", cast(int)s.namelen, s.name, cast(int)s.bodylen, s.body);
1853 1
                sections.push(s);
1854 1
                if (!summary && !namelen)
1855 1
                    summary = s;
1856
            }
1857 1
            if (idlen)
1858
            {
1859 1
                name = idstart;
1860 1
                namelen = idlen;
1861
            }
1862
            else
1863
            {
1864 1
                name = null;
1865 1
                namelen = 0;
1866 1
                if (!*p)
1867 1
                    break;
1868
            }
1869
        }
1870
    }
1871

1872
    void writeSections(Scope* sc, Dsymbols* a, OutBuffer* buf)
1873
    {
1874 1
        assert(a.dim);
1875
        //printf("DocComment::writeSections()\n");
1876 1
        Loc loc = (*a)[0].loc;
1877 1
        if (Module m = (*a)[0].isModule())
1878
        {
1879 1
            if (m.md)
1880 1
                loc = m.md.loc;
1881
        }
1882 1
        size_t offset1 = buf.length;
1883 1
        buf.writestring("$(DDOC_SECTIONS ");
1884 1
        size_t offset2 = buf.length;
1885 1
        for (size_t i = 0; i < sections.dim; i++)
1886
        {
1887 1
            Section sec = sections[i];
1888 1
            if (sec.nooutput)
1889 1
                continue;
1890
            //printf("Section: '%.*s' = '%.*s'\n", cast(int)sec.namelen, sec.name, cast(int)sec.bodylen, sec.body);
1891 1
            if (!sec.name.length && i == 0)
1892
            {
1893 1
                buf.writestring("$(DDOC_SUMMARY ");
1894 1
                size_t o = buf.length;
1895 1
                buf.write(sec.body_);
1896 1
                escapeStrayParenthesis(loc, buf, o, true);
1897 1
                highlightText(sc, a, loc, *buf, o);
1898 1
                buf.writestring(")");
1899
            }
1900
            else
1901 1
                sec.write(loc, &this, sc, a, buf);
1902
        }
1903 1
        for (size_t i = 0; i < a.dim; i++)
1904
        {
1905 1
            Dsymbol s = (*a)[i];
1906 1
            if (Dsymbol td = getEponymousParent(s))
1907 1
                s = td;
1908 1
            for (UnitTestDeclaration utd = s.ddocUnittest; utd; utd = utd.ddocUnittest)
1909
            {
1910 1
                if (utd.protection.kind == Prot.Kind.private_ || !utd.comment || !utd.fbody)
1911 1
                    continue;
1912
                // Strip whitespaces to avoid showing empty summary
1913 1
                const(char)* c = utd.comment;
1914 1
                while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')
1915 1
                    ++c;
1916 1
                buf.writestring("$(DDOC_EXAMPLES ");
1917 1
                size_t o = buf.length;
1918 1
                buf.writestring(cast(char*)c);
1919 1
                if (utd.codedoc)
1920
                {
1921 1
                    auto codedoc = utd.codedoc.stripLeadingNewlines;
1922 1
                    size_t n = getCodeIndent(codedoc);
1923 1
                    while (n--)
1924 1
                        buf.writeByte(' ');
1925 1
                    buf.writestring("----\n");
1926 1
                    buf.writestring(codedoc);
1927 1
                    buf.writestring("----\n");
1928 1
                    highlightText(sc, a, loc, *buf, o);
1929
                }
1930 1
                buf.writestring(")");
1931
            }
1932
        }
1933 1
        if (buf.length == offset2)
1934
        {
1935
            /* Didn't write out any sections, so back out last write
1936
             */
1937 1
            buf.setsize(offset1);
1938 1
            buf.writestring("\n");
1939
        }
1940
        else
1941 1
            buf.writestring(")");
1942
    }
1943
}
1944

1945
/*****************************************
1946
 * Return true if comment consists entirely of "ditto".
1947
 */
1948
private bool isDitto(const(char)* comment)
1949
{
1950 1
    if (comment)
1951
    {
1952 1
        const(char)* p = skipwhitespace(comment);
1953 1
        if (Port.memicmp(p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
1954 1
            return true;
1955
    }
1956 1
    return false;
1957
}
1958

1959
/**********************************************
1960
 * Skip white space.
1961
 */
1962
private const(char)* skipwhitespace(const(char)* p)
1963
{
1964 1
    return skipwhitespace(p.toDString).ptr;
1965
}
1966

1967
/// Ditto
1968
private const(char)[] skipwhitespace(const(char)[] p)
1969
{
1970 1
    foreach (idx, char c; p)
1971
    {
1972 1
        switch (c)
1973
        {
1974 1
        case ' ':
1975 1
        case '\t':
1976 1
        case '\n':
1977 1
            continue;
1978 1
        default:
1979 1
            return p[idx .. $];
1980
        }
1981
    }
1982 1
    return p[$ .. $];
1983
}
1984

1985
/************************************************
1986
 * Scan past all instances of the given characters.
1987
 * Params:
1988
 *  buf           = an OutBuffer containing the DDoc
1989
 *  i             = the index within `buf` to start scanning from
1990
 *  chars         = the characters to skip; order is unimportant
1991
 * Returns: the index after skipping characters.
1992
 */
1993
private size_t skipChars(ref OutBuffer buf, size_t i, string chars)
1994
{
1995
    Outer:
1996 1
    foreach (j, c; buf[][i..$])
1997
    {
1998 1
        foreach (d; chars)
1999
        {
2000 1
            if (d == c)
2001 1
                continue Outer;
2002
        }
2003 1
        return i + j;
2004
    }
2005 1
    return buf.length;
2006
}
2007

2008
unittest {
2009 1
    OutBuffer buf;
2010 1
    string data = "test ---\r\n\r\nend";
2011 1
    buf.write(data);
2012

2013 1
    assert(skipChars(buf, 0, "-") == 0);
2014 1
    assert(skipChars(buf, 4, "-") == 4);
2015 1
    assert(skipChars(buf, 4, " -") == 8);
2016 1
    assert(skipChars(buf, 8, "\r\n") == 12);
2017 1
    assert(skipChars(buf, 12, "dne") == 15);
2018
}
2019

2020
/****************************************************
2021
 * Replace all instances of `c` with `r` in the given string
2022
 * Params:
2023
 *  s = the string to do replacements in
2024
 *  c = the character to look for
2025
 *  r = the string to replace `c` with
2026
 * Returns: `s` with `c` replaced with `r`
2027
 */
2028
private inout(char)[] replaceChar(inout(char)[] s, char c, string r) pure
2029
{
2030 1
    int count = 0;
2031 1
    foreach (char sc; s)
2032 1
        if (sc == c)
2033 1
            ++count;
2034 1
    if (count == 0)
2035 1
        return s;
2036

2037 1
    char[] result;
2038 1
    result.reserve(s.length - count + (r.length * count));
2039 1
    size_t start = 0;
2040 1
    foreach (i, char sc; s)
2041
    {
2042 1
        if (sc == c)
2043
        {
2044 1
            result ~= s[start..i];
2045 1
            result ~= r;
2046 1
            start = i+1;
2047
        }
2048
    }
2049 1
    result ~= s[start..$];
2050 1
    return result;
2051
}
2052

2053
///
2054
unittest
2055
{
2056 1
    assert("".replaceChar(',', "$(COMMA)") == "");
2057 1
    assert("ab".replaceChar(',', "$(COMMA)") == "ab");
2058 1
    assert("a,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)b");
2059 1
    assert("a,,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)$(COMMA)b");
2060 1
    assert(",ab".replaceChar(',', "$(COMMA)") == "$(COMMA)ab");
2061 1
    assert("ab,".replaceChar(',', "$(COMMA)") == "ab$(COMMA)");
2062
}
2063

2064
/**
2065
 * Return a lowercased copy of a string.
2066
 * Params:
2067
 *  s = the string to lowercase
2068
 * Returns: the lowercase version of the string or the original if already lowercase
2069
 */
2070
private string toLowercase(string s) pure
2071
{
2072 1
    string lower;
2073 1
    foreach (size_t i; 0..s.length)
2074
    {
2075 1
        char c = s[i];
2076
// TODO: maybe unicode lowercase, somehow
2077 1
        if (c >= 'A' && c <= 'Z')
2078
        {
2079 1
            if (!lower.length) {
2080 1
                lower.reserve(s.length);
2081
            }
2082 1
            lower ~= s[lower.length..i];
2083 1
            c += 'a' - 'A';
2084 1
            lower ~= c;
2085
        }
2086
    }
2087 1
    if (lower.length)
2088 1
        lower ~= s[lower.length..$];
2089
    else
2090 1
        lower = s;
2091 1
    return lower;
2092
}
2093

2094
///
2095
unittest
2096
{
2097 1
    assert("".toLowercase == "");
2098 1
    assert("abc".toLowercase == "abc");
2099 1
    assert("ABC".toLowercase == "abc");
2100 1
    assert("aBc".toLowercase == "abc");
2101
}
2102

2103
/************************************************
2104
 * Get the indent from one index to another, counting tab stops as four spaces wide
2105
 * per the Markdown spec.
2106
 * Params:
2107
 *  buf   = an OutBuffer containing the DDoc
2108
 *  from  = the index within `buf` to start counting from, inclusive
2109
 *  to    = the index within `buf` to stop counting at, exclusive
2110
 * Returns: the indent
2111
 */
2112
private int getMarkdownIndent(ref OutBuffer buf, size_t from, size_t to)
2113
{
2114 1
    const slice = buf[];
2115 1
    if (to > slice.length)
2116 0
        to = slice.length;
2117 1
    int indent = 0;
2118 1
    foreach (const c; slice[from..to])
2119 1
        indent += (c == '\t') ? 4 - (indent % 4) : 1;
2120 1
    return indent;
2121
}
2122

2123
/************************************************
2124
 * Scan forward to one of:
2125
 *      start of identifier
2126
 *      beginning of next line
2127
 *      end of buf
2128
 */
2129
size_t skiptoident(ref OutBuffer buf, size_t i)
2130
{
2131 0
    const slice = buf[];
2132 0
    while (i < slice.length)
2133
    {
2134 0
        dchar c;
2135 0
        size_t oi = i;
2136 0
        if (utf_decodeChar(slice, i, c))
2137
        {
2138
            /* Ignore UTF errors, but still consume input
2139
             */
2140 0
            break;
2141
        }
2142 0
        if (c >= 0x80)
2143
        {
2144 0
            if (!isUniAlpha(c))
2145 0
                continue;
2146
        }
2147 0
        else if (!(isalpha(c) || c == '_' || c == '\n'))
2148 0
            continue;
2149 0
        i = oi;
2150 0
        break;
2151
    }
2152 0
    return i;
2153
}
2154

2155
/************************************************
2156
 * Scan forward past end of identifier.
2157
 */
2158
private size_t skippastident(ref OutBuffer buf, size_t i)
2159
{
2160 1
    const slice = buf[];
2161 1
    while (i < slice.length)
2162
    {
2163 1
        dchar c;
2164 1
        size_t oi = i;
2165 1
        if (utf_decodeChar(slice, i, c))
2166
        {
2167
            /* Ignore UTF errors, but still consume input
2168
             */
2169 0
            break;
2170
        }
2171 1
        if (c >= 0x80)
2172
        {
2173 1
            if (isUniAlpha(c))
2174 1
                continue;
2175
        }
2176 1
        else if (isalnum(c) || c == '_')
2177 1
            continue;
2178 1
        i = oi;
2179 1
        break;
2180
    }
2181 1
    return i;
2182
}
2183

2184
/************************************************
2185
 * Scan forward past end of an identifier that might
2186
 * contain dots (e.g. `abc.def`)
2187
 */
2188
private size_t skipPastIdentWithDots(ref OutBuffer buf, size_t i)
2189
{
2190 1
    const slice = buf[];
2191 1
    bool lastCharWasDot;
2192 1
    while (i < slice.length)
2193
    {
2194 1
        dchar c;
2195 1
        size_t oi = i;
2196 1
        if (utf_decodeChar(slice, i, c))
2197
        {
2198
            /* Ignore UTF errors, but still consume input
2199
             */
2200 0
            break;
2201
        }
2202 1
        if (c == '.')
2203
        {
2204
            // We need to distinguish between `abc.def`, abc..def`, and `abc.`
2205
            // Only `abc.def` is a valid identifier
2206

2207 1
            if (lastCharWasDot)
2208
            {
2209 1
                i = oi;
2210 1
                break;
2211
            }
2212

2213 1
            lastCharWasDot = true;
2214 1
            continue;
2215
        }
2216
        else
2217
        {
2218 1
            if (c >= 0x80)
2219
            {
2220 1
                if (isUniAlpha(c))
2221
                {
2222 1
                    lastCharWasDot = false;
2223 1
                    continue;
2224
                }
2225
            }
2226 1
            else if (isalnum(c) || c == '_')
2227
            {
2228 1
                lastCharWasDot = false;
2229 1
                continue;
2230
            }
2231 1
            i = oi;
2232 1
            break;
2233
        }
2234
    }
2235

2236
    // if `abc.`
2237 1
    if (lastCharWasDot)
2238 1
        return i - 1;
2239

2240 1
    return i;
2241
}
2242

2243
/************************************************
2244
 * Scan forward past URL starting at i.
2245
 * We don't want to highlight parts of a URL.
2246
 * Returns:
2247
 *      i if not a URL
2248
 *      index just past it if it is a URL
2249
 */
2250
private size_t skippastURL(ref OutBuffer buf, size_t i)
2251
{
2252 1
    const slice = buf[][i .. $];
2253 1
    size_t j;
2254 1
    bool sawdot = false;
2255 1
    if (slice.length > 7 && Port.memicmp(slice.ptr, "http://", 7) == 0)
2256
    {
2257 1
        j = 7;
2258
    }
2259 1
    else if (slice.length > 8 && Port.memicmp(slice.ptr, "https://", 8) == 0)
2260
    {
2261 1
        j = 8;
2262
    }
2263
    else
2264 1
        goto Lno;
2265 1
    for (; j < slice.length; j++)
2266
    {
2267 1
        const c = slice[j];
2268 1
        if (isalnum(c))
2269 1
            continue;
2270 1
        if (c == '-' || c == '_' || c == '?' || c == '=' || c == '%' ||
2271 1
            c == '&' || c == '/' || c == '+' || c == '#' || c == '~')
2272 1
            continue;
2273 1
        if (c == '.')
2274
        {
2275 1
            sawdot = true;
2276 1
            continue;
2277
        }
2278 1
        break;
2279
    }
2280 1
    if (sawdot)
2281 1
        return i + j;
2282
Lno:
2283 1
    return i;
2284
}
2285

2286
/****************************************************
2287
 * Remove a previously-inserted blank line macro.
2288
 * Params:
2289
 *  buf           = an OutBuffer containing the DDoc
2290
 *  iAt           = the index within `buf` of the start of the `$(DDOC_BLANKLINE)`
2291
 *                  macro. Upon function return its value is set to `0`.
2292
 *  i             = an index within `buf`. If `i` is after `iAt` then it gets
2293
 *                  reduced by the length of the removed macro.
2294
 */
2295
private void removeBlankLineMacro(ref OutBuffer buf, ref size_t iAt, ref size_t i)
2296
{
2297 1
    if (!iAt)
2298 1
        return;
2299

2300
    enum macroLength = "$(DDOC_BLANKLINE)".length;
2301 1
    buf.remove(iAt, macroLength);
2302 1
    if (i > iAt)
2303 1
        i -= macroLength;
2304 1
    iAt = 0;
2305
}
2306

2307
/****************************************************
2308
 * Attempt to detect and replace a Markdown thematic break (HR). These are three
2309
 * or more of the same delimiter, optionally with spaces or tabs between any of
2310
 * them, e.g. `\n- - -\n` becomes `\n$(HR)\n`
2311
 * Params:
2312
 *  buf         = an OutBuffer containing the DDoc
2313
 *  i           = the index within `buf` of the first character of a potential
2314
 *                thematic break. If the replacement is made `i` changes to
2315
 *                point to the closing parenthesis of the `$(HR)` macro.
2316
 *  iLineStart  = the index within `buf` that the thematic break's line starts at
2317
 *  loc         = the current location within the file
2318
 * Returns: whether a thematic break was replaced
2319
 */
2320
private bool replaceMarkdownThematicBreak(ref OutBuffer buf, ref size_t i, size_t iLineStart, const ref Loc loc)
2321
{
2322 1
    if (!global.params.markdown)
2323 0
        return false;
2324

2325 1
    const slice = buf[];
2326 1
    const c = buf[i];
2327 1
    size_t j = i + 1;
2328 1
    int repeat = 1;
2329 1
    for (; j < slice.length; j++)
2330
    {
2331 1
        if (buf[j] == c)
2332 1
            ++repeat;
2333 1
        else if (buf[j] != ' ' && buf[j] != '\t')
2334 1
            break;
2335
    }
2336 1
    if (repeat >= 3)
2337
    {
2338 1
        if (j >= buf.length || buf[j] == '\n' || buf[j] == '\r')
2339
        {
2340 1
            if (global.params.vmarkdown)
2341
            {
2342 1
                const s = buf[][i..j];
2343 1
                message(loc, "Ddoc: converted '%.*s' to a thematic break", cast(int)s.length, s.ptr);
2344
            }
2345

2346 1
            buf.remove(iLineStart, j - iLineStart);
2347 1
            i = buf.insert(iLineStart, "$(HR)") - 1;
2348 1
            return true;
2349
        }
2350
    }
2351 1
    return false;
2352
}
2353

2354
/****************************************************
2355
 * Detect the level of an ATX-style heading, e.g. `## This is a heading` would
2356
 * have a level of `2`.
2357
 * Params:
2358
 *  buf   = an OutBuffer containing the DDoc
2359
 *  i     = the index within `buf` of the first `#` character
2360
 * Returns:
2361
 *          the detected heading level from 1 to 6, or
2362
 *          0 if not at an ATX heading
2363
 */
2364
private int detectAtxHeadingLevel(ref OutBuffer buf, const size_t i)
2365
{
2366 1
    if (!global.params.markdown)
2367 0
        return 0;
2368

2369 1
    const iHeadingStart = i;
2370 1
    const iAfterHashes = skipChars(buf, i, "#");
2371 1
    const headingLevel = cast(int) (iAfterHashes - iHeadingStart);
2372 1
    if (headingLevel > 6)
2373 1
        return 0;
2374

2375 1
    const iTextStart = skipChars(buf, iAfterHashes, " \t");
2376 1
    const emptyHeading = buf[iTextStart] == '\r' || buf[iTextStart] == '\n';
2377

2378
    // require whitespace
2379 1
    if (!emptyHeading && iTextStart == iAfterHashes)
2380 1
        return 0;
2381

2382 1
    return headingLevel;
2383
}
2384

2385
/****************************************************
2386
 * Remove any trailing `##` suffix from an ATX-style heading.
2387
 * Params:
2388
 *  buf   = an OutBuffer containing the DDoc
2389
 *  i     = the index within `buf` to start looking for a suffix at
2390
 */
2391
private void removeAnyAtxHeadingSuffix(ref OutBuffer buf, size_t i)
2392
{
2393 1
    size_t j = i;
2394 1
    size_t iSuffixStart = 0;
2395 1
    size_t iWhitespaceStart = j;
2396 1
    const slice = buf[];
2397 1
    for (; j < slice.length; j++)
2398
    {
2399 1
        switch (slice[j])
2400
        {
2401 1
        case '#':
2402 1
            if (iWhitespaceStart && !iSuffixStart)
2403 1
                iSuffixStart = j;
2404 1
            continue;
2405 1
        case ' ':
2406 1
        case '\t':
2407 1
            if (!iWhitespaceStart)
2408 1
                iWhitespaceStart = j;
2409 1
            continue;
2410 0
        case '\r':
2411 1
        case '\n':
2412 1
            break;
2413 1
        default:
2414 1
            iSuffixStart = 0;
2415 1
            iWhitespaceStart = 0;
2416 1
            continue;
2417
        }
2418 1
        break;
2419
    }
2420 1
    if (iSuffixStart)
2421 1
        buf.remove(iWhitespaceStart, j - iWhitespaceStart);
2422
}
2423

2424
/****************************************************
2425
 * Wrap text in a Markdown heading macro, e.g. `$(H2 heading text`).
2426
 * Params:
2427
 *  buf           = an OutBuffer containing the DDoc
2428
 *  iStart        = the index within `buf` that the Markdown heading starts at
2429
 *  iEnd          = the index within `buf` of the character after the last
2430
 *                  heading character. Is incremented by the length of the
2431
 *                  inserted heading macro when this function ends.
2432
 *  loc           = the location of the Ddoc within the file
2433
 *  headingLevel  = the level (1-6) of heading to end. Is set to `0` when this
2434
 *                  function ends.
2435
 */
2436
private void endMarkdownHeading(ref OutBuffer buf, size_t iStart, ref size_t iEnd, const ref Loc loc, ref int headingLevel)
2437
{
2438 1
    if (!global.params.markdown)
2439 0
        return;
2440 1
    if (global.params.vmarkdown)
2441
    {
2442 1
        const s = buf[][iStart..iEnd];
2443 1
        message(loc, "Ddoc: added heading '%.*s'", cast(int)s.length, s.ptr);
2444
    }
2445

2446 1
    char[5] heading = "$(H0 ";
2447 1
    heading[3] = cast(char) ('0' + headingLevel);
2448 1
    buf.insert(iStart, heading);
2449 1
    iEnd += 5;
2450 1
    size_t iBeforeNewline = iEnd;
2451 1
    while (buf[iBeforeNewline-1] == '\r' || buf[iBeforeNewline-1] == '\n')
2452 0
        --iBeforeNewline;
2453 1
    buf.insert(iBeforeNewline, ")");
2454 1
    headingLevel = 0;
2455
}
2456

2457
/****************************************************
2458
 * End all nested Markdown quotes, if inside any.
2459
 * Params:
2460
 *  buf         = an OutBuffer containing the DDoc
2461
 *  i           = the index within `buf` of the character after the quote text.
2462
 *  quoteLevel  = the current quote level. Is set to `0` when this function ends.
2463
 * Returns: the amount that `i` was moved
2464
 */
2465
private size_t endAllMarkdownQuotes(ref OutBuffer buf, size_t i, ref int quoteLevel)
2466
{
2467 1
    const length = quoteLevel;
2468 1
    for (; quoteLevel > 0; --quoteLevel)
2469 1
        i = buf.insert(i, ")");
2470 1
    return length;
2471
}
2472

2473
/****************************************************
2474
 * Convenience function to end all Markdown lists and quotes, if inside any, and
2475
 * set `quoteMacroLevel` to `0`.
2476
 * Params:
2477
 *  buf         = an OutBuffer containing the DDoc
2478
 *  i           = the index within `buf` of the character after the list and/or
2479
 *                quote text. Is adjusted when this function ends if any lists
2480
 *                and/or quotes were ended.
2481
 *  nestedLists = a set of nested lists. Upon return it will be empty.
2482
 *  quoteLevel  = the current quote level. Is set to `0` when this function ends.
2483
 *  quoteMacroLevel   = the macro level that the quote was started at. Is set to
2484
 *                      `0` when this function ends.
2485
 * Returns: the amount that `i` was moved
2486
 */
2487
private size_t endAllListsAndQuotes(ref OutBuffer buf, ref size_t i, ref MarkdownList[] nestedLists, ref int quoteLevel, out int quoteMacroLevel)
2488
{
2489 1
    quoteMacroLevel = 0;
2490 1
    const i0 = i;
2491 1
    i += MarkdownList.endAllNestedLists(buf, i, nestedLists);
2492 1
    i += endAllMarkdownQuotes(buf, i, quoteLevel);
2493 1
    return i - i0;
2494
}
2495

2496
/****************************************************
2497
 * Replace Markdown emphasis with the appropriate macro,
2498
 * e.g. `*very* **nice**` becomes `$(EM very) $(STRONG nice)`.
2499
 * Params:
2500
 *  buf               = an OutBuffer containing the DDoc
2501
 *  loc               = the current location within the file
2502
 *  inlineDelimiters  = the collection of delimiters found within a paragraph. When this function returns its length will be reduced to `downToLevel`.
2503
 *  downToLevel       = the length within `inlineDelimiters`` to reduce emphasis to
2504
 * Returns: the number of characters added to the buffer by the replacements
2505
 */
2506
private size_t replaceMarkdownEmphasis(ref OutBuffer buf, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, int downToLevel = 0)
2507
{
2508 1
    if (!global.params.markdown)
2509 0
        return 0;
2510

2511
    size_t replaceEmphasisPair(ref MarkdownDelimiter start, ref MarkdownDelimiter end)
2512
    {
2513 1
        immutable count = start.count == 1 || end.count == 1 ? 1 : 2;
2514

2515 1
        size_t iStart = start.iStart;
2516 1
        size_t iEnd = end.iStart;
2517 1
        end.count -= count;
2518 1
        start.count -= count;
2519 1
        iStart += start.count;
2520

2521 1
        if (!start.count)
2522 1
            start.type = 0;
2523 1
        if (!end.count)
2524 1
            end.type = 0;
2525

2526 1
        if (global.params.vmarkdown)
2527
        {
2528 1
            const s = buf[][iStart + count..iEnd];
2529 1
            message(loc, "Ddoc: emphasized text '%.*s'", cast(int)s.length, s.ptr);
2530
        }
2531

2532 1
        buf.remove(iStart, count);
2533 1
        iEnd -= count;
2534 1
        buf.remove(iEnd, count);
2535

2536 1
        string macroName = count >= 2 ? "$(STRONG " : "$(EM ";
2537 1
        buf.insert(iEnd, ")");
2538 1
        buf.insert(iStart, macroName);
2539

2540 1
        const delta = 1 + macroName.length - (count + count);
2541 1
        end.iStart += count;
2542 1
        return delta;
2543
    }
2544

2545 1
    size_t delta = 0;
2546 1
    int start = (cast(int) inlineDelimiters.length) - 1;
2547 1
    while (start >= downToLevel)
2548
    {
2549
        // find start emphasis
2550 1
        while (start >= downToLevel &&
2551 1
            (inlineDelimiters[start].type != '*' || !inlineDelimiters[start].leftFlanking))
2552 1
            --start;
2553 1
        if (start < downToLevel)
2554 1
            break;
2555

2556
        // find the nearest end emphasis
2557 1
        int end = start + 1;
2558 1
        while (end < inlineDelimiters.length &&
2559 1
            (inlineDelimiters[end].type != inlineDelimiters[start].type ||
2560 1
                inlineDelimiters[end].macroLevel != inlineDelimiters[start].macroLevel ||
2561 1
                !inlineDelimiters[end].rightFlanking))
2562 1
            ++end;
2563 1
        if (end == inlineDelimiters.length)
2564
        {
2565
            // the start emphasis has no matching end; if it isn't an end itself then kill it
2566 1
            if (!inlineDelimiters[start].rightFlanking)
2567 1
                inlineDelimiters[start].type = 0;
2568 1
            --start;
2569 1
            continue;
2570
        }
2571

2572
        // multiple-of-3 rule
2573 1
        if (((inlineDelimiters[start].leftFlanking && inlineDelimiters[start].rightFlanking) ||
2574 1
                (inlineDelimiters[end].leftFlanking && inlineDelimiters[end].rightFlanking)) &&
2575 1
            (inlineDelimiters[start].count + inlineDelimiters[end].count) % 3 == 0)
2576
        {
2577 1
            --start;
2578 1
            continue;
2579
        }
2580

2581 1
        immutable delta0 = replaceEmphasisPair(inlineDelimiters[start], inlineDelimiters[end]);
2582

2583 1
        for (; end < inlineDelimiters.length; ++end)
2584 1
            inlineDelimiters[end].iStart += delta0;
2585 1
        delta += delta0;
2586
    }
2587

2588 1
    inlineDelimiters.length = downToLevel;
2589 1
    return delta;
2590
}
2591

2592
/****************************************************
2593
 */
2594
private bool isIdentifier(Dsymbols* a, const(char)* p, size_t len)
2595
{
2596 1
    foreach (member; *a)
2597
    {
2598 1
        if (auto imp = member.isImport())
2599
        {
2600
            // For example: `public import str = core.stdc.string;`
2601
            // This checks if `p` is equal to `str`
2602 1
            if (imp.aliasId)
2603
            {
2604 1
                if (p[0 .. len] == imp.aliasId.toString())
2605 1
                    return true;
2606
            }
2607
            else
2608
            {
2609
                // The general case:  `public import core.stdc.string;`
2610

2611
                // fully qualify imports so `core.stdc.string` doesn't appear as `core`
2612 1
                string fullyQualifiedImport;
2613 1
                if (imp.packages && imp.packages.dim)
2614
                {
2615 1
                    foreach (const pid; *imp.packages)
2616
                    {
2617 1
                        fullyQualifiedImport ~= pid.toString() ~ ".";
2618
                    }
2619
                }
2620 1
                fullyQualifiedImport ~= imp.id.toString();
2621

2622
                // Check if `p` == `core.stdc.string`
2623 1
                if (p[0 .. len] == fullyQualifiedImport)
2624 1
                    return true;
2625
            }
2626
        }
2627 1
        else if (member.ident)
2628
        {
2629 1
            if (p[0 .. len] == member.ident.toString())
2630 1
                return true;
2631
        }
2632

2633
    }
2634 1
    return false;
2635
}
2636

2637
/****************************************************
2638
 */
2639
private bool isKeyword(const(char)* p, size_t len)
2640
{
2641 1
    immutable string[3] table = ["true", "false", "null"];
2642 1
    foreach (s; table)
2643
    {
2644 1
        if (p[0 .. len] == s)
2645 1
            return true;
2646
    }
2647 1
    return false;
2648
}
2649

2650
/****************************************************
2651
 */
2652
private TypeFunction isTypeFunction(Dsymbol s)
2653
{
2654 1
    FuncDeclaration f = s.isFuncDeclaration();
2655
    /* f.type may be NULL for template members.
2656
     */
2657 1
    if (f && f.type)
2658
    {
2659 1
        Type t = f.originalType ? f.originalType : f.type;
2660 1
        if (t.ty == Tfunction)
2661 1
            return cast(TypeFunction)t;
2662
    }
2663 1
    return null;
2664
}
2665

2666
/****************************************************
2667
 */
2668
private Parameter isFunctionParameter(Dsymbol s, const(char)* p, size_t len)
2669
{
2670 1
    TypeFunction tf = isTypeFunction(s);
2671 1
    if (tf && tf.parameterList.parameters)
2672
    {
2673 1
        foreach (fparam; *tf.parameterList.parameters)
2674
        {
2675 1
            if (fparam.ident && p[0 .. len] == fparam.ident.toString())
2676
            {
2677 1
                return fparam;
2678
            }
2679
        }
2680
    }
2681 1
    return null;
2682
}
2683

2684
/****************************************************
2685
 */
2686
private Parameter isFunctionParameter(Dsymbols* a, const(char)* p, size_t len)
2687
{
2688 1
    for (size_t i = 0; i < a.dim; i++)
2689
    {
2690 1
        Parameter fparam = isFunctionParameter((*a)[i], p, len);
2691 1
        if (fparam)
2692
        {
2693 1
            return fparam;
2694
        }
2695
    }
2696 1
    return null;
2697
}
2698

2699
/****************************************************
2700
 */
2701
private Parameter isEponymousFunctionParameter(Dsymbols *a, const(char) *p, size_t len)
2702
{
2703 1
    for (size_t i = 0; i < a.dim; i++)
2704
    {
2705 1
        TemplateDeclaration td = (*a)[i].isTemplateDeclaration();
2706 1
        if (td && td.onemember)
2707
        {
2708
            /* Case 1: we refer to a template declaration inside the template
2709

2710
               /// ...ddoc...
2711
               template case1(T) {
2712
                 void case1(R)() {}
2713
               }
2714
             */
2715 1
            td = td.onemember.isTemplateDeclaration();
2716
        }
2717 1
        if (!td)
2718
        {
2719
            /* Case 2: we're an alias to a template declaration
2720

2721
               /// ...ddoc...
2722
               alias case2 = case1!int;
2723
             */
2724 1
            AliasDeclaration ad = (*a)[i].isAliasDeclaration();
2725 1
            if (ad && ad.aliassym)
2726
            {
2727 1
                td = ad.aliassym.isTemplateDeclaration();
2728
            }
2729
        }
2730 1
        while (td)
2731
        {
2732 1
            Dsymbol sym = getEponymousMember(td);
2733 1
            if (sym)
2734
            {
2735 1
                Parameter fparam = isFunctionParameter(sym, p, len);
2736 1
                if (fparam)
2737
                {
2738 1
                    return fparam;
2739
                }
2740
            }
2741 1
            td = td.overnext;
2742
        }
2743
    }
2744 1
    return null;
2745
}
2746

2747
/****************************************************
2748
 */
2749
private TemplateParameter isTemplateParameter(Dsymbols* a, const(char)* p, size_t len)
2750
{
2751 1
    for (size_t i = 0; i < a.dim; i++)
2752
    {
2753 1
        TemplateDeclaration td = (*a)[i].isTemplateDeclaration();
2754
        // Check for the parent, if the current symbol is not a template declaration.
2755 1
        if (!td)
2756 1
            td = getEponymousParent((*a)[i]);
2757 1
        if (td && td.origParameters)
2758
        {
2759 1
            foreach (tp; *td.origParameters)
2760
            {
2761 1
                if (tp.ident && p[0 .. len] == tp.ident.toString())
2762
                {
2763 1
                    return tp;
2764
                }
2765
            }
2766
        }
2767
    }
2768 1
    return null;
2769
}
2770

2771
/****************************************************
2772
 * Return true if str is a reserved symbol name
2773
 * that starts with a double underscore.
2774
 */
2775
private bool isReservedName(const(char)[] str)
2776
{
2777 1
    immutable string[] table =
2778
    [
2779
        "__ctor",
2780
        "__dtor",
2781
        "__postblit",
2782
        "__invariant",
2783
        "__unitTest",
2784
        "__require",
2785
        "__ensure",
2786
        "__dollar",
2787
        "__ctfe",
2788
        "__withSym",
2789
        "__result",
2790
        "__returnLabel",
2791
        "__vptr",
2792
        "__monitor",
2793
        "__gate",
2794
        "__xopEquals",
2795
        "__xopCmp",
2796
        "__LINE__",
2797
        "__FILE__",
2798
        "__MODULE__",
2799
        "__FUNCTION__",
2800
        "__PRETTY_FUNCTION__",
2801
        "__DATE__",
2802
        "__TIME__",
2803
        "__TIMESTAMP__",
2804
        "__VENDOR__",
2805
        "__VERSION__",
2806
        "__EOF__",
2807
        "__CXXLIB__",
2808
        "__LOCAL_SIZE",
2809
        "__entrypoint",
2810
    ];
2811 1
    foreach (s; table)
2812
    {
2813 1
        if (str == s)
2814 1
            return true;
2815
    }
2816 1
    return false;
2817
}
2818

2819
/****************************************************
2820
 * A delimiter for Markdown inline content like emphasis and links.
2821
 */
2822
private struct MarkdownDelimiter
2823
{
2824
    size_t iStart;  /// the index where this delimiter starts
2825
    int count;      /// the length of this delimeter's start sequence
2826
    int macroLevel; /// the count of nested DDoc macros when the delimiter is started
2827
    bool leftFlanking;  /// whether the delimiter is left-flanking, as defined by the CommonMark spec
2828
    bool rightFlanking; /// whether the delimiter is right-flanking, as defined by the CommonMark spec
2829
    bool atParagraphStart;  /// whether the delimiter is at the start of a paragraph
2830
    char type;      /// the type of delimiter, defined by its starting character
2831

2832
    /// whether this describes a valid delimiter
2833 1
    @property bool isValid() const { return count != 0; }
2834

2835
    /// flag this delimiter as invalid
2836 0
    void invalidate() { count = 0; }
2837
}
2838

2839
/****************************************************
2840
 * Info about a Markdown list.
2841
 */
2842
private struct MarkdownList
2843
{
2844
    string orderedStart;    /// an optional start number--if present then the list starts at this number
2845
    size_t iStart;          /// the index where the list item starts
2846
    size_t iContentStart;   /// the index where the content starts after the list delimiter
2847
    int delimiterIndent;    /// the level of indent the list delimiter starts at
2848
    int contentIndent;      /// the level of indent the content starts at
2849
    int macroLevel;         /// the count of nested DDoc macros when the list is started
2850
    char type;              /// the type of list, defined by its starting character
2851

2852
    /// whether this describes a valid list
2853 1
    @property bool isValid() const { return type != type.init; }
2854

2855
    /****************************************************
2856
     * Try to parse a list item, returning whether successful.
2857
     * Params:
2858
     *  buf           = an OutBuffer containing the DDoc
2859
     *  iLineStart    = the index within `buf` of the first character of the line
2860
     *  i             = the index within `buf` of the potential list item
2861
     * Returns: the parsed list item. Its `isValid` property describes whether parsing succeeded.
2862
     */
2863
    static MarkdownList parseItem(ref OutBuffer buf, size_t iLineStart, size_t i)
2864
    {
2865 1
        if (!global.params.markdown)
2866 0
            return MarkdownList();
2867

2868 1
        if (buf[i] == '+' || buf[i] == '-' || buf[i] == '*')
2869 1
            return parseUnorderedListItem(buf, iLineStart, i);
2870
        else
2871 1
            return parseOrderedListItem(buf, iLineStart, i);
2872
    }
2873

2874
    /****************************************************
2875
     * Return whether the context is at a list item of the same type as this list.
2876
     * Params:
2877
     *  buf           = an OutBuffer containing the DDoc
2878
     *  iLineStart    = the index within `buf` of the first character of the line
2879
     *  i             = the index within `buf` of the list item
2880
     * Returns: whether `i` is at a list item of the same type as this list
2881
     */
2882
    private bool isAtItemInThisList(ref OutBuffer buf, size_t iLineStart, size_t i)
2883
    {
2884 1
        MarkdownList item = (type == '.' || type == ')') ?
2885 1
            parseOrderedListItem(buf, iLineStart, i) :
2886 1
            parseUnorderedListItem(buf, iLineStart, i);
2887 1
        if (item.type == type)
2888 1
            return item.delimiterIndent < contentIndent && item.contentIndent > delimiterIndent;
2889 1
        return false;
2890
    }
2891

2892
    /****************************************************
2893
     * Start a Markdown list item by creating/deleting nested lists and starting the item.
2894
     * Params:
2895
     *  buf           = an OutBuffer containing the DDoc
2896
     *  iLineStart    = the index within `buf` of the first character of the line. If this function succeeds it will be adjuested to equal `i`.
2897
     *  i             = the index within `buf` of the list item. If this function succeeds `i` will be adjusted to fit the inserted macro.
2898
     *  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`.
2899
     *  nestedLists   = a set of nested lists. If this function succeeds it may contain a new nested list.
2900
     *  loc           = the location of the Ddoc within the file
2901
     * Returns: `true` if a list was created
2902
     */
2903
    bool startItem(ref OutBuffer buf, ref size_t iLineStart, ref size_t i, ref size_t iPrecedingBlankLine, ref MarkdownList[] nestedLists, const ref Loc loc)
2904
    {
2905 1
        buf.remove(iStart, iContentStart - iStart);
2906

2907 1
        if (!nestedLists.length ||
2908 1
            delimiterIndent >= nestedLists[$-1].contentIndent ||
2909 1
            buf[iLineStart - 4..iLineStart] == "$(LI")
2910
        {
2911
            // start a list macro
2912 1
            nestedLists ~= this;
2913 1
            if (type == '.')
2914
            {
2915 1
                if (orderedStart.length)
2916
                {
2917 1
                    iStart = buf.insert(iStart, "$(OL_START ");
2918 1
                    iStart = buf.insert(iStart, orderedStart);
2919 1 <