1 /**
2  * Copyright: Copyright (c) 2012 Jacob Carlborg. All rights reserved.
3  * Authors: Jacob Carlborg
4  * Version: Initial created: May 10, 2012
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
6  */
7 module dstep.translator.Enum;
8 
9 import clang.c.Index;
10 import clang.Cursor;
11 import clang.Visitor;
12 import clang.Util;
13 
14 import dstep.translator.MacroParser;
15 import dstep.translator.MacroDefinition;
16 import dstep.translator.ConvertCase;
17 import dstep.translator.Context;
18 import dstep.translator.Declaration;
19 import dstep.translator.Output;
20 import dstep.translator.Translator;
21 import dstep.translator.Type;
22 
23 void translateEnumConstantDecl(
24     Output output,
25     Context context,
26     Cursor cursor,
27     string spelling,
28     bool last)
29 {
30     import std.conv;
31     import std.format;
32     import std.stdio;
33 
34     context.defineSpellingTranslation(cursor, spelling);
35 
36     auto expression = parseEnumMember(cursor.tokens, context.typeNames());
37 
38     auto expressionContext = ExpressionContext.make(context);
39 
40     expressionContext.scope_ = cursor.lexicalParent;
41 
42     auto translated = expression.hasValue
43         ? expression.debraced.translate(expressionContext)
44         : cursor.enum_.value.to!string();
45 
46     output.singleLine(
47         cursor.extent,
48         "%s = %s%s",
49         spelling,
50         translated,
51         last ? "" : ",");
52 }
53 
54 void generateEnumAliases(Output output, Context context, Cursor cursor, string spelling)
55 {
56     string subscope = cursorScopeString(context, cursor) ~ ".";
57 
58     version (D1)
59         enum fmt = "alias %2$s %1$s;";
60     else
61         enum fmt = "alias %1$s = %2$s;";
62 
63     foreach (item; cursor.all)
64     {
65         switch (item.kind)
66         {
67             case CXCursorKind.enumConstantDecl:
68                 output.singleLine(
69                     fmt,
70                     item.spelling,
71                     subscope ~ item.spelling);
72                 break;
73 
74             default:
75                 break;
76         }
77     }
78 }
79 
80 auto renameEnumMembers(MemberRange)(string enumSpelling, MemberRange memberSpellings)
81 {
82     import std.array;
83     import std.algorithm;
84     import std.ascii;
85     import std.range;
86 
87     struct Component
88     {
89         string spelling;
90         size_t offset;
91     }
92 
93     Component[] separateWords(string spelling)
94     {
95         Component[] result;
96 
97         size_t begin = 0;
98 
99         if (spelling.canFind!(a => a.isLower))
100         {
101             for (size_t i = 1; i < spelling.length; ++i)
102             {
103                 if (spelling[i].isUpper ||
104                     (spelling[i - 1] == '_' && spelling[i].isLower))
105                 {
106                     if (!result.empty)
107                         result.back.offset = begin;
108 
109                     result ~= Component(spelling[begin..i].stripRight('_'), i);
110                     begin = i;
111                 }
112             }
113         }
114         else
115         {
116             for (size_t i = 1; i < spelling.length; ++i)
117             {
118                 if (spelling[i - 1] == '_')
119                 {
120                     if (!result.empty)
121                         result.back.offset = begin;
122 
123                     result ~= Component(spelling[begin .. i].stripRight('_'), i);
124                     begin = i;
125                 }
126             }
127         }
128 
129         if (begin != spelling.length)
130         {
131             result ~= Component(
132                 spelling[begin .. $].stripRight('_'),
133                 spelling.length);
134         }
135 
136         return result;
137     }
138 
139     size_t trimWhenEnumIsCamelAndMemberIsCapitals(
140         string enumSpelling,
141         string memberSpelling,
142         Component[] enumSeparated)
143     {
144         size_t numWords = 0;
145         size_t offset = 0;
146         string uppercasePrefix;
147 
148         while (offset < memberSpelling.length && numWords < enumSeparated.length)
149         {
150             if (!enumSeparated[numWords].spelling.canFind!isLower &&
151                 memberSpelling[offset .. $]
152                     .startsWith(enumSeparated[numWords].spelling))
153             {
154                 uppercasePrefix ~= enumSeparated[numWords].spelling;
155                 offset += enumSeparated[numWords].spelling.length;
156                 ++numWords;
157             }
158             else
159             {
160                 break;
161             }
162         }
163 
164         return commonPrefix(uppercasePrefix, enumSpelling).length;
165     }
166 
167     size_t refactorSingleton(string enumSpelling, string memberSpelling)
168     {
169         auto enumSeparated = separateWords(enumSpelling);
170         auto memberSeparated = separateWords(memberSpelling);
171 
172         alias predicate = (a, b) =>
173             a.spelling.startsWith!((a, b) => a.toLower == b.toLower)(b.spelling);
174 
175         auto prefix = commonPrefix!(predicate)(enumSeparated, memberSeparated);
176 
177         auto tentative = !prefix.empty
178             ? memberSeparated[prefix.length - 1].offset
179             : 0;
180 
181         if (!memberSeparated.canFind!((Component a) => a.spelling.canFind!isLower))
182         {
183             auto special = trimWhenEnumIsCamelAndMemberIsCapitals(
184                 enumSpelling,
185                 memberSpelling,
186                 enumSeparated);
187 
188             return max(tentative, special);
189         }
190         else
191         {
192             auto candidate = findSplitBefore(memberSpelling.retro, "_");
193             return !candidate[1].empty
194                 ? max(candidate[1].walkLength, tentative)
195                 : tentative;
196         }
197     }
198 
199     struct Range
200     {
201         private MemberRange memberSpellings;
202         private size_t prefixSize;
203 
204         this(string enumSpelling, MemberRange memberSpellings)
205         {
206             this.memberSpellings = memberSpellings;
207 
208             auto candidate = findSplitAfter(enumSpelling, "_");
209             auto minorPrefix = fold!commonPrefix(memberSpellings);
210 
211             if (memberSpellings.walkLength(2) == 1)
212             {
213                 prefixSize = refactorSingleton(
214                     enumSpelling,
215                     memberSpellings.front);
216             }
217             else if (minorPrefix.endsWith("_"))
218             {
219                 prefixSize = minorPrefix.length;
220             }
221             else
222             {
223                 auto underscoreSplit = findSplitBefore(minorPrefix.retro, "_");
224 
225                 prefixSize = !underscoreSplit[1].empty
226                     && underscoreSplit[1].canFind!(x => x != '_')
227                     ? underscoreSplit[1].walkLength
228                     : minorPrefix.length;
229             }
230         }
231 
232         bool empty()
233         {
234             return memberSpellings.empty;
235         }
236 
237         string front()
238         {
239             return memberSpellings.front[prefixSize .. $];
240         }
241 
242         void popFront()
243         {
244             memberSpellings.popFront();
245         }
246     }
247 
248     return Range(enumSpelling, memberSpellings);
249 }
250 
251 void translateEnumDef(Output output, Context context, Cursor cursor)
252 {
253     import std.algorithm;
254     import std.format : format;
255     import std.range;
256 
257     auto variables = cursor.variablesInParentScope();
258     auto anonymous = context.shouldBeAnonymous(cursor);
259     auto spelling = "enum";
260 
261     if (!anonymous || variables || !cursor.isGlobal)
262         spelling = "enum " ~ translateIdentifier(context.translateTagSpelling(cursor));
263 
264     output.subscopeStrong(cursor.extent, "%s", spelling) in
265     {
266         auto members = cursor.children
267             .filter!(cursor => cursor.kind == CXCursorKind.enumConstantDecl);
268 
269         size_t length = members.walkLength();
270 
271         if (context.options.renameEnumMembers)
272         {
273             auto renamed = renameEnumMembers(
274                 context.translateTagSpelling(cursor),
275                 map!(x => x.spelling)(members));
276 
277             foreach (member, spelling; zip(members, enumerate(renamed)))
278             {
279                 translateEnumConstantDecl(
280                     output,
281                     context,
282                     member,
283                     spelling.value.toCamelCase.translateIdentifier,
284                     length == spelling.index + 1);
285             }
286         }
287         else
288         {
289             foreach (index, member; enumerate(members))
290             {
291                 translateEnumConstantDecl(
292                     output,
293                     context,
294                     member,
295                     member.spelling,
296                     length == index + 1);
297             }
298         }
299     };
300 
301     if ((anonymous && variables) || !cursor.isGlobal || context.options.aliasEnumMembers)
302         generateEnumAliases(context.globalScope, context, cursor, spelling);
303 }
304 
305 void translateEnum(Output output, Context context, Cursor cursor)
306 {
307     auto canonical = cursor.canonical;
308 
309     if (!context.alreadyDefined(cursor.canonical))
310     {
311         translateEnumDef(output, context, canonical.definition);
312         context.markAsDefined(cursor);
313     }
314 }