1
/**
2
 * Text macro processor for Ddoc.
3
 *
4
 * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
5
 * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
6
 * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dmacro.d, _dmacro.d)
8
 * Documentation:  https://dlang.org/phobos/dmd_dmacro.html
9
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dmacro.d
10
 */
11

12
module dmd.dmacro;
13

14
import core.stdc.ctype;
15
import core.stdc.string;
16
import dmd.doc;
17
import dmd.errors;
18
import dmd.globals;
19
import dmd.root.outbuffer;
20
import dmd.root.rmem;
21

22
extern (C++) struct MacroTable
23
{
24
    /**********************************
25
     * Define name=text macro.
26
     * If macro `name` already exists, replace the text for it.
27
     * Params:
28
     *  name = name of macro
29
     *  text = text of macro
30
     */
31
    extern (D) void define(const(char)[] name, const(char)[] text)
32
    {
33
        //printf("MacroTable::define('%.*s' = '%.*s')\n", cast(int)name.length, name.ptr, text.length, text.ptr);
34 1
        Macro* table;
35 1
        for (table = mactab; table; table = table.next)
36
        {
37 1
            if (table.name == name)
38
            {
39 1
                table.text = text;
40 1
                return;
41
            }
42
        }
43 1
        table = new Macro(name, text);
44 1
        table.next = mactab;
45 1
        mactab = table;
46
    }
47

48
    /*****************************************************
49
     * Look for macros in buf and expand them in place.
50
     * Only look at the text in buf from start to pend.
51
     */
52
    extern (D) void expand(ref OutBuffer buf, size_t start, ref size_t pend, const(char)[] arg)
53
    {
54
        version (none)
55
        {
56
            printf("Macro::expand(buf[%d..%d], arg = '%.*s')\n", start, pend, cast(int)arg.length, arg.ptr);
57
            printf("Buf is: '%.*s'\n", cast(int)(pend - start), buf.data + start);
58
        }
59
        // limit recursive expansion
60 1
        __gshared int nest;
61 1
        if (nest > global.recursionLimit)
62
        {
63 0
            error(Loc.initial, "DDoc macro expansion limit exceeded; more than %d expansions.",
64
                  global.recursionLimit);
65 0
            return;
66
        }
67 1
        nest++;
68 1
        size_t end = pend;
69 1
        assert(start <= end);
70 1
        assert(end <= buf.length);
71
        /* First pass - replace $0
72
         */
73 1
        arg = memdup(arg);
74 1
        for (size_t u = start; u + 1 < end;)
75
        {
76 1
            char* p = cast(char*)buf[].ptr; // buf.data is not loop invariant
77
            /* Look for $0, but not $$0, and replace it with arg.
78
             */
79 1
            if (p[u] == '$' && (isdigit(p[u + 1]) || p[u + 1] == '+'))
80
            {
81 1
                if (u > start && p[u - 1] == '$')
82
                {
83
                    // Don't expand $$0, but replace it with $0
84 0
                    buf.remove(u - 1, 1);
85 0
                    end--;
86 0
                    u += 1; // now u is one past the closing '1'
87 0
                    continue;
88
                }
89 1
                char c = p[u + 1];
90 1
                int n = (c == '+') ? -1 : c - '0';
91 1
                const(char)[] marg;
92 1
                if (n == 0)
93
                {
94 1
                    marg = arg;
95
                }
96
                else
97 1
                    extractArgN(arg, marg, n);
98 1
                if (marg.length == 0)
99
                {
100
                    // Just remove macro invocation
101
                    //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], cast(int)marg.length, marg.ptr);
102 1
                    buf.remove(u, 2);
103 1
                    end -= 2;
104
                }
105 1
                else if (c == '+')
106
                {
107
                    // Replace '$+' with 'arg'
108
                    //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], cast(int)marg.length, marg.ptr);
109 1
                    buf.remove(u, 2);
110 1
                    buf.insert(u, marg);
111 1
                    end += marg.length - 2;
112
                    // Scan replaced text for further expansion
113 1
                    size_t mend = u + marg.length;
114 1
                    expand(buf, u, mend, null);
115 1
                    end += mend - (u + marg.length);
116 1
                    u = mend;
117
                }
118
                else
119
                {
120
                    // Replace '$1' with '\xFF{arg\xFF}'
121
                    //printf("Replacing '$%c' with '\xFF{%.*s\xFF}'\n", p[u + 1], cast(int)marg.length, marg.ptr);
122 1
                    ubyte[] slice = cast(ubyte[])buf[];
123 1
                    slice[u] = 0xFF;
124 1
                    slice[u + 1] = '{';
125 1
                    buf.insert(u + 2, marg);
126 1
                    buf.insert(u + 2 + marg.length, "\xFF}");
127 1
                    end += -2 + 2 + marg.length + 2;
128
                    // Scan replaced text for further expansion
129 1
                    size_t mend = u + 2 + marg.length;
130 1
                    expand(buf, u + 2, mend, null);
131 1
                    end += mend - (u + 2 + marg.length);
132 1
                    u = mend;
133
                }
134
                //printf("u = %d, end = %d\n", u, end);
135
                //printf("#%.*s#\n", cast(int)end, &buf.data[0]);
136 1
                continue;
137
            }
138 1
            u++;
139
        }
140
        /* Second pass - replace other macros
141
         */
142 1
        for (size_t u = start; u + 4 < end;)
143
        {
144 1
            char* p = cast(char*)buf[].ptr; // buf.data is not loop invariant
145
            /* A valid start of macro expansion is $(c, where c is
146
             * an id start character, and not $$(c.
147
             */
148 1
            if (p[u] == '$' && p[u + 1] == '(' && isIdStart(p + u + 2))
149
            {
150
                //printf("\tfound macro start '%c'\n", p[u + 2]);
151 1
                char* name = p + u + 2;
152 1
                size_t namelen = 0;
153 1
                const(char)[] marg;
154 1
                size_t v;
155
                /* Scan forward to find end of macro name and
156
                 * beginning of macro argument (marg).
157
                 */
158 1
                for (v = u + 2; v < end; v += utfStride(p + v))
159
                {
160 1
                    if (!isIdTail(p + v))
161
                    {
162
                        // We've gone past the end of the macro name.
163 1
                        namelen = v - (u + 2);
164 1
                        break;
165
                    }
166
                }
167 1
                v += extractArgN(p[v .. end], marg, 0);
168 1
                assert(v <= end);
169 1
                if (v < end)
170
                {
171
                    // v is on the closing ')'
172 1
                    if (u > start && p[u - 1] == '$')
173
                    {
174
                        // Don't expand $$(NAME), but replace it with $(NAME)
175 1
                        buf.remove(u - 1, 1);
176 1
                        end--;
177 1
                        u = v; // now u is one past the closing ')'
178 1
                        continue;
179
                    }
180 1
                    Macro* m = search(name[0 .. namelen]);
181 1
                    if (!m)
182
                    {
183 1
                        immutable undef = "DDOC_UNDEFINED_MACRO";
184 1
                        m = search(undef);
185 1
                        if (m)
186
                        {
187
                            // Macro was not defined, so this is an expansion of
188
                            //   DDOC_UNDEFINED_MACRO. Prepend macro name to args.
189
                            // marg = name[ ] ~ "," ~ marg[ ];
190 1
                            if (marg.length)
191
                            {
192 1
                                char* q = cast(char*)mem.xmalloc(namelen + 1 + marg.length);
193 1
                                assert(q);
194 1
                                memcpy(q, name, namelen);
195 1
                                q[namelen] = ',';
196 1
                                memcpy(q + namelen + 1, marg.ptr, marg.length);
197 1
                                marg = q[0 .. marg.length + namelen + 1];
198
                            }
199
                            else
200
                            {
201 0
                                marg = name[0 .. namelen];
202
                            }
203
                        }
204
                    }
205 1
                    if (m)
206
                    {
207 1
                        if (m.inuse && marg.length == 0)
208
                        {
209
                            // Remove macro invocation
210 1
                            buf.remove(u, v + 1 - u);
211 1
                            end -= v + 1 - u;
212
                        }
213 1
                        else if (m.inuse && ((arg.length == marg.length && memcmp(arg.ptr, marg.ptr, arg.length) == 0) ||
214 1
                                             (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] == '}')))
215
                        {
216
                            /* Recursive expansion:
217
                             *   marg is same as arg (with blue paint added)
218
                             * Just leave in place.
219
                             */
220
                        }
221
                        else
222
                        {
223
                            //printf("\tmacro '%.*s'(%.*s) = '%.*s'\n", cast(int)m.namelen, m.name, cast(int)marg.length, marg.ptr, cast(int)m.textlen, m.text);
224 1
                            marg = memdup(marg);
225
                            // Insert replacement text
226 1
                            buf.spread(v + 1, 2 + m.text.length + 2);
227 1
                            ubyte[] slice = cast(ubyte[])buf[];
228 1
                            slice[v + 1] = 0xFF;
229 1
                            slice[v + 2] = '{';
230 1
                            slice[v + 3 .. v + 3 + m.text.length] = cast(ubyte[])m.text[];
231 1
                            slice[v + 3 + m.text.length] = 0xFF;
232 1
                            slice[v + 3 + m.text.length + 1] = '}';
233 1
                            end += 2 + m.text.length + 2;
234
                            // Scan replaced text for further expansion
235 1
                            m.inuse++;
236 1
                            size_t mend = v + 1 + 2 + m.text.length + 2;
237 1
                            expand(buf, v + 1, mend, marg);
238 1
                            end += mend - (v + 1 + 2 + m.text.length + 2);
239 1
                            m.inuse--;
240 1
                            buf.remove(u, v + 1 - u);
241 1
                            end -= v + 1 - u;
242 1
                            u += mend - (v + 1);
243 1
                            mem.xfree(cast(char*)marg.ptr);
244
                            //printf("u = %d, end = %d\n", u, end);
245
                            //printf("#%.*s#\n", cast(int)(end - u), &buf.data[u]);
246 1
                            continue;
247
                        }
248
                    }
249
                    else
250
                    {
251
                        // Replace $(NAME) with nothing
252 1
                        buf.remove(u, v + 1 - u);
253 1
                        end -= (v + 1 - u);
254 1
                        continue;
255
                    }
256
                }
257
            }
258 1
            u++;
259
        }
260 1
        mem.xfree(cast(char*)arg);
261 1
        pend = end;
262 1
        nest--;
263
    }
264

265
  private:
266

267
    extern (D) Macro* search(const(char)[] name)
268
    {
269 1
        Macro* table;
270
        //printf("Macro::search(%.*s)\n", cast(int)name.length, name.ptr);
271 1
        for (table = mactab; table; table = table.next)
272
        {
273 1
            if (table.name == name)
274
            {
275
                //printf("\tfound %d\n", table.textlen);
276 1
                break;
277
            }
278
        }
279 1
        return table;
280
    }
281

282
    Macro* mactab;
283
}
284

285
/* ************************************************************************ */
286

287
private:
288

289
struct Macro
290
{
291
    Macro* next;            // next in list
292
    const(char)[] name;     // macro name
293
    const(char)[] text;     // macro replacement text
294
    int inuse;              // macro is in use (don't expand)
295

296 1
    this(const(char)[] name, const(char)[] text)
297
    {
298 1
        this.name = name;
299 1
        this.text = text;
300
    }
301
}
302

303
/************************
304
 * Make mutable copy of slice p.
305
 * Params:
306
 *      p = slice
307
 * Returns:
308
 *      copy allocated with mem.xmalloc()
309
 */
310

311
char[] memdup(const(char)[] p)
312
{
313 1
    size_t len = p.length;
314 1
    return (cast(char*)memcpy(mem.xmalloc(len), p.ptr, len))[0 .. len];
315
}
316

317
/**********************************************************
318
 * Given buffer buf[], extract argument marg[].
319
 * Params:
320
 *      buf = source string
321
 *      marg = set to slice of buf[]
322
 *      n =     0:      get entire argument
323
 *              1..9:   get nth argument
324
 *              -1:     get 2nd through end
325
 */
326
size_t extractArgN(const(char)[] buf, out const(char)[] marg, int n)
327
{
328
    /* Scan forward for matching right parenthesis.
329
     * Nest parentheses.
330
     * Skip over "..." and '...' strings inside HTML tags.
331
     * Skip over <!-- ... --> comments.
332
     * Skip over previous macro insertions
333
     * Set marg.
334
     */
335 1
    uint parens = 1;
336 1
    ubyte instring = 0;
337 1
    uint incomment = 0;
338 1
    uint intag = 0;
339 1
    uint inexp = 0;
340 1
    uint argn = 0;
341 1
    size_t v = 0;
342 1
    const p = buf.ptr;
343 1
    const end = buf.length;
344
Largstart:
345
    // Skip first space, if any, to find the start of the macro argument
346 1
    if (n != 1 && v < end && isspace(p[v]))
347 1
        v++;
348 1
    size_t vstart = v;
349 1
    for (; v < end; v++)
350
    {
351 1
        char c = p[v];
352 1
        switch (c)
353
        {
354 1
        case ',':
355 1
            if (!inexp && !instring && !incomment && parens == 1)
356
            {
357 1
                argn++;
358 1
                if (argn == 1 && n == -1)
359
                {
360 1
                    v++;
361 1
                    goto Largstart;
362
                }
363 1
                if (argn == n)
364 1
                    break;
365 1
                if (argn + 1 == n)
366
                {
367 1
                    v++;
368 1
                    goto Largstart;
369
                }
370
            }
371 1
            continue;
372 1
        case '(':
373 1
            if (!inexp && !instring && !incomment)
374 1
                parens++;
375 1
            continue;
376 1
        case ')':
377 1
            if (!inexp && !instring && !incomment && --parens == 0)
378
            {
379 1
                break;
380
            }
381 1
            continue;
382 1
        case '"':
383 1
        case '\'':
384 1
            if (!inexp && !incomment && intag)
385
            {
386 1
                if (c == instring)
387 1
                    instring = 0;
388 1
                else if (!instring)
389 1
                    instring = c;
390
            }
391 1
            continue;
392 1
        case '<':
393 1
            if (!inexp && !instring && !incomment)
394
            {
395 1
                if (v + 6 < end && p[v + 1] == '!' && p[v + 2] == '-' && p[v + 3] == '-')
396
                {
397 1
                    incomment = 1;
398 1
                    v += 3;
399
                }
400 1
                else if (v + 2 < end && isalpha(p[v + 1]))
401 1
                    intag = 1;
402
            }
403 1
            continue;
404 1
        case '>':
405 1
            if (!inexp)
406 1
                intag = 0;
407 1
            continue;
408 1
        case '-':
409 1
            if (!inexp && !instring && incomment && v + 2 < end && p[v + 1] == '-' && p[v + 2] == '>')
410
            {
411 1
                incomment = 0;
412 1
                v += 2;
413
            }
414 1
            continue;
415 1
        case 0xFF:
416 1
            if (v + 1 < end)
417
            {
418 1
                if (p[v + 1] == '{')
419 1
                    inexp++;
420 1
                else if (p[v + 1] == '}')
421 1
                    inexp--;
422
            }
423 1
            continue;
424 1
        default:
425 1
            continue;
426
        }
427 1
        break;
428
    }
429 1
    if (argn == 0 && n == -1)
430 1
        marg = p[v .. v];
431
    else
432 1
        marg = p[vstart .. v];
433
    //printf("extractArg%d('%.*s') = '%.*s'\n", n, cast(int)end, p, cast(int)marg.length, marg.ptr);
434 1
    return v;
435
}

Read our documentation on viewing source code .

Loading