| /** |
| * 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; |
| } |