1 module bamboo.codegen.field_declarations.atomic;
2 
3 import bamboo.codegen.field_declarations;
4 
5 string generateAtomic(AtomicField field, bool stub)
6 {
7     string generated;
8     string fieldType;
9 
10     bool isComplex = field.parameters.length > 1;
11     bool isProperty = field.name.startsWith("set");
12 
13     string name = (() @trusted{
14         if (!isProperty)
15         {
16             return field.name;
17         }
18 
19         if (field.name[3 .. $].all!(x => isUpper(x)))
20         {
21             return field.name[3 .. $].toLower;
22         }
23 
24         return cast(char)(field.name[3].toLower) ~ field.name[4 .. $];
25     })();
26 
27     int counter = field.id;
28 
29     foreach (parameter; field.parameters)
30     {
31         counter++;
32         parameter.symbol = generateName(parameter.symbol, parameter.parameterTypeName, counter);
33     }
34 
35     string generateUnderlyingAtomicField()
36     {
37         string format;
38 
39         if (isComplex)
40         {
41             format ~= "private Tuple!(";
42             foreach (parameter; field.parameters)
43             {
44                 string[] parts = generateDefinition(parameter).split(' ');
45                 string type = parts[0];
46                 string param = parts[1];
47 
48                 format ~= type ~ "," ~ "`" ~ param ~ "`,";
49             }
50             format ~= ") _" ~ name ~ "; ";
51         }
52         else if (field.parameters.length == 1)
53         {
54             auto parameter = field.parameters[0];
55             format ~= "private " ~ generateDefinition(parameter).split(' ')[0] ~ " _" ~ name ~ ";";
56         }
57         else
58         {
59             assert(0, name ~ " is a setter with no value!");
60         }
61 
62         return format;
63     }
64 
65     string generateAtomicDeclaration(out string fieldType)
66     {
67         string format;
68 
69         format ~= "@FieldId(" ~ field.id.to!string ~ ") ";
70 
71         if (isProperty)
72         {
73             if (isComplex && stub)
74             {
75                 fieldType = "typeof(_" ~ name ~ ")";
76             }
77             else if (!isComplex)
78             {
79                 fieldType = generateDefinition(field.parameters[0]).split(' ')[0];
80             }
81         }
82 
83         foreach (keyword; field.keywords)
84         {
85             format ~= "@" ~ keyword ~ " ";
86         }
87 
88         if (!stub)
89         {
90             format ~= " abstract ";
91         }
92         format ~= " void ";
93 
94         format ~= name;
95 
96         format ~= "(";
97         format ~= generateParameterListFor(field);
98         format ~= ") ";
99 
100         if (!isComplex && isProperty)
101         {
102             format ~= "@property";
103         }
104 
105         return format;
106     }
107 
108     string generateComplexSetter()
109     {
110         return q{
111             void %1$s(T)(T value) if(__traits(compiles, %1$s(value.expand)))
112             {
113                 %1$s(value.expand);
114             }
115         }.format(name);
116     }
117 
118     string generateContracts()
119     {
120         string contracts;
121         string format;
122 
123         foreach (parameter; field.parameters)
124         {
125             contracts ~= generateContractFor(parameter);
126         }
127 
128         if (contracts.length > 0)
129         {
130             format ~= " in {";
131             format ~= contracts;
132             format ~= "}";
133             if (stub)
134             {
135                 format ~= "body";
136             }
137         }
138 
139         return format;
140     }
141 
142     string generateBody()
143     {
144         string format;
145 
146         if (stub)
147         {
148             format ~= "{";
149             if (isProperty)
150             {
151                 if (isComplex)
152                 {
153                     foreach (parameter; field.parameters)
154                     {
155                         format ~= "_" ~ name ~ "." ~ parameter.symbol;
156                         format ~= "=" ~ parameter.symbol ~ ";";
157                     }
158                 }
159                 else if (field.parameters.length == 1)
160                 {
161                     auto parameter = field.parameters[0];
162                     format ~= "_" ~ name;
163                     format ~= "=" ~ parameter.symbol ~ ";";
164                 }
165             }
166             else
167             {
168                 format ~= `assert(0, "Override body not defined for ` ~ field.name ~ `!");`;
169             }
170 
171             format ~= "}";
172         }
173         else
174         {
175             format ~= ";";
176         }
177 
178         return format;
179     }
180 
181     string generateGetter()
182     {
183         string format;
184 
185         format ~= "@FieldId(" ~ field.id.to!string ~ ") ";
186         if (!isComplex)
187         {
188             format ~= "@property ";
189         }
190 
191         if (!stub)
192         {
193             format ~= "abstract " ~ fieldType ~ " " ~ name ~ "();";
194         }
195         else
196         {
197             format ~= fieldType ~ " " ~ name ~ "() { ";
198             format ~= "return _" ~ name ~ ";";
199             format ~= "}";
200         }
201 
202         return format;
203     }
204 
205     autogenParameterNames(field);
206 
207     if (stub && isProperty)
208     {
209         generated ~= generateUnderlyingAtomicField();
210     }
211 
212     generated ~= generateAtomicDeclaration(fieldType);
213     generated ~= generateContracts();
214     generated ~= generateBody();
215 
216     if (isProperty)
217     {
218         if (isComplex)
219         {
220             generated ~= generateComplexSetter();
221         }
222         generated ~= generateGetter();
223         generated ~= text("alias ", field.name, " = ", name, ";"); // setField
224         generated ~= text("alias get", field.name[3 .. $], " = ", name, ";"); // getField
225     }
226 
227     return generated;
228 }
229 
230 private:
231 
232 /**
233  * This function generates parameter names from a field's name.
234  * 
235  * Notes: 
236  *      Some fields follow the pattern of, e.g., `setXYZ(float32, float32, float32)`.
237  *      Using the name generator, ugly parameter names will be generated when 
238  *      enough semantic information is already available to properly generate
239  *      parameter names.
240  *
241  *      This method would take `setXYZ(float32, float32, float32)` and transform
242  *      it to `setXYZ(float32 x, float32, y, float32 z)`, for example.
243  */
244 void autogenParameterNames(AtomicField field)
245 {
246     if (!field.symbol.startsWith("set"))
247     {
248         return;
249     }
250 
251     if (field.parameters.length == 1)
252     {
253         string name = field.symbol[3 .. $];
254         field.parameters[0].symbol = cast(char) name[0].toLower ~ name[1 .. $];
255     }
256 
257     foreach (parameter; field.parameters)
258     {
259         if (parameter.symbol.length > 0)
260         {
261             return;
262         }
263     }
264 
265     string name = field.symbol[3 .. $];
266     if (name.filter!(a => isUpper(cast(dchar) a))().array.length != field.parameters.length)
267     {
268         return;
269     }
270 
271     int start;
272     int index = 1;
273     int param;
274 
275     string[] names;
276 
277     void appendName()
278     {
279         if (index - start == 1)
280         {
281             names ~= [cast(char)(name[start].toLower)];
282         }
283         else
284         {
285             names ~= cast(char)(name[start].toLower) ~ name[start + 1 .. index];
286         }
287 
288     }
289 
290     foreach (value; name[1 .. $])
291     {
292         if (isUpper(cast(dchar) value))
293         {
294             appendName();
295             start = index;
296         }
297         index++;
298     }
299     if (names.length != field.parameters.length)
300     {
301         appendName();
302     }
303 
304     foreach (i, para; field.parameters)
305     {
306         para.symbol = names[i];
307     }
308 }