2 * Text macro processor for Ddoc.
4 * Copyright: Copyright (C) 1999-2024 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
14 import core
.stdc
.ctype
;
15 import core
.stdc
.string
;
16 import dmd
.common
.outbuffer
;
23 /**********************************
24 * Define name=text macro.
25 * If macro `name` already exists, replace the text for it.
27 * name = name of macro
28 * text = text of macro
30 void define(const(char)[] name
, const(char)[] text
) nothrow pure @safe
32 //printf("MacroTable::define('%.*s' = '%.*s')\n", cast(int)name.length, name.ptr, text.length, text.ptr);
33 if (auto table
= name
in mactab
)
38 mactab
[name
] = new Macro(name
, text
);
41 alias fp_t
= bool function(const(char)* p
) @nogc nothrow pure;
43 /*****************************************************
44 * Look for macros in buf and expand them in place.
45 * Only look at the text in buf from start to pend.
47 * Returns: `true` on success, `false` when the recursion limit was reached
49 bool expand(ref OutBuffer buf
, size_t start
, ref size_t pend
, const(char)[] arg
, int recursionLimit
,
50 fp_t isIdStart
, fp_t isIdTail
) nothrow pure
54 printf("Macro::expand(buf[%d..%d], arg = '%.*s')\n", start
, pend
, cast(int)arg
.length
, arg
.ptr
);
55 printf("Buf is: '%.*s'\n", cast(int)(pend
- start
), buf
.data
+ start
);
57 // limit recursive expansion
59 if (recursionLimit
< 0)
64 assert(end
<= buf
.length
);
65 /* First pass - replace $0
68 for (size_t u
= start
; u
+ 1 < end
;)
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.
73 if (p
[u
] == '$' && (isdigit(p
[u
+ 1]) || p
[u
+ 1] == '+'))
75 if (u
> start
&& p
[u
- 1] == '$')
77 // Don't expand $$0, but replace it with $0
80 u
+= 1; // now u is one past the closing '1'
84 int n
= (c
== '+') ?
-1 : c
- '0';
91 extractArgN(arg
, marg
, n
);
94 // Just remove macro invocation
95 //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], cast(int)marg.length, marg.ptr);
101 // Replace '$+' with 'arg'
102 //printf("Replacing '$%c' with '%.*s'\n", p[u + 1], cast(int)marg.length, marg.ptr);
105 end
+= marg
.length
- 2;
106 // Scan replaced text for further expansion
107 size_t mend
= u
+ marg
.length
;
108 const success
= expand(buf
, u
, mend
, null, recursionLimit
, isIdStart
, isIdTail
);
111 end
+= mend
- (u
+ marg
.length
);
116 // Replace '$1' with '\xFF{arg\xFF}'
117 //printf("Replacing '$%c' with '\xFF{%.*s\xFF}'\n", p[u + 1], cast(int)marg.length, marg.ptr);
118 ubyte[] slice
= cast(ubyte[])buf
[];
121 buf
.insert(u
+ 2, marg
);
122 buf
.insert(u
+ 2 + marg
.length
, "\xFF}");
123 end
+= -2 + 2 + marg
.length
+ 2;
124 // Scan replaced text for further expansion
125 size_t mend
= u
+ 2 + marg
.length
;
126 const success
= expand(buf
, u
+ 2, mend
, null, recursionLimit
, isIdStart
, isIdTail
);
129 end
+= mend
- (u
+ 2 + marg
.length
);
132 //printf("u = %d, end = %d\n", u, end);
133 //printf("#%.*s#\n", cast(int)end, &buf.data[0]);
138 /* Second pass - replace other macros
140 for (size_t u
= start
; u
+ 4 < end
;)
142 char* p
= cast(char*)buf
[].ptr
; // buf.data is not loop invariant
143 /* A valid start of macro expansion is $(c, where c is
144 * an id start character, and not $$(c.
146 if (p
[u
] == '$' && p
[u
+ 1] == '(' && isIdStart(p
+ u
+ 2))
148 //printf("\tfound macro start '%c'\n", p[u + 2]);
149 char* name
= p
+ u
+ 2;
153 /* Scan forward to find end of macro name and
154 * beginning of macro argument (marg).
156 for (v
= u
+ 2; v
< end
; v
+= utfStride(p
[v
]))
158 if (!isIdTail(p
+ v
))
160 // We've gone past the end of the macro name.
161 namelen
= v
- (u
+ 2);
165 v
+= extractArgN(p
[v
.. end
], marg
, 0);
169 // v is on the closing ')'
170 if (u
> start
&& p
[u
- 1] == '$')
172 // Don't expand $$(NAME), but replace it with $(NAME)
173 buf
.remove(u
- 1, 1);
175 u
= v
; // now u is one past the closing ')'
178 Macro
* m
= search(name
[0 .. namelen
]);
181 immutable undef
= "DDOC_UNDEFINED_MACRO";
185 // Macro was not defined, so this is an expansion of
186 // DDOC_UNDEFINED_MACRO. Prepend macro name to args.
187 // marg = name[ ] ~ "," ~ marg[ ];
190 char* q
= cast(char*)mem
.xmalloc(namelen
+ 1 + marg
.length
);
192 memcpy(q
, name
, namelen
);
194 memcpy(q
+ namelen
+ 1, marg
.ptr
, marg
.length
);
195 marg
= q
[0 .. marg
.length
+ namelen
+ 1];
199 marg
= name
[0 .. namelen
];
205 if (m
.inuse
&& marg
.length
== 0)
207 // Remove macro invocation
208 buf
.remove(u
, v
+ 1 - u
);
211 else if (m
.inuse
&& ((arg
.length
== marg
.length
&& memcmp(arg
.ptr
, marg
.ptr
, arg
.length
) == 0) ||
212 (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] == '}')))
214 /* Recursive expansion:
215 * marg is same as arg (with blue paint added)
216 * Just leave in place.
221 //printf("\tmacro '%.*s'(%.*s) = '%.*s'\n", cast(int)m.namelen, m.name, cast(int)marg.length, marg.ptr, cast(int)m.textlen, m.text);
223 // Insert replacement text
224 buf
.spread(v
+ 1, 2 + m
.text
.length
+ 2);
225 ubyte[] slice
= cast(ubyte[])buf
[];
228 slice
[v
+ 3 .. v
+ 3 + m
.text
.length
] = cast(ubyte[])m
.text
[];
229 slice
[v
+ 3 + m
.text
.length
] = 0xFF;
230 slice
[v
+ 3 + m
.text
.length
+ 1] = '}';
231 end
+= 2 + m
.text
.length
+ 2;
232 // Scan replaced text for further expansion
234 size_t mend
= v
+ 1 + 2 + m
.text
.length
+ 2;
235 const success
= expand(buf
, v
+ 1, mend
, marg
, recursionLimit
, isIdStart
, isIdTail
);
238 end
+= mend
- (v
+ 1 + 2 + m
.text
.length
+ 2);
240 buf
.remove(u
, v
+ 1 - u
);
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]);
251 // Replace $(NAME) with nothing
252 buf
.remove(u
, v
+ 1 - u
);
260 mem
.xfree(cast(char*)arg
);
267 Macro
* search(const(char)[] name
) @nogc nothrow pure @safe
269 //printf("Macro::search(%.*s)\n", cast(int)name.length, name.ptr);
270 if (auto table
= name
in mactab
)
272 //printf("\tfound %d\n", table.textlen);
278 private Macro
*[const(char)[]] mactab
;
281 /* ************************************************************************ */
287 const(char)[] name
; // macro name
288 const(char)[] text
; // macro replacement text
289 int inuse
; // macro is in use (don't expand)
291 this(const(char)[] name
, const(char)[] text
) @nogc nothrow pure @safe
298 /************************
299 * Make mutable copy of slice p.
303 * copy allocated with mem.xmalloc()
306 char[] memdup(const(char)[] p
) nothrow pure
308 size_t len
= p
.length
;
309 return (cast(char*)memcpy(mem
.xmalloc(len
), p
.ptr
, len
))[0 .. len
];
312 /**********************************************************
313 * Given buffer buf[], extract argument marg[].
315 * buf = source string
316 * marg = set to slice of buf[]
317 * n = 0: get entire argument
318 * 1..9: get nth argument
319 * -1: get 2nd through end
321 size_t
extractArgN(const(char)[] buf
, out const(char)[] marg
, int n
) @nogc nothrow pure
323 /* Scan forward for matching right parenthesis.
325 * Skip over "..." and '...' strings inside HTML tags.
326 * Skip over <!-- ... --> comments.
327 * Skip over previous macro insertions
338 const end
= buf
.length
;
340 // Skip first space, if any, to find the start of the macro argument
341 if (n
!= 1 && v
< end
&& isspace(p
[v
]))
350 if (!inexp
&& !instring
&& !incomment
&& parens
== 1)
353 if (argn
== 1 && n
== -1)
368 if (!inexp
&& !instring
&& !incomment
)
372 if (!inexp
&& !instring
&& !incomment
&& --parens
== 0)
379 if (!inexp
&& !incomment
&& intag
)
388 if (!inexp
&& !instring
&& !incomment
)
390 if (v
+ 6 < end
&& p
[v
+ 1] == '!' && p
[v
+ 2] == '-' && p
[v
+ 3] == '-')
395 else if (v
+ 2 < end
&& isalpha(p
[v
+ 1]))
404 if (!inexp
&& !instring
&& incomment
&& v
+ 2 < end
&& p
[v
+ 1] == '-' && p
[v
+ 2] == '>')
415 else if (p
[v
+ 1] == '}')
424 if (argn
== 0 && n
== -1)
427 marg
= p
[vstart
.. v
];
428 //printf("extractArg%d('%.*s') = '%.*s'\n", n, cast(int)end, p, cast(int)marg.length, marg.ptr);
432 /*****************************************
433 * Get number of UTF-8 code units in code point that starts with `c`
435 * c = starting code unit
436 * Returns: number of UTF-8 code units (i.e. bytes), else 1 on invalid UTF start
439 int utfStride(char c
) @nogc nothrow pure
443 c
< 0xC0 ?
1 : // invalid UTF start
449 1; // invalid UTF start
454 assert(utfStride(0) == 1);
455 assert(utfStride(0x80) == 1);
456 assert(utfStride(0xC0) == 2);
457 assert(utfStride(0xE0) == 3);
458 assert(utfStride(0xF0) == 4);
459 assert(utfStride(0xF8) == 5);
460 assert(utfStride(0xFC) == 6);
461 assert(utfStride(0xFE) == 1);