1 1.1 mrg /** 2 1.1 mrg * Ddoc documentation generation. 3 1.1 mrg * 4 1.1 mrg * Specification: $(LINK2 https://dlang.org/spec/ddoc.html, Documentation Generator) 5 1.1 mrg * 6 1.1 mrg * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved 7 1.1 mrg * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 8 1.1 mrg * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 1.1 mrg * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/doc.d, _doc.d) 10 1.1 mrg * Documentation: https://dlang.org/phobos/dmd_doc.html 11 1.1 mrg * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/doc.d 12 1.1 mrg */ 13 1.1 mrg 14 1.1 mrg module dmd.doc; 15 1.1 mrg 16 1.1 mrg import core.stdc.ctype; 17 1.1 mrg import core.stdc.stdlib; 18 1.1 mrg import core.stdc.stdio; 19 1.1 mrg import core.stdc.string; 20 1.1 mrg import core.stdc.time; 21 1.1 mrg import dmd.aggregate; 22 1.1 mrg import dmd.arraytypes; 23 1.1 mrg import dmd.astenums; 24 1.1 mrg import dmd.attrib; 25 1.1 mrg import dmd.cond; 26 1.1 mrg import dmd.dclass; 27 1.1 mrg import dmd.declaration; 28 1.1 mrg import dmd.denum; 29 1.1 mrg import dmd.dimport; 30 1.1 mrg import dmd.dmacro; 31 1.1 mrg import dmd.dmodule; 32 1.1 mrg import dmd.dscope; 33 1.1 mrg import dmd.dstruct; 34 1.1 mrg import dmd.dsymbol; 35 1.1 mrg import dmd.dsymbolsem; 36 1.1 mrg import dmd.dtemplate; 37 1.1 mrg import dmd.errors; 38 1.1 mrg import dmd.func; 39 1.1 mrg import dmd.globals; 40 1.1 mrg import dmd.hdrgen; 41 1.1 mrg import dmd.id; 42 1.1 mrg import dmd.identifier; 43 1.1 mrg import dmd.lexer; 44 1.1 mrg import dmd.mtype; 45 1.1 mrg import dmd.root.array; 46 1.1 mrg import dmd.root.file; 47 1.1 mrg import dmd.root.filename; 48 1.1 mrg import dmd.common.outbuffer; 49 1.1 mrg import dmd.root.port; 50 1.1 mrg import dmd.root.rmem; 51 1.1 mrg import dmd.root.string; 52 1.1 mrg import dmd.root.utf; 53 1.1 mrg import dmd.tokens; 54 1.1 mrg import dmd.utils; 55 1.1 mrg import dmd.visitor; 56 1.1 mrg 57 1.1 mrg struct Escape 58 1.1 mrg { 59 1.1 mrg const(char)[][char.max] strings; 60 1.1 mrg 61 1.1 mrg /*************************************** 62 1.1 mrg * Find character string to replace c with. 63 1.1 mrg */ 64 1.1 mrg const(char)[] escapeChar(char c) 65 1.1 mrg { 66 1.1 mrg version (all) 67 1.1 mrg { 68 1.1 mrg //printf("escapeChar('%c') => %p, %p\n", c, strings, strings[c].ptr); 69 1.1 mrg return strings[c]; 70 1.1 mrg } 71 1.1 mrg else 72 1.1 mrg { 73 1.1 mrg const(char)[] s; 74 1.1 mrg switch (c) 75 1.1 mrg { 76 1.1 mrg case '<': 77 1.1 mrg s = "<"; 78 1.1 mrg break; 79 1.1 mrg case '>': 80 1.1 mrg s = ">"; 81 1.1 mrg break; 82 1.1 mrg case '&': 83 1.1 mrg s = "&"; 84 1.1 mrg break; 85 1.1 mrg default: 86 1.1 mrg s = null; 87 1.1 mrg break; 88 1.1 mrg } 89 1.1 mrg return s; 90 1.1 mrg } 91 1.1 mrg } 92 1.1 mrg } 93 1.1 mrg 94 1.1 mrg /*********************************************************** 95 1.1 mrg */ 96 1.1 mrg private class Section 97 1.1 mrg { 98 1.1 mrg const(char)[] name; 99 1.1 mrg const(char)[] body_; 100 1.1 mrg int nooutput; 101 1.1 mrg 102 1.1 mrg override string toString() const 103 1.1 mrg { 104 1.1 mrg assert(0); 105 1.1 mrg } 106 1.1 mrg 107 1.1 mrg void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf) 108 1.1 mrg { 109 1.1 mrg assert(a.dim); 110 1.1 mrg if (name.length) 111 1.1 mrg { 112 1.1 mrg static immutable table = 113 1.1 mrg [ 114 1.1 mrg "AUTHORS", 115 1.1 mrg "BUGS", 116 1.1 mrg "COPYRIGHT", 117 1.1 mrg "DATE", 118 1.1 mrg "DEPRECATED", 119 1.1 mrg "EXAMPLES", 120 1.1 mrg "HISTORY", 121 1.1 mrg "LICENSE", 122 1.1 mrg "RETURNS", 123 1.1 mrg "SEE_ALSO", 124 1.1 mrg "STANDARDS", 125 1.1 mrg "THROWS", 126 1.1 mrg "VERSION", 127 1.1 mrg ]; 128 1.1 mrg foreach (entry; table) 129 1.1 mrg { 130 1.1 mrg if (iequals(entry, name)) 131 1.1 mrg { 132 1.1 mrg buf.printf("$(DDOC_%s ", entry.ptr); 133 1.1 mrg goto L1; 134 1.1 mrg } 135 1.1 mrg } 136 1.1 mrg buf.writestring("$(DDOC_SECTION "); 137 1.1 mrg // Replace _ characters with spaces 138 1.1 mrg buf.writestring("$(DDOC_SECTION_H "); 139 1.1 mrg size_t o = buf.length; 140 1.1 mrg foreach (char c; name) 141 1.1 mrg buf.writeByte((c == '_') ? ' ' : c); 142 1.1 mrg escapeStrayParenthesis(loc, buf, o, false); 143 1.1 mrg buf.writestring(")"); 144 1.1 mrg } 145 1.1 mrg else 146 1.1 mrg { 147 1.1 mrg buf.writestring("$(DDOC_DESCRIPTION "); 148 1.1 mrg } 149 1.1 mrg L1: 150 1.1 mrg size_t o = buf.length; 151 1.1 mrg buf.write(body_); 152 1.1 mrg escapeStrayParenthesis(loc, buf, o, true); 153 1.1 mrg highlightText(sc, a, loc, *buf, o); 154 1.1 mrg buf.writestring(")"); 155 1.1 mrg } 156 1.1 mrg } 157 1.1 mrg 158 1.1 mrg /*********************************************************** 159 1.1 mrg */ 160 1.1 mrg private final class ParamSection : Section 161 1.1 mrg { 162 1.1 mrg override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf) 163 1.1 mrg { 164 1.1 mrg assert(a.dim); 165 1.1 mrg Dsymbol s = (*a)[0]; // test 166 1.1 mrg const(char)* p = body_.ptr; 167 1.1 mrg size_t len = body_.length; 168 1.1 mrg const(char)* pend = p + len; 169 1.1 mrg const(char)* tempstart = null; 170 1.1 mrg size_t templen = 0; 171 1.1 mrg const(char)* namestart = null; 172 1.1 mrg size_t namelen = 0; // !=0 if line continuation 173 1.1 mrg const(char)* textstart = null; 174 1.1 mrg size_t textlen = 0; 175 1.1 mrg size_t paramcount = 0; 176 1.1 mrg buf.writestring("$(DDOC_PARAMS "); 177 1.1 mrg while (p < pend) 178 1.1 mrg { 179 1.1 mrg // Skip to start of macro 180 1.1 mrg while (1) 181 1.1 mrg { 182 1.1 mrg switch (*p) 183 1.1 mrg { 184 1.1 mrg case ' ': 185 1.1 mrg case '\t': 186 1.1 mrg p++; 187 1.1 mrg continue; 188 1.1 mrg case '\n': 189 1.1 mrg p++; 190 1.1 mrg goto Lcont; 191 1.1 mrg default: 192 1.1 mrg if (isIdStart(p) || isCVariadicArg(p[0 .. cast(size_t)(pend - p)])) 193 1.1 mrg break; 194 1.1 mrg if (namelen) 195 1.1 mrg goto Ltext; 196 1.1 mrg // continuation of prev macro 197 1.1 mrg goto Lskipline; 198 1.1 mrg } 199 1.1 mrg break; 200 1.1 mrg } 201 1.1 mrg tempstart = p; 202 1.1 mrg while (isIdTail(p)) 203 1.1 mrg p += utfStride(p); 204 1.1 mrg if (isCVariadicArg(p[0 .. cast(size_t)(pend - p)])) 205 1.1 mrg p += 3; 206 1.1 mrg templen = p - tempstart; 207 1.1 mrg while (*p == ' ' || *p == '\t') 208 1.1 mrg p++; 209 1.1 mrg if (*p != '=') 210 1.1 mrg { 211 1.1 mrg if (namelen) 212 1.1 mrg goto Ltext; 213 1.1 mrg // continuation of prev macro 214 1.1 mrg goto Lskipline; 215 1.1 mrg } 216 1.1 mrg p++; 217 1.1 mrg if (namelen) 218 1.1 mrg { 219 1.1 mrg // Output existing param 220 1.1 mrg L1: 221 1.1 mrg //printf("param '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart); 222 1.1 mrg ++paramcount; 223 1.1 mrg HdrGenState hgs; 224 1.1 mrg buf.writestring("$(DDOC_PARAM_ROW "); 225 1.1 mrg { 226 1.1 mrg buf.writestring("$(DDOC_PARAM_ID "); 227 1.1 mrg { 228 1.1 mrg size_t o = buf.length; 229 1.1 mrg Parameter fparam = isFunctionParameter(a, namestart, namelen); 230 1.1 mrg if (!fparam) 231 1.1 mrg { 232 1.1 mrg // Comments on a template might refer to function parameters within. 233 1.1 mrg // Search the parameters of nested eponymous functions (with the same name.) 234 1.1 mrg fparam = isEponymousFunctionParameter(a, namestart, namelen); 235 1.1 mrg } 236 1.1 mrg bool isCVariadic = isCVariadicParameter(a, namestart[0 .. namelen]); 237 1.1 mrg if (isCVariadic) 238 1.1 mrg { 239 1.1 mrg buf.writestring("..."); 240 1.1 mrg } 241 1.1 mrg else if (fparam && fparam.type && fparam.ident) 242 1.1 mrg { 243 1.1 mrg .toCBuffer(fparam.type, buf, fparam.ident, &hgs); 244 1.1 mrg } 245 1.1 mrg else 246 1.1 mrg { 247 1.1 mrg if (isTemplateParameter(a, namestart, namelen)) 248 1.1 mrg { 249 1.1 mrg // 10236: Don't count template parameters for params check 250 1.1 mrg --paramcount; 251 1.1 mrg } 252 1.1 mrg else if (!fparam) 253 1.1 mrg { 254 1.1 mrg warning(s.loc, "Ddoc: function declaration has no parameter '%.*s'", cast(int)namelen, namestart); 255 1.1 mrg } 256 1.1 mrg buf.write(namestart[0 .. namelen]); 257 1.1 mrg } 258 1.1 mrg escapeStrayParenthesis(loc, buf, o, true); 259 1.1 mrg highlightCode(sc, a, *buf, o); 260 1.1 mrg } 261 1.1 mrg buf.writestring(")"); 262 1.1 mrg buf.writestring("$(DDOC_PARAM_DESC "); 263 1.1 mrg { 264 1.1 mrg size_t o = buf.length; 265 1.1 mrg buf.write(textstart[0 .. textlen]); 266 1.1 mrg escapeStrayParenthesis(loc, buf, o, true); 267 1.1 mrg highlightText(sc, a, loc, *buf, o); 268 1.1 mrg } 269 1.1 mrg buf.writestring(")"); 270 1.1 mrg } 271 1.1 mrg buf.writestring(")"); 272 1.1 mrg namelen = 0; 273 1.1 mrg if (p >= pend) 274 1.1 mrg break; 275 1.1 mrg } 276 1.1 mrg namestart = tempstart; 277 1.1 mrg namelen = templen; 278 1.1 mrg while (*p == ' ' || *p == '\t') 279 1.1 mrg p++; 280 1.1 mrg textstart = p; 281 1.1 mrg Ltext: 282 1.1 mrg while (*p != '\n') 283 1.1 mrg p++; 284 1.1 mrg textlen = p - textstart; 285 1.1 mrg p++; 286 1.1 mrg Lcont: 287 1.1 mrg continue; 288 1.1 mrg Lskipline: 289 1.1 mrg // Ignore this line 290 1.1 mrg while (*p++ != '\n') 291 1.1 mrg { 292 1.1 mrg } 293 1.1 mrg } 294 1.1 mrg if (namelen) 295 1.1 mrg goto L1; 296 1.1 mrg // write out last one 297 1.1 mrg buf.writestring(")"); 298 1.1 mrg TypeFunction tf = a.dim == 1 ? isTypeFunction(s) : null; 299 1.1 mrg if (tf) 300 1.1 mrg { 301 1.1 mrg size_t pcount = (tf.parameterList.parameters ? tf.parameterList.parameters.dim : 0) + 302 1.1 mrg cast(int)(tf.parameterList.varargs == VarArg.variadic); 303 1.1 mrg if (pcount != paramcount) 304 1.1 mrg { 305 1.1 mrg warning(s.loc, "Ddoc: parameter count mismatch, expected %llu, got %llu", 306 1.1 mrg cast(ulong) pcount, cast(ulong) paramcount); 307 1.1 mrg if (paramcount == 0) 308 1.1 mrg { 309 1.1 mrg // Chances are someone messed up the format 310 1.1 mrg warningSupplemental(s.loc, "Note that the format is `param = description`"); 311 1.1 mrg } 312 1.1 mrg } 313 1.1 mrg } 314 1.1 mrg } 315 1.1 mrg } 316 1.1 mrg 317 1.1 mrg /*********************************************************** 318 1.1 mrg */ 319 1.1 mrg private final class MacroSection : Section 320 1.1 mrg { 321 1.1 mrg override void write(Loc loc, DocComment* dc, Scope* sc, Dsymbols* a, OutBuffer* buf) 322 1.1 mrg { 323 1.1 mrg //printf("MacroSection::write()\n"); 324 1.1 mrg DocComment.parseMacros(dc.escapetable, *dc.pmacrotable, body_); 325 1.1 mrg } 326 1.1 mrg } 327 1.1 mrg 328 1.1 mrg private alias Sections = Array!(Section); 329 1.1 mrg 330 1.1 mrg // Workaround for missing Parameter instance for variadic params. (it's unnecessary to instantiate one). 331 1.1 mrg private bool isCVariadicParameter(Dsymbols* a, const(char)[] p) 332 1.1 mrg { 333 1.1 mrg foreach (member; *a) 334 1.1 mrg { 335 1.1 mrg TypeFunction tf = isTypeFunction(member); 336 1.1 mrg if (tf && tf.parameterList.varargs == VarArg.variadic && p == "...") 337 1.1 mrg return true; 338 1.1 mrg } 339 1.1 mrg return false; 340 1.1 mrg } 341 1.1 mrg 342 1.1 mrg private Dsymbol getEponymousMember(TemplateDeclaration td) 343 1.1 mrg { 344 1.1 mrg if (!td.onemember) 345 1.1 mrg return null; 346 1.1 mrg if (AggregateDeclaration ad = td.onemember.isAggregateDeclaration()) 347 1.1 mrg return ad; 348 1.1 mrg if (FuncDeclaration fd = td.onemember.isFuncDeclaration()) 349 1.1 mrg return fd; 350 1.1 mrg if (auto em = td.onemember.isEnumMember()) 351 1.1 mrg return null; // Keep backward compatibility. See compilable/ddoc9.d 352 1.1 mrg if (VarDeclaration vd = td.onemember.isVarDeclaration()) 353 1.1 mrg return td.constraint ? null : vd; 354 1.1 mrg return null; 355 1.1 mrg } 356 1.1 mrg 357 1.1 mrg private TemplateDeclaration getEponymousParent(Dsymbol s) 358 1.1 mrg { 359 1.1 mrg if (!s.parent) 360 1.1 mrg return null; 361 1.1 mrg TemplateDeclaration td = s.parent.isTemplateDeclaration(); 362 1.1 mrg return (td && getEponymousMember(td)) ? td : null; 363 1.1 mrg } 364 1.1 mrg 365 1.1 mrg private immutable ddoc_default = import("default_ddoc_theme." ~ ddoc_ext); 366 1.1 mrg private immutable ddoc_decl_s = "$(DDOC_DECL "; 367 1.1 mrg private immutable ddoc_decl_e = ")\n"; 368 1.1 mrg private immutable ddoc_decl_dd_s = "$(DDOC_DECL_DD "; 369 1.1 mrg private immutable ddoc_decl_dd_e = ")\n"; 370 1.1 mrg 371 1.1 mrg /**************************************************** 372 1.1 mrg */ 373 1.1 mrg extern(C++) void gendocfile(Module m) 374 1.1 mrg { 375 1.1 mrg __gshared OutBuffer mbuf; 376 1.1 mrg __gshared int mbuf_done; 377 1.1 mrg OutBuffer buf; 378 1.1 mrg //printf("Module::gendocfile()\n"); 379 1.1 mrg if (!mbuf_done) // if not already read the ddoc files 380 1.1 mrg { 381 1.1 mrg mbuf_done = 1; 382 1.1 mrg // Use our internal default 383 1.1 mrg mbuf.writestring(ddoc_default); 384 1.1 mrg // Override with DDOCFILE specified in the sc.ini file 385 1.1 mrg char* p = getenv("DDOCFILE"); 386 1.1 mrg if (p) 387 1.1 mrg global.params.ddocfiles.shift(p); 388 1.1 mrg // Override with the ddoc macro files from the command line 389 1.1 mrg for (size_t i = 0; i < global.params.ddocfiles.dim; i++) 390 1.1 mrg { 391 1.1 mrg auto buffer = readFile(m.loc, global.params.ddocfiles[i]); 392 1.1 mrg // BUG: convert file contents to UTF-8 before use 393 1.1 mrg const data = buffer.data; 394 1.1 mrg //printf("file: '%.*s'\n", cast(int)data.length, data.ptr); 395 1.1 mrg mbuf.write(data); 396 1.1 mrg } 397 1.1 mrg } 398 1.1 mrg DocComment.parseMacros(m.escapetable, m.macrotable, mbuf[]); 399 1.1 mrg Scope* sc = Scope.createGlobal(m); // create root scope 400 1.1 mrg DocComment* dc = DocComment.parse(m, m.comment); 401 1.1 mrg dc.pmacrotable = &m.macrotable; 402 1.1 mrg dc.escapetable = m.escapetable; 403 1.1 mrg sc.lastdc = dc; 404 1.1 mrg // Generate predefined macros 405 1.1 mrg // Set the title to be the name of the module 406 1.1 mrg { 407 1.1 mrg const p = m.toPrettyChars().toDString; 408 1.1 mrg m.macrotable.define("TITLE", p); 409 1.1 mrg } 410 1.1 mrg // Set time macros 411 1.1 mrg { 412 1.1 mrg time_t t; 413 1.1 mrg time(&t); 414 1.1 mrg char* p = ctime(&t); 415 1.1 mrg p = mem.xstrdup(p); 416 1.1 mrg m.macrotable.define("DATETIME", p.toDString()); 417 1.1 mrg m.macrotable.define("YEAR", p[20 .. 20 + 4]); 418 1.1 mrg } 419 1.1 mrg const srcfilename = m.srcfile.toString(); 420 1.1 mrg m.macrotable.define("SRCFILENAME", srcfilename); 421 1.1 mrg const docfilename = m.docfile.toString(); 422 1.1 mrg m.macrotable.define("DOCFILENAME", docfilename); 423 1.1 mrg if (dc.copyright) 424 1.1 mrg { 425 1.1 mrg dc.copyright.nooutput = 1; 426 1.1 mrg m.macrotable.define("COPYRIGHT", dc.copyright.body_); 427 1.1 mrg } 428 1.1 mrg if (m.filetype == FileType.ddoc) 429 1.1 mrg { 430 1.1 mrg const ploc = m.md ? &m.md.loc : &m.loc; 431 1.1 mrg const loc = Loc(ploc.filename ? ploc.filename : srcfilename.ptr, 432 1.1 mrg ploc.linnum, 433 1.1 mrg ploc.charnum); 434 1.1 mrg 435 1.1 mrg size_t commentlen = strlen(cast(char*)m.comment); 436 1.1 mrg Dsymbols a; 437 1.1 mrg // https://issues.dlang.org/show_bug.cgi?id=9764 438 1.1 mrg // Don't push m in a, to prevent emphasize ddoc file name. 439 1.1 mrg if (dc.macros) 440 1.1 mrg { 441 1.1 mrg commentlen = dc.macros.name.ptr - m.comment; 442 1.1 mrg dc.macros.write(loc, dc, sc, &a, &buf); 443 1.1 mrg } 444 1.1 mrg buf.write(m.comment[0 .. commentlen]); 445 1.1 mrg highlightText(sc, &a, loc, buf, 0); 446 1.1 mrg } 447 1.1 mrg else 448 1.1 mrg { 449 1.1 mrg Dsymbols a; 450 1.1 mrg a.push(m); 451 1.1 mrg dc.writeSections(sc, &a, &buf); 452 1.1 mrg emitMemberComments(m, buf, sc); 453 1.1 mrg } 454 1.1 mrg //printf("BODY= '%.*s'\n", cast(int)buf.length, buf.data); 455 1.1 mrg m.macrotable.define("BODY", buf[]); 456 1.1 mrg OutBuffer buf2; 457 1.1 mrg buf2.writestring("$(DDOC)"); 458 1.1 mrg size_t end = buf2.length; 459 1.1 mrg m.macrotable.expand(buf2, 0, end, null); 460 1.1 mrg version (all) 461 1.1 mrg { 462 1.1 mrg /* Remove all the escape sequences from buf2, 463 1.1 mrg * and make CR-LF the newline. 464 1.1 mrg */ 465 1.1 mrg { 466 1.1 mrg const slice = buf2[]; 467 1.1 mrg buf.setsize(0); 468 1.1 mrg buf.reserve(slice.length); 469 1.1 mrg auto p = slice.ptr; 470 1.1 mrg for (size_t j = 0; j < slice.length; j++) 471 1.1 mrg { 472 1.1 mrg char c = p[j]; 473 1.1 mrg if (c == 0xFF && j + 1 < slice.length) 474 1.1 mrg { 475 1.1 mrg j++; 476 1.1 mrg continue; 477 1.1 mrg } 478 1.1 mrg if (c == '\n') 479 1.1 mrg buf.writeByte('\r'); 480 1.1 mrg else if (c == '\r') 481 1.1 mrg { 482 1.1 mrg buf.writestring("\r\n"); 483 1.1 mrg if (j + 1 < slice.length && p[j + 1] == '\n') 484 1.1 mrg { 485 1.1 mrg j++; 486 1.1 mrg } 487 1.1 mrg continue; 488 1.1 mrg } 489 1.1 mrg buf.writeByte(c); 490 1.1 mrg } 491 1.1 mrg } 492 1.1 mrg writeFile(m.loc, m.docfile.toString(), buf[]); 493 1.1 mrg } 494 1.1 mrg else 495 1.1 mrg { 496 1.1 mrg /* Remove all the escape sequences from buf2 497 1.1 mrg */ 498 1.1 mrg { 499 1.1 mrg size_t i = 0; 500 1.1 mrg char* p = buf2.data; 501 1.1 mrg for (size_t j = 0; j < buf2.length; j++) 502 1.1 mrg { 503 1.1 mrg if (p[j] == 0xFF && j + 1 < buf2.length) 504 1.1 mrg { 505 1.1 mrg j++; 506 1.1 mrg continue; 507 1.1 mrg } 508 1.1 mrg p[i] = p[j]; 509 1.1 mrg i++; 510 1.1 mrg } 511 1.1 mrg buf2.setsize(i); 512 1.1 mrg } 513 1.1 mrg writeFile(m.loc, m.docfile.toString(), buf2[]); 514 1.1 mrg } 515 1.1 mrg } 516 1.1 mrg 517 1.1 mrg /**************************************************** 518 1.1 mrg * Having unmatched parentheses can hose the output of Ddoc, 519 1.1 mrg * as the macros depend on properly nested parentheses. 520 1.1 mrg * This function replaces all ( with $(LPAREN) and ) with $(RPAREN) 521 1.1 mrg * to preserve text literally. This also means macros in the 522 1.1 mrg * text won't be expanded. 523 1.1 mrg */ 524 1.1 mrg void escapeDdocString(OutBuffer* buf, size_t start) 525 1.1 mrg { 526 1.1 mrg for (size_t u = start; u < buf.length; u++) 527 1.1 mrg { 528 1.1 mrg char c = (*buf)[u]; 529 1.1 mrg switch (c) 530 1.1 mrg { 531 1.1 mrg case '$': 532 1.1 mrg buf.remove(u, 1); 533 1.1 mrg buf.insert(u, "$(DOLLAR)"); 534 1.1 mrg u += 8; 535 1.1 mrg break; 536 1.1 mrg case '(': 537 1.1 mrg buf.remove(u, 1); //remove the ( 538 1.1 mrg buf.insert(u, "$(LPAREN)"); //insert this instead 539 1.1 mrg u += 8; //skip over newly inserted macro 540 1.1 mrg break; 541 1.1 mrg case ')': 542 1.1 mrg buf.remove(u, 1); //remove the ) 543 1.1 mrg buf.insert(u, "$(RPAREN)"); //insert this instead 544 1.1 mrg u += 8; //skip over newly inserted macro 545 1.1 mrg break; 546 1.1 mrg default: 547 1.1 mrg break; 548 1.1 mrg } 549 1.1 mrg } 550 1.1 mrg } 551 1.1 mrg 552 1.1 mrg /**************************************************** 553 1.1 mrg * Having unmatched parentheses can hose the output of Ddoc, 554 1.1 mrg * as the macros depend on properly nested parentheses. 555 1.1 mrg * 556 1.1 mrg * Fix by replacing unmatched ( with $(LPAREN) and unmatched ) with $(RPAREN). 557 1.1 mrg * 558 1.1 mrg * Params: 559 1.1 mrg * loc = source location of start of text. It is a mutable copy to allow incrementing its linenum, for printing the correct line number when an error is encountered in a multiline block of ddoc. 560 1.1 mrg * buf = an OutBuffer containing the DDoc 561 1.1 mrg * start = the index within buf to start replacing unmatched parentheses 562 1.1 mrg * respectBackslashEscapes = if true, always replace parentheses that are 563 1.1 mrg * directly preceeded by a backslash with $(LPAREN) or $(RPAREN) instead of 564 1.1 mrg * counting them as stray parentheses 565 1.1 mrg */ 566 1.1 mrg private void escapeStrayParenthesis(Loc loc, OutBuffer* buf, size_t start, bool respectBackslashEscapes) 567 1.1 mrg { 568 1.1 mrg uint par_open = 0; 569 1.1 mrg char inCode = 0; 570 1.1 mrg bool atLineStart = true; 571 1.1 mrg for (size_t u = start; u < buf.length; u++) 572 1.1 mrg { 573 1.1 mrg char c = (*buf)[u]; 574 1.1 mrg switch (c) 575 1.1 mrg { 576 1.1 mrg case '(': 577 1.1 mrg if (!inCode) 578 1.1 mrg par_open++; 579 1.1 mrg atLineStart = false; 580 1.1 mrg break; 581 1.1 mrg case ')': 582 1.1 mrg if (!inCode) 583 1.1 mrg { 584 1.1 mrg if (par_open == 0) 585 1.1 mrg { 586 1.1 mrg //stray ')' 587 1.1 mrg warning(loc, "Ddoc: Stray ')'. This may cause incorrect Ddoc output. Use $(RPAREN) instead for unpaired right parentheses."); 588 1.1 mrg buf.remove(u, 1); //remove the ) 589 1.1 mrg buf.insert(u, "$(RPAREN)"); //insert this instead 590 1.1 mrg u += 8; //skip over newly inserted macro 591 1.1 mrg } 592 1.1 mrg else 593 1.1 mrg par_open--; 594 1.1 mrg } 595 1.1 mrg atLineStart = false; 596 1.1 mrg break; 597 1.1 mrg case '\n': 598 1.1 mrg atLineStart = true; 599 1.1 mrg version (none) 600 1.1 mrg { 601 1.1 mrg // For this to work, loc must be set to the beginning of the passed 602 1.1 mrg // text which is currently not possible 603 1.1 mrg // (loc is set to the Loc of the Dsymbol) 604 1.1 mrg loc.linnum++; 605 1.1 mrg } 606 1.1 mrg break; 607 1.1 mrg case ' ': 608 1.1 mrg case '\r': 609 1.1 mrg case '\t': 610 1.1 mrg break; 611 1.1 mrg case '-': 612 1.1 mrg case '`': 613 1.1 mrg case '~': 614 1.1 mrg // Issue 15465: don't try to escape unbalanced parens inside code 615 1.1 mrg // blocks. 616 1.1 mrg int numdash = 1; 617 1.1 mrg for (++u; u < buf.length && (*buf)[u] == c; ++u) 618 1.1 mrg ++numdash; 619 1.1 mrg --u; 620 1.1 mrg if (c == '`' || (atLineStart && numdash >= 3)) 621 1.1 mrg { 622 1.1 mrg if (inCode == c) 623 1.1 mrg inCode = 0; 624 1.1 mrg else if (!inCode) 625 1.1 mrg inCode = c; 626 1.1 mrg } 627 1.1 mrg atLineStart = false; 628 1.1 mrg break; 629 1.1 mrg case '\\': 630 1.1 mrg // replace backslash-escaped parens with their macros 631 1.1 mrg if (!inCode && respectBackslashEscapes && u+1 < buf.length && global.params.markdown) 632 1.1 mrg { 633 1.1 mrg if ((*buf)[u+1] == '(' || (*buf)[u+1] == ')') 634 1.1 mrg { 635 1.1 mrg const paren = (*buf)[u+1] == '(' ? "$(LPAREN)" : "$(RPAREN)"; 636 1.1 mrg buf.remove(u, 2); //remove the \) 637 1.1 mrg buf.insert(u, paren); //insert this instead 638 1.1 mrg u += 8; //skip over newly inserted macro 639 1.1 mrg } 640 1.1 mrg else if ((*buf)[u+1] == '\\') 641 1.1 mrg ++u; 642 1.1 mrg } 643 1.1 mrg break; 644 1.1 mrg default: 645 1.1 mrg atLineStart = false; 646 1.1 mrg break; 647 1.1 mrg } 648 1.1 mrg } 649 1.1 mrg if (par_open) // if any unmatched lparens 650 1.1 mrg { 651 1.1 mrg par_open = 0; 652 1.1 mrg for (size_t u = buf.length; u > start;) 653 1.1 mrg { 654 1.1 mrg u--; 655 1.1 mrg char c = (*buf)[u]; 656 1.1 mrg switch (c) 657 1.1 mrg { 658 1.1 mrg case ')': 659 1.1 mrg par_open++; 660 1.1 mrg break; 661 1.1 mrg case '(': 662 1.1 mrg if (par_open == 0) 663 1.1 mrg { 664 1.1 mrg //stray '(' 665 1.1 mrg warning(loc, "Ddoc: Stray '('. This may cause incorrect Ddoc output. Use $(LPAREN) instead for unpaired left parentheses."); 666 1.1 mrg buf.remove(u, 1); //remove the ( 667 1.1 mrg buf.insert(u, "$(LPAREN)"); //insert this instead 668 1.1 mrg } 669 1.1 mrg else 670 1.1 mrg par_open--; 671 1.1 mrg break; 672 1.1 mrg default: 673 1.1 mrg break; 674 1.1 mrg } 675 1.1 mrg } 676 1.1 mrg } 677 1.1 mrg } 678 1.1 mrg 679 1.1 mrg // Basically, this is to skip over things like private{} blocks in a struct or 680 1.1 mrg // class definition that don't add any components to the qualified name. 681 1.1 mrg private Scope* skipNonQualScopes(Scope* sc) 682 1.1 mrg { 683 1.1 mrg while (sc && !sc.scopesym) 684 1.1 mrg sc = sc.enclosing; 685 1.1 mrg return sc; 686 1.1 mrg } 687 1.1 mrg 688 1.1 mrg private bool emitAnchorName(ref OutBuffer buf, Dsymbol s, Scope* sc, bool includeParent) 689 1.1 mrg { 690 1.1 mrg if (!s || s.isPackage() || s.isModule()) 691 1.1 mrg return false; 692 1.1 mrg // Add parent names first 693 1.1 mrg bool dot = false; 694 1.1 mrg auto eponymousParent = getEponymousParent(s); 695 1.1 mrg if (includeParent && s.parent || eponymousParent) 696 1.1 mrg dot = emitAnchorName(buf, s.parent, sc, includeParent); 697 1.1 mrg else if (includeParent && sc) 698 1.1 mrg dot = emitAnchorName(buf, sc.scopesym, skipNonQualScopes(sc.enclosing), includeParent); 699 1.1 mrg // Eponymous template members can share the parent anchor name 700 1.1 mrg if (eponymousParent) 701 1.1 mrg return dot; 702 1.1 mrg if (dot) 703 1.1 mrg buf.writeByte('.'); 704 1.1 mrg // Use "this" not "__ctor" 705 1.1 mrg TemplateDeclaration td; 706 1.1 mrg if (s.isCtorDeclaration() || ((td = s.isTemplateDeclaration()) !is null && td.onemember && td.onemember.isCtorDeclaration())) 707 1.1 mrg { 708 1.1 mrg buf.writestring("this"); 709 1.1 mrg } 710 1.1 mrg else 711 1.1 mrg { 712 1.1 mrg /* We just want the identifier, not overloads like TemplateDeclaration::toChars. 713 1.1 mrg * We don't want the template parameter list and constraints. */ 714 1.1 mrg buf.writestring(s.Dsymbol.toChars()); 715 1.1 mrg } 716 1.1 mrg return true; 717 1.1 mrg } 718 1.1 mrg 719 1.1 mrg private void emitAnchor(ref OutBuffer buf, Dsymbol s, Scope* sc, bool forHeader = false) 720 1.1 mrg { 721 1.1 mrg Identifier ident; 722 1.1 mrg { 723 1.1 mrg OutBuffer anc; 724 1.1 mrg emitAnchorName(anc, s, skipNonQualScopes(sc), true); 725 1.1 mrg ident = Identifier.idPool(anc[]); 726 1.1 mrg } 727 1.1 mrg 728 1.1 mrg auto pcount = cast(void*)ident in sc.anchorCounts; 729 1.1 mrg typeof(*pcount) count; 730 1.1 mrg if (!forHeader) 731 1.1 mrg { 732 1.1 mrg if (pcount) 733 1.1 mrg { 734 1.1 mrg // Existing anchor, 735 1.1 mrg // don't write an anchor for matching consecutive ditto symbols 736 1.1 mrg TemplateDeclaration td = getEponymousParent(s); 737 1.1 mrg if (sc.prevAnchor == ident && sc.lastdc && (isDitto(s.comment) || (td && isDitto(td.comment)))) 738 1.1 mrg return; 739 1.1 mrg 740 1.1 mrg count = ++*pcount; 741 1.1 mrg } 742 1.1 mrg else 743 1.1 mrg { 744 1.1 mrg sc.anchorCounts[cast(void*)ident] = 1; 745 1.1 mrg count = 1; 746 1.1 mrg } 747 1.1 mrg } 748 1.1 mrg 749 1.1 mrg // cache anchor name 750 1.1 mrg sc.prevAnchor = ident; 751 1.1 mrg auto macroName = forHeader ? "DDOC_HEADER_ANCHOR" : "DDOC_ANCHOR"; 752 1.1 mrg 753 1.1 mrg if (auto imp = s.isImport()) 754 1.1 mrg { 755 1.1 mrg // For example: `public import core.stdc.string : memcpy, memcmp;` 756 1.1 mrg if (imp.aliases.dim > 0) 757 1.1 mrg { 758 1.1 mrg for(int i = 0; i < imp.aliases.dim; i++) 759 1.1 mrg { 760 1.1 mrg // Need to distinguish between 761 1.1 mrg // `public import core.stdc.string : memcpy, memcmp;` and 762 1.1 mrg // `public import core.stdc.string : copy = memcpy, compare = memcmp;` 763 1.1 mrg auto a = imp.aliases[i]; 764 1.1 mrg auto id = a ? a : imp.names[i]; 765 1.1 mrg auto loc = Loc.init; 766 1.1 mrg if (auto symFromId = sc.search(loc, id, null)) 767 1.1 mrg { 768 1.1 mrg emitAnchor(buf, symFromId, sc, forHeader); 769 1.1 mrg } 770 1.1 mrg } 771 1.1 mrg } 772 1.1 mrg else 773 1.1 mrg { 774 1.1 mrg // For example: `public import str = core.stdc.string;` 775 1.1 mrg if (imp.aliasId) 776 1.1 mrg { 777 1.1 mrg auto symbolName = imp.aliasId.toString(); 778 1.1 mrg 779 1.1 mrg buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr, 780 1.1 mrg cast(int) symbolName.length, symbolName.ptr); 781 1.1 mrg 782 1.1 mrg if (forHeader) 783 1.1 mrg { 784 1.1 mrg buf.printf(", %.*s", cast(int) symbolName.length, symbolName.ptr); 785 1.1 mrg } 786 1.1 mrg } 787 1.1 mrg else 788 1.1 mrg { 789 1.1 mrg // The general case: `public import core.stdc.string;` 790 1.1 mrg 791 1.1 mrg // fully qualify imports so `core.stdc.string` doesn't appear as `core` 792 1.1 mrg void printFullyQualifiedImport() 793 1.1 mrg { 794 1.1 mrg foreach (const pid; imp.packages) 795 1.1 mrg { 796 1.1 mrg buf.printf("%s.", pid.toChars()); 797 1.1 mrg } 798 1.1 mrg buf.writestring(imp.id.toString()); 799 1.1 mrg } 800 1.1 mrg 801 1.1 mrg buf.printf("$(%.*s ", cast(int) macroName.length, macroName.ptr); 802 1.1 mrg printFullyQualifiedImport(); 803 1.1 mrg 804 1.1 mrg if (forHeader) 805 1.1 mrg { 806 1.1 mrg buf.printf(", "); 807 1.1 mrg printFullyQualifiedImport(); 808 1.1 mrg } 809 1.1 mrg } 810 1.1 mrg 811 1.1 mrg buf.writeByte(')'); 812 1.1 mrg } 813 1.1 mrg } 814 1.1 mrg else 815 1.1 mrg { 816 1.1 mrg auto symbolName = ident.toString(); 817 1.1 mrg buf.printf("$(%.*s %.*s", cast(int) macroName.length, macroName.ptr, 818 1.1 mrg cast(int) symbolName.length, symbolName.ptr); 819 1.1 mrg 820 1.1 mrg // only append count once there's a duplicate 821 1.1 mrg if (count > 1) 822 1.1 mrg buf.printf(".%u", count); 823 1.1 mrg 824 1.1 mrg if (forHeader) 825 1.1 mrg { 826 1.1 mrg Identifier shortIdent; 827 1.1 mrg { 828 1.1 mrg OutBuffer anc; 829 1.1 mrg emitAnchorName(anc, s, skipNonQualScopes(sc), false); 830 1.1 mrg shortIdent = Identifier.idPool(anc[]); 831 1.1 mrg } 832 1.1 mrg 833 1.1 mrg auto shortName = shortIdent.toString(); 834 1.1 mrg buf.printf(", %.*s", cast(int) shortName.length, shortName.ptr); 835 1.1 mrg } 836 1.1 mrg 837 1.1 mrg buf.writeByte(')'); 838 1.1 mrg } 839 1.1 mrg } 840 1.1 mrg 841 1.1 mrg /******************************* emitComment **********************************/ 842 1.1 mrg 843 1.1 mrg /** Get leading indentation from 'src' which represents lines of code. */ 844 1.1 mrg private size_t getCodeIndent(const(char)* src) 845 1.1 mrg { 846 1.1 mrg while (src && (*src == '\r' || *src == '\n')) 847 1.1 mrg ++src; // skip until we find the first non-empty line 848 1.1 mrg size_t codeIndent = 0; 849 1.1 mrg while (src && (*src == ' ' || *src == '\t')) 850 1.1 mrg { 851 1.1 mrg codeIndent++; 852 1.1 mrg src++; 853 1.1 mrg } 854 1.1 mrg return codeIndent; 855 1.1 mrg } 856 1.1 mrg 857 1.1 mrg /** Recursively expand template mixin member docs into the scope. */ 858 1.1 mrg private void expandTemplateMixinComments(TemplateMixin tm, ref OutBuffer buf, Scope* sc) 859 1.1 mrg { 860 1.1 mrg if (!tm.semanticRun) 861 1.1 mrg tm.dsymbolSemantic(sc); 862 1.1 mrg TemplateDeclaration td = (tm && tm.tempdecl) ? tm.tempdecl.isTemplateDeclaration() : null; 863 1.1 mrg if (td && td.members) 864 1.1 mrg { 865 1.1 mrg for (size_t i = 0; i < td.members.dim; i++) 866 1.1 mrg { 867 1.1 mrg Dsymbol sm = (*td.members)[i]; 868 1.1 mrg TemplateMixin tmc = sm.isTemplateMixin(); 869 1.1 mrg if (tmc && tmc.comment) 870 1.1 mrg expandTemplateMixinComments(tmc, buf, sc); 871 1.1 mrg else 872 1.1 mrg emitComment(sm, buf, sc); 873 1.1 mrg } 874 1.1 mrg } 875 1.1 mrg } 876 1.1 mrg 877 1.1 mrg private void emitMemberComments(ScopeDsymbol sds, ref OutBuffer buf, Scope* sc) 878 1.1 mrg { 879 1.1 mrg if (!sds.members) 880 1.1 mrg return; 881 1.1 mrg //printf("ScopeDsymbol::emitMemberComments() %s\n", toChars()); 882 1.1 mrg const(char)[] m = "$(DDOC_MEMBERS "; 883 1.1 mrg if (sds.isTemplateDeclaration()) 884 1.1 mrg m = "$(DDOC_TEMPLATE_MEMBERS "; 885 1.1 mrg else if (sds.isClassDeclaration()) 886 1.1 mrg m = "$(DDOC_CLASS_MEMBERS "; 887 1.1 mrg else if (sds.isStructDeclaration()) 888 1.1 mrg m = "$(DDOC_STRUCT_MEMBERS "; 889 1.1 mrg else if (sds.isEnumDeclaration()) 890 1.1 mrg m = "$(DDOC_ENUM_MEMBERS "; 891 1.1 mrg else if (sds.isModule()) 892 1.1 mrg m = "$(DDOC_MODULE_MEMBERS "; 893 1.1 mrg size_t offset1 = buf.length; // save starting offset 894 1.1 mrg buf.writestring(m); 895 1.1 mrg size_t offset2 = buf.length; // to see if we write anything 896 1.1 mrg sc = sc.push(sds); 897 1.1 mrg for (size_t i = 0; i < sds.members.dim; i++) 898 1.1 mrg { 899 1.1 mrg Dsymbol s = (*sds.members)[i]; 900 1.1 mrg //printf("\ts = '%s'\n", s.toChars()); 901 1.1 mrg // only expand if parent is a non-template (semantic won't work) 902 1.1 mrg if (s.comment && s.isTemplateMixin() && s.parent && !s.parent.isTemplateDeclaration()) 903 1.1 mrg expandTemplateMixinComments(cast(TemplateMixin)s, buf, sc); 904 1.1 mrg emitComment(s, buf, sc); 905 1.1 mrg } 906 1.1 mrg emitComment(null, buf, sc); 907 1.1 mrg sc.pop(); 908 1.1 mrg if (buf.length == offset2) 909 1.1 mrg { 910 1.1 mrg /* Didn't write out any members, so back out last write 911 1.1 mrg */ 912 1.1 mrg buf.setsize(offset1); 913 1.1 mrg } 914 1.1 mrg else 915 1.1 mrg buf.writestring(")"); 916 1.1 mrg } 917 1.1 mrg 918 1.1 mrg private void emitVisibility(ref OutBuffer buf, Import i) 919 1.1 mrg { 920 1.1 mrg // imports are private by default, which is different from other declarations 921 1.1 mrg // so they should explicitly show their visibility 922 1.1 mrg emitVisibility(buf, i.visibility); 923 1.1 mrg } 924 1.1 mrg 925 1.1 mrg private void emitVisibility(ref OutBuffer buf, Declaration d) 926 1.1 mrg { 927 1.1 mrg auto vis = d.visibility; 928 1.1 mrg if (vis.kind != Visibility.Kind.undefined && vis.kind != Visibility.Kind.public_) 929 1.1 mrg { 930 1.1 mrg emitVisibility(buf, vis); 931 1.1 mrg } 932 1.1 mrg } 933 1.1 mrg 934 1.1 mrg private void emitVisibility(ref OutBuffer buf, Visibility vis) 935 1.1 mrg { 936 1.1 mrg visibilityToBuffer(&buf, vis); 937 1.1 mrg buf.writeByte(' '); 938 1.1 mrg } 939 1.1 mrg 940 1.1 mrg private void emitComment(Dsymbol s, ref OutBuffer buf, Scope* sc) 941 1.1 mrg { 942 1.1 mrg extern (C++) final class EmitComment : Visitor 943 1.1 mrg { 944 1.1 mrg alias visit = Visitor.visit; 945 1.1 mrg public: 946 1.1 mrg OutBuffer* buf; 947 1.1 mrg Scope* sc; 948 1.1 mrg 949 1.1 mrg extern (D) this(ref OutBuffer buf, Scope* sc) 950 1.1 mrg { 951 1.1 mrg this.buf = &buf; 952 1.1 mrg this.sc = sc; 953 1.1 mrg } 954 1.1 mrg 955 1.1 mrg override void visit(Dsymbol) 956 1.1 mrg { 957 1.1 mrg } 958 1.1 mrg 959 1.1 mrg override void visit(InvariantDeclaration) 960 1.1 mrg { 961 1.1 mrg } 962 1.1 mrg 963 1.1 mrg override void visit(UnitTestDeclaration) 964 1.1 mrg { 965 1.1 mrg } 966 1.1 mrg 967 1.1 mrg override void visit(PostBlitDeclaration) 968 1.1 mrg { 969 1.1 mrg } 970 1.1 mrg 971 1.1 mrg override void visit(DtorDeclaration) 972 1.1 mrg { 973 1.1 mrg } 974 1.1 mrg 975 1.1 mrg override void visit(StaticCtorDeclaration) 976 1.1 mrg { 977 1.1 mrg } 978 1.1 mrg 979 1.1 mrg override void visit(StaticDtorDeclaration) 980 1.1 mrg { 981 1.1 mrg } 982 1.1 mrg 983 1.1 mrg override void visit(TypeInfoDeclaration) 984 1.1 mrg { 985 1.1 mrg } 986 1.1 mrg 987 1.1 mrg void emit(Scope* sc, Dsymbol s, const(char)* com) 988 1.1 mrg { 989 1.1 mrg if (s && sc.lastdc && isDitto(com)) 990 1.1 mrg { 991 1.1 mrg sc.lastdc.a.push(s); 992 1.1 mrg return; 993 1.1 mrg } 994 1.1 mrg // Put previous doc comment if exists 995 1.1 mrg if (DocComment* dc = sc.lastdc) 996 1.1 mrg { 997 1.1 mrg assert(dc.a.dim > 0, "Expects at least one declaration for a" ~ 998 1.1 mrg "documentation comment"); 999 1.1 mrg 1000 1.1 mrg auto symbol = dc.a[0]; 1001 1.1 mrg 1002 1.1 mrg buf.writestring("$(DDOC_MEMBER"); 1003 1.1 mrg buf.writestring("$(DDOC_MEMBER_HEADER"); 1004 1.1 mrg emitAnchor(*buf, symbol, sc, true); 1005 1.1 mrg buf.writeByte(')'); 1006 1.1 mrg 1007 1.1 mrg // Put the declaration signatures as the document 'title' 1008 1.1 mrg buf.writestring(ddoc_decl_s); 1009 1.1 mrg for (size_t i = 0; i < dc.a.dim; i++) 1010 1.1 mrg { 1011 1.1 mrg Dsymbol sx = dc.a[i]; 1012 1.1 mrg // the added linebreaks in here make looking at multiple 1013 1.1 mrg // signatures more appealing 1014 1.1 mrg if (i == 0) 1015 1.1 mrg { 1016 1.1 mrg size_t o = buf.length; 1017 1.1 mrg toDocBuffer(sx, *buf, sc); 1018 1.1 mrg highlightCode(sc, sx, *buf, o); 1019 1.1 mrg buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)"); 1020 1.1 mrg continue; 1021 1.1 mrg } 1022 1.1 mrg buf.writestring("$(DDOC_DITTO "); 1023 1.1 mrg { 1024 1.1 mrg size_t o = buf.length; 1025 1.1 mrg toDocBuffer(sx, *buf, sc); 1026 1.1 mrg highlightCode(sc, sx, *buf, o); 1027 1.1 mrg } 1028 1.1 mrg buf.writestring("$(DDOC_OVERLOAD_SEPARATOR)"); 1029 1.1 mrg buf.writeByte(')'); 1030 1.1 mrg } 1031 1.1 mrg buf.writestring(ddoc_decl_e); 1032 1.1 mrg // Put the ddoc comment as the document 'description' 1033 1.1 mrg buf.writestring(ddoc_decl_dd_s); 1034 1.1 mrg { 1035 1.1 mrg dc.writeSections(sc, &dc.a, buf); 1036 1.1 mrg if (ScopeDsymbol sds = dc.a[0].isScopeDsymbol()) 1037 1.1 mrg emitMemberComments(sds, *buf, sc); 1038 1.1 mrg } 1039 1.1 mrg buf.writestring(ddoc_decl_dd_e); 1040 1.1 mrg buf.writeByte(')'); 1041 1.1 mrg //printf("buf.2 = [[%.*s]]\n", cast(int)(buf.length - o0), buf.data + o0); 1042 1.1 mrg } 1043 1.1 mrg if (s) 1044 1.1 mrg { 1045 1.1 mrg DocComment* dc = DocComment.parse(s, com); 1046 1.1 mrg dc.pmacrotable = &sc._module.macrotable; 1047 1.1 mrg sc.lastdc = dc; 1048 1.1 mrg } 1049 1.1 mrg } 1050 1.1 mrg 1051 1.1 mrg override void visit(Import imp) 1052 1.1 mrg { 1053 1.1 mrg if (imp.visible().kind != Visibility.Kind.public_ && sc.visibility.kind != Visibility.Kind.export_) 1054 1.1 mrg return; 1055 1.1 mrg 1056 1.1 mrg if (imp.comment) 1057 1.1 mrg emit(sc, imp, imp.comment); 1058 1.1 mrg } 1059 1.1 mrg 1060 1.1 mrg override void visit(Declaration d) 1061 1.1 mrg { 1062 1.1 mrg //printf("Declaration::emitComment(%p '%s'), comment = '%s'\n", d, d.toChars(), d.comment); 1063 1.1 mrg //printf("type = %p\n", d.type); 1064 1.1 mrg const(char)* com = d.comment; 1065 1.1 mrg if (TemplateDeclaration td = getEponymousParent(d)) 1066 1.1 mrg { 1067 1.1 mrg if (isDitto(td.comment)) 1068 1.1 mrg com = td.comment; 1069 1.1 mrg else 1070 1.1 mrg com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true); 1071 1.1 mrg } 1072 1.1 mrg else 1073 1.1 mrg { 1074 1.1 mrg if (!d.ident) 1075 1.1 mrg return; 1076 1.1 mrg if (!d.type) 1077 1.1 mrg { 1078 1.1 mrg if (!d.isCtorDeclaration() && 1079 1.1 mrg !d.isAliasDeclaration() && 1080 1.1 mrg !d.isVarDeclaration()) 1081 1.1 mrg { 1082 1.1 mrg return; 1083 1.1 mrg } 1084 1.1 mrg } 1085 1.1 mrg if (d.visibility.kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_) 1086 1.1 mrg return; 1087 1.1 mrg } 1088 1.1 mrg if (!com) 1089 1.1 mrg return; 1090 1.1 mrg emit(sc, d, com); 1091 1.1 mrg } 1092 1.1 mrg 1093 1.1 mrg override void visit(AggregateDeclaration ad) 1094 1.1 mrg { 1095 1.1 mrg //printf("AggregateDeclaration::emitComment() '%s'\n", ad.toChars()); 1096 1.1 mrg const(char)* com = ad.comment; 1097 1.1 mrg if (TemplateDeclaration td = getEponymousParent(ad)) 1098 1.1 mrg { 1099 1.1 mrg if (isDitto(td.comment)) 1100 1.1 mrg com = td.comment; 1101 1.1 mrg else 1102 1.1 mrg com = Lexer.combineComments(td.comment.toDString(), com.toDString(), true); 1103 1.1 mrg } 1104 1.1 mrg else 1105 1.1 mrg { 1106 1.1 mrg if (ad.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_) 1107 1.1 mrg return; 1108 1.1 mrg if (!ad.comment) 1109 1.1 mrg return; 1110 1.1 mrg } 1111 1.1 mrg if (!com) 1112 1.1 mrg return; 1113 1.1 mrg emit(sc, ad, com); 1114 1.1 mrg } 1115 1.1 mrg 1116 1.1 mrg override void visit(TemplateDeclaration td) 1117 1.1 mrg { 1118 1.1 mrg //printf("TemplateDeclaration::emitComment() '%s', kind = %s\n", td.toChars(), td.kind()); 1119 1.1 mrg if (td.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_) 1120 1.1 mrg return; 1121 1.1 mrg if (!td.comment) 1122 1.1 mrg return; 1123 1.1 mrg if (Dsymbol ss = getEponymousMember(td)) 1124 1.1 mrg { 1125 1.1 mrg ss.accept(this); 1126 1.1 mrg return; 1127 1.1 mrg } 1128 1.1 mrg emit(sc, td, td.comment); 1129 1.1 mrg } 1130 1.1 mrg 1131 1.1 mrg override void visit(EnumDeclaration ed) 1132 1.1 mrg { 1133 1.1 mrg if (ed.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_) 1134 1.1 mrg return; 1135 1.1 mrg if (ed.isAnonymous() && ed.members) 1136 1.1 mrg { 1137 1.1 mrg for (size_t i = 0; i < ed.members.dim; i++) 1138 1.1 mrg { 1139 1.1 mrg Dsymbol s = (*ed.members)[i]; 1140 1.1 mrg emitComment(s, *buf, sc); 1141 1.1 mrg } 1142 1.1 mrg return; 1143 1.1 mrg } 1144 1.1 mrg if (!ed.comment) 1145 1.1 mrg return; 1146 1.1 mrg if (ed.isAnonymous()) 1147 1.1 mrg return; 1148 1.1 mrg emit(sc, ed, ed.comment); 1149 1.1 mrg } 1150 1.1 mrg 1151 1.1 mrg override void visit(EnumMember em) 1152 1.1 mrg { 1153 1.1 mrg //printf("EnumMember::emitComment(%p '%s'), comment = '%s'\n", em, em.toChars(), em.comment); 1154 1.1 mrg if (em.visible().kind == Visibility.Kind.private_ || sc.visibility.kind == Visibility.Kind.private_) 1155 1.1 mrg return; 1156 1.1 mrg if (!em.comment) 1157 1.1 mrg return; 1158 1.1 mrg emit(sc, em, em.comment); 1159 1.1 mrg } 1160 1.1 mrg 1161 1.1 mrg override void visit(AttribDeclaration ad) 1162 1.1 mrg { 1163 1.1 mrg //printf("AttribDeclaration::emitComment(sc = %p)\n", sc); 1164 1.1 mrg /* A general problem with this, 1165 1.1 mrg * illustrated by https://issues.dlang.org/show_bug.cgi?id=2516 1166 1.1 mrg * is that attributes are not transmitted through to the underlying 1167 1.1 mrg * member declarations for template bodies, because semantic analysis 1168 1.1 mrg * is not done for template declaration bodies 1169 1.1 mrg * (only template instantiations). 1170 1.1 mrg * Hence, Ddoc omits attributes from template members. 1171 1.1 mrg */ 1172 1.1 mrg Dsymbols* d = ad.include(null); 1173 1.1 mrg if (d) 1174 1.1 mrg { 1175 1.1 mrg for (size_t i = 0; i < d.dim; i++) 1176 1.1 mrg { 1177 1.1 mrg Dsymbol s = (*d)[i]; 1178 1.1 mrg //printf("AttribDeclaration::emitComment %s\n", s.toChars()); 1179 1.1 mrg emitComment(s, *buf, sc); 1180 1.1 mrg } 1181 1.1 mrg } 1182 1.1 mrg } 1183 1.1 mrg 1184 1.1 mrg override void visit(VisibilityDeclaration pd) 1185 1.1 mrg { 1186 1.1 mrg if (pd.decl) 1187 1.1 mrg { 1188 1.1 mrg Scope* scx = sc; 1189 1.1 mrg sc = sc.copy(); 1190 1.1 mrg sc.visibility = pd.visibility; 1191 1.1 mrg visit(cast(AttribDeclaration)pd); 1192 1.1 mrg scx.lastdc = sc.lastdc; 1193 1.1 mrg sc = sc.pop(); 1194 1.1 mrg } 1195 1.1 mrg } 1196 1.1 mrg 1197 1.1 mrg override void visit(ConditionalDeclaration cd) 1198 1.1 mrg { 1199 1.1 mrg //printf("ConditionalDeclaration::emitComment(sc = %p)\n", sc); 1200 1.1 mrg if (cd.condition.inc != Include.notComputed) 1201 1.1 mrg { 1202 1.1 mrg visit(cast(AttribDeclaration)cd); 1203 1.1 mrg return; 1204 1.1 mrg } 1205 1.1 mrg /* If generating doc comment, be careful because if we're inside 1206 1.1 mrg * a template, then include(null) will fail. 1207 1.1 mrg */ 1208 1.1 mrg Dsymbols* d = cd.decl ? cd.decl : cd.elsedecl; 1209 1.1 mrg for (size_t i = 0; i < d.dim; i++) 1210 1.1 mrg { 1211 1.1 mrg Dsymbol s = (*d)[i]; 1212 1.1 mrg emitComment(s, *buf, sc); 1213 1.1 mrg } 1214 1.1 mrg } 1215 1.1 mrg } 1216 1.1 mrg 1217 1.1 mrg scope EmitComment v = new EmitComment(buf, sc); 1218 1.1 mrg if (!s) 1219 1.1 mrg v.emit(sc, null, null); 1220 1.1 mrg else 1221 1.1 mrg s.accept(v); 1222 1.1 mrg } 1223 1.1 mrg 1224 1.1 mrg private void toDocBuffer(Dsymbol s, ref OutBuffer buf, Scope* sc) 1225 1.1 mrg { 1226 1.1 mrg extern (C++) final class ToDocBuffer : Visitor 1227 1.1 mrg { 1228 1.1 mrg alias visit = Visitor.visit; 1229 1.1 mrg public: 1230 1.1 mrg OutBuffer* buf; 1231 1.1 mrg Scope* sc; 1232 1.1 mrg 1233 1.1 mrg extern (D) this(ref OutBuffer buf, Scope* sc) 1234 1.1 mrg { 1235 1.1 mrg this.buf = &buf; 1236 1.1 mrg this.sc = sc; 1237 1.1 mrg } 1238 1.1 mrg 1239 1.1 mrg override void visit(Dsymbol s) 1240 1.1 mrg { 1241 1.1 mrg //printf("Dsymbol::toDocbuffer() %s\n", s.toChars()); 1242 1.1 mrg HdrGenState hgs; 1243 1.1 mrg hgs.ddoc = true; 1244 1.1 mrg .toCBuffer(s, buf, &hgs); 1245 1.1 mrg } 1246 1.1 mrg 1247 1.1 mrg void prefix(Dsymbol s) 1248 1.1 mrg { 1249 1.1 mrg if (s.isDeprecated()) 1250 1.1 mrg buf.writestring("deprecated "); 1251 1.1 mrg if (Declaration d = s.isDeclaration()) 1252 1.1 mrg { 1253 1.1 mrg emitVisibility(*buf, d); 1254 1.1 mrg if (d.isStatic()) 1255 1.1 mrg buf.writestring("static "); 1256 1.1 mrg else if (d.isFinal()) 1257 1.1 mrg buf.writestring("final "); 1258 1.1 mrg else if (d.isAbstract()) 1259 1.1 mrg buf.writestring("abstract "); 1260 1.1 mrg 1261 1.1 mrg if (d.isFuncDeclaration()) // functionToBufferFull handles this 1262 1.1 mrg return; 1263 1.1 mrg 1264 1.1 mrg if (d.isImmutable()) 1265 1.1 mrg buf.writestring("immutable "); 1266 1.1 mrg if (d.storage_class & STC.shared_) 1267 1.1 mrg buf.writestring("shared "); 1268 1.1 mrg if (d.isWild()) 1269 1.1 mrg buf.writestring("inout "); 1270 1.1 mrg if (d.isConst()) 1271 1.1 mrg buf.writestring("const "); 1272 1.1 mrg 1273 1.1 mrg if (d.isSynchronized()) 1274 1.1 mrg buf.writestring("synchronized "); 1275 1.1 mrg 1276 1.1 mrg if (d.storage_class & STC.manifest) 1277 1.1 mrg buf.writestring("enum "); 1278 1.1 mrg 1279 1.1 mrg // Add "auto" for the untyped variable in template members 1280 1.1 mrg if (!d.type && d.isVarDeclaration() && 1281 1.1 mrg !d.isImmutable() && !(d.storage_class & STC.shared_) && !d.isWild() && !d.isConst() && 1282 1.1 mrg !d.isSynchronized()) 1283 1.1 mrg { 1284 1.1 mrg buf.writestring("auto "); 1285 1.1 mrg } 1286 1.1 mrg } 1287 1.1 mrg } 1288 1.1 mrg 1289 1.1 mrg override void visit(Import i) 1290 1.1 mrg { 1291 1.1 mrg HdrGenState hgs; 1292 1.1 mrg hgs.ddoc = true; 1293 1.1 mrg emitVisibility(*buf, i); 1294 1.1 mrg .toCBuffer(i, buf, &hgs); 1295 1.1 mrg } 1296 1.1 mrg 1297 1.1 mrg override void visit(Declaration d) 1298 1.1 mrg { 1299 1.1 mrg if (!d.ident) 1300 1.1 mrg return; 1301 1.1 mrg TemplateDeclaration td = getEponymousParent(d); 1302 1.1 mrg //printf("Declaration::toDocbuffer() %s, originalType = %s, td = %s\n", d.toChars(), d.originalType ? d.originalType.toChars() : "--", td ? td.toChars() : "--"); 1303 1.1 mrg HdrGenState hgs; 1304 1.1 mrg hgs.ddoc = true; 1305 1.1 mrg if (d.isDeprecated()) 1306 1.1 mrg buf.writestring("$(DEPRECATED "); 1307 1.1 mrg prefix(d); 1308 1.1 mrg if (d.type) 1309 1.1 mrg { 1310 1.1 mrg Type origType = d.originalType ? d.originalType : d.type; 1311 1.1 mrg if (origType.ty == Tfunction) 1312 1.1 mrg { 1313 1.1 mrg functionToBufferFull(cast(TypeFunction)origType, buf, d.ident, &hgs, td); 1314 1.1 mrg } 1315 1.1 mrg else 1316 1.1 mrg .toCBuffer(origType, buf, d.ident, &hgs); 1317 1.1 mrg } 1318 1.1 mrg else 1319 1.1 mrg buf.writestring(d.ident.toString()); 1320 1.1 mrg if (d.isVarDeclaration() && td) 1321 1.1 mrg { 1322 1.1 mrg buf.writeByte('('); 1323 1.1 mrg if (td.origParameters && td.origParameters.dim) 1324 1.1 mrg { 1325 1.1 mrg for (size_t i = 0; i < td.origParameters.dim; i++) 1326 1.1 mrg { 1327 1.1 mrg if (i) 1328 1.1 mrg buf.writestring(", "); 1329 1.1 mrg toCBuffer((*td.origParameters)[i], buf, &hgs); 1330 1.1 mrg } 1331 1.1 mrg } 1332 1.1 mrg buf.writeByte(')'); 1333 1.1 mrg } 1334 1.1 mrg // emit constraints if declaration is a templated declaration 1335 1.1 mrg if (td && td.constraint) 1336 1.1 mrg { 1337 1.1 mrg bool noFuncDecl = td.isFuncDeclaration() is null; 1338 1.1 mrg if (noFuncDecl) 1339 1.1 mrg { 1340 1.1 mrg buf.writestring("$(DDOC_CONSTRAINT "); 1341 1.1 mrg } 1342 1.1 mrg 1343 1.1 mrg .toCBuffer(td.constraint, buf, &hgs); 1344 1.1 mrg 1345 1.1 mrg if (noFuncDecl) 1346 1.1 mrg { 1347 1.1 mrg buf.writestring(")"); 1348 1.1 mrg } 1349 1.1 mrg } 1350 1.1 mrg if (d.isDeprecated()) 1351 1.1 mrg buf.writestring(")"); 1352 1.1 mrg buf.writestring(";\n"); 1353 1.1 mrg } 1354 1.1 mrg 1355 1.1 mrg override void visit(AliasDeclaration ad) 1356 1.1 mrg { 1357 1.1 mrg //printf("AliasDeclaration::toDocbuffer() %s\n", ad.toChars()); 1358 1.1 mrg if (!ad.ident) 1359 1.1 mrg return; 1360 1.1 mrg if (ad.isDeprecated()) 1361 1.1 mrg buf.writestring("deprecated "); 1362 1.1 mrg emitVisibility(*buf, ad); 1363 1.1 mrg buf.printf("alias %s = ", ad.toChars()); 1364 1.1 mrg if (Dsymbol s = ad.aliassym) // ident alias 1365 1.1 mrg { 1366 1.1 mrg prettyPrintDsymbol(s, ad.parent); 1367 1.1 mrg } 1368 1.1 mrg else if (Type type = ad.getType()) // type alias 1369 1.1 mrg { 1370 1.1 mrg if (type.ty == Tclass || type.ty == Tstruct || type.ty == Tenum) 1371 1.1 mrg { 1372 1.1 mrg if (Dsymbol s = type.toDsymbol(null)) // elaborate type 1373 1.1 mrg prettyPrintDsymbol(s, ad.parent); 1374 1.1 mrg else 1375 1.1 mrg buf.writestring(type.toChars()); 1376 1.1 mrg } 1377 1.1 mrg else 1378 1.1 mrg { 1379 1.1 mrg // simple type 1380 1.1 mrg buf.writestring(type.toChars()); 1381 1.1 mrg } 1382 1.1 mrg } 1383 1.1 mrg buf.writestring(";\n"); 1384 1.1 mrg } 1385 1.1 mrg 1386 1.1 mrg void parentToBuffer(Dsymbol s) 1387 1.1 mrg { 1388 1.1 mrg if (s && !s.isPackage() && !s.isModule()) 1389 1.1 mrg { 1390 1.1 mrg parentToBuffer(s.parent); 1391 1.1 mrg buf.writestring(s.toChars()); 1392 1.1 mrg buf.writestring("."); 1393 1.1 mrg } 1394 1.1 mrg } 1395 1.1 mrg 1396 1.1 mrg static bool inSameModule(Dsymbol s, Dsymbol p) 1397 1.1 mrg { 1398 1.1 mrg for (; s; s = s.parent) 1399 1.1 mrg { 1400 1.1 mrg if (s.isModule()) 1401 1.1 mrg break; 1402 1.1 mrg } 1403 1.1 mrg for (; p; p = p.parent) 1404 1.1 mrg { 1405 1.1 mrg if (p.isModule()) 1406 1.1 mrg break; 1407 1.1 mrg } 1408 1.1 mrg return s == p; 1409 1.1 mrg } 1410 1.1 mrg 1411 1.1 mrg void prettyPrintDsymbol(Dsymbol s, Dsymbol parent) 1412 1.1 mrg { 1413 1.1 mrg if (s.parent && (s.parent == parent)) // in current scope -> naked name 1414 1.1 mrg { 1415 1.1 mrg buf.writestring(s.toChars()); 1416 1.1 mrg } 1417 1.1 mrg else if (!inSameModule(s, parent)) // in another module -> full name 1418 1.1 mrg { 1419 1.1 mrg buf.writestring(s.toPrettyChars()); 1420 1.1 mrg } 1421 1.1 mrg else // nested in a type in this module -> full name w/o module name 1422 1.1 mrg { 1423 1.1 mrg // if alias is nested in a user-type use module-scope lookup 1424 1.1 mrg if (!parent.isModule() && !parent.isPackage()) 1425 1.1 mrg buf.writestring("."); 1426 1.1 mrg parentToBuffer(s.parent); 1427 1.1 mrg buf.writestring(s.toChars()); 1428 1.1 mrg } 1429 1.1 mrg } 1430 1.1 mrg 1431 1.1 mrg override void visit(AggregateDeclaration ad) 1432 1.1 mrg { 1433 1.1 mrg if (!ad.ident) 1434 1.1 mrg return; 1435 1.1 mrg version (none) 1436 1.1 mrg { 1437 1.1 mrg emitVisibility(buf, ad); 1438 1.1 mrg } 1439 1.1 mrg buf.printf("%s %s", ad.kind(), ad.toChars()); 1440 1.1 mrg buf.writestring(";\n"); 1441 1.1 mrg } 1442 1.1 mrg 1443 1.1 mrg override void visit(StructDeclaration sd) 1444 1.1 mrg { 1445 1.1 mrg //printf("StructDeclaration::toDocbuffer() %s\n", sd.toChars()); 1446 1.1 mrg if (!sd.ident) 1447 1.1 mrg return; 1448 1.1 mrg version (none) 1449 1.1 mrg { 1450 1.1 mrg emitVisibility(buf, sd); 1451 1.1 mrg } 1452 1.1 mrg if (TemplateDeclaration td = getEponymousParent(sd)) 1453 1.1 mrg { 1454 1.1 mrg toDocBuffer(td, *buf, sc); 1455 1.1 mrg } 1456 1.1 mrg else 1457 1.1 mrg { 1458 1.1 mrg buf.printf("%s %s", sd.kind(), sd.toChars()); 1459 1.1 mrg } 1460 1.1 mrg buf.writestring(";\n"); 1461 1.1 mrg } 1462 1.1 mrg 1463 1.1 mrg override void visit(ClassDeclaration cd) 1464 1.1 mrg { 1465 1.1 mrg //printf("ClassDeclaration::toDocbuffer() %s\n", cd.toChars()); 1466 1.1 mrg if (!cd.ident) 1467 1.1 mrg return; 1468 1.1 mrg version (none) 1469 1.1 mrg { 1470 1.1 mrg emitVisibility(*buf, cd); 1471 1.1 mrg } 1472 1.1 mrg if (TemplateDeclaration td = getEponymousParent(cd)) 1473 1.1 mrg { 1474 1.1 mrg toDocBuffer(td, *buf, sc); 1475 1.1 mrg } 1476 1.1 mrg else 1477 1.1 mrg { 1478 1.1 mrg if (!cd.isInterfaceDeclaration() && cd.isAbstract()) 1479 1.1 mrg buf.writestring("abstract "); 1480 1.1 mrg buf.printf("%s %s", cd.kind(), cd.toChars()); 1481 1.1 mrg } 1482 1.1 mrg int any = 0; 1483 1.1 mrg for (size_t i = 0; i < cd.baseclasses.dim; i++) 1484 1.1 mrg { 1485 1.1 mrg BaseClass* bc = (*cd.baseclasses)[i]; 1486 1.1 mrg if (bc.sym && bc.sym.ident == Id.Object) 1487 1.1 mrg continue; 1488 1.1 mrg if (any) 1489 1.1 mrg buf.writestring(", "); 1490 1.1 mrg else 1491 1.1 mrg { 1492 1.1 mrg buf.writestring(": "); 1493 1.1 mrg any = 1; 1494 1.1 mrg } 1495 1.1 mrg 1496 1.1 mrg if (bc.sym) 1497 1.1 mrg { 1498 1.1 mrg buf.printf("$(DDOC_PSUPER_SYMBOL %s)", bc.sym.toPrettyChars()); 1499 1.1 mrg } 1500 1.1 mrg else 1501 1.1 mrg { 1502 1.1 mrg HdrGenState hgs; 1503 1.1 mrg .toCBuffer(bc.type, buf, null, &hgs); 1504 1.1 mrg } 1505 1.1 mrg } 1506 1.1 mrg buf.writestring(";\n"); 1507 1.1 mrg } 1508 1.1 mrg 1509 1.1 mrg override void visit(EnumDeclaration ed) 1510 1.1 mrg { 1511 1.1 mrg if (!ed.ident) 1512 1.1 mrg return; 1513 1.1 mrg buf.printf("%s %s", ed.kind(), ed.toChars()); 1514 1.1 mrg if (ed.memtype) 1515 1.1 mrg { 1516 1.1 mrg buf.writestring(": $(DDOC_ENUM_BASETYPE "); 1517 1.1 mrg HdrGenState hgs; 1518 1.1 mrg .toCBuffer(ed.memtype, buf, null, &hgs); 1519 1.1 mrg buf.writestring(")"); 1520 1.1 mrg } 1521 1.1 mrg buf.writestring(";\n"); 1522 1.1 mrg } 1523 1.1 mrg 1524 1.1 mrg override void visit(EnumMember em) 1525 1.1 mrg { 1526 1.1 mrg if (!em.ident) 1527 1.1 mrg return; 1528 1.1 mrg buf.writestring(em.toChars()); 1529 1.1 mrg } 1530 1.1 mrg } 1531 1.1 mrg 1532 1.1 mrg scope ToDocBuffer v = new ToDocBuffer(buf, sc); 1533 1.1 mrg s.accept(v); 1534 1.1 mrg } 1535 1.1 mrg 1536 1.1 mrg /*********************************************************** 1537 1.1 mrg */ 1538 1.1 mrg struct DocComment 1539 1.1 mrg { 1540 1.1 mrg Sections sections; // Section*[] 1541 1.1 mrg Section summary; 1542 1.1 mrg Section copyright; 1543 1.1 mrg Section macros; 1544 1.1 mrg MacroTable* pmacrotable; 1545 1.1 mrg Escape* escapetable; 1546 1.1 mrg Dsymbols a; 1547 1.1 mrg 1548 1.1 mrg static DocComment* parse(Dsymbol s, const(char)* comment) 1549 1.1 mrg { 1550 1.1 mrg //printf("parse(%s): '%s'\n", s.toChars(), comment); 1551 1.1 mrg auto dc = new DocComment(); 1552 1.1 mrg dc.a.push(s); 1553 1.1 mrg if (!comment) 1554 1.1 mrg return dc; 1555 1.1 mrg dc.parseSections(comment); 1556 1.1 mrg for (size_t i = 0; i < dc.sections.dim; i++) 1557 1.1 mrg { 1558 1.1 mrg Section sec = dc.sections[i]; 1559 1.1 mrg if (iequals("copyright", sec.name)) 1560 1.1 mrg { 1561 1.1 mrg dc.copyright = sec; 1562 1.1 mrg } 1563 1.1 mrg if (iequals("macros", sec.name)) 1564 1.1 mrg { 1565 1.1 mrg dc.macros = sec; 1566 1.1 mrg } 1567 1.1 mrg } 1568 1.1 mrg return dc; 1569 1.1 mrg } 1570 1.1 mrg 1571 1.1 mrg /************************************************ 1572 1.1 mrg * Parse macros out of Macros: section. 1573 1.1 mrg * Macros are of the form: 1574 1.1 mrg * name1 = value1 1575 1.1 mrg * 1576 1.1 mrg * name2 = value2 1577 1.1 mrg */ 1578 1.1 mrg extern(D) static void parseMacros( 1579 1.1 mrg Escape* escapetable, ref MacroTable pmacrotable, const(char)[] m) 1580 1.1 mrg { 1581 1.1 mrg const(char)* p = m.ptr; 1582 1.1 mrg size_t len = m.length; 1583 1.1 mrg const(char)* pend = p + len; 1584 1.1 mrg const(char)* tempstart = null; 1585 1.1 mrg size_t templen = 0; 1586 1.1 mrg const(char)* namestart = null; 1587 1.1 mrg size_t namelen = 0; // !=0 if line continuation 1588 1.1 mrg const(char)* textstart = null; 1589 1.1 mrg size_t textlen = 0; 1590 1.1 mrg while (p < pend) 1591 1.1 mrg { 1592 1.1 mrg // Skip to start of macro 1593 1.1 mrg while (1) 1594 1.1 mrg { 1595 1.1 mrg if (p >= pend) 1596 1.1 mrg goto Ldone; 1597 1.1 mrg switch (*p) 1598 1.1 mrg { 1599 1.1 mrg case ' ': 1600 1.1 mrg case '\t': 1601 1.1 mrg p++; 1602 1.1 mrg continue; 1603 1.1 mrg case '\r': 1604 1.1 mrg case '\n': 1605 1.1 mrg p++; 1606 1.1 mrg goto Lcont; 1607 1.1 mrg default: 1608 1.1 mrg if (isIdStart(p)) 1609 1.1 mrg break; 1610 1.1 mrg if (namelen) 1611 1.1 mrg goto Ltext; // continuation of prev macro 1612 1.1 mrg goto Lskipline; 1613 1.1 mrg } 1614 1.1 mrg break; 1615 1.1 mrg } 1616 1.1 mrg tempstart = p; 1617 1.1 mrg while (1) 1618 1.1 mrg { 1619 1.1 mrg if (p >= pend) 1620 1.1 mrg goto Ldone; 1621 1.1 mrg if (!isIdTail(p)) 1622 1.1 mrg break; 1623 1.1 mrg p += utfStride(p); 1624 1.1 mrg } 1625 1.1 mrg templen = p - tempstart; 1626 1.1 mrg while (1) 1627 1.1 mrg { 1628 1.1 mrg if (p >= pend) 1629 1.1 mrg goto Ldone; 1630 1.1 mrg if (!(*p == ' ' || *p == '\t')) 1631 1.1 mrg break; 1632 1.1 mrg p++; 1633 1.1 mrg } 1634 1.1 mrg if (*p != '=') 1635 1.1 mrg { 1636 1.1 mrg if (namelen) 1637 1.1 mrg goto Ltext; // continuation of prev macro 1638 1.1 mrg goto Lskipline; 1639 1.1 mrg } 1640 1.1 mrg p++; 1641 1.1 mrg if (p >= pend) 1642 1.1 mrg goto Ldone; 1643 1.1 mrg if (namelen) 1644 1.1 mrg { 1645 1.1 mrg // Output existing macro 1646 1.1 mrg L1: 1647 1.1 mrg //printf("macro '%.*s' = '%.*s'\n", cast(int)namelen, namestart, cast(int)textlen, textstart); 1648 1.1 mrg if (iequals("ESCAPES", namestart[0 .. namelen])) 1649 1.1 mrg parseEscapes(escapetable, textstart[0 .. textlen]); 1650 1.1 mrg else 1651 1.1 mrg pmacrotable.define(namestart[0 .. namelen], textstart[0 .. textlen]); 1652 1.1 mrg namelen = 0; 1653 1.1 mrg if (p >= pend) 1654 1.1 mrg break; 1655 1.1 mrg } 1656 1.1 mrg namestart = tempstart; 1657 1.1 mrg namelen = templen; 1658 1.1 mrg while (p < pend && (*p == ' ' || *p == '\t')) 1659 1.1 mrg p++; 1660 1.1 mrg textstart = p; 1661 1.1 mrg Ltext: 1662 1.1 mrg while (p < pend && *p != '\r' && *p != '\n') 1663 1.1 mrg p++; 1664 1.1 mrg textlen = p - textstart; 1665 1.1 mrg p++; 1666 1.1 mrg //printf("p = %p, pend = %p\n", p, pend); 1667 1.1 mrg Lcont: 1668 1.1 mrg continue; 1669 1.1 mrg Lskipline: 1670 1.1 mrg // Ignore this line 1671 1.1 mrg while (p < pend && *p != '\r' && *p != '\n') 1672 1.1 mrg p++; 1673 1.1 mrg } 1674 1.1 mrg Ldone: 1675 1.1 mrg if (namelen) 1676 1.1 mrg goto L1; // write out last one 1677 1.1 mrg } 1678 1.1 mrg 1679 1.1 mrg /************************************** 1680 1.1 mrg * Parse escapes of the form: 1681 1.1 mrg * /c/string/ 1682 1.1 mrg * where c is a single character. 1683 1.1 mrg * Multiple escapes can be separated 1684 1.1 mrg * by whitespace and/or commas. 1685 1.1 mrg */ 1686 1.1 mrg static void parseEscapes(Escape* escapetable, const(char)[] text) 1687 1.1 mrg { 1688 1.1 mrg if (!escapetable) 1689 1.1 mrg { 1690 1.1 mrg escapetable = new Escape(); 1691 1.1 mrg memset(escapetable, 0, Escape.sizeof); 1692 1.1 mrg } 1693 1.1 mrg //printf("parseEscapes('%.*s') pescapetable = %p\n", cast(int)text.length, text.ptr, escapetable); 1694 1.1 mrg const(char)* p = text.ptr; 1695 1.1 mrg const(char)* pend = p + text.length; 1696 1.1 mrg while (1) 1697 1.1 mrg { 1698 1.1 mrg while (1) 1699 1.1 mrg { 1700 1.1 mrg if (p + 4 >= pend) 1701 1.1 mrg return; 1702 1.1 mrg if (!(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',')) 1703 1.1 mrg break; 1704 1.1 mrg p++; 1705 1.1 mrg } 1706 1.1 mrg if (p[0] != '/' || p[2] != '/') 1707 1.1 mrg return; 1708 1.1 mrg char c = p[1]; 1709 1.1 mrg p += 3; 1710 1.1 mrg const(char)* start = p; 1711 1.1 mrg while (1) 1712 1.1 mrg { 1713 1.1 mrg if (p >= pend) 1714 1.1 mrg return; 1715 1.1 mrg if (*p == '/') 1716 1.1 mrg break; 1717 1.1 mrg p++; 1718 1.1 mrg } 1719 1.1 mrg size_t len = p - start; 1720 1.1 mrg char* s = cast(char*)memcpy(mem.xmalloc(len + 1), start, len); 1721 1.1 mrg s[len] = 0; 1722 1.1 mrg escapetable.strings[c] = s[0 .. len]; 1723 1.1 mrg //printf("\t%c = '%s'\n", c, s); 1724 1.1 mrg p++; 1725 1.1 mrg } 1726 1.1 mrg } 1727 1.1 mrg 1728 1.1 mrg /***************************************** 1729 1.1 mrg * Parse next paragraph out of *pcomment. 1730 1.1 mrg * Update *pcomment to point past paragraph. 1731 1.1 mrg * Returns NULL if no more paragraphs. 1732 1.1 mrg * If paragraph ends in 'identifier:', 1733 1.1 mrg * then (*pcomment)[0 .. idlen] is the identifier. 1734 1.1 mrg */ 1735 1.1 mrg void parseSections(const(char)* comment) 1736 1.1 mrg { 1737 1.1 mrg const(char)* p; 1738 1.1 mrg const(char)* pstart; 1739 1.1 mrg const(char)* pend; 1740 1.1 mrg const(char)* idstart = null; // dead-store to prevent spurious warning 1741 1.1 mrg size_t idlen; 1742 1.1 mrg const(char)* name = null; 1743 1.1 mrg size_t namelen = 0; 1744 1.1 mrg //printf("parseSections('%s')\n", comment); 1745 1.1 mrg p = comment; 1746 1.1 mrg while (*p) 1747 1.1 mrg { 1748 1.1 mrg const(char)* pstart0 = p; 1749 1.1 mrg p = skipwhitespace(p); 1750 1.1 mrg pstart = p; 1751 1.1 mrg pend = p; 1752 1.1 mrg 1753 1.1 mrg // Undo indent if starting with a list item 1754 1.1 mrg if ((*p == '-' || *p == '+' || *p == '*') && (*(p+1) == ' ' || *(p+1) == '\t')) 1755 1.1 mrg pstart = pstart0; 1756 1.1 mrg else 1757 1.1 mrg { 1758 1.1 mrg const(char)* pitem = p; 1759 1.1 mrg while (*pitem >= '0' && *pitem <= '9') 1760 1.1 mrg ++pitem; 1761 1.1 mrg if (pitem > p && *pitem == '.' && (*(pitem+1) == ' ' || *(pitem+1) == '\t')) 1762 1.1 mrg pstart = pstart0; 1763 1.1 mrg } 1764 1.1 mrg 1765 1.1 mrg /* Find end of section, which is ended by one of: 1766 1.1 mrg * 'identifier:' (but not inside a code section) 1767 1.1 mrg * '\0' 1768 1.1 mrg */ 1769 1.1 mrg idlen = 0; 1770 1.1 mrg int inCode = 0; 1771 1.1 mrg while (1) 1772 1.1 mrg { 1773 1.1 mrg // Check for start/end of a code section 1774 1.1 mrg if (*p == '-' || *p == '`' || *p == '~') 1775 1.1 mrg { 1776 1.1 mrg char c = *p; 1777 1.1 mrg int numdash = 0; 1778 1.1 mrg while (*p == c) 1779 1.1 mrg { 1780 1.1 mrg ++numdash; 1781 1.1 mrg p++; 1782 1.1 mrg } 1783 1.1 mrg // BUG: handle UTF PS and LS too 1784 1.1 mrg if ((!*p || *p == '\r' || *p == '\n' || (!inCode && c != '-')) && numdash >= 3) 1785 1.1 mrg { 1786 1.1 mrg inCode = inCode == c ? false : c; 1787 1.1 mrg if (inCode) 1788 1.1 mrg { 1789 1.1 mrg // restore leading indentation 1790 1.1 mrg while (pstart0 < pstart && isIndentWS(pstart - 1)) 1791 1.1 mrg --pstart; 1792 1.1 mrg } 1793 1.1 mrg } 1794 1.1 mrg pend = p; 1795 1.1 mrg } 1796 1.1 mrg if (!inCode && isIdStart(p)) 1797 1.1 mrg { 1798 1.1 mrg const(char)* q = p + utfStride(p); 1799 1.1 mrg while (isIdTail(q)) 1800 1.1 mrg q += utfStride(q); 1801 1.1 mrg 1802 1.1 mrg // Detected tag ends it 1803 1.1 mrg if (*q == ':' && isupper(*p) 1804 1.1 mrg && (isspace(q[1]) || q[1] == 0)) 1805 1.1 mrg { 1806 1.1 mrg idlen = q - p; 1807 1.1 mrg idstart = p; 1808 1.1 mrg for (pend = p; pend > pstart; pend--) 1809 1.1 mrg { 1810 1.1 mrg if (pend[-1] == '\n') 1811 1.1 mrg break; 1812 1.1 mrg } 1813 1.1 mrg p = q + 1; 1814 1.1 mrg break; 1815 1.1 mrg } 1816 1.1 mrg } 1817 1.1 mrg while (1) 1818 1.1 mrg { 1819 1.1 mrg if (!*p) 1820 1.1 mrg goto L1; 1821 1.1 mrg if (*p == '\n') 1822 1.1 mrg { 1823 1.1 mrg p++; 1824 1.1 mrg if (*p == '\n' && !summary && !namelen && !inCode) 1825 1.1 mrg { 1826 1.1 mrg pend = p; 1827 1.1 mrg p++; 1828 1.1 mrg goto L1; 1829 1.1 mrg } 1830 1.1 mrg break; 1831 1.1 mrg } 1832 1.1 mrg p++; 1833 1.1 mrg pend = p; 1834 1.1 mrg } 1835 1.1 mrg p = skipwhitespace(p); 1836 1.1 mrg } 1837 1.1 mrg L1: 1838 1.1 mrg if (namelen || pstart < pend) 1839 1.1 mrg { 1840 1.1 mrg Section s; 1841 1.1 mrg if (iequals("Params", name[0 .. namelen])) 1842 1.1 mrg s = new ParamSection(); 1843 1.1 mrg else if (iequals("Macros", name[0 .. namelen])) 1844 1.1 mrg s = new MacroSection(); 1845 1.1 mrg else 1846 1.1 mrg s = new Section(); 1847 1.1 mrg s.name = name[0 .. namelen]; 1848 1.1 mrg s.body_ = pstart[0 .. pend - pstart]; 1849 1.1 mrg s.nooutput = 0; 1850 1.1 mrg //printf("Section: '%.*s' = '%.*s'\n", cast(int)s.namelen, s.name, cast(int)s.bodylen, s.body); 1851 1.1 mrg sections.push(s); 1852 1.1 mrg if (!summary && !namelen) 1853 1.1 mrg summary = s; 1854 1.1 mrg } 1855 1.1 mrg if (idlen) 1856 1.1 mrg { 1857 1.1 mrg name = idstart; 1858 1.1 mrg namelen = idlen; 1859 1.1 mrg } 1860 1.1 mrg else 1861 1.1 mrg { 1862 1.1 mrg name = null; 1863 1.1 mrg namelen = 0; 1864 1.1 mrg if (!*p) 1865 1.1 mrg break; 1866 1.1 mrg } 1867 1.1 mrg } 1868 1.1 mrg } 1869 1.1 mrg 1870 1.1 mrg void writeSections(Scope* sc, Dsymbols* a, OutBuffer* buf) 1871 1.1 mrg { 1872 1.1 mrg assert(a.dim); 1873 1.1 mrg //printf("DocComment::writeSections()\n"); 1874 1.1 mrg Loc loc = (*a)[0].loc; 1875 1.1 mrg if (Module m = (*a)[0].isModule()) 1876 1.1 mrg { 1877 1.1 mrg if (m.md) 1878 1.1 mrg loc = m.md.loc; 1879 1.1 mrg } 1880 1.1 mrg size_t offset1 = buf.length; 1881 1.1 mrg buf.writestring("$(DDOC_SECTIONS "); 1882 1.1 mrg size_t offset2 = buf.length; 1883 1.1 mrg for (size_t i = 0; i < sections.dim; i++) 1884 1.1 mrg { 1885 1.1 mrg Section sec = sections[i]; 1886 1.1 mrg if (sec.nooutput) 1887 1.1 mrg continue; 1888 1.1 mrg //printf("Section: '%.*s' = '%.*s'\n", cast(int)sec.namelen, sec.name, cast(int)sec.bodylen, sec.body); 1889 1.1 mrg if (!sec.name.length && i == 0) 1890 1.1 mrg { 1891 1.1 mrg buf.writestring("$(DDOC_SUMMARY "); 1892 1.1 mrg size_t o = buf.length; 1893 1.1 mrg buf.write(sec.body_); 1894 1.1 mrg escapeStrayParenthesis(loc, buf, o, true); 1895 1.1 mrg highlightText(sc, a, loc, *buf, o); 1896 1.1 mrg buf.writestring(")"); 1897 1.1 mrg } 1898 1.1 mrg else 1899 1.1 mrg sec.write(loc, &this, sc, a, buf); 1900 1.1 mrg } 1901 1.1 mrg for (size_t i = 0; i < a.dim; i++) 1902 1.1 mrg { 1903 1.1 mrg Dsymbol s = (*a)[i]; 1904 1.1 mrg if (Dsymbol td = getEponymousParent(s)) 1905 1.1 mrg s = td; 1906 1.1 mrg for (UnitTestDeclaration utd = s.ddocUnittest; utd; utd = utd.ddocUnittest) 1907 1.1 mrg { 1908 1.1 mrg if (utd.visibility.kind == Visibility.Kind.private_ || !utd.comment || !utd.fbody) 1909 1.1 mrg continue; 1910 1.1 mrg // Strip whitespaces to avoid showing empty summary 1911 1.1 mrg const(char)* c = utd.comment; 1912 1.1 mrg while (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r') 1913 1.1 mrg ++c; 1914 1.1 mrg buf.writestring("$(DDOC_EXAMPLES "); 1915 1.1 mrg size_t o = buf.length; 1916 1.1 mrg buf.writestring(cast(char*)c); 1917 1.1 mrg if (utd.codedoc) 1918 1.1 mrg { 1919 1.1 mrg auto codedoc = utd.codedoc.stripLeadingNewlines; 1920 1.1 mrg size_t n = getCodeIndent(codedoc); 1921 1.1 mrg while (n--) 1922 1.1 mrg buf.writeByte(' '); 1923 1.1 mrg buf.writestring("----\n"); 1924 1.1 mrg buf.writestring(codedoc); 1925 1.1 mrg buf.writestring("----\n"); 1926 1.1 mrg highlightText(sc, a, loc, *buf, o); 1927 1.1 mrg } 1928 1.1 mrg buf.writestring(")"); 1929 1.1 mrg } 1930 1.1 mrg } 1931 1.1 mrg if (buf.length == offset2) 1932 1.1 mrg { 1933 1.1 mrg /* Didn't write out any sections, so back out last write 1934 1.1 mrg */ 1935 1.1 mrg buf.setsize(offset1); 1936 1.1 mrg buf.writestring("\n"); 1937 1.1 mrg } 1938 1.1 mrg else 1939 1.1 mrg buf.writestring(")"); 1940 1.1 mrg } 1941 1.1 mrg } 1942 1.1 mrg 1943 1.1 mrg /***************************************** 1944 1.1 mrg * Return true if comment consists entirely of "ditto". 1945 1.1 mrg */ 1946 1.1 mrg private bool isDitto(const(char)* comment) 1947 1.1 mrg { 1948 1.1 mrg if (comment) 1949 1.1 mrg { 1950 1.1 mrg const(char)* p = skipwhitespace(comment); 1951 1.1 mrg if (Port.memicmp(p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0) 1952 1.1 mrg return true; 1953 1.1 mrg } 1954 1.1 mrg return false; 1955 1.1 mrg } 1956 1.1 mrg 1957 1.1 mrg /********************************************** 1958 1.1 mrg * Skip white space. 1959 1.1 mrg */ 1960 1.1 mrg private const(char)* skipwhitespace(const(char)* p) 1961 1.1 mrg { 1962 1.1 mrg return skipwhitespace(p.toDString).ptr; 1963 1.1 mrg } 1964 1.1 mrg 1965 1.1 mrg /// Ditto 1966 1.1 mrg private const(char)[] skipwhitespace(const(char)[] p) 1967 1.1 mrg { 1968 1.1 mrg foreach (idx, char c; p) 1969 1.1 mrg { 1970 1.1 mrg switch (c) 1971 1.1 mrg { 1972 1.1 mrg case ' ': 1973 1.1 mrg case '\t': 1974 1.1 mrg case '\n': 1975 1.1 mrg continue; 1976 1.1 mrg default: 1977 1.1 mrg return p[idx .. $]; 1978 1.1 mrg } 1979 1.1 mrg } 1980 1.1 mrg return p[$ .. $]; 1981 1.1 mrg } 1982 1.1 mrg 1983 1.1 mrg /************************************************ 1984 1.1 mrg * Scan past all instances of the given characters. 1985 1.1 mrg * Params: 1986 1.1 mrg * buf = an OutBuffer containing the DDoc 1987 1.1 mrg * i = the index within `buf` to start scanning from 1988 1.1 mrg * chars = the characters to skip; order is unimportant 1989 1.1 mrg * Returns: the index after skipping characters. 1990 1.1 mrg */ 1991 1.1 mrg private size_t skipChars(ref OutBuffer buf, size_t i, string chars) 1992 1.1 mrg { 1993 1.1 mrg Outer: 1994 1.1 mrg foreach (j, c; buf[][i..$]) 1995 1.1 mrg { 1996 1.1 mrg foreach (d; chars) 1997 1.1 mrg { 1998 1.1 mrg if (d == c) 1999 1.1 mrg continue Outer; 2000 1.1 mrg } 2001 1.1 mrg return i + j; 2002 1.1 mrg } 2003 1.1 mrg return buf.length; 2004 1.1 mrg } 2005 1.1 mrg 2006 1.1 mrg unittest { 2007 1.1 mrg OutBuffer buf; 2008 1.1 mrg string data = "test ---\r\n\r\nend"; 2009 1.1 mrg buf.write(data); 2010 1.1 mrg 2011 1.1 mrg assert(skipChars(buf, 0, "-") == 0); 2012 1.1 mrg assert(skipChars(buf, 4, "-") == 4); 2013 1.1 mrg assert(skipChars(buf, 4, " -") == 8); 2014 1.1 mrg assert(skipChars(buf, 8, "\r\n") == 12); 2015 1.1 mrg assert(skipChars(buf, 12, "dne") == 15); 2016 1.1 mrg } 2017 1.1 mrg 2018 1.1 mrg /**************************************************** 2019 1.1 mrg * Replace all instances of `c` with `r` in the given string 2020 1.1 mrg * Params: 2021 1.1 mrg * s = the string to do replacements in 2022 1.1 mrg * c = the character to look for 2023 1.1 mrg * r = the string to replace `c` with 2024 1.1 mrg * Returns: `s` with `c` replaced with `r` 2025 1.1 mrg */ 2026 1.1 mrg private inout(char)[] replaceChar(inout(char)[] s, char c, string r) pure 2027 1.1 mrg { 2028 1.1 mrg int count = 0; 2029 1.1 mrg foreach (char sc; s) 2030 1.1 mrg if (sc == c) 2031 1.1 mrg ++count; 2032 1.1 mrg if (count == 0) 2033 1.1 mrg return s; 2034 1.1 mrg 2035 1.1 mrg char[] result; 2036 1.1 mrg result.reserve(s.length - count + (r.length * count)); 2037 1.1 mrg size_t start = 0; 2038 1.1 mrg foreach (i, char sc; s) 2039 1.1 mrg { 2040 1.1 mrg if (sc == c) 2041 1.1 mrg { 2042 1.1 mrg result ~= s[start..i]; 2043 1.1 mrg result ~= r; 2044 1.1 mrg start = i+1; 2045 1.1 mrg } 2046 1.1 mrg } 2047 1.1 mrg result ~= s[start..$]; 2048 1.1 mrg return result; 2049 1.1 mrg } 2050 1.1 mrg 2051 1.1 mrg /// 2052 1.1 mrg unittest 2053 1.1 mrg { 2054 1.1 mrg assert("".replaceChar(',', "$(COMMA)") == ""); 2055 1.1 mrg assert("ab".replaceChar(',', "$(COMMA)") == "ab"); 2056 1.1 mrg assert("a,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)b"); 2057 1.1 mrg assert("a,,b".replaceChar(',', "$(COMMA)") == "a$(COMMA)$(COMMA)b"); 2058 1.1 mrg assert(",ab".replaceChar(',', "$(COMMA)") == "$(COMMA)ab"); 2059 1.1 mrg assert("ab,".replaceChar(',', "$(COMMA)") == "ab$(COMMA)"); 2060 1.1 mrg } 2061 1.1 mrg 2062 1.1 mrg /** 2063 1.1 mrg * Return a lowercased copy of a string. 2064 1.1 mrg * Params: 2065 1.1 mrg * s = the string to lowercase 2066 1.1 mrg * Returns: the lowercase version of the string or the original if already lowercase 2067 1.1 mrg */ 2068 1.1 mrg private string toLowercase(string s) pure 2069 1.1 mrg { 2070 1.1 mrg string lower; 2071 1.1 mrg foreach (size_t i; 0..s.length) 2072 1.1 mrg { 2073 1.1 mrg char c = s[i]; 2074 1.1 mrg // TODO: maybe unicode lowercase, somehow 2075 1.1 mrg if (c >= 'A' && c <= 'Z') 2076 1.1 mrg { 2077 1.1 mrg if (!lower.length) { 2078 1.1 mrg lower.reserve(s.length); 2079 1.1 mrg } 2080 1.1 mrg lower ~= s[lower.length..i]; 2081 1.1 mrg c += 'a' - 'A'; 2082 1.1 mrg lower ~= c; 2083 1.1 mrg } 2084 1.1 mrg } 2085 1.1 mrg if (lower.length) 2086 1.1 mrg lower ~= s[lower.length..$]; 2087 1.1 mrg else 2088 1.1 mrg lower = s; 2089 1.1 mrg return lower; 2090 1.1 mrg } 2091 1.1 mrg 2092 1.1 mrg /// 2093 1.1 mrg unittest 2094 1.1 mrg { 2095 1.1 mrg assert("".toLowercase == ""); 2096 1.1 mrg assert("abc".toLowercase == "abc"); 2097 1.1 mrg assert("ABC".toLowercase == "abc"); 2098 1.1 mrg assert("aBc".toLowercase == "abc"); 2099 1.1 mrg } 2100 1.1 mrg 2101 1.1 mrg /************************************************ 2102 1.1 mrg * Get the indent from one index to another, counting tab stops as four spaces wide 2103 1.1 mrg * per the Markdown spec. 2104 1.1 mrg * Params: 2105 1.1 mrg * buf = an OutBuffer containing the DDoc 2106 1.1 mrg * from = the index within `buf` to start counting from, inclusive 2107 1.1 mrg * to = the index within `buf` to stop counting at, exclusive 2108 1.1 mrg * Returns: the indent 2109 1.1 mrg */ 2110 1.1 mrg private int getMarkdownIndent(ref OutBuffer buf, size_t from, size_t to) 2111 1.1 mrg { 2112 1.1 mrg const slice = buf[]; 2113 1.1 mrg if (to > slice.length) 2114 1.1 mrg to = slice.length; 2115 1.1 mrg int indent = 0; 2116 1.1 mrg foreach (const c; slice[from..to]) 2117 1.1 mrg indent += (c == '\t') ? 4 - (indent % 4) : 1; 2118 1.1 mrg return indent; 2119 1.1 mrg } 2120 1.1 mrg 2121 1.1 mrg /************************************************ 2122 1.1 mrg * Scan forward to one of: 2123 1.1 mrg * start of identifier 2124 1.1 mrg * beginning of next line 2125 1.1 mrg * end of buf 2126 1.1 mrg */ 2127 1.1 mrg size_t skiptoident(ref OutBuffer buf, size_t i) 2128 1.1 mrg { 2129 1.1 mrg const slice = buf[]; 2130 1.1 mrg while (i < slice.length) 2131 1.1 mrg { 2132 1.1 mrg dchar c; 2133 1.1 mrg size_t oi = i; 2134 1.1 mrg if (utf_decodeChar(slice, i, c)) 2135 1.1 mrg { 2136 1.1 mrg /* Ignore UTF errors, but still consume input 2137 1.1 mrg */ 2138 1.1 mrg break; 2139 1.1 mrg } 2140 1.1 mrg if (c >= 0x80) 2141 1.1 mrg { 2142 1.1 mrg if (!isUniAlpha(c)) 2143 1.1 mrg continue; 2144 1.1 mrg } 2145 1.1 mrg else if (!(isalpha(c) || c == '_' || c == '\n')) 2146 1.1 mrg continue; 2147 1.1 mrg i = oi; 2148 1.1 mrg break; 2149 1.1 mrg } 2150 1.1 mrg return i; 2151 1.1 mrg } 2152 1.1 mrg 2153 1.1 mrg /************************************************ 2154 1.1 mrg * Scan forward past end of identifier. 2155 1.1 mrg */ 2156 1.1 mrg private size_t skippastident(ref OutBuffer buf, size_t i) 2157 1.1 mrg { 2158 1.1 mrg const slice = buf[]; 2159 1.1 mrg while (i < slice.length) 2160 1.1 mrg { 2161 1.1 mrg dchar c; 2162 1.1 mrg size_t oi = i; 2163 1.1 mrg if (utf_decodeChar(slice, i, c)) 2164 1.1 mrg { 2165 1.1 mrg /* Ignore UTF errors, but still consume input 2166 1.1 mrg */ 2167 1.1 mrg break; 2168 1.1 mrg } 2169 1.1 mrg if (c >= 0x80) 2170 1.1 mrg { 2171 1.1 mrg if (isUniAlpha(c)) 2172 1.1 mrg continue; 2173 1.1 mrg } 2174 1.1 mrg else if (isalnum(c) || c == '_') 2175 1.1 mrg continue; 2176 1.1 mrg i = oi; 2177 1.1 mrg break; 2178 1.1 mrg } 2179 1.1 mrg return i; 2180 1.1 mrg } 2181 1.1 mrg 2182 1.1 mrg /************************************************ 2183 1.1 mrg * Scan forward past end of an identifier that might 2184 1.1 mrg * contain dots (e.g. `abc.def`) 2185 1.1 mrg */ 2186 1.1 mrg private size_t skipPastIdentWithDots(ref OutBuffer buf, size_t i) 2187 1.1 mrg { 2188 1.1 mrg const slice = buf[]; 2189 1.1 mrg bool lastCharWasDot; 2190 1.1 mrg while (i < slice.length) 2191 1.1 mrg { 2192 1.1 mrg dchar c; 2193 1.1 mrg size_t oi = i; 2194 1.1 mrg if (utf_decodeChar(slice, i, c)) 2195 1.1 mrg { 2196 1.1 mrg /* Ignore UTF errors, but still consume input 2197 1.1 mrg */ 2198 1.1 mrg break; 2199 1.1 mrg } 2200 1.1 mrg if (c == '.') 2201 1.1 mrg { 2202 1.1 mrg // We need to distinguish between `abc.def`, abc..def`, and `abc.` 2203 1.1 mrg // Only `abc.def` is a valid identifier 2204 1.1 mrg 2205 1.1 mrg if (lastCharWasDot) 2206 1.1 mrg { 2207 1.1 mrg i = oi; 2208 1.1 mrg break; 2209 1.1 mrg } 2210 1.1 mrg 2211 1.1 mrg lastCharWasDot = true; 2212 1.1 mrg continue; 2213 1.1 mrg } 2214 1.1 mrg else 2215 1.1 mrg { 2216 1.1 mrg if (c >= 0x80) 2217 1.1 mrg { 2218 1.1 mrg if (isUniAlpha(c)) 2219 1.1 mrg { 2220 1.1 mrg lastCharWasDot = false; 2221 1.1 mrg continue; 2222 1.1 mrg } 2223 1.1 mrg } 2224 1.1 mrg else if (isalnum(c) || c == '_') 2225 1.1 mrg { 2226 1.1 mrg lastCharWasDot = false; 2227 1.1 mrg continue; 2228 1.1 mrg } 2229 1.1 mrg i = oi; 2230 1.1 mrg break; 2231 1.1 mrg } 2232 1.1 mrg } 2233 1.1 mrg 2234 1.1 mrg // if `abc.` 2235 1.1 mrg if (lastCharWasDot) 2236 1.1 mrg return i - 1; 2237 1.1 mrg 2238 1.1 mrg return i; 2239 1.1 mrg } 2240 1.1 mrg 2241 1.1 mrg /************************************************ 2242 1.1 mrg * Scan forward past URL starting at i. 2243 1.1 mrg * We don't want to highlight parts of a URL. 2244 1.1 mrg * Returns: 2245 1.1 mrg * i if not a URL 2246 1.1 mrg * index just past it if it is a URL 2247 1.1 mrg */ 2248 1.1 mrg private size_t skippastURL(ref OutBuffer buf, size_t i) 2249 1.1 mrg { 2250 1.1 mrg const slice = buf[][i .. $]; 2251 1.1 mrg size_t j; 2252 1.1 mrg bool sawdot = false; 2253 1.1 mrg if (slice.length > 7 && Port.memicmp(slice.ptr, "http://", 7) == 0) 2254 1.1 mrg { 2255 1.1 mrg j = 7; 2256 1.1 mrg } 2257 1.1 mrg else if (slice.length > 8 && Port.memicmp(slice.ptr, "https://", 8) == 0) 2258 1.1 mrg { 2259 1.1 mrg j = 8; 2260 1.1 mrg } 2261 1.1 mrg else 2262 1.1 mrg goto Lno; 2263 1.1 mrg for (; j < slice.length; j++) 2264 1.1 mrg { 2265 1.1 mrg const c = slice[j]; 2266 1.1 mrg if (isalnum(c)) 2267 1.1 mrg continue; 2268 1.1 mrg if (c == '-' || c == '_' || c == '?' || c == '=' || c == '%' || 2269 1.1 mrg c == '&' || c == '/' || c == '+' || c == '#' || c == '~') 2270 1.1 mrg continue; 2271 1.1 mrg if (c == '.') 2272 1.1 mrg { 2273 1.1 mrg sawdot = true; 2274 1.1 mrg continue; 2275 1.1 mrg } 2276 1.1 mrg break; 2277 1.1 mrg } 2278 1.1 mrg if (sawdot) 2279 1.1 mrg return i + j; 2280 1.1 mrg Lno: 2281 1.1 mrg return i; 2282 1.1 mrg } 2283 1.1 mrg 2284 1.1 mrg /**************************************************** 2285 1.1 mrg * Remove a previously-inserted blank line macro. 2286 1.1 mrg * Params: 2287 1.1 mrg * buf = an OutBuffer containing the DDoc 2288 1.1 mrg * iAt = the index within `buf` of the start of the `$(DDOC_BLANKLINE)` 2289 1.1 mrg * macro. Upon function return its value is set to `0`. 2290 1.1 mrg * i = an index within `buf`. If `i` is after `iAt` then it gets 2291 1.1 mrg * reduced by the length of the removed macro. 2292 1.1 mrg */ 2293 1.1 mrg private void removeBlankLineMacro(ref OutBuffer buf, ref size_t iAt, ref size_t i) 2294 1.1 mrg { 2295 1.1 mrg if (!iAt) 2296 1.1 mrg return; 2297 1.1 mrg 2298 1.1 mrg enum macroLength = "$(DDOC_BLANKLINE)".length; 2299 1.1 mrg buf.remove(iAt, macroLength); 2300 1.1 mrg if (i > iAt) 2301 1.1 mrg i -= macroLength; 2302 1.1 mrg iAt = 0; 2303 1.1 mrg } 2304 1.1 mrg 2305 1.1 mrg /**************************************************** 2306 1.1 mrg * Attempt to detect and replace a Markdown thematic break (HR). These are three 2307 1.1 mrg * or more of the same delimiter, optionally with spaces or tabs between any of 2308 1.1 mrg * them, e.g. `\n- - -\n` becomes `\n$(HR)\n` 2309 1.1 mrg * Params: 2310 1.1 mrg * buf = an OutBuffer containing the DDoc 2311 1.1 mrg * i = the index within `buf` of the first character of a potential 2312 1.1 mrg * thematic break. If the replacement is made `i` changes to 2313 1.1 mrg * point to the closing parenthesis of the `$(HR)` macro. 2314 1.1 mrg * iLineStart = the index within `buf` that the thematic break's line starts at 2315 1.1 mrg * loc = the current location within the file 2316 1.1 mrg * Returns: whether a thematic break was replaced 2317 1.1 mrg */ 2318 1.1 mrg private bool replaceMarkdownThematicBreak(ref OutBuffer buf, ref size_t i, size_t iLineStart, const ref Loc loc) 2319 1.1 mrg { 2320 1.1 mrg if (!global.params.markdown) 2321 1.1 mrg return false; 2322 1.1 mrg 2323 1.1 mrg const slice = buf[]; 2324 1.1 mrg const c = buf[i]; 2325 1.1 mrg size_t j = i + 1; 2326 1.1 mrg int repeat = 1; 2327 1.1 mrg for (; j < slice.length; j++) 2328 1.1 mrg { 2329 1.1 mrg if (buf[j] == c) 2330 1.1 mrg ++repeat; 2331 1.1 mrg else if (buf[j] != ' ' && buf[j] != '\t') 2332 1.1 mrg break; 2333 1.1 mrg } 2334 1.1 mrg if (repeat >= 3) 2335 1.1 mrg { 2336 1.1 mrg if (j >= buf.length || buf[j] == '\n' || buf[j] == '\r') 2337 1.1 mrg { 2338 1.1 mrg if (global.params.vmarkdown) 2339 1.1 mrg { 2340 1.1 mrg const s = buf[][i..j]; 2341 1.1 mrg message(loc, "Ddoc: converted '%.*s' to a thematic break", cast(int)s.length, s.ptr); 2342 1.1 mrg } 2343 1.1 mrg 2344 1.1 mrg buf.remove(iLineStart, j - iLineStart); 2345 1.1 mrg i = buf.insert(iLineStart, "$(HR)") - 1; 2346 1.1 mrg return true; 2347 1.1 mrg } 2348 1.1 mrg } 2349 1.1 mrg return false; 2350 1.1 mrg } 2351 1.1 mrg 2352 1.1 mrg /**************************************************** 2353 1.1 mrg * Detect the level of an ATX-style heading, e.g. `## This is a heading` would 2354 1.1 mrg * have a level of `2`. 2355 1.1 mrg * Params: 2356 1.1 mrg * buf = an OutBuffer containing the DDoc 2357 1.1 mrg * i = the index within `buf` of the first `#` character 2358 1.1 mrg * Returns: 2359 1.1 mrg * the detected heading level from 1 to 6, or 2360 1.1 mrg * 0 if not at an ATX heading 2361 1.1 mrg */ 2362 1.1 mrg private int detectAtxHeadingLevel(ref OutBuffer buf, const size_t i) 2363 1.1 mrg { 2364 1.1 mrg if (!global.params.markdown) 2365 1.1 mrg return 0; 2366 1.1 mrg 2367 1.1 mrg const iHeadingStart = i; 2368 1.1 mrg const iAfterHashes = skipChars(buf, i, "#"); 2369 1.1 mrg const headingLevel = cast(int) (iAfterHashes - iHeadingStart); 2370 1.1 mrg if (headingLevel > 6) 2371 1.1 mrg return 0; 2372 1.1 mrg 2373 1.1 mrg const iTextStart = skipChars(buf, iAfterHashes, " \t"); 2374 1.1 mrg const emptyHeading = buf[iTextStart] == '\r' || buf[iTextStart] == '\n'; 2375 1.1 mrg 2376 1.1 mrg // require whitespace 2377 1.1 mrg if (!emptyHeading && iTextStart == iAfterHashes) 2378 1.1 mrg return 0; 2379 1.1 mrg 2380 1.1 mrg return headingLevel; 2381 1.1 mrg } 2382 1.1 mrg 2383 1.1 mrg /**************************************************** 2384 1.1 mrg * Remove any trailing `##` suffix from an ATX-style heading. 2385 1.1 mrg * Params: 2386 1.1 mrg * buf = an OutBuffer containing the DDoc 2387 1.1 mrg * i = the index within `buf` to start looking for a suffix at 2388 1.1 mrg */ 2389 1.1 mrg private void removeAnyAtxHeadingSuffix(ref OutBuffer buf, size_t i) 2390 1.1 mrg { 2391 1.1 mrg size_t j = i; 2392 1.1 mrg size_t iSuffixStart = 0; 2393 1.1 mrg size_t iWhitespaceStart = j; 2394 1.1 mrg const slice = buf[]; 2395 1.1 mrg for (; j < slice.length; j++) 2396 1.1 mrg { 2397 1.1 mrg switch (slice[j]) 2398 1.1 mrg { 2399 1.1 mrg case '#': 2400 1.1 mrg if (iWhitespaceStart && !iSuffixStart) 2401 1.1 mrg iSuffixStart = j; 2402 1.1 mrg continue; 2403 1.1 mrg case ' ': 2404 1.1 mrg case '\t': 2405 1.1 mrg if (!iWhitespaceStart) 2406 1.1 mrg iWhitespaceStart = j; 2407 1.1 mrg continue; 2408 1.1 mrg case '\r': 2409 1.1 mrg case '\n': 2410 1.1 mrg break; 2411 1.1 mrg default: 2412 1.1 mrg iSuffixStart = 0; 2413 1.1 mrg iWhitespaceStart = 0; 2414 1.1 mrg continue; 2415 1.1 mrg } 2416 1.1 mrg break; 2417 1.1 mrg } 2418 1.1 mrg if (iSuffixStart) 2419 1.1 mrg buf.remove(iWhitespaceStart, j - iWhitespaceStart); 2420 1.1 mrg } 2421 1.1 mrg 2422 1.1 mrg /**************************************************** 2423 1.1 mrg * Wrap text in a Markdown heading macro, e.g. `$(H2 heading text`). 2424 1.1 mrg * Params: 2425 1.1 mrg * buf = an OutBuffer containing the DDoc 2426 1.1 mrg * iStart = the index within `buf` that the Markdown heading starts at 2427 1.1 mrg * iEnd = the index within `buf` of the character after the last 2428 1.1 mrg * heading character. Is incremented by the length of the 2429 1.1 mrg * inserted heading macro when this function ends. 2430 1.1 mrg * loc = the location of the Ddoc within the file 2431 1.1 mrg * headingLevel = the level (1-6) of heading to end. Is set to `0` when this 2432 1.1 mrg * function ends. 2433 1.1 mrg */ 2434 1.1 mrg private void endMarkdownHeading(ref OutBuffer buf, size_t iStart, ref size_t iEnd, const ref Loc loc, ref int headingLevel) 2435 1.1 mrg { 2436 1.1 mrg if (!global.params.markdown) 2437 1.1 mrg return; 2438 1.1 mrg if (global.params.vmarkdown) 2439 1.1 mrg { 2440 1.1 mrg const s = buf[][iStart..iEnd]; 2441 1.1 mrg message(loc, "Ddoc: added heading '%.*s'", cast(int)s.length, s.ptr); 2442 1.1 mrg } 2443 1.1 mrg 2444 1.1 mrg char[5] heading = "$(H0 "; 2445 1.1 mrg heading[3] = cast(char) ('0' + headingLevel); 2446 1.1 mrg buf.insert(iStart, heading); 2447 1.1 mrg iEnd += 5; 2448 1.1 mrg size_t iBeforeNewline = iEnd; 2449 1.1 mrg while (buf[iBeforeNewline-1] == '\r' || buf[iBeforeNewline-1] == '\n') 2450 1.1 mrg --iBeforeNewline; 2451 1.1 mrg buf.insert(iBeforeNewline, ")"); 2452 1.1 mrg headingLevel = 0; 2453 1.1 mrg } 2454 1.1 mrg 2455 1.1 mrg /**************************************************** 2456 1.1 mrg * End all nested Markdown quotes, if inside any. 2457 1.1 mrg * Params: 2458 1.1 mrg * buf = an OutBuffer containing the DDoc 2459 1.1 mrg * i = the index within `buf` of the character after the quote text. 2460 1.1 mrg * quoteLevel = the current quote level. Is set to `0` when this function ends. 2461 1.1 mrg * Returns: the amount that `i` was moved 2462 1.1 mrg */ 2463 1.1 mrg private size_t endAllMarkdownQuotes(ref OutBuffer buf, size_t i, ref int quoteLevel) 2464 1.1 mrg { 2465 1.1 mrg const length = quoteLevel; 2466 1.1 mrg for (; quoteLevel > 0; --quoteLevel) 2467 1.1 mrg i = buf.insert(i, ")"); 2468 1.1 mrg return length; 2469 1.1 mrg } 2470 1.1 mrg 2471 1.1 mrg /**************************************************** 2472 1.1 mrg * Convenience function to end all Markdown lists and quotes, if inside any, and 2473 1.1 mrg * set `quoteMacroLevel` to `0`. 2474 1.1 mrg * Params: 2475 1.1 mrg * buf = an OutBuffer containing the DDoc 2476 1.1 mrg * i = the index within `buf` of the character after the list and/or 2477 1.1 mrg * quote text. Is adjusted when this function ends if any lists 2478 1.1 mrg * and/or quotes were ended. 2479 1.1 mrg * nestedLists = a set of nested lists. Upon return it will be empty. 2480 1.1 mrg * quoteLevel = the current quote level. Is set to `0` when this function ends. 2481 1.1 mrg * quoteMacroLevel = the macro level that the quote was started at. Is set to 2482 1.1 mrg * `0` when this function ends. 2483 1.1 mrg * Returns: the amount that `i` was moved 2484 1.1 mrg */ 2485 1.1 mrg private size_t endAllListsAndQuotes(ref OutBuffer buf, ref size_t i, ref MarkdownList[] nestedLists, ref int quoteLevel, out int quoteMacroLevel) 2486 1.1 mrg { 2487 1.1 mrg quoteMacroLevel = 0; 2488 1.1 mrg const i0 = i; 2489 1.1 mrg i += MarkdownList.endAllNestedLists(buf, i, nestedLists); 2490 1.1 mrg i += endAllMarkdownQuotes(buf, i, quoteLevel); 2491 1.1 mrg return i - i0; 2492 1.1 mrg } 2493 1.1 mrg 2494 1.1 mrg /**************************************************** 2495 1.1 mrg * Replace Markdown emphasis with the appropriate macro, 2496 1.1 mrg * e.g. `*very* **nice**` becomes `$(EM very) $(STRONG nice)`. 2497 1.1 mrg * Params: 2498 1.1 mrg * buf = an OutBuffer containing the DDoc 2499 1.1 mrg * loc = the current location within the file 2500 1.1 mrg * inlineDelimiters = the collection of delimiters found within a paragraph. When this function returns its length will be reduced to `downToLevel`. 2501 1.1 mrg * downToLevel = the length within `inlineDelimiters`` to reduce emphasis to 2502 1.1 mrg * Returns: the number of characters added to the buffer by the replacements 2503 1.1 mrg */ 2504 1.1 mrg private size_t replaceMarkdownEmphasis(ref OutBuffer buf, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, int downToLevel = 0) 2505 1.1 mrg { 2506 1.1 mrg if (!global.params.markdown) 2507 1.1 mrg return 0; 2508 1.1 mrg 2509 1.1 mrg size_t replaceEmphasisPair(ref MarkdownDelimiter start, ref MarkdownDelimiter end) 2510 1.1 mrg { 2511 1.1 mrg immutable count = start.count == 1 || end.count == 1 ? 1 : 2; 2512 1.1 mrg 2513 1.1 mrg size_t iStart = start.iStart; 2514 1.1 mrg size_t iEnd = end.iStart; 2515 1.1 mrg end.count -= count; 2516 1.1 mrg start.count -= count; 2517 1.1 mrg iStart += start.count; 2518 1.1 mrg 2519 1.1 mrg if (!start.count) 2520 1.1 mrg start.type = 0; 2521 1.1 mrg if (!end.count) 2522 1.1 mrg end.type = 0; 2523 1.1 mrg 2524 1.1 mrg if (global.params.vmarkdown) 2525 1.1 mrg { 2526 1.1 mrg const s = buf[][iStart + count..iEnd]; 2527 1.1 mrg message(loc, "Ddoc: emphasized text '%.*s'", cast(int)s.length, s.ptr); 2528 1.1 mrg } 2529 1.1 mrg 2530 1.1 mrg buf.remove(iStart, count); 2531 1.1 mrg iEnd -= count; 2532 1.1 mrg buf.remove(iEnd, count); 2533 1.1 mrg 2534 1.1 mrg string macroName = count >= 2 ? "$(STRONG " : "$(EM "; 2535 1.1 mrg buf.insert(iEnd, ")"); 2536 1.1 mrg buf.insert(iStart, macroName); 2537 1.1 mrg 2538 1.1 mrg const delta = 1 + macroName.length - (count + count); 2539 1.1 mrg end.iStart += count; 2540 1.1 mrg return delta; 2541 1.1 mrg } 2542 1.1 mrg 2543 1.1 mrg size_t delta = 0; 2544 1.1 mrg int start = (cast(int) inlineDelimiters.length) - 1; 2545 1.1 mrg while (start >= downToLevel) 2546 1.1 mrg { 2547 1.1 mrg // find start emphasis 2548 1.1 mrg while (start >= downToLevel && 2549 1.1 mrg (inlineDelimiters[start].type != '*' || !inlineDelimiters[start].leftFlanking)) 2550 1.1 mrg --start; 2551 1.1 mrg if (start < downToLevel) 2552 1.1 mrg break; 2553 1.1 mrg 2554 1.1 mrg // find the nearest end emphasis 2555 1.1 mrg int end = start + 1; 2556 1.1 mrg while (end < inlineDelimiters.length && 2557 1.1 mrg (inlineDelimiters[end].type != inlineDelimiters[start].type || 2558 1.1 mrg inlineDelimiters[end].macroLevel != inlineDelimiters[start].macroLevel || 2559 1.1 mrg !inlineDelimiters[end].rightFlanking)) 2560 1.1 mrg ++end; 2561 1.1 mrg if (end == inlineDelimiters.length) 2562 1.1 mrg { 2563 1.1 mrg // the start emphasis has no matching end; if it isn't an end itself then kill it 2564 1.1 mrg if (!inlineDelimiters[start].rightFlanking) 2565 1.1 mrg inlineDelimiters[start].type = 0; 2566 1.1 mrg --start; 2567 1.1 mrg continue; 2568 1.1 mrg } 2569 1.1 mrg 2570 1.1 mrg // multiple-of-3 rule 2571 1.1 mrg if (((inlineDelimiters[start].leftFlanking && inlineDelimiters[start].rightFlanking) || 2572 1.1 mrg (inlineDelimiters[end].leftFlanking && inlineDelimiters[end].rightFlanking)) && 2573 1.1 mrg (inlineDelimiters[start].count + inlineDelimiters[end].count) % 3 == 0) 2574 1.1 mrg { 2575 1.1 mrg --start; 2576 1.1 mrg continue; 2577 1.1 mrg } 2578 1.1 mrg 2579 1.1 mrg immutable delta0 = replaceEmphasisPair(inlineDelimiters[start], inlineDelimiters[end]); 2580 1.1 mrg 2581 1.1 mrg for (; end < inlineDelimiters.length; ++end) 2582 1.1 mrg inlineDelimiters[end].iStart += delta0; 2583 1.1 mrg delta += delta0; 2584 1.1 mrg } 2585 1.1 mrg 2586 1.1 mrg inlineDelimiters.length = downToLevel; 2587 1.1 mrg return delta; 2588 1.1 mrg } 2589 1.1 mrg 2590 1.1 mrg /**************************************************** 2591 1.1 mrg */ 2592 1.1 mrg private bool isIdentifier(Dsymbols* a, const(char)* p, size_t len) 2593 1.1 mrg { 2594 1.1 mrg foreach (member; *a) 2595 1.1 mrg { 2596 1.1 mrg if (auto imp = member.isImport()) 2597 1.1 mrg { 2598 1.1 mrg // For example: `public import str = core.stdc.string;` 2599 1.1 mrg // This checks if `p` is equal to `str` 2600 1.1 mrg if (imp.aliasId) 2601 1.1 mrg { 2602 1.1 mrg if (p[0 .. len] == imp.aliasId.toString()) 2603 1.1 mrg return true; 2604 1.1 mrg } 2605 1.1 mrg else 2606 1.1 mrg { 2607 1.1 mrg // The general case: `public import core.stdc.string;` 2608 1.1 mrg 2609 1.1 mrg // fully qualify imports so `core.stdc.string` doesn't appear as `core` 2610 1.1 mrg string fullyQualifiedImport; 2611 1.1 mrg foreach (const pid; imp.packages) 2612 1.1 mrg { 2613 1.1 mrg fullyQualifiedImport ~= pid.toString() ~ "."; 2614 1.1 mrg } 2615 1.1 mrg fullyQualifiedImport ~= imp.id.toString(); 2616 1.1 mrg 2617 1.1 mrg // Check if `p` == `core.stdc.string` 2618 1.1 mrg if (p[0 .. len] == fullyQualifiedImport) 2619 1.1 mrg return true; 2620 1.1 mrg } 2621 1.1 mrg } 2622 1.1 mrg else if (member.ident) 2623 1.1 mrg { 2624 1.1 mrg if (p[0 .. len] == member.ident.toString()) 2625 1.1 mrg return true; 2626 1.1 mrg } 2627 1.1 mrg 2628 1.1 mrg } 2629 1.1 mrg return false; 2630 1.1 mrg } 2631 1.1 mrg 2632 1.1 mrg /**************************************************** 2633 1.1 mrg */ 2634 1.1 mrg private bool isKeyword(const(char)* p, size_t len) 2635 1.1 mrg { 2636 1.1 mrg immutable string[3] table = ["true", "false", "null"]; 2637 1.1 mrg foreach (s; table) 2638 1.1 mrg { 2639 1.1 mrg if (p[0 .. len] == s) 2640 1.1 mrg return true; 2641 1.1 mrg } 2642 1.1 mrg return false; 2643 1.1 mrg } 2644 1.1 mrg 2645 1.1 mrg /**************************************************** 2646 1.1 mrg */ 2647 1.1 mrg private TypeFunction isTypeFunction(Dsymbol s) 2648 1.1 mrg { 2649 1.1 mrg FuncDeclaration f = s.isFuncDeclaration(); 2650 1.1 mrg /* f.type may be NULL for template members. 2651 1.1 mrg */ 2652 1.1 mrg if (f && f.type) 2653 1.1 mrg { 2654 1.1 mrg Type t = f.originalType ? f.originalType : f.type; 2655 1.1 mrg if (t.ty == Tfunction) 2656 1.1 mrg return cast(TypeFunction)t; 2657 1.1 mrg } 2658 1.1 mrg return null; 2659 1.1 mrg } 2660 1.1 mrg 2661 1.1 mrg /**************************************************** 2662 1.1 mrg */ 2663 1.1 mrg private Parameter isFunctionParameter(Dsymbol s, const(char)* p, size_t len) 2664 1.1 mrg { 2665 1.1 mrg TypeFunction tf = isTypeFunction(s); 2666 1.1 mrg if (tf && tf.parameterList.parameters) 2667 1.1 mrg { 2668 1.1 mrg foreach (fparam; *tf.parameterList.parameters) 2669 1.1 mrg { 2670 1.1 mrg if (fparam.ident && p[0 .. len] == fparam.ident.toString()) 2671 1.1 mrg { 2672 1.1 mrg return fparam; 2673 1.1 mrg } 2674 1.1 mrg } 2675 1.1 mrg } 2676 1.1 mrg return null; 2677 1.1 mrg } 2678 1.1 mrg 2679 1.1 mrg /**************************************************** 2680 1.1 mrg */ 2681 1.1 mrg private Parameter isFunctionParameter(Dsymbols* a, const(char)* p, size_t len) 2682 1.1 mrg { 2683 1.1 mrg for (size_t i = 0; i < a.dim; i++) 2684 1.1 mrg { 2685 1.1 mrg Parameter fparam = isFunctionParameter((*a)[i], p, len); 2686 1.1 mrg if (fparam) 2687 1.1 mrg { 2688 1.1 mrg return fparam; 2689 1.1 mrg } 2690 1.1 mrg } 2691 1.1 mrg return null; 2692 1.1 mrg } 2693 1.1 mrg 2694 1.1 mrg /**************************************************** 2695 1.1 mrg */ 2696 1.1 mrg private Parameter isEponymousFunctionParameter(Dsymbols *a, const(char) *p, size_t len) 2697 1.1 mrg { 2698 1.1 mrg for (size_t i = 0; i < a.dim; i++) 2699 1.1 mrg { 2700 1.1 mrg TemplateDeclaration td = (*a)[i].isTemplateDeclaration(); 2701 1.1 mrg if (td && td.onemember) 2702 1.1 mrg { 2703 1.1 mrg /* Case 1: we refer to a template declaration inside the template 2704 1.1 mrg 2705 1.1 mrg /// ...ddoc... 2706 1.1 mrg template case1(T) { 2707 1.1 mrg void case1(R)() {} 2708 1.1 mrg } 2709 1.1 mrg */ 2710 1.1 mrg td = td.onemember.isTemplateDeclaration(); 2711 1.1 mrg } 2712 1.1 mrg if (!td) 2713 1.1 mrg { 2714 1.1 mrg /* Case 2: we're an alias to a template declaration 2715 1.1 mrg 2716 1.1 mrg /// ...ddoc... 2717 1.1 mrg alias case2 = case1!int; 2718 1.1 mrg */ 2719 1.1 mrg AliasDeclaration ad = (*a)[i].isAliasDeclaration(); 2720 1.1 mrg if (ad && ad.aliassym) 2721 1.1 mrg { 2722 1.1 mrg td = ad.aliassym.isTemplateDeclaration(); 2723 1.1 mrg } 2724 1.1 mrg } 2725 1.1 mrg while (td) 2726 1.1 mrg { 2727 1.1 mrg Dsymbol sym = getEponymousMember(td); 2728 1.1 mrg if (sym) 2729 1.1 mrg { 2730 1.1 mrg Parameter fparam = isFunctionParameter(sym, p, len); 2731 1.1 mrg if (fparam) 2732 1.1 mrg { 2733 1.1 mrg return fparam; 2734 1.1 mrg } 2735 1.1 mrg } 2736 1.1 mrg td = td.overnext; 2737 1.1 mrg } 2738 1.1 mrg } 2739 1.1 mrg return null; 2740 1.1 mrg } 2741 1.1 mrg 2742 1.1 mrg /**************************************************** 2743 1.1 mrg */ 2744 1.1 mrg private TemplateParameter isTemplateParameter(Dsymbols* a, const(char)* p, size_t len) 2745 1.1 mrg { 2746 1.1 mrg for (size_t i = 0; i < a.dim; i++) 2747 1.1 mrg { 2748 1.1 mrg TemplateDeclaration td = (*a)[i].isTemplateDeclaration(); 2749 1.1 mrg // Check for the parent, if the current symbol is not a template declaration. 2750 1.1 mrg if (!td) 2751 1.1 mrg td = getEponymousParent((*a)[i]); 2752 1.1 mrg if (td && td.origParameters) 2753 1.1 mrg { 2754 1.1 mrg foreach (tp; *td.origParameters) 2755 1.1 mrg { 2756 1.1 mrg if (tp.ident && p[0 .. len] == tp.ident.toString()) 2757 1.1 mrg { 2758 1.1 mrg return tp; 2759 1.1 mrg } 2760 1.1 mrg } 2761 1.1 mrg } 2762 1.1 mrg } 2763 1.1 mrg return null; 2764 1.1 mrg } 2765 1.1 mrg 2766 1.1 mrg /**************************************************** 2767 1.1 mrg * Return true if str is a reserved symbol name 2768 1.1 mrg * that starts with a double underscore. 2769 1.1 mrg */ 2770 1.1 mrg private bool isReservedName(const(char)[] str) 2771 1.1 mrg { 2772 1.1 mrg immutable string[] table = 2773 1.1 mrg [ 2774 1.1 mrg "__ctor", 2775 1.1 mrg "__dtor", 2776 1.1 mrg "__postblit", 2777 1.1 mrg "__invariant", 2778 1.1 mrg "__unitTest", 2779 1.1 mrg "__require", 2780 1.1 mrg "__ensure", 2781 1.1 mrg "__dollar", 2782 1.1 mrg "__ctfe", 2783 1.1 mrg "__withSym", 2784 1.1 mrg "__result", 2785 1.1 mrg "__returnLabel", 2786 1.1 mrg "__vptr", 2787 1.1 mrg "__monitor", 2788 1.1 mrg "__gate", 2789 1.1 mrg "__xopEquals", 2790 1.1 mrg "__xopCmp", 2791 1.1 mrg "__LINE__", 2792 1.1 mrg "__FILE__", 2793 1.1 mrg "__MODULE__", 2794 1.1 mrg "__FUNCTION__", 2795 1.1 mrg "__PRETTY_FUNCTION__", 2796 1.1 mrg "__DATE__", 2797 1.1 mrg "__TIME__", 2798 1.1 mrg "__TIMESTAMP__", 2799 1.1 mrg "__VENDOR__", 2800 1.1 mrg "__VERSION__", 2801 1.1 mrg "__EOF__", 2802 1.1 mrg "__CXXLIB__", 2803 1.1 mrg "__LOCAL_SIZE", 2804 1.1 mrg "__entrypoint", 2805 1.1 mrg ]; 2806 1.1 mrg foreach (s; table) 2807 1.1 mrg { 2808 1.1 mrg if (str == s) 2809 1.1 mrg return true; 2810 1.1 mrg } 2811 1.1 mrg return false; 2812 1.1 mrg } 2813 1.1 mrg 2814 1.1 mrg /**************************************************** 2815 1.1 mrg * A delimiter for Markdown inline content like emphasis and links. 2816 1.1 mrg */ 2817 1.1 mrg private struct MarkdownDelimiter 2818 1.1 mrg { 2819 1.1 mrg size_t iStart; /// the index where this delimiter starts 2820 1.1 mrg int count; /// the length of this delimeter's start sequence 2821 1.1 mrg int macroLevel; /// the count of nested DDoc macros when the delimiter is started 2822 1.1 mrg bool leftFlanking; /// whether the delimiter is left-flanking, as defined by the CommonMark spec 2823 1.1 mrg bool rightFlanking; /// whether the delimiter is right-flanking, as defined by the CommonMark spec 2824 1.1 mrg bool atParagraphStart; /// whether the delimiter is at the start of a paragraph 2825 1.1 mrg char type; /// the type of delimiter, defined by its starting character 2826 1.1 mrg 2827 1.1 mrg /// whether this describes a valid delimiter 2828 1.1 mrg @property bool isValid() const { return count != 0; } 2829 1.1 mrg 2830 1.1 mrg /// flag this delimiter as invalid 2831 1.1 mrg void invalidate() { count = 0; } 2832 1.1 mrg } 2833 1.1 mrg 2834 1.1 mrg /**************************************************** 2835 1.1 mrg * Info about a Markdown list. 2836 1.1 mrg */ 2837 1.1 mrg private struct MarkdownList 2838 1.1 mrg { 2839 1.1 mrg string orderedStart; /// an optional start number--if present then the list starts at this number 2840 1.1 mrg size_t iStart; /// the index where the list item starts 2841 1.1 mrg size_t iContentStart; /// the index where the content starts after the list delimiter 2842 1.1 mrg int delimiterIndent; /// the level of indent the list delimiter starts at 2843 1.1 mrg int contentIndent; /// the level of indent the content starts at 2844 1.1 mrg int macroLevel; /// the count of nested DDoc macros when the list is started 2845 1.1 mrg char type; /// the type of list, defined by its starting character 2846 1.1 mrg 2847 1.1 mrg /// whether this describes a valid list 2848 1.1 mrg @property bool isValid() const { return type != type.init; } 2849 1.1 mrg 2850 1.1 mrg /**************************************************** 2851 1.1 mrg * Try to parse a list item, returning whether successful. 2852 1.1 mrg * Params: 2853 1.1 mrg * buf = an OutBuffer containing the DDoc 2854 1.1 mrg * iLineStart = the index within `buf` of the first character of the line 2855 1.1 mrg * i = the index within `buf` of the potential list item 2856 1.1 mrg * Returns: the parsed list item. Its `isValid` property describes whether parsing succeeded. 2857 1.1 mrg */ 2858 1.1 mrg static MarkdownList parseItem(ref OutBuffer buf, size_t iLineStart, size_t i) 2859 1.1 mrg { 2860 1.1 mrg if (!global.params.markdown) 2861 1.1 mrg return MarkdownList(); 2862 1.1 mrg 2863 1.1 mrg if (buf[i] == '+' || buf[i] == '-' || buf[i] == '*') 2864 1.1 mrg return parseUnorderedListItem(buf, iLineStart, i); 2865 1.1 mrg else 2866 1.1 mrg return parseOrderedListItem(buf, iLineStart, i); 2867 1.1 mrg } 2868 1.1 mrg 2869 1.1 mrg /**************************************************** 2870 1.1 mrg * Return whether the context is at a list item of the same type as this list. 2871 1.1 mrg * Params: 2872 1.1 mrg * buf = an OutBuffer containing the DDoc 2873 1.1 mrg * iLineStart = the index within `buf` of the first character of the line 2874 1.1 mrg * i = the index within `buf` of the list item 2875 1.1 mrg * Returns: whether `i` is at a list item of the same type as this list 2876 1.1 mrg */ 2877 1.1 mrg private bool isAtItemInThisList(ref OutBuffer buf, size_t iLineStart, size_t i) 2878 1.1 mrg { 2879 1.1 mrg MarkdownList item = (type == '.' || type == ')') ? 2880 1.1 mrg parseOrderedListItem(buf, iLineStart, i) : 2881 1.1 mrg parseUnorderedListItem(buf, iLineStart, i); 2882 1.1 mrg if (item.type == type) 2883 1.1 mrg return item.delimiterIndent < contentIndent && item.contentIndent > delimiterIndent; 2884 1.1 mrg return false; 2885 1.1 mrg } 2886 1.1 mrg 2887 1.1 mrg /**************************************************** 2888 1.1 mrg * Start a Markdown list item by creating/deleting nested lists and starting the item. 2889 1.1 mrg * Params: 2890 1.1 mrg * buf = an OutBuffer containing the DDoc 2891 1.1 mrg * iLineStart = the index within `buf` of the first character of the line. If this function succeeds it will be adjuested to equal `i`. 2892 1.1 mrg * i = the index within `buf` of the list item. If this function succeeds `i` will be adjusted to fit the inserted macro. 2893 1.1 mrg * iPrecedingBlankLine = the index within `buf` of the preceeding blank line. If non-zero and a new list was started, the preceeding blank line is removed and this value is set to `0`. 2894 1.1 mrg * nestedLists = a set of nested lists. If this function succeeds it may contain a new nested list. 2895 1.1 mrg * loc = the location of the Ddoc within the file 2896 1.1 mrg * Returns: `true` if a list was created 2897 1.1 mrg */ 2898 1.1 mrg bool startItem(ref OutBuffer buf, ref size_t iLineStart, ref size_t i, ref size_t iPrecedingBlankLine, ref MarkdownList[] nestedLists, const ref Loc loc) 2899 1.1 mrg { 2900 1.1 mrg buf.remove(iStart, iContentStart - iStart); 2901 1.1 mrg 2902 1.1 mrg if (!nestedLists.length || 2903 1.1 mrg delimiterIndent >= nestedLists[$-1].contentIndent || 2904 1.1 mrg buf[iLineStart - 4..iLineStart] == "$(LI") 2905 1.1 mrg { 2906 1.1 mrg // start a list macro 2907 1.1 mrg nestedLists ~= this; 2908 1.1 mrg if (type == '.') 2909 1.1 mrg { 2910 1.1 mrg if (orderedStart.length) 2911 1.1 mrg { 2912 1.1 mrg iStart = buf.insert(iStart, "$(OL_START "); 2913 1.1 mrg iStart = buf.insert(iStart, orderedStart); 2914 1.1 mrg iStart = buf.insert(iStart, ",\n"); 2915 1.1 mrg } 2916 1.1 mrg else 2917 1.1 mrg iStart = buf.insert(iStart, "$(OL\n"); 2918 1.1 mrg } 2919 1.1 mrg else 2920 1.1 mrg iStart = buf.insert(iStart, "$(UL\n"); 2921 1.1 mrg 2922 1.1 mrg removeBlankLineMacro(buf, iPrecedingBlankLine, iStart); 2923 1.1 mrg } 2924 1.1 mrg else if (nestedLists.length) 2925 1.1 mrg { 2926 1.1 mrg nestedLists[$-1].delimiterIndent = delimiterIndent; 2927 1.1 mrg nestedLists[$-1].contentIndent = contentIndent; 2928 1.1 mrg } 2929 1.1 mrg 2930 1.1 mrg iStart = buf.insert(iStart, "$(LI\n"); 2931 1.1 mrg i = iStart - 1; 2932 1.1 mrg iLineStart = i; 2933 1.1 mrg 2934 1.1 mrg if (global.params.vmarkdown) 2935 1.1 mrg { 2936 1.1 mrg size_t iEnd = iStart; 2937 1.1 mrg while (iEnd < buf.length && buf[iEnd] != '\r' && buf[iEnd] != '\n') 2938 1.1 mrg ++iEnd; 2939 1.1 mrg const s = buf[][iStart..iEnd]; 2940 1.1 mrg message(loc, "Ddoc: starting list item '%.*s'", cast(int)s.length, s.ptr); 2941 1.1 mrg } 2942 1.1 mrg 2943 1.1 mrg return true; 2944 1.1 mrg } 2945 1.1 mrg 2946 1.1 mrg /**************************************************** 2947 1.1 mrg * End all nested Markdown lists. 2948 1.1 mrg * Params: 2949 1.1 mrg * buf = an OutBuffer containing the DDoc 2950 1.1 mrg * i = the index within `buf` to end lists at. 2951 1.1 mrg * nestedLists = a set of nested lists. Upon return it will be empty. 2952 1.1 mrg * Returns: the amount that `i` changed 2953 1.1 mrg */ 2954 1.1 mrg static size_t endAllNestedLists(ref OutBuffer buf, size_t i, ref MarkdownList[] nestedLists) 2955 1.1 mrg { 2956 1.1 mrg const iStart = i; 2957 1.1 mrg for (; nestedLists.length; --nestedLists.length) 2958 1.1 mrg i = buf.insert(i, ")\n)"); 2959 1.1 mrg return i - iStart; 2960 1.1 mrg } 2961 1.1 mrg 2962 1.1 mrg /**************************************************** 2963 1.1 mrg * Look for a sibling list item or the end of nested list(s). 2964 1.1 mrg * Params: 2965 1.1 mrg * buf = an OutBuffer containing the DDoc 2966 1.1 mrg * i = the index within `buf` to end lists at. If there was a sibling or ending lists `i` will be adjusted to fit the macro endings. 2967 1.1 mrg * iParagraphStart = the index within `buf` to start the next paragraph at at. May be adjusted upon return. 2968 1.1 mrg * nestedLists = a set of nested lists. Some nested lists may have been removed from it upon return. 2969 1.1 mrg */ 2970 1.1 mrg static void handleSiblingOrEndingList(ref OutBuffer buf, ref size_t i, ref size_t iParagraphStart, ref MarkdownList[] nestedLists) 2971 1.1 mrg { 2972 1.1 mrg size_t iAfterSpaces = skipChars(buf, i + 1, " \t"); 2973 1.1 mrg 2974 1.1 mrg if (nestedLists[$-1].isAtItemInThisList(buf, i + 1, iAfterSpaces)) 2975 1.1 mrg { 2976 1.1 mrg // end a sibling list item 2977 1.1 mrg i = buf.insert(i, ")"); 2978 1.1 mrg iParagraphStart = skipChars(buf, i, " \t\r\n"); 2979 1.1 mrg } 2980 1.1 mrg else if (iAfterSpaces >= buf.length || (buf[iAfterSpaces] != '\r' && buf[iAfterSpaces] != '\n')) 2981 1.1 mrg { 2982 1.1 mrg // end nested lists that are indented more than this content 2983 1.1 mrg const indent = getMarkdownIndent(buf, i + 1, iAfterSpaces); 2984 1.1 mrg while (nestedLists.length && nestedLists[$-1].contentIndent > indent) 2985 1.1 mrg { 2986 1.1 mrg i = buf.insert(i, ")\n)"); 2987 1.1 mrg --nestedLists.length; 2988 1.1 mrg iParagraphStart = skipChars(buf, i, " \t\r\n"); 2989 1.1 mrg 2990 1.1 mrg if (nestedLists.length && nestedLists[$-1].isAtItemInThisList(buf, i + 1, iParagraphStart)) 2991 1.1 mrg { 2992 1.1 mrg i = buf.insert(i, ")"); 2993 1.1 mrg ++iParagraphStart; 2994 1.1 mrg break; 2995 1.1 mrg } 2996 1.1 mrg } 2997 1.1 mrg } 2998 1.1 mrg } 2999 1.1 mrg 3000 1.1 mrg /**************************************************** 3001 1.1 mrg * Parse an unordered list item at the current position 3002 1.1 mrg * Params: 3003 1.1 mrg * buf = an OutBuffer containing the DDoc 3004 1.1 mrg * iLineStart = the index within `buf` of the first character of the line 3005 1.1 mrg * i = the index within `buf` of the list item 3006 1.1 mrg * Returns: the parsed list item, or a list item with type `.init` if no list item is available 3007 1.1 mrg */ 3008 1.1 mrg private static MarkdownList parseUnorderedListItem(ref OutBuffer buf, size_t iLineStart, size_t i) 3009 1.1 mrg { 3010 1.1 mrg if (i+1 < buf.length && 3011 1.1 mrg (buf[i] == '-' || 3012 1.1 mrg buf[i] == '*' || 3013 1.1 mrg buf[i] == '+') && 3014 1.1 mrg (buf[i+1] == ' ' || 3015 1.1 mrg buf[i+1] == '\t' || 3016 1.1 mrg buf[i+1] == '\r' || 3017 1.1 mrg buf[i+1] == '\n')) 3018 1.1 mrg { 3019 1.1 mrg const iContentStart = skipChars(buf, i + 1, " \t"); 3020 1.1 mrg const delimiterIndent = getMarkdownIndent(buf, iLineStart, i); 3021 1.1 mrg const contentIndent = getMarkdownIndent(buf, iLineStart, iContentStart); 3022 1.1 mrg auto list = MarkdownList(null, iLineStart, iContentStart, delimiterIndent, contentIndent, 0, buf[i]); 3023 1.1 mrg return list; 3024 1.1 mrg } 3025 1.1 mrg return MarkdownList(); 3026 1.1 mrg } 3027 1.1 mrg 3028 1.1 mrg /**************************************************** 3029 1.1 mrg * Parse an ordered list item at the current position 3030 1.1 mrg * Params: 3031 1.1 mrg * buf = an OutBuffer containing the DDoc 3032 1.1 mrg * iLineStart = the index within `buf` of the first character of the line 3033 1.1 mrg * i = the index within `buf` of the list item 3034 1.1 mrg * Returns: the parsed list item, or a list item with type `.init` if no list item is available 3035 1.1 mrg */ 3036 1.1 mrg private static MarkdownList parseOrderedListItem(ref OutBuffer buf, size_t iLineStart, size_t i) 3037 1.1 mrg { 3038 1.1 mrg size_t iAfterNumbers = skipChars(buf, i, "0123456789"); 3039 1.1 mrg if (iAfterNumbers - i > 0 && 3040 1.1 mrg iAfterNumbers - i <= 9 && 3041 1.1 mrg iAfterNumbers + 1 < buf.length && 3042 1.1 mrg buf[iAfterNumbers] == '.' && 3043 1.1 mrg (buf[iAfterNumbers+1] == ' ' || 3044 1.1 mrg buf[iAfterNumbers+1] == '\t' || 3045 1.1 mrg buf[iAfterNumbers+1] == '\r' || 3046 1.1 mrg buf[iAfterNumbers+1] == '\n')) 3047 1.1 mrg { 3048 1.1 mrg const iContentStart = skipChars(buf, iAfterNumbers + 1, " \t"); 3049 1.1 mrg const delimiterIndent = getMarkdownIndent(buf, iLineStart, i); 3050 1.1 mrg const contentIndent = getMarkdownIndent(buf, iLineStart, iContentStart); 3051 1.1 mrg size_t iNumberStart = skipChars(buf, i, "0"); 3052 1.1 mrg if (iNumberStart == iAfterNumbers) 3053 1.1 mrg --iNumberStart; 3054 1.1 mrg auto orderedStart = buf[][iNumberStart .. iAfterNumbers]; 3055 1.1 mrg if (orderedStart == "1") 3056 1.1 mrg orderedStart = null; 3057 1.1 mrg return MarkdownList(orderedStart.idup, iLineStart, iContentStart, delimiterIndent, contentIndent, 0, buf[iAfterNumbers]); 3058 1.1 mrg } 3059 1.1 mrg return MarkdownList(); 3060 1.1 mrg } 3061 1.1 mrg } 3062 1.1 mrg 3063 1.1 mrg /**************************************************** 3064 1.1 mrg * A Markdown link. 3065 1.1 mrg */ 3066 1.1 mrg private struct MarkdownLink 3067 1.1 mrg { 3068 1.1 mrg string href; /// the link destination 3069 1.1 mrg string title; /// an optional title for the link 3070 1.1 mrg string label; /// an optional label for the link 3071 1.1 mrg Dsymbol symbol; /// an optional symbol to link to 3072 1.1 mrg 3073 1.1 mrg /**************************************************** 3074 1.1 mrg * Replace a Markdown link or link definition in the form of: 3075 1.1 mrg * - Inline link: `[foo](url/ 'optional title')` 3076 1.1 mrg * - Reference link: `[foo][bar]`, `[foo][]` or `[foo]` 3077 1.1 mrg * - Link reference definition: `[bar]: url/ 'optional title'` 3078 1.1 mrg * Params: 3079 1.1 mrg * buf = an OutBuffer containing the DDoc 3080 1.1 mrg * i = the index within `buf` that points to the `]` character of the potential link. 3081 1.1 mrg * If this function succeeds it will be adjusted to fit the inserted link macro. 3082 1.1 mrg * loc = the current location within the file 3083 1.1 mrg * inlineDelimiters = previously parsed Markdown delimiters, including emphasis and link/image starts 3084 1.1 mrg * delimiterIndex = the index within `inlineDelimiters` of the nearest link/image starting delimiter 3085 1.1 mrg * linkReferences = previously parsed link references. When this function returns it may contain 3086 1.1 mrg * additional previously unparsed references. 3087 1.1 mrg * Returns: whether a reference link was found and replaced at `i` 3088 1.1 mrg */ 3089 1.1 mrg static bool replaceLink(ref OutBuffer buf, ref size_t i, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, int delimiterIndex, ref MarkdownLinkReferences linkReferences) 3090 1.1 mrg { 3091 1.1 mrg const delimiter = inlineDelimiters[delimiterIndex]; 3092 1.1 mrg MarkdownLink link; 3093 1.1 mrg 3094 1.1 mrg size_t iEnd = link.parseReferenceDefinition(buf, i, delimiter); 3095 1.1 mrg if (iEnd > i) 3096 1.1 mrg { 3097 1.1 mrg i = delimiter.iStart; 3098 1.1 mrg link.storeAndReplaceDefinition(buf, i, iEnd, linkReferences, loc); 3099 1.1 mrg inlineDelimiters.length = delimiterIndex; 3100 1.1 mrg return true; 3101 1.1 mrg } 3102 1.1 mrg 3103 1.1 mrg iEnd = link.parseInlineLink(buf, i); 3104 1.1 mrg if (iEnd == i) 3105 1.1 mrg { 3106 1.1 mrg iEnd = link.parseReferenceLink(buf, i, delimiter); 3107 1.1 mrg if (iEnd > i) 3108 1.1 mrg { 3109 1.1 mrg const label = link.label; 3110 1.1 mrg link = linkReferences.lookupReference(label, buf, i, loc); 3111 1.1 mrg // check rightFlanking to avoid replacing things like int[string] 3112 1.1 mrg if (!link.href.length && !delimiter.rightFlanking) 3113 1.1 mrg link = linkReferences.lookupSymbol(label); 3114 1.1 mrg if (!link.href.length) 3115 1.1 mrg return false; 3116 1.1 mrg } 3117 1.1 mrg } 3118 1.1 mrg 3119 1.1 mrg if (iEnd == i) 3120 1.1 mrg return false; 3121 1.1 mrg 3122 1.1 mrg immutable delta = replaceMarkdownEmphasis(buf, loc, inlineDelimiters, delimiterIndex); 3123 1.1 mrg iEnd += delta; 3124 1.1 mrg i += delta; 3125 1.1 mrg 3126 1.1 mrg if (global.params.vmarkdown) 3127 1.1 mrg { 3128 1.1 mrg const s = buf[][delimiter.iStart..iEnd]; 3129 1.1 mrg message(loc, "Ddoc: linking '%.*s' to '%.*s'", cast(int)s.length, s.ptr, cast(int)link.href.length, link.href.ptr); 3130 1.1 mrg } 3131 1.1 mrg 3132 1.1 mrg link.replaceLink(buf, i, iEnd, delimiter); 3133 1.1 mrg return true; 3134 1.1 mrg } 3135 1.1 mrg 3136 1.1 mrg /**************************************************** 3137 1.1 mrg * Replace a Markdown link definition in the form of `[bar]: url/ 'optional title'` 3138 1.1 mrg * Params: 3139 1.1 mrg * buf = an OutBuffer containing the DDoc 3140 1.1 mrg * i = the index within `buf` that points to the `]` character of the potential link. 3141 1.1 mrg * If this function succeeds it will be adjusted to fit the inserted link macro. 3142 1.1 mrg * inlineDelimiters = previously parsed Markdown delimiters, including emphasis and link/image starts 3143 1.1 mrg * delimiterIndex = the index within `inlineDelimiters` of the nearest link/image starting delimiter 3144 1.1 mrg * linkReferences = previously parsed link references. When this function returns it may contain 3145 1.1 mrg * additional previously unparsed references. 3146 1.1 mrg * loc = the current location in the file 3147 1.1 mrg * Returns: whether a reference link was found and replaced at `i` 3148 1.1 mrg */ 3149 1.1 mrg static bool replaceReferenceDefinition(ref OutBuffer buf, ref size_t i, ref MarkdownDelimiter[] inlineDelimiters, int delimiterIndex, ref MarkdownLinkReferences linkReferences, const ref Loc loc) 3150 1.1 mrg { 3151 1.1 mrg const delimiter = inlineDelimiters[delimiterIndex]; 3152 1.1 mrg MarkdownLink link; 3153 1.1 mrg size_t iEnd = link.parseReferenceDefinition(buf, i, delimiter); 3154 1.1 mrg if (iEnd == i) 3155 1.1 mrg return false; 3156 1.1 mrg 3157 1.1 mrg i = delimiter.iStart; 3158 1.1 mrg link.storeAndReplaceDefinition(buf, i, iEnd, linkReferences, loc); 3159 1.1 mrg inlineDelimiters.length = delimiterIndex; 3160 1.1 mrg return true; 3161 1.1 mrg } 3162 1.1 mrg 3163 1.1 mrg /**************************************************** 3164 1.1 mrg * Parse a Markdown inline link in the form of `[foo](url/ 'optional title')` 3165 1.1 mrg * Params: 3166 1.1 mrg * buf = an OutBuffer containing the DDoc 3167 1.1 mrg * i = the index within `buf` that points to the `]` character of the inline link. 3168 1.1 mrg * Returns: the index at the end of parsing the link, or `i` if parsing failed. 3169 1.1 mrg */ 3170 1.1 mrg private size_t parseInlineLink(ref OutBuffer buf, size_t i) 3171 1.1 mrg { 3172 1.1 mrg size_t iEnd = i + 1; 3173 1.1 mrg if (iEnd >= buf.length || buf[iEnd] != '(') 3174 1.1 mrg return i; 3175 1.1 mrg ++iEnd; 3176 1.1 mrg 3177 1.1 mrg if (!parseHref(buf, iEnd)) 3178 1.1 mrg return i; 3179 1.1 mrg 3180 1.1 mrg iEnd = skipChars(buf, iEnd, " \t\r\n"); 3181 1.1 mrg if (buf[iEnd] != ')') 3182 1.1 mrg { 3183 1.1 mrg if (parseTitle(buf, iEnd)) 3184 1.1 mrg iEnd = skipChars(buf, iEnd, " \t\r\n"); 3185 1.1 mrg } 3186 1.1 mrg 3187 1.1 mrg if (buf[iEnd] != ')') 3188 1.1 mrg return i; 3189 1.1 mrg 3190 1.1 mrg return iEnd + 1; 3191 1.1 mrg } 3192 1.1 mrg 3193 1.1 mrg /**************************************************** 3194 1.1 mrg * Parse a Markdown reference link in the form of `[foo][bar]`, `[foo][]` or `[foo]` 3195 1.1 mrg * Params: 3196 1.1 mrg * buf = an OutBuffer containing the DDoc 3197 1.1 mrg * i = the index within `buf` that points to the `]` character of the inline link. 3198 1.1 mrg * delimiter = the delimiter that starts this link 3199 1.1 mrg * Returns: the index at the end of parsing the link, or `i` if parsing failed. 3200 1.1 mrg */ 3201 1.1 mrg private size_t parseReferenceLink(ref OutBuffer buf, size_t i, MarkdownDelimiter delimiter) 3202 1.1 mrg { 3203 1.1 mrg size_t iStart = i + 1; 3204 1.1 mrg size_t iEnd = iStart; 3205 1.1 mrg if (iEnd >= buf.length || buf[iEnd] != '[' || (iEnd+1 < buf.length && buf[iEnd+1] == ']')) 3206 1.1 mrg { 3207 1.1 mrg // collapsed reference [foo][] or shortcut reference [foo] 3208 1.1 mrg iStart = delimiter.iStart + delimiter.count - 1; 3209 1.1 mrg if (buf[iEnd] == '[') 3210 1.1 mrg iEnd += 2; 3211 1.1 mrg } 3212 1.1 mrg 3213 1.1 mrg parseLabel(buf, iStart); 3214 1.1 mrg if (!label.length) 3215 1.1 mrg return i; 3216 1.1 mrg 3217 1.1 mrg if (iEnd < iStart) 3218 1.1 mrg iEnd = iStart; 3219 1.1 mrg return iEnd; 3220 1.1 mrg } 3221 1.1 mrg 3222 1.1 mrg /**************************************************** 3223 1.1 mrg * Parse a Markdown reference definition in the form of `[bar]: url/ 'optional title'` 3224 1.1 mrg * Params: 3225 1.1 mrg * buf = an OutBuffer containing the DDoc 3226 1.1 mrg * i = the index within `buf` that points to the `]` character of the inline link. 3227 1.1 mrg * delimiter = the delimiter that starts this link 3228 1.1 mrg * Returns: the index at the end of parsing the link, or `i` if parsing failed. 3229 1.1 mrg */ 3230 1.1 mrg private size_t parseReferenceDefinition(ref OutBuffer buf, size_t i, MarkdownDelimiter delimiter) 3231 1.1 mrg { 3232 1.1 mrg if (!delimiter.atParagraphStart || delimiter.type != '[' || 3233 1.1 mrg i+1 >= buf.length || buf[i+1] != ':') 3234 1.1 mrg return i; 3235 1.1 mrg 3236 1.1 mrg size_t iEnd = delimiter.iStart; 3237 1.1 mrg parseLabel(buf, iEnd); 3238 1.1 mrg if (label.length == 0 || iEnd != i + 1) 3239 1.1 mrg return i; 3240 1.1 mrg 3241 1.1 mrg ++iEnd; 3242 1.1 mrg iEnd = skipChars(buf, iEnd, " \t"); 3243 1.1 mrg skipOneNewline(buf, iEnd); 3244 1.1 mrg 3245 1.1 mrg if (!parseHref(buf, iEnd) || href.length == 0) 3246 1.1 mrg return i; 3247 1.1 mrg 3248 1.1 mrg iEnd = skipChars(buf, iEnd, " \t"); 3249 1.1 mrg const requireNewline = !skipOneNewline(buf, iEnd); 3250 1.1 mrg const iBeforeTitle = iEnd; 3251 1.1 mrg 3252 1.1 mrg if (parseTitle(buf, iEnd)) 3253 1.1 mrg { 3254 1.1 mrg iEnd = skipChars(buf, iEnd, " \t"); 3255 1.1 mrg if (iEnd < buf.length && buf[iEnd] != '\r' && buf[iEnd] != '\n') 3256 1.1 mrg { 3257 1.1 mrg // the title must end with a newline 3258 1.1 mrg title.length = 0; 3259 1.1 mrg iEnd = iBeforeTitle; 3260 1.1 mrg } 3261 1.1 mrg } 3262 1.1 mrg 3263 1.1 mrg iEnd = skipChars(buf, iEnd, " \t"); 3264 1.1 mrg if (requireNewline && iEnd < buf.length-1 && buf[iEnd] != '\r' && buf[iEnd] != '\n') 3265 1.1 mrg return i; 3266 1.1 mrg 3267 1.1 mrg return iEnd; 3268 1.1 mrg } 3269 1.1 mrg 3270 1.1 mrg /**************************************************** 3271 1.1 mrg * Parse and normalize a Markdown reference label 3272 1.1 mrg * Params: 3273 1.1 mrg * buf = an OutBuffer containing the DDoc 3274 1.1 mrg * i = the index within `buf` that points to the `[` character at the start of the label. 3275 1.1 mrg * If this function returns a non-empty label then `i` will point just after the ']' at the end of the label. 3276 1.1 mrg * Returns: the parsed and normalized label, possibly empty 3277 1.1 mrg */ 3278 1.1 mrg private bool parseLabel(ref OutBuffer buf, ref size_t i) 3279 1.1 mrg { 3280 1.1 mrg if (buf[i] != '[') 3281 1.1 mrg return false; 3282 1.1 mrg 3283 1.1 mrg const slice = buf[]; 3284 1.1 mrg size_t j = i + 1; 3285 1.1 mrg 3286 1.1 mrg // Some labels have already been en-symboled; handle that 3287 1.1 mrg const inSymbol = j+15 < slice.length && slice[j..j+15] == "$(DDOC_PSYMBOL "; 3288 1.1 mrg if (inSymbol) 3289 1.1 mrg j += 15; 3290 1.1 mrg 3291 1.1 mrg for (; j < slice.length; ++j) 3292 1.1 mrg { 3293 1.1 mrg const c = slice[j]; 3294 1.1 mrg switch (c) 3295 1.1 mrg { 3296 1.1 mrg case ' ': 3297 1.1 mrg case '\t': 3298 1.1 mrg case '\r': 3299 1.1 mrg case '\n': 3300 1.1 mrg if (label.length && label[$-1] != ' ') 3301 1.1 mrg label ~= ' '; 3302 1.1 mrg break; 3303 1.1 mrg case ')': 3304 1.1 mrg if (inSymbol && j+1 < slice.length && slice[j+1] == ']') 3305 1.1 mrg { 3306 1.1 mrg ++j; 3307 1.1 mrg goto case ']'; 3308 1.1 mrg } 3309 1.1 mrg goto default; 3310 1.1 mrg case '[': 3311 1.1 mrg if (slice[j-1] != '\\') 3312 1.1 mrg { 3313 1.1 mrg label.length = 0; 3314 1.1 mrg return false; 3315 1.1 mrg } 3316 1.1 mrg break; 3317 1.1 mrg case ']': 3318 1.1 mrg if (label.length && label[$-1] == ' ') 3319 1.1 mrg --label.length; 3320 1.1 mrg if (label.length) 3321 1.1 mrg { 3322 1.1 mrg i = j + 1; 3323 1.1 mrg return true; 3324 1.1 mrg } 3325 1.1 mrg return false; 3326 1.1 mrg default: 3327 1.1 mrg label ~= c; 3328 1.1 mrg break; 3329 1.1 mrg } 3330 1.1 mrg } 3331 1.1 mrg label.length = 0; 3332 1.1 mrg return false; 3333 1.1 mrg } 3334 1.1 mrg 3335 1.1 mrg /**************************************************** 3336 1.1 mrg * Parse and store a Markdown link URL, optionally enclosed in `<>` brackets 3337 1.1 mrg * Params: 3338 1.1 mrg * buf = an OutBuffer containing the DDoc 3339 1.1 mrg * i = the index within `buf` that points to the first character of the URL. 3340 1.1 mrg * If this function succeeds `i` will point just after the the end of the URL. 3341 1.1 mrg * Returns: whether a URL was found and parsed 3342 1.1 mrg */ 3343 1.1 mrg private bool parseHref(ref OutBuffer buf, ref size_t i) 3344 1.1 mrg { 3345 1.1 mrg size_t j = skipChars(buf, i, " \t"); 3346 1.1 mrg 3347 1.1 mrg size_t iHrefStart = j; 3348 1.1 mrg size_t parenDepth = 1; 3349 1.1 mrg bool inPointy = false; 3350 1.1 mrg const slice = buf[]; 3351 1.1 mrg for (; j < slice.length; j++) 3352 1.1 mrg { 3353 1.1 mrg switch (slice[j]) 3354 1.1 mrg { 3355 1.1 mrg case '<': 3356 1.1 mrg if (!inPointy && j == iHrefStart) 3357 1.1 mrg { 3358 1.1 mrg inPointy = true; 3359 1.1 mrg ++iHrefStart; 3360 1.1 mrg } 3361 1.1 mrg break; 3362 1.1 mrg case '>': 3363 1.1 mrg if (inPointy && slice[j-1] != '\\') 3364 1.1 mrg goto LReturnHref; 3365 1.1 mrg break; 3366 1.1 mrg case '(': 3367 1.1 mrg if (!inPointy && slice[j-1] != '\\') 3368 1.1 mrg ++parenDepth; 3369 1.1 mrg break; 3370 1.1 mrg case ')': 3371 1.1 mrg if (!inPointy && slice[j-1] != '\\') 3372 1.1 mrg { 3373 1.1 mrg --parenDepth; 3374 1.1 mrg if (!parenDepth) 3375 1.1 mrg goto LReturnHref; 3376 1.1 mrg } 3377 1.1 mrg break; 3378 1.1 mrg case ' ': 3379 1.1 mrg case '\t': 3380 1.1 mrg case '\r': 3381 1.1 mrg case '\n': 3382 1.1 mrg if (inPointy) 3383 1.1 mrg { 3384 1.1 mrg // invalid link 3385 1.1 mrg return false; 3386 1.1 mrg } 3387 1.1 mrg goto LReturnHref; 3388 1.1 mrg default: 3389 1.1 mrg break; 3390 1.1 mrg } 3391 1.1 mrg } 3392 1.1 mrg if (inPointy) 3393 1.1 mrg return false; 3394 1.1 mrg LReturnHref: 3395 1.1 mrg auto href = slice[iHrefStart .. j].dup; 3396 1.1 mrg this.href = cast(string) percentEncode(removeEscapeBackslashes(href)).replaceChar(',', "$(COMMA)"); 3397 1.1 mrg i = j; 3398 1.1 mrg if (inPointy) 3399 1.1 mrg ++i; 3400 1.1 mrg return true; 3401 1.1 mrg } 3402 1.1 mrg 3403 1.1 mrg /**************************************************** 3404 1.1 mrg * Parse and store a Markdown link title, enclosed in parentheses or `'` or `"` quotes 3405 1.1 mrg * Params: 3406 1.1 mrg * buf = an OutBuffer containing the DDoc 3407 1.1 mrg * i = the index within `buf` that points to the first character of the title. 3408 1.1 mrg * If this function succeeds `i` will point just after the the end of the title. 3409 1.1 mrg * Returns: whether a title was found and parsed 3410 1.1 mrg */ 3411 1.1 mrg private bool parseTitle(ref OutBuffer buf, ref size_t i) 3412 1.1 mrg { 3413 1.1 mrg size_t j = skipChars(buf, i, " \t"); 3414 1.1 mrg if (j >= buf.length) 3415 1.1 mrg return false; 3416 1.1 mrg 3417 1.1 mrg char type = buf[j]; 3418 1.1 mrg if (type != '"' && type != '\'' && type != '(') 3419 1.1 mrg return false; 3420 1.1 mrg if (type == '(') 3421 1.1 mrg type = ')'; 3422 1.1 mrg 3423 1.1 mrg const iTitleStart = j + 1; 3424 1.1 mrg size_t iNewline = 0; 3425 1.1 mrg const slice = buf[]; 3426 1.1 mrg for (j = iTitleStart; j < slice.length; j++) 3427 1.1 mrg { 3428 1.1 mrg const c = slice[j]; 3429 1.1 mrg switch (c) 3430 1.1 mrg { 3431 1.1 mrg case ')': 3432 1.1 mrg case '"': 3433 1.1 mrg case '\'': 3434 1.1 mrg if (type == c && slice[j-1] != '\\') 3435 1.1 mrg goto LEndTitle; 3436 1.1 mrg iNewline = 0; 3437 1.1 mrg break; 3438 1.1 mrg case ' ': 3439 1.1 mrg case '\t': 3440 1.1 mrg case '\r': 3441 1.1 mrg break; 3442 1.1 mrg case '\n': 3443 1.1 mrg if (iNewline) 3444 1.1 mrg { 3445 1.1 mrg // no blank lines in titles 3446 1.1 mrg return false; 3447 1.1 mrg } 3448 1.1 mrg iNewline = j; 3449 1.1 mrg break; 3450 1.1 mrg default: 3451 1.1 mrg iNewline = 0; 3452 1.1 mrg break; 3453 1.1 mrg } 3454 1.1 mrg } 3455 1.1 mrg return false; 3456 1.1 mrg LEndTitle: 3457 1.1 mrg auto title = slice[iTitleStart .. j].dup; 3458 1.1 mrg this.title = cast(string) removeEscapeBackslashes(title). 3459 1.1 mrg replaceChar(',', "$(COMMA)"). 3460 1.1 mrg replaceChar('"', "$(QUOTE)"); 3461 1.1 mrg i = j + 1; 3462 1.1 mrg return true; 3463 1.1 mrg } 3464 1.1 mrg 3465 1.1 mrg /**************************************************** 3466 1.1 mrg * Replace a Markdown link or image with the appropriate macro 3467 1.1 mrg * Params: 3468 1.1 mrg * buf = an OutBuffer containing the DDoc 3469 1.1 mrg * i = the index within `buf` that points to the `]` character of the inline link. 3470 1.1 mrg * When this function returns it will be adjusted to the end of the inserted macro. 3471 1.1 mrg * iLinkEnd = the index within `buf` that points just after the last character of the link 3472 1.1 mrg * delimiter = the Markdown delimiter that started the link or image 3473 1.1 mrg */ 3474 1.1 mrg private void replaceLink(ref OutBuffer buf, ref size_t i, size_t iLinkEnd, MarkdownDelimiter delimiter) 3475 1.1 mrg { 3476 1.1 mrg size_t iAfterLink = i - delimiter.count; 3477 1.1 mrg string macroName; 3478 1.1 mrg if (symbol) 3479 1.1 mrg { 3480 1.1 mrg macroName = "$(SYMBOL_LINK "; 3481 1.1 mrg } 3482 1.1 mrg else if (title.length) 3483 1.1 mrg { 3484 1.1 mrg if (delimiter.type == '[') 3485 1.1 mrg macroName = "$(LINK_TITLE "; 3486 1.1 mrg else 3487 1.1 mrg macroName = "$(IMAGE_TITLE "; 3488 1.1 mrg } 3489 1.1 mrg else 3490 1.1 mrg { 3491 1.1 mrg if (delimiter.type == '[') 3492 1.1 mrg macroName = "$(LINK2 "; 3493 1.1 mrg else 3494 1.1 mrg macroName = "$(IMAGE "; 3495 1.1 mrg } 3496 1.1 mrg buf.remove(delimiter.iStart, delimiter.count); 3497 1.1 mrg buf.remove(i - delimiter.count, iLinkEnd - i); 3498 1.1 mrg iLinkEnd = buf.insert(delimiter.iStart, macroName); 3499 1.1 mrg iLinkEnd = buf.insert(iLinkEnd, href); 3500 1.1 mrg iLinkEnd = buf.insert(iLinkEnd, ", "); 3501 1.1 mrg iAfterLink += macroName.length + href.length + 2; 3502 1.1 mrg if (title.length) 3503 1.1 mrg { 3504 1.1 mrg iLinkEnd = buf.insert(iLinkEnd, title); 3505 1.1 mrg iLinkEnd = buf.insert(iLinkEnd, ", "); 3506 1.1 mrg iAfterLink += title.length + 2; 3507 1.1 mrg 3508 1.1 mrg // Link macros with titles require escaping commas 3509 1.1 mrg for (size_t j = iLinkEnd; j < iAfterLink; ++j) 3510 1.1 mrg if (buf[j] == ',') 3511 1.1 mrg { 3512 1.1 mrg buf.remove(j, 1); 3513 1.1 mrg j = buf.insert(j, "$(COMMA)") - 1; 3514 1.1 mrg iAfterLink += 7; 3515 1.1 mrg } 3516 1.1 mrg } 3517 1.1 mrg // TODO: if image, remove internal macros, leaving only text 3518 1.1 mrg buf.insert(iAfterLink, ")"); 3519 1.1 mrg i = iAfterLink; 3520 1.1 mrg } 3521 1.1 mrg 3522 1.1 mrg /**************************************************** 3523 1.1 mrg * Store the Markdown link definition and remove it from `buf` 3524 1.1 mrg * Params: 3525 1.1 mrg * buf = an OutBuffer containing the DDoc 3526 1.1 mrg * i = the index within `buf` that points to the `[` character at the start of the link definition. 3527 1.1 mrg * When this function returns it will be adjusted to exclude the link definition. 3528 1.1 mrg * iEnd = the index within `buf` that points just after the end of the definition 3529 1.1 mrg * linkReferences = previously parsed link references. When this function returns it may contain 3530 1.1 mrg * an additional reference. 3531 1.1 mrg * loc = the current location in the file 3532 1.1 mrg */ 3533 1.1 mrg private void storeAndReplaceDefinition(ref OutBuffer buf, ref size_t i, size_t iEnd, ref MarkdownLinkReferences linkReferences, const ref Loc loc) 3534 1.1 mrg { 3535 1.1 mrg if (global.params.vmarkdown) 3536 1.1 mrg message(loc, "Ddoc: found link reference '%.*s' to '%.*s'", cast(int)label.length, label.ptr, cast(int)href.length, href.ptr); 3537 1.1 mrg 3538 1.1 mrg // Remove the definition and trailing whitespace 3539 1.1 mrg iEnd = skipChars(buf, iEnd, " \t\r\n"); 3540 1.1 mrg buf.remove(i, iEnd - i); 3541 1.1 mrg i -= 2; 3542 1.1 mrg 3543 1.1 mrg string lowercaseLabel = label.toLowercase(); 3544 1.1 mrg if (lowercaseLabel !in linkReferences.references) 3545 1.1 mrg linkReferences.references[lowercaseLabel] = this; 3546 1.1 mrg } 3547 1.1 mrg 3548 1.1 mrg /**************************************************** 3549 1.1 mrg * Remove Markdown escaping backslashes from the given string 3550 1.1 mrg * Params: 3551 1.1 mrg * s = the string to remove escaping backslashes from 3552 1.1 mrg * Returns: `s` without escaping backslashes in it 3553 1.1 mrg */ 3554 1.1 mrg private static char[] removeEscapeBackslashes(char[] s) 3555 1.1 mrg { 3556 1.1 mrg if (!s.length) 3557 1.1 mrg return s; 3558 1.1 mrg 3559 1.1 mrg // avoid doing anything if there isn't anything to escape 3560 1.1 mrg size_t i; 3561 1.1 mrg for (i = 0; i < s.length-1; ++i) 3562 1.1 mrg if (s[i] == '\\' && ispunct(s[i+1])) 3563 1.1 mrg break; 3564 1.1 mrg if (i == s.length-1) 3565 1.1 mrg return s; 3566 1.1 mrg 3567 1.1 mrg // copy characters backwards, then truncate 3568 1.1 mrg size_t j = i + 1; 3569 1.1 mrg s[i] = s[j]; 3570 1.1 mrg for (++i, ++j; j < s.length; ++i, ++j) 3571 1.1 mrg { 3572 1.1 mrg if (j < s.length-1 && s[j] == '\\' && ispunct(s[j+1])) 3573 1.1 mrg ++j; 3574 1.1 mrg s[i] = s[j]; 3575 1.1 mrg } 3576 1.1 mrg s.length -= (j - i); 3577 1.1 mrg return s; 3578 1.1 mrg } 3579 1.1 mrg 3580 1.1 mrg /// 3581 1.1 mrg unittest 3582 1.1 mrg { 3583 1.1 mrg assert(removeEscapeBackslashes("".dup) == ""); 3584 1.1 mrg assert(removeEscapeBackslashes(`\a`.dup) == `\a`); 3585 1.1 mrg assert(removeEscapeBackslashes(`.\`.dup) == `.\`); 3586 1.1 mrg assert(removeEscapeBackslashes(`\.\`.dup) == `.\`); 3587 1.1 mrg assert(removeEscapeBackslashes(`\.`.dup) == `.`); 3588 1.1 mrg assert(removeEscapeBackslashes(`\.\.`.dup) == `..`); 3589 1.1 mrg assert(removeEscapeBackslashes(`a\.b\.c`.dup) == `a.b.c`); 3590 1.1 mrg } 3591 1.1 mrg 3592 1.1 mrg /**************************************************** 3593 1.1 mrg * Percent-encode (AKA URL-encode) the given string 3594 1.1 mrg * Params: 3595 1.1 mrg * s = the string to percent-encode 3596 1.1 mrg * Returns: `s` with special characters percent-encoded 3597 1.1 mrg */ 3598 1.1 mrg private static inout(char)[] percentEncode(inout(char)[] s) pure 3599 1.1 mrg { 3600 1.1 mrg static bool shouldEncode(char c) 3601 1.1 mrg { 3602 1.1 mrg return ((c < '0' && c != '!' && c != '#' && c != '$' && c != '%' && c != '&' && c != '\'' && c != '(' && 3603 1.1 mrg c != ')' && c != '*' && c != '+' && c != ',' && c != '-' && c != '.' && c != '/') 3604 1.1 mrg || (c > '9' && c < 'A' && c != ':' && c != ';' && c != '=' && c != '?' && c != '@') 3605 1.1 mrg || (c > 'Z' && c < 'a' && c != '[' && c != ']' && c != '_') 3606 1.1 mrg || (c > 'z' && c != '~')); 3607 1.1 mrg } 3608 1.1 mrg 3609 1.1 mrg for (size_t i = 0; i < s.length; ++i) 3610 1.1 mrg { 3611 1.1 mrg if (shouldEncode(s[i])) 3612 1.1 mrg { 3613 1.1 mrg immutable static hexDigits = "0123456789ABCDEF"; 3614 1.1 mrg immutable encoded1 = hexDigits[s[i] >> 4]; 3615 1.1 mrg immutable encoded2 = hexDigits[s[i] & 0x0F]; 3616 1.1 mrg s = s[0..i] ~ '%' ~ encoded1 ~ encoded2 ~ s[i+1..$]; 3617 1.1 mrg i += 2; 3618 1.1 mrg } 3619 1.1 mrg } 3620 1.1 mrg return s; 3621 1.1 mrg } 3622 1.1 mrg 3623 1.1 mrg /// 3624 1.1 mrg unittest 3625 1.1 mrg { 3626 1.1 mrg assert(percentEncode("") == ""); 3627 1.1 mrg assert(percentEncode("aB12-._~/?") == "aB12-._~/?"); 3628 1.1 mrg assert(percentEncode("<\n>") == "%3C%0A%3E"); 3629 1.1 mrg } 3630 1.1 mrg 3631 1.1 mrg /************************************************** 3632 1.1 mrg * Skip a single newline at `i` 3633 1.1 mrg * Params: 3634 1.1 mrg * buf = an OutBuffer containing the DDoc 3635 1.1 mrg * i = the index within `buf` to start looking at. 3636 1.1 mrg * If this function succeeds `i` will point after the newline. 3637 1.1 mrg * Returns: whether a newline was skipped 3638 1.1 mrg */ 3639 1.1 mrg private static bool skipOneNewline(ref OutBuffer buf, ref size_t i) pure 3640 1.1 mrg { 3641 1.1 mrg if (i < buf.length && buf[i] == '\r') 3642 1.1 mrg ++i; 3643 1.1 mrg if (i < buf.length && buf[i] == '\n') 3644 1.1 mrg { 3645 1.1 mrg ++i; 3646 1.1 mrg return true; 3647 1.1 mrg } 3648 1.1 mrg return false; 3649 1.1 mrg } 3650 1.1 mrg } 3651 1.1 mrg 3652 1.1 mrg /************************************************** 3653 1.1 mrg * A set of Markdown link references. 3654 1.1 mrg */ 3655 1.1 mrg private struct MarkdownLinkReferences 3656 1.1 mrg { 3657 1.1 mrg MarkdownLink[string] references; // link references keyed by normalized label 3658 1.1 mrg MarkdownLink[string] symbols; // link symbols keyed by name 3659 1.1 mrg Scope* _scope; // the current scope 3660 1.1 mrg bool extractedAll; // the index into the buffer of the last-parsed reference 3661 1.1 mrg 3662 1.1 mrg /************************************************** 3663 1.1 mrg * Look up a reference by label, searching through the rest of the buffer if needed. 3664 1.1 mrg * Symbols in the current scope are searched for if the DDoc doesn't define the reference. 3665 1.1 mrg * Params: 3666 1.1 mrg * label = the label to find the reference for 3667 1.1 mrg * buf = an OutBuffer containing the DDoc 3668 1.1 mrg * i = the index within `buf` to start searching for references at 3669 1.1 mrg * loc = the current location in the file 3670 1.1 mrg * Returns: a link. If the `href` member has a value then the reference is valid. 3671 1.1 mrg */ 3672 1.1 mrg MarkdownLink lookupReference(string label, ref OutBuffer buf, size_t i, const ref Loc loc) 3673 1.1 mrg { 3674 1.1 mrg const lowercaseLabel = label.toLowercase(); 3675 1.1 mrg if (lowercaseLabel !in references) 3676 1.1 mrg extractReferences(buf, i, loc); 3677 1.1 mrg 3678 1.1 mrg if (lowercaseLabel in references) 3679 1.1 mrg return references[lowercaseLabel]; 3680 1.1 mrg 3681 1.1 mrg return MarkdownLink(); 3682 1.1 mrg } 3683 1.1 mrg 3684 1.1 mrg /** 3685 1.1 mrg * Look up the link for the D symbol with the given name. 3686 1.1 mrg * If found, the link is cached in the `symbols` member. 3687 1.1 mrg * Params: 3688 1.1 mrg * name = the name of the symbol 3689 1.1 mrg * Returns: the link for the symbol or a link with a `null` href 3690 1.1 mrg */ 3691 1.1 mrg MarkdownLink lookupSymbol(string name) 3692 1.1 mrg { 3693 1.1 mrg if (name in symbols) 3694 1.1 mrg return symbols[name]; 3695 1.1 mrg 3696 1.1 mrg const ids = split(name, '.'); 3697 1.1 mrg 3698 1.1 mrg MarkdownLink link; 3699 1.1 mrg auto id = Identifier.lookup(ids[0].ptr, ids[0].length); 3700 1.1 mrg if (id) 3701 1.1 mrg { 3702 1.1 mrg auto loc = Loc(); 3703 1.1 mrg auto symbol = _scope.search(loc, id, null, IgnoreErrors); 3704 1.1 mrg for (size_t i = 1; symbol && i < ids.length; ++i) 3705 1.1 mrg { 3706 1.1 mrg id = Identifier.lookup(ids[i].ptr, ids[i].length); 3707 1.1 mrg symbol = id !is null ? symbol.search(loc, id, IgnoreErrors) : null; 3708 1.1 mrg } 3709 1.1 mrg if (symbol) 3710 1.1 mrg link = MarkdownLink(createHref(symbol), null, name, symbol); 3711 1.1 mrg } 3712 1.1 mrg 3713 1.1 mrg symbols[name] = link; 3714 1.1 mrg return link; 3715 1.1 mrg } 3716 1.1 mrg 3717 1.1 mrg /************************************************** 3718 1.1 mrg * Remove and store all link references from the document, in the form of 3719 1.1 mrg * `[label]: href "optional title"` 3720 1.1 mrg * Params: 3721 1.1 mrg * buf = an OutBuffer containing the DDoc 3722 1.1 mrg * i = the index within `buf` to start looking at 3723 1.1 mrg * loc = the current location in the file 3724 1.1 mrg * Returns: whether a reference was extracted 3725 1.1 mrg */ 3726 1.1 mrg private void extractReferences(ref OutBuffer buf, size_t i, const ref Loc loc) 3727 1.1 mrg { 3728 1.1 mrg static bool isFollowedBySpace(ref OutBuffer buf, size_t i) 3729 1.1 mrg { 3730 1.1 mrg return i+1 < buf.length && (buf[i+1] == ' ' || buf[i+1] == '\t'); 3731 1.1 mrg } 3732 1.1 mrg 3733 1.1 mrg if (extractedAll) 3734 1.1 mrg return; 3735 1.1 mrg 3736 1.1 mrg bool leadingBlank = false; 3737 1.1 mrg int inCode = false; 3738 1.1 mrg bool newParagraph = true; 3739 1.1 mrg MarkdownDelimiter[] delimiters; 3740 1.1 mrg for (; i < buf.length; ++i) 3741 1.1 mrg { 3742 1.1 mrg const c = buf[i]; 3743 1.1 mrg switch (c) 3744 1.1 mrg { 3745 1.1 mrg case ' ': 3746 1.1 mrg case '\t': 3747 1.1 mrg break; 3748 1.1 mrg case '\n': 3749 1.1 mrg if (leadingBlank && !inCode) 3750 1.1 mrg newParagraph = true; 3751 1.1 mrg leadingBlank = true; 3752 1.1 mrg break; 3753 1.1 mrg case '\\': 3754 1.1 mrg ++i; 3755 1.1 mrg break; 3756 1.1 mrg case '#': 3757 1.1 mrg if (leadingBlank && !inCode) 3758 1.1 mrg newParagraph = true; 3759 1.1 mrg leadingBlank = false; 3760 1.1 mrg break; 3761 1.1 mrg case '>': 3762 1.1 mrg if (leadingBlank && !inCode) 3763 1.1 mrg newParagraph = true; 3764 1.1 mrg break; 3765 1.1 mrg case '+': 3766 1.1 mrg if (leadingBlank && !inCode && isFollowedBySpace(buf, i)) 3767 1.1 mrg newParagraph = true; 3768 1.1 mrg else 3769 1.1 mrg leadingBlank = false; 3770 1.1 mrg break; 3771 1.1 mrg case '0': 3772 1.1 mrg .. 3773 1.1 mrg case '9': 3774 1.1 mrg if (leadingBlank && !inCode) 3775 1.1 mrg { 3776 1.1 mrg i = skipChars(buf, i, "0123456789"); 3777 1.1 mrg if (i < buf.length && 3778 1.1 mrg (buf[i] == '.' || buf[i] == ')') && 3779 1.1 mrg isFollowedBySpace(buf, i)) 3780 1.1 mrg newParagraph = true; 3781 1.1 mrg else 3782 1.1 mrg leadingBlank = false; 3783 1.1 mrg } 3784 1.1 mrg break; 3785 1.1 mrg case '*': 3786 1.1 mrg if (leadingBlank && !inCode) 3787 1.1 mrg { 3788 1.1 mrg newParagraph = true; 3789 1.1 mrg if (!isFollowedBySpace(buf, i)) 3790 1.1 mrg leadingBlank = false; 3791 1.1 mrg } 3792 1.1 mrg break; 3793 1.1 mrg case '`': 3794 1.1 mrg case '~': 3795 1.1 mrg if (leadingBlank && i+2 < buf.length && buf[i+1] == c && buf[i+2] == c) 3796 1.1 mrg { 3797 1.1 mrg inCode = inCode == c ? false : c; 3798 1.1 mrg i = skipChars(buf, i, [c]) - 1; 3799 1.1 mrg newParagraph = true; 3800 1.1 mrg } 3801 1.1 mrg leadingBlank = false; 3802 1.1 mrg break; 3803 1.1 mrg case '-': 3804 1.1 mrg if (leadingBlank && !inCode && isFollowedBySpace(buf, i)) 3805 1.1 mrg goto case '+'; 3806 1.1 mrg else 3807 1.1 mrg goto case '`'; 3808 1.1 mrg case '[': 3809 1.1 mrg if (leadingBlank && !inCode && newParagraph) 3810 1.1 mrg delimiters ~= MarkdownDelimiter(i, 1, 0, false, false, true, c); 3811 1.1 mrg break; 3812 1.1 mrg case ']': 3813 1.1 mrg if (delimiters.length && !inCode && 3814 1.1 mrg MarkdownLink.replaceReferenceDefinition(buf, i, delimiters, cast(int) delimiters.length - 1, this, loc)) 3815 1.1 mrg --i; 3816 1.1 mrg break; 3817 1.1 mrg default: 3818 1.1 mrg if (leadingBlank) 3819 1.1 mrg newParagraph = false; 3820 1.1 mrg leadingBlank = false; 3821 1.1 mrg break; 3822 1.1 mrg } 3823 1.1 mrg } 3824 1.1 mrg extractedAll = true; 3825 1.1 mrg } 3826 1.1 mrg 3827 1.1 mrg /** 3828 1.1 mrg * Split a string by a delimiter, excluding the delimiter. 3829 1.1 mrg * Params: 3830 1.1 mrg * s = the string to split 3831 1.1 mrg * delimiter = the character to split by 3832 1.1 mrg * Returns: the resulting array of strings 3833 1.1 mrg */ 3834 1.1 mrg private static string[] split(string s, char delimiter) pure 3835 1.1 mrg { 3836 1.1 mrg string[] result; 3837 1.1 mrg size_t iStart = 0; 3838 1.1 mrg foreach (size_t i; 0..s.length) 3839 1.1 mrg if (s[i] == delimiter) 3840 1.1 mrg { 3841 1.1 mrg result ~= s[iStart..i]; 3842 1.1 mrg iStart = i + 1; 3843 1.1 mrg } 3844 1.1 mrg result ~= s[iStart..$]; 3845 1.1 mrg return result; 3846 1.1 mrg } 3847 1.1 mrg 3848 1.1 mrg /// 3849 1.1 mrg unittest 3850 1.1 mrg { 3851 1.1 mrg assert(split("", ',') == [""]); 3852 1.1 mrg assert(split("ab", ',') == ["ab"]); 3853 1.1 mrg assert(split("a,b", ',') == ["a", "b"]); 3854 1.1 mrg assert(split("a,,b", ',') == ["a", "", "b"]); 3855 1.1 mrg assert(split(",ab", ',') == ["", "ab"]); 3856 1.1 mrg assert(split("ab,", ',') == ["ab", ""]); 3857 1.1 mrg } 3858 1.1 mrg 3859 1.1 mrg /** 3860 1.1 mrg * Create a HREF for the given D symbol. 3861 1.1 mrg * The HREF is relative to the current location if possible. 3862 1.1 mrg * Params: 3863 1.1 mrg * symbol = the symbol to create a HREF for. 3864 1.1 mrg * Returns: the resulting href 3865 1.1 mrg */ 3866 1.1 mrg private string createHref(Dsymbol symbol) 3867 1.1 mrg { 3868 1.1 mrg Dsymbol root = symbol; 3869 1.1 mrg 3870 1.1 mrg const(char)[] lref; 3871 1.1 mrg while (symbol && symbol.ident && !symbol.isModule()) 3872 1.1 mrg { 3873 1.1 mrg if (lref.length) 3874 1.1 mrg lref = '.' ~ lref; 3875 1.1 mrg lref = symbol.ident.toString() ~ lref; 3876 1.1 mrg symbol = symbol.parent; 3877 1.1 mrg } 3878 1.1 mrg 3879 1.1 mrg const(char)[] path; 3880 1.1 mrg if (symbol && symbol.ident && symbol.isModule() != _scope._module) 3881 1.1 mrg { 3882 1.1 mrg do 3883 1.1 mrg { 3884 1.1 mrg root = symbol; 3885 1.1 mrg 3886 1.1 mrg // If the module has a file name, we're done 3887 1.1 mrg if (const m = symbol.isModule()) 3888 1.1 mrg if (m.docfile) 3889 1.1 mrg { 3890 1.1 mrg path = m.docfile.toString(); 3891 1.1 mrg break; 3892 1.1 mrg } 3893 1.1 mrg 3894 1.1 mrg if (path.length) 3895 1.1 mrg path = '_' ~ path; 3896 1.1 mrg path = symbol.ident.toString() ~ path; 3897 1.1 mrg symbol = symbol.parent; 3898 1.1 mrg } while (symbol && symbol.ident); 3899 1.1 mrg 3900 1.1 mrg if (!symbol && path.length) 3901 1.1 mrg path ~= "$(DOC_EXTENSION)"; 3902 1.1 mrg } 3903 1.1 mrg 3904 1.1 mrg // Attempt an absolute URL if not in the same package 3905 1.1 mrg while (root.parent) 3906 1.1 mrg root = root.parent; 3907 1.1 mrg Dsymbol scopeRoot = _scope._module; 3908 1.1 mrg while (scopeRoot.parent) 3909 1.1 mrg scopeRoot = scopeRoot.parent; 3910 1.1 mrg if (scopeRoot != root) 3911 1.1 mrg { 3912 1.1 mrg path = "$(DOC_ROOT_" ~ root.ident.toString() ~ ')' ~ path; 3913 1.1 mrg lref = '.' ~ lref; // remote URIs like Phobos and Mir use .prefixes 3914 1.1 mrg } 3915 1.1 mrg 3916 1.1 mrg return cast(string) (path ~ '#' ~ lref); 3917 1.1 mrg } 3918 1.1 mrg } 3919 1.1 mrg 3920 1.1 mrg private enum TableColumnAlignment 3921 1.1 mrg { 3922 1.1 mrg none, 3923 1.1 mrg left, 3924 1.1 mrg center, 3925 1.1 mrg right 3926 1.1 mrg } 3927 1.1 mrg 3928 1.1 mrg /**************************************************** 3929 1.1 mrg * Parse a Markdown table delimiter row in the form of `| -- | :-- | :--: | --: |` 3930 1.1 mrg * where the example text has four columns with the following alignments: 3931 1.1 mrg * default, left, center, and right. The first and last pipes are optional. If a 3932 1.1 mrg * delimiter row is found it will be removed from `buf`. 3933 1.1 mrg * 3934 1.1 mrg * Params: 3935 1.1 mrg * buf = an OutBuffer containing the DDoc 3936 1.1 mrg * iStart = the index within `buf` that the delimiter row starts at 3937 1.1 mrg * inQuote = whether the table is inside a quote 3938 1.1 mrg * columnAlignments = alignments to populate for each column 3939 1.1 mrg * Returns: the index of the end of the parsed delimiter, or `0` if not found 3940 1.1 mrg */ 3941 1.1 mrg private size_t parseTableDelimiterRow(ref OutBuffer buf, const size_t iStart, bool inQuote, ref TableColumnAlignment[] columnAlignments) 3942 1.1 mrg { 3943 1.1 mrg size_t i = skipChars(buf, iStart, inQuote ? ">| \t" : "| \t"); 3944 1.1 mrg while (i < buf.length && buf[i] != '\r' && buf[i] != '\n') 3945 1.1 mrg { 3946 1.1 mrg const leftColon = buf[i] == ':'; 3947 1.1 mrg if (leftColon) 3948 1.1 mrg ++i; 3949 1.1 mrg 3950 1.1 mrg if (i >= buf.length || buf[i] != '-') 3951 1.1 mrg break; 3952 1.1 mrg i = skipChars(buf, i, "-"); 3953 1.1 mrg 3954 1.1 mrg const rightColon = i < buf.length && buf[i] == ':'; 3955 1.1 mrg i = skipChars(buf, i, ": \t"); 3956 1.1 mrg 3957 1.1 mrg if (i >= buf.length || (buf[i] != '|' && buf[i] != '\r' && buf[i] != '\n')) 3958 1.1 mrg break; 3959 1.1 mrg i = skipChars(buf, i, "| \t"); 3960 1.1 mrg 3961 1.1 mrg columnAlignments ~= (leftColon && rightColon) ? TableColumnAlignment.center : 3962 1.1 mrg leftColon ? TableColumnAlignment.left : 3963 1.1 mrg rightColon ? TableColumnAlignment.right : 3964 1.1 mrg TableColumnAlignment.none; 3965 1.1 mrg } 3966 1.1 mrg 3967 1.1 mrg if (i < buf.length && buf[i] != '\r' && buf[i] != '\n' && buf[i] != ')') 3968 1.1 mrg { 3969 1.1 mrg columnAlignments.length = 0; 3970 1.1 mrg return 0; 3971 1.1 mrg } 3972 1.1 mrg 3973 1.1 mrg if (i < buf.length && buf[i] == '\r') ++i; 3974 1.1 mrg if (i < buf.length && buf[i] == '\n') ++i; 3975 1.1 mrg return i; 3976 1.1 mrg } 3977 1.1 mrg 3978 1.1 mrg /**************************************************** 3979 1.1 mrg * Look for a table delimiter row, and if found parse the previous row as a 3980 1.1 mrg * table header row. If both exist with a matching number of columns, start a 3981 1.1 mrg * table. 3982 1.1 mrg * 3983 1.1 mrg * Params: 3984 1.1 mrg * buf = an OutBuffer containing the DDoc 3985 1.1 mrg * iStart = the index within `buf` that the table header row starts at, inclusive 3986 1.1 mrg * iEnd = the index within `buf` that the table header row ends at, exclusive 3987 1.1 mrg * loc = the current location in the file 3988 1.1 mrg * inQuote = whether the table is inside a quote 3989 1.1 mrg * inlineDelimiters = delimiters containing columns separators and any inline emphasis 3990 1.1 mrg * columnAlignments = the parsed alignments for each column 3991 1.1 mrg * Returns: the number of characters added by starting the table, or `0` if unchanged 3992 1.1 mrg */ 3993 1.1 mrg private size_t startTable(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, bool inQuote, ref MarkdownDelimiter[] inlineDelimiters, out TableColumnAlignment[] columnAlignments) 3994 1.1 mrg { 3995 1.1 mrg const iDelimiterRowEnd = parseTableDelimiterRow(buf, iEnd + 1, inQuote, columnAlignments); 3996 1.1 mrg if (iDelimiterRowEnd) 3997 1.1 mrg { 3998 1.1 mrg size_t delta; 3999 1.1 mrg if (replaceTableRow(buf, iStart, iEnd, loc, inlineDelimiters, columnAlignments, true, delta)) 4000 1.1 mrg { 4001 1.1 mrg buf.remove(iEnd + delta, iDelimiterRowEnd - iEnd); 4002 1.1 mrg buf.insert(iEnd + delta, "$(TBODY "); 4003 1.1 mrg buf.insert(iStart, "$(TABLE "); 4004 1.1 mrg return delta + 15; 4005 1.1 mrg } 4006 1.1 mrg } 4007 1.1 mrg 4008 1.1 mrg columnAlignments.length = 0; 4009 1.1 mrg return 0; 4010 1.1 mrg } 4011 1.1 mrg 4012 1.1 mrg /**************************************************** 4013 1.1 mrg * Replace a Markdown table row in the form of table cells delimited by pipes: 4014 1.1 mrg * `| cell | cell | cell`. The first and last pipes are optional. 4015 1.1 mrg * 4016 1.1 mrg * Params: 4017 1.1 mrg * buf = an OutBuffer containing the DDoc 4018 1.1 mrg * iStart = the index within `buf` that the table row starts at, inclusive 4019 1.1 mrg * iEnd = the index within `buf` that the table row ends at, exclusive 4020 1.1 mrg * loc = the current location in the file 4021 1.1 mrg * inlineDelimiters = delimiters containing columns separators and any inline emphasis 4022 1.1 mrg * columnAlignments = alignments for each column 4023 1.1 mrg * headerRow = if `true` then the number of columns will be enforced to match 4024 1.1 mrg * `columnAlignments.length` and the row will be surrounded by a 4025 1.1 mrg * `THEAD` macro 4026 1.1 mrg * delta = the number of characters added by replacing the row, or `0` if unchanged 4027 1.1 mrg * Returns: `true` if a table row was found and replaced 4028 1.1 mrg */ 4029 1.1 mrg private bool replaceTableRow(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, TableColumnAlignment[] columnAlignments, bool headerRow, out size_t delta) 4030 1.1 mrg { 4031 1.1 mrg delta = 0; 4032 1.1 mrg 4033 1.1 mrg if (!columnAlignments.length || iStart == iEnd) 4034 1.1 mrg return false; 4035 1.1 mrg 4036 1.1 mrg iStart = skipChars(buf, iStart, " \t"); 4037 1.1 mrg int cellCount = 0; 4038 1.1 mrg foreach (delimiter; inlineDelimiters) 4039 1.1 mrg if (delimiter.type == '|' && !delimiter.leftFlanking) 4040 1.1 mrg ++cellCount; 4041 1.1 mrg bool ignoreLast = inlineDelimiters.length > 0 && inlineDelimiters[$-1].type == '|'; 4042 1.1 mrg if (ignoreLast) 4043 1.1 mrg { 4044 1.1 mrg const iLast = skipChars(buf, inlineDelimiters[$-1].iStart + inlineDelimiters[$-1].count, " \t"); 4045 1.1 mrg ignoreLast = iLast >= iEnd; 4046 1.1 mrg } 4047 1.1 mrg if (!ignoreLast) 4048 1.1 mrg ++cellCount; 4049 1.1 mrg 4050 1.1 mrg if (headerRow && cellCount != columnAlignments.length) 4051 1.1 mrg return false; 4052 1.1 mrg 4053 1.1 mrg if (headerRow && global.params.vmarkdown) 4054 1.1 mrg { 4055 1.1 mrg const s = buf[][iStart..iEnd]; 4056 1.1 mrg message(loc, "Ddoc: formatting table '%.*s'", cast(int)s.length, s.ptr); 4057 1.1 mrg } 4058 1.1 mrg 4059 1.1 mrg void replaceTableCell(size_t iCellStart, size_t iCellEnd, int cellIndex, int di) 4060 1.1 mrg { 4061 1.1 mrg const eDelta = replaceMarkdownEmphasis(buf, loc, inlineDelimiters, di); 4062 1.1 mrg delta += eDelta; 4063 1.1 mrg iCellEnd += eDelta; 4064 1.1 mrg 4065 1.1 mrg // strip trailing whitespace and delimiter 4066 1.1 mrg size_t i = iCellEnd - 1; 4067 1.1 mrg while (i > iCellStart && (buf[i] == '|' || buf[i] == ' ' || buf[i] == '\t')) 4068 1.1 mrg --i; 4069 1.1 mrg ++i; 4070 1.1 mrg buf.remove(i, iCellEnd - i); 4071 1.1 mrg delta -= iCellEnd - i; 4072 1.1 mrg iCellEnd = i; 4073 1.1 mrg 4074 1.1 mrg buf.insert(iCellEnd, ")"); 4075 1.1 mrg ++delta; 4076 1.1 mrg 4077 1.1 mrg // strip initial whitespace and delimiter 4078 1.1 mrg i = skipChars(buf, iCellStart, "| \t"); 4079 1.1 mrg buf.remove(iCellStart, i - iCellStart); 4080 1.1 mrg delta -= i - iCellStart; 4081 1.1 mrg 4082 1.1 mrg switch (columnAlignments[cellIndex]) 4083 1.1 mrg { 4084 1.1 mrg case TableColumnAlignment.none: 4085 1.1 mrg buf.insert(iCellStart, headerRow ? "$(TH " : "$(TD "); 4086 1.1 mrg delta += 5; 4087 1.1 mrg break; 4088 1.1 mrg case TableColumnAlignment.left: 4089 1.1 mrg buf.insert(iCellStart, "left, "); 4090 1.1 mrg delta += 6; 4091 1.1 mrg goto default; 4092 1.1 mrg case TableColumnAlignment.center: 4093 1.1 mrg buf.insert(iCellStart, "center, "); 4094 1.1 mrg delta += 8; 4095 1.1 mrg goto default; 4096 1.1 mrg case TableColumnAlignment.right: 4097 1.1 mrg buf.insert(iCellStart, "right, "); 4098 1.1 mrg delta += 7; 4099 1.1 mrg goto default; 4100 1.1 mrg default: 4101 1.1 mrg buf.insert(iCellStart, headerRow ? "$(TH_ALIGN " : "$(TD_ALIGN "); 4102 1.1 mrg delta += 11; 4103 1.1 mrg break; 4104 1.1 mrg } 4105 1.1 mrg } 4106 1.1 mrg 4107 1.1 mrg int cellIndex = cellCount - 1; 4108 1.1 mrg size_t iCellEnd = iEnd; 4109 1.1 mrg foreach_reverse (di, delimiter; inlineDelimiters) 4110 1.1 mrg { 4111 1.1 mrg if (delimiter.type == '|') 4112 1.1 mrg { 4113 1.1 mrg if (ignoreLast && di == inlineDelimiters.length-1) 4114 1.1 mrg { 4115 1.1 mrg ignoreLast = false; 4116 1.1 mrg continue; 4117 1.1 mrg } 4118 1.1 mrg 4119 1.1 mrg if (cellIndex >= columnAlignments.length) 4120 1.1 mrg { 4121 1.1 mrg // kill any extra cells 4122 1.1 mrg buf.remove(delimiter.iStart, iEnd + delta - delimiter.iStart); 4123 1.1 mrg delta -= iEnd + delta - delimiter.iStart; 4124 1.1 mrg iCellEnd = iEnd + delta; 4125 1.1 mrg --cellIndex; 4126 1.1 mrg continue; 4127 1.1 mrg } 4128 1.1 mrg 4129 1.1 mrg replaceTableCell(delimiter.iStart, iCellEnd, cellIndex, cast(int) di); 4130 1.1 mrg iCellEnd = delimiter.iStart; 4131 1.1 mrg --cellIndex; 4132 1.1 mrg } 4133 1.1 mrg } 4134 1.1 mrg 4135 1.1 mrg // if no starting pipe, replace from the start 4136 1.1 mrg if (cellIndex >= 0) 4137 1.1 mrg replaceTableCell(iStart, iCellEnd, cellIndex, 0); 4138 1.1 mrg 4139 1.1 mrg buf.insert(iEnd + delta, ")"); 4140 1.1 mrg buf.insert(iStart, "$(TR "); 4141 1.1 mrg delta += 6; 4142 1.1 mrg 4143 1.1 mrg if (headerRow) 4144 1.1 mrg { 4145 1.1 mrg buf.insert(iEnd + delta, ")"); 4146 1.1 mrg buf.insert(iStart, "$(THEAD "); 4147 1.1 mrg delta += 9; 4148 1.1 mrg } 4149 1.1 mrg 4150 1.1 mrg return true; 4151 1.1 mrg } 4152 1.1 mrg 4153 1.1 mrg /**************************************************** 4154 1.1 mrg * End a table, if in one. 4155 1.1 mrg * 4156 1.1 mrg * Params: 4157 1.1 mrg * buf = an OutBuffer containing the DDoc 4158 1.1 mrg * i = the index within `buf` to end the table at 4159 1.1 mrg * columnAlignments = alignments for each column; upon return is set to length `0` 4160 1.1 mrg * Returns: the number of characters added by ending the table, or `0` if unchanged 4161 1.1 mrg */ 4162 1.1 mrg private size_t endTable(ref OutBuffer buf, size_t i, ref TableColumnAlignment[] columnAlignments) 4163 1.1 mrg { 4164 1.1 mrg if (!columnAlignments.length) 4165 1.1 mrg return 0; 4166 1.1 mrg 4167 1.1 mrg buf.insert(i, "))"); 4168 1.1 mrg columnAlignments.length = 0; 4169 1.1 mrg return 2; 4170 1.1 mrg } 4171 1.1 mrg 4172 1.1 mrg /**************************************************** 4173 1.1 mrg * End a table row and then the table itself. 4174 1.1 mrg * 4175 1.1 mrg * Params: 4176 1.1 mrg * buf = an OutBuffer containing the DDoc 4177 1.1 mrg * iStart = the index within `buf` that the table row starts at, inclusive 4178 1.1 mrg * iEnd = the index within `buf` that the table row ends at, exclusive 4179 1.1 mrg * loc = the current location in the file 4180 1.1 mrg * inlineDelimiters = delimiters containing columns separators and any inline emphasis 4181 1.1 mrg * columnAlignments = alignments for each column; upon return is set to length `0` 4182 1.1 mrg * Returns: the number of characters added by replacing the row, or `0` if unchanged 4183 1.1 mrg */ 4184 1.1 mrg private size_t endRowAndTable(ref OutBuffer buf, size_t iStart, size_t iEnd, const ref Loc loc, ref MarkdownDelimiter[] inlineDelimiters, ref TableColumnAlignment[] columnAlignments) 4185 1.1 mrg { 4186 1.1 mrg size_t delta; 4187 1.1 mrg replaceTableRow(buf, iStart, iEnd, loc, inlineDelimiters, columnAlignments, false, delta); 4188 1.1 mrg delta += endTable(buf, iEnd + delta, columnAlignments); 4189 1.1 mrg return delta; 4190 1.1 mrg } 4191 1.1 mrg 4192 1.1 mrg /************************************************** 4193 1.1 mrg * Highlight text section. 4194 1.1 mrg * 4195 1.1 mrg * Params: 4196 1.1 mrg * scope = the current parse scope 4197 1.1 mrg * a = an array of D symbols at the current scope 4198 1.1 mrg * loc = source location of start of text. It is a mutable copy to allow incrementing its linenum, for printing the correct line number when an error is encountered in a multiline block of ddoc. 4199 1.1 mrg * buf = an OutBuffer containing the DDoc 4200 1.1 mrg * offset = the index within buf to start highlighting 4201 1.1 mrg */ 4202 1.1 mrg private void highlightText(Scope* sc, Dsymbols* a, Loc loc, ref OutBuffer buf, size_t offset) 4203 1.1 mrg { 4204 1.1 mrg const incrementLoc = loc.linnum == 0 ? 1 : 0; 4205 1.1 mrg loc.linnum += incrementLoc; 4206 1.1 mrg loc.charnum = 0; 4207 1.1 mrg //printf("highlightText()\n"); 4208 1.1 mrg bool leadingBlank = true; 4209 1.1 mrg size_t iParagraphStart = offset; 4210 1.1 mrg size_t iPrecedingBlankLine = 0; 4211 1.1 mrg int headingLevel = 0; 4212 1.1 mrg int headingMacroLevel = 0; 4213 1.1 mrg int quoteLevel = 0; 4214 1.1 mrg bool lineQuoted = false; 4215 1.1 mrg int quoteMacroLevel = 0; 4216 1.1 mrg MarkdownList[] nestedLists; 4217 1.1 mrg MarkdownDelimiter[] inlineDelimiters; 4218 1.1 mrg MarkdownLinkReferences linkReferences; 4219 1.1 mrg TableColumnAlignment[] columnAlignments; 4220 1.1 mrg bool tableRowDetected = false; 4221 1.1 mrg int inCode = 0; 4222 1.1 mrg int inBacktick = 0; 4223 1.1 mrg int macroLevel = 0; 4224 1.1 mrg int previousMacroLevel = 0; 4225 1.1 mrg int parenLevel = 0; 4226 1.1 mrg size_t iCodeStart = 0; // start of code section 4227 1.1 mrg size_t codeFenceLength = 0; 4228 1.1 mrg size_t codeIndent = 0; 4229 1.1 mrg string codeLanguage; 4230 1.1 mrg size_t iLineStart = offset; 4231 1.1 mrg linkReferences._scope = sc; 4232 1.1 mrg for (size_t i = offset; i < buf.length; i++) 4233 1.1 mrg { 4234 1.1 mrg char c = buf[i]; 4235 1.1 mrg Lcont: 4236 1.1 mrg switch (c) 4237 1.1 mrg { 4238 1.1 mrg case ' ': 4239 1.1 mrg case '\t': 4240 1.1 mrg break; 4241 1.1 mrg case '\n': 4242 1.1 mrg if (inBacktick) 4243 1.1 mrg { 4244 1.1 mrg // `inline code` is only valid if contained on a single line 4245 1.1 mrg // otherwise, the backticks should be output literally. 4246 1.1 mrg // 4247 1.1 mrg // This lets things like `output from the linker' display 4248 1.1 mrg // unmolested while keeping the feature consistent with GitHub. 4249 1.1 mrg inBacktick = false; 4250 1.1 mrg inCode = false; // the backtick also assumes we're in code 4251 1.1 mrg // Nothing else is necessary since the DDOC_BACKQUOTED macro is 4252 1.1 mrg // inserted lazily at the close quote, meaning the rest of the 4253 1.1 mrg // text is already OK. 4254 1.1 mrg } 4255 1.1 mrg if (headingLevel) 4256 1.1 mrg { 4257 1.1 mrg i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters); 4258 1.1 mrg endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel); 4259 1.1 mrg removeBlankLineMacro(buf, iPrecedingBlankLine, i); 4260 1.1 mrg ++i; 4261 1.1 mrg iParagraphStart = skipChars(buf, i, " \t\r\n"); 4262 1.1 mrg } 4263 1.1 mrg 4264 1.1 mrg if (tableRowDetected && !columnAlignments.length) 4265 1.1 mrg i += startTable(buf, iLineStart, i, loc, lineQuoted, inlineDelimiters, columnAlignments); 4266 1.1 mrg else if (columnAlignments.length) 4267 1.1 mrg { 4268 1.1 mrg size_t delta; 4269 1.1 mrg if (replaceTableRow(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments, false, delta)) 4270 1.1 mrg i += delta; 4271 1.1 mrg else 4272 1.1 mrg i += endTable(buf, i, columnAlignments); 4273 1.1 mrg } 4274 1.1 mrg 4275 1.1 mrg if (!inCode && nestedLists.length && !quoteLevel) 4276 1.1 mrg MarkdownList.handleSiblingOrEndingList(buf, i, iParagraphStart, nestedLists); 4277 1.1 mrg 4278 1.1 mrg iPrecedingBlankLine = 0; 4279 1.1 mrg if (!inCode && i == iLineStart && i + 1 < buf.length) // if "\n\n" 4280 1.1 mrg { 4281 1.1 mrg i += endTable(buf, i, columnAlignments); 4282 1.1 mrg if (!lineQuoted && quoteLevel) 4283 1.1 mrg endAllListsAndQuotes(buf, i, nestedLists, quoteLevel, quoteMacroLevel); 4284 1.1 mrg i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters); 4285 1.1 mrg 4286 1.1 mrg // if we don't already know about this paragraph break then 4287 1.1 mrg // insert a blank line and record the paragraph break 4288 1.1 mrg if (iParagraphStart <= i) 4289 1.1 mrg { 4290 1.1 mrg iPrecedingBlankLine = i; 4291 1.1 mrg i = buf.insert(i, "$(DDOC_BLANKLINE)"); 4292 1.1 mrg iParagraphStart = i + 1; 4293 1.1 mrg } 4294 1.1 mrg } 4295 1.1 mrg else if (inCode && 4296 1.1 mrg i == iLineStart && 4297 1.1 mrg i + 1 < buf.length && 4298 1.1 mrg !lineQuoted && 4299 1.1 mrg quoteLevel) // if "\n\n" in quoted code 4300 1.1 mrg { 4301 1.1 mrg inCode = false; 4302 1.1 mrg i = buf.insert(i, ")"); 4303 1.1 mrg i += endAllMarkdownQuotes(buf, i, quoteLevel); 4304 1.1 mrg quoteMacroLevel = 0; 4305 1.1 mrg } 4306 1.1 mrg leadingBlank = true; 4307 1.1 mrg lineQuoted = false; 4308 1.1 mrg tableRowDetected = false; 4309 1.1 mrg iLineStart = i + 1; 4310 1.1 mrg loc.linnum += incrementLoc; 4311 1.1 mrg 4312 1.1 mrg // update the paragraph start if we just entered a macro 4313 1.1 mrg if (previousMacroLevel < macroLevel && iParagraphStart < iLineStart) 4314 1.1 mrg iParagraphStart = iLineStart; 4315 1.1 mrg previousMacroLevel = macroLevel; 4316 1.1 mrg break; 4317 1.1 mrg 4318 1.1 mrg case '<': 4319 1.1 mrg { 4320 1.1 mrg leadingBlank = false; 4321 1.1 mrg if (inCode) 4322 1.1 mrg break; 4323 1.1 mrg const slice = buf[]; 4324 1.1 mrg auto p = &slice[i]; 4325 1.1 mrg const se = sc._module.escapetable.escapeChar('<'); 4326 1.1 mrg if (se == "<") 4327 1.1 mrg { 4328 1.1 mrg // Generating HTML 4329 1.1 mrg // Skip over comments 4330 1.1 mrg if (p[1] == '!' && p[2] == '-' && p[3] == '-') 4331 1.1 mrg { 4332 1.1 mrg size_t j = i + 4; 4333 1.1 mrg p += 4; 4334 1.1 mrg while (1) 4335 1.1 mrg { 4336 1.1 mrg if (j == slice.length) 4337 1.1 mrg goto L1; 4338 1.1 mrg if (p[0] == '-' && p[1] == '-' && p[2] == '>') 4339 1.1 mrg { 4340 1.1 mrg i = j + 2; // place on closing '>' 4341 1.1 mrg break; 4342 1.1 mrg } 4343 1.1 mrg j++; 4344 1.1 mrg p++; 4345 1.1 mrg } 4346 1.1 mrg break; 4347 1.1 mrg } 4348 1.1 mrg // Skip over HTML tag 4349 1.1 mrg if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2]))) 4350 1.1 mrg { 4351 1.1 mrg size_t j = i + 2; 4352 1.1 mrg p += 2; 4353 1.1 mrg while (1) 4354 1.1 mrg { 4355 1.1 mrg if (j == slice.length) 4356 1.1 mrg break; 4357 1.1 mrg if (p[0] == '>') 4358 1.1 mrg { 4359 1.1 mrg i = j; // place on closing '>' 4360 1.1 mrg break; 4361 1.1 mrg } 4362 1.1 mrg j++; 4363 1.1 mrg p++; 4364 1.1 mrg } 4365 1.1 mrg break; 4366 1.1 mrg } 4367 1.1 mrg } 4368 1.1 mrg L1: 4369 1.1 mrg // Replace '<' with '<' character entity 4370 1.1 mrg if (se.length) 4371 1.1 mrg { 4372 1.1 mrg buf.remove(i, 1); 4373 1.1 mrg i = buf.insert(i, se); 4374 1.1 mrg i--; // point to ';' 4375 1.1 mrg } 4376 1.1 mrg break; 4377 1.1 mrg } 4378 1.1 mrg 4379 1.1 mrg case '>': 4380 1.1 mrg { 4381 1.1 mrg if (leadingBlank && (!inCode || quoteLevel) && global.params.markdown) 4382 1.1 mrg { 4383 1.1 mrg if (!quoteLevel && global.params.vmarkdown) 4384 1.1 mrg { 4385 1.1 mrg size_t iEnd = i + 1; 4386 1.1 mrg while (iEnd < buf.length && buf[iEnd] != '\n') 4387 1.1 mrg ++iEnd; 4388 1.1 mrg const s = buf[][i .. iEnd]; 4389 1.1 mrg message(loc, "Ddoc: starting quote block with '%.*s'", cast(int)s.length, s.ptr); 4390 1.1 mrg } 4391 1.1 mrg 4392 1.1 mrg lineQuoted = true; 4393 1.1 mrg int lineQuoteLevel = 1; 4394 1.1 mrg size_t iAfterDelimiters = i + 1; 4395 1.1 mrg for (; iAfterDelimiters < buf.length; ++iAfterDelimiters) 4396 1.1 mrg { 4397 1.1 mrg const c0 = buf[iAfterDelimiters]; 4398 1.1 mrg if (c0 == '>') 4399 1.1 mrg ++lineQuoteLevel; 4400 1.1 mrg else if (c0 != ' ' && c0 != '\t') 4401 1.1 mrg break; 4402 1.1 mrg } 4403 1.1 mrg if (!quoteMacroLevel) 4404 1.1 mrg quoteMacroLevel = macroLevel; 4405 1.1 mrg buf.remove(i, iAfterDelimiters - i); 4406 1.1 mrg 4407 1.1 mrg if (quoteLevel < lineQuoteLevel) 4408 1.1 mrg { 4409 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 4410 1.1 mrg if (nestedLists.length) 4411 1.1 mrg { 4412 1.1 mrg const indent = getMarkdownIndent(buf, iLineStart, i); 4413 1.1 mrg if (indent < nestedLists[$-1].contentIndent) 4414 1.1 mrg i += MarkdownList.endAllNestedLists(buf, i, nestedLists); 4415 1.1 mrg } 4416 1.1 mrg 4417 1.1 mrg for (; quoteLevel < lineQuoteLevel; ++quoteLevel) 4418 1.1 mrg { 4419 1.1 mrg i = buf.insert(i, "$(BLOCKQUOTE\n"); 4420 1.1 mrg iLineStart = iParagraphStart = i; 4421 1.1 mrg } 4422 1.1 mrg --i; 4423 1.1 mrg } 4424 1.1 mrg else 4425 1.1 mrg { 4426 1.1 mrg --i; 4427 1.1 mrg if (nestedLists.length) 4428 1.1 mrg MarkdownList.handleSiblingOrEndingList(buf, i, iParagraphStart, nestedLists); 4429 1.1 mrg } 4430 1.1 mrg break; 4431 1.1 mrg } 4432 1.1 mrg 4433 1.1 mrg leadingBlank = false; 4434 1.1 mrg if (inCode) 4435 1.1 mrg break; 4436 1.1 mrg // Replace '>' with '>' character entity 4437 1.1 mrg const se = sc._module.escapetable.escapeChar('>'); 4438 1.1 mrg if (se.length) 4439 1.1 mrg { 4440 1.1 mrg buf.remove(i, 1); 4441 1.1 mrg i = buf.insert(i, se); 4442 1.1 mrg i--; // point to ';' 4443 1.1 mrg } 4444 1.1 mrg break; 4445 1.1 mrg } 4446 1.1 mrg 4447 1.1 mrg case '&': 4448 1.1 mrg { 4449 1.1 mrg leadingBlank = false; 4450 1.1 mrg if (inCode) 4451 1.1 mrg break; 4452 1.1 mrg char* p = cast(char*)&buf[].ptr[i]; 4453 1.1 mrg if (p[1] == '#' || isalpha(p[1])) 4454 1.1 mrg break; 4455 1.1 mrg // already a character entity 4456 1.1 mrg // Replace '&' with '&' character entity 4457 1.1 mrg const se = sc._module.escapetable.escapeChar('&'); 4458 1.1 mrg if (se) 4459 1.1 mrg { 4460 1.1 mrg buf.remove(i, 1); 4461 1.1 mrg i = buf.insert(i, se); 4462 1.1 mrg i--; // point to ';' 4463 1.1 mrg } 4464 1.1 mrg break; 4465 1.1 mrg } 4466 1.1 mrg 4467 1.1 mrg case '`': 4468 1.1 mrg { 4469 1.1 mrg const iAfterDelimiter = skipChars(buf, i, "`"); 4470 1.1 mrg const count = iAfterDelimiter - i; 4471 1.1 mrg 4472 1.1 mrg if (inBacktick == count) 4473 1.1 mrg { 4474 1.1 mrg inBacktick = 0; 4475 1.1 mrg inCode = 0; 4476 1.1 mrg OutBuffer codebuf; 4477 1.1 mrg codebuf.write(buf[iCodeStart + count .. i]); 4478 1.1 mrg // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL 4479 1.1 mrg highlightCode(sc, a, codebuf, 0); 4480 1.1 mrg escapeStrayParenthesis(loc, &codebuf, 0, false); 4481 1.1 mrg buf.remove(iCodeStart, i - iCodeStart + count); // also trimming off the current ` 4482 1.1 mrg immutable pre = "$(DDOC_BACKQUOTED "; 4483 1.1 mrg i = buf.insert(iCodeStart, pre); 4484 1.1 mrg i = buf.insert(i, codebuf[]); 4485 1.1 mrg i = buf.insert(i, ")"); 4486 1.1 mrg i--; // point to the ending ) so when the for loop does i++, it will see the next character 4487 1.1 mrg break; 4488 1.1 mrg } 4489 1.1 mrg 4490 1.1 mrg // Perhaps we're starting or ending a Markdown code block 4491 1.1 mrg if (leadingBlank && global.params.markdown && count >= 3) 4492 1.1 mrg { 4493 1.1 mrg bool moreBackticks = false; 4494 1.1 mrg for (size_t j = iAfterDelimiter; !moreBackticks && j < buf.length; ++j) 4495 1.1 mrg if (buf[j] == '`') 4496 1.1 mrg moreBackticks = true; 4497 1.1 mrg else if (buf[j] == '\r' || buf[j] == '\n') 4498 1.1 mrg break; 4499 1.1 mrg if (!moreBackticks) 4500 1.1 mrg goto case '-'; 4501 1.1 mrg } 4502 1.1 mrg 4503 1.1 mrg if (inCode) 4504 1.1 mrg { 4505 1.1 mrg if (inBacktick) 4506 1.1 mrg i = iAfterDelimiter - 1; 4507 1.1 mrg break; 4508 1.1 mrg } 4509 1.1 mrg inCode = c; 4510 1.1 mrg inBacktick = cast(int) count; 4511 1.1 mrg codeIndent = 0; // inline code is not indented 4512 1.1 mrg // All we do here is set the code flags and record 4513 1.1 mrg // the location. The macro will be inserted lazily 4514 1.1 mrg // so we can easily cancel the inBacktick if we come 4515 1.1 mrg // across a newline character. 4516 1.1 mrg iCodeStart = i; 4517 1.1 mrg i = iAfterDelimiter - 1; 4518 1.1 mrg break; 4519 1.1 mrg } 4520 1.1 mrg 4521 1.1 mrg case '#': 4522 1.1 mrg { 4523 1.1 mrg /* A line beginning with # indicates an ATX-style heading. */ 4524 1.1 mrg if (leadingBlank && !inCode) 4525 1.1 mrg { 4526 1.1 mrg leadingBlank = false; 4527 1.1 mrg 4528 1.1 mrg headingLevel = detectAtxHeadingLevel(buf, i); 4529 1.1 mrg if (!headingLevel) 4530 1.1 mrg break; 4531 1.1 mrg 4532 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 4533 1.1 mrg if (!lineQuoted && quoteLevel) 4534 1.1 mrg i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel); 4535 1.1 mrg 4536 1.1 mrg // remove the ### prefix, including whitespace 4537 1.1 mrg i = skipChars(buf, i + headingLevel, " \t"); 4538 1.1 mrg buf.remove(iLineStart, i - iLineStart); 4539 1.1 mrg i = iParagraphStart = iLineStart; 4540 1.1 mrg 4541 1.1 mrg removeAnyAtxHeadingSuffix(buf, i); 4542 1.1 mrg --i; 4543 1.1 mrg 4544 1.1 mrg headingMacroLevel = macroLevel; 4545 1.1 mrg } 4546 1.1 mrg break; 4547 1.1 mrg } 4548 1.1 mrg 4549 1.1 mrg case '~': 4550 1.1 mrg { 4551 1.1 mrg if (leadingBlank && global.params.markdown) 4552 1.1 mrg { 4553 1.1 mrg // Perhaps we're starting or ending a Markdown code block 4554 1.1 mrg const iAfterDelimiter = skipChars(buf, i, "~"); 4555 1.1 mrg if (iAfterDelimiter - i >= 3) 4556 1.1 mrg goto case '-'; 4557 1.1 mrg } 4558 1.1 mrg leadingBlank = false; 4559 1.1 mrg break; 4560 1.1 mrg } 4561 1.1 mrg 4562 1.1 mrg case '-': 4563 1.1 mrg /* A line beginning with --- delimits a code section. 4564 1.1 mrg * inCode tells us if it is start or end of a code section. 4565 1.1 mrg */ 4566 1.1 mrg if (leadingBlank) 4567 1.1 mrg { 4568 1.1 mrg if (!inCode && c == '-') 4569 1.1 mrg { 4570 1.1 mrg const list = MarkdownList.parseItem(buf, iLineStart, i); 4571 1.1 mrg if (list.isValid) 4572 1.1 mrg { 4573 1.1 mrg if (replaceMarkdownThematicBreak(buf, i, iLineStart, loc)) 4574 1.1 mrg { 4575 1.1 mrg removeBlankLineMacro(buf, iPrecedingBlankLine, i); 4576 1.1 mrg iParagraphStart = skipChars(buf, i+1, " \t\r\n"); 4577 1.1 mrg break; 4578 1.1 mrg } 4579 1.1 mrg else 4580 1.1 mrg goto case '+'; 4581 1.1 mrg } 4582 1.1 mrg } 4583 1.1 mrg 4584 1.1 mrg size_t istart = i; 4585 1.1 mrg size_t eollen = 0; 4586 1.1 mrg leadingBlank = false; 4587 1.1 mrg const c0 = c; // if we jumped here from case '`' or case '~' 4588 1.1 mrg size_t iInfoString = 0; 4589 1.1 mrg if (!inCode) 4590 1.1 mrg codeLanguage.length = 0; 4591 1.1 mrg while (1) 4592 1.1 mrg { 4593 1.1 mrg ++i; 4594 1.1 mrg if (i >= buf.length) 4595 1.1 mrg break; 4596 1.1 mrg c = buf[i]; 4597 1.1 mrg if (c == '\n') 4598 1.1 mrg { 4599 1.1 mrg eollen = 1; 4600 1.1 mrg break; 4601 1.1 mrg } 4602 1.1 mrg if (c == '\r') 4603 1.1 mrg { 4604 1.1 mrg eollen = 1; 4605 1.1 mrg if (i + 1 >= buf.length) 4606 1.1 mrg break; 4607 1.1 mrg if (buf[i + 1] == '\n') 4608 1.1 mrg { 4609 1.1 mrg eollen = 2; 4610 1.1 mrg break; 4611 1.1 mrg } 4612 1.1 mrg } 4613 1.1 mrg // BUG: handle UTF PS and LS too 4614 1.1 mrg if (c != c0 || iInfoString) 4615 1.1 mrg { 4616 1.1 mrg if (global.params.markdown && !iInfoString && !inCode && i - istart >= 3) 4617 1.1 mrg { 4618 1.1 mrg // Start a Markdown info string, like ```ruby 4619 1.1 mrg codeFenceLength = i - istart; 4620 1.1 mrg i = iInfoString = skipChars(buf, i, " \t"); 4621 1.1 mrg } 4622 1.1 mrg else if (iInfoString && c != '`') 4623 1.1 mrg { 4624 1.1 mrg if (!codeLanguage.length && (c == ' ' || c == '\t')) 4625 1.1 mrg codeLanguage = cast(string) buf[iInfoString..i].idup; 4626 1.1 mrg } 4627 1.1 mrg else 4628 1.1 mrg { 4629 1.1 mrg iInfoString = 0; 4630 1.1 mrg goto Lcont; 4631 1.1 mrg } 4632 1.1 mrg } 4633 1.1 mrg } 4634 1.1 mrg if (i - istart < 3 || (inCode && (inCode != c0 || (inCode != '-' && i - istart < codeFenceLength)))) 4635 1.1 mrg goto Lcont; 4636 1.1 mrg if (iInfoString) 4637 1.1 mrg { 4638 1.1 mrg if (!codeLanguage.length) 4639 1.1 mrg codeLanguage = cast(string) buf[iInfoString..i].idup; 4640 1.1 mrg } 4641 1.1 mrg else 4642 1.1 mrg codeFenceLength = i - istart; 4643 1.1 mrg 4644 1.1 mrg // We have the start/end of a code section 4645 1.1 mrg // Remove the entire --- line, including blanks and \n 4646 1.1 mrg buf.remove(iLineStart, i - iLineStart + eollen); 4647 1.1 mrg i = iLineStart; 4648 1.1 mrg if (eollen) 4649 1.1 mrg leadingBlank = true; 4650 1.1 mrg if (inCode && (i <= iCodeStart)) 4651 1.1 mrg { 4652 1.1 mrg // Empty code section, just remove it completely. 4653 1.1 mrg inCode = 0; 4654 1.1 mrg break; 4655 1.1 mrg } 4656 1.1 mrg if (inCode) 4657 1.1 mrg { 4658 1.1 mrg inCode = 0; 4659 1.1 mrg // The code section is from iCodeStart to i 4660 1.1 mrg OutBuffer codebuf; 4661 1.1 mrg codebuf.write(buf[iCodeStart .. i]); 4662 1.1 mrg codebuf.writeByte(0); 4663 1.1 mrg // Remove leading indentations from all lines 4664 1.1 mrg bool lineStart = true; 4665 1.1 mrg char* endp = cast(char*)codebuf[].ptr + codebuf.length; 4666 1.1 mrg for (char* p = cast(char*)codebuf[].ptr; p < endp;) 4667 1.1 mrg { 4668 1.1 mrg if (lineStart) 4669 1.1 mrg { 4670 1.1 mrg size_t j = codeIndent; 4671 1.1 mrg char* q = p; 4672 1.1 mrg while (j-- > 0 && q < endp && isIndentWS(q)) 4673 1.1 mrg ++q; 4674 1.1 mrg codebuf.remove(p - cast(char*)codebuf[].ptr, q - p); 4675 1.1 mrg assert(cast(char*)codebuf[].ptr <= p); 4676 1.1 mrg assert(p < cast(char*)codebuf[].ptr + codebuf.length); 4677 1.1 mrg lineStart = false; 4678 1.1 mrg endp = cast(char*)codebuf[].ptr + codebuf.length; // update 4679 1.1 mrg continue; 4680 1.1 mrg } 4681 1.1 mrg if (*p == '\n') 4682 1.1 mrg lineStart = true; 4683 1.1 mrg ++p; 4684 1.1 mrg } 4685 1.1 mrg if (!codeLanguage.length || codeLanguage == "dlang" || codeLanguage == "d") 4686 1.1 mrg highlightCode2(sc, a, codebuf, 0); 4687 1.1 mrg else 4688 1.1 mrg codebuf.remove(codebuf.length-1, 1); // remove the trailing 0 byte 4689 1.1 mrg escapeStrayParenthesis(loc, &codebuf, 0, false); 4690 1.1 mrg buf.remove(iCodeStart, i - iCodeStart); 4691 1.1 mrg i = buf.insert(iCodeStart, codebuf[]); 4692 1.1 mrg i = buf.insert(i, ")\n"); 4693 1.1 mrg i -= 2; // in next loop, c should be '\n' 4694 1.1 mrg } 4695 1.1 mrg else 4696 1.1 mrg { 4697 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 4698 1.1 mrg if (!lineQuoted && quoteLevel) 4699 1.1 mrg { 4700 1.1 mrg const delta = endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel); 4701 1.1 mrg i += delta; 4702 1.1 mrg istart += delta; 4703 1.1 mrg } 4704 1.1 mrg 4705 1.1 mrg inCode = c0; 4706 1.1 mrg codeIndent = istart - iLineStart; // save indent count 4707 1.1 mrg if (codeLanguage.length && codeLanguage != "dlang" && codeLanguage != "d") 4708 1.1 mrg { 4709 1.1 mrg // backslash-escape 4710 1.1 mrg for (size_t j; j < codeLanguage.length - 1; ++j) 4711 1.1 mrg if (codeLanguage[j] == '\\' && ispunct(codeLanguage[j + 1])) 4712 1.1 mrg codeLanguage = codeLanguage[0..j] ~ codeLanguage[j + 1..$]; 4713 1.1 mrg 4714 1.1 mrg if (global.params.vmarkdown) 4715 1.1 mrg message(loc, "Ddoc: adding code block for language '%.*s'", cast(int)codeLanguage.length, codeLanguage.ptr); 4716 1.1 mrg 4717 1.1 mrg i = buf.insert(i, "$(OTHER_CODE "); 4718 1.1 mrg i = buf.insert(i, codeLanguage); 4719 1.1 mrg i = buf.insert(i, ","); 4720 1.1 mrg } 4721 1.1 mrg else 4722 1.1 mrg i = buf.insert(i, "$(D_CODE "); 4723 1.1 mrg iCodeStart = i; 4724 1.1 mrg i--; // place i on > 4725 1.1 mrg leadingBlank = true; 4726 1.1 mrg } 4727 1.1 mrg } 4728 1.1 mrg break; 4729 1.1 mrg 4730 1.1 mrg case '_': 4731 1.1 mrg { 4732 1.1 mrg if (leadingBlank && !inCode && replaceMarkdownThematicBreak(buf, i, iLineStart, loc)) 4733 1.1 mrg { 4734 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 4735 1.1 mrg if (!lineQuoted && quoteLevel) 4736 1.1 mrg i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel); 4737 1.1 mrg removeBlankLineMacro(buf, iPrecedingBlankLine, i); 4738 1.1 mrg iParagraphStart = skipChars(buf, i+1, " \t\r\n"); 4739 1.1 mrg break; 4740 1.1 mrg } 4741 1.1 mrg goto default; 4742 1.1 mrg } 4743 1.1 mrg 4744 1.1 mrg case '+': 4745 1.1 mrg case '0': 4746 1.1 mrg .. 4747 1.1 mrg case '9': 4748 1.1 mrg { 4749 1.1 mrg if (leadingBlank && !inCode) 4750 1.1 mrg { 4751 1.1 mrg MarkdownList list = MarkdownList.parseItem(buf, iLineStart, i); 4752 1.1 mrg if (list.isValid) 4753 1.1 mrg { 4754 1.1 mrg // Avoid starting a numbered list in the middle of a paragraph 4755 1.1 mrg if (!nestedLists.length && list.orderedStart.length && 4756 1.1 mrg iParagraphStart < iLineStart) 4757 1.1 mrg { 4758 1.1 mrg i += list.orderedStart.length - 1; 4759 1.1 mrg break; 4760 1.1 mrg } 4761 1.1 mrg 4762 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 4763 1.1 mrg if (!lineQuoted && quoteLevel) 4764 1.1 mrg { 4765 1.1 mrg const delta = endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel); 4766 1.1 mrg i += delta; 4767 1.1 mrg list.iStart += delta; 4768 1.1 mrg list.iContentStart += delta; 4769 1.1 mrg } 4770 1.1 mrg 4771 1.1 mrg list.macroLevel = macroLevel; 4772 1.1 mrg list.startItem(buf, iLineStart, i, iPrecedingBlankLine, nestedLists, loc); 4773 1.1 mrg break; 4774 1.1 mrg } 4775 1.1 mrg } 4776 1.1 mrg leadingBlank = false; 4777 1.1 mrg break; 4778 1.1 mrg } 4779 1.1 mrg 4780 1.1 mrg case '*': 4781 1.1 mrg { 4782 1.1 mrg if (inCode || inBacktick || !global.params.markdown) 4783 1.1 mrg { 4784 1.1 mrg leadingBlank = false; 4785 1.1 mrg break; 4786 1.1 mrg } 4787 1.1 mrg 4788 1.1 mrg if (leadingBlank) 4789 1.1 mrg { 4790 1.1 mrg // Check for a thematic break 4791 1.1 mrg if (replaceMarkdownThematicBreak(buf, i, iLineStart, loc)) 4792 1.1 mrg { 4793 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 4794 1.1 mrg if (!lineQuoted && quoteLevel) 4795 1.1 mrg i += endAllListsAndQuotes(buf, iLineStart, nestedLists, quoteLevel, quoteMacroLevel); 4796 1.1 mrg removeBlankLineMacro(buf, iPrecedingBlankLine, i); 4797 1.1 mrg iParagraphStart = skipChars(buf, i+1, " \t\r\n"); 4798 1.1 mrg break; 4799 1.1 mrg } 4800 1.1 mrg 4801 1.1 mrg // An initial * indicates a Markdown list item 4802 1.1 mrg const list = MarkdownList.parseItem(buf, iLineStart, i); 4803 1.1 mrg if (list.isValid) 4804 1.1 mrg goto case '+'; 4805 1.1 mrg } 4806 1.1 mrg 4807 1.1 mrg // Markdown emphasis 4808 1.1 mrg const leftC = i > offset ? buf[i-1] : '\0'; 4809 1.1 mrg size_t iAfterEmphasis = skipChars(buf, i+1, "*"); 4810 1.1 mrg const rightC = iAfterEmphasis < buf.length ? buf[iAfterEmphasis] : '\0'; 4811 1.1 mrg int count = cast(int) (iAfterEmphasis - i); 4812 1.1 mrg const leftFlanking = (rightC != '\0' && !isspace(rightC)) && (!ispunct(rightC) || leftC == '\0' || isspace(leftC) || ispunct(leftC)); 4813 1.1 mrg const rightFlanking = (leftC != '\0' && !isspace(leftC)) && (!ispunct(leftC) || rightC == '\0' || isspace(rightC) || ispunct(rightC)); 4814 1.1 mrg auto emphasis = MarkdownDelimiter(i, count, macroLevel, leftFlanking, rightFlanking, false, c); 4815 1.1 mrg 4816 1.1 mrg if (!emphasis.leftFlanking && !emphasis.rightFlanking) 4817 1.1 mrg { 4818 1.1 mrg i = iAfterEmphasis - 1; 4819 1.1 mrg break; 4820 1.1 mrg } 4821 1.1 mrg 4822 1.1 mrg inlineDelimiters ~= emphasis; 4823 1.1 mrg i += emphasis.count; 4824 1.1 mrg --i; 4825 1.1 mrg break; 4826 1.1 mrg } 4827 1.1 mrg 4828 1.1 mrg case '!': 4829 1.1 mrg { 4830 1.1 mrg leadingBlank = false; 4831 1.1 mrg 4832 1.1 mrg if (inCode || !global.params.markdown) 4833 1.1 mrg break; 4834 1.1 mrg 4835 1.1 mrg if (i < buf.length-1 && buf[i+1] == '[') 4836 1.1 mrg { 4837 1.1 mrg const imageStart = MarkdownDelimiter(i, 2, macroLevel, false, false, false, c); 4838 1.1 mrg inlineDelimiters ~= imageStart; 4839 1.1 mrg ++i; 4840 1.1 mrg } 4841 1.1 mrg break; 4842 1.1 mrg } 4843 1.1 mrg case '[': 4844 1.1 mrg { 4845 1.1 mrg if (inCode || !global.params.markdown) 4846 1.1 mrg { 4847 1.1 mrg leadingBlank = false; 4848 1.1 mrg break; 4849 1.1 mrg } 4850 1.1 mrg 4851 1.1 mrg const leftC = i > offset ? buf[i-1] : '\0'; 4852 1.1 mrg const rightFlanking = leftC != '\0' && !isspace(leftC) && !ispunct(leftC); 4853 1.1 mrg const atParagraphStart = leadingBlank && iParagraphStart >= iLineStart; 4854 1.1 mrg const linkStart = MarkdownDelimiter(i, 1, macroLevel, false, rightFlanking, atParagraphStart, c); 4855 1.1 mrg inlineDelimiters ~= linkStart; 4856 1.1 mrg leadingBlank = false; 4857 1.1 mrg break; 4858 1.1 mrg } 4859 1.1 mrg case ']': 4860 1.1 mrg { 4861 1.1 mrg leadingBlank = false; 4862 1.1 mrg 4863 1.1 mrg if (inCode || !global.params.markdown) 4864 1.1 mrg break; 4865 1.1 mrg 4866 1.1 mrg for (int d = cast(int) inlineDelimiters.length - 1; d >= 0; --d) 4867 1.1 mrg { 4868 1.1 mrg const delimiter = inlineDelimiters[d]; 4869 1.1 mrg if (delimiter.type == '[' || delimiter.type == '!') 4870 1.1 mrg { 4871 1.1 mrg if (delimiter.isValid && 4872 1.1 mrg MarkdownLink.replaceLink(buf, i, loc, inlineDelimiters, d, linkReferences)) 4873 1.1 mrg { 4874 1.1 mrg // if we removed a reference link then we're at line start 4875 1.1 mrg if (i <= delimiter.iStart) 4876 1.1 mrg leadingBlank = true; 4877 1.1 mrg 4878 1.1 mrg // don't nest links 4879 1.1 mrg if (delimiter.type == '[') 4880 1.1 mrg for (--d; d >= 0; --d) 4881 1.1 mrg if (inlineDelimiters[d].type == '[') 4882 1.1 mrg inlineDelimiters[d].invalidate(); 4883 1.1 mrg } 4884 1.1 mrg else 4885 1.1 mrg { 4886 1.1 mrg // nothing found, so kill the delimiter 4887 1.1 mrg inlineDelimiters = inlineDelimiters[0..d] ~ inlineDelimiters[d+1..$]; 4888 1.1 mrg } 4889 1.1 mrg break; 4890 1.1 mrg } 4891 1.1 mrg } 4892 1.1 mrg break; 4893 1.1 mrg } 4894 1.1 mrg 4895 1.1 mrg case '|': 4896 1.1 mrg { 4897 1.1 mrg if (inCode || !global.params.markdown) 4898 1.1 mrg { 4899 1.1 mrg leadingBlank = false; 4900 1.1 mrg break; 4901 1.1 mrg } 4902 1.1 mrg 4903 1.1 mrg tableRowDetected = true; 4904 1.1 mrg inlineDelimiters ~= MarkdownDelimiter(i, 1, macroLevel, leadingBlank, false, false, c); 4905 1.1 mrg leadingBlank = false; 4906 1.1 mrg break; 4907 1.1 mrg } 4908 1.1 mrg 4909 1.1 mrg case '\\': 4910 1.1 mrg { 4911 1.1 mrg leadingBlank = false; 4912 1.1 mrg if (inCode || i+1 >= buf.length || !global.params.markdown) 4913 1.1 mrg break; 4914 1.1 mrg 4915 1.1 mrg /* Escape Markdown special characters */ 4916 1.1 mrg char c1 = buf[i+1]; 4917 1.1 mrg if (ispunct(c1)) 4918 1.1 mrg { 4919 1.1 mrg if (global.params.vmarkdown) 4920 1.1 mrg message(loc, "Ddoc: backslash-escaped %c", c1); 4921 1.1 mrg 4922 1.1 mrg buf.remove(i, 1); 4923 1.1 mrg 4924 1.1 mrg auto se = sc._module.escapetable.escapeChar(c1); 4925 1.1 mrg if (!se) 4926 1.1 mrg se = c1 == '$' ? "$(DOLLAR)" : c1 == ',' ? "$(COMMA)" : null; 4927 1.1 mrg if (se) 4928 1.1 mrg { 4929 1.1 mrg buf.remove(i, 1); 4930 1.1 mrg i = buf.insert(i, se); 4931 1.1 mrg i--; // point to escaped char 4932 1.1 mrg } 4933 1.1 mrg } 4934 1.1 mrg break; 4935 1.1 mrg } 4936 1.1 mrg 4937 1.1 mrg case '$': 4938 1.1 mrg { 4939 1.1 mrg /* Look for the start of a macro, '$(Identifier' 4940 1.1 mrg */ 4941 1.1 mrg leadingBlank = false; 4942 1.1 mrg if (inCode || inBacktick) 4943 1.1 mrg break; 4944 1.1 mrg const slice = buf[]; 4945 1.1 mrg auto p = &slice[i]; 4946 1.1 mrg if (p[1] == '(' && isIdStart(&p[2])) 4947 1.1 mrg ++macroLevel; 4948 1.1 mrg break; 4949 1.1 mrg } 4950 1.1 mrg 4951 1.1 mrg case '(': 4952 1.1 mrg { 4953 1.1 mrg if (!inCode && i > offset && buf[i-1] != '$') 4954 1.1 mrg ++parenLevel; 4955 1.1 mrg break; 4956 1.1 mrg } 4957 1.1 mrg 4958 1.1 mrg case ')': 4959 1.1 mrg { /* End of macro 4960 1.1 mrg */ 4961 1.1 mrg leadingBlank = false; 4962 1.1 mrg if (inCode || inBacktick) 4963 1.1 mrg break; 4964 1.1 mrg if (parenLevel > 0) 4965 1.1 mrg --parenLevel; 4966 1.1 mrg else if (macroLevel) 4967 1.1 mrg { 4968 1.1 mrg int downToLevel = cast(int) inlineDelimiters.length; 4969 1.1 mrg while (downToLevel > 0 && inlineDelimiters[downToLevel - 1].macroLevel >= macroLevel) 4970 1.1 mrg --downToLevel; 4971 1.1 mrg if (headingLevel && headingMacroLevel >= macroLevel) 4972 1.1 mrg { 4973 1.1 mrg endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel); 4974 1.1 mrg removeBlankLineMacro(buf, iPrecedingBlankLine, i); 4975 1.1 mrg } 4976 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 4977 1.1 mrg while (nestedLists.length && nestedLists[$-1].macroLevel >= macroLevel) 4978 1.1 mrg { 4979 1.1 mrg i = buf.insert(i, ")\n)"); 4980 1.1 mrg --nestedLists.length; 4981 1.1 mrg } 4982 1.1 mrg if (quoteLevel && quoteMacroLevel >= macroLevel) 4983 1.1 mrg i += endAllMarkdownQuotes(buf, i, quoteLevel); 4984 1.1 mrg i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters, downToLevel); 4985 1.1 mrg 4986 1.1 mrg --macroLevel; 4987 1.1 mrg quoteMacroLevel = 0; 4988 1.1 mrg } 4989 1.1 mrg break; 4990 1.1 mrg } 4991 1.1 mrg 4992 1.1 mrg default: 4993 1.1 mrg leadingBlank = false; 4994 1.1 mrg if (sc._module.filetype == FileType.ddoc || inCode) 4995 1.1 mrg break; 4996 1.1 mrg const start = cast(char*)buf[].ptr + i; 4997 1.1 mrg if (isIdStart(start)) 4998 1.1 mrg { 4999 1.1 mrg size_t j = skippastident(buf, i); 5000 1.1 mrg if (i < j) 5001 1.1 mrg { 5002 1.1 mrg size_t k = skippastURL(buf, i); 5003 1.1 mrg if (i < k) 5004 1.1 mrg { 5005 1.1 mrg /* The URL is buf[i..k] 5006 1.1 mrg */ 5007 1.1 mrg if (macroLevel) 5008 1.1 mrg /* Leave alone if already in a macro 5009 1.1 mrg */ 5010 1.1 mrg i = k - 1; 5011 1.1 mrg else 5012 1.1 mrg { 5013 1.1 mrg /* Replace URL with '$(DDOC_LINK_AUTODETECT URL)' 5014 1.1 mrg */ 5015 1.1 mrg i = buf.bracket(i, "$(DDOC_LINK_AUTODETECT ", k, ")") - 1; 5016 1.1 mrg } 5017 1.1 mrg break; 5018 1.1 mrg } 5019 1.1 mrg } 5020 1.1 mrg else 5021 1.1 mrg break; 5022 1.1 mrg size_t len = j - i; 5023 1.1 mrg // leading '_' means no highlight unless it's a reserved symbol name 5024 1.1 mrg if (c == '_' && (i == 0 || !isdigit(*(start - 1))) && (i == buf.length - 1 || !isReservedName(start[0 .. len]))) 5025 1.1 mrg { 5026 1.1 mrg buf.remove(i, 1); 5027 1.1 mrg i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL_SUPPRESS ", j - 1, ")") - 1; 5028 1.1 mrg break; 5029 1.1 mrg } 5030 1.1 mrg if (isIdentifier(a, start, len)) 5031 1.1 mrg { 5032 1.1 mrg i = buf.bracket(i, "$(DDOC_AUTO_PSYMBOL ", j, ")") - 1; 5033 1.1 mrg break; 5034 1.1 mrg } 5035 1.1 mrg if (isKeyword(start, len)) 5036 1.1 mrg { 5037 1.1 mrg i = buf.bracket(i, "$(DDOC_AUTO_KEYWORD ", j, ")") - 1; 5038 1.1 mrg break; 5039 1.1 mrg } 5040 1.1 mrg if (isFunctionParameter(a, start, len)) 5041 1.1 mrg { 5042 1.1 mrg //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j); 5043 1.1 mrg i = buf.bracket(i, "$(DDOC_AUTO_PARAM ", j, ")") - 1; 5044 1.1 mrg break; 5045 1.1 mrg } 5046 1.1 mrg i = j - 1; 5047 1.1 mrg } 5048 1.1 mrg break; 5049 1.1 mrg } 5050 1.1 mrg } 5051 1.1 mrg 5052 1.1 mrg if (inCode == '-') 5053 1.1 mrg error(loc, "unmatched `---` in DDoc comment"); 5054 1.1 mrg else if (inCode) 5055 1.1 mrg buf.insert(buf.length, ")"); 5056 1.1 mrg 5057 1.1 mrg size_t i = buf.length; 5058 1.1 mrg if (headingLevel) 5059 1.1 mrg { 5060 1.1 mrg endMarkdownHeading(buf, iParagraphStart, i, loc, headingLevel); 5061 1.1 mrg removeBlankLineMacro(buf, iPrecedingBlankLine, i); 5062 1.1 mrg } 5063 1.1 mrg i += endRowAndTable(buf, iLineStart, i, loc, inlineDelimiters, columnAlignments); 5064 1.1 mrg i += replaceMarkdownEmphasis(buf, loc, inlineDelimiters); 5065 1.1 mrg endAllListsAndQuotes(buf, i, nestedLists, quoteLevel, quoteMacroLevel); 5066 1.1 mrg } 5067 1.1 mrg 5068 1.1 mrg /************************************************** 5069 1.1 mrg * Highlight code for DDOC section. 5070 1.1 mrg */ 5071 1.1 mrg private void highlightCode(Scope* sc, Dsymbol s, ref OutBuffer buf, size_t offset) 5072 1.1 mrg { 5073 1.1 mrg auto imp = s.isImport(); 5074 1.1 mrg if (imp && imp.aliases.dim > 0) 5075 1.1 mrg { 5076 1.1 mrg // For example: `public import core.stdc.string : memcpy, memcmp;` 5077 1.1 mrg for(int i = 0; i < imp.aliases.dim; i++) 5078 1.1 mrg { 5079 1.1 mrg // Need to distinguish between 5080 1.1 mrg // `public import core.stdc.string : memcpy, memcmp;` and 5081 1.1 mrg // `public import core.stdc.string : copy = memcpy, compare = memcmp;` 5082 1.1 mrg auto a = imp.aliases[i]; 5083 1.1 mrg auto id = a ? a : imp.names[i]; 5084 1.1 mrg auto loc = Loc.init; 5085 1.1 mrg if (auto symFromId = sc.search(loc, id, null)) 5086 1.1 mrg { 5087 1.1 mrg highlightCode(sc, symFromId, buf, offset); 5088 1.1 mrg } 5089 1.1 mrg } 5090 1.1 mrg } 5091 1.1 mrg else 5092 1.1 mrg { 5093 1.1 mrg OutBuffer ancbuf; 5094 1.1 mrg emitAnchor(ancbuf, s, sc); 5095 1.1 mrg buf.insert(offset, ancbuf[]); 5096 1.1 mrg offset += ancbuf.length; 5097 1.1 mrg 5098 1.1 mrg Dsymbols a; 5099 1.1 mrg a.push(s); 5100 1.1 mrg highlightCode(sc, &a, buf, offset); 5101 1.1 mrg } 5102 1.1 mrg } 5103 1.1 mrg 5104 1.1 mrg /**************************************************** 5105 1.1 mrg */ 5106 1.1 mrg private void highlightCode(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset) 5107 1.1 mrg { 5108 1.1 mrg //printf("highlightCode(a = '%s')\n", a.toChars()); 5109 1.1 mrg bool resolvedTemplateParameters = false; 5110 1.1 mrg 5111 1.1 mrg for (size_t i = offset; i < buf.length; i++) 5112 1.1 mrg { 5113 1.1 mrg char c = buf[i]; 5114 1.1 mrg const se = sc._module.escapetable.escapeChar(c); 5115 1.1 mrg if (se.length) 5116 1.1 mrg { 5117 1.1 mrg buf.remove(i, 1); 5118 1.1 mrg i = buf.insert(i, se); 5119 1.1 mrg i--; // point to ';' 5120 1.1 mrg continue; 5121 1.1 mrg } 5122 1.1 mrg char* start = cast(char*)buf[].ptr + i; 5123 1.1 mrg if (isIdStart(start)) 5124 1.1 mrg { 5125 1.1 mrg size_t j = skipPastIdentWithDots(buf, i); 5126 1.1 mrg if (i < j) 5127 1.1 mrg { 5128 1.1 mrg size_t len = j - i; 5129 1.1 mrg if (isIdentifier(a, start, len)) 5130 1.1 mrg { 5131 1.1 mrg i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; 5132 1.1 mrg continue; 5133 1.1 mrg } 5134 1.1 mrg } 5135 1.1 mrg 5136 1.1 mrg j = skippastident(buf, i); 5137 1.1 mrg if (i < j) 5138 1.1 mrg { 5139 1.1 mrg size_t len = j - i; 5140 1.1 mrg if (isIdentifier(a, start, len)) 5141 1.1 mrg { 5142 1.1 mrg i = buf.bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1; 5143 1.1 mrg continue; 5144 1.1 mrg } 5145 1.1 mrg if (isFunctionParameter(a, start, len)) 5146 1.1 mrg { 5147 1.1 mrg //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j); 5148 1.1 mrg i = buf.bracket(i, "$(DDOC_PARAM ", j, ")") - 1; 5149 1.1 mrg continue; 5150 1.1 mrg } 5151 1.1 mrg i = j - 1; 5152 1.1 mrg } 5153 1.1 mrg } 5154 1.1 mrg else if (!resolvedTemplateParameters) 5155 1.1 mrg { 5156 1.1 mrg size_t previ = i; 5157 1.1 mrg 5158 1.1 mrg // hunt for template declarations: 5159 1.1 mrg foreach (symi; 0 .. a.dim) 5160 1.1 mrg { 5161 1.1 mrg FuncDeclaration fd = (*a)[symi].isFuncDeclaration(); 5162 1.1 mrg 5163 1.1 mrg if (!fd || !fd.parent || !fd.parent.isTemplateDeclaration()) 5164 1.1 mrg { 5165 1.1 mrg continue; 5166 1.1 mrg } 5167 1.1 mrg 5168 1.1 mrg TemplateDeclaration td = fd.parent.isTemplateDeclaration(); 5169 1.1 mrg 5170 1.1 mrg // build the template parameters 5171 1.1 mrg Array!(size_t) paramLens; 5172 1.1 mrg paramLens.reserve(td.parameters.dim); 5173 1.1 mrg 5174 1.1 mrg OutBuffer parametersBuf; 5175 1.1 mrg HdrGenState hgs; 5176 1.1 mrg 5177 1.1 mrg parametersBuf.writeByte('('); 5178 1.1 mrg 5179 1.1 mrg foreach (parami; 0 .. td.parameters.dim) 5180 1.1 mrg { 5181 1.1 mrg TemplateParameter tp = (*td.parameters)[parami]; 5182 1.1 mrg 5183 1.1 mrg if (parami) 5184 1.1 mrg parametersBuf.writestring(", "); 5185 1.1 mrg 5186 1.1 mrg size_t lastOffset = parametersBuf.length; 5187 1.1 mrg 5188 1.1 mrg .toCBuffer(tp, ¶metersBuf, &hgs); 5189 1.1 mrg 5190 1.1 mrg paramLens[parami] = parametersBuf.length - lastOffset; 5191 1.1 mrg } 5192 1.1 mrg parametersBuf.writeByte(')'); 5193 1.1 mrg 5194 1.1 mrg const templateParams = parametersBuf[]; 5195 1.1 mrg 5196 1.1 mrg //printf("templateDecl: %s\ntemplateParams: %s\nstart: %s\n", td.toChars(), templateParams, start); 5197 1.1 mrg if (start[0 .. templateParams.length] == templateParams) 5198 1.1 mrg { 5199 1.1 mrg immutable templateParamListMacro = "$(DDOC_TEMPLATE_PARAM_LIST "; 5200 1.1 mrg buf.bracket(i, templateParamListMacro.ptr, i + templateParams.length, ")"); 5201 1.1 mrg 5202 1.1 mrg // We have the parameter list. While we're here we might 5203 1.1 mrg // as well wrap the parameters themselves as well 5204 1.1 mrg 5205 1.1 mrg // + 1 here to take into account the opening paren of the 5206 1.1 mrg // template param list 5207 1.1 mrg i += templateParamListMacro.length + 1; 5208 1.1 mrg 5209 1.1 mrg foreach (const len; paramLens) 5210 1.1 mrg { 5211 1.1 mrg i = buf.bracket(i, "$(DDOC_TEMPLATE_PARAM ", i + len, ")"); 5212 1.1 mrg // increment two here for space + comma 5213 1.1 mrg i += 2; 5214 1.1 mrg } 5215 1.1 mrg 5216 1.1 mrg resolvedTemplateParameters = true; 5217 1.1 mrg // reset i to be positioned back before we found the template 5218 1.1 mrg // param list this assures that anything within the template 5219 1.1 mrg // param list that needs to be escaped or otherwise altered 5220 1.1 mrg // has an opportunity for that to happen outside of this context 5221 1.1 mrg i = previ; 5222 1.1 mrg 5223 1.1 mrg continue; 5224 1.1 mrg } 5225 1.1 mrg } 5226 1.1 mrg } 5227 1.1 mrg } 5228 1.1 mrg } 5229 1.1 mrg 5230 1.1 mrg /**************************************** 5231 1.1 mrg */ 5232 1.1 mrg private void highlightCode3(Scope* sc, ref OutBuffer buf, const(char)* p, const(char)* pend) 5233 1.1 mrg { 5234 1.1 mrg for (; p < pend; p++) 5235 1.1 mrg { 5236 1.1 mrg const se = sc._module.escapetable.escapeChar(*p); 5237 1.1 mrg if (se.length) 5238 1.1 mrg buf.writestring(se); 5239 1.1 mrg else 5240 1.1 mrg buf.writeByte(*p); 5241 1.1 mrg } 5242 1.1 mrg } 5243 1.1 mrg 5244 1.1 mrg /************************************************** 5245 1.1 mrg * Highlight code for CODE section. 5246 1.1 mrg */ 5247 1.1 mrg private void highlightCode2(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset) 5248 1.1 mrg { 5249 1.1 mrg uint errorsave = global.startGagging(); 5250 1.1 mrg 5251 1.1 mrg scope Lexer lex = new Lexer(null, cast(char*)buf[].ptr, 0, buf.length - 1, 0, 1); 5252 1.1 mrg OutBuffer res; 5253 1.1 mrg const(char)* lastp = cast(char*)buf[].ptr; 5254 1.1 mrg //printf("highlightCode2('%.*s')\n", cast(int)(buf.length - 1), buf[].ptr); 5255 1.1 mrg res.reserve(buf.length); 5256 1.1 mrg while (1) 5257 1.1 mrg { 5258 1.1 mrg Token tok; 5259 1.1 mrg lex.scan(&tok); 5260 1.1 mrg highlightCode3(sc, res, lastp, tok.ptr); 5261 1.1 mrg string highlight = null; 5262 1.1 mrg switch (tok.value) 5263 1.1 mrg { 5264 1.1 mrg case TOK.identifier: 5265 1.1 mrg { 5266 1.1 mrg if (!sc) 5267 1.1 mrg break; 5268 1.1 mrg size_t len = lex.p - tok.ptr; 5269 1.1 mrg if (isIdentifier(a, tok.ptr, len)) 5270 1.1 mrg { 5271 1.1 mrg highlight = "$(D_PSYMBOL "; 5272 1.1 mrg break; 5273 1.1 mrg } 5274 1.1 mrg if (isFunctionParameter(a, tok.ptr, len)) 5275 1.1 mrg { 5276 1.1 mrg //printf("highlighting arg '%s', i = %d, j = %d\n", arg.ident.toChars(), i, j); 5277 1.1 mrg highlight = "$(D_PARAM "; 5278 1.1 mrg break; 5279 1.1 mrg } 5280 1.1 mrg break; 5281 1.1 mrg } 5282 1.1 mrg case TOK.comment: 5283 1.1 mrg highlight = "$(D_COMMENT "; 5284 1.1 mrg break; 5285 1.1 mrg case TOK.string_: 5286 1.1 mrg highlight = "$(D_STRING "; 5287 1.1 mrg break; 5288 1.1 mrg default: 5289 1.1 mrg if (tok.isKeyword()) 5290 1.1 mrg highlight = "$(D_KEYWORD "; 5291 1.1 mrg break; 5292 1.1 mrg } 5293 1.1 mrg if (highlight) 5294 1.1 mrg { 5295 1.1 mrg res.writestring(highlight); 5296 1.1 mrg size_t o = res.length; 5297 1.1 mrg highlightCode3(sc, res, tok.ptr, lex.p); 5298 1.1 mrg if (tok.value == TOK.comment || tok.value == TOK.string_) 5299 1.1 mrg /* https://issues.dlang.org/show_bug.cgi?id=7656 5300 1.1 mrg * https://issues.dlang.org/show_bug.cgi?id=7715 5301 1.1 mrg * https://issues.dlang.org/show_bug.cgi?id=10519 5302 1.1 mrg */ 5303 1.1 mrg escapeDdocString(&res, o); 5304 1.1 mrg res.writeByte(')'); 5305 1.1 mrg } 5306 1.1 mrg else 5307 1.1 mrg highlightCode3(sc, res, tok.ptr, lex.p); 5308 1.1 mrg if (tok.value == TOK.endOfFile) 5309 1.1 mrg break; 5310 1.1 mrg lastp = lex.p; 5311 1.1 mrg } 5312 1.1 mrg buf.setsize(offset); 5313 1.1 mrg buf.write(&res); 5314 1.1 mrg global.endGagging(errorsave); 5315 1.1 mrg } 5316 1.1 mrg 5317 1.1 mrg /**************************************** 5318 1.1 mrg * Determine if p points to the start of a "..." parameter identifier. 5319 1.1 mrg */ 5320 1.1 mrg private bool isCVariadicArg(const(char)[] p) 5321 1.1 mrg { 5322 1.1 mrg return p.length >= 3 && p[0 .. 3] == "..."; 5323 1.1 mrg } 5324 1.1 mrg 5325 1.1 mrg /**************************************** 5326 1.1 mrg * Determine if p points to the start of an identifier. 5327 1.1 mrg */ 5328 1.1 mrg bool isIdStart(const(char)* p) 5329 1.1 mrg { 5330 1.1 mrg dchar c = *p; 5331 1.1 mrg if (isalpha(c) || c == '_') 5332 1.1 mrg return true; 5333 1.1 mrg if (c >= 0x80) 5334 1.1 mrg { 5335 1.1 mrg size_t i = 0; 5336 1.1 mrg if (utf_decodeChar(p[0 .. 4], i, c)) 5337 1.1 mrg return false; // ignore errors 5338 1.1 mrg if (isUniAlpha(c)) 5339 1.1 mrg return true; 5340 1.1 mrg } 5341 1.1 mrg return false; 5342 1.1 mrg } 5343 1.1 mrg 5344 1.1 mrg /**************************************** 5345 1.1 mrg * Determine if p points to the rest of an identifier. 5346 1.1 mrg */ 5347 1.1 mrg bool isIdTail(const(char)* p) 5348 1.1 mrg { 5349 1.1 mrg dchar c = *p; 5350 1.1 mrg if (isalnum(c) || c == '_') 5351 1.1 mrg return true; 5352 1.1 mrg if (c >= 0x80) 5353 1.1 mrg { 5354 1.1 mrg size_t i = 0; 5355 1.1 mrg if (utf_decodeChar(p[0 .. 4], i, c)) 5356 1.1 mrg return false; // ignore errors 5357 1.1 mrg if (isUniAlpha(c)) 5358 1.1 mrg return true; 5359 1.1 mrg } 5360 1.1 mrg return false; 5361 1.1 mrg } 5362 1.1 mrg 5363 1.1 mrg /**************************************** 5364 1.1 mrg * Determine if p points to the indentation space. 5365 1.1 mrg */ 5366 1.1 mrg private bool isIndentWS(const(char)* p) 5367 1.1 mrg { 5368 1.1 mrg return (*p == ' ') || (*p == '\t'); 5369 1.1 mrg } 5370 1.1 mrg 5371 1.1 mrg /***************************************** 5372 1.1 mrg * Return number of bytes in UTF character. 5373 1.1 mrg */ 5374 1.1 mrg int utfStride(const(char)* p) 5375 1.1 mrg { 5376 1.1 mrg dchar c = *p; 5377 1.1 mrg if (c < 0x80) 5378 1.1 mrg return 1; 5379 1.1 mrg size_t i = 0; 5380 1.1 mrg utf_decodeChar(p[0 .. 4], i, c); // ignore errors, but still consume input 5381 1.1 mrg return cast(int)i; 5382 1.1 mrg } 5383 1.1 mrg 5384 1.1 mrg private inout(char)* stripLeadingNewlines(inout(char)* s) 5385 1.1 mrg { 5386 1.1 mrg while (s && *s == '\n' || *s == '\r') 5387 1.1 mrg s++; 5388 1.1 mrg 5389 1.1 mrg return s; 5390 1.1 mrg } 5391