1 /** 2 C++ template translations 3 */ 4 module dpp.translation.template_; 5 6 7 import dpp.from; 8 9 10 string templateParamList(R)(R range) { 11 import std.array: join; 12 return `(` ~ () @trusted { return range.join(", "); }() ~ `)`; 13 } 14 15 string templateSpelling(R)(in from!"clang".Cursor cursor, R range) { 16 return cursor.spelling ~ templateParamList(range); 17 } 18 19 20 // Deal with full and partial template specialisations 21 package string[] translateSpecialisedTemplateParams( 22 in from!"clang".Cursor cursor, 23 ref from!"dpp.runtime.context".Context context) 24 @safe 25 in(cursor.type.numTemplateArguments != -1) 26 do 27 { 28 return isFromVariadicTemplate(cursor) 29 ? translateSpecialisedTemplateParamsVariadic(cursor, context) 30 : translateSpecialisedTemplateParamsFinite(cursor, context); 31 } 32 33 34 private string[] translateSpecialisedTemplateParamsFinite( 35 in from!"clang".Cursor cursor, 36 ref from!"dpp.runtime.context".Context context) 37 @safe 38 { 39 import dpp.translation.type: translate, templateArgumentKind, TemplateArgumentKind, 40 translateTemplateParamSpecialisation; 41 import clang: Type; 42 import std.algorithm: map; 43 import std.range: iota; 44 import std.array: array, join; 45 import std.typecons: No; 46 47 // get the original list of template parameters and translate them 48 // e.g. template<bool, bool, typename> -> (bool V0, bool V1, T) 49 const translatedTemplateParams = () @trusted { 50 return translateTemplateParams(cursor, context, No.defaults) 51 .array; 52 }(); 53 54 // e.g. for template<> struct foo<false, true, int32_t> 55 // 0 -> `bool V0: false`, 1 -> `bool V1: true`, 2 -> `T0: int` 56 string element(in Type templateArgType, in int index) { 57 58 import dpp.translation.exception: UntranslatableException; 59 60 string errorMsg(in string keyword) { 61 import std.conv: text; 62 return text("Cannot translate ", index, "th template arg of ", cursor, " due to `", 63 keyword, "` template type parameter specialisation"); 64 } 65 66 if(templateArgType.isConstQualified) 67 throw new UntranslatableException(errorMsg(`const`)); 68 69 if(templateArgType.isVolatileQualified) 70 throw new UntranslatableException(errorMsg(`volatile`)); 71 72 string ret = translatedTemplateParams[index]; // e.g. `T`, `bool V0` 73 const maybeSpecialisation = translateTemplateParamSpecialisation(cursor.type, templateArgType, index, context); 74 const templateArgKind = templateArgumentKind(templateArgType); 75 76 with(TemplateArgumentKind) { 77 const isSpecialised = 78 templateArgKind == SpecialisedType || 79 (templateArgKind == Value && isValueOfType(cursor, context, index, maybeSpecialisation)); 80 81 if(isSpecialised) ret ~= ": " ~ maybeSpecialisation; 82 } 83 84 return ret; 85 } 86 87 return () @trusted { 88 return 89 cursor.type.numTemplateArguments 90 .iota 91 .map!(i => element(cursor.type.typeTemplateArgument(i), i)) 92 .array 93 ; 94 }(); 95 } 96 97 98 // FIXME: refactor 99 private auto translateSpecialisedTemplateParamsVariadic(in from!"clang".Cursor cursor, 100 ref from!"dpp.runtime.context".Context context) 101 @safe 102 in(isFromVariadicTemplate(cursor) && cursor.type.numTemplateArguments != -1) 103 do 104 { 105 import dpp.translation.type: translate; 106 107 string[] ret; 108 109 foreach(i; 0 .. cursor.type.numTemplateArguments) { 110 ret ~= translate(cursor.type.typeTemplateArgument(i), context); 111 } 112 113 return ret; 114 } 115 116 // In the case cursor is a partial or full template specialisation, 117 // check to see if `maybeSpecialisation` can be converted to the 118 // indexth template parater of the cursor's original template. 119 // If it can, then it's a value of that type. 120 private bool isValueOfType( 121 in from!"clang".Cursor cursor, 122 ref from!"dpp.runtime.context".Context context, 123 in int index, 124 in string maybeSpecialisation, 125 ) 126 @safe 127 { 128 import dpp.translation.type: translate; 129 import dpp.translation.exception: UntranslatableException; 130 import std.array: array; 131 import std.exception: collectException; 132 import std.conv: to; 133 import core.stdc.config: c_long, c_ulong; 134 135 // the original template cursor (no specialisations) 136 const templateCursor = cursor.specializedCursorTemplate; 137 // the type of the indexth template parameter 138 const templateParamCursor = () @trusted { return templateCursor.templateParams.array[index]; }(); 139 // the D translation of that type 140 const dtype = translate(templateParamCursor.type, context); 141 142 Exception conversionException; 143 144 void tryConvert(T)() { 145 conversionException = collectException(maybeSpecialisation.to!T); 146 } 147 148 switch(dtype) { 149 default: throw new UntranslatableException("isValueOfType cannot handle type `" ~ dtype ~ "`"); 150 case "bool": tryConvert!bool; break; 151 case "char": tryConvert!char; break; 152 case "wchar": tryConvert!wchar; break; 153 case "dchar": tryConvert!dchar; break; 154 case "short": tryConvert!short; break; 155 case "ushort": tryConvert!ushort; break; 156 case "int": tryConvert!int; break; 157 case "uint": tryConvert!uint; break; 158 case "long": tryConvert!long; break; 159 case "ulong": tryConvert!ulong; break; 160 case "c_ulong": tryConvert!c_ulong; break; 161 case "c_long": tryConvert!c_long; break; 162 } 163 164 return conversionException is null; 165 } 166 167 168 // Translates a C++ template parameter (value or type) to a D declaration 169 // e.g. `template<typename, bool, typename>` -> ["T0", "bool V0", "T1"] 170 // Returns a range of string 171 package auto translateTemplateParams( 172 in from!"clang".Cursor cursor, 173 ref from!"dpp.runtime.context".Context context, 174 from!"std.typecons".Flag!"defaults" defaults = from!"std.typecons".Yes.defaults, 175 ) @safe 176 { 177 import dpp.translation.type: translate, translateString; 178 import clang: Cursor; 179 import std.conv: text; 180 import std.algorithm: map, filter, countUntil; 181 import std.array: array; 182 import std.range: enumerate; 183 184 int templateParamIndex; // used to generate names when there are none 185 186 string newTemplateParamName() { 187 // FIXME 188 // the naming convention is to match what libclang gives, but there's no 189 // guarantee that it'll always match. 190 return text("type_parameter_0_", templateParamIndex++); 191 } 192 193 // translate a template parameter cursor 194 string translateTemplateParam(in long index, in Cursor templateParam) { 195 import dpp.translation.type: translate; 196 import dpp.translation.tokens: translateTokens; 197 import clang: Token; 198 199 // The template parameter might be a value (bool, int, etc.) 200 // or a type. If it's a value we get its type here. 201 const maybeType = 202 // a type doesn't have a type 203 templateParam.kind == Cursor.Kind.TemplateTypeParameter 204 // In C++, variadic templates can be values of a type, e.g. 205 // `template<int...>` 206 // The only way to declare this in D would be using a template contraint, 207 // but the main declaration just needs a name and the ellipsis - in D variadic 208 // templates can be types, values, or symbols. To prevent us from trying to 209 // declare in D `int param...`, which isn't valid, we don't use the type of 210 // a value parameter if it's variadic 211 || (cursor.isVariadicTemplate && index == cursor.templateParams.length - 1) 212 ? "" 213 : translate(templateParam.type, context) ~ " "; 214 215 // D requires template parameters to have names 216 const spelling = templateParam.spelling == "" ? newTemplateParamName : templateParam.spelling; 217 218 // There's no direct way to extract default template parameters from libclang 219 // so we search for something like `T = Foo` in the tokens 220 const equalIndex = templateParam.tokens.countUntil!(t => t.kind == Token.Kind.Punctuation && 221 t.spelling == "="); 222 223 const maybeDefault = equalIndex == -1 || !defaults 224 ? "" 225 : templateParam.tokens[equalIndex .. $] 226 .array 227 .translateTokens 228 ; 229 230 // e.g. "bool param", "T0" 231 return maybeType ~ spelling ~ maybeDefault; 232 } 233 234 auto templateParams = cursor.templateParams; 235 context.log("Children: ", cursor.children); 236 context.log("Template Params: ", templateParams); 237 auto translated = templateParams 238 .enumerate 239 .map!(a => translateTemplateParam(a[0], a[1])) 240 ; 241 242 // might need to be a variadic parameter 243 string maybeVariadic(in long index, in string name) { 244 return cursor.isVariadicTemplate && index == translated.length - 1 245 // If it's variadic, come up with a new name in case it's variadic 246 // values. D doesn't really care. 247 ? name ~ "..." 248 : name; 249 } 250 251 return () @trusted { 252 return translated 253 .enumerate 254 .map!(a => maybeVariadic(a[0], a[1])) 255 ; 256 }(); 257 } 258 259 // If the original template is variadic 260 private bool isFromVariadicTemplate(in from!"clang".Cursor cursor) @safe { 261 return isVariadicTemplate(cursor.specializedCursorTemplate); 262 } 263 264 private bool isVariadicTemplate(in from!"clang".Cursor cursor) @safe { 265 import clang: Cursor, Token; 266 import std.array: array; 267 import std.algorithm: canFind, countUntil; 268 269 const templateParamChildren = () @trusted { return cursor.templateParams.array; }(); 270 271 // There might be a "..." token inside the body of the struct/class, and we don't want to 272 // look at that. So instead we stop looking at tokens when the struct/class definition begins. 273 const closeAngleIndex = cursor.tokens.countUntil!(a => a.kind == 274 Token.Kind.Punctuation && 275 (a.spelling == ">" || a.spelling == ">>")); 276 const tokens = closeAngleIndex == -1 ? cursor.tokens : cursor.tokens[0 .. closeAngleIndex]; 277 278 return tokens.canFind(Token(Token.Kind.Punctuation, "...")); 279 } 280 281 282 // e.g. `template <typename T> using foo = bar;` 283 string[] translateTypeAliasTemplate(in from!"clang".Cursor cursor, 284 ref from!"dpp.runtime.context".Context context) 285 @safe 286 in(cursor.kind == from!"clang".Cursor.Kind.TypeAliasTemplateDecl) 287 do 288 { 289 import dpp.translation.type: translate; 290 import dpp.translation.exception: UntranslatableException; 291 import clang: Cursor, Type; 292 import std.conv: text; 293 import std.algorithm: countUntil; 294 import std.typecons: No; 295 import std.array: join, replace; 296 297 // see contract.templates.using 298 const typeAliasIndex = cursor.children.countUntil!(c => c.kind == Cursor.Kind.TypeAliasDecl); 299 assert(typeAliasIndex != -1, text(cursor.children)); 300 const typeAlias = cursor.children[typeAliasIndex]; 301 302 const underlying = () { 303 if(typeAlias.underlyingType.kind == Type.Kind.Unexposed) { 304 305 const templateRefIndex = typeAlias 306 .children 307 .countUntil!(c => c.kind == Cursor.Kind.TemplateRef); 308 309 if(templateRefIndex < 0 || templateRefIndex >= typeAlias.children.length) 310 throw new UntranslatableException( 311 text("templateRefIndex (", templateRefIndex, ") out of bounds. Children:\n", typeAlias.children)); 312 313 const templateRef = typeAlias.children[templateRefIndex]; 314 return templateRef.spelling; 315 316 } else 317 return translate(typeAlias.underlyingType, context, No.translatingFunction); 318 }(); 319 320 // FIXME 321 // Not sure what to do here to be able to satisfy both: 322 // ---------- 323 // template<typename T> struct bar; 324 // template<typename T> using foo = bar; 325 // ---------- 326 // And: 327 // ---------- 328 // template<typename...> using void_t = void; 329 // ---------- 330 // The first one can be either `alias foo = bar` or `alias foo(T) = bar(T)` 331 // but the 2nd one has to be `alias void_t(T...) = void`. 332 // The first example has to have the same template arguments on both sides, 333 // the second needs it only on the left. 334 const templateParams = isVariadicTemplate(cursor) 335 ? "(" ~ translateTemplateParams(cursor, context).join(", ") ~ ")" 336 : ""; 337 338 return [text("alias ", cursor.spelling, templateParams ~ " = ", underlying, ";")]; 339 }