| |
| /* Compiler implementation of the D programming language |
| * Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved |
| * written by Walter Bright |
| * http://www.digitalmars.com |
| * Distributed under the Boost Software License, Version 1.0. |
| * http://www.boost.org/LICENSE_1_0.txt |
| * https://github.com/D-Programming-Language/dmd/blob/master/src/doc.c |
| */ |
| |
| // This implements the Ddoc capability. |
| |
| #include "root/dsystem.h" |
| #include "root/rmem.h" |
| #include "root/root.h" |
| #include "root/port.h" |
| #include "root/aav.h" |
| |
| #include "attrib.h" |
| #include "cond.h" |
| #include "mars.h" |
| #include "dsymbol.h" |
| #include "macro.h" |
| #include "template.h" |
| #include "lexer.h" |
| #include "aggregate.h" |
| #include "declaration.h" |
| #include "statement.h" |
| #include "enum.h" |
| #include "id.h" |
| #include "module.h" |
| #include "scope.h" |
| #include "hdrgen.h" |
| #include "doc.h" |
| #include "mtype.h" |
| #include "utf.h" |
| |
| void emitMemberComments(ScopeDsymbol *sds, OutBuffer *buf, Scope *sc); |
| void toDocBuffer(Dsymbol *s, OutBuffer *buf, Scope *sc); |
| void emitComment(Dsymbol *s, OutBuffer *buf, Scope *sc); |
| |
| struct Escape |
| { |
| const char *strings[256]; |
| |
| const char *escapeChar(unsigned c); |
| }; |
| |
| class Section |
| { |
| public: |
| const utf8_t *name; |
| size_t namelen; |
| |
| const utf8_t *body; |
| size_t bodylen; |
| |
| int nooutput; |
| |
| virtual void write(Loc loc, DocComment *dc, Scope *sc, Dsymbols *a, OutBuffer *buf); |
| }; |
| |
| class ParamSection : public Section |
| { |
| public: |
| void write(Loc loc, DocComment *dc, Scope *sc, Dsymbols *a, OutBuffer *buf); |
| }; |
| |
| class MacroSection : public Section |
| { |
| public: |
| void write(Loc loc, DocComment *dc, Scope *sc, Dsymbols *a, OutBuffer *buf); |
| }; |
| |
| typedef Array<Section *> Sections; |
| |
| struct DocComment |
| { |
| Sections sections; // Section*[] |
| |
| Section *summary; |
| Section *copyright; |
| Section *macros; |
| Macro **pmacrotable; |
| Escape **pescapetable; |
| |
| Dsymbols a; |
| |
| DocComment() : |
| summary(NULL), copyright(NULL), macros(NULL), pmacrotable(NULL), pescapetable(NULL) |
| { } |
| |
| static DocComment *parse(Dsymbol *s, const utf8_t *comment); |
| static void parseMacros(Escape **pescapetable, Macro **pmacrotable, const utf8_t *m, size_t mlen); |
| static void parseEscapes(Escape **pescapetable, const utf8_t *textstart, size_t textlen); |
| |
| void parseSections(const utf8_t *comment); |
| void writeSections(Scope *sc, Dsymbols *a, OutBuffer *buf); |
| }; |
| |
| |
| int cmp(const char *stringz, const void *s, size_t slen); |
| int icmp(const char *stringz, const void *s, size_t slen); |
| bool isDitto(const utf8_t *comment); |
| const utf8_t *skipwhitespace(const utf8_t *p); |
| size_t skiptoident(OutBuffer *buf, size_t i); |
| size_t skippastident(OutBuffer *buf, size_t i); |
| size_t skippastURL(OutBuffer *buf, size_t i); |
| void highlightText(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset); |
| void highlightCode(Scope *sc, Dsymbol *s, OutBuffer *buf, size_t offset); |
| void highlightCode(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset); |
| void highlightCode2(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset); |
| void highlightCode3(Scope *sc, OutBuffer *buf, const utf8_t *p, const utf8_t *pend); |
| TypeFunction *isTypeFunction(Dsymbol *s); |
| Parameter *isFunctionParameter(Dsymbols *a, const utf8_t *p, size_t len); |
| TemplateParameter *isTemplateParameter(Dsymbols *a, const utf8_t *p, size_t len); |
| |
| bool isIdStart(const utf8_t *p); |
| bool isCVariadicArg(const utf8_t *p, size_t len); |
| bool isIdTail(const utf8_t *p); |
| bool isIndentWS(const utf8_t *p); |
| int utfStride(const utf8_t *p); |
| |
| // Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one). |
| bool isCVariadicParameter(Dsymbols *a, const utf8_t *p, size_t len) |
| { |
| for (size_t i = 0; i < a->dim; i++) |
| { |
| TypeFunction *tf = isTypeFunction((*a)[i]); |
| if (tf && tf->varargs == 1 && cmp("...", p, len) == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| /**************************************************** |
| */ |
| static Parameter *isFunctionParameter(Dsymbol *s, const utf8_t *p, size_t len) |
| { |
| TypeFunction *tf = isTypeFunction(s); |
| if (tf && tf->parameters) |
| { |
| for (size_t k = 0; k < tf->parameters->dim; k++) |
| { |
| Parameter *fparam = (*tf->parameters)[k]; |
| if (fparam->ident && cmp(fparam->ident->toChars(), p, len) == 0) |
| { |
| return fparam; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| static Dsymbol *getEponymousMember(TemplateDeclaration *td) |
| { |
| if (!td->onemember) |
| return NULL; |
| |
| if (AggregateDeclaration *ad = td->onemember->isAggregateDeclaration()) |
| return ad; |
| if (FuncDeclaration *fd = td->onemember->isFuncDeclaration()) |
| return fd; |
| if (td->onemember->isEnumMember()) |
| return NULL; // Keep backward compatibility. See compilable/ddoc9.d |
| if (VarDeclaration *vd = td->onemember->isVarDeclaration()) |
| return td->constraint ? NULL : vd; |
| |
| return NULL; |
| } |
| |
| /**************************************************** |
| */ |
| static Parameter *isEponymousFunctionParameter(Dsymbols *a, const utf8_t *p, size_t len) |
| { |
| for (size_t i = 0; i < a->dim; i++) |
| { |
| TemplateDeclaration *td = (*a)[i]->isTemplateDeclaration(); |
| if (td && td->onemember) |
| { |
| /* Case 1: we refer to a template declaration inside the template |
| |
| /// ...ddoc... |
| template case1(T) { |
| void case1(R)() {} |
| } |
| */ |
| td = td->onemember->isTemplateDeclaration(); |
| } |
| if (!td) |
| { |
| /* Case 2: we're an alias to a template declaration |
| |
| /// ...ddoc... |
| alias case2 = case1!int; |
| */ |
| AliasDeclaration *ad = (*a)[i]->isAliasDeclaration(); |
| if (ad && ad->aliassym) |
| { |
| td = ad->aliassym->isTemplateDeclaration(); |
| } |
| } |
| while (td) |
| { |
| Dsymbol *sym = getEponymousMember(td); |
| if (sym) |
| { |
| Parameter *fparam = isFunctionParameter(sym, p, len); |
| if (fparam) |
| { |
| return fparam; |
| } |
| } |
| td = td->overnext; |
| } |
| } |
| return NULL; |
| } |
| |
| static TemplateDeclaration *getEponymousParent(Dsymbol *s) |
| { |
| if (!s->parent) |
| return NULL; |
| TemplateDeclaration *td = s->parent->isTemplateDeclaration(); |
| return (td && getEponymousMember(td)) ? td : NULL; |
| } |
| |
| static const char ddoc_default[] = "\ |
| DDOC = <html><head>\n\ |
| <META http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n\ |
| <title>$(TITLE)</title>\n\ |
| </head><body>\n\ |
| <h1>$(TITLE)</h1>\n\ |
| $(BODY)\n\ |
| <hr>$(SMALL Page generated by $(LINK2 http://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT))\n\ |
| </body></html>\n\ |
| \n\ |
| B = <b>$0</b>\n\ |
| I = <i>$0</i>\n\ |
| U = <u>$0</u>\n\ |
| P = <p>$0</p>\n\ |
| DL = <dl>$0</dl>\n\ |
| DT = <dt>$0</dt>\n\ |
| DD = <dd>$0</dd>\n\ |
| TABLE = <table>$0</table>\n\ |
| TR = <tr>$0</tr>\n\ |
| TH = <th>$0</th>\n\ |
| TD = <td>$0</td>\n\ |
| OL = <ol>$0</ol>\n\ |
| UL = <ul>$0</ul>\n\ |
| LI = <li>$0</li>\n\ |
| BIG = <big>$0</big>\n\ |
| SMALL = <small>$0</small>\n\ |
| BR = <br>\n\ |
| LINK = <a href=\"$0\">$0</a>\n\ |
| LINK2 = <a href=\"$1\">$+</a>\n\ |
| LPAREN= (\n\ |
| RPAREN= )\n\ |
| BACKTICK= `\n\ |
| DOLLAR= $\n\ |
| DEPRECATED= $0\n\ |
| \n\ |
| RED = <font color=red>$0</font>\n\ |
| BLUE = <font color=blue>$0</font>\n\ |
| GREEN = <font color=green>$0</font>\n\ |
| YELLOW =<font color=yellow>$0</font>\n\ |
| BLACK = <font color=black>$0</font>\n\ |
| WHITE = <font color=white>$0</font>\n\ |
| \n\ |
| D_CODE = <pre class=\"d_code\">$0</pre>\n\ |
| DDOC_BACKQUOTED = $(D_INLINECODE $0)\n\ |
| D_INLINECODE = <pre style=\"display:inline;\" class=\"d_inline_code\">$0</pre>\n\ |
| D_COMMENT = $(GREEN $0)\n\ |
| D_STRING = $(RED $0)\n\ |
| D_KEYWORD = $(BLUE $0)\n\ |
| D_PSYMBOL = $(U $0)\n\ |
| D_PARAM = $(I $0)\n\ |
| \n\ |
| DDOC_COMMENT = <!-- $0 -->\n\ |
| DDOC_DECL = $(DT $(BIG $0))\n\ |
| DDOC_DECL_DD = $(DD $0)\n\ |
| DDOC_DITTO = $(BR)$0\n\ |
| DDOC_SECTIONS = $0\n\ |
| DDOC_SUMMARY = $0$(BR)$(BR)\n\ |
| DDOC_DESCRIPTION = $0$(BR)$(BR)\n\ |
| DDOC_AUTHORS = $(B Authors:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_BUGS = $(RED BUGS:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_COPYRIGHT = $(B Copyright:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_DATE = $(B Date:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_DEPRECATED = $(RED Deprecated:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_EXAMPLES = $(B Examples:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_HISTORY = $(B History:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_LICENSE = $(B License:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_RETURNS = $(B Returns:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_SEE_ALSO = $(B See Also:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_STANDARDS = $(B Standards:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_THROWS = $(B Throws:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_VERSION = $(B Version:)$(BR)\n$0$(BR)$(BR)\n\ |
| DDOC_SECTION_H = $(B $0)$(BR)\n\ |
| DDOC_SECTION = $0$(BR)$(BR)\n\ |
| DDOC_MEMBERS = $(DL $0)\n\ |
| DDOC_MODULE_MEMBERS = $(DDOC_MEMBERS $0)\n\ |
| DDOC_CLASS_MEMBERS = $(DDOC_MEMBERS $0)\n\ |
| DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0)\n\ |
| DDOC_ENUM_MEMBERS = $(DDOC_MEMBERS $0)\n\ |
| DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0)\n\ |
| DDOC_ENUM_BASETYPE = $0\n\ |
| DDOC_PARAMS = $(B Params:)$(BR)\n$(TABLE $0)$(BR)\n\ |
| DDOC_PARAM_ROW = $(TR $0)\n\ |
| DDOC_PARAM_ID = $(TD $0)\n\ |
| DDOC_PARAM_DESC = $(TD $0)\n\ |
| DDOC_BLANKLINE = $(BR)$(BR)\n\ |
| \n\ |
| DDOC_ANCHOR = <a name=\"$1\"></a>\n\ |
| DDOC_PSYMBOL = $(U $0)\n\ |
| DDOC_PSUPER_SYMBOL = $(U $0)\n\ |
| DDOC_KEYWORD = $(B $0)\n\ |
| DDOC_PARAM = $(I $0)\n\ |
| \n\ |
| ESCAPES = /</</\n\ |
| />/>/\n\ |
| /&/&/\n\ |
| "; |
| |
| static const char ddoc_decl_s[] = "$(DDOC_DECL "; |
| static const char ddoc_decl_e[] = ")\n"; |
| |
| static const char ddoc_decl_dd_s[] = "$(DDOC_DECL_DD "; |
| static const char ddoc_decl_dd_e[] = ")\n"; |
| |
| |
| /**************************************************** |
| */ |
| |
| void gendocfile(Module *m) |
| { |
| static OutBuffer mbuf; |
| static int mbuf_done; |
| |
| OutBuffer buf; |
| |
| //printf("Module::gendocfile()\n"); |
| |
| if (!mbuf_done) // if not already read the ddoc files |
| { |
| mbuf_done = 1; |
| |
| // Use our internal default |
| mbuf.write(ddoc_default, strlen(ddoc_default)); |
| |
| // Override with DDOCFILE specified in the sc.ini file |
| char *p = getenv("DDOCFILE"); |
| if (p) |
| global.params.ddocfiles->shift(p); |
| |
| // Override with the ddoc macro files from the command line |
| for (size_t i = 0; i < global.params.ddocfiles->dim; i++) |
| { |
| FileName f((*global.params.ddocfiles)[i]); |
| File file(&f); |
| readFile(m->loc, &file); |
| // BUG: convert file contents to UTF-8 before use |
| |
| //printf("file: '%.*s'\n", file.len, file.buffer); |
| mbuf.write(file.buffer, file.len); |
| } |
| } |
| DocComment::parseMacros(&m->escapetable, &m->macrotable, (utf8_t *)mbuf.data, mbuf.offset); |
| |
| Scope *sc = Scope::createGlobal(m); // create root scope |
| |
| DocComment *dc = DocComment::parse(m, m->comment); |
| dc->pmacrotable = &m->macrotable; |
| dc->pescapetable = &m->escapetable; |
| sc->lastdc = dc; |
| |
| // Generate predefined macros |
| |
| // Set the title to be the name of the module |
| { |
| const char *p = m->toPrettyChars(); |
| Macro::define(&m->macrotable, (const utf8_t *)"TITLE", 5, (const utf8_t *)p, strlen(p)); |
| } |
| |
| // Set time macros |
| { |
| time_t t; |
| time(&t); |
| char *p = ctime(&t); |
| p = mem.xstrdup(p); |
| Macro::define(&m->macrotable, (const utf8_t *)"DATETIME", 8, (const utf8_t *)p, strlen(p)); |
| Macro::define(&m->macrotable, (const utf8_t *)"YEAR", 4, (const utf8_t *)p + 20, 4); |
| } |
| |
| const char *srcfilename = m->srcfile->toChars(); |
| Macro::define(&m->macrotable, (const utf8_t *)"SRCFILENAME", 11, (const utf8_t *)srcfilename, strlen(srcfilename)); |
| |
| const char *docfilename = m->docfile->toChars(); |
| Macro::define(&m->macrotable, (const utf8_t *)"DOCFILENAME", 11, (const utf8_t *)docfilename, strlen(docfilename)); |
| |
| if (dc->copyright) |
| { |
| dc->copyright->nooutput = 1; |
| Macro::define(&m->macrotable, (const utf8_t *)"COPYRIGHT", 9, dc->copyright->body, dc->copyright->bodylen); |
| } |
| |
| buf.printf("$(DDOC_COMMENT Generated by Ddoc from %s)\n", m->srcfile->toChars()); |
| if (m->isDocFile) |
| { |
| Loc loc = m->md ? m->md->loc : m->loc; |
| size_t commentlen = strlen((const char *)m->comment); |
| Dsymbols a; |
| // Bugzilla 9764: Don't push m in a, to prevent emphasize ddoc file name. |
| if (dc->macros) |
| { |
| commentlen = dc->macros->name - m->comment; |
| dc->macros->write(loc, dc, sc, &a, &buf); |
| } |
| buf.write(m->comment, commentlen); |
| highlightText(sc, &a, &buf, 0); |
| } |
| else |
| { |
| Dsymbols a; |
| a.push(m); |
| dc->writeSections(sc, &a, &buf); |
| emitMemberComments(m, &buf, sc); |
| } |
| |
| //printf("BODY= '%.*s'\n", buf.offset, buf.data); |
| Macro::define(&m->macrotable, (const utf8_t *)"BODY", 4, (const utf8_t *)buf.data, buf.offset); |
| |
| OutBuffer buf2; |
| buf2.writestring("$(DDOC)\n"); |
| size_t end = buf2.offset; |
| m->macrotable->expand(&buf2, 0, &end, NULL, 0); |
| |
| /* Remove all the escape sequences from buf2, |
| * and make CR-LF the newline. |
| */ |
| { |
| buf.setsize(0); |
| buf.reserve(buf2.offset); |
| utf8_t *p = (utf8_t *)buf2.data; |
| for (size_t j = 0; j < buf2.offset; j++) |
| { |
| utf8_t c = p[j]; |
| if (c == 0xFF && j + 1 < buf2.offset) |
| { |
| j++; |
| continue; |
| } |
| if (c == '\n') |
| buf.writeByte('\r'); |
| else if (c == '\r') |
| { |
| buf.writestring("\r\n"); |
| if (j + 1 < buf2.offset && p[j + 1] == '\n') |
| { |
| j++; |
| } |
| continue; |
| } |
| buf.writeByte(c); |
| } |
| } |
| |
| // Transfer image to file |
| assert(m->docfile); |
| m->docfile->setbuffer(buf.data, buf.offset); |
| m->docfile->ref = 1; |
| ensurePathToNameExists(Loc(), m->docfile->toChars()); |
| writeFile(m->loc, m->docfile); |
| } |
| |
| /**************************************************** |
| * Having unmatched parentheses can hose the output of Ddoc, |
| * as the macros depend on properly nested parentheses. |
| * This function replaces all ( with $(LPAREN) and ) with $(RPAREN) |
| * to preserve text literally. This also means macros in the |
| * text won't be expanded. |
| */ |
| void escapeDdocString(OutBuffer *buf, size_t start) |
| { |
| for (size_t u = start; u < buf->offset; u++) |
| { |
| utf8_t c = buf->data[u]; |
| switch(c) |
| { |
| case '$': |
| buf->remove(u, 1); |
| buf->insert(u, (const char *)"$(DOLLAR)", 9); |
| u += 8; |
| break; |
| |
| case '(': |
| buf->remove(u, 1); //remove the ( |
| buf->insert(u, (const char *)"$(LPAREN)", 9); //insert this instead |
| u += 8; //skip over newly inserted macro |
| break; |
| |
| case ')': |
| buf->remove(u, 1); //remove the ) |
| buf->insert(u, (const char *)"$(RPAREN)", 9); //insert this instead |
| u += 8; //skip over newly inserted macro |
| break; |
| } |
| } |
| } |
| |
| /**************************************************** |
| * Having unmatched parentheses can hose the output of Ddoc, |
| * as the macros depend on properly nested parentheses. |
| |
| * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN). |
| */ |
| void escapeStrayParenthesis(Loc loc, OutBuffer *buf, size_t start) |
| { |
| unsigned par_open = 0; |
| |
| for (size_t u = start; u < buf->offset; u++) |
| { |
| utf8_t c = buf->data[u]; |
| switch(c) |
| { |
| case '(': |
| par_open++; |
| break; |
| |
| case ')': |
| if (par_open == 0) |
| { |
| //stray ')' |
| warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output." |
| " Use $(RPAREN) instead for unpaired right parentheses."); |
| buf->remove(u, 1); //remove the ) |
| buf->insert(u, (const char *)"$(RPAREN)", 9); //insert this instead |
| u += 8; //skip over newly inserted macro |
| } |
| else |
| par_open--; |
| break; |
| } |
| } |
| |
| if (par_open) // if any unmatched lparens |
| { |
| par_open = 0; |
| for (size_t u = buf->offset; u > start;) |
| { |
| u--; |
| utf8_t c = buf->data[u]; |
| switch(c) |
| { |
| case ')': |
| par_open++; |
| break; |
| |
| case '(': |
| if (par_open == 0) |
| { |
| //stray '(' |
| warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output." |
| " Use $(LPAREN) instead for unpaired left parentheses."); |
| buf->remove(u, 1); //remove the ( |
| buf->insert(u, (const char *)"$(LPAREN)", 9); //insert this instead |
| } |
| else |
| par_open--; |
| break; |
| } |
| } |
| } |
| } |
| |
| // Basically, this is to skip over things like private{} blocks in a struct or |
| // class definition that don't add any components to the qualified name. |
| static Scope *skipNonQualScopes(Scope *sc) |
| { |
| while (sc && !sc->scopesym) |
| sc = sc->enclosing; |
| return sc; |
| } |
| |
| static bool emitAnchorName(OutBuffer *buf, Dsymbol *s, Scope *sc) |
| { |
| if (!s || s->isPackage() || s->isModule()) |
| return false; |
| |
| // Add parent names first |
| bool dot = false; |
| if (s->parent) |
| dot = emitAnchorName(buf, s->parent, sc); |
| else if (sc) |
| dot = emitAnchorName(buf, sc->scopesym, skipNonQualScopes(sc->enclosing)); |
| |
| // Eponymous template members can share the parent anchor name |
| if (getEponymousParent(s)) |
| return dot; |
| if (dot) |
| buf->writeByte('.'); |
| |
| // Use "this" not "__ctor" |
| TemplateDeclaration *td; |
| if (s->isCtorDeclaration() || ((td = s->isTemplateDeclaration()) != NULL && |
| td->onemember && td->onemember->isCtorDeclaration())) |
| { |
| buf->writestring("this"); |
| } |
| else |
| { |
| /* We just want the identifier, not overloads like TemplateDeclaration::toChars. |
| * We don't want the template parameter list and constraints. */ |
| buf->writestring(s->Dsymbol::toChars()); |
| } |
| return true; |
| } |
| |
| static void emitAnchor(OutBuffer *buf, Dsymbol *s, Scope *sc) |
| { |
| Identifier *ident; |
| { |
| OutBuffer anc; |
| emitAnchorName(&anc, s, skipNonQualScopes(sc)); |
| ident = Identifier::idPool(anc.peekString()); |
| } |
| size_t *count = (size_t*)dmd_aaGet(&sc->anchorCounts, (void *)ident); |
| TemplateDeclaration *td = getEponymousParent(s); |
| // don't write an anchor for matching consecutive ditto symbols |
| if (*count > 0 && sc->prevAnchor == ident && |
| sc->lastdc && (isDitto(s->comment) || (td && isDitto(td->comment)))) |
| return; |
| |
| (*count)++; |
| // cache anchor name |
| sc->prevAnchor = ident; |
| |
| buf->writestring("$(DDOC_ANCHOR "); |
| buf->writestring(ident->toChars()); |
| // only append count once there's a duplicate |
| if (*count != 1) |
| buf->printf(".%u", *count); |
| buf->writeByte(')'); |
| } |
| |
| /******************************* emitComment **********************************/ |
| |
| /** Get leading indentation from 'src' which represents lines of code. */ |
| static size_t getCodeIndent(const char *src) |
| { |
| while (src && (*src == '\r' || *src == '\n')) |
| ++src; // skip until we find the first non-empty line |
| |
| size_t codeIndent = 0; |
| while (src && (*src == ' ' || *src == '\t')) |
| { |
| codeIndent++; |
| src++; |
| } |
| return codeIndent; |
| } |
| |
| /** Recursively expand template mixin member docs into the scope. */ |
| static void expandTemplateMixinComments(TemplateMixin *tm, OutBuffer *buf, Scope *sc) |
| { |
| if (!tm->semanticRun) tm->semantic(sc); |
| TemplateDeclaration *td = (tm && tm->tempdecl) ? |
| tm->tempdecl->isTemplateDeclaration() : NULL; |
| if (td && td->members) |
| { |
| for (size_t i = 0; i < td->members->dim; i++) |
| { |
| Dsymbol *sm = (*td->members)[i]; |
| TemplateMixin *tmc = sm->isTemplateMixin(); |
| if (tmc && tmc->comment) |
| expandTemplateMixinComments(tmc, buf, sc); |
| else |
| emitComment(sm, buf, sc); |
| } |
| } |
| } |
| |
| void emitMemberComments(ScopeDsymbol *sds, OutBuffer *buf, Scope *sc) |
| { |
| if (!sds->members) |
| return; |
| |
| //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars()); |
| |
| const char *m = "$(DDOC_MEMBERS "; |
| if (sds->isTemplateDeclaration()) |
| m = "$(DDOC_TEMPLATE_MEMBERS "; |
| else if (sds->isClassDeclaration()) |
| m = "$(DDOC_CLASS_MEMBERS "; |
| else if (sds->isStructDeclaration()) |
| m = "$(DDOC_STRUCT_MEMBERS "; |
| else if (sds->isEnumDeclaration()) |
| m = "$(DDOC_ENUM_MEMBERS "; |
| else if (sds->isModule()) |
| m = "$(DDOC_MODULE_MEMBERS "; |
| |
| size_t offset1 = buf->offset; // save starting offset |
| buf->writestring(m); |
| size_t offset2 = buf->offset; // to see if we write anything |
| |
| sc = sc->push(sds); |
| |
| for (size_t i = 0; i < sds->members->dim; i++) |
| { |
| Dsymbol *s = (*sds->members)[i]; |
| //printf("\ts = '%s'\n", s->toChars()); |
| |
| // only expand if parent is a non-template (semantic won't work) |
| if (s->comment && s->isTemplateMixin() && s->parent && !s->parent->isTemplateDeclaration()) |
| expandTemplateMixinComments((TemplateMixin *)s, buf, sc); |
| |
| emitComment(s, buf, sc); |
| } |
| emitComment(NULL, buf, sc); |
| |
| sc->pop(); |
| |
| if (buf->offset == offset2) |
| { |
| /* Didn't write out any members, so back out last write |
| */ |
| buf->offset = offset1; |
| } |
| else |
| buf->writestring(")\n"); |
| } |
| |
| void emitProtection(OutBuffer *buf, Prot prot) |
| { |
| if (prot.kind != PROTundefined && prot.kind != PROTpublic) |
| { |
| protectionToBuffer(buf, prot); |
| buf->writeByte(' '); |
| } |
| } |
| |
| void emitComment(Dsymbol *s, OutBuffer *buf, Scope *sc) |
| { |
| class EmitComment : public Visitor |
| { |
| public: |
| OutBuffer *buf; |
| Scope *sc; |
| |
| EmitComment(OutBuffer *buf, Scope *sc) |
| : buf(buf), sc(sc) |
| { |
| } |
| |
| void visit(Dsymbol *) {} |
| void visit(InvariantDeclaration *) {} |
| void visit(UnitTestDeclaration *) {} |
| void visit(PostBlitDeclaration *) {} |
| void visit(DtorDeclaration *) {} |
| void visit(StaticCtorDeclaration *) {} |
| void visit(StaticDtorDeclaration *) {} |
| void visit(TypeInfoDeclaration *) {} |
| |
| void emit(Scope *sc, Dsymbol *s, const utf8_t *com) |
| { |
| if (s && sc->lastdc && isDitto(com)) |
| { |
| sc->lastdc->a.push(s); |
| return; |
| } |
| |
| // Put previous doc comment if exists |
| if (DocComment *dc = sc->lastdc) |
| { |
| // Put the declaration signatures as the document 'title' |
| buf->writestring(ddoc_decl_s); |
| for (size_t i = 0; i < dc->a.dim; i++) |
| { |
| Dsymbol *sx = dc->a[i]; |
| |
| if (i == 0) |
| { |
| size_t o = buf->offset; |
| toDocBuffer(sx, buf, sc); |
| highlightCode(sc, sx, buf, o); |
| continue; |
| } |
| |
| buf->writestring("$(DDOC_DITTO "); |
| { |
| size_t o = buf->offset; |
| toDocBuffer(sx, buf, sc); |
| highlightCode(sc, sx, buf, o); |
| } |
| buf->writeByte(')'); |
| } |
| buf->writestring(ddoc_decl_e); |
| |
| // Put the ddoc comment as the document 'description' |
| buf->writestring(ddoc_decl_dd_s); |
| { |
| dc->writeSections(sc, &dc->a, buf); |
| if (ScopeDsymbol *sds = dc->a[0]->isScopeDsymbol()) |
| emitMemberComments(sds, buf, sc); |
| } |
| buf->writestring(ddoc_decl_dd_e); |
| //printf("buf.2 = [[%.*s]]\n", buf->offset - o0, buf->data + o0); |
| } |
| |
| if (s) |
| { |
| DocComment *dc = DocComment::parse(s, com); |
| dc->pmacrotable = &sc->_module->macrotable; |
| sc->lastdc = dc; |
| } |
| } |
| |
| void visit(Declaration *d) |
| { |
| //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d->toChars(), d->comment); |
| //printf("type = %p\n", d->type); |
| const utf8_t *com = d->comment; |
| if (TemplateDeclaration *td = getEponymousParent(d)) |
| { |
| if (isDitto(td->comment)) |
| com = td->comment; |
| else |
| com = Lexer::combineComments(td->comment, com); |
| } |
| else |
| { |
| if (!d->ident) |
| return; |
| if (!d->type && !d->isCtorDeclaration() && !d->isAliasDeclaration()) |
| return; |
| if (d->protection.kind == PROTprivate || sc->protection.kind == PROTprivate) |
| return; |
| } |
| if (!com) |
| return; |
| |
| emit(sc, d, com); |
| } |
| |
| void visit(AggregateDeclaration *ad) |
| { |
| //printf("AggregateDeclaration::emitComment() '%s'\n", ad->toChars()); |
| const utf8_t *com = ad->comment; |
| if (TemplateDeclaration *td = getEponymousParent(ad)) |
| { |
| if (isDitto(td->comment)) |
| com = td->comment; |
| else |
| com = Lexer::combineComments(td->comment, com); |
| } |
| else |
| { |
| if (ad->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) |
| return; |
| if (!ad->comment) |
| return; |
| } |
| if (!com) |
| return; |
| |
| emit(sc, ad, com); |
| } |
| |
| void visit(TemplateDeclaration *td) |
| { |
| //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td->toChars(), td->kind()); |
| if (td->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) |
| return; |
| if (!td->comment) |
| return; |
| |
| if (Dsymbol *ss = getEponymousMember(td)) |
| { |
| ss->accept(this); |
| return; |
| } |
| emit(sc, td, td->comment); |
| } |
| |
| void visit(EnumDeclaration *ed) |
| { |
| if (ed->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) |
| return; |
| if (ed->isAnonymous() && ed->members) |
| { |
| for (size_t i = 0; i < ed->members->dim; i++) |
| { |
| Dsymbol *s = (*ed->members)[i]; |
| emitComment(s, buf, sc); |
| } |
| return; |
| } |
| if (!ed->comment) |
| return; |
| if (ed->isAnonymous()) |
| return; |
| |
| emit(sc, ed, ed->comment); |
| } |
| |
| void visit(EnumMember *em) |
| { |
| //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em->toChars(), em->comment); |
| if (em->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) |
| return; |
| if (!em->comment) |
| return; |
| |
| emit(sc, em, em->comment); |
| } |
| |
| void visit(AttribDeclaration *ad) |
| { |
| //printf("AttribDeclaration::emitComment(sc = %p)\n", sc); |
| |
| /* A general problem with this, illustrated by BUGZILLA 2516, |
| * is that attributes are not transmitted through to the underlying |
| * member declarations for template bodies, because semantic analysis |
| * is not done for template declaration bodies |
| * (only template instantiations). |
| * Hence, Ddoc omits attributes from template members. |
| */ |
| |
| Dsymbols *d = ad->include(NULL, NULL); |
| |
| if (d) |
| { |
| for (size_t i = 0; i < d->dim; i++) |
| { |
| Dsymbol *s = (*d)[i]; |
| //printf("AttribDeclaration::emitComment %s\n", s->toChars()); |
| emitComment(s, buf, sc); |
| } |
| } |
| } |
| |
| void visit(ProtDeclaration *pd) |
| { |
| if (pd->decl) |
| { |
| Scope *scx = sc; |
| sc = sc->copy(); |
| sc->protection = pd->protection; |
| visit((AttribDeclaration *)pd); |
| scx->lastdc = sc->lastdc; |
| sc = sc->pop(); |
| } |
| } |
| |
| void visit(ConditionalDeclaration *cd) |
| { |
| //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc); |
| if (cd->condition->inc) |
| { |
| visit((AttribDeclaration *)cd); |
| return; |
| } |
| |
| /* If generating doc comment, be careful because if we're inside |
| * a template, then include(NULL, NULL) will fail. |
| */ |
| Dsymbols *d = cd->decl ? cd->decl : cd->elsedecl; |
| for (size_t i = 0; i < d->dim; i++) |
| { |
| Dsymbol *s = (*d)[i]; |
| emitComment(s, buf, sc); |
| } |
| } |
| }; |
| |
| EmitComment v(buf, sc); |
| |
| if (!s) |
| v.emit(sc, NULL, NULL); |
| else |
| s->accept(&v); |
| } |
| |
| /******************************* toDocBuffer **********************************/ |
| |
| void toDocBuffer(Dsymbol *s, OutBuffer *buf, Scope *sc) |
| { |
| class ToDocBuffer : public Visitor |
| { |
| public: |
| OutBuffer *buf; |
| Scope *sc; |
| |
| ToDocBuffer(OutBuffer *buf, Scope *sc) |
| : buf(buf), sc(sc) |
| { |
| } |
| |
| void visit(Dsymbol *s) |
| { |
| //printf("Dsymbol::toDocbuffer() %s\n", s->toChars()); |
| HdrGenState hgs; |
| hgs.ddoc = true; |
| ::toCBuffer(s, buf, &hgs); |
| } |
| |
| void prefix(Dsymbol *s) |
| { |
| if (s->isDeprecated()) |
| buf->writestring("deprecated "); |
| |
| if (Declaration *d = s->isDeclaration()) |
| { |
| emitProtection(buf, d->protection); |
| |
| if (d->isStatic()) |
| buf->writestring("static "); |
| else if (d->isFinal()) |
| buf->writestring("final "); |
| else if (d->isAbstract()) |
| buf->writestring("abstract "); |
| |
| if (!d->isFuncDeclaration()) // functionToBufferFull handles this |
| { |
| if (d->isConst()) |
| buf->writestring("const "); |
| if (d->isImmutable()) |
| buf->writestring("immutable "); |
| if (d->isSynchronized()) |
| buf->writestring("synchronized "); |
| |
| if (d->storage_class & STCmanifest) |
| buf->writestring("enum "); |
| } |
| } |
| } |
| |
| void visit(Declaration *d) |
| { |
| if (!d->ident) |
| return; |
| |
| TemplateDeclaration *td = getEponymousParent(d); |
| //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d->toChars(), d->originalType ? d->originalType->toChars() : "--", td ? td->toChars() : "--"); |
| |
| HdrGenState hgs; |
| hgs.ddoc = true; |
| |
| if (d->isDeprecated()) |
| buf->writestring("$(DEPRECATED "); |
| |
| prefix(d); |
| |
| if (d->type) |
| { |
| Type *origType = d->originalType ? d->originalType : d->type; |
| if (origType->ty == Tfunction) |
| { |
| functionToBufferFull((TypeFunction *)origType, buf, d->ident, &hgs, td); |
| } |
| else |
| ::toCBuffer(origType, buf, d->ident, &hgs); |
| } |
| else |
| buf->writestring(d->ident->toChars()); |
| |
| if (d->isVarDeclaration() && td) |
| { |
| buf->writeByte('('); |
| if (td->origParameters && td->origParameters->dim) |
| { |
| for (size_t i = 0; i < td->origParameters->dim; i++) |
| { |
| if (i) |
| buf->writestring(", "); |
| toCBuffer((*td->origParameters)[i], buf, &hgs); |
| } |
| } |
| buf->writeByte(')'); |
| } |
| |
| // emit constraints if declaration is a templated declaration |
| if (td && td->constraint) |
| { |
| buf->writestring(" if ("); |
| ::toCBuffer(td->constraint, buf, &hgs); |
| buf->writeByte(')'); |
| } |
| |
| if (d->isDeprecated()) |
| buf->writestring(")"); |
| |
| buf->writestring(";\n"); |
| } |
| |
| void visit(AliasDeclaration *ad) |
| { |
| //printf("AliasDeclaration::toDocbuffer() %s\n", ad->toChars()); |
| if (!ad->ident) |
| return; |
| |
| if (ad->isDeprecated()) |
| buf->writestring("deprecated "); |
| |
| emitProtection(buf, ad->protection); |
| buf->printf("alias %s = ", ad->toChars()); |
| |
| if (Dsymbol *s = ad->aliassym) // ident alias |
| { |
| prettyPrintDsymbol(s, ad->parent); |
| } |
| else if (Type *type = ad->getType()) // type alias |
| { |
| if (type->ty == Tclass || type->ty == Tstruct || type->ty == Tenum) |
| { |
| if (Dsymbol *s = type->toDsymbol(NULL)) // elaborate type |
| prettyPrintDsymbol(s, ad->parent); |
| else |
| buf->writestring(type->toChars()); |
| } |
| else |
| { |
| // simple type |
| buf->writestring(type->toChars()); |
| } |
| } |
| |
| buf->writestring(";\n"); |
| } |
| |
| void parentToBuffer(Dsymbol *s) |
| { |
| if (s && !s->isPackage() && !s->isModule()) |
| { |
| parentToBuffer(s->parent); |
| buf->writestring(s->toChars()); |
| buf->writestring("."); |
| } |
| } |
| |
| static bool inSameModule(Dsymbol *s, Dsymbol *p) |
| { |
| for ( ; s ; s = s->parent) |
| { |
| if (s->isModule()) |
| break; |
| } |
| |
| for ( ; p ; p = p->parent) |
| { |
| if (p->isModule()) |
| break; |
| } |
| |
| return s == p; |
| } |
| |
| void prettyPrintDsymbol(Dsymbol *s, Dsymbol *parent) |
| { |
| if (s->parent && (s->parent == parent)) // in current scope -> naked name |
| { |
| buf->writestring(s->toChars()); |
| } |
| else if (!inSameModule(s, parent)) // in another module -> full name |
| { |
| buf->writestring(s->toPrettyChars()); |
| } |
| else // nested in a type in this module -> full name w/o module name |
| { |
| // if alias is nested in a user-type use module-scope lookup |
| if (!parent->isModule() && !parent->isPackage()) |
| buf->writestring("."); |
| |
| parentToBuffer(s->parent); |
| buf->writestring(s->toChars()); |
| } |
| } |
| |
| void visit(AggregateDeclaration *ad) |
| { |
| if (!ad->ident) |
| return; |
| |
| buf->printf("%s %s", ad->kind(), ad->toChars()); |
| buf->writestring(";\n"); |
| } |
| |
| void visit(StructDeclaration *sd) |
| { |
| //printf("StructDeclaration::toDocbuffer() %s\n", sd->toChars()); |
| if (!sd->ident) |
| return; |
| |
| if (TemplateDeclaration *td = getEponymousParent(sd)) |
| { |
| toDocBuffer(td, buf, sc); |
| } |
| else |
| { |
| buf->printf("%s %s", sd->kind(), sd->toChars()); |
| } |
| buf->writestring(";\n"); |
| } |
| |
| void visit(ClassDeclaration *cd) |
| { |
| //printf("ClassDeclaration::toDocbuffer() %s\n", cd->toChars()); |
| if (!cd->ident) |
| return; |
| |
| if (TemplateDeclaration *td = getEponymousParent(cd)) |
| { |
| toDocBuffer(td, buf, sc); |
| } |
| else |
| { |
| if (!cd->isInterfaceDeclaration() && cd->isAbstract()) |
| buf->writestring("abstract "); |
| buf->printf("%s %s", cd->kind(), cd->toChars()); |
| } |
| int any = 0; |
| for (size_t i = 0; i < cd->baseclasses->dim; i++) |
| { |
| BaseClass *bc = (*cd->baseclasses)[i]; |
| |
| if (bc->sym && bc->sym->ident == Id::Object) |
| continue; |
| |
| if (any) |
| buf->writestring(", "); |
| else |
| { |
| buf->writestring(": "); |
| any = 1; |
| } |
| emitProtection(buf, Prot(PROTpublic)); |
| if (bc->sym) |
| { |
| buf->printf("$(DDOC_PSUPER_SYMBOL %s)", bc->sym->toPrettyChars()); |
| } |
| else |
| { |
| HdrGenState hgs; |
| ::toCBuffer(bc->type, buf, NULL, &hgs); |
| } |
| } |
| buf->writestring(";\n"); |
| } |
| |
| void visit(EnumDeclaration *ed) |
| { |
| if (!ed->ident) |
| return; |
| |
| buf->printf("%s %s", ed->kind(), ed->toChars()); |
| if (ed->memtype) |
| { |
| buf->writestring(": $(DDOC_ENUM_BASETYPE "); |
| HdrGenState hgs; |
| ::toCBuffer(ed->memtype, buf, NULL, &hgs); |
| buf->writestring(")"); |
| } |
| buf->writestring(";\n"); |
| } |
| |
| void visit(EnumMember *em) |
| { |
| if (!em->ident) |
| return; |
| |
| buf->writestring(em->toChars()); |
| } |
| }; |
| |
| ToDocBuffer v(buf, sc); |
| s->accept(&v); |
| } |
| |
| /********************************* DocComment *********************************/ |
| |
| DocComment *DocComment::parse(Dsymbol *s, const utf8_t *comment) |
| { |
| //printf("parse(%s): '%s'\n", s->toChars(), comment); |
| DocComment *dc = new DocComment(); |
| dc->a.push(s); |
| if (!comment) |
| return dc; |
| |
| dc->parseSections(comment); |
| |
| for (size_t i = 0; i < dc->sections.dim; i++) |
| { |
| Section *sec = dc->sections[i]; |
| |
| if (icmp("copyright", sec->name, sec->namelen) == 0) |
| { |
| dc->copyright = sec; |
| } |
| if (icmp("macros", sec->name, sec->namelen) == 0) |
| { |
| dc->macros = sec; |
| } |
| } |
| |
| return dc; |
| } |
| |
| /***************************************** |
| * Parse next paragraph out of *pcomment. |
| * Update *pcomment to point past paragraph. |
| * Returns NULL if no more paragraphs. |
| * If paragraph ends in 'identifier:', |
| * then (*pcomment)[0 .. idlen] is the identifier. |
| */ |
| |
| void DocComment::parseSections(const utf8_t *comment) |
| { |
| const utf8_t *p; |
| const utf8_t *pstart; |
| const utf8_t *pend; |
| const utf8_t *idstart = NULL; // dead-store to prevent spurious warning |
| size_t idlen; |
| |
| const utf8_t *name = NULL; |
| size_t namelen = 0; |
| |
| //printf("parseSections('%s')\n", comment); |
| p = comment; |
| while (*p) |
| { |
| const utf8_t *pstart0 = p; |
| p = skipwhitespace(p); |
| pstart = p; |
| pend = p; |
| |
| /* Find end of section, which is ended by one of: |
| * 'identifier:' (but not inside a code section) |
| * '\0' |
| */ |
| idlen = 0; |
| int inCode = 0; |
| while (1) |
| { |
| // Check for start/end of a code section |
| if (*p == '-') |
| { |
| if (!inCode) |
| { |
| // restore leading indentation |
| while (pstart0 < pstart && isIndentWS(pstart-1)) --pstart; |
| } |
| |
| int numdash = 0; |
| while (*p == '-') |
| { |
| ++numdash; |
| p++; |
| } |
| // BUG: handle UTF PS and LS too |
| if ((!*p || *p == '\r' || *p == '\n') && numdash >= 3) |
| inCode ^= 1; |
| pend = p; |
| } |
| |
| if (!inCode && isIdStart(p)) |
| { |
| const utf8_t *q = p + utfStride(p); |
| while (isIdTail(q)) |
| q += utfStride(q); |
| // Detected tag ends it |
| if (*q == ':' && isupper(*p) |
| && (isspace(q[1]) || q[1] == 0)) |
| { |
| idlen = q - p; |
| idstart = p; |
| for (pend = p; pend > pstart; pend--) |
| { |
| if (pend[-1] == '\n') |
| break; |
| } |
| p = q + 1; |
| break; |
| } |
| } |
| while (1) |
| { |
| if (!*p) |
| goto L1; |
| if (*p == '\n') |
| { |
| p++; |
| if (*p == '\n' && !summary && !namelen && !inCode) |
| { |
| pend = p; |
| p++; |
| goto L1; |
| } |
| break; |
| } |
| p++; |
| pend = p; |
| } |
| p = skipwhitespace(p); |
| } |
| L1: |
| |
| if (namelen || pstart < pend) |
| { |
| Section *s; |
| if (icmp("Params", name, namelen) == 0) |
| s = new ParamSection(); |
| else if (icmp("Macros", name, namelen) == 0) |
| s = new MacroSection(); |
| else |
| s = new Section(); |
| s->name = name; |
| s->namelen = namelen; |
| s->body = pstart; |
| s->bodylen = pend - pstart; |
| s->nooutput = 0; |
| |
| //printf("Section: '%.*s' = '%.*s'\n", s->namelen, s->name, s->bodylen, s->body); |
| |
| sections.push(s); |
| |
| if (!summary && !namelen) |
| summary = s; |
| } |
| |
| if (idlen) |
| { |
| name = idstart; |
| namelen = idlen; |
| } |
| else |
| { |
| name = NULL; |
| namelen = 0; |
| if (!*p) |
| break; |
| } |
| } |
| } |
| |
| void DocComment::writeSections(Scope *sc, Dsymbols *a, OutBuffer *buf) |
| { |
| assert(a->dim); |
| |
| //printf("DocComment::writeSections()\n"); |
| Loc loc = (*a)[0]->loc; |
| if (Module *m = (*a)[0]->isModule()) |
| { |
| if (m->md) |
| loc = m->md->loc; |
| } |
| |
| size_t offset1 = buf->offset; |
| buf->writestring("$(DDOC_SECTIONS "); |
| size_t offset2 = buf->offset; |
| |
| for (size_t i = 0; i < sections.dim; i++) |
| { |
| Section *sec = sections[i]; |
| if (sec->nooutput) |
| continue; |
| |
| //printf("Section: '%.*s' = '%.*s'\n", sec->namelen, sec->name, sec->bodylen, sec->body); |
| if (!sec->namelen && i == 0) |
| { |
| buf->writestring("$(DDOC_SUMMARY "); |
| size_t o = buf->offset; |
| buf->write(sec->body, sec->bodylen); |
| escapeStrayParenthesis(loc, buf, o); |
| highlightText(sc, a, buf, o); |
| buf->writestring(")\n"); |
| } |
| else |
| sec->write(loc, this, sc, a, buf); |
| } |
| |
| for (size_t i = 0; i < a->dim; i++) |
| { |
| Dsymbol *s = (*a)[i]; |
| if (Dsymbol *td = getEponymousParent(s)) |
| s = td; |
| |
| for (UnitTestDeclaration *utd = s->ddocUnittest; utd; utd = utd->ddocUnittest) |
| { |
| if (utd->protection.kind == PROTprivate || !utd->comment || !utd->fbody) |
| continue; |
| |
| // Strip whitespaces to avoid showing empty summary |
| const utf8_t *c = utd->comment; |
| while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r') ++c; |
| |
| buf->writestring("$(DDOC_EXAMPLES "); |
| |
| size_t o = buf->offset; |
| buf->writestring((const char *)c); |
| |
| if (utd->codedoc) |
| { |
| size_t n = getCodeIndent(utd->codedoc); |
| while (n--) buf->writeByte(' '); |
| buf->writestring("----\n"); |
| buf->writestring(utd->codedoc); |
| buf->writestring("----\n"); |
| highlightText(sc, a, buf, o); |
| } |
| |
| buf->writestring(")"); |
| } |
| } |
| |
| if (buf->offset == offset2) |
| { |
| /* Didn't write out any sections, so back out last write |
| */ |
| buf->offset = offset1; |
| buf->writestring("$(DDOC_BLANKLINE)\n"); |
| } |
| else |
| buf->writestring(")\n"); |
| } |
| |
| /*************************************************** |
| */ |
| |
| void Section::write(Loc loc, DocComment *, Scope *sc, Dsymbols *a, OutBuffer *buf) |
| { |
| assert(a->dim); |
| |
| if (namelen) |
| { |
| static const char *table[] = |
| { |
| "AUTHORS", "BUGS", "COPYRIGHT", "DATE", |
| "DEPRECATED", "EXAMPLES", "HISTORY", "LICENSE", |
| "RETURNS", "SEE_ALSO", "STANDARDS", "THROWS", |
| "VERSION", NULL |
| }; |
| |
| for (size_t i = 0; table[i]; i++) |
| { |
| if (icmp(table[i], name, namelen) == 0) |
| { |
| buf->printf("$(DDOC_%s ", table[i]); |
| goto L1; |
| } |
| } |
| |
| buf->writestring("$(DDOC_SECTION "); |
| |
| // Replace _ characters with spaces |
| buf->writestring("$(DDOC_SECTION_H "); |
| size_t o = buf->offset; |
| for (size_t u = 0; u < namelen; u++) |
| { |
| utf8_t c = name[u]; |
| buf->writeByte((c == '_') ? ' ' : c); |
| } |
| escapeStrayParenthesis(loc, buf, o); |
| buf->writestring(":)\n"); |
| } |
| else |
| { |
| buf->writestring("$(DDOC_DESCRIPTION "); |
| } |
| L1: |
| size_t o = buf->offset; |
| buf->write(body, bodylen); |
| escapeStrayParenthesis(loc, buf, o); |
| highlightText(sc, a, buf, o); |
| buf->writestring(")\n"); |
| } |
| |
| /*************************************************** |
| */ |
| |
| void ParamSection::write(Loc loc, DocComment *, Scope *sc, Dsymbols *a, OutBuffer *buf) |
| { |
| assert(a->dim); |
| Dsymbol *s = (*a)[0]; // test |
| |
| const utf8_t *p = body; |
| size_t len = bodylen; |
| const utf8_t *pend = p + len; |
| |
| const utf8_t *tempstart = NULL; |
| size_t templen = 0; |
| |
| const utf8_t *namestart = NULL; |
| size_t namelen = 0; // !=0 if line continuation |
| |
| const utf8_t *textstart = NULL; |
| size_t textlen = 0; |
| |
| size_t paramcount = 0; |
| |
| buf->writestring("$(DDOC_PARAMS "); |
| while (p < pend) |
| { |
| // Skip to start of macro |
| while (1) |
| { |
| switch (*p) |
| { |
| case ' ': |
| case '\t': |
| p++; |
| continue; |
| |
| case '\n': |
| p++; |
| goto Lcont; |
| |
| default: |
| if (isIdStart(p) || isCVariadicArg(p, pend - p)) |
| break; |
| if (namelen) |
| goto Ltext; // continuation of prev macro |
| goto Lskipline; |
| } |
| break; |
| } |
| tempstart = p; |
| |
| while (isIdTail(p)) |
| p += utfStride(p); |
| if (isCVariadicArg(p, pend - p)) |
| p += 3; |
| |
| templen = p - tempstart; |
| |
| while (*p == ' ' || *p == '\t') |
| p++; |
| |
| if (*p != '=') |
| { |
| if (namelen) |
| goto Ltext; // continuation of prev macro |
| goto Lskipline; |
| } |
| p++; |
| |
| if (namelen) |
| { |
| // Output existing param |
| |
| L1: |
| //printf("param '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart); |
| ++paramcount; |
| HdrGenState hgs; |
| buf->writestring("$(DDOC_PARAM_ROW "); |
| { |
| buf->writestring("$(DDOC_PARAM_ID "); |
| { |
| size_t o = buf->offset; |
| Parameter *fparam = isFunctionParameter(a, namestart, namelen); |
| if (!fparam) |
| { |
| // Comments on a template might refer to function parameters within. |
| // Search the parameters of nested eponymous functions (with the same name.) |
| fparam = isEponymousFunctionParameter(a, namestart, namelen); |
| } |
| bool isCVariadic = isCVariadicParameter(a, namestart, namelen); |
| if (isCVariadic) |
| { |
| buf->writestring("..."); |
| } |
| else if (fparam && fparam->type && fparam->ident) |
| { |
| ::toCBuffer(fparam->type, buf, fparam->ident, &hgs); |
| } |
| else |
| { |
| if (isTemplateParameter(a, namestart, namelen)) |
| { |
| // 10236: Don't count template parameters for params check |
| --paramcount; |
| } |
| else if (!fparam) |
| { |
| warning(s->loc, "Ddoc: function declaration has no parameter '%.*s'", (int)namelen, namestart); |
| } |
| buf->write(namestart, namelen); |
| } |
| escapeStrayParenthesis(loc, buf, o); |
| highlightCode(sc, a, buf, o); |
| } |
| buf->writestring(")\n"); |
| |
| buf->writestring("$(DDOC_PARAM_DESC "); |
| { |
| size_t o = buf->offset; |
| buf->write(textstart, textlen); |
| escapeStrayParenthesis(loc, buf, o); |
| highlightText(sc, a, buf, o); |
| } |
| buf->writestring(")"); |
| } |
| buf->writestring(")\n"); |
| namelen = 0; |
| if (p >= pend) |
| break; |
| } |
| |
| namestart = tempstart; |
| namelen = templen; |
| |
| while (*p == ' ' || *p == '\t') |
| p++; |
| textstart = p; |
| |
| Ltext: |
| while (*p != '\n') |
| p++; |
| textlen = p - textstart; |
| p++; |
| |
| Lcont: |
| continue; |
| |
| Lskipline: |
| // Ignore this line |
| while (*p++ != '\n') |
| ; |
| } |
| if (namelen) |
| goto L1; // write out last one |
| buf->writestring(")\n"); |
| |
| TypeFunction *tf = a->dim == 1 ? isTypeFunction(s) : NULL; |
| if (tf) |
| { |
| size_t pcount = (tf->parameters ? tf->parameters->dim : 0) + (int)(tf->varargs == 1); |
| if (pcount != paramcount) |
| { |
| warning(s->loc, "Ddoc: parameter count mismatch"); |
| } |
| } |
| } |
| |
| /*************************************************** |
| */ |
| |
| void MacroSection::write(Loc, DocComment *dc, Scope *, Dsymbols *, OutBuffer *) |
| { |
| //printf("MacroSection::write()\n"); |
| DocComment::parseMacros(dc->pescapetable, dc->pmacrotable, body, bodylen); |
| } |
| |
| /************************************************ |
| * Parse macros out of Macros: section. |
| * Macros are of the form: |
| * name1 = value1 |
| * |
| * name2 = value2 |
| */ |
| |
| void DocComment::parseMacros(Escape **pescapetable, Macro **pmacrotable, const utf8_t *m, size_t mlen) |
| { |
| const utf8_t *p = m; |
| size_t len = mlen; |
| const utf8_t *pend = p + len; |
| |
| const utf8_t *tempstart = NULL; |
| size_t templen = 0; |
| |
| const utf8_t *namestart = NULL; |
| size_t namelen = 0; // !=0 if line continuation |
| |
| const utf8_t *textstart = NULL; |
| size_t textlen = 0; |
| |
| while (p < pend) |
| { |
| // Skip to start of macro |
| while (1) |
| { |
| if (p >= pend) |
| goto Ldone; |
| switch (*p) |
| { |
| case ' ': |
| case '\t': |
| p++; |
| continue; |
| |
| case '\r': |
| case '\n': |
| p++; |
| goto Lcont; |
| |
| default: |
| if (isIdStart(p)) |
| break; |
| if (namelen) |
| goto Ltext; // continuation of prev macro |
| goto Lskipline; |
| } |
| break; |
| } |
| tempstart = p; |
| |
| while (1) |
| { |
| if (p >= pend) |
| goto Ldone; |
| if (!isIdTail(p)) |
| break; |
| p += utfStride(p); |
| } |
| templen = p - tempstart; |
| |
| while (1) |
| { |
| if (p >= pend) |
| goto Ldone; |
| if (!(*p == ' ' || *p == '\t')) |
| break; |
| p++; |
| } |
| |
| if (*p != '=') |
| { |
| if (namelen) |
| goto Ltext; // continuation of prev macro |
| goto Lskipline; |
| } |
| p++; |
| if (p >= pend) |
| goto Ldone; |
| |
| if (namelen) |
| { |
| // Output existing macro |
| L1: |
| //printf("macro '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart); |
| if (icmp("ESCAPES", namestart, namelen) == 0) |
| parseEscapes(pescapetable, textstart, textlen); |
| else |
| Macro::define(pmacrotable, namestart, namelen, textstart, textlen); |
| namelen = 0; |
| if (p >= pend) |
| break; |
| } |
| |
| namestart = tempstart; |
| namelen = templen; |
| |
| while (p < pend && (*p == ' ' || *p == '\t')) |
| p++; |
| textstart = p; |
| |
| Ltext: |
| while (p < pend && *p != '\r' && *p != '\n') |
| p++; |
| textlen = p - textstart; |
| |
| p++; |
| //printf("p = %p, pend = %p\n", p, pend); |
| |
| Lcont: |
| continue; |
| |
| Lskipline: |
| // Ignore this line |
| while (p < pend && *p != '\r' && *p != '\n') |
| p++; |
| } |
| Ldone: |
| if (namelen) |
| goto L1; // write out last one |
| } |
| |
| /************************************** |
| * Parse escapes of the form: |
| * /c/string/ |
| * where c is a single character. |
| * Multiple escapes can be separated |
| * by whitespace and/or commas. |
| */ |
| |
| void DocComment::parseEscapes(Escape **pescapetable, const utf8_t *textstart, size_t textlen) |
| { |
| Escape *escapetable = *pescapetable; |
| |
| if (!escapetable) |
| { |
| escapetable = new Escape; |
| memset(escapetable, 0, sizeof(Escape)); |
| *pescapetable = escapetable; |
| } |
| //printf("parseEscapes('%.*s') pescapetable = %p\n", textlen, textstart, pescapetable); |
| const utf8_t *p = textstart; |
| const utf8_t *pend = p + textlen; |
| |
| while (1) |
| { |
| while (1) |
| { |
| if (p + 4 >= pend) |
| return; |
| if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',')) |
| break; |
| p++; |
| } |
| if (p[0] != '/' || p[2] != '/') |
| return; |
| utf8_t c = p[1]; |
| p += 3; |
| const utf8_t *start = p; |
| while (1) |
| { |
| if (p >= pend) |
| return; |
| if (*p == '/') |
| break; |
| p++; |
| } |
| size_t len = p - start; |
| char *s = (char *)memcpy(mem.xmalloc(len + 1), start, len); |
| s[len] = 0; |
| escapetable->strings[c] = s; |
| //printf("\t%c = '%s'\n", c, s); |
| p++; |
| } |
| } |
| |
| |
| /****************************************** |
| * Compare 0-terminated string with length terminated string. |
| * Return < 0, ==0, > 0 |
| */ |
| |
| int cmp(const char *stringz, const void *s, size_t slen) |
| { |
| size_t len1 = strlen(stringz); |
| |
| if (len1 != slen) |
| return (int)(len1 - slen); |
| return memcmp(stringz, s, slen); |
| } |
| |
| int icmp(const char *stringz, const void *s, size_t slen) |
| { |
| size_t len1 = strlen(stringz); |
| |
| if (len1 != slen) |
| return (int)(len1 - slen); |
| return Port::memicmp(stringz, (const char *)s, slen); |
| } |
| |
| /***************************************** |
| * Return true if comment consists entirely of "ditto". |
| */ |
| |
| bool isDitto(const utf8_t *comment) |
| { |
| if (comment) |
| { |
| const utf8_t *p = skipwhitespace(comment); |
| |
| if (Port::memicmp((const char *)p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| /********************************************** |
| * Skip white space. |
| */ |
| |
| const utf8_t *skipwhitespace(const utf8_t *p) |
| { |
| for (; 1; p++) |
| { |
| switch (*p) |
| { |
| case ' ': |
| case '\t': |
| case '\n': |
| continue; |
| } |
| break; |
| } |
| return p; |
| } |
| |
| |
| /************************************************ |
| * Scan forward to one of: |
| * start of identifier |
| * beginning of next line |
| * end of buf |
| */ |
| |
| size_t skiptoident(OutBuffer *buf, size_t i) |
| { |
| while (i < buf->offset) |
| { |
| dchar_t c; |
| |
| size_t oi = i; |
| if (utf_decodeChar((utf8_t *)buf->data, buf->offset, &i, &c)) |
| { |
| /* Ignore UTF errors, but still consume input |
| */ |
| break; |
| } |
| if (c >= 0x80) |
| { |
| if (!isUniAlpha(c)) |
| continue; |
| } |
| else if (!(isalpha(c) || c == '_' || c == '\n')) |
| continue; |
| i = oi; |
| break; |
| } |
| return i; |
| } |
| |
| /************************************************ |
| * Scan forward past end of identifier. |
| */ |
| |
| size_t skippastident(OutBuffer *buf, size_t i) |
| { |
| while (i < buf->offset) |
| { |
| dchar_t c; |
| |
| size_t oi = i; |
| if (utf_decodeChar((utf8_t *)buf->data, buf->offset, &i, &c)) |
| { |
| /* Ignore UTF errors, but still consume input |
| */ |
| break; |
| } |
| if (c >= 0x80) |
| { |
| if (isUniAlpha(c)) |
| continue; |
| } |
| else if (isalnum(c) || c == '_') |
| continue; |
| i = oi; |
| break; |
| } |
| return i; |
| } |
| |
| |
| /************************************************ |
| * Scan forward past URL starting at i. |
| * We don't want to highlight parts of a URL. |
| * Returns: |
| * i if not a URL |
| * index just past it if it is a URL |
| */ |
| |
| size_t skippastURL(OutBuffer *buf, size_t i) |
| { |
| size_t length = buf->offset - i; |
| utf8_t *p = (utf8_t *)&buf->data[i]; |
| size_t j; |
| unsigned sawdot = 0; |
| |
| if (length > 7 && Port::memicmp((char *)p, "http://", 7) == 0) |
| { |
| j = 7; |
| } |
| else if (length > 8 && Port::memicmp((char *)p, "https://", 8) == 0) |
| { |
| j = 8; |
| } |
| else |
| goto Lno; |
| |
| for (; j < length; j++) |
| { |
| utf8_t c = p[j]; |
| if (isalnum(c)) |
| continue; |
| if (c == '-' || c == '_' || c == '?' || |
| c == '=' || c == '%' || c == '&' || |
| c == '/' || c == '+' || c == '#' || |
| c == '~') |
| continue; |
| if (c == '.') |
| { |
| sawdot = 1; |
| continue; |
| } |
| break; |
| } |
| if (sawdot) |
| return i + j; |
| |
| Lno: |
| return i; |
| } |
| |
| |
| /**************************************************** |
| */ |
| |
| bool isIdentifier(Dsymbols *a, const utf8_t *p, size_t len) |
| { |
| for (size_t i = 0; i < a->dim; i++) |
| { |
| const char *s = (*a)[i]->ident->toChars(); |
| if (cmp(s, p, len) == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| /**************************************************** |
| */ |
| |
| bool isKeyword(utf8_t *p, size_t len) |
| { |
| static const char *table[] = { "true", "false", "null", NULL }; |
| |
| for (int i = 0; table[i]; i++) |
| { |
| if (cmp(table[i], p, len) == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| /**************************************************** |
| */ |
| |
| TypeFunction *isTypeFunction(Dsymbol *s) |
| { |
| FuncDeclaration *f = s->isFuncDeclaration(); |
| |
| /* f->type may be NULL for template members. |
| */ |
| if (f && f->type) |
| { |
| Type *t = f->originalType ? f->originalType : f->type; |
| if (t->ty == Tfunction) |
| return (TypeFunction *)t; |
| } |
| return NULL; |
| } |
| |
| /**************************************************** |
| */ |
| |
| Parameter *isFunctionParameter(Dsymbols *a, const utf8_t *p, size_t len) |
| { |
| for (size_t i = 0; i < a->dim; i++) |
| { |
| Parameter *fparam = isFunctionParameter((*a)[i], p, len); |
| if (fparam) |
| { |
| return fparam; |
| } |
| } |
| return NULL; |
| } |
| |
| /**************************************************** |
| */ |
| |
| TemplateParameter *isTemplateParameter(Dsymbols *a, const utf8_t *p, size_t len) |
| { |
| for (size_t i = 0; i < a->dim; i++) |
| { |
| TemplateDeclaration *td = (*a)[i]->isTemplateDeclaration(); |
| // Check for the parent, if the current symbol is not a template declaration. |
| if (!td) |
| td = getEponymousParent((*a)[i]); |
| if (td && td->origParameters) |
| { |
| for (size_t k = 0; k < td->origParameters->dim; k++) |
| { |
| TemplateParameter *tp = (*td->origParameters)[k]; |
| if (tp->ident && cmp(tp->ident->toChars(), p, len) == 0) |
| { |
| return tp; |
| } |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| /**************************************************** |
| * Return true if str is a reserved symbol name |
| * that starts with a double underscore. |
| */ |
| |
| bool isReservedName(utf8_t *str, size_t len) |
| { |
| static const char *table[] = { |
| "__ctor", "__dtor", "__postblit", "__invariant", "__unitTest", |
| "__require", "__ensure", "__dollar", "__ctfe", "__withSym", "__result", |
| "__returnLabel", "__vptr", "__monitor", "__gate", "__xopEquals", "__xopCmp", |
| "__LINE__", "__FILE__", "__MODULE__", "__FUNCTION__", "__PRETTY_FUNCTION__", |
| "__DATE__", "__TIME__", "__TIMESTAMP__", "__VENDOR__", "__VERSION__", |
| "__EOF__", "__LOCAL_SIZE", "___tls_get_addr", "__entrypoint", NULL }; |
| |
| for (int i = 0; table[i]; i++) |
| { |
| if (cmp(table[i], str, len) == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| /************************************************** |
| * Highlight text section. |
| */ |
| |
| void highlightText(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset) |
| { |
| Dsymbol *s = a->dim ? (*a)[0] : NULL; // test |
| |
| //printf("highlightText()\n"); |
| |
| int leadingBlank = 1; |
| int inCode = 0; |
| int inBacktick = 0; |
| //int inComment = 0; // in <!-- ... --> comment |
| size_t iCodeStart = 0; // start of code section |
| size_t codeIndent = 0; |
| |
| size_t iLineStart = offset; |
| |
| for (size_t i = offset; i < buf->offset; i++) |
| { |
| utf8_t c = buf->data[i]; |
| |
| Lcont: |
| switch (c) |
| { |
| case ' ': |
| case '\t': |
| break; |
| |
| case '\n': |
| if (inBacktick) |
| { |
| // `inline code` is only valid if contained on a single line |
| // otherwise, the backticks should be output literally. |
| // |
| // This lets things like `output from the linker' display |
| // unmolested while keeping the feature consistent with GitHub. |
| |
| inBacktick = false; |
| inCode = false; // the backtick also assumes we're in code |
| |
| // Nothing else is necessary since the DDOC_BACKQUOTED macro is |
| // inserted lazily at the close quote, meaning the rest of the |
| // text is already OK. |
| } |
| |
| if (!sc->_module->isDocFile && |
| !inCode && i == iLineStart && i + 1 < buf->offset) // if "\n\n" |
| { |
| static const char blankline[] = "$(DDOC_BLANKLINE)\n"; |
| |
| i = buf->insert(i, blankline, strlen(blankline)); |
| } |
| leadingBlank = 1; |
| iLineStart = i + 1; |
| break; |
| |
| case '<': |
| { |
| leadingBlank = 0; |
| if (inCode) |
| break; |
| utf8_t *p = (utf8_t *)&buf->data[i]; |
| const char *se = sc->_module->escapetable->escapeChar('<'); |
| if (se && strcmp(se, "<") == 0) |
| { |
| // Generating HTML |
| // Skip over comments |
| if (p[1] == '!' && p[2] == '-' && p[3] == '-') |
| { |
| size_t j = i + 4; |
| p += 4; |
| while (1) |
| { |
| if (j == buf->offset) |
| goto L1; |
| if (p[0] == '-' && p[1] == '-' && p[2] == '>') |
| { |
| i = j + 2; // place on closing '>' |
| break; |
| } |
| j++; |
| p++; |
| } |
| break; |
| } |
| |
| // Skip over HTML tag |
| if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2]))) |
| { |
| size_t j = i + 2; |
| p += 2; |
| while (1) |
| { |
| if (j == buf->offset) |
| break; |
| if (p[0] == '>') |
| { |
| i = j; // place on closing '>' |
| break; |
| } |
| j++; |
| p++; |
| } |
| break; |
| } |
| } |
| L1: |
| // Replace '<' with '<' character entity |
| if (se) |
| { |
| size_t len = strlen(se); |
| buf->remove(i, 1); |
| i = buf->insert(i, se, len); |
| i--; // point to ';' |
| } |
| break; |
| } |
| case '>': |
| { |
| leadingBlank = 0; |
| if (inCode) |
| break; |
| // Replace '>' with '>' character entity |
| const char *se = sc->_module->escapetable->escapeChar('>'); |
| if (se) |
| { |
| size_t len = strlen(se); |
| buf->remove(i, 1); |
| i = buf->insert(i, se, len); |
| i--; // point to ';' |
| } |
| break; |
| } |
| case '&': |
| { |
| leadingBlank = 0; |
| if (inCode) |
| break; |
| utf8_t *p = (utf8_t *)&buf->data[i]; |
| if (p[1] == '#' || isalpha(p[1])) |
| break; // already a character entity |
| // Replace '&' with '&' character entity |
| const char *se = sc->_module->escapetable->escapeChar('&'); |
| if (se) |
| { |
| size_t len = strlen(se); |
| buf->remove(i, 1); |
| i = buf->insert(i, se, len); |
| i--; // point to ';' |
| } |
| break; |
| } |
| case '`': |
| { |
| if (inBacktick) |
| { |
| inBacktick = 0; |
| inCode = 0; |
| |
| OutBuffer codebuf; |
| |
| codebuf.write(buf->data + iCodeStart + 1, i - (iCodeStart + 1)); |
| |
| // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL |
| highlightCode(sc, a, &codebuf, 0); |
| |
| buf->remove(iCodeStart, i - iCodeStart + 1); // also trimming off the current ` |
| |
| static const char pre[] = "$(DDOC_BACKQUOTED "; |
| i = buf->insert(iCodeStart, pre, strlen(pre)); |
| i = buf->insert(i, (char *)codebuf.data, codebuf.offset); |
| i = buf->insert(i, ")", 1); |
| |
| i--; // point to the ending ) so when the for loop does i++, it will see the next character |
| |
| break; |
| } |
| |
| if (inCode) |
| break; |
| |
| inCode = 1; |
| inBacktick = 1; |
| codeIndent = 0; // inline code is not indented |
| |
| // All we do here is set the code flags and record |
| // the location. The macro will be inserted lazily |
| // so we can easily cancel the inBacktick if we come |
| // across a newline character. |
| iCodeStart = i; |
| |
| break; |
| } |
| case '-': |
| /* A line beginning with --- delimits a code section. |
| * inCode tells us if it is start or end of a code section. |
| */ |
| if (leadingBlank) |
| { |
| size_t istart = i; |
| size_t eollen = 0; |
| |
| leadingBlank = 0; |
| while (1) |
| { |
| ++i; |
| if (i >= buf->offset) |
| break; |
| c = buf->data[i]; |
| if (c == '\n') |
| { |
| eollen = 1; |
| break; |
| } |
| if (c == '\r') |
| { |
| eollen = 1; |
| if (i + 1 >= buf->offset) |
| break; |
| if (buf->data[i + 1] == '\n') |
| { |
| eollen = 2; |
| break; |
| } |
| } |
| // BUG: handle UTF PS and LS too |
| if (c != '-') |
| goto Lcont; |
| } |
| if (i - istart < 3) |
| goto Lcont; |
| |
| // We have the start/end of a code section |
| |
| // Remove the entire --- line, including blanks and \n |
| buf->remove(iLineStart, i - iLineStart + eollen); |
| i = iLineStart; |
| |
| if (inCode && (i <= iCodeStart)) |
| { |
| // Empty code section, just remove it completely. |
| inCode = 0; |
| break; |
| } |
| |
| if (inCode) |
| { |
| inCode = 0; |
| // The code section is from iCodeStart to i |
| OutBuffer codebuf; |
| |
| codebuf.write(buf->data + iCodeStart, i - iCodeStart); |
| codebuf.writeByte(0); |
| |
| // Remove leading indentations from all lines |
| bool lineStart = true; |
| utf8_t *endp = (utf8_t *)codebuf.data + codebuf.offset; |
| for (utf8_t *p = (utf8_t *)codebuf.data; p < endp; ) |
| { |
| if (lineStart) |
| { |
| size_t j = codeIndent; |
| utf8_t *q = p; |
| while (j-- > 0 && q < endp && isIndentWS(q)) |
| ++q; |
| codebuf.remove(p - (utf8_t *)codebuf.data, q - p); |
| assert((utf8_t *)codebuf.data <= p); |
| assert(p < (utf8_t *)codebuf.data + codebuf.offset); |
| lineStart = false; |
| endp = (utf8_t *)codebuf.data + codebuf.offset; // update |
| continue; |
| } |
| if (*p == '\n') |
| lineStart = true; |
| ++p; |
| } |
| |
| highlightCode2(sc, a, &codebuf, 0); |
| buf->remove(iCodeStart, i - iCodeStart); |
| i = buf->insert(iCodeStart, codebuf.data, codebuf.offset); |
| i = buf->insert(i, (const char *)")\n", 2); |
| i -= 2; // in next loop, c should be '\n' |
| } |
| else |
| { |
| static const char d_code[] = "$(D_CODE "; |
| |
| inCode = 1; |
| codeIndent = istart - iLineStart; // save indent count |
| i = buf->insert(i, d_code, strlen(d_code)); |
| iCodeStart = i; |
| i--; // place i on > |
| leadingBlank = true; |
| } |
| } |
| break; |
| |
| default: |
| leadingBlank = 0; |
| if (sc->_module->isDocFile || inCode) |
| break; |
| |
| utf8_t *start = (utf8_t *)buf->data + i; |
| if (isIdStart(start)) |
| { |
| size_t j = skippastident(buf, i); |
| if (i < j) |
| { |
| size_t k = skippastURL(buf, i); |
| if (i < k) |
| { |
| i = k - 1; |
| break; |
| } |
| } |
| else |
| break; |
| size_t len = j - i; |
| |
| // leading '_' means no highlight unless it's a reserved symbol name |
| if (c == '_' && |
| (i == 0 || !isdigit(*(start - 1))) && |
| (i == buf->offset - 1 || !isReservedName(start, len))) |
| { |
| buf->remove(i, 1); |
| i = j - 1; |
| break; |
| } |
| if (isIdentifier(a, start, len)) |
| { |
| i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; |
| break; |
| } |
| if (isKeyword(start, len)) |
| { |
| i = buf->bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1; |
| break; |
| } |
| if (isFunctionParameter(a, start, len)) |
| { |
| //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); |
| i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1; |
| break; |
| } |
| |
| i = j - 1; |
| } |
| break; |
| } |
| } |
| if (inCode) |
| error(s ? s->loc : Loc(), "unmatched --- in DDoc comment"); |
| } |
| |
| /************************************************** |
| * Highlight code for DDOC section. |
| */ |
| |
| void highlightCode(Scope *sc, Dsymbol *s, OutBuffer *buf, size_t offset) |
| { |
| //printf("highlightCode(s = %s '%s')\n", s->kind(), s->toChars()); |
| OutBuffer ancbuf; |
| emitAnchor(&ancbuf, s, sc); |
| buf->insert(offset, (char *)ancbuf.data, ancbuf.offset); |
| offset += ancbuf.offset; |
| |
| Dsymbols a; |
| a.push(s); |
| highlightCode(sc, &a, buf, offset); |
| } |
| |
| /**************************************************** |
| */ |
| |
| void highlightCode(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset) |
| { |
| //printf("highlightCode(a = '%s')\n", a->toChars()); |
| |
| for (size_t i = offset; i < buf->offset; i++) |
| { |
| utf8_t c = buf->data[i]; |
| const char *se = sc->_module->escapetable->escapeChar(c); |
| if (se) |
| { |
| size_t len = strlen(se); |
| buf->remove(i, 1); |
| i = buf->insert(i, se, len); |
| i--; // point to ';' |
| continue; |
| } |
| |
| utf8_t *start = (utf8_t *)buf->data + i; |
| if (isIdStart(start)) |
| { |
| size_t j = skippastident(buf, i); |
| if (i < j) |
| { |
| size_t len = j - i; |
| if (isIdentifier(a, start, len)) |
| { |
| i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; |
| continue; |
| } |
| if (isFunctionParameter(a, start, len)) |
| { |
| //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); |
| i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1; |
| continue; |
| } |
| i = j - 1; |
| } |
| } |
| } |
| } |
| |
| /**************************************** |
| */ |
| |
| void highlightCode3(Scope *sc, OutBuffer *buf, const utf8_t *p, const utf8_t *pend) |
| { |
| for (; p < pend; p++) |
| { |
| const char *s = sc->_module->escapetable->escapeChar(*p); |
| if (s) |
| buf->writestring(s); |
| else |
| buf->writeByte(*p); |
| } |
| } |
| |
| /************************************************** |
| * Highlight code for CODE section. |
| */ |
| |
| void highlightCode2(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset) |
| { |
| unsigned errorsave = global.errors; |
| Lexer lex(NULL, (utf8_t *)buf->data, 0, buf->offset - 1, 0, 1); |
| OutBuffer res; |
| const utf8_t *lastp = (utf8_t *)buf->data; |
| |
| //printf("highlightCode2('%.*s')\n", buf->offset - 1, buf->data); |
| res.reserve(buf->offset); |
| while (1) |
| { |
| Token tok; |
| lex.scan(&tok); |
| highlightCode3(sc, &res, lastp, tok.ptr); |
| |
| const char *highlight = NULL; |
| switch (tok.value) |
| { |
| case TOKidentifier: |
| { |
| if (!sc) |
| break; |
| size_t len = lex.p - tok.ptr; |
| if (isIdentifier(a, tok.ptr, len)) |
| { |
| highlight = "$(D_PSYMBOL "; |
| break; |
| } |
| if (isFunctionParameter(a, tok.ptr, len)) |
| { |
| //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); |
| highlight = "$(D_PARAM "; |
| break; |
| } |
| break; |
| } |
| case TOKcomment: |
| highlight = "$(D_COMMENT "; |
| break; |
| |
| case TOKstring: |
| highlight = "$(D_STRING "; |
| break; |
| |
| default: |
| if (tok.isKeyword()) |
| highlight = "$(D_KEYWORD "; |
| break; |
| } |
| if (highlight) |
| { |
| res.writestring(highlight); |
| size_t o = res.offset; |
| highlightCode3(sc, &res, tok.ptr, lex.p); |
| if (tok.value == TOKcomment || tok.value == TOKstring) |
| escapeDdocString(&res, o); // Bugzilla 7656, 7715, and 10519 |
| res.writeByte(')'); |
| } |
| else |
| highlightCode3(sc, &res, tok.ptr, lex.p); |
| if (tok.value == TOKeof) |
| break; |
| lastp = lex.p; |
| } |
| buf->setsize(offset); |
| buf->write(&res); |
| global.errors = errorsave; |
| } |
| |
| /*************************************** |
| * Find character string to replace c with. |
| */ |
| |
| const char *Escape::escapeChar(unsigned c) |
| { |
| assert(c < 256); |
| //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c]); |
| return strings[c]; |
| } |
| |
| /**************************************** |
| * Determine if p points to the start of a "..." parameter identifier. |
| */ |
| |
| bool isCVariadicArg(const utf8_t *p, size_t len) |
| { |
| return len >= 3 && cmp("...", p, 3) == 0; |
| } |
| |
| /**************************************** |
| * Determine if p points to the start of an identifier. |
| */ |
| |
| bool isIdStart(const utf8_t *p) |
| { |
| unsigned c = *p; |
| if (isalpha(c) || c == '_') |
| return true; |
| if (c >= 0x80) |
| { |
| size_t i = 0; |
| if (utf_decodeChar(p, 4, &i, &c)) |
| return false; // ignore errors |
| if (isUniAlpha(c)) |
| return true; |
| } |
| return false; |
| } |
| |
| /**************************************** |
| * Determine if p points to the rest of an identifier. |
| */ |
| |
| bool isIdTail(const utf8_t *p) |
| { |
| unsigned c = *p; |
| if (isalnum(c) || c == '_') |
| return true; |
| if (c >= 0x80) |
| { |
| size_t i = 0; |
| if (utf_decodeChar(p, 4, &i, &c)) |
| return false; // ignore errors |
| if (isUniAlpha(c)) |
| return true; |
| } |
| return false; |
| } |
| |
| /**************************************** |
| * Determine if p points to the indentation space. |
| */ |
| |
| bool isIndentWS(const utf8_t *p) |
| { |
| return (*p == ' ') || (*p == '\t'); |
| } |
| |
| /***************************************** |
| * Return number of bytes in UTF character. |
| */ |
| |
| int utfStride(const utf8_t *p) |
| { |
| unsigned c = *p; |
| if (c < 0x80) |
| return 1; |
| size_t i = 0; |
| utf_decodeChar(p, 4, &i, &c); // ignore errors, but still consume input |
| return (int)i; |
| } |