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 }