1 module bamboo.codegen;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 import std.math;
7 import std.string;
8 import std.uni;
9 
10 import bamboo.hashgen;
11 import bamboo.types;
12 import bamboo.util;
13 
14 /// Aliases for dclass int type names.
15 enum intTypes = `
16 alias int8  = byte;
17 alias int16 = short;
18 alias int32 = int;
19 alias int64 = long;
20 
21 alias uint8  = ubyte;
22 alias uint16 = ushort;
23 alias uint32 = uint;
24 alias uint64 = long;
25 `;
26 
27 /// Aliases for dclass float type names.
28 enum floatTypes = `
29 alias float32 = float;
30 alias float64 = double;
31 `;
32 
33 // NB: string and char are properly aliased in D.
34 
35 /// Alias for the dclass `blob` type.
36 enum blobTypes = `
37 alias blob = immutable ubyte[];
38 `;
39 
40 /// Handy mixin for all dclass primitives.
41 enum Primitives = `
42 mixin(intTypes);
43 mixin(floatTypes);
44 mixin(blobTypes);
45 `;
46 
47 /// Provides metadata for D introspection on dclass types.
48 struct TypeId
49 {
50     int id;
51     string name;
52     int firstFieldId;
53 }
54 
55 /// Provides metadata for D introspection on dclass fields.
56 struct FieldId
57 {
58     int id;
59 }
60 
61 /// Provides metadata for D introspection on dclass fields.
62 struct FieldType(T)
63 {
64     T typeInit = T.init;
65 }
66 
67 /// Generates D source code from a given module.  It will optionally declare a D module.
68 /// This utility function also imports codegen helpers and privately aliases dclass primitives.
69 /// This function is suitable for creating a self-contained file.
70 /// See_Also: $(D generateModule).
71 string generateFile(Module file, string moduleName = "",
72         string distributedObjectModule = "libastrond", string baseType = "",
73         bool generateStubs = true)
74 {
75     string format;
76 
77     if (moduleName.length == 0)
78     {
79         moduleName = file.symbol;
80     }
81 
82     format ~= "// This code was generated by a tool.\n";
83     if (moduleName.length > 0)
84     {
85         format ~= "module ";
86         format ~= moduleName;
87         format ~= ";";
88     }
89     format ~= "import std.exception;";
90     format ~= "import bamboo.codegen;";
91     format ~= "import " ~ distributedObjectModule ~ ";";
92 
93     foreach (imprt; file.importDeclarations)
94     {
95         if (imprt.packageName == moduleName)
96         {
97             continue;
98         }
99         format ~= "import " ~ imprt.packageName ~ " : ";
100         foreach (type; imprt.symbols[0 .. $ - 1])
101         {
102             format ~= type ~ ", ";
103         }
104         format ~= imprt.symbols[$ - 1] ~ ";";
105     }
106 
107     format ~= "private { mixin(Primitives); }";
108     format ~= generateModule(file, baseType, generateStubs);
109 
110     return format;
111 }
112 
113 /// Ditto.
114 string generateFile(string dcFile, string moduleName = "",
115         string distributedObjectModule = "libastrond", string baseType = "",
116         bool generateStubs = true)
117 {
118     import bamboo.astgen : parseModule;
119 
120     Module file = parseModule(dcFile);
121     return generateFile(file, moduleName, distributedObjectModule, baseType, generateStubs);
122 }
123 
124 /// Generates D source code from a given module. This function is 
125 /// suitable for mixing into a larger D source file.
126 /// See_Also: $(D generateFile).
127 string generateModule(Module file, string baseType = "", bool generateStubs = true)
128 {
129     string format;
130 
131     foreach (type; file.typesById)
132     {
133         if (auto cls = cast(ClassDeclaration) type)
134         {
135             format ~= generateClass(cls, baseType, generateStubs);
136         }
137         else if (auto strct = cast(StructDeclaration) type)
138         {
139             format ~= generateStruct(strct);
140         }
141     }
142 
143     return format;
144 }
145 
146 /// Ditto.
147 string generateModule(string dcFile, string baseType = "", bool generateStubs = true)
148 {
149     import bamboo.astgen : parseModule;
150 
151     Module file = parseModule(dcFile);
152     return generateModule(file, baseType, generateStubs);
153 }
154 
155 private:
156 
157 string generateClass(ClassDeclaration cls, string baseType, bool generateStubs)
158 {
159     string format;
160     format ~= "@TypeId(" ~ cls.id.to!string ~ ", `" ~ cls.symbol ~ "`, "
161         ~ cls.fields[0].id.to!string ~ ") ";
162 
163     if (!generateStubs)
164     {
165         format ~= "abstract ";
166     }
167 
168     format ~= "class ";
169     format ~= cls.symbol;
170 
171     if (cls.hasSuperclass)
172     {
173         format ~= " : ";
174         format ~= cls.parents[0].symbol;
175     }
176     else if (baseType.length > 0)
177     {
178         format ~= " : ";
179         format ~= baseType;
180     }
181 
182     format ~= "{";
183 
184     if (cls.hasConstructor)
185     {
186         assert(0, "Constructors are not supported.");
187     }
188 
189     foreach (field; cls.fields)
190     {
191         format ~= generateField(field, generateStubs);
192     }
193 
194     format ~= "}";
195     return format;
196 }
197 
198 string generateStruct(StructDeclaration strct)
199 {
200     string format;
201     format ~= "@TypeId(" ~ strct.id.to!string ~ ", `" ~ strct.symbol ~ "`, "
202         ~ strct.parameters[0].id.to!string ~ ")";
203     format ~= "struct ";
204     format ~= strct.symbol;
205     format ~= " {";
206     foreach (field; strct.parameters)
207     {
208         format ~= generateParameterField(field, true);
209     }
210     format ~= "}";
211     return format;
212 }
213 
214 string generateField(FieldDeclaration field, bool generateStubs)
215 {
216     string format;
217     if (auto molecular = cast(MolecularField) field)
218     {
219         format ~= generateMolecular(molecular);
220     }
221     else if (auto atomic = cast(AtomicField) field)
222     {
223         format ~= generateAtomic(atomic, generateStubs);
224     }
225     else if (auto parameter = cast(ParameterField) field)
226     {
227         format ~= generateParameterField(parameter, generateStubs);
228     }
229     else
230     {
231         assert(0);
232     }
233     return format;
234 }
235 
236 string generateMolecular(MolecularField field)
237 {
238     string format;
239     format ~= "@FieldId(" ~ field.id.to!string ~ ")";
240     format ~= "void ";
241     format ~= field.symbol;
242 
243     format ~= "(";
244 
245     foreach (reference; field.references)
246     {
247         format ~= generateParameterListFor(reference);
248     }
249 
250     format ~= ") {";
251     foreach (reference; field.references)
252     {
253         format ~= generateCallFor(reference) ~ ";";
254     }
255 
256     format ~= "}";
257 
258     return format;
259 }
260 
261 /**
262  * This function generates parameter names from a field's name.
263  * 
264  * Notes: 
265  *      Some fields follow the pattern of, e.g., `setXYZ(float32, float32, float32)`.
266  *      Using the name generator, ugly parameter names will be generated when 
267  *      enough semantic information is already available to properly generate
268  *      parameter names.
269  *
270  *      This method would take `setXYZ(float32, float32, float32)` and transform
271  *      it to `setXYZ(float32 x, float32, y, float32 z)`, for example.
272  */
273 void autogenParameterNames(AtomicField field)
274 {
275     if (!field.symbol.startsWith("set"))
276     {
277         return;
278     }
279 
280     foreach (parameter; field.parameters)
281     {
282         if (parameter.symbol.length > 0)
283         {
284             return;
285         }
286     }
287 
288     string name = field.symbol[3 .. $];
289     if (name.filter!(a => isUpper(cast(dchar) a))().array.length != field.parameters.length)
290     {
291         return;
292     }
293 
294     int start;
295     int index = 1;
296     int param;
297 
298     string[] names;
299 
300     void appendName()
301     {
302         if (index - start == 1)
303         {
304             names ~= [cast(char)(name[start].toLower)];
305         }
306         else
307         {
308             names ~= cast(char)(name[start].toLower) ~ name[start + 1 .. index];
309         }
310 
311     }
312 
313     foreach (value; name[1 .. $])
314     {
315         if (isUpper(cast(dchar) value))
316         {
317             appendName();
318             start = index;
319         }
320         index++;
321     }
322     if (names.length != field.parameters.length)
323     {
324         appendName();
325     }
326 
327     foreach (i, para; field.parameters)
328     {
329         para.symbol = names[i];
330     }
331 }
332 
333 string generateAtomic(AtomicField field, bool stub)
334 {
335     string format;
336 
337     bool isComplex = field.parameters.length > 1;
338     bool isProperty = field.name.startsWith("set");
339     string name;
340 
341     autogenParameterNames(field);
342 
343     if (isProperty)
344     {
345         name = cast(char)(field.name[3].toLower) ~ field.name[4 .. $];
346         if (name[1 .. $].count!(x => isUpper(cast(dchar) x)) == name.length - 1)
347         {
348             name = field.name[3 .. $];
349         }
350         if (stub)
351         {
352             if (isComplex)
353             {
354                 format ~= "struct " ~ name ~ "_t {";
355                 foreach (parameter; field.parameters)
356                 {
357                     format ~= generateDefinition(parameter) ~ ";";
358                 }
359                 format ~= "}";
360                 format ~= "private " ~ name ~ "_t _" ~ name ~ "; ";
361             }
362             else if (field.parameters.length == 1)
363             {
364                 auto parameter = field.parameters[0];
365                 format ~= "private " ~ generateDefinition(parameter).split(' ')[0] ~ " _"
366                     ~ name ~ ";";
367             }
368             else
369             {
370                 assert(0, name ~ " is a setter with no value!");
371             }
372         }
373     }
374     else
375     {
376         name = field.name;
377     }
378     format ~= "@FieldId(" ~ field.id.to!string ~ ") ";
379     string fieldType;
380 
381     if (isProperty)
382     {
383         if (isComplex && stub)
384         {
385             fieldType = name ~ "_t";
386         }
387         else if (!isComplex)
388         {
389             fieldType = generateDefinition(field.parameters[0]).split(' ')[0];
390         }
391         if (stub)
392         {
393             format ~= "@FieldType!(" ~ fieldType ~ ") ";
394         }
395     }
396 
397     foreach (keyword; field.keywords)
398     {
399         format ~= "@" ~ keyword ~ " ";
400     }
401 
402     if (!stub)
403     {
404         format ~= " abstract ";
405     }
406     format ~= " void ";
407 
408     if (isComplex)
409     {
410         format ~= field.symbol;
411     }
412     else
413     {
414         format ~= name;
415     }
416 
417     format ~= "(";
418     format ~= generateParameterListFor(field);
419     format ~= ") ";
420 
421     if (!isComplex && isProperty)
422     {
423         format ~= "@property ";
424     }
425 
426     string contracts;
427 
428     foreach (parameter; field.parameters)
429     {
430         contracts ~= generateContractFor(parameter);
431     }
432     if (contracts.length > 0)
433     {
434         format ~= " in {";
435         format ~= contracts;
436         format ~= "}";
437         if (stub)
438         {
439             format ~= "body";
440         }
441     }
442 
443     if (stub)
444     {
445         format ~= "{";
446         if (isProperty)
447         {
448             if (isComplex)
449             {
450                 foreach (parameter; field.parameters)
451                 {
452                     format ~= "_" ~ name ~ "." ~ parameter.symbol;
453                     format ~= "=" ~ parameter.symbol ~ ";";
454                 }
455             }
456             else if (field.parameters.length == 1)
457             {
458                 auto parameter = field.parameters[0];
459                 format ~= "_" ~ name;
460                 format ~= "=" ~ parameter.symbol ~ ";";
461             }
462         }
463         else
464         {
465             format ~= `assert(0, "Override body not defined for ` ~ field.name ~ `!");`;
466         }
467 
468         format ~= "}";
469     }
470     else
471     {
472         format ~= ";";
473     }
474 
475     if (isProperty)
476     {
477         format ~= "@FieldId(" ~ field.id.to!string ~ ") ";
478 
479         if (!isComplex)
480         {
481             if (!stub)
482             {
483                 format ~= "abstract " ~ fieldType ~ " " ~ name ~ "() inout @property;";
484             }
485             else
486             {
487                 format ~= fieldType ~ " " ~ name ~ "() inout @property { ";
488                 format ~= "return _" ~ name ~ ";";
489                 format ~= "}";
490             }
491         }
492         else
493         {
494             if (stub)
495             {
496                 format ~= "auto " ~ name ~ "() inout @property {";
497                 format ~= "return _" ~ name ~ ";";
498                 format ~= "}";
499             }
500         }
501     }
502 
503     return format;
504 }
505 
506 string generateParameterField(ParameterField field, bool stub)
507 {
508     string format;
509     string def = generateDefinition(field.parameter);
510     string type = def.split(' ')[0];
511     string name = def.split(' ')[1];
512 
513     format ~= "@FieldId(" ~ field.id.to!string ~ ") ";
514     format ~= "@FieldType!(" ~ generateDefinition(field.parameter).split(' ')[0] ~ ") ";
515 
516     foreach (keyword; field.keywords)
517     {
518         format ~= " @" ~ keyword ~ " ";
519     }
520 
521     if (!stub)
522     {
523         format ~= "abstract void " ~ name ~ "(" ~ type ~ " value) @property;";
524         format ~= "abstract " ~ def ~ "();";
525     }
526     else
527     {
528         format ~= "void " ~ name ~ "(" ~ type ~ " value) @property {";
529         format ~= "_" ~ name ~ " = value;";
530         format ~= "}";
531 
532         format ~= "@FieldId(" ~ field.id.to!string ~ ") ";
533 
534         format ~= type ~ " " ~ name ~ "() inout @property {";
535         format ~= "return _" ~ name ~ ";";
536         format ~= "}";
537         format ~= type ~ " _" ~ name ~ ";";
538     }
539 
540     return format;
541 }
542 
543 string generateCallFor(FieldDeclaration field)
544 {
545     if (auto molecular = cast(MolecularField) field)
546     {
547         return generateMolecularCall(molecular);
548     }
549     else if (auto atomic = cast(AtomicField) field)
550     {
551         return generateAtomicCall(atomic);
552     }
553     else if (auto parameter = cast(ParameterField) field)
554     {
555         return generateParameterFieldCall(parameter);
556     }
557     else
558     {
559         assert(0);
560     }
561 
562 }
563 
564 string[] flattenArgs(MolecularField field)
565 {
566     string[] args;
567     foreach (reference; field.references)
568     {
569         if (auto molecular = cast(MolecularField) field)
570         {
571             args ~= flattenArgs(molecular);
572         }
573         else if (auto atomic = cast(AtomicField) field)
574         {
575             foreach (arg; atomic.parameters)
576             {
577                 args ~= arg.symbol;
578             }
579         }
580         else if (auto paraField = cast(ParameterField) field)
581         {
582             args ~= paraField.parameter.symbol;
583         }
584     }
585     return args;
586 }
587 
588 string generateMolecularCall(MolecularField field)
589 {
590     string[] args = flattenArgs(field);
591     string format;
592     format ~= field.symbol;
593     format ~= "(";
594     foreach (arg; args)
595     {
596         format ~= arg ~ ",";
597     }
598     format ~= ")";
599 
600     return format;
601 }
602 
603 string generateAtomicCall(AtomicField field)
604 {
605     string format;
606     if (field.parameters.length > 1)
607     {
608         format ~= field.symbol;
609     }
610     else
611     {
612         format ~= "this." ~ cast(char)(field.name[3].toLower) ~ field.name[4 .. $];
613     }
614     format ~= "(";
615     foreach (arg; field.parameters)
616     {
617         format ~= arg.symbol ~ ",";
618     }
619     format ~= ")";
620 
621     return format;
622 }
623 
624 string generateParameterFieldCall(ParameterField field)
625 {
626     return "this." ~ field.name ~ "=" ~ field.name;
627 }
628 
629 string generateParameterListFor(FieldDeclaration field)
630 {
631     if (auto molecular = cast(MolecularField) field)
632     {
633         string format;
634         foreach (reference; molecular.references)
635         {
636             format ~= generateParameterListFor(reference);
637         }
638         return format;
639     }
640     else if (auto atomic = cast(AtomicField) field)
641     {
642         return generateParameterList(atomic.parameters);
643     }
644     else if (auto parameter = cast(ParameterField) field)
645     {
646         return generateParameterList([parameter.parameter]);
647     }
648     return "";
649 }
650 
651 string generateParameterList(Parameter[] parameters)
652 {
653     string format;
654 
655     foreach (parameter; parameters)
656     {
657         format ~= generateDefinition(parameter) ~ ",";
658     }
659 
660     return format;
661 }
662 
663 string generateDefinition(Parameter parameter)
664 {
665     switch (parameter.syntaxKind)
666     {
667     case SyntaxKind.StructParameter:
668         return generateDefinition(cast(StructParameter) parameter);
669     case SyntaxKind.ArrayParameter:
670         return generateDefinition(cast(ArrayParameter) parameter);
671     case SyntaxKind.SizedParameter:
672         return generateDefinition(cast(SizedParameter) parameter);
673     case SyntaxKind.NumericParameter:
674         return generateDefinition(cast(NumericParameter) parameter);
675     default:
676         assert(0);
677     }
678 }
679 
680 string generateDefinition(StructParameter parameter)
681 {
682     enum string format = `${type} ${name}`;
683 
684     string type = mapType(parameter.type.symbol);
685     string name = generateName(parameter.symbol, type);
686 
687     parameter.symbol = name;
688 
689     return mixin(interp!format);
690 }
691 
692 string generateDefinition(ArrayParameter array)
693 {
694     enum string format = `${type}[${maxLength}] ${name}`;
695 
696     string type = mapType(array.elementType.symbol);
697     string name = generateName(array.symbol, type);
698     string maxLength = "";
699 
700     version (RespectFixedLength)
701     {
702         if (array.hasRange && array.size.isFixedLength)
703         {
704             maxLength = array.size.maxLength.to!string;
705         }
706     }
707 
708     array.symbol = name;
709 
710     return mixin(interp!format);
711 }
712 
713 string generateDefinition(SizedParameter array)
714 {
715     enum string format = `${type} ${name}`;
716 
717     string type;
718     string maxLength = "";
719     string defaultVal = "";
720 
721     version (RespectFixedLength)
722     {
723         if (array.hasRange && array.size.isFixedLength)
724         {
725             maxLength = array.size.maxLength.to!string;
726         }
727 
728         switch (array.parameterType) with (Type)
729         {
730         case string_:
731         case varstring:
732             type = "immutable char[${maxLength}]";
733             break;
734         case blob:
735         case varblob:
736             type = "immutable ubyte[${maxLength}]";
737             break;
738         default:
739             assert(0);
740         }
741         type = mixin(interp!type);
742     }
743     else
744     {
745         switch (array.parameterType) with (Type)
746         {
747         case string_:
748         case varstring:
749             type = "string";
750             break;
751         case blob:
752         case varblob:
753             type = "blob";
754             break;
755         default:
756             assert(0);
757         }
758     }
759 
760     type = mapType(type);
761 
762     string name = generateName(array.symbol, type);
763 
764     if (array.defaultVal.length > 0)
765     {
766         defaultVal = mixin(interp!" = \"${array.defaultVal}\"");
767     }
768 
769     array.symbol = name;
770 
771     return mixin(interp!format);
772 }
773 
774 string generateDefinition(NumericParameter numeric)
775 {
776     enum string format = `${type} ${name}`;
777 
778     string type = mapType(numeric.type.to!string());
779     string name = generateName(numeric.symbol, type);
780 
781     numeric.symbol = name;
782 
783     return mixin(interp!format);
784 }
785 
786 string mapType(string type)
787 {
788     // dfmt off
789     enum types = [
790             "int8": "byte", 
791             "int16": "short",
792             "int32": "int", 
793             "int64": "long", 
794             "uint8": "ubyte", 
795             "uint16": "ushort", 
796             "uint32": "uint", 
797             "uint64": "ulong", 
798             "float32": "float", 
799             "float64": "double", 
800             "char_": "char",
801             "varstring": "string", 
802             "blob": "ubyte[]", 
803             "varblob": "ubyte[]", 
804         ];
805     // dfmt on
806 
807     if (auto ret = type in types)
808     {
809         return *ret;
810     }
811     return type;
812 }
813 
814 string generateContractFor(Parameter parameter)
815 {
816     if (auto array = cast(SizedParameter) parameter)
817     {
818         return generateContract(array);
819     }
820     else if (auto array = cast(ArrayParameter) parameter)
821     {
822         return generateContract(array);
823     }
824     else if (auto numeric = cast(NumericParameter) parameter)
825     {
826         return generateContract(numeric);
827     }
828     return "";
829 }
830 
831 string generateContract(SizedParameter array)
832 {
833     enum string format = `
834     assert(${parameter}.length <= ${maxLength}, "${parameter} is oversized!");
835     assert(${parameter}.length >= ${minLength}, "${parameter} is undersized!");
836     `;
837 
838     if (!array.hasRange)
839     {
840         return "";
841     }
842 
843     string parameter = array.symbol;
844     string maxLength = array.size.maxSize.to!string;
845     string minLength = array.size.minSize.to!string;
846 
847     return mixin(interp!format);
848 }
849 
850 string generateContract(ArrayParameter array)
851 {
852     enum string format = `
853     assert(${parameter}.length <= ${maxLength}, "${parameter} is oversized!");
854     assert(${parameter}.length >= ${minLength}, "${parameter} is undersized!");
855     `;
856 
857     if (!array.hasRange)
858     {
859         return "";
860     }
861 
862     string parameter = array.symbol;
863     string maxLength = array.size.maxLength.to!string;
864     string minLength = array.size.minLength.to!string;
865 
866     return mixin(interp!format);
867 }
868 
869 string generateContract(NumericParameter numeric)
870 {
871     string formatStr(string parameter, string op, string value, string msg)
872     {
873         return `assert(` ~ parameter ~ ` ` ~ op ~ ` ` ~ value ~ `, "` ~ parameter
874             ~ ` is too ` ~ msg ~ `!");`;
875     }
876 
877     string format;
878 
879     if (!numeric.hasRange)
880     {
881         return "";
882     }
883 
884     string parameter = numeric.symbol;
885     string max = numeric.range.max.to!string;
886     string min;
887 
888     format ~= formatStr(parameter, "<=", max, "large");
889 
890     if (!isNaN(numeric.range.min))
891     {
892         min = numeric.range.min.to!string;
893         format ~= formatStr(parameter, ">=", min, "small");
894     }
895 
896     return format;
897 }
898 
899 private:
900 
901 static int g_nameCounter;
902 
903 string generateName(string name, string type)
904 {
905     if (name.length == 0)
906     {
907         HashGenerator gen;
908         gen.addInt(g_nameCounter++);
909         gen.addString(type);
910         name = mixin(interp!"_${type.toLower}${gen.hash}");
911     }
912     return name;
913 }