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         Macro* table;
35         for (table = mactab; table; table = table.next)
36         {
37             if (table.name == name)
38             {
39                 table.text = text;
40                 return;
41             }
42         }
43         table = new Macro(name, text);
44         table.next = mactab;
45         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         __gshared int nest;
61         if (nest > global.recursionLimit)
62         {
63             error(Loc.initial, "DDoc macro expansion limit exceeded; more than %d expansions.",
64                   global.recursionLimit);
65             return;
66         }
67         nest++;
68         size_t end = pend;
69         assert(start <= end);
70         assert(end <= buf.length);
71         /* First pass - replace $0
72          */
73         arg = memdup(arg);
74         for (size_t u = start; u + 1 < end;)
75         {
76             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             if (p[u] == '$' && (isdigit(p[u + 1]) || p[u + 1] == '+'))
80             {
81                 if (u > start && p[u - 1] == '$')
82                 {
83                     // Don't expand $$0, but replace it with $0
84                     buf.remove(u - 1, 1);
85                     end--;
86                     u += 1; // now u is one past the closing '1'
87                     continue;
88                 }
89                 char c = p[u + 1];
90                 int n = (c == '+') ? -1 : c - '0';
91                 const(char)[] marg;
92                 if (n == 0)
93                 {
94                     marg = arg;
95                 }
96                 else
97                     extractArgN(arg, marg, n);
98                 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                     buf.remove(u, 2);
103                     end -= 2;
104                 }
105                 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                     buf.remove(u, 2);
110                     buf.insert(u, marg);
111                     end += marg.length - 2;
112                     // Scan replaced text for further expansion
113                     size_t mend = u + marg.length;
114                     expand(buf, u, mend, null);
115                     end += mend - (u + marg.length);
116                     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                     ubyte[] slice = cast(ubyte[])buf[];
123                     slice[u] = 0xFF;
124                     slice[u + 1] = '{';
125                     buf.insert(u + 2, marg);
126                     buf.insert(u + 2 + marg.length, "\xFF}");
127                     end += -2 + 2 + marg.length + 2;
128                     // Scan replaced text for further expansion
129                     size_t mend = u + 2 + marg.length;
130                     expand(buf, u + 2, mend, null);
131                     end += mend - (u + 2 + marg.length);
132                     u = mend;
133                 }
134                 //printf("u = %d, end = %d\n", u, end);
135                 //printf("#%.*s#\n", cast(int)end, &buf.data[0]);
136                 continue;
137             }
138             u++;
139         }
140         /* Second pass - replace other macros
141          */
142         for (size_t u = start; u + 4 < end;)
143         {
144             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             if (p[u] == '$' && p[u + 1] == '(' && isIdStart(p + u + 2))
149             {
150                 //printf("\tfound macro start '%c'\n", p[u + 2]);
151                 char* name = p + u + 2;
152                 size_t namelen = 0;
153                 const(char)[] marg;
154                 size_t v;
155                 /* Scan forward to find end of macro name and
156                  * beginning of macro argument (marg).
157                  */
158                 for (v = u + 2; v < end; v += utfStride(p + v))
159                 {
160                     if (!isIdTail(p + v))
161                     {
162                         // We've gone past the end of the macro name.
163                         namelen = v - (u + 2);
164                         break;
165                     }
166                 }
167                 v += extractArgN(p[v .. end], marg, 0);
168                 assert(v <= end);
169                 if (v < end)
170                 {
171                     // v is on the closing ')'
172                     if (u > start && p[u - 1] == '$')
173                     {
174                         // Don't expand $$(NAME), but replace it with $(NAME)
175                         buf.remove(u - 1, 1);
176                         end--;
177                         u = v; // now u is one past the closing ')'
178                         continue;
179                     }
180                     Macro* m = search(name[0 .. namelen]);
181                     if (!m)
182                     {
183                         immutable undef = "DDOC_UNDEFINED_MACRO";
184                         m = search(undef);
185                         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                             if (marg.length)
191                             {
192                                 char* q = cast(char*)mem.xmalloc(namelen + 1 + marg.length);
193                                 assert(q);
194                                 memcpy(q, name, namelen);
195                                 q[namelen] = ',';
196                                 memcpy(q + namelen + 1, marg.ptr, marg.length);
197                                 marg = q[0 .. marg.length + namelen + 1];
198                             }
199                             else
200                             {
201                                 marg = name[0 .. namelen];
202                             }
203                         }
204                     }
205                     if (m)
206                     {
207                         if (m.inuse && marg.length == 0)
208                         {
209                             // Remove macro invocation
210                             buf.remove(u, v + 1 - u);
211                             end -= v + 1 - u;
212                         }
213                         else if (m.inuse && ((arg.length == marg.length && memcmp(arg.ptr, marg.ptr, arg.length) == 0) ||
214                                              (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                             marg = memdup(marg);
225                             // Insert replacement text
226                             buf.spread(v + 1, 2 + m.text.length + 2);
227                             ubyte[] slice = cast(ubyte[])buf[];
228                             slice[v + 1] = 0xFF;
229                             slice[v + 2] = '{';
230                             slice[v + 3 .. v + 3 + m.text.length] = cast(ubyte[])m.text[];
231                             slice[v + 3 + m.text.length] = 0xFF;
232                             slice[v + 3 + m.text.length + 1] = '}';
233                             end += 2 + m.text.length + 2;
234                             // Scan replaced text for further expansion
235                             m.inuse++;
236                             size_t mend = v + 1 + 2 + m.text.length + 2;
237                             expand(buf, v + 1, mend, marg);
238                             end += mend - (v + 1 + 2 + m.text.length + 2);
239                             m.inuse--;
240                             buf.remove(u, v + 1 - u);
241                             end -= v + 1 - u;
242                             u += mend - (v + 1);
243                             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                             continue;
247                         }
248                     }
249                     else
250                     {
251                         // Replace $(NAME) with nothing
252                         buf.remove(u, v + 1 - u);
253                         end -= (v + 1 - u);
254                         continue;
255                     }
256                 }
257             }
258             u++;
259         }
260         mem.xfree(cast(char*)arg);
261         pend = end;
262         nest--;
263     }
264 
265   private:
266 
267     extern (D) Macro* search(const(char)[] name)
268     {
269         Macro* table;
270         //printf("Macro::search(%.*s)\n", cast(int)name.length, name.ptr);
271         for (table = mactab; table; table = table.next)
272         {
273             if (table.name == name)
274             {
275                 //printf("\tfound %d\n", table.textlen);
276                 break;
277             }
278         }
279         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     this(const(char)[] name, const(char)[] text)
297     {
298         this.name = name;
299         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     size_t len = p.length;
314     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     uint parens = 1;
336     ubyte instring = 0;
337     uint incomment = 0;
338     uint intag = 0;
339     uint inexp = 0;
340     uint argn = 0;
341     size_t v = 0;
342     const p = buf.ptr;
343     const end = buf.length;
344 Largstart:
345     // Skip first space, if any, to find the start of the macro argument
346     if (n != 1 && v < end && isspace(p[v]))
347         v++;
348     size_t vstart = v;
349     for (; v < end; v++)
350     {
351         char c = p[v];
352         switch (c)
353         {
354         case ',':
355             if (!inexp && !instring && !incomment && parens == 1)
356             {
357                 argn++;
358                 if (argn == 1 && n == -1)
359                 {
360                     v++;
361                     goto Largstart;
362                 }
363                 if (argn == n)
364                     break;
365                 if (argn + 1 == n)
366                 {
367                     v++;
368                     goto Largstart;
369                 }
370             }
371             continue;
372         case '(':
373             if (!inexp && !instring && !incomment)
374                 parens++;
375             continue;
376         case ')':
377             if (!inexp && !instring && !incomment && --parens == 0)
378             {
379                 break;
380             }
381             continue;
382         case '"':
383         case '\'':
384             if (!inexp && !incomment && intag)
385             {
386                 if (c == instring)
387                     instring = 0;
388                 else if (!instring)
389                     instring = c;
390             }
391             continue;
392         case '<':
393             if (!inexp && !instring && !incomment)
394             {
395                 if (v + 6 < end && p[v + 1] == '!' && p[v + 2] == '-' && p[v + 3] == '-')
396                 {
397                     incomment = 1;
398                     v += 3;
399                 }
400                 else if (v + 2 < end && isalpha(p[v + 1]))
401                     intag = 1;
402             }
403             continue;
404         case '>':
405             if (!inexp)
406                 intag = 0;
407             continue;
408         case '-':
409             if (!inexp && !instring && incomment && v + 2 < end && p[v + 1] == '-' && p[v + 2] == '>')
410             {
411                 incomment = 0;
412                 v += 2;
413             }
414             continue;
415         case 0xFF:
416             if (v + 1 < end)
417             {
418                 if (p[v + 1] == '{')
419                     inexp++;
420                 else if (p[v + 1] == '}')
421                     inexp--;
422             }
423             continue;
424         default:
425             continue;
426         }
427         break;
428     }
429     if (argn == 0 && n == -1)
430         marg = p[v .. v];
431     else
432         marg = p[vstart .. v];
433     //printf("extractArg%d('%.*s') = '%.*s'\n", n, cast(int)end, p, cast(int)marg.length, marg.ptr);
434     return v;
435 }