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 }