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