ldc/ddmd/doc.d
David Nadlinger 9f998a398d Initial merge of upstream v2.071.0-b2
Notably, the glue layer side of the changed multiple interface
inheritance layout (DMD a54e89d) has not been implemented yet.

This corresponds to DMD commit 3f6a763c0589dd03c1c206eafd434b593702564e.
2016-04-03 15:15:14 +01:00

2755 lines
84 KiB
D

// Compiler implementation of the D programming language
// Copyright (c) 1999-2015 by Digital Mars
// 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
module ddmd.doc;
import core.stdc.ctype;
import core.stdc.stdlib;
import core.stdc.stdio;
import core.stdc.string;
import core.stdc.time;
import ddmd.aggregate;
import ddmd.arraytypes;
import ddmd.attrib;
import ddmd.dclass;
import ddmd.declaration;
import ddmd.denum;
import ddmd.dmacro;
import ddmd.dmodule;
import ddmd.dscope;
import ddmd.dstruct;
import ddmd.dsymbol;
import ddmd.dtemplate;
import ddmd.errors;
import ddmd.func;
import ddmd.globals;
import ddmd.hdrgen;
import ddmd.id;
import ddmd.identifier;
import ddmd.lexer;
import ddmd.mars;
import ddmd.mtype;
import ddmd.root.array;
import ddmd.root.file;
import ddmd.root.filename;
import ddmd.root.outbuffer;
import ddmd.root.port;
import ddmd.root.rmem;
import ddmd.tokens;
import ddmd.utf;
import ddmd.visitor;
struct Escape
{
const(char)*[256] strings;
/***************************************
* Find character string to replace c with.
*/
extern (C++) const(char)* escapeChar(uint c)
{
version (all)
{
assert(c < 256);
//printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c]);
return strings[c];
}
else
{
const(char)* s;
switch (c)
{
case '<':
s = "&lt;";
break;
case '>':
s = "&gt;";
break;
case '&':
s = "&amp;";
break;
default:
s = null;
break;
}
return s;
}
}
}
/***********************************************************
*/
extern (C++) class Section
{
public:
const(char)* name;
size_t namelen;
const(char)* _body;
size_t bodylen;
int nooutput;
void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
{
assert(a.dim);
if (namelen)
{
static __gshared 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++)
{
char 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");
}
}
/***********************************************************
*/
extern (C++) final class ParamSection : Section
{
public:
override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
{
assert(a.dim);
Dsymbol s = (*a)[0]; // test
const(char)* p = _body;
size_t len = bodylen;
const(char)* pend = p + len;
const(char)* tempstart = null;
size_t templen = 0;
const(char)* namestart = null;
size_t namelen = 0; // !=0 if line continuation
const(char)* 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);
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'", 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) + cast(int)(tf.varargs == 1);
if (pcount != paramcount)
{
warning(s.loc, "Ddoc: parameter count mismatch");
}
}
}
}
/***********************************************************
*/
extern (C++) final class MacroSection : Section
{
public:
override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf)
{
//printf("MacroSection::write()\n");
DocComment.parseMacros(dc.pescapetable, dc.pmacrotable, _body, bodylen);
}
}
alias Sections = Array!(Section);
// Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one).
extern (C++) bool isCVariadicParameter(Dsymbols* a, const(char)* 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;
}
extern (C++) 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 (auto em = 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;
}
extern (C++) static TemplateDeclaration getEponymousParent(Dsymbol s)
{
if (!s.parent)
return null;
TemplateDeclaration td = s.parent.isTemplateDeclaration();
return (td && getEponymousMember(td)) ? td : null;
}
extern (C++) __gshared const(char)* ddoc_default = "DDOC = <html><head>
$(DDOC_COMMENT Generated by Ddoc from $(SRCFILENAME))
<META http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">
<title>$(TITLE)</title>
</head><body>
<h1>$(TITLE)</h1>
$(BODY)
<hr>$(SMALL Page generated by $(LINK2 http://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT))
</body></html>
B = <b>$0</b>
I = <i>$0</i>
U = <u>$0</u>
P = <p>$0</p>
DL = <dl>$0</dl>
DT = <dt>$0</dt>
DD = <dd>$0</dd>
TABLE = <table>$0</table>
TR = <tr>$0</tr>
TH = <th>$0</th>
TD = <td>$0</td>
OL = <ol>$0</ol>
UL = <ul>$0</ul>
LI = <li>$0</li>
BIG = <big>$0</big>
SMALL = <small>$0</small>
BR = <br>
LINK = <a href=\"$0\">$0</a>
LINK2 = <a href=\"$1\">$+</a>
LPAREN= (
RPAREN= )
BACKTICK= `
DOLLAR= $
DEPRECATED= $0
RED = <font color=red>$0</font>
BLUE = <font color=blue>$0</font>
GREEN = <font color=green>$0</font>
YELLOW =<font color=yellow>$0</font>
BLACK = <font color=black>$0</font>
WHITE = <font color=white>$0</font>
D_CODE = <pre class=\"d_code\">$0</pre>
DDOC_BACKQUOTED = $(D_INLINECODE $0)
D_INLINECODE = <pre style=\"display:inline;\" class=\"d_inline_code\">$0</pre>
D_COMMENT = $(GREEN $0)
D_STRING = $(RED $0)
D_KEYWORD = $(BLUE $0)
D_PSYMBOL = $(U $0)
D_PARAM = $(I $0)
DDOC_COMMENT = <!-- $0 -->
DDOC_DECL = $(DT $(BIG $0))
DDOC_DECL_DD = $(DD $0)
DDOC_DITTO = $(BR)$0
DDOC_SECTIONS = $0
DDOC_SUMMARY = $0$(BR)$(BR)
DDOC_DESCRIPTION = $0$(BR)$(BR)
DDOC_AUTHORS = $(B Authors:)$(BR)
$0$(BR)$(BR)
DDOC_BUGS = $(RED BUGS:)$(BR)
$0$(BR)$(BR)
DDOC_COPYRIGHT = $(B Copyright:)$(BR)
$0$(BR)$(BR)
DDOC_DATE = $(B Date:)$(BR)
$0$(BR)$(BR)
DDOC_DEPRECATED = $(RED Deprecated:)$(BR)
$0$(BR)$(BR)
DDOC_EXAMPLES = $(B Examples:)$(BR)
$0$(BR)$(BR)
DDOC_HISTORY = $(B History:)$(BR)
$0$(BR)$(BR)
DDOC_LICENSE = $(B License:)$(BR)
$0$(BR)$(BR)
DDOC_RETURNS = $(B Returns:)$(BR)
$0$(BR)$(BR)
DDOC_SEE_ALSO = $(B See Also:)$(BR)
$0$(BR)$(BR)
DDOC_STANDARDS = $(B Standards:)$(BR)
$0$(BR)$(BR)
DDOC_THROWS = $(B Throws:)$(BR)
$0$(BR)$(BR)
DDOC_VERSION = $(B Version:)$(BR)
$0$(BR)$(BR)
DDOC_SECTION_H = $(B $0)$(BR)
DDOC_SECTION = $0$(BR)$(BR)
DDOC_MEMBERS = $(DL $0)
DDOC_MODULE_MEMBERS = $(DDOC_MEMBERS $0)
DDOC_CLASS_MEMBERS = $(DDOC_MEMBERS $0)
DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0)
DDOC_ENUM_MEMBERS = $(DDOC_MEMBERS $0)
DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0)
DDOC_ENUM_BASETYPE = $0
DDOC_PARAMS = $(B Params:)$(BR)
$(TABLE $0)$(BR)
DDOC_PARAM_ROW = $(TR $0)
DDOC_PARAM_ID = $(TD $0)
DDOC_PARAM_DESC = $(TD $0)
DDOC_BLANKLINE = $(BR)$(BR)
DDOC_ANCHOR = <a name=\"$1\"></a>
DDOC_PSYMBOL = $(U $0)
DDOC_PSUPER_SYMBOL = $(U $0)
DDOC_KEYWORD = $(B $0)
DDOC_PARAM = $(I $0)
DDOC_CONSTRAINT = $(DDOC_CONSTRAINT) if ($0)
DDOC_OVERLOAD_SEPARATOR =
DDOC_TEMPLATE_PARAM_LIST = $0
DDOC_TEMPLATE_PARAM = $0
ESCAPES = /</&lt;/
/>/&gt;/
/&/&amp;/
";
extern (C++) __gshared const(char)* ddoc_decl_s = "$(DDOC_DECL ";
extern (C++) __gshared const(char)* ddoc_decl_e = ")\n";
extern (C++) __gshared const(char)* ddoc_decl_dd_s = "$(DDOC_DECL_DD ";
extern (C++) __gshared const(char)* ddoc_decl_dd_e = ")\n";
/****************************************************
*/
extern (C++) void gendocfile(Module m)
{
static __gshared OutBuffer mbuf;
static __gshared 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++)
{
auto f = FileName((*global.params.ddocfiles)[i]);
auto 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, cast(char*)mbuf.data, mbuf.offset);
Scope* sc = Scope.createGlobal(m); // create root scope
DocComment* dc = DocComment.parse(sc, 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, cast(char*)"TITLE", 5, cast(char*)p, strlen(p));
}
// Set time macros
{
time_t t;
time(&t);
char* p = ctime(&t);
p = mem.xstrdup(p);
Macro.define(&m.macrotable, cast(char*)"DATETIME", 8, p, strlen(p));
Macro.define(&m.macrotable, cast(char*)"YEAR", 4, p + 20, 4);
}
const srcfilename = m.srcfile.toChars();
Macro.define(&m.macrotable, "SRCFILENAME", 11, srcfilename, strlen(srcfilename));
const docfilename = m.docfile.toChars();
Macro.define(&m.macrotable, "DOCFILENAME", 11, docfilename, strlen(docfilename));
if (dc.copyright)
{
dc.copyright.nooutput = 1;
Macro.define(&m.macrotable, cast(char*)"COPYRIGHT", 9, dc.copyright._body, dc.copyright.bodylen);
}
if (m.isDocFile)
{
Loc loc = m.md ? m.md.loc : m.loc;
size_t commentlen = strlen(cast(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, cast(char*)"BODY", 4, cast(char*)buf.data, buf.offset);
OutBuffer buf2;
buf2.writestring("$(DDOC)\n");
size_t end = buf2.offset;
m.macrotable.expand(&buf2, 0, &end, null, 0);
version (all)
{
/* Remove all the escape sequences from buf2,
* and make CR-LF the newline.
*/
{
buf.setsize(0);
buf.reserve(buf2.offset);
char* p = cast(char*)buf2.data;
for (size_t j = 0; j < buf2.offset; j++)
{
char 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);
}
else
{
/* Remove all the escape sequences from buf2
*/
{
size_t i = 0;
char* p = buf2.data;
for (size_t j = 0; j < buf2.offset; j++)
{
if (p[j] == 0xFF && j + 1 < buf2.offset)
{
j++;
continue;
}
p[i] = p[j];
i++;
}
buf2.setsize(i);
}
// Transfer image to file
m.docfile.setbuffer(buf2.data, buf2.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.
*/
extern (C++) void escapeDdocString(OutBuffer* buf, size_t start)
{
for (size_t u = start; u < buf.offset; u++)
{
char c = buf.data[u];
switch (c)
{
case '$':
buf.remove(u, 1);
buf.insert(u, cast(const(char)*)"$(DOLLAR)", 9);
u += 8;
break;
case '(':
buf.remove(u, 1); //remove the (
buf.insert(u, cast(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, cast(const(char)*)"$(RPAREN)", 9); //insert this instead
u += 8; //skip over newly inserted macro
break;
default:
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).
*/
extern (C++) void escapeStrayParenthesis(Loc loc, OutBuffer* buf, size_t start)
{
uint par_open = 0;
bool inCode = 0;
for (size_t u = start; u < buf.offset; u++)
{
char c = buf.data[u];
switch (c)
{
case '(':
if (!inCode)
par_open++;
break;
case ')':
if (!inCode)
{
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, cast(const(char)*)"$(RPAREN)", 9); //insert this instead
u += 8; //skip over newly inserted macro
}
else
par_open--;
}
break;
version (none)
{
// For this to work, loc must be set to the beginning of the passed
// text which is currently not possible
// (loc is set to the Loc of the Dsymbol)
case '\n':
loc.linnum++;
break;
}
case '-':
// Issue 15465: don't try to escape unbalanced parens inside code
// blocks.
int numdash = 0;
while (u < buf.offset && buf.data[u] == '-')
{
numdash++;
u++;
}
if (numdash >= 3)
inCode = !inCode;
break;
default:
break;
}
}
if (par_open) // if any unmatched lparens
{
par_open = 0;
for (size_t u = buf.offset; u > start;)
{
u--;
char 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, cast(const(char)*)"$(LPAREN)", 9); //insert this instead
}
else
par_open--;
break;
default:
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.
extern (C++) static Scope* skipNonQualScopes(Scope* sc)
{
while (sc && !sc.scopesym)
sc = sc.enclosing;
return sc;
}
extern (C++) 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()) !is 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;
}
extern (C++) static void emitAnchor(OutBuffer* buf, Dsymbol s, Scope* sc)
{
Identifier ident;
{
OutBuffer anc;
emitAnchorName(&anc, s, skipNonQualScopes(sc));
ident = Identifier.idPool(anc.peekSlice());
}
auto pcount = cast(void*)ident in sc.anchorCounts;
typeof(*pcount) count;
if (pcount)
{
// Existing anchor,
// don't write an anchor for matching consecutive ditto symbols
TemplateDeclaration td = getEponymousParent(s);
if (sc.prevAnchor == ident && sc.lastdc && (isDitto(s.comment) || (td && isDitto(td.comment))))
return;
count = ++*pcount;
}
else
{
sc.anchorCounts[cast(void*)ident] = 1;
count = 1;
}
// cache anchor name
sc.prevAnchor = ident;
buf.writestring("$(DDOC_ANCHOR ");
buf.writestring(ident.string);
// 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. */
extern (C++) 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. */
extern (C++) 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);
}
}
}
extern (C++) 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(cast(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");
}
extern (C++) void emitProtection(OutBuffer* buf, Prot prot)
{
if (prot.kind != PROTundefined && prot.kind != PROTpublic)
{
protectionToBuffer(buf, prot);
buf.writeByte(' ');
}
}
extern (C++) void emitComment(Dsymbol s, OutBuffer* buf, Scope* sc)
{
extern (C++) final class EmitComment : Visitor
{
alias visit = super.visit;
public:
OutBuffer* buf;
Scope* sc;
extern (D) this(OutBuffer* buf, Scope* sc)
{
this.buf = buf;
this.sc = sc;
}
override void visit(Dsymbol)
{
}
override void visit(InvariantDeclaration)
{
}
override void visit(UnitTestDeclaration)
{
}
override void visit(PostBlitDeclaration)
{
}
override void visit(DtorDeclaration)
{
}
override void visit(StaticCtorDeclaration)
{
}
override void visit(StaticDtorDeclaration)
{
}
override void visit(TypeInfoDeclaration)
{
}
void emit(Scope* sc, Dsymbol s, const(char)* 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];
// the added linebreaks in here make looking at multiple
// signatures more appealing
if (i == 0)
{
size_t o = buf.offset;
toDocBuffer(sx, buf, sc);
highlightCode(sc, sx, buf, o);
buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
continue;
}
buf.writestring("$(DDOC_DITTO ");
{
size_t o = buf.offset;
toDocBuffer(sx, buf, sc);
highlightCode(sc, sx, buf, o);
}
buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)");
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(sc, s, com);
dc.pmacrotable = &sc._module.macrotable;
sc.lastdc = dc;
}
}
override void visit(Declaration d)
{
//printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d->toChars(), d->comment);
//printf("type = %p\n", d->type);
const(char)* 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)
{
if (!d.isCtorDeclaration() &&
!d.isAliasDeclaration() &&
!d.isVarDeclaration())
{
return;
}
}
if (d.protection.kind == PROTprivate || sc.protection.kind == PROTprivate)
return;
}
if (!com)
return;
emit(sc, d, com);
}
override void visit(AggregateDeclaration ad)
{
//printf("AggregateDeclaration::emitComment() '%s'\n", ad->toChars());
const(char)* 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);
}
override 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);
}
override 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);
}
override 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);
}
override 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);
}
}
}
override void visit(ProtDeclaration pd)
{
if (pd.decl)
{
Scope* scx = sc;
sc = sc.copy();
sc.protection = pd.protection;
visit(cast(AttribDeclaration)pd);
scx.lastdc = sc.lastdc;
sc = sc.pop();
}
}
override void visit(ConditionalDeclaration cd)
{
//printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc);
if (cd.condition.inc)
{
visit(cast(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);
}
}
}
scope EmitComment v = new EmitComment(buf, sc);
if (!s)
v.emit(sc, null, null);
else
s.accept(v);
}
extern (C++) void toDocBuffer(Dsymbol s, OutBuffer* buf, Scope* sc)
{
extern (C++) final class ToDocBuffer : Visitor
{
alias visit = super.visit;
public:
OutBuffer* buf;
Scope* sc;
extern (D) this(OutBuffer* buf, Scope* sc)
{
this.buf = buf;
this.sc = sc;
}
override 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
return;
if (d.isImmutable())
buf.writestring("immutable ");
if (d.storage_class & STCshared)
buf.writestring("shared ");
if (d.isWild())
buf.writestring("inout ");
if (d.isConst())
buf.writestring("const ");
if (d.isSynchronized())
buf.writestring("synchronized ");
if (d.storage_class & STCmanifest)
buf.writestring("enum ");
// Add "auto" for the untyped variable in template members
if (!d.type && d.isVarDeclaration() &&
!d.isImmutable() && !(d.storage_class & STCshared) && !d.isWild() && !d.isConst() &&
!d.isSynchronized())
{
buf.writestring("auto ");
}
}
}
override 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(cast(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)
{
bool noFuncDecl = td.isFuncDeclaration() is null;
if (noFuncDecl)
{
buf.writestring("$(DDOC_CONSTRAINT ");
}
.toCBuffer(td.constraint, buf, &hgs);
if (noFuncDecl)
{
buf.writestring(")");
}
}
if (d.isDeprecated())
buf.writestring(")");
buf.writestring(";\n");
}
override 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());
}
}
override void visit(AggregateDeclaration ad)
{
if (!ad.ident)
return;
version (none)
{
emitProtection(buf, ad.protection);
}
buf.printf("%s %s", ad.kind(), ad.toChars());
buf.writestring(";\n");
}
override void visit(StructDeclaration sd)
{
//printf("StructDeclaration::toDocbuffer() %s\n", sd->toChars());
if (!sd.ident)
return;
version (none)
{
emitProtection(buf, sd.protection);
}
if (TemplateDeclaration td = getEponymousParent(sd))
{
toDocBuffer(td, buf, sc);
}
else
{
buf.printf("%s %s", sd.kind(), sd.toChars());
}
buf.writestring(";\n");
}
override void visit(ClassDeclaration cd)
{
//printf("ClassDeclaration::toDocbuffer() %s\n", cd->toChars());
if (!cd.ident)
return;
version (none)
{
emitProtection(buf, cd.protection);
}
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.protection.kind == PROTprivate)
continue;
if (bc.sym && bc.sym.ident == Id.Object)
continue;
if (any)
buf.writestring(", ");
else
{
buf.writestring(": ");
any = 1;
}
emitProtection(buf, bc.protection);
if (bc.sym)
{
buf.printf("$(DDOC_PSUPER_SYMBOL %s)", bc.sym.toPrettyChars());
}
else
{
HdrGenState hgs;
.toCBuffer(bc.type, buf, null, &hgs);
}
}
buf.writestring(";\n");
}
override 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");
}
override void visit(EnumMember em)
{
if (!em.ident)
return;
buf.writestring(em.toChars());
}
}
scope ToDocBuffer v = new ToDocBuffer(buf, sc);
s.accept(v);
}
/***********************************************************
*/
struct DocComment
{
Sections sections; // Section*[]
Section summary;
Section copyright;
Section macros;
Macro** pmacrotable;
Escape** pescapetable;
Dsymbols a;
extern (C++) static DocComment* parse(Scope* sc, Dsymbol s, const(char)* comment)
{
//printf("parse(%s): '%s'\n", s->toChars(), comment);
auto 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 macros out of Macros: section.
* Macros are of the form:
* name1 = value1
*
* name2 = value2
*/
extern (C++) static void parseMacros(Escape** pescapetable, Macro** pmacrotable, const(char)* m, size_t mlen)
{
const(char)* p = m;
size_t len = mlen;
const(char)* pend = p + len;
const(char)* tempstart = null;
size_t templen = 0;
const(char)* namestart = null;
size_t namelen = 0; // !=0 if line continuation
const(char)* 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.
*/
extern (C++) static void parseEscapes(Escape** pescapetable, const(char)* textstart, size_t textlen)
{
Escape* escapetable = *pescapetable;
if (!escapetable)
{
escapetable = new Escape();
memset(escapetable, 0, Escape.sizeof);
*pescapetable = escapetable;
}
//printf("parseEscapes('%.*s') pescapetable = %p\n", textlen, textstart, pescapetable);
const(char)* p = textstart;
const(char)* 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;
char c = p[1];
p += 3;
const(char)* start = p;
while (1)
{
if (p >= pend)
return;
if (*p == '/')
break;
p++;
}
size_t len = p - start;
char* s = cast(char*)memcpy(mem.xmalloc(len + 1), start, len);
s[len] = 0;
escapetable.strings[c] = s;
//printf("\t%c = '%s'\n", c, s);
p++;
}
}
/*****************************************
* 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.
*/
extern (C++) void parseSections(const(char)* comment)
{
const(char)* p;
const(char)* pstart;
const(char)* pend;
const(char)* idstart = null; // dead-store to prevent spurious warning
size_t idlen;
const(char)* name = null;
size_t namelen = 0;
//printf("parseSections('%s')\n", comment);
p = comment;
while (*p)
{
const(char)* 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(char)* q = p + utfStride(p);
while (isIdTail(q))
q += utfStride(q);
if (*q == ':') // identifier: ends it
{
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;
}
}
}
extern (C++) void 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(char)* c = utd.comment;
while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')
++c;
buf.writestring("$(DDOC_EXAMPLES ");
size_t o = buf.offset;
buf.writestring(cast(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");
}
}
/******************************************
* Compare 0-terminated string with length terminated string.
* Return < 0, ==0, > 0
*/
extern (C++) int cmp(const(char)* stringz, const(void)* s, size_t slen)
{
size_t len1 = strlen(stringz);
if (len1 != slen)
return cast(int)(len1 - slen);
return memcmp(stringz, s, slen);
}
extern (C++) int icmp(const(char)* stringz, const(void)* s, size_t slen)
{
size_t len1 = strlen(stringz);
if (len1 != slen)
return cast(int)(len1 - slen);
return Port.memicmp(stringz, cast(char*)s, slen);
}
/*****************************************
* Return true if comment consists entirely of "ditto".
*/
extern (C++) bool isDitto(const(char)* comment)
{
if (comment)
{
const(char)* p = skipwhitespace(comment);
if (Port.memicmp(p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
return true;
}
return false;
}
/**********************************************
* Skip white space.
*/
extern (C++) const(char)* skipwhitespace(const(char)* p)
{
for (; 1; p++)
{
switch (*p)
{
case ' ':
case '\t':
case '\n':
continue;
default:
break;
}
break;
}
return p;
}
/************************************************
* Scan forward to one of:
* start of identifier
* beginning of next line
* end of buf
*/
extern (C++) size_t skiptoident(OutBuffer* buf, size_t i)
{
while (i < buf.offset)
{
dchar c;
size_t oi = i;
if (utf_decodeChar(cast(char*)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.
*/
extern (C++) size_t skippastident(OutBuffer* buf, size_t i)
{
while (i < buf.offset)
{
dchar c;
size_t oi = i;
if (utf_decodeChar(cast(char*)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
*/
extern (C++) size_t skippastURL(OutBuffer* buf, size_t i)
{
size_t length = buf.offset - i;
char* p = cast(char*)&buf.data[i];
size_t j;
uint sawdot = 0;
if (length > 7 && Port.memicmp(p, "http://", 7) == 0)
{
j = 7;
}
else if (length > 8 && Port.memicmp(p, "https://", 8) == 0)
{
j = 8;
}
else
goto Lno;
for (; j < length; j++)
{
char 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;
}
/****************************************************
*/
extern (C++) bool isIdentifier(Dsymbols* a, const(char)* 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;
}
/****************************************************
*/
extern (C++) bool isKeyword(char* p, size_t len)
{
static __gshared 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;
}
/****************************************************
*/
extern (C++) 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 cast(TypeFunction)t;
}
return null;
}
/****************************************************
*/
extern (C++) Parameter isFunctionParameter(Dsymbols* a, const(char)* p, size_t len)
{
for (size_t i = 0; i < a.dim; i++)
{
TypeFunction tf = isTypeFunction((*a)[i]);
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;
}
/****************************************************
*/
extern (C++) TemplateParameter isTemplateParameter(Dsymbols* a, const(char)* p, size_t len)
{
for (size_t i = 0; i < a.dim; i++)
{
TemplateDeclaration 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.
*/
extern (C++) bool isReservedName(char* str, size_t len)
{
static __gshared 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",
"__va_argsave_t",
"__va_argsave",
null
];
for (int i = 0; table[i]; i++)
{
if (cmp(table[i], str, len) == 0)
return true;
}
return false;
}
/**************************************************
* Highlight text section.
*/
extern (C++) 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++)
{
char 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 __gshared 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;
char* p = cast(char*)&buf.data[i];
const(char)* se = sc._module.escapetable.escapeChar('<');
if (se && strcmp(se, "&lt;") == 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 '&lt;' 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 '&gt;' 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;
char* p = cast(char*)&buf.data[i];
if (p[1] == '#' || isalpha(p[1]))
break;
// already a character entity
// Replace '&' with '&amp;' 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 __gshared const(char)* pre = "$(DDOC_BACKQUOTED ";
i = buf.insert(iCodeStart, pre, strlen(pre));
i = buf.insert(i, cast(char*)codebuf.data, codebuf.offset);
i = buf.insert(i, cast(char*)")", 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;
char* endp = cast(char*)codebuf.data + codebuf.offset;
for (char* p = cast(char*)codebuf.data; p < endp;)
{
if (lineStart)
{
size_t j = codeIndent;
char* q = p;
while (j-- > 0 && q < endp && isIndentWS(q))
++q;
codebuf.remove(p - cast(char*)codebuf.data, q - p);
assert(cast(char*)codebuf.data <= p);
assert(p < cast(char*)codebuf.data + codebuf.offset);
lineStart = false;
endp = cast(char*)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, cast(const(char)*)")\n", 2);
i -= 2; // in next loop, c should be '\n'
}
else
{
static __gshared 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;
char* start = cast(char*)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.size - 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.
*/
extern (C++) 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, cast(char*)ancbuf.data, ancbuf.offset);
offset += ancbuf.offset;
Dsymbols a;
a.push(s);
highlightCode(sc, &a, buf, offset);
}
/****************************************************
*/
extern (C++) void highlightCode(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset)
{
//printf("highlightCode(a = '%s')\n", a->toChars());
bool resolvedTemplateParameters = false;
for (size_t i = offset; i < buf.offset; i++)
{
char 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;
}
char* start = cast(char*)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;
}
}
else if (!resolvedTemplateParameters)
{
size_t previ = i;
// hunt for template declarations:
foreach (symi; 0 .. a.dim)
{
FuncDeclaration fd = (*a)[symi].isFuncDeclaration();
if (!fd || !fd.parent || !fd.parent.isTemplateDeclaration())
{
continue;
}
TemplateDeclaration td = fd.parent.isTemplateDeclaration();
// build the template parameters
Array!(size_t) paramLens;
paramLens.reserve(td.parameters.dim);
OutBuffer parametersBuf;
HdrGenState hgs;
parametersBuf.writeByte('(');
foreach (parami; 0 .. td.parameters.dim)
{
TemplateParameter tp = (*td.parameters)[parami];
if (parami)
parametersBuf.writestring(", ");
size_t lastOffset = parametersBuf.offset;
.toCBuffer(tp, &parametersBuf, &hgs);
paramLens[parami] = parametersBuf.offset - lastOffset;
}
parametersBuf.writeByte(')');
const(char*) templateParams = parametersBuf.extractString();
size_t templateParamsLen = strlen(templateParams);
//printf("templateDecl: %s\ntemplateParams: %s\nstart: %s\n", td.toChars(), templateParams, start);
if (cmp(templateParams, start, templateParamsLen) == 0)
{
static immutable templateParamListMacro = "$(DDOC_TEMPLATE_PARAM_LIST ";
size_t paramListEnd = buf.bracket(i, templateParamListMacro.ptr, i + templateParamsLen, ")") - 1;
// We have the parameter list. While we're here we might
// as well wrap the parameters themselves as well
// + 1 here to take into account the opening paren of the
// template param list
i += templateParamListMacro.length + 1;
foreach (size_t len; paramLens)
{
i = buf.bracket(i, "$(DDOC_TEMPLATE_PARAM ", i + len, ")");
// increment two here for space + comma
i += 2;
}
resolvedTemplateParameters = true;
// reset i to be positioned back before we found the template
// param list this assures that anything within the template
// param list that needs to be escaped or otherwise altered
// has an opportunity for that to happen outside of this context
i = previ;
continue;
}
}
}
}
}
/****************************************
*/
extern (C++) void highlightCode3(Scope* sc, OutBuffer* buf, const(char)* p, const(char)* 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.
*/
extern (C++) void highlightCode2(Scope* sc, Dsymbols* a, OutBuffer* buf, size_t offset)
{
uint errorsave = global.errors;
scope Lexer lex = new Lexer(null, cast(char*)buf.data, 0, buf.offset - 1, 0, 1);
OutBuffer res;
const(char)* lastp = cast(char*)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;
}
/****************************************
* Determine if p points to the start of a "..." parameter identifier.
*/
extern (C++) bool isCVariadicArg(const(char)* p, size_t len)
{
return len >= 3 && cmp("...", p, 3) == 0;
}
/****************************************
* Determine if p points to the start of an identifier.
*/
extern (C++) bool isIdStart(const(char)* p)
{
dchar 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.
*/
extern (C++) bool isIdTail(const(char)* p)
{
dchar 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.
*/
extern (C++) bool isIndentWS(const(char)* p)
{
return (*p == ' ') || (*p == '\t');
}
/*****************************************
* Return number of bytes in UTF character.
*/
extern (C++) int utfStride(const(char)* p)
{
dchar c = *p;
if (c < 0x80)
return 1;
size_t i = 0;
utf_decodeChar(p, 4, i, c); // ignore errors, but still consume input
return cast(int)i;
}