aboutsummaryrefslogtreecommitdiff
path: root/gcc/d/dmd/doc.c
diff options
context:
space:
mode:
authorIain Buclaw <ibuclaw@gcc.gnu.org>2018-10-28 19:51:47 +0000
committerIain Buclaw <ibuclaw@gcc.gnu.org>2018-10-28 19:51:47 +0000
commitb4c522fabd0df7be08882d2207df8b2765026110 (patch)
treeb5ffc312b0a441c1ba24323152aec463fdbe5e9f /gcc/d/dmd/doc.c
parent01ce9e31a02c8039d88e90f983735104417bf034 (diff)
downloadgcc-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.c2741
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 = /</&lt;/\n\
+ />/&gt;/\n\
+ /&/&amp;/\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, "&lt;") == 0)
+ {
+ // Generating HTML
+ // Skip over comments
+ if (p[1] == '!' && p[2] == '-' && p[3] == '-')
+ {
+ size_t j = i + 4;
+ p += 4;
+ while (1)
+ {
+ if (j == buf->offset)
+ goto L1;
+ if (p[0] == '-' && p[1] == '-' && p[2] == '>')
+ {
+ i = j + 2; // place on closing '>'
+ break;
+ }
+ j++;
+ p++;
+ }
+ break;
+ }
+
+ // Skip over HTML tag
+ if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2])))
+ {
+ size_t j = i + 2;
+ p += 2;
+ while (1)
+ {
+ if (j == buf->offset)
+ break;
+ if (p[0] == '>')
+ {
+ i = j; // place on closing '>'
+ break;
+ }
+ j++;
+ p++;
+ }
+ break;
+ }
+ }
+ L1:
+ // Replace '<' with '&lt;' character entity
+ if (se)
+ {
+ size_t len = strlen(se);
+ buf->remove(i, 1);
+ i = buf->insert(i, se, len);
+ i--; // point to ';'
+ }
+ break;
+ }
+ case '>':
+ {
+ leadingBlank = 0;
+ if (inCode)
+ break;
+ // Replace '>' with '&gt;' character entity
+ const char *se = sc->_module->escapetable->escapeChar('>');
+ if (se)
+ {
+ size_t len = strlen(se);
+ buf->remove(i, 1);
+ i = buf->insert(i, se, len);
+ i--; // point to ';'
+ }
+ break;
+ }
+ case '&':
+ {
+ leadingBlank = 0;
+ if (inCode)
+ break;
+ utf8_t *p = (utf8_t *)&buf->data[i];
+ if (p[1] == '#' || isalpha(p[1]))
+ break; // already a character entity
+ // Replace '&' with '&amp;' character entity
+ const char *se = sc->_module->escapetable->escapeChar('&');
+ if (se)
+ {
+ size_t len = strlen(se);
+ buf->remove(i, 1);
+ i = buf->insert(i, se, len);
+ i--; // point to ';'
+ }
+ break;
+ }
+ case '`':
+ {
+ if (inBacktick)
+ {
+ inBacktick = 0;
+ inCode = 0;
+
+ OutBuffer codebuf;
+
+ codebuf.write(buf->data + iCodeStart + 1, i - (iCodeStart + 1));
+
+ // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL
+ highlightCode(sc, a, &codebuf, 0);
+
+ buf->remove(iCodeStart, i - iCodeStart + 1); // also trimming off the current `
+
+ static 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;
+}