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