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 }