Merged Delight changes to D1 into D2
[delight/core.git] / dmd / macro.c
blob4c3cba954113cc8099db7586573af2ad9250e315
2 // Copyright (c) 1999-2006 by Digital Mars
3 // All Rights Reserved
4 // written by Walter Bright
5 // http://www.digitalmars.com
6 // License for redistribution is by either the Artistic License
7 // in artistic.txt, or the GNU General Public License in gnu.txt.
8 // See the included readme.txt for details.
10 /* Simple macro text processor.
13 #include <stdio.h>
14 #include <string.h>
15 #include <time.h>
16 #include <ctype.h>
17 #include <assert.h>
19 #ifdef IN_GCC
20 #include "mem.h"
21 #else
22 #if _WIN32
23 #include "..\root\mem.h"
24 #elif linux
25 #include "../root/mem.h"
26 #else
27 #error "fix this"
28 #endif
29 #endif
31 #include "root.h"
32 #include "macro.h"
34 #define isidstart(c) (isalpha(c) || (c) == '_')
35 #define isidchar(c) (isalnum(c) || (c) == '_')
37 unsigned char *memdup(unsigned char *p, size_t len)
39 return (unsigned char *)memcpy(mem.malloc(len), p, len);
42 Macro::Macro(unsigned char *name, size_t namelen, unsigned char *text, size_t textlen)
44 next = NULL;
46 #if 1
47 this->name = name;
48 this->namelen = namelen;
50 this->text = text;
51 this->textlen = textlen;
52 #else
53 this->name = name;
54 this->namelen = namelen;
56 this->text = text;
57 this->textlen = textlen;
58 #endif
59 inuse = 0;
63 Macro *Macro::search(unsigned char *name, size_t namelen)
64 { Macro *table;
66 //printf("Macro::search(%.*s)\n", namelen, name);
67 for (table = this; table; table = table->next)
69 if (table->namelen == namelen &&
70 memcmp(table->name, name, namelen) == 0)
72 //printf("\tfound %d\n", table->textlen);
73 break;
76 return table;
79 Macro *Macro::define(Macro **ptable, unsigned char *name, size_t namelen, unsigned char *text, size_t textlen)
81 //printf("Macro::define('%.*s' = '%.*s')\n", namelen, name, textlen, text);
83 Macro *table;
85 //assert(ptable);
86 for (table = *ptable; table; table = table->next)
88 if (table->namelen == namelen &&
89 memcmp(table->name, name, namelen) == 0)
91 table->text = text;
92 table->textlen = textlen;
93 return table;
96 table = new Macro(name, namelen, text, textlen);
97 table->next = *ptable;
98 *ptable = table;
99 return table;
102 /**********************************************************
103 * Given buffer p[0..end], extract argument marg[0..marglen].
104 * Params:
105 * n 0: get entire argument
106 * 1..9: get nth argument
107 * -1: get 2nd through end
110 unsigned extractArgN(unsigned char *p, unsigned end, unsigned char **pmarg, unsigned *pmarglen, int n)
112 /* Scan forward for matching right parenthesis.
113 * Nest parentheses.
114 * Skip over $( and $)
115 * Skip over "..." and '...' strings inside HTML tags.
116 * Skip over <!-- ... --> comments.
117 * Skip over previous macro insertions
118 * Set marglen.
120 unsigned parens = 1;
121 unsigned char instring = 0;
122 unsigned incomment = 0;
123 unsigned intag = 0;
124 unsigned inexp = 0;
125 unsigned argn = 0;
127 unsigned v = 0;
129 Largstart:
130 #if 1
131 // Skip first space, if any, to find the start of the macro argument
132 if (v < end && isspace(p[v]))
133 v++;
134 #else
135 // Skip past spaces to find the start of the macro argument
136 for (; v < end && isspace(p[v]); v++)
138 #endif
139 *pmarg = p + v;
141 for (; v < end; v++)
142 { unsigned char c = p[v];
144 switch (c)
146 case ',':
147 if (!inexp && !instring && !incomment && parens == 1)
149 argn++;
150 if (argn == 1 && n == -1)
151 { v++;
152 goto Largstart;
154 if (argn == n)
155 break;
156 if (argn + 1 == n)
157 { v++;
158 goto Largstart;
161 continue;
163 case '(':
164 if (!inexp && !instring && !incomment)
165 parens++;
166 continue;
168 case ')':
169 if (!inexp && !instring && !incomment && --parens == 0)
171 break;
173 continue;
175 case '"':
176 case '\'':
177 if (!inexp && !incomment && intag)
179 if (c == instring)
180 instring = 0;
181 else if (!instring)
182 instring = c;
184 continue;
186 case '<':
187 if (!inexp && !instring && !incomment)
189 if (v + 6 < end &&
190 p[v + 1] == '!' &&
191 p[v + 2] == '-' &&
192 p[v + 3] == '-')
194 incomment = 1;
195 v += 3;
197 else if (v + 2 < end &&
198 isalpha(p[v + 1]))
199 intag = 1;
201 continue;
203 case '>':
204 if (!inexp)
205 intag = 0;
206 continue;
208 case '-':
209 if (!inexp &&
210 !instring &&
211 incomment &&
212 v + 2 < end &&
213 p[v + 1] == '-' &&
214 p[v + 2] == '>')
216 incomment = 0;
217 v += 2;
219 continue;
221 case 0xFF:
222 if (v + 1 < end)
224 if (p[v + 1] == '{')
225 inexp++;
226 else if (p[v + 1] == '}')
227 inexp--;
229 continue;
231 default:
232 continue;
234 break;
236 if (argn == 0 && n == -1)
237 *pmarg = p + v;
238 *pmarglen = p + v - *pmarg;
239 //printf("extractArg%d('%.*s') = '%.*s'\n", n, end, p, *pmarglen, *pmarg);
240 return v;
244 /*****************************************************
245 * Expand macro in place in buf.
246 * Only look at the text in buf from start to end.
249 void Macro::expand(OutBuffer *buf, unsigned start, unsigned *pend,
250 unsigned char *arg, unsigned arglen)
252 #if 0
253 printf("Macro::expand(buf[%d..%d], arg = '%.*s')\n", start, *pend, arglen, arg);
254 printf("Buf is: '%.*s'\n", *pend - start, buf->data + start);
255 #endif
257 static int nest;
258 if (nest > 100) // limit recursive expansion
259 return;
260 nest++;
262 unsigned end = *pend;
263 assert(start <= end);
264 assert(end <= buf->offset);
266 /* First pass - replace $0
268 arg = memdup(arg, arglen);
269 for (unsigned u = start; u + 1 < end; )
271 unsigned char *p = buf->data; // buf->data is not loop invariant
273 /* Look for $0, but not $$0, and replace it with arg.
275 if (p[u] == '$' && (isdigit(p[u + 1]) || p[u + 1] == '+'))
277 if (u > start && p[u - 1] == '$')
278 { // Don't expand $$0, but replace it with $0
279 buf->remove(u - 1, 1);
280 end--;
281 u += 1; // now u is one past the closing '1'
282 continue;
285 unsigned char c = p[u + 1];
286 int n = (c == '+') ? -1 : c - '0';
288 unsigned char *marg;
289 unsigned marglen;
290 extractArgN(arg, arglen, &marg, &marglen, n);
291 if (marglen == 0)
292 { // Just remove macro invocation
293 //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], marglen, marg);
294 buf->remove(u, 2);
295 end -= 2;
297 else if (c == '+')
299 // Replace '$+' with 'arg'
300 //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], marglen, marg);
301 buf->remove(u, 2);
302 buf->insert(u, marg, marglen);
303 end += marglen - 2;
305 // Scan replaced text for further expansion
306 unsigned mend = u + marglen;
307 expand(buf, u, &mend, NULL, 0);
308 end += mend - (u + marglen);
309 u = mend;
311 else
313 // Replace '$1' with '\xFF{arg\xFF}'
314 //printf("Replacing '$%c' with '\xFF{%.*s\xFF}'\n", p[u + 1], marglen, marg);
315 buf->data[u] = 0xFF;
316 buf->data[u + 1] = '{';
317 buf->insert(u + 2, marg, marglen);
318 buf->insert(u + 2 + marglen, "\xFF}", 2);
319 end += -2 + 2 + marglen + 2;
321 // Scan replaced text for further expansion
322 unsigned mend = u + 2 + marglen;
323 expand(buf, u + 2, &mend, NULL, 0);
324 end += mend - (u + 2 + marglen);
325 u = mend;
327 //printf("u = %d, end = %d\n", u, end);
328 //printf("#%.*s#\n", end, &buf->data[0]);
329 continue;
332 u++;
335 /* Second pass - replace other macros
337 for (unsigned u = start; u + 4 < end; )
339 unsigned char *p = buf->data; // buf->data is not loop invariant
341 /* A valid start of macro expansion is $(c, where c is
342 * an id start character, and not $$(c.
344 if (p[u] == '$' && p[u + 1] == '(' && isidstart(p[u + 2]))
346 //printf("\tfound macro start '%c'\n", p[u + 2]);
347 unsigned char *name = p + u + 2;
348 unsigned namelen = 0;
350 unsigned char *marg;
351 unsigned marglen;
353 unsigned v;
354 /* Scan forward to find end of macro name and
355 * beginning of macro argument (marg).
357 for (v = u + 2; v < end; v++)
358 { unsigned char c = p[v];
360 if (!isidchar(c))
361 { // We've gone past the end of the macro name.
362 namelen = v - (u + 2);
363 break;
367 v += extractArgN(p + v, end - v, &marg, &marglen, 0);
368 assert(v <= end);
370 if (v < end)
371 { // v is on the closing ')'
372 if (u > start && p[u - 1] == '$')
373 { // Don't expand $$(NAME), but replace it with $(NAME)
374 buf->remove(u - 1, 1);
375 end--;
376 u = v; // now u is one past the closing ')'
377 continue;
380 Macro *m = search(name, namelen);
381 if (m)
383 #if 0
384 if (m->textlen && m->text[0] == ' ')
385 { m->text++;
386 m->textlen--;
388 #endif
389 if (m->inuse && marglen == 0)
390 { // Remove macro invocation
391 buf->remove(u, v + 1 - u);
392 end -= v + 1 - u;
394 else if (m->inuse && arglen == marglen && memcmp(arg, marg, arglen) == 0)
395 { // Recursive expansion; just leave in place
398 else
400 //printf("\tmacro '%.*s'(%.*s) = '%.*s'\n", m->namelen, m->name, marglen, marg, m->textlen, m->text);
401 #if 1
402 marg = memdup(marg, marglen);
403 // Insert replacement text
404 buf->spread(v + 1, 2 + m->textlen + 2);
405 buf->data[v + 1] = 0xFF;
406 buf->data[v + 2] = '{';
407 memcpy(buf->data + v + 3, m->text, m->textlen);
408 buf->data[v + 3 + m->textlen] = 0xFF;
409 buf->data[v + 3 + m->textlen + 1] = '}';
411 end += 2 + m->textlen + 2;
413 // Scan replaced text for further expansion
414 m->inuse++;
415 unsigned mend = v + 1 + 2+m->textlen+2;
416 expand(buf, v + 1, &mend, marg, marglen);
417 end += mend - (v + 1 + 2+m->textlen+2);
418 m->inuse--;
420 buf->remove(u, v + 1 - u);
421 end -= v + 1 - u;
422 u += mend - (v + 1);
423 #else
424 // Insert replacement text
425 buf->insert(v + 1, m->text, m->textlen);
426 end += m->textlen;
428 // Scan replaced text for further expansion
429 m->inuse++;
430 unsigned mend = v + 1 + m->textlen;
431 expand(buf, v + 1, &mend, marg, marglen);
432 end += mend - (v + 1 + m->textlen);
433 m->inuse--;
435 buf->remove(u, v + 1 - u);
436 end -= v + 1 - u;
437 u += mend - (v + 1);
438 #endif
439 mem.free(marg);
440 //printf("u = %d, end = %d\n", u, end);
441 //printf("#%.*s#\n", end - u, &buf->data[u]);
442 continue;
445 else
447 // Replace $(NAME) with nothing
448 buf->remove(u, v + 1 - u);
449 end -= (v + 1 - u);
450 continue;
454 u++;
456 mem.free(arg);
457 *pend = end;
458 nest--;