1 module dpp.translation.macro_;
2 
3 
4 import dpp.from;
5 
6 
7 string[] translateMacro(in from!"clang".Cursor cursor,
8                         ref from!"dpp.runtime.context".Context context)
9     @safe
10     in(cursor.kind == from!"clang".Cursor.Kind.MacroDefinition)
11 {
12     import dpp.translation.dlang: maybeRename;
13     import clang: Cursor;
14     import std.file: exists;
15     import std.algorithm: startsWith;
16     import std.conv: text;
17 
18     // we want non-built-in macro definitions to be defined and then preprocessed
19     // again
20 
21     if(isBuiltinMacro(cursor)) return [];
22 
23     const tokens = cursor.tokens;
24 
25     // the only sane way for us to be able to see a macro definition
26     // for a macro that has already been defined is if an #undef happened
27     // in the meanwhile. Unfortunately, libclang has no way of passing
28     // that information to us
29     const maybeUndef = context.macroAlreadyDefined(cursor)
30         ? "#undef " ~ cursor.spelling
31         : "";
32 
33     context.rememberMacro(cursor);
34     const spelling = maybeRename(cursor, context);
35     const dbody = translateToD(cursor, context, tokens);
36 
37     // We try here to make it so that literal macros can be imported from
38     // another D module. We also try and make non-function-like macros
39     // that aren't a literal constant but an expression can be imported
40     // as well. To that end we check that we can mixin a declaration of
41     // an enum with the same name of the macro with the original C code.
42     // If so, we mix it in.
43     // Below that, we declare the macro so the the #including .dpp file
44     // uses the preprocessor.
45     if(!cursor.isMacroFunction && tokens.length > 1) {
46         const defineEnum = `enum ` ~ spelling ~ ` = ` ~ dbody ~ `;`;
47         const enumVarName = `enumMixinStr_` ~ spelling;
48         return [
49             `#ifdef ` ~ spelling,
50             `#    undef ` ~ spelling,
51             `#endif`,
52             `static if(!is(typeof(` ~ spelling ~ `))) {`,
53             "    private enum " ~ enumVarName ~ " = `" ~ defineEnum ~ "`;",
54             `    static if(is(typeof({ mixin(` ~ enumVarName ~ `); }))) {`,
55             `        mixin(`  ~ enumVarName ~ `);`,
56             `    }`,
57             `}`,
58             `#define ` ~ spelling ~ ` ` ~ dbody,
59         ];
60     }
61 
62     // Define a template function with the same name as the macro
63     // in an attempt to make it importable from outside the .dpp file.
64     enum prefix = "_dpp_impl_"; // can't use the macro name as-is
65     const emitFunction = cursor.isMacroFunction && context.options.functionMacros;
66     auto maybeFunction = emitFunction
67         ? macroToTemplateFunction(cursor, prefix, spelling)
68         : [];
69     const maybeSpace = cursor.isMacroFunction ? "" : " ";
70     const restOfLine = spelling ~ maybeSpace ~ dbody;
71     const maybeDefineWithPrefix = emitFunction
72         ? `#define ` ~ prefix ~ restOfLine
73         : "";
74     const define = `#define ` ~ restOfLine;
75 
76     return maybeUndef ~ maybeDefineWithPrefix ~ maybeFunction ~ define;
77 }
78 
79 private string[] macroToTemplateFunction(in from!"clang".Cursor cursor, in string prefix, in string spelling)
80     @safe
81     in(cursor.kind == from!"clang".Cursor.Kind.MacroDefinition)
82     in(cursor.isMacroFunction)
83 {
84     import clang : Token;
85     import std.algorithm : countUntil, count, map, startsWith;
86     import std.range: iota;
87     import std.conv: text;
88     import std.array : join;
89 
90     if(spelling.startsWith("__")) return [];
91 
92     const tokens = cursor.tokens;
93     assert(tokens[0].kind == Token.Kind.Identifier);
94     assert(tokens[1] == Token(Token.Kind.Punctuation, "("));
95 
96     const closeParenIndex = tokens[2 .. $].countUntil(Token(Token.Kind.Punctuation, ")")) + 2;
97     const numCommas = tokens[2 .. closeParenIndex].count(Token(Token.Kind.Punctuation, ","));
98     const numElements = closeParenIndex == 2 ? 0 : numCommas + 1;
99     const isVariadic = tokens[closeParenIndex - 1] == Token(Token.Kind.Punctuation, "...");
100     const numArgs = isVariadic ? numElements - 1 : numElements;
101     const maybeVarTemplate = isVariadic ? ", REST..." : "";
102     const templateParams = `(` ~ numArgs.iota.map!(i => text(`A`, i)).join(`, `) ~ maybeVarTemplate ~ `)`;
103     const maybeVarParam = isVariadic ? ", REST rest" : "";
104     const runtimeParams = `(` ~ numArgs.iota.map!(i => text(`A`, i, ` arg`, i)).join(`, `) ~ maybeVarParam ~ `)`;
105     const maybeVarArg = isVariadic ? ", rest" : "";
106     const runtimeArgs = numArgs.iota.map!(i => text(`arg`, i)).join(`, `) ~ maybeVarArg;
107     auto lines = [
108         `auto ` ~ spelling ~ templateParams ~ runtimeParams ~ ` {`,
109         `    return ` ~ prefix ~ spelling ~ `(` ~ runtimeArgs ~ `);`,
110         `}`,
111     ];
112     const functionMixinStr = lines.map!(l => "    " ~ l).join("\n");
113     const enumName = prefix ~ spelling ~ `_mixin`;
114     return [
115         `enum ` ~ enumName ~ " = `" ~ functionMixinStr ~ "`;",
116         `static if(__traits(compiles, { mixin(` ~  enumName ~ `); })) {`,
117         `    mixin(` ~ enumName ~ `);`,
118         `}`
119     ];
120 }
121 
122 
123 bool isBuiltinMacro(in from!"clang".Cursor cursor)
124     @safe
125 {
126     import clang: Cursor;
127     import std.file: exists;
128     import std.algorithm: startsWith;
129 
130     if(cursor.kind != Cursor.Kind.MacroDefinition) return false;
131 
132     return
133         cursor.sourceRange.path == ""
134         || !cursor.sourceRange.path.exists
135         || cursor.isPredefined
136         || cursor.spelling.startsWith("__STDC_")
137         ;
138 }
139 
140 
141 private bool isLiteralMacro(in from!"clang".Token[] tokens) @safe @nogc pure nothrow {
142     import clang: Token;
143 
144     return
145         tokens.length == 2
146         && tokens[0].kind == Token.Kind.Identifier
147         && tokens[1].kind == Token.Kind.Literal
148         ;
149 }
150 
151 
152 private string translateToD(
153     in from!"clang".Cursor cursor,
154     ref from!"dpp.runtime.context".Context context,
155     in from!"clang".Token[] tokens,
156     )
157     @safe
158 {
159     import dpp.translation.type: translateElaborated;
160     import clang: Token;
161     import std.algorithm: map;
162 
163     if(isLiteralMacro(tokens)) return fixLiteral(tokens[1]);
164     if(tokens.length == 1) return ""; // e.g. `#define FOO`
165 
166     auto fixLiteralOrPassThrough(in Token t) {
167         return t.kind == Token.Kind.Literal
168             ? Token(Token.Kind.Literal, fixLiteral(t))
169             : t;
170     }
171 
172     return tokens
173         .fixSizeof(cursor)
174         .fixCasts(cursor, context)
175         .fixArrow
176         .fixNull
177         .map!fixLiteralOrPassThrough
178         .toString
179         .translateElaborated(context)
180         ;
181 }
182 
183 
184 private string toString(R)(R tokens) {
185     import clang: Token;
186     import std.algorithm: map;
187     import std.array: join;
188 
189     // skip the identifier because of DPP_ENUM_
190     return tokens[1..$]
191         .map!(t => t.spelling)
192         .join(" ")
193         ;
194 
195 }
196 
197 private string fixLiteral(in from!"clang".Token token)
198     @safe pure
199     in(token.kind == from!"clang".Token.Kind.Literal)
200     do
201 {
202     return token.spelling
203         .fixLowercaseSuffix
204         .fixMultiCharacterLiterals
205         .fixWideCharStrings
206         .fixOctal
207         .fixMicrosoftSuffixes
208         .fixLongLong
209         ;
210 }
211 
212 
213 private auto fixArrow(R)(R tokens) {
214     import clang: Token;
215     import std.algorithm: map;
216 
217     static const(Token) replace(in Token token) {
218         return token == Token(Token.Kind.Punctuation, "->")
219             ? Token(Token.Kind.Punctuation, ".")
220             : token;
221     }
222 
223     return tokens
224         .map!replace
225         ;
226 }
227 
228 private auto fixNull(R)(R tokens)
229 {
230     import clang: Token;
231     import std.algorithm: map;
232     import std.array: array;
233 
234     static const(Token) replace(in Token token) {
235         return token == Token(Token.Kind.Identifier, "NULL")
236             ? Token(Token.Kind.Identifier, "null")
237             : token;
238     }
239 
240     return tokens
241         .map!replace
242         ;
243 }
244 
245 version(Windows)
246 private string fixMicrosoftSuffixes(in string str) @safe pure nothrow {
247     import std.algorithm: endsWith;
248 
249     if(str.endsWith("i64"))
250         return str[0 .. $-3] ~ "L";
251     else if(str.endsWith("i32"))
252         return str[0 .. $-3];
253     else if(str.endsWith("i16"))
254         return str[0 .. $-3];
255     else if(str.endsWith("i8"))
256         return str[0 .. $-3];
257     return str;
258 }
259 else
260 private string fixMicrosoftSuffixes(in string str) @safe pure nothrow {
261     return str;
262 }
263 
264 private string fixWideCharStrings(in string str) @safe pure nothrow {
265     if(str.length >=3 && str[0] == 'L' && str[1] == '"' && str[$-1] == '"') {
266         return str[1 .. $] ~ "w";
267     }
268 
269     return str;
270 }
271 
272 private string fixMultiCharacterLiterals(in string str) @safe pure nothrow {
273     // multi-character literals are implementation-defined, but allowed,
274     // in C I aim to identify them and then distinguish them from a
275     // non-ASCII character, which I'll just forward to D assuming utf-8 source
276     // moreover, the '\uxxx' or other escape sequences should be forwarded
277     if(str.length > 3 && str[0] == '\'' && str[$-1] == '\'' && str[1] != '\\') {
278         // apparently a multi-char literal, let's translate to int
279         // the way this is typically done in common compilers, e.g.
280         // https://gcc.gnu.org/onlinedocs/cpp/Implementation-defined-behavior.html
281         int result;
282         foreach(char ch; str[1 .. $-1]) {
283             // any multi-byte character I'm going to assume
284             // is just a single UTF-8 char and punt on it.
285             if(ch > 127) return str;
286             result <<= 8;
287             result |= cast(ubyte) ch;
288         }
289         import std.conv;
290         return to!string(result);
291     }
292     return str; // not one of these, don't touch
293 }
294 
295 private string fixLowercaseSuffix(in string str) @safe pure nothrow {
296     import std.algorithm: endsWith;
297 
298     if(str.endsWith("ll"))
299         return str[0 .. $-2] ~ "LL";
300     if(str.endsWith("l"))
301         return str[0 .. $-1] ~ "L";
302     return str;
303 }
304 
305 private string fixLongLong(in string str) @safe pure {
306     import std.uni : toUpper;
307     const suffix = str.length < 3 ? "" : str[$-3 .. $].toUpper;
308 
309     if (suffix.length > 0) {
310         if (suffix == "LLU" || suffix == "ULL")
311             return str[0 .. $-3] ~ "LU";
312 
313         if (suffix[1 .. $] == "LL")
314             return str[0 .. $-2] ~ "L";
315     }
316 
317     return str;
318 }
319 
320 
321 private string fixOctal(in string spelling) @safe pure {
322     import clang: Token;
323     import std.algorithm: countUntil;
324     import std.uni: isNumber;
325     import std.conv : text;
326 
327     const isOctal =
328         spelling.length > 1
329         && spelling[0] == '0'
330         && spelling[1].isNumber
331         ;
332 
333     if(!isOctal) return spelling;
334 
335     const firstNonZero = spelling.countUntil!(a => a != '0');
336     if(firstNonZero == -1) return "0";
337 
338     const base8_representation = spelling[firstNonZero .. $];
339     const base8_length = base8_representation.length;
340     int base10_number = 0;
341     foreach(i, c; base8_representation)
342     {
343         const power = base8_length - i - 1;
344         const digit = c - '0';
345         base10_number += digit * 8 ^^ power;
346     }
347 
348     return "/+converted from octal '" ~ base8_representation ~ "'+/ " ~ base10_number.text;
349 }
350 
351 
352 private auto fixSizeof(R)(R tokens, in from !"clang".Cursor cursor)
353 {
354     import clang: Token;
355     import std.conv: text;
356     import std.algorithm: countUntil;
357 
358     // find the closing paren for the function-like macro's argument list
359     size_t lastIndex = 0;
360     if(cursor.isMacroFunction) {
361         lastIndex = tokens
362             .countUntil!(t => t == Token(Token.Kind.Punctuation, ")"))
363             +1; // skip the right paren
364 
365         if(lastIndex == 0)  // given the +1 above, -1 becomes 0
366             throw new Exception(text("Can't fix sizeof in function-like macro with tokens: ", tokens));
367     }
368 
369     const beginning = tokens[0 .. lastIndex];
370     const(Token)[] middle;
371 
372     for(size_t i = lastIndex; i < tokens.length - 1; ++i) {
373         if(tokens[i] == Token(Token.Kind.Keyword, "sizeof")
374            && tokens[i + 1] == Token(Token.Kind.Punctuation, "("))
375         {
376             // find closing paren
377             long open = 1;
378             size_t scanIndex = i + 2;  // skip i + 1 since that's the open paren
379 
380             while(open != 0) {
381                 if(tokens[scanIndex] == Token(Token.Kind.Punctuation, "("))
382                     ++open;
383                 if(tokens[scanIndex] == Token(Token.Kind.Punctuation, ")"))
384                     --open;
385 
386                 ++scanIndex;
387             }
388 
389             middle ~= tokens[lastIndex .. i] ~ tokens[i + 1 .. scanIndex] ~ Token(Token.Kind.Keyword, ".sizeof");
390             lastIndex = scanIndex;
391             // advance i past the sizeof. -1 because of ++i in the for loop
392             i = lastIndex - 1;
393         }
394     }
395 
396     // can't chain here due to fixCasts appending to const(Token)[]
397     return beginning ~ middle ~ tokens[lastIndex .. $];
398 }
399 
400 
401 private auto fixCasts(R)(
402     R tokens,
403     in from !"clang".Cursor cursor,
404     in from!"dpp.runtime.context".Context context,
405     )
406 {
407     import dpp.translation.exception: UntranslatableException;
408     import dpp.translation.type : translateString;
409     import clang: Token;
410     import std.conv: text;
411     import std.algorithm: countUntil, count, canFind, all, map;
412     import std.range: chain;
413     import std.array: split, join;
414 
415     // If the cursor is a macro function return its parameters
416     Token[] macroFunctionParams() {
417         assert(cursor.tokens[0].kind == Token.Kind.Identifier);
418         assert(cursor.tokens[1] == Token(Token.Kind.Punctuation, "("));
419         enum fromParen = 2;
420         const closeParenIndex = cursor.tokens[fromParen .. $].countUntil(Token(Token.Kind.Punctuation, ")")) + fromParen;
421         return cursor.tokens[fromParen .. closeParenIndex].split(Token(Token.Kind.Punctuation, ",")).join;
422     }
423 
424     const params = cursor.isMacroFunction ? macroFunctionParams : [];
425 
426     // if the token array is a built-in or user-defined type
427     bool isType(in Token[] tokens) {
428 
429         if( // fundamental type
430             tokens.length == 1
431             && tokens[0].kind == Token.Kind.Keyword
432             && tokens[0].spelling != "sizeof"
433             && tokens[0].spelling != "alignof"
434             )
435             return true;
436 
437         // fundamental type like `unsigned char`
438         if(tokens.length > 1 && tokens.all!(t => t.kind == Token.Kind.Keyword))
439             return true;
440 
441         if( // user defined type
442             tokens.length == 1
443             && tokens[0].kind == Token.Kind.Identifier
444             && context.isUserDefinedType(tokens[0].spelling)
445             )
446             return true;
447 
448         if(  // pointer to a type
449             tokens.length >= 2
450             && tokens[$-1] == Token(Token.Kind.Punctuation, "*")
451             && (isType(tokens[0 .. $-1]) || params.canFind(tokens[$-2]) )
452             )
453             return true;
454 
455         if( // const type
456             tokens.length >= 2
457             && tokens[0] == Token(Token.Kind.Keyword, "const")
458             && isType(tokens[1..$])
459             )
460             return true;
461 
462         if( // typeof
463             tokens.length >= 2
464             && tokens[0] == Token(Token.Kind.Keyword, "typeof")
465             )
466             return true;
467 
468         if ( // macro attribute (e.g. __force) + type
469             tokens.length >= 2
470             && tokens[0].kind == Token.Kind.Identifier
471             && isType(tokens[1..$])
472             )
473             return true;
474 
475         return false;
476     }
477 
478     size_t lastIndex = 0;
479     // find the closing paren for the function-like macro's argument list
480     if(cursor.isMacroFunction) {
481         lastIndex = tokens
482             .countUntil!(t => t == Token(Token.Kind.Punctuation, ")"))
483             +1; // skip the right paren
484         if(lastIndex == 0)
485             throw new Exception(text("Can't fix casts in function-like macro with tokens: ", tokens));
486     }
487 
488     const beginning = tokens[0 .. lastIndex];
489     const(Token)[] middle;
490 
491     // See #244 - macros can have unbalanced parentheses
492     // Apparently libclang tokenises `\\n)` as including the backslash and the newline
493     const numLeftParens  = tokens.count!(a => a == Token(Token.Kind.Punctuation, "(") ||
494                                          a == Token(Token.Kind.Punctuation, "\\\n("));
495     const numRightParens = tokens.count!(a => a == Token(Token.Kind.Punctuation, ")") ||
496                                          a == Token(Token.Kind.Punctuation, "\\\n)"));
497 
498     if(numLeftParens != numRightParens)
499         throw new UntranslatableException("Unbalanced parentheses in macro `" ~ cursor.spelling ~ "`");
500 
501     for(size_t i = lastIndex; i < tokens.length - 1; ++i) {
502         if(tokens[i] == Token(Token.Kind.Punctuation, "(")) {
503             // find closing paren
504             long open = 1;
505             size_t scanIndex = i + 1;  // skip i + 1 since that's the open paren
506 
507             while(open != 0) {
508                 if(tokens[scanIndex] == Token(Token.Kind.Punctuation, "("))
509                     ++open;
510                 // for the 2nd condition, esee it.c.compile.preprocessor.multiline
511                 if(tokens[scanIndex] == Token(Token.Kind.Punctuation, ")") ||
512                    tokens[scanIndex] == Token(Token.Kind.Punctuation, "\\\n)"))
513                     --open;
514 
515                 ++scanIndex;
516             }
517             // at this point scanIndex is the 1 + index of closing paren
518 
519             // we want to ignore e.g. `(int)(foo).sizeof` even if `foo` is a type
520             const followedByDot =
521                 tokens.length > scanIndex
522                 && tokens[scanIndex].spelling[0] == '.'
523                 ;
524 
525             if(isType(tokens[i + 1 .. scanIndex - 1]) && !followedByDot) {
526                 // -1 to not include the closing paren
527                 const cTypeString = tokens[i + 1 .. scanIndex - 1].map!(t => t.spelling).join(" ");
528                 const dTypeString = translateString(cTypeString, context);
529                 middle ~= tokens[lastIndex .. i] ~
530                     Token(Token.Kind.Punctuation, "cast(") ~
531                     Token(Token.Kind.Keyword, dTypeString) ~
532                     Token(Token.Kind.Punctuation, ")");
533 
534                 lastIndex = scanIndex;
535                 // advance i past the sizeof. -1 because of ++i in the for loop
536                 i = lastIndex - 1;
537             }
538         }
539     }
540 
541     return chain(beginning, middle, tokens[lastIndex .. $]);
542 }