/** * Text macro processor for Ddoc. * * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dmacro.d, _dmacro.d) * Documentation: https://dlang.org/phobos/dmd_dmacro.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dmacro.d */ module dmd.dmacro; import core.stdc.ctype; import core.stdc.string; import dmd.doc; import dmd.common.outbuffer; import dmd.root.rmem; extern (C++) struct MacroTable { /********************************** * Define name=text macro. * If macro `name` already exists, replace the text for it. * Params: * name = name of macro * text = text of macro */ extern (D) void define(const(char)[] name, const(char)[] text) nothrow pure @safe { //printf("MacroTable::define('%.*s' = '%.*s')\n", cast(int)name.length, name.ptr, text.length, text.ptr); if (auto table = name in mactab) { (*table).text = text; return; } mactab[name] = new Macro(name, text); } /***************************************************** * Look for macros in buf and expand them in place. * Only look at the text in buf from start to pend. * * Returns: `true` on success, `false` when the recursion limit was reached */ extern (D) bool expand(ref OutBuffer buf, size_t start, ref size_t pend, const(char)[] arg, int recursionLimit) nothrow pure { version (none) { printf("Macro::expand(buf[%d..%d], arg = '%.*s')\n", start, pend, cast(int)arg.length, arg.ptr); printf("Buf is: '%.*s'\n", cast(int)(pend - start), buf.data + start); } // limit recursive expansion recursionLimit--; if (recursionLimit < 0) return false; size_t end = pend; assert(start <= end); assert(end <= buf.length); /* First pass - replace $0 */ arg = memdup(arg); for (size_t u = start; u + 1 < end;) { char* p = cast(char*)buf[].ptr; // buf.data is not loop invariant /* Look for $0, but not $$0, and replace it with arg. */ if (p[u] == '$' && (isdigit(p[u + 1]) || p[u + 1] == '+')) { if (u > start && p[u - 1] == '$') { // Don't expand $$0, but replace it with $0 buf.remove(u - 1, 1); end--; u += 1; // now u is one past the closing '1' continue; } char c = p[u + 1]; int n = (c == '+') ? -1 : c - '0'; const(char)[] marg; if (n == 0) { marg = arg; } else extractArgN(arg, marg, n); if (marg.length == 0) { // Just remove macro invocation //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], cast(int)marg.length, marg.ptr); buf.remove(u, 2); end -= 2; } else if (c == '+') { // Replace '$+' with 'arg' //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], cast(int)marg.length, marg.ptr); buf.remove(u, 2); buf.insert(u, marg); end += marg.length - 2; // Scan replaced text for further expansion size_t mend = u + marg.length; const success = expand(buf, u, mend, null, recursionLimit); if (!success) return false; end += mend - (u + marg.length); u = mend; } else { // Replace '$1' with '\xFF{arg\xFF}' //printf("Replacing '$%c' with '\xFF{%.*s\xFF}'\n", p[u + 1], cast(int)marg.length, marg.ptr); ubyte[] slice = cast(ubyte[])buf[]; slice[u] = 0xFF; slice[u + 1] = '{'; buf.insert(u + 2, marg); buf.insert(u + 2 + marg.length, "\xFF}"); end += -2 + 2 + marg.length + 2; // Scan replaced text for further expansion size_t mend = u + 2 + marg.length; const success = expand(buf, u + 2, mend, null, recursionLimit); if (!success) return false; end += mend - (u + 2 + marg.length); u = mend; } //printf("u = %d, end = %d\n", u, end); //printf("#%.*s#\n", cast(int)end, &buf.data[0]); continue; } u++; } /* Second pass - replace other macros */ for (size_t u = start; u + 4 < end;) { char* p = cast(char*)buf[].ptr; // buf.data is not loop invariant /* A valid start of macro expansion is $(c, where c is * an id start character, and not $$(c. */ if (p[u] == '$' && p[u + 1] == '(' && isIdStart(p + u + 2)) { //printf("\tfound macro start '%c'\n", p[u + 2]); char* name = p + u + 2; size_t namelen = 0; const(char)[] marg; size_t v; /* Scan forward to find end of macro name and * beginning of macro argument (marg). */ for (v = u + 2; v < end; v += utfStride(p + v)) { if (!isIdTail(p + v)) { // We've gone past the end of the macro name. namelen = v - (u + 2); break; } } v += extractArgN(p[v .. end], marg, 0); assert(v <= end); if (v < end) { // v is on the closing ')' if (u > start && p[u - 1] == '$') { // Don't expand $$(NAME), but replace it with $(NAME) buf.remove(u - 1, 1); end--; u = v; // now u is one past the closing ')' continue; } Macro* m = search(name[0 .. namelen]); if (!m) { immutable undef = "DDOC_UNDEFINED_MACRO"; m = search(undef); if (m) { // Macro was not defined, so this is an expansion of // DDOC_UNDEFINED_MACRO. Prepend macro name to args. // marg = name[ ] ~ "," ~ marg[ ]; if (marg.length) { char* q = cast(char*)mem.xmalloc(namelen + 1 + marg.length); assert(q); memcpy(q, name, namelen); q[namelen] = ','; memcpy(q + namelen + 1, marg.ptr, marg.length); marg = q[0 .. marg.length + namelen + 1]; } else { marg = name[0 .. namelen]; } } } if (m) { if (m.inuse && marg.length == 0) { // Remove macro invocation buf.remove(u, v + 1 - u); end -= v + 1 - u; } else if (m.inuse && ((arg.length == marg.length && memcmp(arg.ptr, marg.ptr, arg.length) == 0) || (arg.length + 4 == marg.length && marg[0] == 0xFF && marg[1] == '{' && memcmp(arg.ptr, marg.ptr + 2, arg.length) == 0 && marg[marg.length - 2] == 0xFF && marg[marg.length - 1] == '}'))) { /* Recursive expansion: * marg is same as arg (with blue paint added) * Just leave in place. */ } else { //printf("\tmacro '%.*s'(%.*s) = '%.*s'\n", cast(int)m.namelen, m.name, cast(int)marg.length, marg.ptr, cast(int)m.textlen, m.text); marg = memdup(marg); // Insert replacement text buf.spread(v + 1, 2 + m.text.length + 2); ubyte[] slice = cast(ubyte[])buf[]; slice[v + 1] = 0xFF; slice[v + 2] = '{'; slice[v + 3 .. v + 3 + m.text.length] = cast(ubyte[])m.text[]; slice[v + 3 + m.text.length] = 0xFF; slice[v + 3 + m.text.length + 1] = '}'; end += 2 + m.text.length + 2; // Scan replaced text for further expansion m.inuse++; size_t mend = v + 1 + 2 + m.text.length + 2; const success = expand(buf, v + 1, mend, marg, recursionLimit); if (!success) return false; end += mend - (v + 1 + 2 + m.text.length + 2); m.inuse--; buf.remove(u, v + 1 - u); end -= v + 1 - u; u += mend - (v + 1); mem.xfree(cast(char*)marg.ptr); //printf("u = %d, end = %d\n", u, end); //printf("#%.*s#\n", cast(int)(end - u), &buf.data[u]); continue; } } else { // Replace $(NAME) with nothing buf.remove(u, v + 1 - u); end -= (v + 1 - u); continue; } } } u++; } mem.xfree(cast(char*)arg); pend = end; return true; } private: extern (D) Macro* search(const(char)[] name) @nogc nothrow pure @safe { //printf("Macro::search(%.*s)\n", cast(int)name.length, name.ptr); if (auto table = name in mactab) { //printf("\tfound %d\n", table.textlen); return *table; } return null; } private Macro*[const(char)[]] mactab; } /* ************************************************************************ */ private: struct Macro { const(char)[] name; // macro name const(char)[] text; // macro replacement text int inuse; // macro is in use (don't expand) this(const(char)[] name, const(char)[] text) @nogc nothrow pure @safe { this.name = name; this.text = text; } } /************************ * Make mutable copy of slice p. * Params: * p = slice * Returns: * copy allocated with mem.xmalloc() */ char[] memdup(const(char)[] p) nothrow pure @trusted { size_t len = p.length; return (cast(char*)memcpy(mem.xmalloc(len), p.ptr, len))[0 .. len]; } /********************************************************** * Given buffer buf[], extract argument marg[]. * Params: * buf = source string * marg = set to slice of buf[] * n = 0: get entire argument * 1..9: get nth argument * -1: get 2nd through end */ size_t extractArgN(const(char)[] buf, out const(char)[] marg, int n) @nogc nothrow pure { /* Scan forward for matching right parenthesis. * Nest parentheses. * Skip over "..." and '...' strings inside HTML tags. * Skip over comments. * Skip over previous macro insertions * Set marg. */ uint parens = 1; ubyte instring = 0; uint incomment = 0; uint intag = 0; uint inexp = 0; uint argn = 0; size_t v = 0; const p = buf.ptr; const end = buf.length; Largstart: // Skip first space, if any, to find the start of the macro argument if (n != 1 && v < end && isspace(p[v])) v++; size_t vstart = v; for (; v < end; v++) { char c = p[v]; switch (c) { case ',': if (!inexp && !instring && !incomment && parens == 1) { argn++; if (argn == 1 && n == -1) { v++; goto Largstart; } if (argn == n) break; if (argn + 1 == n) { v++; goto Largstart; } } continue; case '(': if (!inexp && !instring && !incomment) parens++; continue; case ')': if (!inexp && !instring && !incomment && --parens == 0) { break; } continue; case '"': case '\'': if (!inexp && !incomment && intag) { if (c == instring) instring = 0; else if (!instring) instring = c; } continue; case '<': if (!inexp && !instring && !incomment) { if (v + 6 < end && p[v + 1] == '!' && p[v + 2] == '-' && p[v + 3] == '-') { incomment = 1; v += 3; } else if (v + 2 < end && isalpha(p[v + 1])) intag = 1; } continue; case '>': if (!inexp) intag = 0; continue; case '-': if (!inexp && !instring && incomment && v + 2 < end && p[v + 1] == '-' && p[v + 2] == '>') { incomment = 0; v += 2; } continue; case 0xFF: if (v + 1 < end) { if (p[v + 1] == '{') inexp++; else if (p[v + 1] == '}') inexp--; } continue; default: continue; } break; } if (argn == 0 && n == -1) marg = p[v .. v]; else marg = p[vstart .. v]; //printf("extractArg%d('%.*s') = '%.*s'\n", n, cast(int)end, p, cast(int)marg.length, marg.ptr); return v; }