1 /**
2    Cursor translations
3  */
4 module dpp.translation.translation;
5 
6 import dpp.from;
7 
8 
9 alias Translator = string[] function(
10     in from!"clang".Cursor cursor,
11     ref from!"dpp.runtime.context".Context context,
12 ) @safe;
13 
14 string translateTopLevelCursor(in from!"clang".Cursor cursor,
15                                ref from!"dpp.runtime.context".Context context,
16                                in string file = __FILE__,
17                                in size_t line = __LINE__)
18     @safe
19 {
20     import std.array: join;
21     import std.algorithm: map;
22 
23     return skipTopLevel(cursor, context)
24         ? ""
25         : translate(cursor, context, file, line).map!(a => "    " ~ a).join("\n");
26 }
27 
28 private bool skipTopLevel(in from!"clang".Cursor cursor,
29                           in from!"dpp.runtime.context".Context context)
30     @safe
31 {
32     import dpp.translation.aggregate: isAggregateC;
33     import clang: Cursor;
34     import std.algorithm: startsWith, canFind;
35 
36     if(context.isFromIgnoredPath(cursor))
37         return true;
38 
39     // We want to ignore anonymous structs and unions but not enums. See #54
40     if(cursor.spelling == "" && cursor.kind == Cursor.Kind.EnumDecl)
41         return false;
42 
43     // don't bother translating top-level anonymous aggregates
44     if(isAggregateC(cursor) && cursor.spelling == "")
45         return true;
46 
47     if(context.options.ignoreMacros && cursor.kind == Cursor.Kind.MacroDefinition)
48         return true;
49 
50     static immutable forbiddenSpellings =
51         [
52             "ulong", "ushort", "uint",
53             "va_list", "__gnuc_va_list",
54             "_IO_2_1_stdin_", "_IO_2_1_stdout_", "_IO_2_1_stderr_",
55         ];
56 
57     return forbiddenSpellings.canFind(cursor.spelling) ||
58         cursor.isPredefined ||
59         cursor.kind == Cursor.Kind.MacroExpansion
60         ;
61 }
62 
63 
64 string[] translate(in from!"clang".Cursor cursor,
65                    ref from!"dpp.runtime.context".Context context,
66                    in string file = __FILE__,
67                    in size_t line = __LINE__)
68     @safe
69 {
70     import dpp.runtime.context: Language;
71     import dpp.translation.exception: UntranslatableException;
72     import std.conv: text;
73     import std.algorithm: canFind, any;
74     import std.array: join;
75 
76     debugCursor(cursor, context);
77 
78     if(context.language == Language.Cpp && ignoredCppCursorSpellings.canFind(cursor.spelling)) {
79         return [];
80     }
81 
82     if(context.options.ignoredCursors.canFind(cursor.spelling)) {
83         return [];
84     }
85 
86     if(cursor.kind !in translators) {
87         if(context.options.hardFail)
88             throw new Exception(text("Cannot translate unknown cursor kind ", cursor.kind),
89                                 file,
90                                 line);
91         else
92             return [];
93     }
94 
95     const indentation = context.indentation;
96     scope(exit) context.setIndentation(indentation);
97     context.indent;
98 
99     try {
100         auto lines = translators[cursor.kind](cursor, context);
101 
102         if(lines.any!untranslatable)
103             throw new UntranslatableException(
104                 text("Not valid D:\n",
105                      "------------\n",
106                      lines.join("\n"),
107                     "\n------------\n",));
108 
109         return lines;
110     } catch(UntranslatableException e) {
111 
112         debug {
113             import std.stdio: stderr;
114             () @trusted {
115                 if(context.options.detailedUntranslatable)
116                     stderr.writeln("\nUntranslatable cursor ", cursor,
117                                    "\nmsg: ", e.msg,
118                                    "\nsourceRange: ", cursor.sourceRange,
119                                    "\nchildren: ", cursor.children,
120                                    "\n");
121                 else
122                     stderr.writeln("Untranslatable cursor ", cursor);
123             }();
124         }
125 
126         if(context.options.hardFail)
127             throw e;
128         else
129             return [];
130 
131     } catch(Exception e) {
132 
133         debug {
134             import std.stdio: stderr;
135             () @trusted {
136                 stderr.writeln("\nCould not translate cursor ", cursor,
137                                "\nmsg: ", e.msg,
138                                "\nsourceRange: ", cursor.sourceRange,
139                                "\nchildren: ", cursor.children, "\n");
140             }();
141         }
142 
143         throw e;
144     }
145 }
146 
147 void debugCursor(in from!"clang".Cursor cursor,
148                  in from!"dpp.runtime.context".Context context)
149     @safe
150 {
151     import clang: Cursor;
152     import std.algorithm: startsWith, canFind;
153 
154     version(unittest) {}
155     else if(!context.debugOutput) return;
156 
157     const isMacro = cursor.kind == Cursor.Kind.MacroDefinition;
158     const isOkMacro =
159         !cursor.spelling.startsWith("__") &&
160         !cursor.spelling.startsWith("_GLIBCXX") &&
161         !["_LP64", "unix", "linux"].canFind(cursor.spelling);
162     const canonical = cursor.isCanonical ? " CAN" : "";
163     const definition = cursor.isDefinition ? " DEF" : "";
164 
165     if(!isMacro || isOkMacro) {
166         context.log(cursor, canonical, definition, " @ ", cursor.sourceRange);
167     }
168 }
169 
170 
171 Translator[from!"clang".Cursor.Kind] translators() @safe {
172     static Translator[from!"clang".Cursor.Kind] ret;
173     if(ret == ret.init) ret = translatorsImpl;
174     return ret;
175 }
176 
177 
178 private Translator[from!"clang".Cursor.Kind] translatorsImpl() @safe pure {
179     import dpp.translation;
180     import clang: Cursor;
181 
182     static string[] ignore(
183         in Cursor cursor,
184         ref from!"dpp.runtime.context".Context context)
185     {
186         return [];
187     }
188 
189     static string[] translateUnexposed(
190         in Cursor cursor,
191         ref from!"dpp.runtime.context".Context context)
192     {
193         import clang: Type;
194         import std.conv: text;
195 
196         switch(cursor.type.kind) with(Type.Kind) {
197             default:
198                 throw new Exception(text("Unknown unexposed declaration type ", cursor.type));
199             case Invalid:
200                 return [];
201         }
202         assert(0);
203     }
204 
205     static string[] translateAccess(
206         in Cursor cursor,
207         ref from!"dpp.runtime.context".Context context)
208     {
209         import clang: AccessSpecifier;
210 
211         context.accessSpecifier = cursor.accessSpecifier;
212 
213         final switch(cursor.accessSpecifier) with(AccessSpecifier) {
214             case InvalidAccessSpecifier: assert(0);
215             case Public: return ["    public:"];
216             case Protected: return ["    protected:"];
217             case Private: return ["    private:"];
218         }
219 
220         assert(0);
221     }
222 
223     with(Cursor.Kind) {
224         return [
225             ClassDecl:                          &translateClass,
226             StructDecl:                         &translateStruct,
227             UnionDecl:                          &translateUnion,
228             EnumDecl:                           &translateEnum,
229             FunctionDecl:                       &translateFunction,
230             FieldDecl:                          &translateField,
231             TypedefDecl:                        &translateTypedef,
232             MacroDefinition:                    &translateMacro,
233             InclusionDirective:                 &translateInclude,
234             EnumConstantDecl:                   &translateEnumConstant,
235             VarDecl:                            &translateVariable,
236             UnexposedDecl:                      &translateUnexposed,
237             CXXAccessSpecifier:                 &translateAccess,
238             CXXMethod:                          &translateFunction,
239             Constructor:                        &translateFunction,
240             Destructor:                         &translateFunction,
241             TypeAliasDecl:                      &translateTypedef,
242             ClassTemplate:                      &translateClass,
243             TemplateTypeParameter:              &ignore,
244             NonTypeTemplateParameter:           &ignore,
245             ConversionFunction:                 &translateFunction,
246             Namespace:                          &translateNamespace,
247             VisibilityAttr:                     &ignore, // ???
248             // FirstAttr appears when there are compiler-specific attributes on a type
249             FirstAttr:                          &ignore,
250             ClassTemplatePartialSpecialization: &translateClass,
251             TypeAliasTemplateDecl:              &translateTypeAliasTemplate,
252             FunctionTemplate:                   &translateFunction,
253             // For ParmDecl, see it.cpp.opaque.std::function
254             ParmDecl:                           &ignore,
255             CXXBaseSpecifier:                   &ignore,
256             UsingDeclaration:                   &translateInheritingConstructor,
257         ];
258     }
259 }
260 
261 string[] translateInclude(in from!"clang".Cursor cursor,
262                           ref from!"dpp.runtime.context".Context context)
263     @safe
264 {
265     if(auto ptr = cursor.spelling in context.options.prebuiltHeaders)
266         return ["import " ~ *ptr ~ ";"];
267     return null;
268 }
269 
270 // if this translated line can't be valid D code
271 bool untranslatable(in string line) @safe pure {
272     import std.algorithm: canFind;
273 
274     // now that comments are preserved...
275     if (line.canFind("/*") || line.canFind("//"))
276         return false;
277 
278     return
279         line.canFind(`&)`)
280         || line.canFind("&,")
281         || line.canFind("&...")
282         || line.canFind(" (*)")
283         || line.canFind("variant!")
284         || line.canFind("value _ ")
285         || line.canFind("enable_if_c")
286         || line.canFind(`(this_)_M_t._M_equal_range_tr(`)
287         || line.canFind(`this-`)
288         || line.canFind("_BoundArgs...")
289         || line.canFind("sizeof...")
290         || line.canFind("template<")  // FIXME: mir_slice
291         ;
292 }
293 
294 
295 // blacklist of cursors in the C++ standard library that dpp can't handle
296 private string[] ignoredCppCursorSpellings() @safe pure nothrow {
297     return
298         [
299             "is_function",  // dmd bug
300             "is_const",
301             "is_volatile",
302             "allocator_traits",  // FIXME
303             "pair",  // FIXME
304             "underlying_type",
305             "underlying_type_t",
306             "result_of",
307             "result_of_t",
308             "pointer_traits", // FIXME
309             "iterator_traits",  // FIXME
310             "piecewise_construct", // FIXME
311             "is_rvalue_reference",
312             "remove_reference",
313             "remove_reference_t",
314             "remove_extent",  // FIXME
315             "remove_extent_t",  // FIXME
316             "remove_all_extents",  // FIXME
317             "remove_all_extents_t",  // FIXME
318 
319             // derives from std::iterator, which is untranslatable due to it taking a
320             // reference template parameter
321             "_Bit_iterator_base",
322             "_Bit_iterator",
323             "_Bit_const_iterator",
324             // needs _Bit_iterator and co
325             "_Bvector_base",
326         ];
327 }