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 // returns a range of string
22 package string[] translateSpecialisedTemplateParams(
23     in from!"clang".Cursor cursor,
24     ref from!"dpp.runtime.context".Context context)
25     @safe
26 {
27     import dpp.translation.type: translate, templateArgumentKind, TemplateArgumentKind,
28         translateTemplateParamSpecialisation;
29     import clang: Type;
30     import std.algorithm: map;
31     import std.range: iota;
32     import std.array: array, join;
33     import std.typecons: No;
34 
35     assert(cursor.type.numTemplateArguments != -1);
36 
37     if(isFromVariadicTemplate(cursor))
38         return translateSpecialisedTemplateParamsVariadic(cursor, context);
39 
40     // get the original list of template parameters and translate them
41     // e.g. template<bool, bool, typename> -> (bool V0, bool V1, T)
42     const translatedTemplateParams = () @trusted {
43         return translateTemplateParams(cursor, context, No.defaults)
44         .array;
45     }();
46 
47     // e.g. for template<> struct foo<false, true, int32_t>
48     // 0 -> `bool V0: false`, 1 -> `bool V1: true`, 2 -> `T0: int`
49     string element(in Type templateArgType, in int index) {
50 
51         import dpp.translation.exception: UntranslatableException;
52 
53         if(templateArgType.isConstQualified)
54             throw new UntranslatableException("Cannot translate const template type parameter specialisation");
55 
56         if(templateArgType.isVolatileQualified)
57             throw new UntranslatableException("Cannot translate volatile template type parameter specialisation");
58 
59         string ret = translatedTemplateParams[index];  // e.g. `T`,  `bool V0`
60         const maybeSpecialisation = translateTemplateParamSpecialisation(cursor.type, templateArgType, index, context);
61         const templateArgKind = templateArgumentKind(templateArgType);
62 
63         with(TemplateArgumentKind) {
64             const isSpecialised =
65                 templateArgKind == SpecialisedType ||
66                 (templateArgKind == Value && isValueOfType(cursor, context, index, maybeSpecialisation));
67 
68             if(isSpecialised) ret ~= ": " ~ maybeSpecialisation;
69         }
70 
71         return ret;
72     }
73 
74     return () @trusted {
75         return
76             cursor.type.numTemplateArguments
77             .iota
78             .map!(i => element(cursor.type.typeTemplateArgument(i), i))
79             .array
80             ;
81     }();
82 }
83 
84 // FIXME: refactor
85 private auto translateSpecialisedTemplateParamsVariadic(in from!"clang".Cursor cursor,
86                                                         ref from!"dpp.runtime.context".Context context)
87     @safe
88 {
89     import dpp.translation.type: translate;
90 
91     assert(isFromVariadicTemplate(cursor));
92     assert(cursor.type.numTemplateArguments != -1);
93 
94     string[] ret;
95 
96     foreach(i; 0 .. cursor.type.numTemplateArguments) {
97         ret ~= translate(cursor.type.typeTemplateArgument(i), context);
98     }
99 
100     return ret;
101 }
102 
103 // In the case cursor is a partial or full template specialisation,
104 // check to see if `maybeSpecialisation` can be converted to the
105 // indexth template parater of the cursor's original template.
106 // If it can, then it's a value of that type.
107 private bool isValueOfType(
108     in from!"clang".Cursor cursor,
109     ref from!"dpp.runtime.context".Context context,
110     in int index,
111     in string maybeSpecialisation,
112     )
113     @safe
114 {
115     import dpp.translation.type: translate;
116     import std.array: array;
117     import std.exception: collectException;
118     import std.conv: to;
119     import core.stdc.config: c_long, c_ulong;
120 
121     // the original template cursor (no specialisations)
122     const templateCursor = cursor.specializedCursorTemplate;
123     // the type of the indexth template parameter
124     const templateParamCursor = () @trusted { return templateCursor.templateParams.array[index]; }();
125     // the D translation of that type
126     const dtype = translate(templateParamCursor.type, context);
127 
128     Exception conversionException;
129 
130     void tryConvert(T)() {
131         conversionException = collectException(maybeSpecialisation.to!T);
132     }
133 
134     switch(dtype) {
135         default: throw new Exception("isValueOfType cannot handle type `" ~ dtype ~ "`");
136         case "bool":    tryConvert!bool;    break;
137         case "char":    tryConvert!char;    break;
138         case "wchar":   tryConvert!wchar;   break;
139         case "dchar":   tryConvert!dchar;   break;
140         case "short":   tryConvert!short;   break;
141         case "ushort":  tryConvert!ushort;  break;
142         case "int":     tryConvert!int;     break;
143         case "uint":    tryConvert!uint;    break;
144         case "long":    tryConvert!long;    break;
145         case "ulong":   tryConvert!ulong;   break;
146         case "c_ulong": tryConvert!c_ulong; break;
147         case "c_long":  tryConvert!c_long;  break;
148     }
149 
150     return conversionException is null;
151 }
152 
153 
154 // Translates a C++ template parameter (value or type) to a D declaration
155 // e.g. `template<typename, bool, typename>` -> ["T0", "bool V0", "T1"]
156 // Returns a range of string
157 package auto translateTemplateParams(
158     in from!"clang".Cursor cursor,
159     ref from!"dpp.runtime.context".Context context,
160     from!"std.typecons".Flag!"defaults" defaults = from!"std.typecons".Yes.defaults,
161     ) @safe
162 {
163     import dpp.translation.type: translate, translateString;
164     import clang: Cursor;
165     import std.conv: text;
166     import std.algorithm: map, filter, countUntil;
167     import std.array: array;
168     import std.range: enumerate;
169 
170     int templateParamIndex;  // used to generate names when there are none
171 
172     string newTemplateParamName() {
173         // FIXME
174         // the naming convention is to match what libclang gives, but there's no
175         // guarantee that it'll always match.
176         return text("type_parameter_0_", templateParamIndex++);
177     }
178 
179     string translateTemplateParam(in Cursor cursor) {
180         import dpp.translation.type: translate;
181         import dpp.translation.tokens: translateTokens;
182         import clang: Token;
183 
184         // The template parameter might be a value (bool, int, etc.)
185         // or a type. If it's a value we get its type here.
186         const maybeType = cursor.kind == Cursor.Kind.TemplateTypeParameter
187             ? ""  // a type doesn't have a type
188             : translate(cursor.type, context) ~ " ";
189 
190         // D requires template parameters to have names
191         const spelling = cursor.spelling == "" ? newTemplateParamName : cursor.spelling;
192 
193         // There's no direct way to extract default template parameters from libclang
194         // so we search for something like `T = Foo` in the tokens
195         const equalIndex = cursor.tokens.countUntil!(t => t.kind == Token.Kind.Punctuation && t.spelling == "=");
196 
197         const maybeDefault = equalIndex == -1 || !defaults
198             ? ""
199             : cursor.tokens[equalIndex .. $]
200                 .array
201                 .translateTokens
202             ;
203 
204         // e.g. "bool param", "T0"
205         return maybeType ~ spelling ~ maybeDefault;
206     }
207 
208     auto templateParams = cursor.templateParams;
209     auto translated = templateParams.map!translateTemplateParam.array;
210 
211     // might need to be a variadic parameter
212     string maybeVariadic(in long index, in string name) {
213         return cursor.isVariadicTemplate && index == translated.length -1
214             // If it's variadic, come up with a new name in case it's variadic
215             // values. D doesn't really care.
216             ? newTemplateParamName ~ "..."
217             : name;
218     }
219 
220     return () @trusted {
221         return translated
222             .enumerate
223             .map!(a => maybeVariadic(a[0], a[1]))
224         ;
225     }();
226 }
227 
228 // If the original template is variadic
229 private bool isFromVariadicTemplate(in from!"clang".Cursor cursor) @safe {
230     return isVariadicTemplate(cursor.specializedCursorTemplate);
231 }
232 
233 private bool isVariadicTemplate(in from!"clang".Cursor cursor) @safe {
234     import clang: Cursor, Token;
235     import std.array: array;
236     import std.algorithm: canFind, countUntil;
237 
238     const templateParamChildren = () @trusted { return cursor.templateParams.array; }();
239 
240     // There might be a "..." token inside the body of the struct/class, and we don't want to
241     // look at that. So instead we stop looking at tokens when the struct/class definition begins.
242     const openCurlyBracketIndex = cursor.tokens.countUntil!(a => a.kind ==
243                                                             Token.Kind.Punctuation && a.spelling == "{");
244     const tokens = openCurlyBracketIndex == -1 ? cursor.tokens : cursor.tokens[0 .. openCurlyBracketIndex];
245 
246     return
247         templateParamChildren.length > 0 &&
248         (templateParamChildren[$-1].kind == Cursor.Kind.TemplateTypeParameter ||
249          templateParamChildren[$-1].kind == Cursor.Kind.NonTypeTemplateParameter) &&
250         tokens.canFind(Token(Token.Kind.Punctuation, "..."));
251 }