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