view dmd/doc.c @ 1138:4c8bb03e4fbc

Update DtoConstFP() to be correct after LLVM r67562, which changed the way the APFloat constructor expects its i80 APInts to be formatted. (They're now actually consistent with the x87 format)
author Frits van Bommel <fvbommel wxs.nl>
date Tue, 24 Mar 2009 15:24:59 +0100
parents eeb8b95ea92e
children def7a1d494fd
line wrap: on
line source


// Compiler implementation of the D programming language
// Copyright (c) 1999-2008 by Digital Mars
// All Rights Reserved
// written by Walter Bright
// http://www.digitalmars.com
// License for redistribution is by either the Artistic License
// in artistic.txt, or the GNU General Public License in gnu.txt.
// See the included readme.txt for details.

// This implements the Ddoc capability.

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <assert.h>

#include "rmem.h"
#include "root.h"

#include "mars.h"
#include "dsymbol.h"
#include "macro.h"
#include "template.h"
#include "lexer.h"
#include "aggregate.h"
#include "declaration.h"
#include "enum.h"
#include "id.h"
#include "module.h"
#include "scope.h"
#include "hdrgen.h"
#include "doc.h"
#include "mtype.h"

struct Escape
{
    const char *strings[256];

    static const char *escapeChar(unsigned c);
};

struct Section
{
    unsigned char *name;
    unsigned namelen;

    unsigned char *body;
    unsigned bodylen;

    int nooutput;

    virtual void write(DocComment *dc, Scope *sc, Dsymbol *s, OutBuffer *buf);
};

struct ParamSection : Section
{
    void write(DocComment *dc, Scope *sc, Dsymbol *s, OutBuffer *buf);
};

struct MacroSection : Section
{
    void write(DocComment *dc, Scope *sc, Dsymbol *s, OutBuffer *buf);
};

struct DocComment
{
    Array sections;		// Section*[]

    Section *summary;
    Section *copyright;
    Section *macros;
    Macro **pmacrotable;
    Escape **pescapetable;

    DocComment();

    static DocComment *parse(Scope *sc, Dsymbol *s, unsigned char *comment);
    static void parseMacros(Escape **pescapetable, Macro **pmacrotable, unsigned char *m, unsigned mlen);
    static void parseEscapes(Escape **pescapetable, unsigned char *textstart, unsigned textlen);

    void parseSections(unsigned char *comment);
    void writeSections(Scope *sc, Dsymbol *s, OutBuffer *buf);
};


int cmp(const char *stringz, void *s, size_t slen);
int icmp(const char *stringz, void *s, size_t slen);
int isDitto(unsigned char *comment);
unsigned char *skipwhitespace(unsigned char *p);
unsigned skiptoident(OutBuffer *buf, unsigned i);
unsigned skippastident(OutBuffer *buf, unsigned i);
unsigned skippastURL(OutBuffer *buf, unsigned i);
void highlightText(Scope *sc, Dsymbol *s, OutBuffer *buf, unsigned offset);
void highlightCode(Scope *sc, Dsymbol *s, OutBuffer *buf, unsigned offset);
void highlightCode2(Scope *sc, Dsymbol *s, OutBuffer *buf, unsigned offset);
Argument *isFunctionParameter(Dsymbol *s, unsigned char *p, unsigned len);

static unsigned char ddoc_default[] = "\
DDOC =	<html><head>\n\
	<META http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n\
	<title>$(TITLE)</title>\n\
	</head><body>\n\
	<h1>$(TITLE)</h1>\n\
	$(BODY)\n\
	<hr>$(SMALL Page generated by $(LINK2 http://www.digitalmars.com/d/1.0/ddoc.html, Ddoc). $(COPYRIGHT))\n\
	</body></html>\n\
\n\
B =	<b>$0</b>\n\
I =	<i>$0</i>\n\
U =	<u>$0</u>\n\
P =	<p>$0</p>\n\
DL =	<dl>$0</dl>\n\
DT =	<dt>$0</dt>\n\
DD =	<dd>$0</dd>\n\
TABLE =	<table>$0</table>\n\
TR =	<tr>$0</tr>\n\
TH =	<th>$0</th>\n\
TD =	<td>$0</td>\n\
OL =	<ol>$0</ol>\n\
UL =	<ul>$0</ul>\n\
LI =	<li>$0</li>\n\
BIG =	<big>$0</big>\n\
SMALL =	<small>$0</small>\n\
BR =	<br>\n\
LINK =	<a href=\"$0\">$0</a>\n\
LINK2 =	<a href=\"$1\">$+</a>\n\
\n\
RED =	<font color=red>$0</font>\n\
BLUE =	<font color=blue>$0</font>\n\
GREEN =	<font color=green>$0</font>\n\
YELLOW =<font color=yellow>$0</font>\n\
BLACK =	<font color=black>$0</font>\n\
WHITE =	<font color=white>$0</font>\n\
\n\
D_CODE = <pre class=\"d_code\">$0</pre>\n\
D_COMMENT = $(GREEN $0)\n\
D_STRING  = $(RED $0)\n\
D_KEYWORD = $(BLUE $0)\n\
D_PSYMBOL = $(U $0)\n\
D_PARAM	  = $(I $0)\n\
\n\
DDOC_COMMENT   = <!-- $0 -->\n\
DDOC_DECL      = $(DT $(BIG $0))\n\
DDOC_DECL_DD   = $(DD $0)\n\
DDOC_DITTO     = $(BR)$0\n\
DDOC_SECTIONS  = $0\n\
DDOC_SUMMARY   = $0$(BR)$(BR)\n\
DDOC_DESCRIPTION = $0$(BR)$(BR)\n\
DDOC_AUTHORS   = $(B Authors:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_BUGS      = $(RED BUGS:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_COPYRIGHT = $(B Copyright:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_DATE      = $(B Date:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_DEPRECATED = $(RED Deprecated:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_EXAMPLES  = $(B Examples:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_HISTORY   = $(B History:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_LICENSE   = $(B License:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_RETURNS   = $(B Returns:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_SEE_ALSO  = $(B See Also:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_STANDARDS = $(B Standards:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_THROWS    = $(B Throws:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_VERSION   = $(B Version:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_SECTION_H = $(B $0)$(BR)\n\
DDOC_SECTION   = $0$(BR)$(BR)\n\
DDOC_MEMBERS   = $(DL $0)\n\
DDOC_MODULE_MEMBERS = $(DDOC_MEMBERS $0)\n\
DDOC_CLASS_MEMBERS  = $(DDOC_MEMBERS $0)\n\
DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0)\n\
DDOC_ENUM_MEMBERS   = $(DDOC_MEMBERS $0)\n\
DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0)\n\
DDOC_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_PSYMBOL	= $(U $0)\n\
DDOC_KEYWORD	= $(B $0)\n\
DDOC_PARAM	= $(I $0)\n\
\n\
ESCAPES = /</&lt;/\n\
	  />/&gt;/\n\
	  /&/&amp;/\n\
";

static char ddoc_decl_s[] = "$(DDOC_DECL ";
static char ddoc_decl_e[] = ")\n";

static char ddoc_decl_dd_s[] = "$(DDOC_DECL_DD ";
static char ddoc_decl_dd_e[] = ")\n";


/****************************************************
 */

void Module::gendocfile()
{
    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, sizeof(ddoc_default) - 1);

	// 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 (int i = 0; i < global.params.ddocfiles->dim; i++)
	{
	    FileName f((char *)global.params.ddocfiles->data[i], 0);
	    File file(&f);
	    file.readv();
	    // 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(&escapetable, &macrotable, mbuf.data, mbuf.offset);

    Scope *sc = Scope::createGlobal(this);	// create root scope
    sc->docbuf = &buf;

    DocComment *dc = DocComment::parse(sc, this, comment);
    dc->pmacrotable = &macrotable;
    dc->pescapetable = &escapetable;

    // Generate predefined macros

    // Set the title to be the name of the module
    {	char *p = toPrettyChars();
	Macro::define(&macrotable, (unsigned char *)"TITLE", 5, (unsigned char *)p, strlen(p));
    }

    time_t t;
    time(&t);
    char *p = ctime(&t);
    p = mem.strdup(p);
    Macro::define(&macrotable, (unsigned char *)"DATETIME", 8, (unsigned char *)p, strlen(p));
    Macro::define(&macrotable, (unsigned char *)"YEAR", 4, (unsigned char *)p + 20, 4);

    char *docfilename = docfile->toChars();
    Macro::define(&macrotable, (unsigned char *)"DOCFILENAME", 11, (unsigned char *)docfilename, strlen(docfilename));

    if (dc->copyright)
    {
	dc->copyright->nooutput = 1;
	Macro::define(&macrotable, (unsigned char *)"COPYRIGHT", 9, dc->copyright->body, dc->copyright->bodylen);
    }

    buf.printf("$(DDOC_COMMENT Generated by Ddoc from %s)\n", srcfile->toChars());
    if (isDocFile)
    {
	size_t commentlen = strlen((char *)comment);
	if (dc->macros)
	{
	    commentlen = dc->macros->name - comment;
	    dc->macros->write(dc, sc, this, sc->docbuf);
	}
	sc->docbuf->write(comment, commentlen);
	highlightText(NULL, this, sc->docbuf, 0);
    }
    else
    {
	dc->writeSections(sc, this, sc->docbuf);
	emitMemberComments(sc);
    }

    //printf("BODY= '%.*s'\n", buf.offset, buf.data);
    Macro::define(&macrotable, (unsigned char *)"BODY", 4, buf.data, buf.offset);

    OutBuffer buf2;
    buf2.writestring("$(DDOC)\n");
    unsigned end = buf2.offset;
    macrotable->expand(&buf2, 0, &end, NULL, 0);

#if 1
    /* Remove all the escape sequences from buf2,
     * and make CR-LF the newline.
     */
    {
	buf.setsize(0);
	buf.reserve(buf2.offset);
	unsigned char *p = buf2.data;
	for (unsigned j = 0; j < buf2.offset; j++)
	{
	    unsigned 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(docfile);
    docfile->setbuffer(buf.data, buf.offset);
    docfile->ref = 1;
    char *pt = FileName::path(docfile->toChars());
    if (*pt)
	FileName::ensurePathExists(pt);
    mem.free(pt);
    docfile->writev();
#else
    /* Remove all the escape sequences from buf2
     */
    {	unsigned i = 0;
	unsigned char *p = buf2.data;
	for (unsigned 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
    docfile->setbuffer(buf2.data, buf2.offset);
    docfile->ref = 1;
    char *pt = FileName::path(docfile->toChars());
    if (*pt)
	FileName::ensurePathExists(pt);
    mem.free(pt);
    docfile->writev();
#endif
}

/******************************* emitComment **********************************/

/*
 * Emit doc comment to documentation file
 */

void Dsymbol::emitDitto(Scope *sc)
{
    //printf("Dsymbol::emitDitto() %s %s\n", kind(), toChars());
    OutBuffer *buf = sc->docbuf;
    unsigned o;
    OutBuffer b;

    b.writestring("$(DDOC_DITTO ");
	o = b.offset;
	toDocBuffer(&b);
	//printf("b: '%.*s'\n", b.offset, b.data);
	/* If 'this' is a function template, then highlightCode() was
	 * already run by FuncDeclaration::toDocbuffer().
	 */
	TemplateDeclaration *td;
	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{
	}
	else
	    highlightCode(sc, this, &b, o);
    b.writeByte(')');
    buf->spread(sc->lastoffset, b.offset);
    memcpy(buf->data + sc->lastoffset, b.data, b.offset);
    sc->lastoffset += b.offset;
}

void ScopeDsymbol::emitMemberComments(Scope *sc)
{
    //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars());
    OutBuffer *buf = sc->docbuf;

    if (members)
    {	const char *m = "$(DDOC_MEMBERS \n";

	if (isModule())
	    m = "$(DDOC_MODULE_MEMBERS \n";
	else if (isClassDeclaration())
	    m = "$(DDOC_CLASS_MEMBERS \n";
	else if (isStructDeclaration())
	    m = "$(DDOC_STRUCT_MEMBERS \n";
	else if (isEnumDeclaration())
	    m = "$(DDOC_ENUM_MEMBERS \n";
	else if (isTemplateDeclaration())
	    m = "$(DDOC_TEMPLATE_MEMBERS \n";

	unsigned offset1 = buf->offset;		// save starting offset
	buf->writestring(m);
	unsigned offset2 = buf->offset;		// to see if we write anything
	sc = sc->push(this);
	for (int i = 0; i < members->dim; i++)
	{
	    Dsymbol *s = (Dsymbol *)members->data[i];
	    //printf("\ts = '%s'\n", s->toChars());
	    s->emitComment(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)
{
    const char *p;

    switch (prot)
    {
	case PROTpackage:	p = "package";	 break;
	case PROTprotected:	p = "protected"; break;
	case PROTexport:	p = "export";	 break;
	default:		p = NULL;	 break;
    }
    if (p)
	buf->printf("%s ", p);
}

void Dsymbol::emitComment(Scope *sc)		   { }
void InvariantDeclaration::emitComment(Scope *sc)  { }
#if DMDV2
void PostBlitDeclaration::emitComment(Scope *sc)   { }
#endif
void DtorDeclaration::emitComment(Scope *sc)	   { }
void StaticCtorDeclaration::emitComment(Scope *sc) { }
void StaticDtorDeclaration::emitComment(Scope *sc) { }
void ClassInfoDeclaration::emitComment(Scope *sc)  { }
void ModuleInfoDeclaration::emitComment(Scope *sc) { }
void TypeInfoDeclaration::emitComment(Scope *sc)   { }


void Declaration::emitComment(Scope *sc)
{
    //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", this, toChars(), comment);
    //printf("type = %p\n", type);

    if (protection == PROTprivate || !ident ||
	(!type && !isCtorDeclaration() && !isAliasDeclaration()))
	return;
    if (!comment)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);
    unsigned o;

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	o = buf->offset;
	toDocBuffer(buf);
	highlightCode(sc, this, buf, o);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    buf->writestring(ddoc_decl_dd_e);
}

void AggregateDeclaration::emitComment(Scope *sc)
{
    //printf("AggregateDeclaration::emitComment() '%s'\n", toChars());
    if (prot() == PROTprivate)
	return;
    if (!comment)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
    toDocBuffer(buf);
    sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    emitMemberComments(sc);
    buf->writestring(ddoc_decl_dd_e);
}

void TemplateDeclaration::emitComment(Scope *sc)
{
    //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", toChars(), kind());
    if (prot() == PROTprivate)
	return;

    unsigned char *com = comment;
    int hasmembers = 1;

    Dsymbol *ss = this;

    if (onemember)
    {
	ss = onemember->isAggregateDeclaration();
	if (!ss)
	{
	    ss = onemember->isFuncDeclaration();
	    if (ss)
	    {	hasmembers = 0;
		if (com != ss->comment)
		    com = Lexer::combineComments(com, ss->comment);
	    }
	    else
		ss = this;
	}
    }

    if (!com)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, com);
    unsigned o;

    if (!dc)
    {
	ss->emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	o = buf->offset;
	ss->toDocBuffer(buf);
	if (ss == this)
	    highlightCode(sc, this, buf, o);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    if (hasmembers)
	((ScopeDsymbol *)ss)->emitMemberComments(sc);
    buf->writestring(ddoc_decl_dd_e);
}

void EnumDeclaration::emitComment(Scope *sc)
{
    if (prot() == PROTprivate)
	return;
//    if (!comment)
    {	if (isAnonymous() && members)
	{
	    for (int i = 0; i < members->dim; i++)
	    {
		Dsymbol *s = (Dsymbol *)members->data[i];
		s->emitComment(sc);
	    }
	    return;
	}
    }
    if (!comment)
	return;
    if (isAnonymous())
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	toDocBuffer(buf);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    emitMemberComments(sc);
    buf->writestring(ddoc_decl_dd_e);
}

void EnumMember::emitComment(Scope *sc)
{
    //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", this, toChars(), comment);
    if (prot() == PROTprivate)
	return;
    if (!comment)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);
    unsigned o;

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	o = buf->offset;
	toDocBuffer(buf);
	highlightCode(sc, this, buf, o);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    buf->writestring(ddoc_decl_dd_e);
}

/******************************* toDocBuffer **********************************/

void Dsymbol::toDocBuffer(OutBuffer *buf)
{
    //printf("Dsymbol::toDocbuffer() %s\n", toChars());
    HdrGenState hgs;

    hgs.ddoc = 1;
    toCBuffer(buf, &hgs);
}

void prefix(OutBuffer *buf, Dsymbol *s)
{
    if (s->isDeprecated())
	buf->writestring("deprecated ");
    Declaration *d = s->isDeclaration();
    if (d)
    {
	emitProtection(buf, d->protection);
	if (d->isAbstract())
	    buf->writestring("abstract ");
	if (d->isStatic())
	    buf->writestring("static ");
	if (d->isConst())
	    buf->writestring("const ");
#if DMDV2
	if (d->isInvariant())
	    buf->writestring("invariant ");
#endif
	if (d->isFinal())
	    buf->writestring("final ");
	if (d->isSynchronized())
	    buf->writestring("synchronized ");
    }
}

void Declaration::toDocBuffer(OutBuffer *buf)
{
    //printf("Declaration::toDocbuffer() %s, originalType = %p\n", toChars(), originalType);
    if (ident)
    {
	prefix(buf, this);

	if (type)
	{   HdrGenState hgs;
	    hgs.ddoc = 1;
	    if (originalType)
	    {	//originalType->print();
		originalType->toCBuffer(buf, ident, &hgs);
	    }
	    else
		type->toCBuffer(buf, ident, &hgs);
	}
	else
	    buf->writestring(ident->toChars());
	buf->writestring(";\n");
    }
}


void AliasDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("AliasDeclaration::toDocbuffer() %s\n", toChars());
    if (ident)
    {
	if (isDeprecated())
	    buf->writestring("deprecated ");

	emitProtection(buf, protection);
	buf->writestring("alias ");
	buf->writestring(toChars());
	buf->writestring(";\n");
    }
}


void TypedefDeclaration::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
	if (isDeprecated())
	    buf->writestring("deprecated ");

	emitProtection(buf, protection);
	buf->writestring("typedef ");
	buf->writestring(toChars());
	buf->writestring(";\n");
    }
}


void FuncDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("FuncDeclaration::toDocbuffer() %s\n", toChars());
    if (ident)
    {
	TemplateDeclaration *td;

	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{   /* It's a function template
	     */
	    HdrGenState hgs;
	    unsigned o = buf->offset;
	    TypeFunction *tf = (TypeFunction *)type;

	    hgs.ddoc = 1;
	    prefix(buf, td);
	    tf->next->toCBuffer(buf, NULL, &hgs);
	    buf->writeByte(' ');
	    buf->writestring(ident->toChars());
	    buf->writeByte('(');
	    for (int i = 0; i < td->origParameters->dim; i++)
	    {
		TemplateParameter *tp = (TemplateParameter *)td->origParameters->data[i];
		if (i)
		    buf->writestring(", ");
		tp->toCBuffer(buf, &hgs);
	    }
	    buf->writeByte(')');
	    Argument::argsToCBuffer(buf, &hgs, tf->parameters, tf->varargs);
	    buf->writestring(";\n");

	    highlightCode(NULL, this, buf, o);
	}
	else
	{
	    Declaration::toDocBuffer(buf);
	}
    }
}

void CtorDeclaration::toDocBuffer(OutBuffer *buf)
{
    HdrGenState hgs;

    buf->writestring("this");
    Argument::argsToCBuffer(buf, &hgs, arguments, varargs);
    buf->writestring(";\n");
}


void AggregateDeclaration::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
#if 0
	emitProtection(buf, protection);
#endif
	buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), toChars());
	buf->writestring(";\n");
    }
}

void StructDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("StructDeclaration::toDocbuffer() %s\n", toChars());
    if (ident)
    {
#if 0
	emitProtection(buf, protection);
#endif
	TemplateDeclaration *td;

	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{   unsigned o = buf->offset;
	    td->toDocBuffer(buf);
	    highlightCode(NULL, this, buf, o);
	}
	else
	{
	    buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), toChars());
	}
	buf->writestring(";\n");
    }
}

void ClassDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("ClassDeclaration::toDocbuffer() %s\n", toChars());
    if (ident)
    {
#if 0
	emitProtection(buf, protection);
#endif
	TemplateDeclaration *td;

	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{   unsigned o = buf->offset;
	    td->toDocBuffer(buf);
	    highlightCode(NULL, this, buf, o);
	}
	else
	{
	    if (isAbstract())
		buf->writestring("abstract ");
	    buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), toChars());
	}
	int any = 0;
	for (int i = 0; i < baseclasses.dim; i++)
	{   BaseClass *bc = (BaseClass *)baseclasses.data[i];

	    if (bc->protection == PROTprivate)
		continue;
	    if (bc->base && bc->base->ident == Id::Object)
		continue;

	    if (any)
		buf->writestring(", ");
	    else
	    {	buf->writestring(": ");
		any = 1;
	    }
	    emitProtection(buf, bc->protection);
	    if (bc->base)
	    {
		buf->writestring(bc->base->toPrettyChars());
	    }
	    else
	    {
		HdrGenState hgs;
		bc->type->toCBuffer(buf, NULL, &hgs);
	    }
	}
	buf->writestring(";\n");
    }
}


void EnumDeclaration::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
	buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), toChars());
	buf->writestring(";\n");
    }
}

void EnumMember::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
	buf->writestring(toChars());
    }
}


/********************************* DocComment *********************************/

DocComment::DocComment()
{
    memset(this, 0, sizeof(DocComment));
}

DocComment *DocComment::parse(Scope *sc, Dsymbol *s, unsigned char *comment)
{   unsigned idlen;

    //printf("parse(%s): '%s'\n", s->toChars(), comment);
    if (sc->lastdc && isDitto(comment))
	return NULL;

    DocComment *dc = new DocComment();
    if (!comment)
	return dc;

    dc->parseSections(comment);

    for (int i = 0; i < dc->sections.dim; i++)
    {	Section *s = (Section *)dc->sections.data[i];

	if (icmp("copyright", s->name, s->namelen) == 0)
	{
	    dc->copyright = s;
	}
	if (icmp("macros", s->name, s->namelen) == 0)
	{
	    dc->macros = s;
	}
    }

    sc->lastdc = dc;
    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(unsigned char *comment)
{   unsigned char *p;
    unsigned char *pstart;
    unsigned char *pend;
    unsigned char *q;
    unsigned char *idstart;
    unsigned idlen;

    unsigned char *name = NULL;
    unsigned namelen = 0;

    //printf("parseSections('%s')\n", comment);
    p = comment;
    while (*p)
    {
	p = skipwhitespace(p);
	pstart = p;

	/* Find end of section, which is ended by one of:
	 *	'identifier:'
	 *	'\0'
	 */
	idlen = 0;
	while (1)
	{
	    if (isalpha(*p) || *p == '_')
	    {
		q = p + 1;
		while (isalnum(*q) || *q == '_')
		    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)
		{   pend = p;
		    goto L1;
		}
		if (*p == '\n')
		{   p++;
		    if (*p == '\n' && !summary && !namelen)
		    {
			pend = p;
			p++;
			goto L1;
		    }
		    break;
		}
		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, Dsymbol *s, OutBuffer *buf)
{
    //printf("DocComment::writeSections()\n");
    if (sections.dim)
    {
	buf->writestring("$(DDOC_SECTIONS \n");
	for (int i = 0; i < sections.dim; i++)
	{   Section *sec = (Section *)sections.data[i];

	    if (sec->nooutput)
		continue;
	    //printf("Section: '%.*s' = '%.*s'\n", sec->namelen, sec->name, sec->bodylen, sec->body);
	    if (sec->namelen || i)
		sec->write(this, sc, s, buf);
	    else
	    {
		buf->writestring("$(DDOC_SUMMARY ");
		    unsigned o = buf->offset;
		    buf->write(sec->body, sec->bodylen);
		    highlightText(sc, s, buf, o);
		buf->writestring(")\n");
	    }
	}
	buf->writestring(")\n");
    }
    else
    {
	buf->writestring("$(DDOC_BLANKLINE)\n");
    }
}

/***************************************************
 */

void Section::write(DocComment *dc, Scope *sc, Dsymbol *s, OutBuffer *buf)
{
    if (namelen)
    {
	static const char *table[] =
	{	"AUTHORS", "BUGS", "COPYRIGHT", "DATE",
		"DEPRECATED", "EXAMPLES", "HISTORY", "LICENSE",
		"RETURNS", "SEE_ALSO", "STANDARDS", "THROWS",
		"VERSION" };

	for (int i = 0; i < sizeof(table) / sizeof(table[0]); 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 ");
	    for (unsigned u = 0; u < namelen; u++)
	    {   unsigned char c = name[u];
		buf->writeByte((c == '_') ? ' ' : c);
	    }
	    buf->writestring(":)\n");
    }
    else
    {
	buf->writestring("$(DDOC_DESCRIPTION ");
    }
  L1:
    unsigned o = buf->offset;
    buf->write(body, bodylen);
    highlightText(sc, s, buf, o);
    buf->writestring(")\n");
}

/***************************************************
 */

void ParamSection::write(DocComment *dc, Scope *sc, Dsymbol *s, OutBuffer *buf)
{
    unsigned char *p = body;
    unsigned len = bodylen;
    unsigned char *pend = p + len;

    unsigned char *tempstart;
    unsigned templen;

    unsigned char *namestart;
    unsigned namelen = 0;	// !=0 if line continuation

    unsigned char *textstart;
    unsigned textlen;

    unsigned o;
    Argument *arg;

    buf->writestring("$(DDOC_PARAMS \n");
    while (p < pend)
    {
	// Skip to start of macro
	for (; 1; p++)
	{
	    switch (*p)
	    {
		case ' ':
		case '\t':
		    continue;

		case '\n':
		    p++;
		    goto Lcont;

		default:
		    if (!(isalpha(*p) || *p == '_'))
		    {
			if (namelen)
			    goto Ltext;		// continuation of prev macro
			goto Lskipline;
		    }
		    break;
	    }
	    break;
	}
	tempstart = p;

	while (isalnum(*p) || *p == '_')
	    p++;
	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);
	    HdrGenState hgs;
	    buf->writestring("$(DDOC_PARAM_ROW ");
		buf->writestring("$(DDOC_PARAM_ID ");
		    o = buf->offset;
		    arg = isFunctionParameter(s, namestart, namelen);
		    if (arg && arg->type && arg->ident)
			arg->type->toCBuffer(buf, arg->ident, &hgs);
		    else
			buf->write(namestart, namelen);
		    highlightCode(sc, s, buf, o);
		buf->writestring(")\n");

		buf->writestring("$(DDOC_PARAM_DESC ");
		    o = buf->offset;
		    buf->write(textstart, textlen);
		    highlightText(sc, s, 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");
}

/***************************************************
 */

void MacroSection::write(DocComment *dc, Scope *sc, Dsymbol *s, OutBuffer *buf)
{
    //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, unsigned char *m, unsigned mlen)
{
    unsigned char *p = m;
    unsigned len = mlen;
    unsigned char *pend = p + len;

    unsigned char *tempstart;
    unsigned templen;

    unsigned char *namestart;
    unsigned namelen = 0;	// !=0 if line continuation

    unsigned char *textstart;
    unsigned textlen;

    while (p < pend)
    {
	// Skip to start of macro
	for (; 1; p++)
	{
	    if (p >= pend)
		goto Ldone;
	    switch (*p)
	    {
		case ' ':
		case '\t':
		    continue;

		case '\n':
		    p++;
		    goto Lcont;

		default:
		    if (!(isalpha(*p) || *p == '_'))
		    {
			if (namelen)
			    goto Ltext;		// continuation of prev macro
			goto Lskipline;
		    }
		    break;
	    }
	    break;
	}
	tempstart = p;

	while (1)
	{
	    if (p >= pend)
		goto Ldone;
	    if (!(isalnum(*p) || *p == '_'))
		break;
	    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 != '\n')
	    p++;
	textlen = p - textstart;

	// Remove trailing \r if there is one
	if (p > m && p[-1] == '\r')
	    textlen--;

	p++;
	//printf("p = %p, pend = %p\n", p, pend);

     Lcont:
	continue;

     Lskipline:
	// Ignore this line
	while (p < pend && *p++ != '\n')
	    ;
    }
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, unsigned char *textstart, unsigned textlen)
{   Escape *escapetable = *pescapetable;

    if (!escapetable)
    {	escapetable = new Escape;
	*pescapetable = escapetable;
    }
    unsigned char *p = textstart;
    unsigned char *pend = p + textlen;

    while (1)
    {
	while (1)
	{
	    if (p + 4 >= pend)
		return;
	    if (!(*p == ' ' || *p == '\t' || *p == '\n' || *p == ','))
		break;
	    p++;
	}
	if (p[0] != '/' || p[2] != '/')
	    return;
	unsigned char c = p[1];
	p += 3;
	unsigned char *start = p;
	while (1)
	{
	    if (p >= pend)
		return;
	    if (*p == '/')
		break;
	    p++;
	}
	size_t len = p - start;
	char *s = (char *)memcpy(mem.malloc(len + 1), start, len);
	s[len] = 0;
	escapetable->strings[c] = s;
	//printf("%c = '%s'\n", c, s);
	p++;
    }
}


/******************************************
 * Compare 0-terminated string with length terminated string.
 * Return < 0, ==0, > 0
 */

int cmp(const char *stringz, void *s, size_t slen)
{
    size_t len1 = strlen(stringz);

    if (len1 != slen)
	return len1 - slen;
    return memcmp(stringz, s, slen);
}

int icmp(const char *stringz, void *s, size_t slen)
{
    size_t len1 = strlen(stringz);

    if (len1 != slen)
	return len1 - slen;
    return memicmp(stringz, (char *)s, slen);
}

/*****************************************
 * Return !=0 if comment consists entirely of "ditto".
 */

int isDitto(unsigned char *comment)
{
    if (comment)
    {
	unsigned char *p = skipwhitespace(comment);

	if (memicmp((char *)p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
	    return 1;
    }
    return 0;
}

/**********************************************
 * Skip white space.
 */

unsigned char *skipwhitespace(unsigned char *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
 */

unsigned skiptoident(OutBuffer *buf, unsigned i)
{
    for (; i < buf->offset; i++)
    {
	// BUG: handle unicode alpha's
	unsigned char c = buf->data[i];
	if (isalpha(c) || c == '_')
	    break;
	if (c == '\n')
	    break;
    }
    return i;
}

/************************************************
 * Scan forward past end of identifier.
 */

unsigned skippastident(OutBuffer *buf, unsigned i)
{
    for (; i < buf->offset; i++)
    {
	// BUG: handle unicode alpha's
	unsigned char c = buf->data[i];
	if (!(isalnum(c) || c == '_'))
	    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
 */

unsigned skippastURL(OutBuffer *buf, unsigned i)
{   unsigned length = buf->offset - i;
    unsigned char *p = &buf->data[i];
    unsigned j;
    unsigned sawdot = 0;

    if (length > 7 && memicmp((char *)p, "http://", 7) == 0)
    {
	j = 7;
    }
    else if (length > 8 && memicmp((char *)p, "https://", 8) == 0)
    {
	j = 8;
    }
    else
	goto Lno;

    for (; j < length; j++)
    {	unsigned 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;
}


/****************************************************
 */

int isKeyword(unsigned char *p, unsigned len)
{
    static const char *table[] = { "true", "false", "null" };

    for (int i = 0; i < sizeof(table) / sizeof(table[0]); i++)
    {
	if (cmp(table[i], p, len) == 0)
	    return 1;
    }
    return 0;
}

/****************************************************
 */

Argument *isFunctionParameter(Dsymbol *s, unsigned char *p, unsigned len)
{
    FuncDeclaration *f = s->isFuncDeclaration();

    /* f->type may be NULL for template members.
     */
    if (f && f->type)
    {
	TypeFunction *tf;
	if (f->originalType)
	{
	    tf = (TypeFunction *)f->originalType;
	}
	else
	    tf = (TypeFunction *)f->type;

	if (tf->parameters)
	{
	    for (size_t k = 0; k < tf->parameters->dim; k++)
	    {   Argument *arg = (Argument *)tf->parameters->data[k];

		if (arg->ident && cmp(arg->ident->toChars(), p, len) == 0)
		{
		    return arg;
		}
	    }
	}
    }
    return NULL;
}

/**************************************************
 * Highlight text section.
 */

void highlightText(Scope *sc, Dsymbol *s, OutBuffer *buf, unsigned offset)
{
    //printf("highlightText()\n");
    const char *sid = s->ident->toChars();
    FuncDeclaration *f = s->isFuncDeclaration();
    unsigned char *p;
    const char *se;

    int leadingBlank = 1;
    int inCode = 0;
    int inComment = 0;			// in <!-- ... --> comment
    unsigned iCodeStart;		// start of code section

    unsigned iLineStart = offset;

    for (unsigned i = offset; i < buf->offset; i++)
    {	unsigned char c = buf->data[i];

     Lcont:
	switch (c)
	{
	    case ' ':
	    case '\t':
		break;

	    case '\n':
		if (sc && !inCode && i == iLineStart && i + 1 < buf->offset)	// if "\n\n"
		{
		    static char blankline[] = "$(DDOC_BLANKLINE)\n";

		    i = buf->insert(i, blankline, sizeof(blankline) - 1);
		}
		leadingBlank = 1;
		iLineStart = i + 1;
		break;

	    case '<':
		leadingBlank = 0;
		if (inCode)
		    break;
		p = &buf->data[i];

		// Skip over comments
		if (p[1] == '!' && p[2] == '-' && p[3] == '-')
		{   unsigned 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])))
		{   unsigned j = i + 2;
		    p += 2;
		    while (1)
		    {
			if (j == buf->offset)
			    goto L1;
			if (p[0] == '>')
			{
			    i = j;	// place on closing '>'
			    break;
			}
			j++;
			p++;
		    }
		    break;
		}

	    L1:
		// Replace '<' with '&lt;' character entity
		se = Escape::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;
		// Replace '>' with '&gt;' character entity
		se = Escape::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;
		p = &buf->data[i];
		if (p[1] == '#' || isalpha(p[1]))
		    break;			// already a character entity
		// Replace '&' with '&amp;' character entity
		se = Escape::escapeChar('&');
		if (se)
		{   size_t len = strlen(se);
		    buf->remove(i, 1);
		    i = buf->insert(i, se, len);
		    i--;	// point to ';'
		}
		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)
		{   int istart = i;
		    int 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)
		    {
			inCode = 0;
			// The code section is from iCodeStart to i
			OutBuffer codebuf;

			codebuf.write(buf->data + iCodeStart, i - iCodeStart);
			codebuf.writeByte(0);
			highlightCode2(sc, s, &codebuf, 0);
			buf->remove(iCodeStart, i - iCodeStart);
			i = buf->insert(iCodeStart, codebuf.data, codebuf.offset);
			i = buf->insert(i, ")\n", 2);
			i--;
		    }
		    else
		    {	static char pre[] = "$(D_CODE \n";

			inCode = 1;
			i = buf->insert(i, pre, sizeof(pre) - 1);
			iCodeStart = i;
			i--;		// place i on >
		    }
		}
		break;

	    default:
		leadingBlank = 0;
		if (sc && !inCode && (isalpha(c) || c == '_'))
		{   unsigned j;

		    j = skippastident(buf, i);
		    if (j > i)
		    {
			unsigned k = skippastURL(buf, i);
			if (k > i)
			{   i = k - 1;
			    break;
			}

			if (buf->data[i] == '_')	// leading '_' means no highlight
			{
			    buf->remove(i, 1);
			    i = j - 1;
			}
			else
			{
			    if (cmp(sid, buf->data + i, j - i) == 0)
			    {
				i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
				break;
			    }
			    else if (isKeyword(buf->data + i, j - i))
			    {
				i = buf->bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1;
				break;
			    }
			    else
			    {
				if (f && isFunctionParameter(f, buf->data + i, j - i))
				{
				    //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;
	}
    }
  Ldone:
    ;
}

/**************************************************
 * Highlight code for DDOC section.
 */

void highlightCode(Scope *sc, Dsymbol *s, OutBuffer *buf, unsigned offset)
{
    char *sid = s->ident->toChars();
    FuncDeclaration *f = s->isFuncDeclaration();

    //printf("highlightCode(s = '%s', kind = %s)\n", sid, s->kind());
    for (unsigned i = offset; i < buf->offset; i++)
    {	unsigned char c = buf->data[i];
	const char *se;

	se = Escape::escapeChar(c);
	if (se)
	{
	    size_t len = strlen(se);
	    buf->remove(i, 1);
	    i = buf->insert(i, se, len);
	    i--;		// point to ';'
	}
	else if (isalpha(c) || c == '_')
	{   unsigned j;

	    j = skippastident(buf, i);
	    if (j > i)
	    {
		if (cmp(sid, buf->data + i, j - i) == 0)
		{
		    i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
		    continue;
		}
		else if (f)
		{
		    if (isFunctionParameter(f, buf->data + i, j - i))
		    {
			//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(OutBuffer *buf, unsigned char *p, unsigned char *pend)
{
    for (; p < pend; p++)
    {	const char *s = Escape::escapeChar(*p);
	if (s)
	    buf->writestring(s);
	else
	    buf->writeByte(*p);
    }
}

/**************************************************
 * Highlight code for CODE section.
 */


void highlightCode2(Scope *sc, Dsymbol *s, OutBuffer *buf, unsigned offset)
{
    char *sid = s->ident->toChars();
    FuncDeclaration *f = s->isFuncDeclaration();
    unsigned errorsave = global.errors;
    Lexer lex(NULL, buf->data, 0, buf->offset - 1, 0, 1);
    Token tok;
    OutBuffer res;
    unsigned char *lastp = buf->data;
    const char *highlight;

    //printf("highlightCode2('%.*s')\n", buf->offset - 1, buf->data);
    res.reserve(buf->offset);
    while (1)
    {
	lex.scan(&tok);
	highlightCode3(&res, lastp, tok.ptr);
	highlight = NULL;
	switch (tok.value)
	{
	    case TOKidentifier:
		if (!sc)
		    break;
		if (cmp(sid, tok.ptr, lex.p - tok.ptr) == 0)
		{
		    highlight = "$(D_PSYMBOL ";
		    break;
		}
		else if (f)
		{
		    if (isFunctionParameter(f, tok.ptr, lex.p - tok.ptr))
		    {
			//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);
	highlightCode3(&res, tok.ptr, lex.p);
	if (highlight)
	    res.writeByte(')');
	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)
{   const char *s;

    switch (c)
    {
	case '<':
	    s = "&lt;";
	    break;
	case '>':
	    s = "&gt;";
	    break;
	case '&':
	    s = "&amp;";
	    break;
	default:
	    s = NULL;
	    break;
    }
    return s;
}