/* 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
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 = \n\ \n\ $(TITLE)\n\ \n\

$(TITLE)

\n\ $(BODY)\n\
$(SMALL Page generated by $(LINK2 http://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT))\n\ \n\ \n\ B = $0\n\ I = $0\n\ U = $0\n\ P =

$0

\n\ DL =
$0
\n\ DT =
$0
\n\ DD =
$0
\n\ TABLE = $0
\n\ TR = $0\n\ TH = $0\n\ TD = $0\n\ OL =
    $0
\n\ UL = \n\ LI =
  • $0
  • \n\ BIG = $0\n\ SMALL = $0\n\ BR =
    \n\ LINK = $0\n\ LINK2 = $+\n\ LPAREN= (\n\ RPAREN= )\n\ BACKTICK= `\n\ DOLLAR= $\n\ DEPRECATED= $0\n\ \n\ RED = $0\n\ BLUE = $0\n\ GREEN = $0\n\ YELLOW =$0\n\ BLACK = $0\n\ WHITE = $0\n\ \n\ D_CODE =
    $0
    \n\ DDOC_BACKQUOTED = $(D_INLINECODE $0)\n\ D_INLINECODE =
    $0
    \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 = \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 = \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\ "; 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; }