]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/d/dmd/doc.c
ipa-param-manip: Be careful about a reallocating hash_map
[thirdparty/gcc.git] / gcc / d / dmd / doc.c
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
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.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 */
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 */
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.
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 }