1 /**
2  * Compiler implementation of the
3  * $(LINK2 http://www.dlang.org, D programming language).
4  *
5  * Copyright:   Copyright (c) 1999-2016 by Digital Mars, All Rights Reserved
6  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
7  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8  * Source:      $(DMDSRC _dmacro.d)
9  */
10 
11 module ddmd.dmacro;
12 
13 import core.stdc.ctype;
14 import core.stdc..string;
15 import ddmd.doc;
16 import ddmd.errors;
17 import ddmd.globals;
18 import ddmd.root.outbuffer;
19 import ddmd.root.rmem;
20 
21 struct Macro
22 {
23 private:
24     Macro* next;            // next in list
25     const(char)[] name;     // macro name
26     const(char)[] text;     // macro replacement text
27     int inuse;              // macro is in use (don't expand)
28 
29     this(const(char)[] name, const(char)[] text)
30     {
31         this.name = name;
32         this.text = text;
33     }
34 
35     Macro* search(const(char)[] name)
36     {
37         Macro* table;
38         //printf("Macro::search(%.*s)\n", name.length, name.ptr);
39         for (table = &this; table; table = table.next)
40         {
41             if (table.name == name)
42             {
43                 //printf("\tfound %d\n", table->textlen);
44                 break;
45             }
46         }
47         return table;
48     }
49 
50 public:
51     static Macro* define(Macro** ptable, const(char)[] name, const(char)[] text)
52     {
53         //printf("Macro::define('%.*s' = '%.*s')\n", name.length, name.ptr, text.length, text.ptr);
54         Macro* table;
55         //assert(ptable);
56         for (table = *ptable; table; table = table.next)
57         {
58             if (table.name == name)
59             {
60                 table.text = text;
61                 return table;
62             }
63         }
64         table = new Macro(name, text);
65         table.next = *ptable;
66         *ptable = table;
67         return table;
68     }
69 
70     /*****************************************************
71      * Expand macro in place in buf.
72      * Only look at the text in buf from start to end.
73      */
74     extern (C++) void expand(OutBuffer* buf, size_t start, size_t* pend, const(char)* arg, size_t arglen)
75     {
76         version (none)
77         {
78             printf("Macro::expand(buf[%d..%d], arg = '%.*s')\n", start, *pend, arglen, arg);
79             printf("Buf is: '%.*s'\n", *pend - start, buf.data + start);
80         }
81         // limit recursive expansion
82         static __gshared int nest;
83         static __gshared const(int) nestLimit = 1000;
84         if (nest > nestLimit)
85         {
86             error(Loc(), "DDoc macro expansion limit exceeded; more than %d expansions.", nestLimit);
87             return;
88         }
89         nest++;
90         size_t end = *pend;
91         assert(start <= end);
92         assert(end <= buf.offset);
93         /* First pass - replace $0
94          */
95         arg = memdup(arg, arglen);
96         for (size_t u = start; u + 1 < end;)
97         {
98             char* p = cast(char*)buf.data; // buf->data is not loop invariant
99             /* Look for $0, but not $$0, and replace it with arg.
100              */
101             if (p[u] == '$' && (isdigit(p[u + 1]) || p[u + 1] == '+'))
102             {
103                 if (u > start && p[u - 1] == '$')
104                 {
105                     // Don't expand $$0, but replace it with $0
106                     buf.remove(u - 1, 1);
107                     end--;
108                     u += 1; // now u is one past the closing '1'
109                     continue;
110                 }
111                 char c = p[u + 1];
112                 int n = (c == '+') ? -1 : c - '0';
113                 const(char)* marg;
114                 size_t marglen;
115                 if (n == 0)
116                 {
117                     marg = arg;
118                     marglen = arglen;
119                 }
120                 else
121                     extractArgN(arg, arglen, &marg, &marglen, n);
122                 if (marglen == 0)
123                 {
124                     // Just remove macro invocation
125                     //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], marglen, marg);
126                     buf.remove(u, 2);
127                     end -= 2;
128                 }
129                 else if (c == '+')
130                 {
131                     // Replace '$+' with 'arg'
132                     //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], marglen, marg);
133                     buf.remove(u, 2);
134                     buf.insert(u, marg, marglen);
135                     end += marglen - 2;
136                     // Scan replaced text for further expansion
137                     size_t mend = u + marglen;
138                     expand(buf, u, &mend, null, 0);
139                     end += mend - (u + marglen);
140                     u = mend;
141                 }
142                 else
143                 {
144                     // Replace '$1' with '\xFF{arg\xFF}'
145                     //printf("Replacing '$%c' with '\xFF{%.*s\xFF}'\n", p[u + 1], marglen, marg);
146                     buf.data[u] = 0xFF;
147                     buf.data[u + 1] = '{';
148                     buf.insert(u + 2, marg, marglen);
149                     buf.insert(u + 2 + marglen, "\xFF}");
150                     end += -2 + 2 + marglen + 2;
151                     // Scan replaced text for further expansion
152                     size_t mend = u + 2 + marglen;
153                     expand(buf, u + 2, &mend, null, 0);
154                     end += mend - (u + 2 + marglen);
155                     u = mend;
156                 }
157                 //printf("u = %d, end = %d\n", u, end);
158                 //printf("#%.*s#\n", end, &buf->data[0]);
159                 continue;
160             }
161             u++;
162         }
163         /* Second pass - replace other macros
164          */
165         for (size_t u = start; u + 4 < end;)
166         {
167             char* p = cast(char*)buf.data; // buf->data is not loop invariant
168             /* A valid start of macro expansion is $(c, where c is
169              * an id start character, and not $$(c.
170              */
171             if (p[u] == '$' && p[u + 1] == '(' && isIdStart(p + u + 2))
172             {
173                 //printf("\tfound macro start '%c'\n", p[u + 2]);
174                 char* name = p + u + 2;
175                 size_t namelen = 0;
176                 const(char)* marg;
177                 size_t marglen;
178                 size_t v;
179                 /* Scan forward to find end of macro name and
180                  * beginning of macro argument (marg).
181                  */
182                 for (v = u + 2; v < end; v += utfStride(p + v))
183                 {
184                     if (!isIdTail(p + v))
185                     {
186                         // We've gone past the end of the macro name.
187                         namelen = v - (u + 2);
188                         break;
189                     }
190                 }
191                 v += extractArgN(p + v, end - v, &marg, &marglen, 0);
192                 assert(v <= end);
193                 if (v < end)
194                 {
195                     // v is on the closing ')'
196                     if (u > start && p[u - 1] == '$')
197                     {
198                         // Don't expand $$(NAME), but replace it with $(NAME)
199                         buf.remove(u - 1, 1);
200                         end--;
201                         u = v; // now u is one past the closing ')'
202                         continue;
203                     }
204                     Macro* m = search(name[0 .. namelen]);
205                     if (!m)
206                     {
207                         immutable undef = "DDOC_UNDEFINED_MACRO";
208                         m = search(undef);
209                         if (m)
210                         {
211                             // Macro was not defined, so this is an expansion of
212                             //   DDOC_UNDEFINED_MACRO. Prepend macro name to args.
213                             // marg = name[ ] ~ "," ~ marg[ ];
214                             if (marglen)
215                             {
216                                 char* q = cast(char*)mem.xmalloc(namelen + 1 + marglen);
217                                 assert(q);
218                                 memcpy(q, name, namelen);
219                                 q[namelen] = ',';
220                                 memcpy(q + namelen + 1, marg, marglen);
221                                 marg = q;
222                                 marglen += namelen + 1;
223                             }
224                             else
225                             {
226                                 marg = name;
227                                 marglen = namelen;
228                             }
229                         }
230                     }
231                     if (m)
232                     {
233                         if (m.inuse && marglen == 0)
234                         {
235                             // Remove macro invocation
236                             buf.remove(u, v + 1 - u);
237                             end -= v + 1 - u;
238                         }
239                         else if (m.inuse && ((arglen == marglen && memcmp(arg, marg, arglen) == 0) || (arglen + 4 == marglen && marg[0] == 0xFF && marg[1] == '{' && memcmp(arg, marg + 2, arglen) == 0 && marg[marglen - 2] == 0xFF && marg[marglen - 1] == '}')))
240                         {
241                             /* Recursive expansion:
242                              *   marg is same as arg (with blue paint added)
243                              * Just leave in place.
244                              */
245                         }
246                         else
247                         {
248                             //printf("\tmacro '%.*s'(%.*s) = '%.*s'\n", m->namelen, m->name, marglen, marg, m->textlen, m->text);
249                             marg = memdup(marg, marglen);
250                             // Insert replacement text
251                             buf.spread(v + 1, 2 + m.text.length + 2);
252                             buf.data[v + 1] = 0xFF;
253                             buf.data[v + 2] = '{';
254                             buf.data[v + 3 .. v + 3 + m.text.length] = cast(ubyte[])m.text[];
255                             buf.data[v + 3 + m.text.length] = 0xFF;
256                             buf.data[v + 3 + m.text.length + 1] = '}';
257                             end += 2 + m.text.length + 2;
258                             // Scan replaced text for further expansion
259                             m.inuse++;
260                             size_t mend = v + 1 + 2 + m.text.length + 2;
261                             expand(buf, v + 1, &mend, marg, marglen);
262                             end += mend - (v + 1 + 2 + m.text.length + 2);
263                             m.inuse--;
264                             buf.remove(u, v + 1 - u);
265                             end -= v + 1 - u;
266                             u += mend - (v + 1);
267                             mem.xfree(cast(char*)marg);
268                             //printf("u = %d, end = %d\n", u, end);
269                             //printf("#%.*s#\n", end - u, &buf->data[u]);
270                             continue;
271                         }
272                     }
273                     else
274                     {
275                         // Replace $(NAME) with nothing
276                         buf.remove(u, v + 1 - u);
277                         end -= (v + 1 - u);
278                         continue;
279                     }
280                 }
281             }
282             u++;
283         }
284         mem.xfree(cast(char*)arg);
285         *pend = end;
286         nest--;
287     }
288 }
289 
290 extern (C++) char* memdup(const(char)* p, size_t len)
291 {
292     return cast(char*)memcpy(mem.xmalloc(len), p, len);
293 }
294 
295 /**********************************************************
296  * Given buffer p[0..end], extract argument marg[0..marglen].
297  * Params:
298  *      n       0:      get entire argument
299  *              1..9:   get nth argument
300  *              -1:     get 2nd through end
301  */
302 private size_t extractArgN(const(char)* p, size_t end, const(char)** pmarg, size_t* pmarglen, int n)
303 {
304     /* Scan forward for matching right parenthesis.
305      * Nest parentheses.
306      * Skip over "..." and '...' strings inside HTML tags.
307      * Skip over <!-- ... --> comments.
308      * Skip over previous macro insertions
309      * Set marglen.
310      */
311     uint parens = 1;
312     ubyte instring = 0;
313     uint incomment = 0;
314     uint intag = 0;
315     uint inexp = 0;
316     uint argn = 0;
317     size_t v = 0;
318 Largstart:
319     // Skip first space, if any, to find the start of the macro argument
320     if (n != 1 && v < end && isspace(p[v]))
321         v++;
322     *pmarg = p + v;
323     for (; v < end; v++)
324     {
325         char c = p[v];
326         switch (c)
327         {
328         case ',':
329             if (!inexp && !instring && !incomment && parens == 1)
330             {
331                 argn++;
332                 if (argn == 1 && n == -1)
333                 {
334                     v++;
335                     goto Largstart;
336                 }
337                 if (argn == n)
338                     break;
339                 if (argn + 1 == n)
340                 {
341                     v++;
342                     goto Largstart;
343                 }
344             }
345             continue;
346         case '(':
347             if (!inexp && !instring && !incomment)
348                 parens++;
349             continue;
350         case ')':
351             if (!inexp && !instring && !incomment && --parens == 0)
352             {
353                 break;
354             }
355             continue;
356         case '"':
357         case '\'':
358             if (!inexp && !incomment && intag)
359             {
360                 if (c == instring)
361                     instring = 0;
362                 else if (!instring)
363                     instring = c;
364             }
365             continue;
366         case '<':
367             if (!inexp && !instring && !incomment)
368             {
369                 if (v + 6 < end && p[v + 1] == '!' && p[v + 2] == '-' && p[v + 3] == '-')
370                 {
371                     incomment = 1;
372                     v += 3;
373                 }
374                 else if (v + 2 < end && isalpha(p[v + 1]))
375                     intag = 1;
376             }
377             continue;
378         case '>':
379             if (!inexp)
380                 intag = 0;
381             continue;
382         case '-':
383             if (!inexp && !instring && incomment && v + 2 < end && p[v + 1] == '-' && p[v + 2] == '>')
384             {
385                 incomment = 0;
386                 v += 2;
387             }
388             continue;
389         case 0xFF:
390             if (v + 1 < end)
391             {
392                 if (p[v + 1] == '{')
393                     inexp++;
394                 else if (p[v + 1] == '}')
395                     inexp--;
396             }
397             continue;
398         default:
399             continue;
400         }
401         break;
402     }
403     if (argn == 0 && n == -1)
404         *pmarg = p + v;
405     *pmarglen = p + v - *pmarg;
406     //printf("extractArg%d('%.*s') = '%.*s'\n", n, end, p, *pmarglen, *pmarg);
407     return v;
408 }