diff options
author | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
commit | b4c522fabd0df7be08882d2207df8b2765026110 (patch) | |
tree | b5ffc312b0a441c1ba24323152aec463fdbe5e9f /gcc/d/dmd/doc.c | |
parent | 01ce9e31a02c8039d88e90f983735104417bf034 (diff) | |
download | gcc-b4c522fabd0df7be08882d2207df8b2765026110.zip gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.gz gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.bz2 |
Add D front-end, libphobos library, and D2 testsuite.
ChangeLog:
* Makefile.def (target_modules): Add libphobos.
(flags_to_pass): Add GDC, GDCFLAGS, GDC_FOR_TARGET and
GDCFLAGS_FOR_TARGET.
(dependencies): Make libphobos depend on libatomic, libbacktrace
configure, and zlib configure.
(language): Add language d.
* Makefile.in: Rebuild.
* Makefile.tpl (BUILD_EXPORTS): Add GDC and GDCFLAGS.
(HOST_EXPORTS): Add GDC.
(POSTSTAGE1_HOST_EXPORTS): Add GDC and GDC_FOR_BUILD.
(BASE_TARGET_EXPORTS): Add GDC.
(GDC_FOR_BUILD, GDC, GDCFLAGS): New variables.
(GDC_FOR_TARGET, GDC_FLAGS_FOR_TARGET): New variables.
(EXTRA_HOST_FLAGS): Add GDC.
(STAGE1_FLAGS_TO_PASS): Add GDC.
(EXTRA_TARGET_FLAGS): Add GDC and GDCFLAGS.
* config-ml.in: Treat GDC and GDCFLAGS like other compiler/flag
environment variables.
* configure: Rebuild.
* configure.ac: Add target-libphobos to target_libraries. Set and
substitute GDC_FOR_BUILD and GDC_FOR_TARGET.
config/ChangeLog:
* multi.m4: Set GDC.
gcc/ChangeLog:
* Makefile.in (tm_d_file_list, tm_d_include_list): New variables.
(TM_D_H, D_TARGET_DEF, D_TARGET_H, D_TARGET_OBJS): New variables.
(tm_d.h, cs-tm_d.h, default-d.o): New rules.
(d/d-target-hooks-def.h, s-d-target-hooks-def-h): New rules.
(s-tm-texi): Also check timestamp on d-target.def.
(generated_files): Add TM_D_H and d-target-hooks-def.h.
(build/genhooks.o): Also depend on D_TARGET_DEF.
* config.gcc (tm_d_file, d_target_objs, target_has_targetdm): New
variables.
* config/aarch64/aarch64-d.c: New file.
* config/aarch64/aarch64-linux.h (GNU_USER_TARGET_D_CRITSEC_SIZE):
Define.
* config/aarch64/aarch64-protos.h (aarch64_d_target_versions): New
prototype.
* config/aarch64/aarch64.h (TARGET_D_CPU_VERSIONS): Define.
* config/aarch64/t-aarch64 (aarch64-d.o): New rule.
* config/arm/arm-d.c: New file.
* config/arm/arm-protos.h (arm_d_target_versions): New prototype.
* config/arm/arm.h (TARGET_D_CPU_VERSIONS): Define.
* config/arm/linux-eabi.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
* config/arm/t-arm (arm-d.o): New rule.
* config/default-d.c: New file.
* config/glibc-d.c: New file.
* config/gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/i386/i386-d.c: New file.
* config/i386/i386-protos.h (ix86_d_target_versions): New prototype.
* config/i386/i386.h (TARGET_D_CPU_VERSIONS): Define.
* config/i386/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
(GNU_USER_TARGET_D_CRITSEC_SIZE): Define.
* config/i386/t-i386 (i386-d.o): New rule.
* config/kfreebsd-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/kopensolaris-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/linux-android.h (ANDROID_TARGET_D_OS_VERSIONS): Define.
* config/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/mips/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
* config/mips/mips-d.c: New file.
* config/mips/mips-protos.h (mips_d_target_versions): New prototype.
* config/mips/mips.h (TARGET_D_CPU_VERSIONS): Define.
* config/mips/t-mips (mips-d.o): New rule.
* config/powerpcspe/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/powerpcspe/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/powerpcspe/powerpcspe-d.c: New file.
* config/powerpcspe/powerpcspe-protos.h (rs6000_d_target_versions):
New prototype.
* config/powerpcspe/powerpcspe.c (rs6000_output_function_epilogue):
Support GNU D by using 0 as the language type.
* config/powerpcspe/powerpcspe.h (TARGET_D_CPU_VERSIONS): Define.
* config/powerpcspe/t-powerpcspe (powerpcspe-d.o): New rule.
* config/riscv/riscv-d.c: New file.
* config/riscv/riscv-protos.h (riscv_d_target_versions): New
prototype.
* config/riscv/riscv.h (TARGET_D_CPU_VERSIONS): Define.
* config/riscv/t-riscv (riscv-d.o): New rule.
* config/rs6000/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/rs6000/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/rs6000/rs6000-d.c: New file.
* config/rs6000/rs6000-protos.h (rs6000_d_target_versions): New
prototype.
* config/rs6000/rs6000.c (rs6000_output_function_epilogue):
Support GNU D by using 0 as the language type.
* config/rs6000/rs6000.h (TARGET_D_CPU_VERSIONS): Define.
* config/rs6000/t-rs6000 (rs6000-d.o): New rule.
* config/s390/s390-d.c: New file.
* config/s390/s390-protos.h (s390_d_target_versions): New prototype.
* config/s390/s390.h (TARGET_D_CPU_VERSIONS): Define.
* config/s390/t-s390 (s390-d.o): New rule.
* config/sparc/sparc-d.c: New file.
* config/sparc/sparc-protos.h (sparc_d_target_versions): New
prototype.
* config/sparc/sparc.h (TARGET_D_CPU_VERSIONS): Define.
* config/sparc/t-sparc (sparc-d.o): New rule.
* config/t-glibc (glibc-d.o): New rule.
* configure: Regenerated.
* configure.ac (tm_d_file): New variable.
(tm_d_file_list, tm_d_include_list, d_target_objs): Add substitutes.
* doc/contrib.texi (Contributors): Add self for the D frontend.
* doc/frontends.texi (G++ and GCC): Mention D as a supported language.
* doc/install.texi (Configuration): Mention libphobos as an option for
--enable-shared. Mention d as an option for --enable-languages.
(Testing): Mention check-d as a target.
* doc/invoke.texi (Overall Options): Mention .d, .dd, and .di as file
name suffixes. Mention d as a -x option.
* doc/sourcebuild.texi (Top Level): Mention libphobos.
* doc/standards.texi (Standards): Add section on D language.
* doc/tm.texi: Regenerated.
* doc/tm.texi.in: Add @node for D language and ABI, and @hook for
TARGET_CPU_VERSIONS, TARGET_D_OS_VERSIONS, and TARGET_D_CRITSEC_SIZE.
* dwarf2out.c (is_dlang): New function.
(gen_compile_unit_die): Use DW_LANG_D for D.
(declare_in_namespace): Return module die for D, instead of adding
extra declarations into the namespace.
(gen_namespace_die): Generate DW_TAG_module for D.
(gen_decl_die): Handle CONST_DECLSs for D.
(dwarf2out_decl): Likewise.
(prune_unused_types_walk_local_classes): Handle DW_tag_interface_type.
(prune_unused_types_walk): Handle DW_tag_interface_type same as other
kinds of aggregates.
* gcc.c (default_compilers): Add entries for .d, .dd and .di.
* genhooks.c: Include d/d-target.def.
gcc/po/ChangeLog:
* EXCLUDES: Add sources from d/dmd.
gcc/testsuite/ChangeLog:
* gcc.misc-tests/help.exp: Add D to option descriptions check.
* gdc.dg/asan/asan.exp: New file.
* gdc.dg/asan/gdc272.d: New test.
* gdc.dg/compilable.d: New test.
* gdc.dg/dg.exp: New file.
* gdc.dg/gdc254.d: New test.
* gdc.dg/gdc260.d: New test.
* gdc.dg/gdc270a.d: New test.
* gdc.dg/gdc270b.d: New test.
* gdc.dg/gdc282.d: New test.
* gdc.dg/gdc283.d: New test.
* gdc.dg/imports/gdc170.d: New test.
* gdc.dg/imports/gdc231.d: New test.
* gdc.dg/imports/gdc239.d: New test.
* gdc.dg/imports/gdc241a.d: New test.
* gdc.dg/imports/gdc241b.d: New test.
* gdc.dg/imports/gdc251a.d: New test.
* gdc.dg/imports/gdc251b.d: New test.
* gdc.dg/imports/gdc253.d: New test.
* gdc.dg/imports/gdc254a.d: New test.
* gdc.dg/imports/gdc256.d: New test.
* gdc.dg/imports/gdc27.d: New test.
* gdc.dg/imports/gdcpkg256/package.d: New test.
* gdc.dg/imports/runnable.d: New test.
* gdc.dg/link.d: New test.
* gdc.dg/lto/lto.exp: New file.
* gdc.dg/lto/ltotests_0.d: New test.
* gdc.dg/lto/ltotests_1.d: New test.
* gdc.dg/runnable.d: New test.
* gdc.dg/simd.d: New test.
* gdc.test/gdc-test.exp: New file.
* lib/gdc-dg.exp: New file.
* lib/gdc.exp: New file.
libphobos/ChangeLog:
* Makefile.am: New file.
* Makefile.in: New file.
* acinclude.m4: New file.
* aclocal.m4: New file.
* config.h.in: New file.
* configure: New file.
* configure.ac: New file.
* d_rules.am: New file.
* libdruntime/Makefile.am: New file.
* libdruntime/Makefile.in: New file.
* libdruntime/__entrypoint.di: New file.
* libdruntime/__main.di: New file.
* libdruntime/gcc/attribute.d: New file.
* libdruntime/gcc/backtrace.d: New file.
* libdruntime/gcc/builtins.d: New file.
* libdruntime/gcc/config.d.in: New file.
* libdruntime/gcc/deh.d: New file.
* libdruntime/gcc/libbacktrace.d.in: New file.
* libdruntime/gcc/unwind/arm.d: New file.
* libdruntime/gcc/unwind/arm_common.d: New file.
* libdruntime/gcc/unwind/c6x.d: New file.
* libdruntime/gcc/unwind/generic.d: New file.
* libdruntime/gcc/unwind/package.d: New file.
* libdruntime/gcc/unwind/pe.d: New file.
* m4/autoconf.m4: New file.
* m4/druntime.m4: New file.
* m4/druntime/cpu.m4: New file.
* m4/druntime/libraries.m4: New file.
* m4/druntime/os.m4: New file.
* m4/gcc_support.m4: New file.
* m4/gdc.m4: New file.
* m4/libtool.m4: New file.
* src/Makefile.am: New file.
* src/Makefile.in: New file.
* src/libgphobos.spec.in: New file.
* testsuite/Makefile.am: New file.
* testsuite/Makefile.in: New file.
* testsuite/config/default.exp: New file.
* testsuite/lib/libphobos-dg.exp: New file.
* testsuite/lib/libphobos.exp: New file.
* testsuite/testsuite_flags.in: New file.
From-SVN: r265573
Diffstat (limited to 'gcc/d/dmd/doc.c')
-rw-r--r-- | gcc/d/dmd/doc.c | 2741 |
1 files changed, 2741 insertions, 0 deletions
diff --git a/gcc/d/dmd/doc.c b/gcc/d/dmd/doc.c new file mode 100644 index 0000000..92ce33c --- /dev/null +++ b/gcc/d/dmd/doc.c @@ -0,0 +1,2741 @@ + +/* Compiler implementation of the D programming language + * Copyright (C) 1999-2018 by The D Language Foundation, All Rights Reserved + * written by Walter Bright + * http://www.digitalmars.com + * Distributed under the Boost Software License, Version 1.0. + * http://www.boost.org/LICENSE_1_0.txt + * https://github.com/D-Programming-Language/dmd/blob/master/src/doc.c + */ + +// This implements the Ddoc capability. + +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <ctype.h> +#include <assert.h> + +#include "root/rmem.h" +#include "root/root.h" +#include "root/port.h" +#include "root/aav.h" + +#include "attrib.h" +#include "cond.h" +#include "mars.h" +#include "dsymbol.h" +#include "macro.h" +#include "template.h" +#include "lexer.h" +#include "aggregate.h" +#include "declaration.h" +#include "statement.h" +#include "enum.h" +#include "id.h" +#include "module.h" +#include "scope.h" +#include "hdrgen.h" +#include "doc.h" +#include "mtype.h" +#include "utf.h" + +void emitMemberComments(ScopeDsymbol *sds, OutBuffer *buf, Scope *sc); +void toDocBuffer(Dsymbol *s, OutBuffer *buf, Scope *sc); +void emitComment(Dsymbol *s, OutBuffer *buf, Scope *sc); + +struct Escape +{ + const char *strings[256]; + + const char *escapeChar(unsigned c); +}; + +class Section +{ +public: + const utf8_t *name; + size_t namelen; + + const utf8_t *body; + size_t bodylen; + + int nooutput; + + virtual void write(Loc loc, DocComment *dc, Scope *sc, Dsymbols *a, OutBuffer *buf); +}; + +class ParamSection : public Section +{ +public: + void write(Loc loc, DocComment *dc, Scope *sc, Dsymbols *a, OutBuffer *buf); +}; + +class MacroSection : public Section +{ +public: + void write(Loc loc, DocComment *dc, Scope *sc, Dsymbols *a, OutBuffer *buf); +}; + +typedef Array<Section *> Sections; + +struct DocComment +{ + Sections sections; // Section*[] + + Section *summary; + Section *copyright; + Section *macros; + Macro **pmacrotable; + Escape **pescapetable; + + Dsymbols a; + + DocComment() : + summary(NULL), copyright(NULL), macros(NULL), pmacrotable(NULL), pescapetable(NULL) + { } + + static DocComment *parse(Dsymbol *s, const utf8_t *comment); + static void parseMacros(Escape **pescapetable, Macro **pmacrotable, const utf8_t *m, size_t mlen); + static void parseEscapes(Escape **pescapetable, const utf8_t *textstart, size_t textlen); + + void parseSections(const utf8_t *comment); + void writeSections(Scope *sc, Dsymbols *a, OutBuffer *buf); +}; + + +int cmp(const char *stringz, const void *s, size_t slen); +int icmp(const char *stringz, const void *s, size_t slen); +bool isDitto(const utf8_t *comment); +const utf8_t *skipwhitespace(const utf8_t *p); +size_t skiptoident(OutBuffer *buf, size_t i); +size_t skippastident(OutBuffer *buf, size_t i); +size_t skippastURL(OutBuffer *buf, size_t i); +void highlightText(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset); +void highlightCode(Scope *sc, Dsymbol *s, OutBuffer *buf, size_t offset); +void highlightCode(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset); +void highlightCode2(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset); +void highlightCode3(Scope *sc, OutBuffer *buf, const utf8_t *p, const utf8_t *pend); +TypeFunction *isTypeFunction(Dsymbol *s); +Parameter *isFunctionParameter(Dsymbols *a, const utf8_t *p, size_t len); +TemplateParameter *isTemplateParameter(Dsymbols *a, const utf8_t *p, size_t len); + +bool isIdStart(const utf8_t *p); +bool isCVariadicArg(const utf8_t *p, size_t len); +bool isIdTail(const utf8_t *p); +bool isIndentWS(const utf8_t *p); +int utfStride(const utf8_t *p); + +// Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one). +bool isCVariadicParameter(Dsymbols *a, const utf8_t *p, size_t len) +{ + for (size_t i = 0; i < a->dim; i++) + { + TypeFunction *tf = isTypeFunction((*a)[i]); + if (tf && tf->varargs == 1 && cmp("...", p, len) == 0) + return true; + } + return false; +} + +static Dsymbol *getEponymousMember(TemplateDeclaration *td) +{ + if (!td->onemember) + return NULL; + + if (AggregateDeclaration *ad = td->onemember->isAggregateDeclaration()) + return ad; + if (FuncDeclaration *fd = td->onemember->isFuncDeclaration()) + return fd; + if (td->onemember->isEnumMember()) + return NULL; // Keep backward compatibility. See compilable/ddoc9.d + if (VarDeclaration *vd = td->onemember->isVarDeclaration()) + return td->constraint ? NULL : vd; + + return NULL; +} + +static TemplateDeclaration *getEponymousParent(Dsymbol *s) +{ + if (!s->parent) + return NULL; + TemplateDeclaration *td = s->parent->isTemplateDeclaration(); + return (td && getEponymousMember(td)) ? td : NULL; +} + +static const char ddoc_default[] = "\ +DDOC = <html><head>\n\ + <META http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n\ + <title>$(TITLE)</title>\n\ + </head><body>\n\ + <h1>$(TITLE)</h1>\n\ + $(BODY)\n\ + <hr>$(SMALL Page generated by $(LINK2 http://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT))\n\ + </body></html>\n\ +\n\ +B = <b>$0</b>\n\ +I = <i>$0</i>\n\ +U = <u>$0</u>\n\ +P = <p>$0</p>\n\ +DL = <dl>$0</dl>\n\ +DT = <dt>$0</dt>\n\ +DD = <dd>$0</dd>\n\ +TABLE = <table>$0</table>\n\ +TR = <tr>$0</tr>\n\ +TH = <th>$0</th>\n\ +TD = <td>$0</td>\n\ +OL = <ol>$0</ol>\n\ +UL = <ul>$0</ul>\n\ +LI = <li>$0</li>\n\ +BIG = <big>$0</big>\n\ +SMALL = <small>$0</small>\n\ +BR = <br>\n\ +LINK = <a href=\"$0\">$0</a>\n\ +LINK2 = <a href=\"$1\">$+</a>\n\ +LPAREN= (\n\ +RPAREN= )\n\ +BACKTICK= `\n\ +DOLLAR= $\n\ +DEPRECATED= $0\n\ +\n\ +RED = <font color=red>$0</font>\n\ +BLUE = <font color=blue>$0</font>\n\ +GREEN = <font color=green>$0</font>\n\ +YELLOW =<font color=yellow>$0</font>\n\ +BLACK = <font color=black>$0</font>\n\ +WHITE = <font color=white>$0</font>\n\ +\n\ +D_CODE = <pre class=\"d_code\">$0</pre>\n\ +DDOC_BACKQUOTED = $(D_INLINECODE $0)\n\ +D_INLINECODE = <pre style=\"display:inline;\" class=\"d_inline_code\">$0</pre>\n\ +D_COMMENT = $(GREEN $0)\n\ +D_STRING = $(RED $0)\n\ +D_KEYWORD = $(BLUE $0)\n\ +D_PSYMBOL = $(U $0)\n\ +D_PARAM = $(I $0)\n\ +\n\ +DDOC_COMMENT = <!-- $0 -->\n\ +DDOC_DECL = $(DT $(BIG $0))\n\ +DDOC_DECL_DD = $(DD $0)\n\ +DDOC_DITTO = $(BR)$0\n\ +DDOC_SECTIONS = $0\n\ +DDOC_SUMMARY = $0$(BR)$(BR)\n\ +DDOC_DESCRIPTION = $0$(BR)$(BR)\n\ +DDOC_AUTHORS = $(B Authors:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_BUGS = $(RED BUGS:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_COPYRIGHT = $(B Copyright:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_DATE = $(B Date:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_DEPRECATED = $(RED Deprecated:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_EXAMPLES = $(B Examples:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_HISTORY = $(B History:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_LICENSE = $(B License:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_RETURNS = $(B Returns:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_SEE_ALSO = $(B See Also:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_STANDARDS = $(B Standards:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_THROWS = $(B Throws:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_VERSION = $(B Version:)$(BR)\n$0$(BR)$(BR)\n\ +DDOC_SECTION_H = $(B $0)$(BR)\n\ +DDOC_SECTION = $0$(BR)$(BR)\n\ +DDOC_MEMBERS = $(DL $0)\n\ +DDOC_MODULE_MEMBERS = $(DDOC_MEMBERS $0)\n\ +DDOC_CLASS_MEMBERS = $(DDOC_MEMBERS $0)\n\ +DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0)\n\ +DDOC_ENUM_MEMBERS = $(DDOC_MEMBERS $0)\n\ +DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0)\n\ +DDOC_ENUM_BASETYPE = $0\n\ +DDOC_PARAMS = $(B Params:)$(BR)\n$(TABLE $0)$(BR)\n\ +DDOC_PARAM_ROW = $(TR $0)\n\ +DDOC_PARAM_ID = $(TD $0)\n\ +DDOC_PARAM_DESC = $(TD $0)\n\ +DDOC_BLANKLINE = $(BR)$(BR)\n\ +\n\ +DDOC_ANCHOR = <a name=\"$1\"></a>\n\ +DDOC_PSYMBOL = $(U $0)\n\ +DDOC_PSUPER_SYMBOL = $(U $0)\n\ +DDOC_KEYWORD = $(B $0)\n\ +DDOC_PARAM = $(I $0)\n\ +\n\ +ESCAPES = /</</\n\ + />/>/\n\ + /&/&/\n\ +"; + +static const char ddoc_decl_s[] = "$(DDOC_DECL "; +static const char ddoc_decl_e[] = ")\n"; + +static const char ddoc_decl_dd_s[] = "$(DDOC_DECL_DD "; +static const char ddoc_decl_dd_e[] = ")\n"; + + +/**************************************************** + */ + +void gendocfile(Module *m) +{ + static OutBuffer mbuf; + static int mbuf_done; + + OutBuffer buf; + + //printf("Module::gendocfile()\n"); + + if (!mbuf_done) // if not already read the ddoc files + { + mbuf_done = 1; + + // Use our internal default + mbuf.write(ddoc_default, strlen(ddoc_default)); + + // Override with DDOCFILE specified in the sc.ini file + char *p = getenv("DDOCFILE"); + if (p) + global.params.ddocfiles->shift(p); + + // Override with the ddoc macro files from the command line + for (size_t i = 0; i < global.params.ddocfiles->dim; i++) + { + FileName f((*global.params.ddocfiles)[i]); + File file(&f); + readFile(m->loc, &file); + // BUG: convert file contents to UTF-8 before use + + //printf("file: '%.*s'\n", file.len, file.buffer); + mbuf.write(file.buffer, file.len); + } + } + DocComment::parseMacros(&m->escapetable, &m->macrotable, (utf8_t *)mbuf.data, mbuf.offset); + + Scope *sc = Scope::createGlobal(m); // create root scope + + DocComment *dc = DocComment::parse(m, m->comment); + dc->pmacrotable = &m->macrotable; + dc->pescapetable = &m->escapetable; + sc->lastdc = dc; + + // Generate predefined macros + + // Set the title to be the name of the module + { + const char *p = m->toPrettyChars(); + Macro::define(&m->macrotable, (const utf8_t *)"TITLE", 5, (const utf8_t *)p, strlen(p)); + } + + // Set time macros + { + time_t t; + time(&t); + char *p = ctime(&t); + p = mem.xstrdup(p); + Macro::define(&m->macrotable, (const utf8_t *)"DATETIME", 8, (const utf8_t *)p, strlen(p)); + Macro::define(&m->macrotable, (const utf8_t *)"YEAR", 4, (const utf8_t *)p + 20, 4); + } + + const char *srcfilename = m->srcfile->toChars(); + Macro::define(&m->macrotable, (const utf8_t *)"SRCFILENAME", 11, (const utf8_t *)srcfilename, strlen(srcfilename)); + + const char *docfilename = m->docfile->toChars(); + Macro::define(&m->macrotable, (const utf8_t *)"DOCFILENAME", 11, (const utf8_t *)docfilename, strlen(docfilename)); + + if (dc->copyright) + { + dc->copyright->nooutput = 1; + Macro::define(&m->macrotable, (const utf8_t *)"COPYRIGHT", 9, dc->copyright->body, dc->copyright->bodylen); + } + + buf.printf("$(DDOC_COMMENT Generated by Ddoc from %s)\n", m->srcfile->toChars()); + if (m->isDocFile) + { + Loc loc = m->md ? m->md->loc : m->loc; + size_t commentlen = strlen((const char *)m->comment); + Dsymbols a; + // Bugzilla 9764: Don't push m in a, to prevent emphasize ddoc file name. + if (dc->macros) + { + commentlen = dc->macros->name - m->comment; + dc->macros->write(loc, dc, sc, &a, &buf); + } + buf.write(m->comment, commentlen); + highlightText(sc, &a, &buf, 0); + } + else + { + Dsymbols a; + a.push(m); + dc->writeSections(sc, &a, &buf); + emitMemberComments(m, &buf, sc); + } + + //printf("BODY= '%.*s'\n", buf.offset, buf.data); + Macro::define(&m->macrotable, (const utf8_t *)"BODY", 4, (const utf8_t *)buf.data, buf.offset); + + OutBuffer buf2; + buf2.writestring("$(DDOC)\n"); + size_t end = buf2.offset; + m->macrotable->expand(&buf2, 0, &end, NULL, 0); + + /* Remove all the escape sequences from buf2, + * and make CR-LF the newline. + */ + { + buf.setsize(0); + buf.reserve(buf2.offset); + utf8_t *p = (utf8_t *)buf2.data; + for (size_t j = 0; j < buf2.offset; j++) + { + utf8_t c = p[j]; + if (c == 0xFF && j + 1 < buf2.offset) + { + j++; + continue; + } + if (c == '\n') + buf.writeByte('\r'); + else if (c == '\r') + { + buf.writestring("\r\n"); + if (j + 1 < buf2.offset && p[j + 1] == '\n') + { + j++; + } + continue; + } + buf.writeByte(c); + } + } + + // Transfer image to file + assert(m->docfile); + m->docfile->setbuffer(buf.data, buf.offset); + m->docfile->ref = 1; + ensurePathToNameExists(Loc(), m->docfile->toChars()); + writeFile(m->loc, m->docfile); +} + +/**************************************************** + * Having unmatched parentheses can hose the output of Ddoc, + * as the macros depend on properly nested parentheses. + * This function replaces all ( with $(LPAREN) and ) with $(RPAREN) + * to preserve text literally. This also means macros in the + * text won't be expanded. + */ +void escapeDdocString(OutBuffer *buf, size_t start) +{ + for (size_t u = start; u < buf->offset; u++) + { + utf8_t c = buf->data[u]; + switch(c) + { + case '$': + buf->remove(u, 1); + buf->insert(u, (const char *)"$(DOLLAR)", 9); + u += 8; + break; + + case '(': + buf->remove(u, 1); //remove the ( + buf->insert(u, (const char *)"$(LPAREN)", 9); //insert this instead + u += 8; //skip over newly inserted macro + break; + + case ')': + buf->remove(u, 1); //remove the ) + buf->insert(u, (const char *)"$(RPAREN)", 9); //insert this instead + u += 8; //skip over newly inserted macro + break; + } + } +} + +/**************************************************** + * Having unmatched parentheses can hose the output of Ddoc, + * as the macros depend on properly nested parentheses. + + * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN). + */ +void escapeStrayParenthesis(Loc loc, OutBuffer *buf, size_t start) +{ + unsigned par_open = 0; + + for (size_t u = start; u < buf->offset; u++) + { + utf8_t c = buf->data[u]; + switch(c) + { + case '(': + par_open++; + break; + + case ')': + if (par_open == 0) + { + //stray ')' + warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output." + " Use $(RPAREN) instead for unpaired right parentheses."); + buf->remove(u, 1); //remove the ) + buf->insert(u, (const char *)"$(RPAREN)", 9); //insert this instead + u += 8; //skip over newly inserted macro + } + else + par_open--; + break; + } + } + + if (par_open) // if any unmatched lparens + { + par_open = 0; + for (size_t u = buf->offset; u > start;) + { + u--; + utf8_t c = buf->data[u]; + switch(c) + { + case ')': + par_open++; + break; + + case '(': + if (par_open == 0) + { + //stray '(' + warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output." + " Use $(LPAREN) instead for unpaired left parentheses."); + buf->remove(u, 1); //remove the ( + buf->insert(u, (const char *)"$(LPAREN)", 9); //insert this instead + } + else + par_open--; + break; + } + } + } +} + +// Basically, this is to skip over things like private{} blocks in a struct or +// class definition that don't add any components to the qualified name. +static Scope *skipNonQualScopes(Scope *sc) +{ + while (sc && !sc->scopesym) + sc = sc->enclosing; + return sc; +} + +static bool emitAnchorName(OutBuffer *buf, Dsymbol *s, Scope *sc) +{ + if (!s || s->isPackage() || s->isModule()) + return false; + + // Add parent names first + bool dot = false; + if (s->parent) + dot = emitAnchorName(buf, s->parent, sc); + else if (sc) + dot = emitAnchorName(buf, sc->scopesym, skipNonQualScopes(sc->enclosing)); + + // Eponymous template members can share the parent anchor name + if (getEponymousParent(s)) + return dot; + if (dot) + buf->writeByte('.'); + + // Use "this" not "__ctor" + TemplateDeclaration *td; + if (s->isCtorDeclaration() || ((td = s->isTemplateDeclaration()) != NULL && + td->onemember && td->onemember->isCtorDeclaration())) + { + buf->writestring("this"); + } + else + { + /* We just want the identifier, not overloads like TemplateDeclaration::toChars. + * We don't want the template parameter list and constraints. */ + buf->writestring(s->Dsymbol::toChars()); + } + return true; +} + +static void emitAnchor(OutBuffer *buf, Dsymbol *s, Scope *sc) +{ + Identifier *ident; + { + OutBuffer anc; + emitAnchorName(&anc, s, skipNonQualScopes(sc)); + ident = Identifier::idPool(anc.peekString()); + } + size_t *count = (size_t*)dmd_aaGet(&sc->anchorCounts, (void *)ident); + TemplateDeclaration *td = getEponymousParent(s); + // don't write an anchor for matching consecutive ditto symbols + if (*count > 0 && sc->prevAnchor == ident && + sc->lastdc && (isDitto(s->comment) || (td && isDitto(td->comment)))) + return; + + (*count)++; + // cache anchor name + sc->prevAnchor = ident; + + buf->writestring("$(DDOC_ANCHOR "); + buf->writestring(ident->toChars()); + // only append count once there's a duplicate + if (*count != 1) + buf->printf(".%u", *count); + buf->writeByte(')'); +} + +/******************************* emitComment **********************************/ + +/** Get leading indentation from 'src' which represents lines of code. */ +static size_t getCodeIndent(const char *src) +{ + while (src && (*src == '\r' || *src == '\n')) + ++src; // skip until we find the first non-empty line + + size_t codeIndent = 0; + while (src && (*src == ' ' || *src == '\t')) + { + codeIndent++; + src++; + } + return codeIndent; +} + +/** Recursively expand template mixin member docs into the scope. */ +static void expandTemplateMixinComments(TemplateMixin *tm, OutBuffer *buf, Scope *sc) +{ + if (!tm->semanticRun) tm->semantic(sc); + TemplateDeclaration *td = (tm && tm->tempdecl) ? + tm->tempdecl->isTemplateDeclaration() : NULL; + if (td && td->members) + { + for (size_t i = 0; i < td->members->dim; i++) + { + Dsymbol *sm = (*td->members)[i]; + TemplateMixin *tmc = sm->isTemplateMixin(); + if (tmc && tmc->comment) + expandTemplateMixinComments(tmc, buf, sc); + else + emitComment(sm, buf, sc); + } + } +} + +void emitMemberComments(ScopeDsymbol *sds, OutBuffer *buf, Scope *sc) +{ + if (!sds->members) + return; + + //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars()); + + const char *m = "$(DDOC_MEMBERS "; + if (sds->isTemplateDeclaration()) + m = "$(DDOC_TEMPLATE_MEMBERS "; + else if (sds->isClassDeclaration()) + m = "$(DDOC_CLASS_MEMBERS "; + else if (sds->isStructDeclaration()) + m = "$(DDOC_STRUCT_MEMBERS "; + else if (sds->isEnumDeclaration()) + m = "$(DDOC_ENUM_MEMBERS "; + else if (sds->isModule()) + m = "$(DDOC_MODULE_MEMBERS "; + + size_t offset1 = buf->offset; // save starting offset + buf->writestring(m); + size_t offset2 = buf->offset; // to see if we write anything + + sc = sc->push(sds); + + for (size_t i = 0; i < sds->members->dim; i++) + { + Dsymbol *s = (*sds->members)[i]; + //printf("\ts = '%s'\n", s->toChars()); + + // only expand if parent is a non-template (semantic won't work) + if (s->comment && s->isTemplateMixin() && s->parent && !s->parent->isTemplateDeclaration()) + expandTemplateMixinComments((TemplateMixin *)s, buf, sc); + + emitComment(s, buf, sc); + } + emitComment(NULL, buf, sc); + + sc->pop(); + + if (buf->offset == offset2) + { + /* Didn't write out any members, so back out last write + */ + buf->offset = offset1; + } + else + buf->writestring(")\n"); +} + +void emitProtection(OutBuffer *buf, Prot prot) +{ + if (prot.kind != PROTundefined && prot.kind != PROTpublic) + { + protectionToBuffer(buf, prot); + buf->writeByte(' '); + } +} + +void emitComment(Dsymbol *s, OutBuffer *buf, Scope *sc) +{ + class EmitComment : public Visitor + { + public: + OutBuffer *buf; + Scope *sc; + + EmitComment(OutBuffer *buf, Scope *sc) + : buf(buf), sc(sc) + { + } + + void visit(Dsymbol *) {} + void visit(InvariantDeclaration *) {} + void visit(UnitTestDeclaration *) {} + void visit(PostBlitDeclaration *) {} + void visit(DtorDeclaration *) {} + void visit(StaticCtorDeclaration *) {} + void visit(StaticDtorDeclaration *) {} + void visit(TypeInfoDeclaration *) {} + + void emit(Scope *sc, Dsymbol *s, const utf8_t *com) + { + if (s && sc->lastdc && isDitto(com)) + { + sc->lastdc->a.push(s); + return; + } + + // Put previous doc comment if exists + if (DocComment *dc = sc->lastdc) + { + // Put the declaration signatures as the document 'title' + buf->writestring(ddoc_decl_s); + for (size_t i = 0; i < dc->a.dim; i++) + { + Dsymbol *sx = dc->a[i]; + + if (i == 0) + { + size_t o = buf->offset; + toDocBuffer(sx, buf, sc); + highlightCode(sc, sx, buf, o); + continue; + } + + buf->writestring("$(DDOC_DITTO "); + { + size_t o = buf->offset; + toDocBuffer(sx, buf, sc); + highlightCode(sc, sx, buf, o); + } + buf->writeByte(')'); + } + buf->writestring(ddoc_decl_e); + + // Put the ddoc comment as the document 'description' + buf->writestring(ddoc_decl_dd_s); + { + dc->writeSections(sc, &dc->a, buf); + if (ScopeDsymbol *sds = dc->a[0]->isScopeDsymbol()) + emitMemberComments(sds, buf, sc); + } + buf->writestring(ddoc_decl_dd_e); + //printf("buf.2 = [[%.*s]]\n", buf->offset - o0, buf->data + o0); + } + + if (s) + { + DocComment *dc = DocComment::parse(s, com); + dc->pmacrotable = &sc->_module->macrotable; + sc->lastdc = dc; + } + } + + void visit(Declaration *d) + { + //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d->toChars(), d->comment); + //printf("type = %p\n", d->type); + const utf8_t *com = d->comment; + if (TemplateDeclaration *td = getEponymousParent(d)) + { + if (isDitto(td->comment)) + com = td->comment; + else + com = Lexer::combineComments(td->comment, com); + } + else + { + if (!d->ident) + return; + if (!d->type && !d->isCtorDeclaration() && !d->isAliasDeclaration()) + return; + if (d->protection.kind == PROTprivate || sc->protection.kind == PROTprivate) + return; + } + if (!com) + return; + + emit(sc, d, com); + } + + void visit(AggregateDeclaration *ad) + { + //printf("AggregateDeclaration::emitComment() '%s'\n", ad->toChars()); + const utf8_t *com = ad->comment; + if (TemplateDeclaration *td = getEponymousParent(ad)) + { + if (isDitto(td->comment)) + com = td->comment; + else + com = Lexer::combineComments(td->comment, com); + } + else + { + if (ad->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) + return; + if (!ad->comment) + return; + } + if (!com) + return; + + emit(sc, ad, com); + } + + void visit(TemplateDeclaration *td) + { + //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td->toChars(), td->kind()); + if (td->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) + return; + if (!td->comment) + return; + + if (Dsymbol *ss = getEponymousMember(td)) + { + ss->accept(this); + return; + } + emit(sc, td, td->comment); + } + + void visit(EnumDeclaration *ed) + { + if (ed->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) + return; + if (ed->isAnonymous() && ed->members) + { + for (size_t i = 0; i < ed->members->dim; i++) + { + Dsymbol *s = (*ed->members)[i]; + emitComment(s, buf, sc); + } + return; + } + if (!ed->comment) + return; + if (ed->isAnonymous()) + return; + + emit(sc, ed, ed->comment); + } + + void visit(EnumMember *em) + { + //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em->toChars(), em->comment); + if (em->prot().kind == PROTprivate || sc->protection.kind == PROTprivate) + return; + if (!em->comment) + return; + + emit(sc, em, em->comment); + } + + void visit(AttribDeclaration *ad) + { + //printf("AttribDeclaration::emitComment(sc = %p)\n", sc); + + /* A general problem with this, illustrated by BUGZILLA 2516, + * is that attributes are not transmitted through to the underlying + * member declarations for template bodies, because semantic analysis + * is not done for template declaration bodies + * (only template instantiations). + * Hence, Ddoc omits attributes from template members. + */ + + Dsymbols *d = ad->include(NULL, NULL); + + if (d) + { + for (size_t i = 0; i < d->dim; i++) + { + Dsymbol *s = (*d)[i]; + //printf("AttribDeclaration::emitComment %s\n", s->toChars()); + emitComment(s, buf, sc); + } + } + } + + void visit(ProtDeclaration *pd) + { + if (pd->decl) + { + Scope *scx = sc; + sc = sc->copy(); + sc->protection = pd->protection; + visit((AttribDeclaration *)pd); + scx->lastdc = sc->lastdc; + sc = sc->pop(); + } + } + + void visit(ConditionalDeclaration *cd) + { + //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc); + if (cd->condition->inc) + { + visit((AttribDeclaration *)cd); + return; + } + + /* If generating doc comment, be careful because if we're inside + * a template, then include(NULL, NULL) will fail. + */ + Dsymbols *d = cd->decl ? cd->decl : cd->elsedecl; + for (size_t i = 0; i < d->dim; i++) + { + Dsymbol *s = (*d)[i]; + emitComment(s, buf, sc); + } + } + }; + + EmitComment v(buf, sc); + + if (!s) + v.emit(sc, NULL, NULL); + else + s->accept(&v); +} + +/******************************* toDocBuffer **********************************/ + +void toDocBuffer(Dsymbol *s, OutBuffer *buf, Scope *sc) +{ + class ToDocBuffer : public Visitor + { + public: + OutBuffer *buf; + Scope *sc; + + ToDocBuffer(OutBuffer *buf, Scope *sc) + : buf(buf), sc(sc) + { + } + + void visit(Dsymbol *s) + { + //printf("Dsymbol::toDocbuffer() %s\n", s->toChars()); + HdrGenState hgs; + hgs.ddoc = true; + ::toCBuffer(s, buf, &hgs); + } + + void prefix(Dsymbol *s) + { + if (s->isDeprecated()) + buf->writestring("deprecated "); + + if (Declaration *d = s->isDeclaration()) + { + emitProtection(buf, d->protection); + + if (d->isStatic()) + buf->writestring("static "); + else if (d->isFinal()) + buf->writestring("final "); + else if (d->isAbstract()) + buf->writestring("abstract "); + + if (!d->isFuncDeclaration()) // functionToBufferFull handles this + { + if (d->isConst()) + buf->writestring("const "); + if (d->isImmutable()) + buf->writestring("immutable "); + if (d->isSynchronized()) + buf->writestring("synchronized "); + + if (d->storage_class & STCmanifest) + buf->writestring("enum "); + } + } + } + + void visit(Declaration *d) + { + if (!d->ident) + return; + + TemplateDeclaration *td = getEponymousParent(d); + //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d->toChars(), d->originalType ? d->originalType->toChars() : "--", td ? td->toChars() : "--"); + + HdrGenState hgs; + hgs.ddoc = true; + + if (d->isDeprecated()) + buf->writestring("$(DEPRECATED "); + + prefix(d); + + if (d->type) + { + Type *origType = d->originalType ? d->originalType : d->type; + if (origType->ty == Tfunction) + { + functionToBufferFull((TypeFunction *)origType, buf, d->ident, &hgs, td); + } + else + ::toCBuffer(origType, buf, d->ident, &hgs); + } + else + buf->writestring(d->ident->toChars()); + + if (d->isVarDeclaration() && td) + { + buf->writeByte('('); + if (td->origParameters && td->origParameters->dim) + { + for (size_t i = 0; i < td->origParameters->dim; i++) + { + if (i) + buf->writestring(", "); + toCBuffer((*td->origParameters)[i], buf, &hgs); + } + } + buf->writeByte(')'); + } + + // emit constraints if declaration is a templated declaration + if (td && td->constraint) + { + buf->writestring(" if ("); + ::toCBuffer(td->constraint, buf, &hgs); + buf->writeByte(')'); + } + + if (d->isDeprecated()) + buf->writestring(")"); + + buf->writestring(";\n"); + } + + void visit(AliasDeclaration *ad) + { + //printf("AliasDeclaration::toDocbuffer() %s\n", ad->toChars()); + if (!ad->ident) + return; + + if (ad->isDeprecated()) + buf->writestring("deprecated "); + + emitProtection(buf, ad->protection); + buf->printf("alias %s = ", ad->toChars()); + + if (Dsymbol *s = ad->aliassym) // ident alias + { + prettyPrintDsymbol(s, ad->parent); + } + else if (Type *type = ad->getType()) // type alias + { + if (type->ty == Tclass || type->ty == Tstruct || type->ty == Tenum) + { + if (Dsymbol *s = type->toDsymbol(NULL)) // elaborate type + prettyPrintDsymbol(s, ad->parent); + else + buf->writestring(type->toChars()); + } + else + { + // simple type + buf->writestring(type->toChars()); + } + } + + buf->writestring(";\n"); + } + + void parentToBuffer(Dsymbol *s) + { + if (s && !s->isPackage() && !s->isModule()) + { + parentToBuffer(s->parent); + buf->writestring(s->toChars()); + buf->writestring("."); + } + } + + static bool inSameModule(Dsymbol *s, Dsymbol *p) + { + for ( ; s ; s = s->parent) + { + if (s->isModule()) + break; + } + + for ( ; p ; p = p->parent) + { + if (p->isModule()) + break; + } + + return s == p; + } + + void prettyPrintDsymbol(Dsymbol *s, Dsymbol *parent) + { + if (s->parent && (s->parent == parent)) // in current scope -> naked name + { + buf->writestring(s->toChars()); + } + else if (!inSameModule(s, parent)) // in another module -> full name + { + buf->writestring(s->toPrettyChars()); + } + else // nested in a type in this module -> full name w/o module name + { + // if alias is nested in a user-type use module-scope lookup + if (!parent->isModule() && !parent->isPackage()) + buf->writestring("."); + + parentToBuffer(s->parent); + buf->writestring(s->toChars()); + } + } + + void visit(AggregateDeclaration *ad) + { + if (!ad->ident) + return; + + buf->printf("%s %s", ad->kind(), ad->toChars()); + buf->writestring(";\n"); + } + + void visit(StructDeclaration *sd) + { + //printf("StructDeclaration::toDocbuffer() %s\n", sd->toChars()); + if (!sd->ident) + return; + + if (TemplateDeclaration *td = getEponymousParent(sd)) + { + toDocBuffer(td, buf, sc); + } + else + { + buf->printf("%s %s", sd->kind(), sd->toChars()); + } + buf->writestring(";\n"); + } + + void visit(ClassDeclaration *cd) + { + //printf("ClassDeclaration::toDocbuffer() %s\n", cd->toChars()); + if (!cd->ident) + return; + + if (TemplateDeclaration *td = getEponymousParent(cd)) + { + toDocBuffer(td, buf, sc); + } + else + { + if (!cd->isInterfaceDeclaration() && cd->isAbstract()) + buf->writestring("abstract "); + buf->printf("%s %s", cd->kind(), cd->toChars()); + } + int any = 0; + for (size_t i = 0; i < cd->baseclasses->dim; i++) + { + BaseClass *bc = (*cd->baseclasses)[i]; + + if (bc->sym && bc->sym->ident == Id::Object) + continue; + + if (any) + buf->writestring(", "); + else + { + buf->writestring(": "); + any = 1; + } + emitProtection(buf, Prot(PROTpublic)); + if (bc->sym) + { + buf->printf("$(DDOC_PSUPER_SYMBOL %s)", bc->sym->toPrettyChars()); + } + else + { + HdrGenState hgs; + ::toCBuffer(bc->type, buf, NULL, &hgs); + } + } + buf->writestring(";\n"); + } + + void visit(EnumDeclaration *ed) + { + if (!ed->ident) + return; + + buf->printf("%s %s", ed->kind(), ed->toChars()); + if (ed->memtype) + { + buf->writestring(": $(DDOC_ENUM_BASETYPE "); + HdrGenState hgs; + ::toCBuffer(ed->memtype, buf, NULL, &hgs); + buf->writestring(")"); + } + buf->writestring(";\n"); + } + + void visit(EnumMember *em) + { + if (!em->ident) + return; + + buf->writestring(em->toChars()); + } + }; + + ToDocBuffer v(buf, sc); + s->accept(&v); +} + +/********************************* DocComment *********************************/ + +DocComment *DocComment::parse(Dsymbol *s, const utf8_t *comment) +{ + //printf("parse(%s): '%s'\n", s->toChars(), comment); + DocComment *dc = new DocComment(); + dc->a.push(s); + if (!comment) + return dc; + + dc->parseSections(comment); + + for (size_t i = 0; i < dc->sections.dim; i++) + { + Section *sec = dc->sections[i]; + + if (icmp("copyright", sec->name, sec->namelen) == 0) + { + dc->copyright = sec; + } + if (icmp("macros", sec->name, sec->namelen) == 0) + { + dc->macros = sec; + } + } + + return dc; +} + +/***************************************** + * Parse next paragraph out of *pcomment. + * Update *pcomment to point past paragraph. + * Returns NULL if no more paragraphs. + * If paragraph ends in 'identifier:', + * then (*pcomment)[0 .. idlen] is the identifier. + */ + +void DocComment::parseSections(const utf8_t *comment) +{ + const utf8_t *p; + const utf8_t *pstart; + const utf8_t *pend; + const utf8_t *idstart = NULL; // dead-store to prevent spurious warning + size_t idlen; + + const utf8_t *name = NULL; + size_t namelen = 0; + + //printf("parseSections('%s')\n", comment); + p = comment; + while (*p) + { + const utf8_t *pstart0 = p; + p = skipwhitespace(p); + pstart = p; + pend = p; + + /* Find end of section, which is ended by one of: + * 'identifier:' (but not inside a code section) + * '\0' + */ + idlen = 0; + int inCode = 0; + while (1) + { + // Check for start/end of a code section + if (*p == '-') + { + if (!inCode) + { + // restore leading indentation + while (pstart0 < pstart && isIndentWS(pstart-1)) --pstart; + } + + int numdash = 0; + while (*p == '-') + { + ++numdash; + p++; + } + // BUG: handle UTF PS and LS too + if ((!*p || *p == '\r' || *p == '\n') && numdash >= 3) + inCode ^= 1; + pend = p; + } + + if (!inCode && isIdStart(p)) + { + const utf8_t *q = p + utfStride(p); + while (isIdTail(q)) + q += utfStride(q); + // Detected tag ends it + if (*q == ':' && isupper(*p) + && (isspace(q[1]) || q[1] == 0)) + { + idlen = q - p; + idstart = p; + for (pend = p; pend > pstart; pend--) + { + if (pend[-1] == '\n') + break; + } + p = q + 1; + break; + } + } + while (1) + { + if (!*p) + goto L1; + if (*p == '\n') + { + p++; + if (*p == '\n' && !summary && !namelen && !inCode) + { + pend = p; + p++; + goto L1; + } + break; + } + p++; + pend = p; + } + p = skipwhitespace(p); + } + L1: + + if (namelen || pstart < pend) + { + Section *s; + if (icmp("Params", name, namelen) == 0) + s = new ParamSection(); + else if (icmp("Macros", name, namelen) == 0) + s = new MacroSection(); + else + s = new Section(); + s->name = name; + s->namelen = namelen; + s->body = pstart; + s->bodylen = pend - pstart; + s->nooutput = 0; + + //printf("Section: '%.*s' = '%.*s'\n", s->namelen, s->name, s->bodylen, s->body); + + sections.push(s); + + if (!summary && !namelen) + summary = s; + } + + if (idlen) + { + name = idstart; + namelen = idlen; + } + else + { + name = NULL; + namelen = 0; + if (!*p) + break; + } + } +} + +void DocComment::writeSections(Scope *sc, Dsymbols *a, OutBuffer *buf) +{ + assert(a->dim); + + //printf("DocComment::writeSections()\n"); + Loc loc = (*a)[0]->loc; + if (Module *m = (*a)[0]->isModule()) + { + if (m->md) + loc = m->md->loc; + } + + size_t offset1 = buf->offset; + buf->writestring("$(DDOC_SECTIONS "); + size_t offset2 = buf->offset; + + for (size_t i = 0; i < sections.dim; i++) + { + Section *sec = sections[i]; + if (sec->nooutput) + continue; + + //printf("Section: '%.*s' = '%.*s'\n", sec->namelen, sec->name, sec->bodylen, sec->body); + if (!sec->namelen && i == 0) + { + buf->writestring("$(DDOC_SUMMARY "); + size_t o = buf->offset; + buf->write(sec->body, sec->bodylen); + escapeStrayParenthesis(loc, buf, o); + highlightText(sc, a, buf, o); + buf->writestring(")\n"); + } + else + sec->write(loc, this, sc, a, buf); + } + + for (size_t i = 0; i < a->dim; i++) + { + Dsymbol *s = (*a)[i]; + if (Dsymbol *td = getEponymousParent(s)) + s = td; + + for (UnitTestDeclaration *utd = s->ddocUnittest; utd; utd = utd->ddocUnittest) + { + if (utd->protection.kind == PROTprivate || !utd->comment || !utd->fbody) + continue; + + // Strip whitespaces to avoid showing empty summary + const utf8_t *c = utd->comment; + while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r') ++c; + + buf->writestring("$(DDOC_EXAMPLES "); + + size_t o = buf->offset; + buf->writestring((const char *)c); + + if (utd->codedoc) + { + size_t n = getCodeIndent(utd->codedoc); + while (n--) buf->writeByte(' '); + buf->writestring("----\n"); + buf->writestring(utd->codedoc); + buf->writestring("----\n"); + highlightText(sc, a, buf, o); + } + + buf->writestring(")"); + } + } + + if (buf->offset == offset2) + { + /* Didn't write out any sections, so back out last write + */ + buf->offset = offset1; + buf->writestring("$(DDOC_BLANKLINE)\n"); + } + else + buf->writestring(")\n"); +} + +/*************************************************** + */ + +void Section::write(Loc loc, DocComment *, Scope *sc, Dsymbols *a, OutBuffer *buf) +{ + assert(a->dim); + + if (namelen) + { + static const char *table[] = + { + "AUTHORS", "BUGS", "COPYRIGHT", "DATE", + "DEPRECATED", "EXAMPLES", "HISTORY", "LICENSE", + "RETURNS", "SEE_ALSO", "STANDARDS", "THROWS", + "VERSION", NULL + }; + + for (size_t i = 0; table[i]; i++) + { + if (icmp(table[i], name, namelen) == 0) + { + buf->printf("$(DDOC_%s ", table[i]); + goto L1; + } + } + + buf->writestring("$(DDOC_SECTION "); + + // Replace _ characters with spaces + buf->writestring("$(DDOC_SECTION_H "); + size_t o = buf->offset; + for (size_t u = 0; u < namelen; u++) + { + utf8_t c = name[u]; + buf->writeByte((c == '_') ? ' ' : c); + } + escapeStrayParenthesis(loc, buf, o); + buf->writestring(":)\n"); + } + else + { + buf->writestring("$(DDOC_DESCRIPTION "); + } + L1: + size_t o = buf->offset; + buf->write(body, bodylen); + escapeStrayParenthesis(loc, buf, o); + highlightText(sc, a, buf, o); + buf->writestring(")\n"); +} + +/*************************************************** + */ + +void ParamSection::write(Loc loc, DocComment *, Scope *sc, Dsymbols *a, OutBuffer *buf) +{ + assert(a->dim); + Dsymbol *s = (*a)[0]; // test + + const utf8_t *p = body; + size_t len = bodylen; + const utf8_t *pend = p + len; + + const utf8_t *tempstart = NULL; + size_t templen = 0; + + const utf8_t *namestart = NULL; + size_t namelen = 0; // !=0 if line continuation + + const utf8_t *textstart = NULL; + size_t textlen = 0; + + size_t paramcount = 0; + + buf->writestring("$(DDOC_PARAMS "); + while (p < pend) + { + // Skip to start of macro + while (1) + { + switch (*p) + { + case ' ': + case '\t': + p++; + continue; + + case '\n': + p++; + goto Lcont; + + default: + if (isIdStart(p) || isCVariadicArg(p, pend - p)) + break; + if (namelen) + goto Ltext; // continuation of prev macro + goto Lskipline; + } + break; + } + tempstart = p; + + while (isIdTail(p)) + p += utfStride(p); + if (isCVariadicArg(p, pend - p)) + p += 3; + + templen = p - tempstart; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p != '=') + { + if (namelen) + goto Ltext; // continuation of prev macro + goto Lskipline; + } + p++; + + if (namelen) + { + // Output existing param + + L1: + //printf("param '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart); + ++paramcount; + HdrGenState hgs; + buf->writestring("$(DDOC_PARAM_ROW "); + { + buf->writestring("$(DDOC_PARAM_ID "); + { + size_t o = buf->offset; + Parameter *fparam = isFunctionParameter(a, namestart, namelen); + bool isCVariadic = isCVariadicParameter(a, namestart, namelen); + if (isCVariadic) + { + buf->writestring("..."); + } + else if (fparam && fparam->type && fparam->ident) + { + ::toCBuffer(fparam->type, buf, fparam->ident, &hgs); + } + else + { + if (isTemplateParameter(a, namestart, namelen)) + { + // 10236: Don't count template parameters for params check + --paramcount; + } + else if (!fparam) + { + warning(s->loc, "Ddoc: function declaration has no parameter '%.*s'", (int)namelen, namestart); + } + buf->write(namestart, namelen); + } + escapeStrayParenthesis(loc, buf, o); + highlightCode(sc, a, buf, o); + } + buf->writestring(")\n"); + + buf->writestring("$(DDOC_PARAM_DESC "); + { + size_t o = buf->offset; + buf->write(textstart, textlen); + escapeStrayParenthesis(loc, buf, o); + highlightText(sc, a, buf, o); + } + buf->writestring(")"); + } + buf->writestring(")\n"); + namelen = 0; + if (p >= pend) + break; + } + + namestart = tempstart; + namelen = templen; + + while (*p == ' ' || *p == '\t') + p++; + textstart = p; + + Ltext: + while (*p != '\n') + p++; + textlen = p - textstart; + p++; + + Lcont: + continue; + + Lskipline: + // Ignore this line + while (*p++ != '\n') + ; + } + if (namelen) + goto L1; // write out last one + buf->writestring(")\n"); + + TypeFunction *tf = a->dim == 1 ? isTypeFunction(s) : NULL; + if (tf) + { + size_t pcount = (tf->parameters ? tf->parameters->dim : 0) + (int)(tf->varargs == 1); + if (pcount != paramcount) + { + warning(s->loc, "Ddoc: parameter count mismatch"); + } + } +} + +/*************************************************** + */ + +void MacroSection::write(Loc, DocComment *dc, Scope *, Dsymbols *, OutBuffer *) +{ + //printf("MacroSection::write()\n"); + DocComment::parseMacros(dc->pescapetable, dc->pmacrotable, body, bodylen); +} + +/************************************************ + * Parse macros out of Macros: section. + * Macros are of the form: + * name1 = value1 + * + * name2 = value2 + */ + +void DocComment::parseMacros(Escape **pescapetable, Macro **pmacrotable, const utf8_t *m, size_t mlen) +{ + const utf8_t *p = m; + size_t len = mlen; + const utf8_t *pend = p + len; + + const utf8_t *tempstart = NULL; + size_t templen = 0; + + const utf8_t *namestart = NULL; + size_t namelen = 0; // !=0 if line continuation + + const utf8_t *textstart = NULL; + size_t textlen = 0; + + while (p < pend) + { + // Skip to start of macro + while (1) + { + if (p >= pend) + goto Ldone; + switch (*p) + { + case ' ': + case '\t': + p++; + continue; + + case '\r': + case '\n': + p++; + goto Lcont; + + default: + if (isIdStart(p)) + break; + if (namelen) + goto Ltext; // continuation of prev macro + goto Lskipline; + } + break; + } + tempstart = p; + + while (1) + { + if (p >= pend) + goto Ldone; + if (!isIdTail(p)) + break; + p += utfStride(p); + } + templen = p - tempstart; + + while (1) + { + if (p >= pend) + goto Ldone; + if (!(*p == ' ' || *p == '\t')) + break; + p++; + } + + if (*p != '=') + { + if (namelen) + goto Ltext; // continuation of prev macro + goto Lskipline; + } + p++; + if (p >= pend) + goto Ldone; + + if (namelen) + { + // Output existing macro + L1: + //printf("macro '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart); + if (icmp("ESCAPES", namestart, namelen) == 0) + parseEscapes(pescapetable, textstart, textlen); + else + Macro::define(pmacrotable, namestart, namelen, textstart, textlen); + namelen = 0; + if (p >= pend) + break; + } + + namestart = tempstart; + namelen = templen; + + while (p < pend && (*p == ' ' || *p == '\t')) + p++; + textstart = p; + + Ltext: + while (p < pend && *p != '\r' && *p != '\n') + p++; + textlen = p - textstart; + + p++; + //printf("p = %p, pend = %p\n", p, pend); + + Lcont: + continue; + + Lskipline: + // Ignore this line + while (p < pend && *p != '\r' && *p != '\n') + p++; + } +Ldone: + if (namelen) + goto L1; // write out last one +} + +/************************************** + * Parse escapes of the form: + * /c/string/ + * where c is a single character. + * Multiple escapes can be separated + * by whitespace and/or commas. + */ + +void DocComment::parseEscapes(Escape **pescapetable, const utf8_t *textstart, size_t textlen) +{ + Escape *escapetable = *pescapetable; + + if (!escapetable) + { + escapetable = new Escape; + memset(escapetable, 0, sizeof(Escape)); + *pescapetable = escapetable; + } + //printf("parseEscapes('%.*s') pescapetable = %p\n", textlen, textstart, pescapetable); + const utf8_t *p = textstart; + const utf8_t *pend = p + textlen; + + while (1) + { + while (1) + { + if (p + 4 >= pend) + return; + if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',')) + break; + p++; + } + if (p[0] != '/' || p[2] != '/') + return; + utf8_t c = p[1]; + p += 3; + const utf8_t *start = p; + while (1) + { + if (p >= pend) + return; + if (*p == '/') + break; + p++; + } + size_t len = p - start; + char *s = (char *)memcpy(mem.xmalloc(len + 1), start, len); + s[len] = 0; + escapetable->strings[c] = s; + //printf("\t%c = '%s'\n", c, s); + p++; + } +} + + +/****************************************** + * Compare 0-terminated string with length terminated string. + * Return < 0, ==0, > 0 + */ + +int cmp(const char *stringz, const void *s, size_t slen) +{ + size_t len1 = strlen(stringz); + + if (len1 != slen) + return (int)(len1 - slen); + return memcmp(stringz, s, slen); +} + +int icmp(const char *stringz, const void *s, size_t slen) +{ + size_t len1 = strlen(stringz); + + if (len1 != slen) + return (int)(len1 - slen); + return Port::memicmp(stringz, (const char *)s, slen); +} + +/***************************************** + * Return true if comment consists entirely of "ditto". + */ + +bool isDitto(const utf8_t *comment) +{ + if (comment) + { + const utf8_t *p = skipwhitespace(comment); + + if (Port::memicmp((const char *)p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0) + return true; + } + return false; +} + +/********************************************** + * Skip white space. + */ + +const utf8_t *skipwhitespace(const utf8_t *p) +{ + for (; 1; p++) + { + switch (*p) + { + case ' ': + case '\t': + case '\n': + continue; + } + break; + } + return p; +} + + +/************************************************ + * Scan forward to one of: + * start of identifier + * beginning of next line + * end of buf + */ + +size_t skiptoident(OutBuffer *buf, size_t i) +{ + while (i < buf->offset) + { + dchar_t c; + + size_t oi = i; + if (utf_decodeChar((utf8_t *)buf->data, buf->offset, &i, &c)) + { + /* Ignore UTF errors, but still consume input + */ + break; + } + if (c >= 0x80) + { + if (!isUniAlpha(c)) + continue; + } + else if (!(isalpha(c) || c == '_' || c == '\n')) + continue; + i = oi; + break; + } + return i; +} + +/************************************************ + * Scan forward past end of identifier. + */ + +size_t skippastident(OutBuffer *buf, size_t i) +{ + while (i < buf->offset) + { + dchar_t c; + + size_t oi = i; + if (utf_decodeChar((utf8_t *)buf->data, buf->offset, &i, &c)) + { + /* Ignore UTF errors, but still consume input + */ + break; + } + if (c >= 0x80) + { + if (isUniAlpha(c)) + continue; + } + else if (isalnum(c) || c == '_') + continue; + i = oi; + break; + } + return i; +} + + +/************************************************ + * Scan forward past URL starting at i. + * We don't want to highlight parts of a URL. + * Returns: + * i if not a URL + * index just past it if it is a URL + */ + +size_t skippastURL(OutBuffer *buf, size_t i) +{ + size_t length = buf->offset - i; + utf8_t *p = (utf8_t *)&buf->data[i]; + size_t j; + unsigned sawdot = 0; + + if (length > 7 && Port::memicmp((char *)p, "http://", 7) == 0) + { + j = 7; + } + else if (length > 8 && Port::memicmp((char *)p, "https://", 8) == 0) + { + j = 8; + } + else + goto Lno; + + for (; j < length; j++) + { + utf8_t c = p[j]; + if (isalnum(c)) + continue; + if (c == '-' || c == '_' || c == '?' || + c == '=' || c == '%' || c == '&' || + c == '/' || c == '+' || c == '#' || + c == '~') + continue; + if (c == '.') + { + sawdot = 1; + continue; + } + break; + } + if (sawdot) + return i + j; + +Lno: + return i; +} + + +/**************************************************** + */ + +bool isIdentifier(Dsymbols *a, const utf8_t *p, size_t len) +{ + for (size_t i = 0; i < a->dim; i++) + { + const char *s = (*a)[i]->ident->toChars(); + if (cmp(s, p, len) == 0) + return true; + } + return false; +} + +/**************************************************** + */ + +bool isKeyword(utf8_t *p, size_t len) +{ + static const char *table[] = { "true", "false", "null", NULL }; + + for (int i = 0; table[i]; i++) + { + if (cmp(table[i], p, len) == 0) + return true; + } + return false; +} + +/**************************************************** + */ + +TypeFunction *isTypeFunction(Dsymbol *s) +{ + FuncDeclaration *f = s->isFuncDeclaration(); + + /* f->type may be NULL for template members. + */ + if (f && f->type) + { + Type *t = f->originalType ? f->originalType : f->type; + if (t->ty == Tfunction) + return (TypeFunction *)t; + } + return NULL; +} + +/**************************************************** + */ + +Parameter *isFunctionParameter(Dsymbols *a, const utf8_t *p, size_t len) +{ + for (size_t i = 0; i < a->dim; i++) + { + 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; +} + +/**************************************************** + */ + +TemplateParameter *isTemplateParameter(Dsymbols *a, const utf8_t *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. + */ + +bool isReservedName(utf8_t *str, size_t len) +{ + static const char *table[] = { + "__ctor", "__dtor", "__postblit", "__invariant", "__unitTest", + "__require", "__ensure", "__dollar", "__ctfe", "__withSym", "__result", + "__returnLabel", "__vptr", "__monitor", "__gate", "__xopEquals", "__xopCmp", + "__LINE__", "__FILE__", "__MODULE__", "__FUNCTION__", "__PRETTY_FUNCTION__", + "__DATE__", "__TIME__", "__TIMESTAMP__", "__VENDOR__", "__VERSION__", + "__EOF__", "__LOCAL_SIZE", "___tls_get_addr", "__entrypoint", NULL }; + + for (int i = 0; table[i]; i++) + { + if (cmp(table[i], str, len) == 0) + return true; + } + return false; +} + +/************************************************** + * Highlight text section. + */ + +void highlightText(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset) +{ + Dsymbol *s = a->dim ? (*a)[0] : NULL; // test + + //printf("highlightText()\n"); + + int leadingBlank = 1; + int inCode = 0; + int inBacktick = 0; + //int inComment = 0; // in <!-- ... --> comment + size_t iCodeStart = 0; // start of code section + size_t codeIndent = 0; + + size_t iLineStart = offset; + + for (size_t i = offset; i < buf->offset; i++) + { + utf8_t c = buf->data[i]; + + Lcont: + switch (c) + { + case ' ': + case '\t': + break; + + case '\n': + if (inBacktick) + { + // `inline code` is only valid if contained on a single line + // otherwise, the backticks should be output literally. + // + // This lets things like `output from the linker' display + // unmolested while keeping the feature consistent with GitHub. + + inBacktick = false; + inCode = false; // the backtick also assumes we're in code + + // Nothing else is necessary since the DDOC_BACKQUOTED macro is + // inserted lazily at the close quote, meaning the rest of the + // text is already OK. + } + + if (!sc->_module->isDocFile && + !inCode && i == iLineStart && i + 1 < buf->offset) // if "\n\n" + { + static const char blankline[] = "$(DDOC_BLANKLINE)\n"; + + i = buf->insert(i, blankline, strlen(blankline)); + } + leadingBlank = 1; + iLineStart = i + 1; + break; + + case '<': + { + leadingBlank = 0; + if (inCode) + break; + utf8_t *p = (utf8_t *)&buf->data[i]; + const char *se = sc->_module->escapetable->escapeChar('<'); + if (se && strcmp(se, "<") == 0) + { + // Generating HTML + // Skip over comments + if (p[1] == '!' && p[2] == '-' && p[3] == '-') + { + size_t j = i + 4; + p += 4; + while (1) + { + if (j == buf->offset) + goto L1; + if (p[0] == '-' && p[1] == '-' && p[2] == '>') + { + i = j + 2; // place on closing '>' + break; + } + j++; + p++; + } + break; + } + + // Skip over HTML tag + if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2]))) + { + size_t j = i + 2; + p += 2; + while (1) + { + if (j == buf->offset) + break; + if (p[0] == '>') + { + i = j; // place on closing '>' + break; + } + j++; + p++; + } + break; + } + } + L1: + // Replace '<' with '<' character entity + if (se) + { + size_t len = strlen(se); + buf->remove(i, 1); + i = buf->insert(i, se, len); + i--; // point to ';' + } + break; + } + case '>': + { + leadingBlank = 0; + if (inCode) + break; + // Replace '>' with '>' character entity + const char *se = sc->_module->escapetable->escapeChar('>'); + if (se) + { + size_t len = strlen(se); + buf->remove(i, 1); + i = buf->insert(i, se, len); + i--; // point to ';' + } + break; + } + case '&': + { + leadingBlank = 0; + if (inCode) + break; + utf8_t *p = (utf8_t *)&buf->data[i]; + if (p[1] == '#' || isalpha(p[1])) + break; // already a character entity + // Replace '&' with '&' character entity + const char *se = sc->_module->escapetable->escapeChar('&'); + if (se) + { + size_t len = strlen(se); + buf->remove(i, 1); + i = buf->insert(i, se, len); + i--; // point to ';' + } + break; + } + case '`': + { + if (inBacktick) + { + inBacktick = 0; + inCode = 0; + + OutBuffer codebuf; + + codebuf.write(buf->data + iCodeStart + 1, i - (iCodeStart + 1)); + + // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL + highlightCode(sc, a, &codebuf, 0); + + buf->remove(iCodeStart, i - iCodeStart + 1); // also trimming off the current ` + + static const char pre[] = "$(DDOC_BACKQUOTED "; + i = buf->insert(iCodeStart, pre, strlen(pre)); + i = buf->insert(i, (char *)codebuf.data, codebuf.offset); + i = buf->insert(i, ")", 1); + + i--; // point to the ending ) so when the for loop does i++, it will see the next character + + break; + } + + if (inCode) + break; + + inCode = 1; + inBacktick = 1; + codeIndent = 0; // inline code is not indented + + // All we do here is set the code flags and record + // the location. The macro will be inserted lazily + // so we can easily cancel the inBacktick if we come + // across a newline character. + iCodeStart = i; + + break; + } + case '-': + /* A line beginning with --- delimits a code section. + * inCode tells us if it is start or end of a code section. + */ + if (leadingBlank) + { + size_t istart = i; + size_t eollen = 0; + + leadingBlank = 0; + while (1) + { + ++i; + if (i >= buf->offset) + break; + c = buf->data[i]; + if (c == '\n') + { + eollen = 1; + break; + } + if (c == '\r') + { + eollen = 1; + if (i + 1 >= buf->offset) + break; + if (buf->data[i + 1] == '\n') + { + eollen = 2; + break; + } + } + // BUG: handle UTF PS and LS too + if (c != '-') + goto Lcont; + } + if (i - istart < 3) + goto Lcont; + + // We have the start/end of a code section + + // Remove the entire --- line, including blanks and \n + buf->remove(iLineStart, i - iLineStart + eollen); + i = iLineStart; + + if (inCode && (i <= iCodeStart)) + { + // Empty code section, just remove it completely. + inCode = 0; + break; + } + + if (inCode) + { + inCode = 0; + // The code section is from iCodeStart to i + OutBuffer codebuf; + + codebuf.write(buf->data + iCodeStart, i - iCodeStart); + codebuf.writeByte(0); + + // Remove leading indentations from all lines + bool lineStart = true; + utf8_t *endp = (utf8_t *)codebuf.data + codebuf.offset; + for (utf8_t *p = (utf8_t *)codebuf.data; p < endp; ) + { + if (lineStart) + { + size_t j = codeIndent; + utf8_t *q = p; + while (j-- > 0 && q < endp && isIndentWS(q)) + ++q; + codebuf.remove(p - (utf8_t *)codebuf.data, q - p); + assert((utf8_t *)codebuf.data <= p); + assert(p < (utf8_t *)codebuf.data + codebuf.offset); + lineStart = false; + endp = (utf8_t *)codebuf.data + codebuf.offset; // update + continue; + } + if (*p == '\n') + lineStart = true; + ++p; + } + + highlightCode2(sc, a, &codebuf, 0); + buf->remove(iCodeStart, i - iCodeStart); + i = buf->insert(iCodeStart, codebuf.data, codebuf.offset); + i = buf->insert(i, (const char *)")\n", 2); + i -= 2; // in next loop, c should be '\n' + } + else + { + static const char d_code[] = "$(D_CODE "; + + inCode = 1; + codeIndent = istart - iLineStart; // save indent count + i = buf->insert(i, d_code, strlen(d_code)); + iCodeStart = i; + i--; // place i on > + leadingBlank = true; + } + } + break; + + default: + leadingBlank = 0; + if (sc->_module->isDocFile || inCode) + break; + + utf8_t *start = (utf8_t *)buf->data + i; + if (isIdStart(start)) + { + size_t j = skippastident(buf, i); + if (i < j) + { + size_t k = skippastURL(buf, i); + if (i < k) + { + i = k - 1; + break; + } + } + else + break; + size_t len = j - i; + + // leading '_' means no highlight unless it's a reserved symbol name + if (c == '_' && + (i == 0 || !isdigit(*(start - 1))) && + (i == buf->offset - 1 || !isReservedName(start, len))) + { + buf->remove(i, 1); + i = j - 1; + break; + } + if (isIdentifier(a, start, len)) + { + i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; + break; + } + if (isKeyword(start, len)) + { + i = buf->bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1; + break; + } + if (isFunctionParameter(a, start, len)) + { + //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); + i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1; + break; + } + + i = j - 1; + } + break; + } + } + if (inCode) + error(s ? s->loc : Loc(), "unmatched --- in DDoc comment"); +} + +/************************************************** + * Highlight code for DDOC section. + */ + +void highlightCode(Scope *sc, Dsymbol *s, OutBuffer *buf, size_t offset) +{ + //printf("highlightCode(s = %s '%s')\n", s->kind(), s->toChars()); + OutBuffer ancbuf; + emitAnchor(&ancbuf, s, sc); + buf->insert(offset, (char *)ancbuf.data, ancbuf.offset); + offset += ancbuf.offset; + + Dsymbols a; + a.push(s); + highlightCode(sc, &a, buf, offset); +} + +/**************************************************** + */ + +void highlightCode(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset) +{ + //printf("highlightCode(a = '%s')\n", a->toChars()); + + for (size_t i = offset; i < buf->offset; i++) + { + utf8_t c = buf->data[i]; + const char *se = sc->_module->escapetable->escapeChar(c); + if (se) + { + size_t len = strlen(se); + buf->remove(i, 1); + i = buf->insert(i, se, len); + i--; // point to ';' + continue; + } + + utf8_t *start = (utf8_t *)buf->data + i; + if (isIdStart(start)) + { + size_t j = skippastident(buf, i); + if (i < j) + { + size_t len = j - i; + if (isIdentifier(a, start, len)) + { + i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; + continue; + } + if (isFunctionParameter(a, start, len)) + { + //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); + i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1; + continue; + } + i = j - 1; + } + } + } +} + +/**************************************** + */ + +void highlightCode3(Scope *sc, OutBuffer *buf, const utf8_t *p, const utf8_t *pend) +{ + for (; p < pend; p++) + { + const char *s = sc->_module->escapetable->escapeChar(*p); + if (s) + buf->writestring(s); + else + buf->writeByte(*p); + } +} + +/************************************************** + * Highlight code for CODE section. + */ + +void highlightCode2(Scope *sc, Dsymbols *a, OutBuffer *buf, size_t offset) +{ + unsigned errorsave = global.errors; + Lexer lex(NULL, (utf8_t *)buf->data, 0, buf->offset - 1, 0, 1); + OutBuffer res; + const utf8_t *lastp = (utf8_t *)buf->data; + + //printf("highlightCode2('%.*s')\n", buf->offset - 1, buf->data); + res.reserve(buf->offset); + while (1) + { + Token tok; + lex.scan(&tok); + highlightCode3(sc, &res, lastp, tok.ptr); + + const char *highlight = NULL; + switch (tok.value) + { + case TOKidentifier: + { + if (!sc) + break; + size_t len = lex.p - tok.ptr; + if (isIdentifier(a, tok.ptr, len)) + { + highlight = "$(D_PSYMBOL "; + break; + } + if (isFunctionParameter(a, tok.ptr, len)) + { + //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->toChars(), i, j); + highlight = "$(D_PARAM "; + break; + } + break; + } + case TOKcomment: + highlight = "$(D_COMMENT "; + break; + + case TOKstring: + highlight = "$(D_STRING "; + break; + + default: + if (tok.isKeyword()) + highlight = "$(D_KEYWORD "; + break; + } + if (highlight) + { + res.writestring(highlight); + size_t o = res.offset; + highlightCode3(sc, &res, tok.ptr, lex.p); + if (tok.value == TOKcomment || tok.value == TOKstring) + escapeDdocString(&res, o); // Bugzilla 7656, 7715, and 10519 + res.writeByte(')'); + } + else + highlightCode3(sc, &res, tok.ptr, lex.p); + if (tok.value == TOKeof) + break; + lastp = lex.p; + } + buf->setsize(offset); + buf->write(&res); + global.errors = errorsave; +} + +/*************************************** + * Find character string to replace c with. + */ + +const char *Escape::escapeChar(unsigned c) +{ + assert(c < 256); + //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c]); + return strings[c]; +} + +/**************************************** + * Determine if p points to the start of a "..." parameter identifier. + */ + +bool isCVariadicArg(const utf8_t *p, size_t len) +{ + return len >= 3 && cmp("...", p, 3) == 0; +} + +/**************************************** + * Determine if p points to the start of an identifier. + */ + +bool isIdStart(const utf8_t *p) +{ + unsigned c = *p; + if (isalpha(c) || c == '_') + return true; + if (c >= 0x80) + { + size_t i = 0; + if (utf_decodeChar(p, 4, &i, &c)) + return false; // ignore errors + if (isUniAlpha(c)) + return true; + } + return false; +} + +/**************************************** + * Determine if p points to the rest of an identifier. + */ + +bool isIdTail(const utf8_t *p) +{ + unsigned c = *p; + if (isalnum(c) || c == '_') + return true; + if (c >= 0x80) + { + size_t i = 0; + if (utf_decodeChar(p, 4, &i, &c)) + return false; // ignore errors + if (isUniAlpha(c)) + return true; + } + return false; +} + +/**************************************** + * Determine if p points to the indentation space. + */ + +bool isIndentWS(const utf8_t *p) +{ + return (*p == ' ') || (*p == '\t'); +} + +/***************************************** + * Return number of bytes in UTF character. + */ + +int utfStride(const utf8_t *p) +{ + unsigned c = *p; + if (c < 0x80) + return 1; + size_t i = 0; + utf_decodeChar(p, 4, &i, &c); // ignore errors, but still consume input + return (int)i; +} |