1 /**
2  * Copyright: Copyright (c) 2012 Jacob Carlborg. All rights reserved.
3  * Authors: Jacob Carlborg
4  * Version: Initial created: Jan 30, 2012
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
6  */
7 module dstep.translator.Type;
8 
9 import std.conv;
10 import std..string;
11 import std.range;
12 import std.typecons: Nullable;
13 
14 import clang.c.Index;
15 import clang.Cursor;
16 import clang.Type;
17 import clang.Token: Token;
18 
19 import dstep.translator.Context;
20 import dstep.translator.IncludeHandler;
21 import dstep.translator.Translator;
22 import dstep.translator.Output;
23 
24 SourceNode translateType (Context context, Cursor cursor, bool rewriteIdToObjcObject = true, bool applyConst = true)
25 {
26     return translateType(context, cursor, cursor.type, rewriteIdToObjcObject, applyConst);
27 }
28 
29 SourceNode translateType (Context context, Cursor cursor, Type type, bool rewriteIdToObjcObject = true, bool applyConst = true)
30 in
31 {
32     assert(type.isValid);
33 }
34 do
35 {
36     SourceNode result;
37 
38     with (CXTypeKind)
39     {
40         if (type.kind == blockPointer || type.isFunctionPointerType)
41             result = translateFunctionPointerType(context, cursor, type.pointee.func);
42 
43         else if (type.isFunctionType)
44             result = translateFunctionPointerType(context, cursor, type.canonical.func);
45 
46         else if (type.kind == objCObjectPointer && !type.isObjCBuiltinType)
47             result = translateObjCObjectPointerType(context, cursor, type);
48 
49         else if (type.isWideCharType)
50             result = makeSourceNode("wchar");
51 
52         else if (type.isObjCIdType)
53             result = makeSourceNode(rewriteIdToObjcObject ? "ObjcObject" : "id");
54 
55         else
56             switch (type.kind)
57             {
58                 case pointer:
59                     return translatePointer(context, cursor, type, rewriteIdToObjcObject, applyConst);
60 
61                 case typedef_:
62                     result = translateTypedef(context, type).makeSourceNode();
63                     break;
64 
65                 case record:
66                 case enum_:
67                     result = makeSourceNode(context.translateTagSpelling(type.declaration));
68                     handleInclude(context, type);
69                     break;
70 
71                 case objCInterface:
72                     if (type.spelling.empty)
73                         result = makeSourceNode(context.getAnonymousName(type.declaration));
74                     else
75                         result = makeSourceNode(type.spelling);
76 
77                     handleInclude(context, type);
78                     break;
79 
80                 case constantArray:
81                 case incompleteArray:
82                     result = translateArray(
83                         context,
84                         cursor,
85                         type,
86                         rewriteIdToObjcObject,
87                         type.array.numDimensions - 1);
88                     break;
89 
90                 case unexposed:
91                     result = translateUnexposed(
92                         context,
93                         type,
94                         rewriteIdToObjcObject);
95                     break;
96 
97                 case elaborated:
98                     result = translateElaborated(
99                         context,
100                         cursor,
101                         type,
102                         rewriteIdToObjcObject);
103                     break;
104 
105                 case complex:
106                     result = translateComplex(type).makeSourceNode();
107                     break;
108 
109                 default:
110                     result = translateType(
111                         context,
112                         type.kind,
113                         rewriteIdToObjcObject)
114                         .makeSourceNode();
115             }
116     }
117 
118     version (D1)
119     {
120         // ignore const
121     }
122     else
123     {
124         if (applyConst && type.isConst)
125             result = result.prefixWith("const ");
126     }
127 
128     return result;
129 }
130 
131 SourceNode translateElaborated (Context context, Cursor cursor, Type type, bool rewriteIdToObjcObject = true, bool applyConst = true)
132 {
133     auto named = type.named();
134 
135     if (named.kind == CXTypeKind.record || named.kind == CXTypeKind.enum_)
136     {
137         auto result = context.translateTagSpelling(named.declaration);
138         handleInclude(context, type);
139         return result.makeSourceNode();
140     }
141     else
142     {
143         return translateType(
144             context,
145             cursor,
146             type.named,
147             rewriteIdToObjcObject);
148     }
149 }
150 
151 string translateSelector (string str, bool fullName = false, bool translateIdentifier = true)
152 {
153     import std.array : replace;
154     import std..string : indexOf;
155 
156     if (fullName)
157         str = str.replace(":", "_");
158 
159     else
160     {
161         auto i = str.indexOf(":");
162 
163         if (i > -1)
164             str = str[0 .. i];
165     }
166 
167     return translateIdentifier ? .translateIdentifier(str) : str;
168 }
169 
170 package string reduceAlias(Type type)
171 {
172     import std.typecons;
173 
174     enum aliasMapping = [
175         tuple("byte", CXTypeKind.uChar): "ubyte",
176         tuple("BOOL", CXTypeKind.bool_): "bool",
177         tuple("BOOL", CXTypeKind.sChar): "bool",
178 
179         tuple("int8_t", CXTypeKind.sChar): "byte",
180         tuple("int16_t", CXTypeKind.short_): "short",
181         tuple("int32_t", CXTypeKind.int_): "int",
182         tuple("int64_t", CXTypeKind.longLong): "long",
183         tuple("uint8_t", CXTypeKind.uChar): "ubyte",
184         tuple("uint16_t", CXTypeKind.uShort): "ushort",
185         tuple("uint32_t", CXTypeKind.uInt): "uint",
186         tuple("uint64_t", CXTypeKind.uLongLong): "ulong",
187 
188         tuple("__s8", CXTypeKind.sChar): "byte",
189         tuple("__s16", CXTypeKind.short_): "short",
190         tuple("__s32", CXTypeKind.int_): "int",
191         tuple("__s64", CXTypeKind.longLong): "long",
192         tuple("__u8", CXTypeKind.uChar): "ubyte",
193         tuple("__u16", CXTypeKind.uShort): "ushort",
194         tuple("__u32", CXTypeKind.uInt): "uint",
195         tuple("__u64", CXTypeKind.uLongLong): "ulong",
196 
197         tuple("s8", CXTypeKind.sChar): "byte",
198         tuple("s16", CXTypeKind.short_): "short",
199         tuple("s32", CXTypeKind.int_): "int",
200         tuple("s64", CXTypeKind.longLong): "long",
201         tuple("u8", CXTypeKind.uChar): "ubyte",
202         tuple("u16", CXTypeKind.uShort): "ushort",
203         tuple("u32", CXTypeKind.uInt): "uint",
204         tuple("u64", CXTypeKind.uLongLong): "ulong"
205     ];
206 
207     auto canonical = type.canonical;
208     auto kind = canonical.kind;
209 
210     if (kind == CXTypeKind.long_ && canonical.sizeOf == 8)
211         kind = CXTypeKind.longLong;
212     else if (kind == CXTypeKind.uLong && canonical.sizeOf == 8)
213         kind = CXTypeKind.uLongLong;
214 
215     if (auto alias_ = tuple(type.spelling, kind) in aliasMapping)
216         return *alias_;
217     else
218         return null;
219 }
220 
221 package bool isAliasReducible(Type type)
222 {
223     return reduceAlias(type) != null;
224 }
225 
226 private:
227 
228 string translateWCharT(Context context, Type type)
229 {
230     if (context.options.portableWCharT)
231     {
232         context.includeHandler.addImport("core.stdc.stddef");
233         return "wchar_t";
234     }
235     else if (type.canonical.kind.isIntegral)
236     {
237         auto sizeOf = type.canonical.sizeOf;
238 
239         if (sizeOf == 4)
240             return "dchar";
241         else if (sizeOf == 2)
242             return "wchar";
243     }
244 
245     return "<unimplemented>";
246 }
247 
248 string translateTypedef(Context context, Type type)
249 {
250     if (context.options.reduceAliases)
251     {
252         if (auto transl = reduceAlias(type))
253             return transl;
254     }
255 
256     auto spelling = type.spelling;
257 
258     with (CXTypeKind)
259         switch (spelling)
260         {
261             case "size_t":
262             case "ptrdiff_t":
263             case "sizediff_t":
264                 return spelling;
265 
266             case "wchar_t":
267                 return translateWCharT(context, type);
268 
269             default: break;
270         }
271 
272 
273     handleInclude(context, type);
274 
275     if (isDKeyword(type.spelling))
276     {
277         return type.spelling != translateType(context, type.canonical.kind)
278             ? renameDKeyword(type.spelling)
279             : type.spelling;
280     }
281     else
282     {
283         return type.spelling;
284     }
285 }
286 
287 SourceNode translateUnexposed (Context context, Type type, bool rewriteIdToObjcObject)
288 in
289 {
290     assert(type.kind == CXTypeKind.unexposed);
291 }
292 do
293 {
294     auto declaration = type.declaration;
295 
296     if (declaration.isValid)
297         return translateType(context, declaration, rewriteIdToObjcObject);
298     else
299         return translateType(context, type.kind, rewriteIdToObjcObject)
300             .makeSourceNode();
301 }
302 
303 string translateComplex (Type type)
304 {
305     switch (type.element.kind)
306     {
307         case CXTypeKind.float_: return "cfloat";
308         case CXTypeKind.double_: return "cdouble";
309         case CXTypeKind.longDouble: return "creal";
310         default: return "<unimplemented>";
311     }
312 }
313 
314 SourceNode translateArrayElement(
315     Context context,
316     Cursor cursor,
317     ArrayType array,
318     bool rewriteIdToObjcObject)
319 {
320     import std.format : format;
321 
322     bool isConst = array.elementType.isConst;
323 
324     auto type = translateType(
325         context,
326         cursor,
327         array.elementType,
328         rewriteIdToObjcObject,
329         !isConst);
330 
331     if (isConst)
332         return type.wrapWith("const(", ")");
333     else
334         return type;
335 }
336 
337 SourceNode translateArray (
338     Context context,
339     Cursor cursor,
340     Type type,
341     bool rewriteIdToObjcObject,
342     size_t dimension = 0)
343 in
344 {
345     assert(type.kind == CXTypeKind.constantArray
346         || type.kind == CXTypeKind.incompleteArray);
347 }
348 do
349 {
350     import std.format : format;
351 
352     auto array = type.array;
353     SourceNode elementType;
354 
355     if (array.elementType.kind == CXTypeKind.constantArray)
356     {
357         elementType = translateArray(
358             context,
359             cursor,
360             array.elementType,
361             rewriteIdToObjcObject,
362             dimension == 0 ? 0 : dimension - 1);
363     }
364     else
365     {
366         elementType = translateArrayElement(
367             context,
368             cursor,
369             array,
370             rewriteIdToObjcObject);
371     }
372 
373     if (array.size >= 0)
374     {
375         auto children = cursor.filterChildren(
376             CXCursorKind.integerLiteral,
377             CXCursorKind.declRefExpr);
378 
379         auto maybeRef(T)(auto ref T value) {
380             return cursor.semanticParent.kind == CXCursorKind.functionDecl && dimension == 0
381                 ? elementType.wrapWith("ref ", format("[%s]", value))
382                 : elementType.suffixWith(format("[%s]", value));
383         }
384 
385         if (dimension < children.length)
386         {
387             if (children[dimension].kind == CXCursorKind.integerLiteral)
388             {
389                 auto token = tokenInsideSquareBrackets(cursor, dimension);
390 
391                 if (!token.isNull)
392                 {
393                     return maybeRef(token.get.spelling);
394                 }
395 
396                 auto expansions = context.macroIndex.queryExpansion(children[dimension]);
397 
398                 if (expansions.length == 1)
399                     return elementType.suffixWith(format("[%s]", expansions[0].spelling));
400             }
401             else if (children[dimension].kind == CXCursorKind.declRefExpr)
402             {
403                 return elementType.suffixWith(format("[%s]", children[dimension].spelling));
404             }
405         }
406 
407         return maybeRef(array.size);
408     }
409     else if (cursor.semanticParent.kind == CXCursorKind.functionDecl)
410     {
411         return elementType.suffixWith("*");
412     }
413     else
414     {
415         // FIXME: Find a way to translate references to static external arrays with unknown size.
416 
417         // extern static arrays (which are normally present in bindings)
418         // have same ABI as extern dynamic arrays, size is only checked
419         // against declaration in header. As it is not possible in D
420         // to define static array with ABI of dynamic one, only way is to
421         // abandon the size information
422         return elementType.suffixWith("[]");
423     }
424 }
425 
426 // find the token for a (possibly multidimensioned) array for a certain dimension,
427 // e.g. int foo[1][2][3] will find the "3" for dimension 0 due to the differences
428 // in array declarations between D and C
429 private Nullable!Token tokenInsideSquareBrackets(Cursor cursor, in size_t dimension)
430 {
431     import std.algorithm: find;
432     import std.range: retro;
433     import clang.Token: TokenKind;
434 
435     auto fromNextBracket(R)(R tokens)
436     {
437         return tokens.find!(a => a.kind == TokenKind.punctuation && a.spelling == "]");
438     }
439 
440     auto tokens = cursor.tokens.retro.find!(_ => true);
441 
442     // dimension + 1 since dimension is 0-indexed and we need to find at least one
443     foreach(_; 0 .. dimension + 1)
444     {
445         tokens = fromNextBracket(tokens);
446         if (tokens.empty) return typeof(return).init;
447         tokens.popFront;
448     }
449 
450     return tokens.empty
451         ? typeof(return).init
452         : typeof(return)(tokens.front);
453 }
454 
455 SourceNode translatePointer (
456     Context context,
457     Cursor cursor,
458     Type type,
459     bool rewriteIdToObjcObject,
460     bool applyConst)
461 in
462 {
463     assert(type.kind == CXTypeKind.pointer);
464 }
465 do
466 {
467     static bool valueTypeIsConst (Type type)
468     {
469         auto pointee = type.pointee;
470 
471         while (pointee.kind == CXTypeKind.pointer)
472             pointee = pointee.pointee;
473 
474         return pointee.isConst;
475     }
476 
477     auto result = translateType(context, cursor, type.pointee, rewriteIdToObjcObject, false);
478 
479     version (D1)
480     {
481         result = result ~ '*';
482     }
483     else
484     {
485         if (applyConst && valueTypeIsConst(type))
486         {
487             if (type.isConst)
488                 result = result.wrapWith("const ", "*");
489             else
490                 result = result.wrapWith("const(", ")*");
491         }
492         else
493             result = result.suffixWith("*");
494     }
495 
496     return result;
497 }
498 
499 Parameter translateParameter (Context context, Cursor parameter)
500 {
501     Parameter result;
502 
503     result.type = translateType(context, parameter);
504     result.name = parameter.spelling;
505     result.isConst = false;
506 
507     return result;
508 }
509 
510 Parameter[] translateParameters (Context context, Cursor cursor, FuncType func)
511 {
512     import std.array : Appender;
513 
514     auto result = Appender!(Parameter[])();
515     auto arguments = func.arguments;
516 
517     foreach (child; cursor.all)
518     {
519         if (child.kind == CXCursorKind.parmDecl)
520             result.put(translateParameter(context, child));
521     }
522 
523     return result.data;
524 }
525 
526 SourceNode translateFunctionPointerType (Context context, Cursor cursor, FuncType func)
527 {
528     auto params = translateParameters(context, cursor, func);
529     auto result = translateType(context, cursor, func.resultType);
530     auto spacer = context.options.spaceAfterFunctionName ? " " : "";
531     auto multiline = cursor.extent.isMultiline &&
532         !context.options.singleLineFunctionSignatures;
533 
534     return translateFunction(
535         result,
536         "function",
537         params,
538         func.isVariadic,
539         "",
540         spacer,
541         multiline);
542 }
543 
544 SourceNode translateObjCObjectPointerType (Context context, Cursor cursor, Type type)
545 in
546 {
547     assert(type.kind == CXTypeKind.objCObjectPointer && !type.isObjCBuiltinType);
548 }
549 do
550 {
551     auto pointee = type.pointee;
552 
553     if (pointee.spelling == "Protocol")
554         return "Protocol*".makeSourceNode();
555 
556     else
557         return translateType(context, cursor, pointee);
558 }
559 
560 string translateType (Context context, CXTypeKind kind, bool rewriteIdToObjcObject = true)
561 {
562     import std.conv;
563 
564     with (CXTypeKind)
565         switch (kind)
566         {
567             case invalid: return "<unimplemented>";
568             case unexposed: return "<unimplemented>";
569             case void_: return "void";
570             case bool_: return "bool";
571             case charU: return "<unimplemented>";
572             case uChar: return "ubyte";
573             case char16: return "wchar";
574             case char32: return "dchar";
575             case uShort: return "ushort";
576             case uInt: return "uint";
577 
578             case uLong:
579                 context.includeHandler.addCompatible();
580                 return "c_ulong";
581 
582             case uLongLong: return "ulong";
583             case uInt128: return "<unimplemented>";
584             case charS: return "char";
585             case sChar: return "byte";
586             case wChar: return "wchar";
587             case short_: return "short";
588             case int_: return "int";
589 
590             case long_:
591                 context.includeHandler.addCompatible();
592                 return "c_long";
593 
594             case longLong: return "long";
595             case int128: return "<unimplemented>";
596             case float_: return "float";
597             case double_: return "double";
598             case longDouble: return "real";
599             case nullPtr: return "null";
600             case overload: return "<unimplemented>";
601             case dependent: return "<unimplemented>";
602             case objCId: return rewriteIdToObjcObject ? "ObjcObject" : "id";
603             case objCClass: return "Class";
604             case objCSel: return "SEL";
605 
606             case pointer:
607             case blockPointer:
608             case lValueReference:
609             case rValueReference:
610             case record:
611             case enum_:
612             case typedef_:
613             case functionNoProto:
614             case functionProto:
615             case vector:
616             case incompleteArray:
617             case variableArray:
618             case dependentSizedArray:
619             case memberPointer:
620             case elaborated:
621                 return "<unimplemented>";
622 
623             default: assert(0, "Unhandled type kind " ~ to!string(kind));
624         }
625 }