1 module dpp.translation.macro_;
2 
3 import dpp.from;
4 
5 string[] translateMacro(in from!"clang".Cursor cursor,
6                         ref from!"dpp.runtime.context".Context context)
7     @safe
8 {
9     import dpp.translation.dlang: maybeRename;
10     import clang: Cursor;
11     import std.file: exists;
12     import std.algorithm: startsWith, canFind;
13     import std.conv: text;
14 
15     assert(cursor.kind == Cursor.Kind.MacroDefinition);
16 
17     // we want non-built-in macro definitions to be defined and then preprocessed
18     // again
19 
20     auto range = cursor.sourceRange;
21 
22     if(range.path == "" || !range.path.exists ||
23        cursor.isPredefined || cursor.spelling.startsWith("__STDC_")) { //built-in macro
24         return [];
25     }
26 
27     // the only sane way for us to be able to see a macro definition
28     // for a macro that has already been defined is if an #undef happened
29     // in the meanwhile. Unfortunately, libclang has no way of passing
30     // that information to us
31     string maybeUndef;
32     if(context.macroAlreadyDefined(cursor))
33         maybeUndef = "#undef " ~ cursor.spelling ~ "\n";
34 
35     context.rememberMacro(cursor);
36     const spelling = maybeRename(cursor, context);
37     const body_ = range.chars.text[cursor.spelling.length .. $];
38     const dbody = translateToD(body_, context);
39 
40     auto redefinition = [maybeUndef ~ "#define " ~ spelling ~ dbody ~ "\n"];
41 
42     // Always redefine the macro, but sometimes also add a D enum
43     // See #103 for why.
44     return onlyRedefine(cursor, dbody)
45         ? redefinition
46         : redefinition ~ [`enum DPP_ENUM_` ~ spelling ~ ` = ` ~ dbody ~ `;`];
47 }
48 
49 
50 private auto chars(in from!"clang".SourceRange range) @safe {
51     import std.stdio: File;
52 
53     const startPos = range.start.offset;
54     const endPos   = range.end.offset;
55 
56     auto file = File(range.path);
57     file.seek(startPos);
58     return () @trusted { return file.rawRead(new char[endPos - startPos]); }();
59 }
60 
61 
62 // whether the macro should only be re-#defined
63 private bool onlyRedefine(in from!"clang".Cursor cursor, in string dbody) @safe pure {
64     import std..string: strip;
65     import std.algorithm: canFind;
66 
67     const isBodyString = dbody.strip.length >=2 && dbody[0] == '"' && dbody.strip[$-1] == '"';
68     const isBodyInteger = dbody.isStringRepr!long;
69     const isBodyFloating = dbody.isStringRepr!double;
70     const isOctal = dbody.strip.canFind("octal!");
71 
72     // See #103 for check to where it's a macro function or not
73     return
74         cursor.isMacroFunction ||
75         (!isBodyString && !isBodyInteger && !isBodyFloating && !isOctal);
76 }
77 
78 private bool isStringRepr(T)(in string str) @safe pure {
79     import std.conv: to;
80     import std.exception: collectException;
81     import std..string: strip;
82 
83     T dummy;
84     return str.strip.to!T.collectException(dummy) is null;
85 }
86 
87 
88 // Some macros define snippets of C code that aren't valid D
89 // We attempt to translate them here.
90 private string translateToD(in string line, in from!"dpp.runtime.context".Context context) @trusted {
91     import dpp.translation.type: translateElaborated;
92     import dpp.translation.exception: UntranslatableException;
93     import std.array: replace;
94     import std.regex: regex, replaceAll;
95 
96     static typeof(regex(``)) sizeofRegex = void;
97     static bool init;
98 
99     if(!init) {
100         init = true;
101         sizeofRegex = regex(`sizeof *?\(([^)]+)\)`);
102     }
103 
104     auto replacements = line
105         .replace("->", ".")
106         .replaceNull
107         ;
108 
109     string regexReps;
110 
111     try {
112         regexReps = replacements
113             .replaceAll(sizeofRegex, "($1).sizeof")
114             .replaceAll(context.castRegex, "cast($1)")
115             ;
116     } catch(Exception ex)
117         throw new UntranslatableException("Regex substitution failed: " ~ ex.msg);
118 
119     return regexReps
120         .fixOctal
121         .translateElaborated
122         ;
123 }
124 
125 
126 private string replaceNull(in string str) @safe pure nothrow {
127     import std.array: replace;
128     import std.algorithm: startsWith;
129     // we don't want to translate the definition of NULL itself
130     return str.startsWith("NULL") ? str : str.replace("NULL", "null");
131 }
132 
133 private string fixOctal(in string str) @safe pure {
134     import std..string: strip;
135     import std.algorithm: countUntil, all;
136     import std.exception: enforce;
137     import std.uni: isWhite;
138 
139     const stripped = str.strip;
140     const isOctal =
141         stripped.length > 1 &&
142         stripped[0] == '0' &&
143         stripped.isStringRepr!long;
144 
145     if(!isOctal) return str;
146 
147     const firstNonZero = stripped.countUntil!(a => a != '0');
148 
149     if(firstNonZero == -1) {
150         enforce(str.all!(a => a == '0' || a.isWhite), "Cannot fix octal '" ~ str ~ "'");
151         return "0";
152     }
153 
154     return ` std.conv.octal!` ~ stripped[firstNonZero .. $];
155 }