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 }