]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/d/dmd/doc.c
d: Merge upstream dmd 73d8e2fec.
[thirdparty/gcc.git] / gcc / d / dmd / doc.c
1
2 /* Compiler implementation of the D programming language
3 * Copyright (C) 1999-2020 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
89 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).
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 */
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
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 */
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
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
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.data, mbuf.offset);
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.offset, buf.data);
431 Macro::define(&m->macrotable, (const utf8_t *)"BODY", 4, (const utf8_t *)buf.data, buf.offset);
432
433 OutBuffer buf2;
434 buf2.writestring("$(DDOC)\n");
435 size_t end = buf2.offset;
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.offset);
444 utf8_t *p = (utf8_t *)buf2.data;
445 for (size_t j = 0; j < buf2.offset; j++)
446 {
447 utf8_t c = p[j];
448 if (c == 0xFF && j + 1 < buf2.offset)
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.offset && 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.data, buf.offset);
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 */
483 void escapeDdocString(OutBuffer *buf, size_t start)
484 {
485 for (size_t u = start; u < buf->offset; u++)
486 {
487 utf8_t c = buf->data[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 */
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->offset; u++)
522 {
523 utf8_t c = buf->data[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->offset; u > start;)
550 {
551 u--;
552 utf8_t c = buf->data[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.
578 static Scope *skipNonQualScopes(Scope *sc)
579 {
580 while (sc && !sc->scopesym)
581 sc = sc->enclosing;
582 return sc;
583 }
584
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
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. */
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. */
664 static void expandTemplateMixinComments(TemplateMixin *tm, OutBuffer *buf, Scope *sc)
665 {
666 if (!tm->semanticRun) tm->semantic(sc);
667 TemplateDeclaration *td = (tm && tm->tempdecl) ?
668 tm->tempdecl->isTemplateDeclaration() : NULL;
669 if (td && td->members)
670 {
671 for (size_t i = 0; i < td->members->length; i++)
672 {
673 Dsymbol *sm = (*td->members)[i];
674 TemplateMixin *tmc = sm->isTemplateMixin();
675 if (tmc && tmc->comment)
676 expandTemplateMixinComments(tmc, buf, sc);
677 else
678 emitComment(sm, buf, sc);
679 }
680 }
681 }
682
683 void emitMemberComments(ScopeDsymbol *sds, OutBuffer *buf, Scope *sc)
684 {
685 if (!sds->members)
686 return;
687
688 //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars());
689
690 const char *m = "$(DDOC_MEMBERS ";
691 if (sds->isTemplateDeclaration())
692 m = "$(DDOC_TEMPLATE_MEMBERS ";
693 else if (sds->isClassDeclaration())
694 m = "$(DDOC_CLASS_MEMBERS ";
695 else if (sds->isStructDeclaration())
696 m = "$(DDOC_STRUCT_MEMBERS ";
697 else if (sds->isEnumDeclaration())
698 m = "$(DDOC_ENUM_MEMBERS ";
699 else if (sds->isModule())
700 m = "$(DDOC_MODULE_MEMBERS ";
701
702 size_t offset1 = buf->offset; // save starting offset
703 buf->writestring(m);
704 size_t offset2 = buf->offset; // to see if we write anything
705
706 sc = sc->push(sds);
707
708 for (size_t i = 0; i < sds->members->length; i++)
709 {
710 Dsymbol *s = (*sds->members)[i];
711 //printf("\ts = '%s'\n", s->toChars());
712
713 // only expand if parent is a non-template (semantic won't work)
714 if (s->comment && s->isTemplateMixin() && s->parent && !s->parent->isTemplateDeclaration())
715 expandTemplateMixinComments((TemplateMixin *)s, buf, sc);
716
717 emitComment(s, buf, sc);
718 }
719 emitComment(NULL, buf, sc);
720
721 sc->pop();
722
723 if (buf->offset == offset2)
724 {
725 /* Didn't write out any members, so back out last write
726 */
727 buf->offset = offset1;
728 }
729 else
730 buf->writestring(")\n");
731 }
732
733 void emitProtection(OutBuffer *buf, Prot prot)
734 {
735 if (prot.kind != Prot::undefined && prot.kind != Prot::public_)
736 {
737 protectionToBuffer(buf, prot);
738 buf->writeByte(' ');
739 }
740 }
741
742 void emitComment(Dsymbol *s, OutBuffer *buf, Scope *sc)
743 {
744 class EmitComment : public Visitor
745 {
746 public:
747 OutBuffer *buf;
748 Scope *sc;
749
750 EmitComment(OutBuffer *buf, Scope *sc)
751 : buf(buf), sc(sc)
752 {
753 }
754
755 void visit(Dsymbol *) {}
756 void visit(InvariantDeclaration *) {}
757 void visit(UnitTestDeclaration *) {}
758 void visit(PostBlitDeclaration *) {}
759 void visit(DtorDeclaration *) {}
760 void visit(StaticCtorDeclaration *) {}
761 void visit(StaticDtorDeclaration *) {}
762 void visit(TypeInfoDeclaration *) {}
763
764 void emit(Scope *sc, Dsymbol *s, const utf8_t *com)
765 {
766 if (s && sc->lastdc && isDitto(com))
767 {
768 sc->lastdc->a.push(s);
769 return;
770 }
771
772 // Put previous doc comment if exists
773 if (DocComment *dc = sc->lastdc)
774 {
775 // Put the declaration signatures as the document 'title'
776 buf->writestring(ddoc_decl_s);
777 for (size_t i = 0; i < dc->a.length; i++)
778 {
779 Dsymbol *sx = dc->a[i];
780
781 if (i == 0)
782 {
783 size_t o = buf->offset;
784 toDocBuffer(sx, buf, sc);
785 highlightCode(sc, sx, buf, o);
786 continue;
787 }
788
789 buf->writestring("$(DDOC_DITTO ");
790 {
791 size_t o = buf->offset;
792 toDocBuffer(sx, buf, sc);
793 highlightCode(sc, sx, buf, o);
794 }
795 buf->writeByte(')');
796 }
797 buf->writestring(ddoc_decl_e);
798
799 // Put the ddoc comment as the document 'description'
800 buf->writestring(ddoc_decl_dd_s);
801 {
802 dc->writeSections(sc, &dc->a, buf);
803 if (ScopeDsymbol *sds = dc->a[0]->isScopeDsymbol())
804 emitMemberComments(sds, buf, sc);
805 }
806 buf->writestring(ddoc_decl_dd_e);
807 //printf("buf.2 = [[%.*s]]\n", buf->offset - o0, buf->data + o0);
808 }
809
810 if (s)
811 {
812 DocComment *dc = DocComment::parse(s, com);
813 dc->pmacrotable = &sc->_module->macrotable;
814 sc->lastdc = dc;
815 }
816 }
817
818 void visit(Declaration *d)
819 {
820 //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d->toChars(), d->comment);
821 //printf("type = %p\n", d->type);
822 const utf8_t *com = d->comment;
823 if (TemplateDeclaration *td = getEponymousParent(d))
824 {
825 if (isDitto(td->comment))
826 com = td->comment;
827 else
828 com = Lexer::combineComments(td->comment, com);
829 }
830 else
831 {
832 if (!d->ident)
833 return;
834 if (!d->type && !d->isCtorDeclaration() && !d->isAliasDeclaration())
835 return;
836 if (d->protection.kind == Prot::private_ || sc->protection.kind == Prot::private_)
837 return;
838 }
839 if (!com)
840 return;
841
842 emit(sc, d, com);
843 }
844
845 void visit(AggregateDeclaration *ad)
846 {
847 //printf("AggregateDeclaration::emitComment() '%s'\n", ad->toChars());
848 const utf8_t *com = ad->comment;
849 if (TemplateDeclaration *td = getEponymousParent(ad))
850 {
851 if (isDitto(td->comment))
852 com = td->comment;
853 else
854 com = Lexer::combineComments(td->comment, com);
855 }
856 else
857 {
858 if (ad->prot().kind == Prot::private_ || sc->protection.kind == Prot::private_)
859 return;
860 if (!ad->comment)
861 return;
862 }
863 if (!com)
864 return;
865
866 emit(sc, ad, com);
867 }
868
869 void visit(TemplateDeclaration *td)
870 {
871 //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td->toChars(), td->kind());
872 if (td->prot().kind == Prot::private_ || sc->protection.kind == Prot::private_)
873 return;
874 if (!td->comment)
875 return;
876
877 if (Dsymbol *ss = getEponymousMember(td))
878 {
879 ss->accept(this);
880 return;
881 }
882 emit(sc, td, td->comment);
883 }
884
885 void visit(EnumDeclaration *ed)
886 {
887 if (ed->prot().kind == Prot::private_ || sc->protection.kind == Prot::private_)
888 return;
889 if (ed->isAnonymous() && ed->members)
890 {
891 for (size_t i = 0; i < ed->members->length; i++)
892 {
893 Dsymbol *s = (*ed->members)[i];
894 emitComment(s, buf, sc);
895 }
896 return;
897 }
898 if (!ed->comment)
899 return;
900 if (ed->isAnonymous())
901 return;
902
903 emit(sc, ed, ed->comment);
904 }
905
906 void visit(EnumMember *em)
907 {
908 //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em->toChars(), em->comment);
909 if (em->prot().kind == Prot::private_ || sc->protection.kind == Prot::private_)
910 return;
911 if (!em->comment)
912 return;
913
914 emit(sc, em, em->comment);
915 }
916
917 void visit(AttribDeclaration *ad)
918 {
919 //printf("AttribDeclaration::emitComment(sc = %p)\n", sc);
920
921 /* A general problem with this, illustrated by BUGZILLA 2516,
922 * is that attributes are not transmitted through to the underlying
923 * member declarations for template bodies, because semantic analysis
924 * is not done for template declaration bodies
925 * (only template instantiations).
926 * Hence, Ddoc omits attributes from template members.
927 */
928
929 Dsymbols *d = ad->include(NULL);
930
931 if (d)
932 {
933 for (size_t i = 0; i < d->length; i++)
934 {
935 Dsymbol *s = (*d)[i];
936 //printf("AttribDeclaration::emitComment %s\n", s->toChars());
937 emitComment(s, buf, sc);
938 }
939 }
940 }
941
942 void visit(ProtDeclaration *pd)
943 {
944 if (pd->decl)
945 {
946 Scope *scx = sc;
947 sc = sc->copy();
948 sc->protection = pd->protection;
949 visit((AttribDeclaration *)pd);
950 scx->lastdc = sc->lastdc;
951 sc = sc->pop();
952 }
953 }
954
955 void visit(ConditionalDeclaration *cd)
956 {
957 //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc);
958 if (cd->condition->inc)
959 {
960 visit((AttribDeclaration *)cd);
961 return;
962 }
963
964 /* If generating doc comment, be careful because if we're inside
965 * a template, then include(NULL) will fail.
966 */
967 Dsymbols *d = cd->decl ? cd->decl : cd->elsedecl;
968 for (size_t i = 0; i < d->length; i++)
969 {
970 Dsymbol *s = (*d)[i];
971 emitComment(s, buf, sc);
972 }
973 }
974 };
975
976 EmitComment v(buf, sc);
977
978 if (!s)
979 v.emit(sc, NULL, NULL);
980 else
981 s->accept(&v);
982 }
983
984 /******************************* toDocBuffer **********************************/
985
986 void toDocBuffer(Dsymbol *s, OutBuffer *buf, Scope *sc)
987 {
988 class ToDocBuffer : public Visitor
989 {
990 public:
991 OutBuffer *buf;
992 Scope *sc;
993
994 ToDocBuffer(OutBuffer *buf, Scope *sc)
995 : buf(buf), sc(sc)
996 {
997 }
998
999 void visit(Dsymbol *s)
1000 {
1001 //printf("Dsymbol::toDocbuffer() %s\n", s->toChars());
1002 HdrGenState hgs;
1003 hgs.ddoc = true;
1004 ::toCBuffer(s, buf, &hgs);
1005 }
1006
1007 void prefix(Dsymbol *s)
1008 {
1009 if (s->isDeprecated())
1010 buf->writestring("deprecated ");
1011
1012 if (Declaration *d = s->isDeclaration())
1013 {
1014 emitProtection(buf, d->protection);
1015
1016 if (d->isStatic())
1017 buf->writestring("static ");
1018 else if (d->isFinal())
1019 buf->writestring("final ");
1020 else if (d->isAbstract())
1021 buf->writestring("abstract ");
1022
1023 if (!d->isFuncDeclaration()) // functionToBufferFull handles this
1024 {
1025 if (d->isConst())
1026 buf->writestring("const ");
1027 if (d->isImmutable())
1028 buf->writestring("immutable ");
1029 if (d->isSynchronized())
1030 buf->writestring("synchronized ");
1031
1032 if (d->storage_class & STCmanifest)
1033 buf->writestring("enum ");
1034 }
1035 }
1036 }
1037
1038 void visit(Declaration *d)
1039 {
1040 if (!d->ident)
1041 return;
1042
1043 TemplateDeclaration *td = getEponymousParent(d);
1044 //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d->toChars(), d->originalType ? d->originalType->toChars() : "--", td ? td->toChars() : "--");
1045
1046 HdrGenState hgs;
1047 hgs.ddoc = true;
1048
1049 if (d->isDeprecated())
1050 buf->writestring("$(DEPRECATED ");
1051
1052 prefix(d);
1053
1054 if (d->type)
1055 {
1056 Type *origType = d->originalType ? d->originalType : d->type;
1057 if (origType->ty == Tfunction)
1058 {
1059 functionToBufferFull((TypeFunction *)origType, buf, d->ident, &hgs, td);
1060 }
1061 else
1062 ::toCBuffer(origType, buf, d->ident, &hgs);
1063 }
1064 else
1065 buf->writestring(d->ident->toChars());
1066
1067 if (d->isVarDeclaration() && td)
1068 {
1069 buf->writeByte('(');
1070 if (td->origParameters && td->origParameters->length)
1071 {
1072 for (size_t i = 0; i < td->origParameters->length; i++)
1073 {
1074 if (i)
1075 buf->writestring(", ");
1076 toCBuffer((*td->origParameters)[i], buf, &hgs);
1077 }
1078 }
1079 buf->writeByte(')');
1080 }
1081
1082 // emit constraints if declaration is a templated declaration
1083 if (td && td->constraint)
1084 {
1085 buf->writestring(" if (");
1086 ::toCBuffer(td->constraint, buf, &hgs);
1087 buf->writeByte(')');
1088 }
1089
1090 if (d->isDeprecated())
1091 buf->writestring(")");
1092
1093 buf->writestring(";\n");
1094 }
1095
1096 void visit(AliasDeclaration *ad)
1097 {
1098 //printf("AliasDeclaration::toDocbuffer() %s\n", ad->toChars());
1099 if (!ad->ident)
1100 return;
1101
1102 if (ad->isDeprecated())
1103 buf->writestring("deprecated ");
1104
1105 emitProtection(buf, ad->protection);
1106 buf->printf("alias %s = ", ad->toChars());
1107
1108 if (Dsymbol *s = ad->aliassym) // ident alias
1109 {
1110 prettyPrintDsymbol(s, ad->parent);
1111 }
1112 else if (Type *type = ad->getType()) // type alias
1113 {
1114 if (type->ty == Tclass || type->ty == Tstruct || type->ty == Tenum)
1115 {
1116 if (Dsymbol *s = type->toDsymbol(NULL)) // elaborate type
1117 prettyPrintDsymbol(s, ad->parent);
1118 else
1119 buf->writestring(type->toChars());
1120 }
1121 else
1122 {
1123 // simple type
1124 buf->writestring(type->toChars());
1125 }
1126 }
1127
1128 buf->writestring(";\n");
1129 }
1130
1131 void parentToBuffer(Dsymbol *s)
1132 {
1133 if (s && !s->isPackage() && !s->isModule())
1134 {
1135 parentToBuffer(s->parent);
1136 buf->writestring(s->toChars());
1137 buf->writestring(".");
1138 }
1139 }
1140
1141 static bool inSameModule(Dsymbol *s, Dsymbol *p)
1142 {
1143 for ( ; s ; s = s->parent)
1144 {
1145 if (s->isModule())
1146 break;
1147 }
1148
1149 for ( ; p ; p = p->parent)
1150 {
1151 if (p->isModule())
1152 break;
1153 }
1154
1155 return s == p;
1156 }
1157
1158 void prettyPrintDsymbol(Dsymbol *s, Dsymbol *parent)
1159 {
1160 if (s->parent && (s->parent == parent)) // in current scope -> naked name
1161 {
1162 buf->writestring(s->toChars());
1163 }
1164 else if (!inSameModule(s, parent)) // in another module -> full name
1165 {
1166 buf->writestring(s->toPrettyChars());
1167 }
1168 else // nested in a type in this module -> full name w/o module name
1169 {
1170 // if alias is nested in a user-type use module-scope lookup
1171 if (!parent->isModule() && !parent->isPackage())
1172 buf->writestring(".");
1173
1174 parentToBuffer(s->parent);
1175 buf->writestring(s->toChars());
1176 }
1177 }
1178
1179 void visit(AggregateDeclaration *ad)
1180 {
1181 if (!ad->ident)
1182 return;
1183
1184 buf->printf("%s %s", ad->kind(), ad->toChars());
1185 buf->writestring(";\n");
1186 }
1187
1188 void visit(StructDeclaration *sd)
1189 {
1190 //printf("StructDeclaration::toDocbuffer() %s\n", sd->toChars());
1191 if (!sd->ident)
1192 return;
1193
1194 if (TemplateDeclaration *td = getEponymousParent(sd))
1195 {
1196 toDocBuffer(td, buf, sc);
1197 }
1198 else
1199 {
1200 buf->printf("%s %s", sd->kind(), sd->toChars());
1201 }
1202 buf->writestring(";\n");
1203 }
1204
1205 void visit(ClassDeclaration *cd)
1206 {
1207 //printf("ClassDeclaration::toDocbuffer() %s\n", cd->toChars());
1208 if (!cd->ident)
1209 return;
1210
1211 if (TemplateDeclaration *td = getEponymousParent(cd))
1212 {
1213 toDocBuffer(td, buf, sc);
1214 }
1215 else
1216 {
1217 if (!cd->isInterfaceDeclaration() && cd->isAbstract())
1218 buf->writestring("abstract ");
1219 buf->printf("%s %s", cd->kind(), cd->toChars());
1220 }
1221 int any = 0;
1222 for (size_t i = 0; i < cd->baseclasses->length; i++)
1223 {
1224 BaseClass *bc = (*cd->baseclasses)[i];
1225
1226 if (bc->sym && bc->sym->ident == Id::Object)
1227 continue;
1228
1229 if (any)
1230 buf->writestring(", ");
1231 else
1232 {
1233 buf->writestring(": ");
1234 any = 1;
1235 }
1236 emitProtection(buf, Prot(Prot::public_));
1237 if (bc->sym)
1238 {
1239 buf->printf("$(DDOC_PSUPER_SYMBOL %s)", bc->sym->toPrettyChars());
1240 }
1241 else
1242 {
1243 HdrGenState hgs;
1244 ::toCBuffer(bc->type, buf, NULL, &hgs);
1245 }
1246 }
1247 buf->writestring(";\n");
1248 }
1249
1250 void visit(EnumDeclaration *ed)
1251 {
1252 if (!ed->ident)
1253 return;
1254
1255 buf->printf("%s %s", ed->kind(), ed->toChars());
1256 if (ed->memtype)
1257 {
1258 buf->writestring(": $(DDOC_ENUM_BASETYPE ");
1259 HdrGenState hgs;
1260 ::toCBuffer(ed->memtype, buf, NULL, &hgs);
1261 buf->writestring(")");
1262 }
1263 buf->writestring(";\n");
1264 }
1265
1266 void visit(EnumMember *em)
1267 {
1268 if (!em->ident)
1269 return;
1270
1271 buf->writestring(em->toChars());
1272 }
1273 };
1274
1275 ToDocBuffer v(buf, sc);
1276 s->accept(&v);
1277 }
1278
1279 /********************************* DocComment *********************************/
1280
1281 DocComment *DocComment::parse(Dsymbol *s, const utf8_t *comment)
1282 {
1283 //printf("parse(%s): '%s'\n", s->toChars(), comment);
1284 DocComment *dc = new DocComment();
1285 dc->a.push(s);
1286 if (!comment)
1287 return dc;
1288
1289 dc->parseSections(comment);
1290
1291 for (size_t i = 0; i < dc->sections.length; i++)
1292 {
1293 Section *sec = dc->sections[i];
1294
1295 if (icmp("copyright", sec->name, sec->namelen) == 0)
1296 {
1297 dc->copyright = sec;
1298 }
1299 if (icmp("macros", sec->name, sec->namelen) == 0)
1300 {
1301 dc->macros = sec;
1302 }
1303 }
1304
1305 return dc;
1306 }
1307
1308 /*****************************************
1309 * Parse next paragraph out of *pcomment.
1310 * Update *pcomment to point past paragraph.
1311 * Returns NULL if no more paragraphs.
1312 * If paragraph ends in 'identifier:',
1313 * then (*pcomment)[0 .. idlen] is the identifier.
1314 */
1315
1316 void DocComment::parseSections(const utf8_t *comment)
1317 {
1318 const utf8_t *p;
1319 const utf8_t *pstart;
1320 const utf8_t *pend;
1321 const utf8_t *idstart = NULL; // dead-store to prevent spurious warning
1322 size_t idlen;
1323
1324 const utf8_t *name = NULL;
1325 size_t namelen = 0;
1326
1327 //printf("parseSections('%s')\n", comment);
1328 p = comment;
1329 while (*p)
1330 {
1331 const utf8_t *pstart0 = p;
1332 p = skipwhitespace(p);
1333 pstart = p;
1334 pend = p;
1335
1336 /* Find end of section, which is ended by one of:
1337 * 'identifier:' (but not inside a code section)
1338 * '\0'
1339 */
1340 idlen = 0;
1341 int inCode = 0;
1342 while (1)
1343 {
1344 // Check for start/end of a code section
1345 if (*p == '-')
1346 {
1347 if (!inCode)
1348 {
1349 // restore leading indentation
1350 while (pstart0 < pstart && isIndentWS(pstart-1)) --pstart;
1351 }
1352
1353 int numdash = 0;
1354 while (*p == '-')
1355 {
1356 ++numdash;
1357 p++;
1358 }
1359 // BUG: handle UTF PS and LS too
1360 if ((!*p || *p == '\r' || *p == '\n') && numdash >= 3)
1361 inCode ^= 1;
1362 pend = p;
1363 }
1364
1365 if (!inCode && isIdStart(p))
1366 {
1367 const utf8_t *q = p + utfStride(p);
1368 while (isIdTail(q))
1369 q += utfStride(q);
1370 // Detected tag ends it
1371 if (*q == ':' && isupper(*p)
1372 && (isspace(q[1]) || q[1] == 0))
1373 {
1374 idlen = q - p;
1375 idstart = p;
1376 for (pend = p; pend > pstart; pend--)
1377 {
1378 if (pend[-1] == '\n')
1379 break;
1380 }
1381 p = q + 1;
1382 break;
1383 }
1384 }
1385 while (1)
1386 {
1387 if (!*p)
1388 goto L1;
1389 if (*p == '\n')
1390 {
1391 p++;
1392 if (*p == '\n' && !summary && !namelen && !inCode)
1393 {
1394 pend = p;
1395 p++;
1396 goto L1;
1397 }
1398 break;
1399 }
1400 p++;
1401 pend = p;
1402 }
1403 p = skipwhitespace(p);
1404 }
1405 L1:
1406
1407 if (namelen || pstart < pend)
1408 {
1409 Section *s;
1410 if (icmp("Params", name, namelen) == 0)
1411 s = new ParamSection();
1412 else if (icmp("Macros", name, namelen) == 0)
1413 s = new MacroSection();
1414 else
1415 s = new Section();
1416 s->name = name;
1417 s->namelen = namelen;
1418 s->body = pstart;
1419 s->bodylen = pend - pstart;
1420 s->nooutput = 0;
1421
1422 //printf("Section: '%.*s' = '%.*s'\n", s->namelen, s->name, s->bodylen, s->body);
1423
1424 sections.push(s);
1425
1426 if (!summary && !namelen)
1427 summary = s;
1428 }
1429
1430 if (idlen)
1431 {
1432 name = idstart;
1433 namelen = idlen;
1434 }
1435 else
1436 {
1437 name = NULL;
1438 namelen = 0;
1439 if (!*p)
1440 break;
1441 }
1442 }
1443 }
1444
1445 void DocComment::writeSections(Scope *sc, Dsymbols *a, OutBuffer *buf)
1446 {
1447 assert(a->length);
1448
1449 //printf("DocComment::writeSections()\n");
1450 Loc loc = (*a)[0]->loc;
1451 if (Module *m = (*a)[0]->isModule())
1452 {
1453 if (m->md)
1454 loc = m->md->loc;
1455 }
1456
1457 size_t offset1 = buf->offset;
1458 buf->writestring("$(DDOC_SECTIONS ");
1459 size_t offset2 = buf->offset;
1460
1461 for (size_t i = 0; i < sections.length; i++)
1462 {
1463 Section *sec = sections[i];
1464 if (sec->nooutput)
1465 continue;
1466
1467 //printf("Section: '%.*s' = '%.*s'\n", sec->namelen, sec->name, sec->bodylen, sec->body);
1468 if (!sec->namelen && i == 0)
1469 {
1470 buf->writestring("$(DDOC_SUMMARY ");
1471 size_t o = buf->offset;
1472 buf->write(sec->body, sec->bodylen);
1473 escapeStrayParenthesis(loc, buf, o);
1474 highlightText(sc, a, buf, o);
1475 buf->writestring(")\n");
1476 }
1477 else
1478 sec->write(loc, this, sc, a, buf);
1479 }
1480
1481 for (size_t i = 0; i < a->length; i++)
1482 {
1483 Dsymbol *s = (*a)[i];
1484 if (Dsymbol *td = getEponymousParent(s))
1485 s = td;
1486
1487 for (UnitTestDeclaration *utd = s->ddocUnittest; utd; utd = utd->ddocUnittest)
1488 {
1489 if (utd->protection.kind == Prot::private_ || !utd->comment || !utd->fbody)
1490 continue;
1491
1492 // Strip whitespaces to avoid showing empty summary
1493 const utf8_t *c = utd->comment;
1494 while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r') ++c;
1495
1496 buf->writestring("$(DDOC_EXAMPLES ");
1497
1498 size_t o = buf->offset;
1499 buf->writestring((const char *)c);
1500
1501 if (utd->codedoc)
1502 {
1503 size_t n = getCodeIndent(utd->codedoc);
1504 while (n--) buf->writeByte(' ');
1505 buf->writestring("----\n");
1506 buf->writestring(utd->codedoc);
1507 buf->writestring("----\n");
1508 highlightText(sc, a, buf, o);
1509 }
1510
1511 buf->writestring(")");
1512 }
1513 }
1514
1515 if (buf->offset == offset2)
1516 {
1517 /* Didn't write out any sections, so back out last write
1518 */
1519 buf->offset = offset1;
1520 buf->writestring("$(DDOC_BLANKLINE)\n");
1521 }
1522 else
1523 buf->writestring(")\n");
1524 }
1525
1526 /***************************************************
1527 */
1528
1529 void Section::write(Loc loc, DocComment *, Scope *sc, Dsymbols *a, OutBuffer *buf)
1530 {
1531 assert(a->length);
1532
1533 if (namelen)
1534 {
1535 static const char *table[] =
1536 {
1537 "AUTHORS", "BUGS", "COPYRIGHT", "DATE",
1538 "DEPRECATED", "EXAMPLES", "HISTORY", "LICENSE",
1539 "RETURNS", "SEE_ALSO", "STANDARDS", "THROWS",
1540 "VERSION", NULL
1541 };
1542
1543 for (size_t i = 0; table[i]; i++)
1544 {
1545 if (icmp(table[i], name, namelen) == 0)
1546 {
1547 buf->printf("$(DDOC_%s ", table[i]);
1548 goto L1;
1549 }
1550 }
1551
1552 buf->writestring("$(DDOC_SECTION ");
1553
1554 // Replace _ characters with spaces
1555 buf->writestring("$(DDOC_SECTION_H ");
1556 size_t o = buf->offset;
1557 for (size_t u = 0; u < namelen; u++)
1558 {
1559 utf8_t c = name[u];
1560 buf->writeByte((c == '_') ? ' ' : c);
1561 }
1562 escapeStrayParenthesis(loc, buf, o);
1563 buf->writestring(":)\n");
1564 }
1565 else
1566 {
1567 buf->writestring("$(DDOC_DESCRIPTION ");
1568 }
1569 L1:
1570 size_t o = buf->offset;
1571 buf->write(body, bodylen);
1572 escapeStrayParenthesis(loc, buf, o);
1573 highlightText(sc, a, buf, o);
1574 buf->writestring(")\n");
1575 }
1576
1577 /***************************************************
1578 */
1579
1580 void ParamSection::write(Loc loc, DocComment *, Scope *sc, Dsymbols *a, OutBuffer *buf)
1581 {
1582 assert(a->length);
1583 Dsymbol *s = (*a)[0]; // test
1584
1585 const utf8_t *p = body;
1586 size_t len = bodylen;
1587 const utf8_t *pend = p + len;
1588
1589 const utf8_t *tempstart = NULL;
1590 size_t templen = 0;
1591
1592 const utf8_t *namestart = NULL;
1593 size_t namelen = 0; // !=0 if line continuation
1594
1595 const utf8_t *textstart = NULL;
1596 size_t textlen = 0;
1597
1598 size_t paramcount = 0;
1599
1600 buf->writestring("$(DDOC_PARAMS ");
1601 while (p < pend)
1602 {
1603 // Skip to start of macro
1604 while (1)
1605 {
1606 switch (*p)
1607 {
1608 case ' ':
1609 case '\t':
1610 p++;
1611 continue;
1612
1613 case '\n':
1614 p++;
1615 goto Lcont;
1616
1617 default:
1618 if (isIdStart(p) || isCVariadicArg(p, pend - p))
1619 break;
1620 if (namelen)
1621 goto Ltext; // continuation of prev macro
1622 goto Lskipline;
1623 }
1624 break;
1625 }
1626 tempstart = p;
1627
1628 while (isIdTail(p))
1629 p += utfStride(p);
1630 if (isCVariadicArg(p, pend - p))
1631 p += 3;
1632
1633 templen = p - tempstart;
1634
1635 while (*p == ' ' || *p == '\t')
1636 p++;
1637
1638 if (*p != '=')
1639 {
1640 if (namelen)
1641 goto Ltext; // continuation of prev macro
1642 goto Lskipline;
1643 }
1644 p++;
1645
1646 if (namelen)
1647 {
1648 // Output existing param
1649
1650 L1:
1651 //printf("param '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart);
1652 ++paramcount;
1653 HdrGenState hgs;
1654 buf->writestring("$(DDOC_PARAM_ROW ");
1655 {
1656 buf->writestring("$(DDOC_PARAM_ID ");
1657 {
1658 size_t o = buf->offset;
1659 Parameter *fparam = isFunctionParameter(a, namestart, namelen);
1660 if (!fparam)
1661 {
1662 // Comments on a template might refer to function parameters within.
1663 // Search the parameters of nested eponymous functions (with the same name.)
1664 fparam = isEponymousFunctionParameter(a, namestart, namelen);
1665 }
1666 bool isCVariadic = isCVariadicParameter(a, namestart, namelen);
1667 if (isCVariadic)
1668 {
1669 buf->writestring("...");
1670 }
1671 else if (fparam && fparam->type && fparam->ident)
1672 {
1673 ::toCBuffer(fparam->type, buf, fparam->ident, &hgs);
1674 }
1675 else
1676 {
1677 if (isTemplateParameter(a, namestart, namelen))
1678 {
1679 // 10236: Don't count template parameters for params check
1680 --paramcount;
1681 }
1682 else if (!fparam)
1683 {
1684 warning(s->loc, "Ddoc: function declaration has no parameter '%.*s'", (int)namelen, namestart);
1685 }
1686 buf->write(namestart, namelen);
1687 }
1688 escapeStrayParenthesis(loc, buf, o);
1689 highlightCode(sc, a, buf, o);
1690 }
1691 buf->writestring(")\n");
1692
1693 buf->writestring("$(DDOC_PARAM_DESC ");
1694 {
1695 size_t o = buf->offset;
1696 buf->write(textstart, textlen);
1697 escapeStrayParenthesis(loc, buf, o);
1698 highlightText(sc, a, buf, o);
1699 }
1700 buf->writestring(")");
1701 }
1702 buf->writestring(")\n");
1703 namelen = 0;
1704 if (p >= pend)
1705 break;
1706 }
1707
1708 namestart = tempstart;
1709 namelen = templen;
1710
1711 while (*p == ' ' || *p == '\t')
1712 p++;
1713 textstart = p;
1714
1715 Ltext:
1716 while (*p != '\n')
1717 p++;
1718 textlen = p - textstart;
1719 p++;
1720
1721 Lcont:
1722 continue;
1723
1724 Lskipline:
1725 // Ignore this line
1726 while (*p++ != '\n')
1727 ;
1728 }
1729 if (namelen)
1730 goto L1; // write out last one
1731 buf->writestring(")\n");
1732
1733 TypeFunction *tf = a->length == 1 ? isTypeFunction(s) : NULL;
1734 if (tf)
1735 {
1736 size_t pcount = (tf->parameterList.parameters ? tf->parameterList.parameters->length : 0) +
1737 (int)(tf->parameterList.varargs == VARARGvariadic);
1738 if (pcount != paramcount)
1739 {
1740 warning(s->loc, "Ddoc: parameter count mismatch");
1741 }
1742 }
1743 }
1744
1745 /***************************************************
1746 */
1747
1748 void MacroSection::write(Loc, DocComment *dc, Scope *, Dsymbols *, OutBuffer *)
1749 {
1750 //printf("MacroSection::write()\n");
1751 DocComment::parseMacros(dc->pescapetable, dc->pmacrotable, body, bodylen);
1752 }
1753
1754 /************************************************
1755 * Parse macros out of Macros: section.
1756 * Macros are of the form:
1757 * name1 = value1
1758 *
1759 * name2 = value2
1760 */
1761
1762 void DocComment::parseMacros(Escape **pescapetable, Macro **pmacrotable, const utf8_t *m, size_t mlen)
1763 {
1764 const utf8_t *p = m;
1765 size_t len = mlen;
1766 const utf8_t *pend = p + len;
1767
1768 const utf8_t *tempstart = NULL;
1769 size_t templen = 0;
1770
1771 const utf8_t *namestart = NULL;
1772 size_t namelen = 0; // !=0 if line continuation
1773
1774 const utf8_t *textstart = NULL;
1775 size_t textlen = 0;
1776
1777 while (p < pend)
1778 {
1779 // Skip to start of macro
1780 while (1)
1781 {
1782 if (p >= pend)
1783 goto Ldone;
1784 switch (*p)
1785 {
1786 case ' ':
1787 case '\t':
1788 p++;
1789 continue;
1790
1791 case '\r':
1792 case '\n':
1793 p++;
1794 goto Lcont;
1795
1796 default:
1797 if (isIdStart(p))
1798 break;
1799 if (namelen)
1800 goto Ltext; // continuation of prev macro
1801 goto Lskipline;
1802 }
1803 break;
1804 }
1805 tempstart = p;
1806
1807 while (1)
1808 {
1809 if (p >= pend)
1810 goto Ldone;
1811 if (!isIdTail(p))
1812 break;
1813 p += utfStride(p);
1814 }
1815 templen = p - tempstart;
1816
1817 while (1)
1818 {
1819 if (p >= pend)
1820 goto Ldone;
1821 if (!(*p == ' ' || *p == '\t'))
1822 break;
1823 p++;
1824 }
1825
1826 if (*p != '=')
1827 {
1828 if (namelen)
1829 goto Ltext; // continuation of prev macro
1830 goto Lskipline;
1831 }
1832 p++;
1833 if (p >= pend)
1834 goto Ldone;
1835
1836 if (namelen)
1837 {
1838 // Output existing macro
1839 L1:
1840 //printf("macro '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart);
1841 if (icmp("ESCAPES", namestart, namelen) == 0)
1842 parseEscapes(pescapetable, textstart, textlen);
1843 else
1844 Macro::define(pmacrotable, namestart, namelen, textstart, textlen);
1845 namelen = 0;
1846 if (p >= pend)
1847 break;
1848 }
1849
1850 namestart = tempstart;
1851 namelen = templen;
1852
1853 while (p < pend && (*p == ' ' || *p == '\t'))
1854 p++;
1855 textstart = p;
1856
1857 Ltext:
1858 while (p < pend && *p != '\r' && *p != '\n')
1859 p++;
1860 textlen = p - textstart;
1861
1862 p++;
1863 //printf("p = %p, pend = %p\n", p, pend);
1864
1865 Lcont:
1866 continue;
1867
1868 Lskipline:
1869 // Ignore this line
1870 while (p < pend && *p != '\r' && *p != '\n')
1871 p++;
1872 }
1873 Ldone:
1874 if (namelen)
1875 goto L1; // write out last one
1876 }
1877
1878 /**************************************
1879 * Parse escapes of the form:
1880 * /c/string/
1881 * where c is a single character.
1882 * Multiple escapes can be separated
1883 * by whitespace and/or commas.
1884 */
1885
1886 void DocComment::parseEscapes(Escape **pescapetable, const utf8_t *textstart, size_t textlen)
1887 {
1888 Escape *escapetable = *pescapetable;
1889
1890 if (!escapetable)
1891 {
1892 escapetable = new Escape;
1893 memset(escapetable, 0, sizeof(Escape));
1894 *pescapetable = escapetable;
1895 }
1896 //printf("parseEscapes('%.*s') pescapetable = %p\n", textlen, textstart, pescapetable);
1897 const utf8_t *p = textstart;
1898 const utf8_t *pend = p + textlen;
1899
1900 while (1)
1901 {
1902 while (1)
1903 {
1904 if (p + 4 >= pend)
1905 return;
1906 if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ','))
1907 break;
1908 p++;
1909 }
1910 if (p[0] != '/' || p[2] != '/')
1911 return;
1912 utf8_t c = p[1];
1913 p += 3;
1914 const utf8_t *start = p;
1915 while (1)
1916 {
1917 if (p >= pend)
1918 return;
1919 if (*p == '/')
1920 break;
1921 p++;
1922 }
1923 size_t len = p - start;
1924 char *s = (char *)memcpy(mem.xmalloc(len + 1), start, len);
1925 s[len] = 0;
1926 escapetable->strings[c] = s;
1927 //printf("\t%c = '%s'\n", c, s);
1928 p++;
1929 }
1930 }
1931
1932
1933 /******************************************
1934 * Compare 0-terminated string with length terminated string.
1935 * Return < 0, ==0, > 0
1936 */
1937
1938 int cmp(const char *stringz, const void *s, size_t slen)
1939 {
1940 size_t len1 = strlen(stringz);
1941
1942 if (len1 != slen)
1943 return (int)(len1 - slen);
1944 return memcmp(stringz, s, slen);
1945 }
1946
1947 int icmp(const char *stringz, const void *s, size_t slen)
1948 {
1949 size_t len1 = strlen(stringz);
1950
1951 if (len1 != slen)
1952 return (int)(len1 - slen);
1953 return Port::memicmp(stringz, (const char *)s, slen);
1954 }
1955
1956 /*****************************************
1957 * Return true if comment consists entirely of "ditto".
1958 */
1959
1960 bool isDitto(const utf8_t *comment)
1961 {
1962 if (comment)
1963 {
1964 const utf8_t *p = skipwhitespace(comment);
1965
1966 if (Port::memicmp((const char *)p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
1967 return true;
1968 }
1969 return false;
1970 }
1971
1972 /**********************************************
1973 * Skip white space.
1974 */
1975
1976 const utf8_t *skipwhitespace(const utf8_t *p)
1977 {
1978 for (; 1; p++)
1979 {
1980 switch (*p)
1981 {
1982 case ' ':
1983 case '\t':
1984 case '\n':
1985 continue;
1986 }
1987 break;
1988 }
1989 return p;
1990 }
1991
1992
1993 /************************************************
1994 * Scan forward to one of:
1995 * start of identifier
1996 * beginning of next line
1997 * end of buf
1998 */
1999
2000 size_t skiptoident(OutBuffer *buf, size_t i)
2001 {
2002 while (i < buf->offset)
2003 {
2004 dchar_t c;
2005
2006 size_t oi = i;
2007 if (utf_decodeChar((utf8_t *)buf->data, buf->offset, &i, &c))
2008 {
2009 /* Ignore UTF errors, but still consume input
2010 */
2011 break;
2012 }
2013 if (c >= 0x80)
2014 {
2015 if (!isUniAlpha(c))
2016 continue;
2017 }
2018 else if (!(isalpha(c) || c == '_' || c == '\n'))
2019 continue;
2020 i = oi;
2021 break;
2022 }
2023 return i;
2024 }
2025
2026 /************************************************
2027 * Scan forward past end of identifier.
2028 */
2029
2030 size_t skippastident(OutBuffer *buf, size_t i)
2031 {
2032 while (i < buf->offset)
2033 {
2034 dchar_t c;
2035
2036 size_t oi = i;
2037 if (utf_decodeChar((utf8_t *)buf->data, buf->offset, &i, &c))
2038 {
2039 /* Ignore UTF errors, but still consume input
2040 */
2041 break;
2042 }
2043 if (c >= 0x80)
2044 {
2045 if (isUniAlpha(c))
2046 continue;
2047 }
2048 else if (isalnum(c) || c == '_')
2049 continue;
2050 i = oi;
2051 break;
2052 }
2053 return i;
2054 }
2055
2056
2057 /************************************************
2058 * Scan forward past URL starting at i.
2059 * We don't want to highlight parts of a URL.
2060 * Returns:
2061 * i if not a URL
2062 * index just past it if it is a URL
2063 */
2064
2065 size_t skippastURL(OutBuffer *buf, size_t i)
2066 {
2067 size_t length = buf->offset - i;
2068 utf8_t *p = (utf8_t *)&buf->data[i];
2069 size_t j;
2070 unsigned sawdot = 0;
2071
2072 if (length > 7 && Port::memicmp((char *)p, "http://", 7) == 0)
2073 {
2074 j = 7;
2075 }
2076 else if (length > 8 && Port::memicmp((char *)p, "https://", 8) == 0)
2077 {
2078 j = 8;
2079 }
2080 else
2081 goto Lno;
2082
2083 for (; j < length; j++)
2084 {
2085 utf8_t c = p[j];
2086 if (isalnum(c))
2087 continue;
2088 if (c == '-' || c == '_' || c == '?' ||
2089 c == '=' || c == '%' || c == '&' ||
2090 c == '/' || c == '+' || c == '#' ||
2091 c == '~')
2092 continue;
2093 if (c == '.')
2094 {
2095 sawdot = 1;
2096 continue;
2097 }
2098 break;
2099 }
2100 if (sawdot)
2101 return i + j;
2102
2103 Lno:
2104 return i;
2105 }
2106
2107
2108 /****************************************************
2109 */
2110
2111 bool isIdentifier(Dsymbols *a, const utf8_t *p, size_t len)
2112 {
2113 for (size_t i = 0; i < a->length; i++)
2114 {
2115 const char *s = (*a)[i]->ident->toChars();
2116 if (cmp(s, p, len) == 0)
2117 return true;
2118 }
2119 return false;
2120 }
2121
2122 /****************************************************
2123 */
2124
2125 bool isKeyword(utf8_t *p, size_t len)
2126 {
2127 static const char *table[] = { "true", "false", "null", NULL };
2128
2129 for (int i = 0; table[i]; i++)
2130 {
2131 if (cmp(table[i], p, len) == 0)
2132 return true;
2133 }
2134 return false;
2135 }
2136
2137 /****************************************************
2138 */
2139
2140 TypeFunction *isTypeFunction(Dsymbol *s)
2141 {
2142 FuncDeclaration *f = s->isFuncDeclaration();
2143
2144 /* f->type may be NULL for template members.
2145 */
2146 if (f && f->type)
2147 {
2148 Type *t = f->originalType ? f->originalType : f->type;
2149 if (t->ty == Tfunction)
2150 return (TypeFunction *)t;
2151 }
2152 return NULL;
2153 }
2154
2155 /****************************************************
2156 */
2157
2158 Parameter *isFunctionParameter(Dsymbols *a, const utf8_t *p, size_t len)
2159 {
2160 for (size_t i = 0; i < a->length; i++)
2161 {
2162 Parameter *fparam = isFunctionParameter((*a)[i], p, len);
2163 if (fparam)
2164 {
2165 return fparam;
2166 }
2167 }
2168 return NULL;
2169 }
2170
2171 /****************************************************
2172 */
2173
2174 TemplateParameter *isTemplateParameter(Dsymbols *a, const utf8_t *p, size_t len)
2175 {
2176 for (size_t i = 0; i < a->length; i++)
2177 {
2178 TemplateDeclaration *td = (*a)[i]->isTemplateDeclaration();
2179 // Check for the parent, if the current symbol is not a template declaration.
2180 if (!td)
2181 td = getEponymousParent((*a)[i]);
2182 if (td && td->origParameters)
2183 {
2184 for (size_t k = 0; k < td->origParameters->length; k++)
2185 {
2186 TemplateParameter *tp = (*td->origParameters)[k];
2187 if (tp->ident && cmp(tp->ident->toChars(), p, len) == 0)
2188 {
2189 return tp;
2190 }
2191 }
2192 }
2193 }
2194 return NULL;
2195 }
2196
2197 /****************************************************
2198 * Return true if str is a reserved symbol name
2199 * that starts with a double underscore.
2200 */
2201
2202 bool isReservedName(utf8_t *str, size_t len)
2203 {
2204 static const char *table[] = {
2205 "__ctor", "__dtor", "__postblit", "__invariant", "__unitTest",
2206 "__require", "__ensure", "__dollar", "__ctfe", "__withSym", "__result",
2207 "__returnLabel", "__vptr", "__monitor", "__gate", "__xopEquals", "__xopCmp",
2208 "__LINE__", "__FILE__", "__MODULE__", "__FUNCTION__", "__PRETTY_FUNCTION__",
2209 "__DATE__", "__TIME__", "__TIMESTAMP__", "__VENDOR__", "__VERSION__",
2210 "__EOF__", "__LOCAL_SIZE", "___tls_get_addr", "__entrypoint", NULL };
2211
2212 for (int i = 0; table[i]; i++)
2213 {
2214 if (cmp(table[i], str, len) == 0)
2215 return true;
2216 }
2217 return false;
2218 }
2219
2220 /**************************************************
2221 * Highlight text section.
2222 */
2223
2224 void highlightText(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset)
2225 {
2226 Dsymbol *s = a->length ? (*a)[0] : NULL; // test
2227
2228 //printf("highlightText()\n");
2229
2230 int leadingBlank = 1;
2231 int inCode = 0;
2232 int inBacktick = 0;
2233 //int inComment = 0; // in <!-- ... --> comment
2234 size_t iCodeStart = 0; // start of code section
2235 size_t codeIndent = 0;
2236
2237 size_t iLineStart = offset;
2238
2239 for (size_t i = offset; i < buf->offset; i++)
2240 {
2241 utf8_t c = buf->data[i];
2242
2243 Lcont:
2244 switch (c)
2245 {
2246 case ' ':
2247 case '\t':
2248 break;
2249
2250 case '\n':
2251 if (inBacktick)
2252 {
2253 // `inline code` is only valid if contained on a single line
2254 // otherwise, the backticks should be output literally.
2255 //
2256 // This lets things like `output from the linker' display
2257 // unmolested while keeping the feature consistent with GitHub.
2258
2259 inBacktick = false;
2260 inCode = false; // the backtick also assumes we're in code
2261
2262 // Nothing else is necessary since the DDOC_BACKQUOTED macro is
2263 // inserted lazily at the close quote, meaning the rest of the
2264 // text is already OK.
2265 }
2266
2267 if (!sc->_module->isDocFile &&
2268 !inCode && i == iLineStart && i + 1 < buf->offset) // if "\n\n"
2269 {
2270 static const char blankline[] = "$(DDOC_BLANKLINE)\n";
2271
2272 i = buf->insert(i, blankline, strlen(blankline));
2273 }
2274 leadingBlank = 1;
2275 iLineStart = i + 1;
2276 break;
2277
2278 case '<':
2279 {
2280 leadingBlank = 0;
2281 if (inCode)
2282 break;
2283 utf8_t *p = (utf8_t *)&buf->data[i];
2284 const char *se = sc->_module->escapetable->escapeChar('<');
2285 if (se && strcmp(se, "&lt;") == 0)
2286 {
2287 // Generating HTML
2288 // Skip over comments
2289 if (p[1] == '!' && p[2] == '-' && p[3] == '-')
2290 {
2291 size_t j = i + 4;
2292 p += 4;
2293 while (1)
2294 {
2295 if (j == buf->offset)
2296 goto L1;
2297 if (p[0] == '-' && p[1] == '-' && p[2] == '>')
2298 {
2299 i = j + 2; // place on closing '>'
2300 break;
2301 }
2302 j++;
2303 p++;
2304 }
2305 break;
2306 }
2307
2308 // Skip over HTML tag
2309 if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2])))
2310 {
2311 size_t j = i + 2;
2312 p += 2;
2313 while (1)
2314 {
2315 if (j == buf->offset)
2316 break;
2317 if (p[0] == '>')
2318 {
2319 i = j; // place on closing '>'
2320 break;
2321 }
2322 j++;
2323 p++;
2324 }
2325 break;
2326 }
2327 }
2328 L1:
2329 // Replace '<' with '&lt;' character entity
2330 if (se)
2331 {
2332 size_t len = strlen(se);
2333 buf->remove(i, 1);
2334 i = buf->insert(i, se, len);
2335 i--; // point to ';'
2336 }
2337 break;
2338 }
2339 case '>':
2340 {
2341 leadingBlank = 0;
2342 if (inCode)
2343 break;
2344 // Replace '>' with '&gt;' character entity
2345 const char *se = sc->_module->escapetable->escapeChar('>');
2346 if (se)
2347 {
2348 size_t len = strlen(se);
2349 buf->remove(i, 1);
2350 i = buf->insert(i, se, len);
2351 i--; // point to ';'
2352 }
2353 break;
2354 }
2355 case '&':
2356 {
2357 leadingBlank = 0;
2358 if (inCode)
2359 break;
2360 utf8_t *p = (utf8_t *)&buf->data[i];
2361 if (p[1] == '#' || isalpha(p[1]))
2362 break; // already a character entity
2363 // Replace '&' with '&amp;' character entity
2364 const char *se = sc->_module->escapetable->escapeChar('&');
2365 if (se)
2366 {
2367 size_t len = strlen(se);
2368 buf->remove(i, 1);
2369 i = buf->insert(i, se, len);
2370 i--; // point to ';'
2371 }
2372 break;
2373 }
2374 case '`':
2375 {
2376 if (inBacktick)
2377 {
2378 inBacktick = 0;
2379 inCode = 0;
2380
2381 OutBuffer codebuf;
2382
2383 codebuf.write(buf->data + iCodeStart + 1, i - (iCodeStart + 1));
2384
2385 // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL
2386 highlightCode(sc, a, &codebuf, 0);
2387
2388 buf->remove(iCodeStart, i - iCodeStart + 1); // also trimming off the current `
2389
2390 static const char pre[] = "$(DDOC_BACKQUOTED ";
2391 i = buf->insert(iCodeStart, pre, strlen(pre));
2392 i = buf->insert(i, (char *)codebuf.data, codebuf.offset);
2393 i = buf->insert(i, ")", 1);
2394
2395 i--; // point to the ending ) so when the for loop does i++, it will see the next character
2396
2397 break;
2398 }
2399
2400 if (inCode)
2401 break;
2402
2403 inCode = 1;
2404 inBacktick = 1;
2405 codeIndent = 0; // inline code is not indented
2406
2407 // All we do here is set the code flags and record
2408 // the location. The macro will be inserted lazily
2409 // so we can easily cancel the inBacktick if we come
2410 // across a newline character.
2411 iCodeStart = i;
2412
2413 break;
2414 }
2415 case '-':
2416 /* A line beginning with --- delimits a code section.
2417 * inCode tells us if it is start or end of a code section.
2418 */
2419 if (leadingBlank)
2420 {
2421 size_t istart = i;
2422 size_t eollen = 0;
2423
2424 leadingBlank = 0;
2425 while (1)
2426 {
2427 ++i;
2428 if (i >= buf->offset)
2429 break;
2430 c = buf->data[i];
2431 if (c == '\n')
2432 {
2433 eollen = 1;
2434 break;
2435 }
2436 if (c == '\r')
2437 {
2438 eollen = 1;
2439 if (i + 1 >= buf->offset)
2440 break;
2441 if (buf->data[i + 1] == '\n')
2442 {
2443 eollen = 2;
2444 break;
2445 }
2446 }
2447 // BUG: handle UTF PS and LS too
2448 if (c != '-')
2449 goto Lcont;
2450 }
2451 if (i - istart < 3)
2452 goto Lcont;
2453
2454 // We have the start/end of a code section
2455
2456 // Remove the entire --- line, including blanks and \n
2457 buf->remove(iLineStart, i - iLineStart + eollen);
2458 i = iLineStart;
2459
2460 if (inCode && (i <= iCodeStart))
2461 {
2462 // Empty code section, just remove it completely.
2463 inCode = 0;
2464 break;
2465 }
2466
2467 if (inCode)
2468 {
2469 inCode = 0;
2470 // The code section is from iCodeStart to i
2471 OutBuffer codebuf;
2472
2473 codebuf.write(buf->data + iCodeStart, i - iCodeStart);
2474 codebuf.writeByte(0);
2475
2476 // Remove leading indentations from all lines
2477 bool lineStart = true;
2478 utf8_t *endp = (utf8_t *)codebuf.data + codebuf.offset;
2479 for (utf8_t *p = (utf8_t *)codebuf.data; p < endp; )
2480 {
2481 if (lineStart)
2482 {
2483 size_t j = codeIndent;
2484 utf8_t *q = p;
2485 while (j-- > 0 && q < endp && isIndentWS(q))
2486 ++q;
2487 codebuf.remove(p - (utf8_t *)codebuf.data, q - p);
2488 assert((utf8_t *)codebuf.data <= p);
2489 assert(p < (utf8_t *)codebuf.data + codebuf.offset);
2490 lineStart = false;
2491 endp = (utf8_t *)codebuf.data + codebuf.offset; // update
2492 continue;
2493 }
2494 if (*p == '\n')
2495 lineStart = true;
2496 ++p;
2497 }
2498
2499 highlightCode2(sc, a, &codebuf, 0);
2500 buf->remove(iCodeStart, i - iCodeStart);
2501 i = buf->insert(iCodeStart, codebuf.data, codebuf.offset);
2502 i = buf->insert(i, (const char *)")\n", 2);
2503 i -= 2; // in next loop, c should be '\n'
2504 }
2505 else
2506 {
2507 static const char d_code[] = "$(D_CODE ";
2508
2509 inCode = 1;
2510 codeIndent = istart - iLineStart; // save indent count
2511 i = buf->insert(i, d_code, strlen(d_code));
2512 iCodeStart = i;
2513 i--; // place i on >
2514 leadingBlank = true;
2515 }
2516 }
2517 break;
2518
2519 default:
2520 leadingBlank = 0;
2521 if (sc->_module->isDocFile || inCode)
2522 break;
2523
2524 utf8_t *start = (utf8_t *)buf->data + i;
2525 if (isIdStart(start))
2526 {
2527 size_t j = skippastident(buf, i);
2528 if (i < j)
2529 {
2530 size_t k = skippastURL(buf, i);
2531 if (i < k)
2532 {
2533 i = k - 1;
2534 break;
2535 }
2536 }
2537 else
2538 break;
2539 size_t len = j - i;
2540
2541 // leading '_' means no highlight unless it's a reserved symbol name
2542 if (c == '_' &&
2543 (i == 0 || !isdigit(*(start - 1))) &&
2544 (i == buf->offset - 1 || !isReservedName(start, len)))
2545 {
2546 buf->remove(i, 1);
2547 i = j - 1;
2548 break;
2549 }
2550 if (isIdentifier(a, start, len))
2551 {
2552 i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
2553 break;
2554 }
2555 if (isKeyword(start, len))
2556 {
2557 i = buf->bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1;
2558 break;
2559 }
2560 if (isFunctionParameter(a, start, len))
2561 {
2562 //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j);
2563 i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
2564 break;
2565 }
2566
2567 i = j - 1;
2568 }
2569 break;
2570 }
2571 }
2572 if (inCode)
2573 error(s ? s->loc : Loc(), "unmatched --- in DDoc comment");
2574 }
2575
2576 /**************************************************
2577 * Highlight code for DDOC section.
2578 */
2579
2580 void highlightCode(Scope *sc, Dsymbol *s, OutBuffer *buf, size_t offset)
2581 {
2582 //printf("highlightCode(s = %s '%s')\n", s->kind(), s->toChars());
2583 OutBuffer ancbuf;
2584 emitAnchor(&ancbuf, s, sc);
2585 buf->insert(offset, (char *)ancbuf.data, ancbuf.offset);
2586 offset += ancbuf.offset;
2587
2588 Dsymbols a;
2589 a.push(s);
2590 highlightCode(sc, &a, buf, offset);
2591 }
2592
2593 /****************************************************
2594 */
2595
2596 void highlightCode(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset)
2597 {
2598 //printf("highlightCode(a = '%s')\n", a->toChars());
2599
2600 for (size_t i = offset; i < buf->offset; i++)
2601 {
2602 utf8_t c = buf->data[i];
2603 const char *se = sc->_module->escapetable->escapeChar(c);
2604 if (se)
2605 {
2606 size_t len = strlen(se);
2607 buf->remove(i, 1);
2608 i = buf->insert(i, se, len);
2609 i--; // point to ';'
2610 continue;
2611 }
2612
2613 utf8_t *start = (utf8_t *)buf->data + i;
2614 if (isIdStart(start))
2615 {
2616 size_t j = skippastident(buf, i);
2617 if (i < j)
2618 {
2619 size_t len = j - i;
2620 if (isIdentifier(a, start, len))
2621 {
2622 i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
2623 continue;
2624 }
2625 if (isFunctionParameter(a, start, len))
2626 {
2627 //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j);
2628 i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
2629 continue;
2630 }
2631 i = j - 1;
2632 }
2633 }
2634 }
2635 }
2636
2637 /****************************************
2638 */
2639
2640 void highlightCode3(Scope *sc, OutBuffer *buf, const utf8_t *p, const utf8_t *pend)
2641 {
2642 for (; p < pend; p++)
2643 {
2644 const char *s = sc->_module->escapetable->escapeChar(*p);
2645 if (s)
2646 buf->writestring(s);
2647 else
2648 buf->writeByte(*p);
2649 }
2650 }
2651
2652 /**************************************************
2653 * Highlight code for CODE section.
2654 */
2655
2656 void highlightCode2(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset)
2657 {
2658 unsigned errorsave = global.errors;
2659 Lexer lex(NULL, (utf8_t *)buf->data, 0, buf->offset - 1, 0, 1);
2660 OutBuffer res;
2661 const utf8_t *lastp = (utf8_t *)buf->data;
2662
2663 //printf("highlightCode2('%.*s')\n", buf->offset - 1, buf->data);
2664 res.reserve(buf->offset);
2665 while (1)
2666 {
2667 Token tok;
2668 lex.scan(&tok);
2669 highlightCode3(sc, &res, lastp, tok.ptr);
2670
2671 const char *highlight = NULL;
2672 switch (tok.value)
2673 {
2674 case TOKidentifier:
2675 {
2676 if (!sc)
2677 break;
2678 size_t len = lex.p - tok.ptr;
2679 if (isIdentifier(a, tok.ptr, len))
2680 {
2681 highlight = "$(D_PSYMBOL ";
2682 break;
2683 }
2684 if (isFunctionParameter(a, tok.ptr, len))
2685 {
2686 //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j);
2687 highlight = "$(D_PARAM ";
2688 break;
2689 }
2690 break;
2691 }
2692 case TOKcomment:
2693 highlight = "$(D_COMMENT ";
2694 break;
2695
2696 case TOKstring:
2697 highlight = "$(D_STRING ";
2698 break;
2699
2700 default:
2701 if (tok.isKeyword())
2702 highlight = "$(D_KEYWORD ";
2703 break;
2704 }
2705 if (highlight)
2706 {
2707 res.writestring(highlight);
2708 size_t o = res.offset;
2709 highlightCode3(sc, &res, tok.ptr, lex.p);
2710 if (tok.value == TOKcomment || tok.value == TOKstring)
2711 escapeDdocString(&res, o); // Bugzilla 7656, 7715, and 10519
2712 res.writeByte(')');
2713 }
2714 else
2715 highlightCode3(sc, &res, tok.ptr, lex.p);
2716 if (tok.value == TOKeof)
2717 break;
2718 lastp = lex.p;
2719 }
2720 buf->setsize(offset);
2721 buf->write(&res);
2722 global.errors = errorsave;
2723 }
2724
2725 /***************************************
2726 * Find character string to replace c with.
2727 */
2728
2729 const char *Escape::escapeChar(unsigned c)
2730 {
2731 assert(c < 256);
2732 //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c]);
2733 return strings[c];
2734 }
2735
2736 /****************************************
2737 * Determine if p points to the start of a "..." parameter identifier.
2738 */
2739
2740 bool isCVariadicArg(const utf8_t *p, size_t len)
2741 {
2742 return len >= 3 && cmp("...", p, 3) == 0;
2743 }
2744
2745 /****************************************
2746 * Determine if p points to the start of an identifier.
2747 */
2748
2749 bool isIdStart(const utf8_t *p)
2750 {
2751 unsigned c = *p;
2752 if (isalpha(c) || c == '_')
2753 return true;
2754 if (c >= 0x80)
2755 {
2756 size_t i = 0;
2757 if (utf_decodeChar(p, 4, &i, &c))
2758 return false; // ignore errors
2759 if (isUniAlpha(c))
2760 return true;
2761 }
2762 return false;
2763 }
2764
2765 /****************************************
2766 * Determine if p points to the rest of an identifier.
2767 */
2768
2769 bool isIdTail(const utf8_t *p)
2770 {
2771 unsigned c = *p;
2772 if (isalnum(c) || c == '_')
2773 return true;
2774 if (c >= 0x80)
2775 {
2776 size_t i = 0;
2777 if (utf_decodeChar(p, 4, &i, &c))
2778 return false; // ignore errors
2779 if (isUniAlpha(c))
2780 return true;
2781 }
2782 return false;
2783 }
2784
2785 /****************************************
2786 * Determine if p points to the indentation space.
2787 */
2788
2789 bool isIndentWS(const utf8_t *p)
2790 {
2791 return (*p == ' ') || (*p == '\t');
2792 }
2793
2794 /*****************************************
2795 * Return number of bytes in UTF character.
2796 */
2797
2798 int utfStride(const utf8_t *p)
2799 {
2800 unsigned c = *p;
2801 if (c < 0x80)
2802 return 1;
2803 size_t i = 0;
2804 utf_decodeChar(p, 4, &i, &c); // ignore errors, but still consume input
2805 return (int)i;
2806 }