1 module jaster.serialise.generator.core;
2 
3 private
4 {    
5     import std.format, std.traits, std.range, std.typecons, std.algorithm, std.array;
6     import jaster.serialise;
7 }
8 
9 public import std.typecons : Nullable;
10 
11 enum TypeFlags : ubyte
12 {
13     None        = 0,
14     IsMainValue = 1 << 0,
15     IsAttribute = 1 << 1,
16     IsOptional  = 1 << 2
17 }
18 
19 struct TypeField
20 {
21     string name;
22     TypeSerialiseInfo info;
23 
24     T as(T)()
25     {
26         return cast(T)info;
27     }
28 }
29 
30 abstract class TypeSerialiseInfo
31 {
32     TypeFlags flags;
33     Serialiser.Settings settings;
34     Serialiser.Settings inheritedSettings;
35 
36     @property
37     Serialiser.Settings allSettings()
38     {
39         return (this.settings | this.inheritedSettings);
40     }
41 
42     string toPrettyString(int tab = 0, string typeName = "Generic")
43     {
44         auto tabs = ['\t'].cycle.take(tab).array;
45         return format(
46              "%s[%s]\n"
47             ~"%sFlags:             %s\n"
48             ~"%sSettings:          %s\n"
49             ~"%sInheritedSettings: %s\n",
50             tabs, typeName,
51             tabs, this.flags,
52             tabs, this.settings,
53             tabs, this.inheritedSettings
54         );
55     }
56 }
57 
58 class BasicSerialiseInfo : TypeSerialiseInfo
59 {
60     TypeInfo type;
61 
62     static BasicSerialiseInfo make(T)()
63     if(ArchiveValue.allowed!T)
64     {
65         auto info = new BasicSerialiseInfo();
66         info.type = typeid(T);
67 
68         return info;
69     }
70 
71     override string toPrettyString(int tab = 0, string typeName = "Basic Type")
72     {
73         auto tabs = ['\t'].cycle.take(tab).array;
74         return format(
75              "%s"
76             ~"%s%sTypeName: %s\n",
77             super.toPrettyString(tab + 1, "Basic Type"),
78             tabs, (tab > 0 ? '\t' : '\0'), this.type.toString()
79         );
80     }
81 }
82 
83 class ArraySerialiseInfo : TypeSerialiseInfo
84 {
85     string            arrayAsObjectName;
86     TypeSerialiseInfo type;
87     Nullable!uint     staticArraySize;
88 
89     this(TypeSerialiseInfo info)
90     {
91         this.type = info;
92     }
93 
94     override string toPrettyString(int tab = 0, string typeName = "Array")
95     {
96         import std.conv : to;
97 
98         auto tabs     = ['\t'].cycle.take(tab).array;
99         auto extraTab = (tab > 0 ? '\t' : '\0');
100         return format(
101              "%s"
102             ~"%s%sStaticSize: %s\n"
103             ~"%s%sValueType: \n%s\n",
104             super.toPrettyString(tab + 1, "Array"),
105             tabs, extraTab, (this.staticArraySize.isNull) ? "null" : this.staticArraySize.get.to!string,
106             tabs, extraTab, this.type.toPrettyString(tab + 1)
107         );
108     }
109 }
110 
111 class StructSerialiseInfo : TypeSerialiseInfo
112 {
113     TypeField[string] fields;
114     TypeInfo structInfo;
115 
116     static StructSerialiseInfo make(T)()
117     if(is(T == struct))
118     {
119         auto info = new StructSerialiseInfo();
120         info.structInfo = typeid(T);
121         return info;
122     }
123 
124     override string toPrettyString(int tab = 0, string typeName = "Struct")
125     {
126         auto tabs = ['\t'].cycle.take(tab).array;
127         return format(
128              "%s"
129             ~"%s%sFields: \n%s\n",
130             super.toPrettyString(tab + 1, "Struct"),
131             tabs, (tab > 0 ? '\t' : '\0'),
132             this.fields.byKeyValue
133                        .map!(p => format("%sFieldName: %s\n", tabs, p.key) ~ p.value.info.toPrettyString(tab + 1))
134                        .joiner("\n")
135         );
136     }
137 }
138 
139 class EnumSerialiseInfo : TypeSerialiseInfo
140 {
141     struct Pair
142     {
143         string name;
144         long   value;
145     }
146 
147     /// Always an integral type.
148     TypeInfo type;
149     TypeInfo enumType;
150     Pair[] values;
151 
152     static EnumSerialiseInfo make(T)()
153     if(is(T == enum))
154     {
155         import std.conv : to;
156 
157         alias Type = OriginalType!T;
158         static assert(isIntegral!Type, "Only integral enum types are supported, since that's only what most languages support.");
159 
160         auto info     = new EnumSerialiseInfo();
161         info.type     = typeid(Type);
162         info.enumType = typeid(T);
163 
164         foreach(member; __traits(allMembers, T))
165             info.values ~= Pair(member.to!string, cast(long)mixin("T."~member));
166 
167         return info;
168     }
169 
170     override string toPrettyString(int tab = 0, string typeName = "Enum")
171     {
172         auto tabs = ['\t'].cycle.take(tab).array;
173         return format(
174              "%s"
175             ~"%s%sValues: \n%s\n",
176             super.toPrettyString(tab + 1, "Enum"),
177             tabs, (tab > 0 ? '\t' : '\0'),
178             this.values.map!(v => format("%s\t%s = %s", tabs, v.name, v.value))
179                        .joiner("\n")
180         );
181     }
182 }
183 
184 class TypeIntrospect
185 {
186     public static TypeField getInfoFor(T)()
187     if(is(T == struct))
188     //out(i; i !is null)
189     {
190         return TypeField(getFieldName!T, getOrIntrospect!(T, T, T, Serialiser.Settings.None)());
191     }
192 
193     private static
194     {
195         TypeSerialiseInfo[TypeInfo] _infoCache;
196         TypeSerialiseInfo getOrIntrospect(T, alias MainSymbol, alias Symbol, Serialiser.Settings InheritedSettings)()
197         {
198             auto ptr = (typeid(T) in _infoCache);
199             if(ptr !is null)
200                 return *ptr;
201 
202             auto info = introspect!(T, MainSymbol, Symbol, InheritedSettings);
203             _infoCache[typeid(T)] = info;
204             return info;
205         }
206 
207         BasicSerialiseInfo introspect(T, alias MainSymbol, alias Symbol, Serialiser.Settings InheritedSettings)()
208         if(ArchiveValue.allowed!T)
209         {
210             auto info = BasicSerialiseInfo.make!T();
211             static if(hasUDA!(Symbol, MainValue))
212                 info.flags |= TypeFlags.IsMainValue;
213             else static if(hasUDA!(Symbol, Attribute))
214                 info.flags |= TypeFlags.IsAttribute;
215 
216             info.settings = getSettings!Symbol;
217             info.inheritedSettings = InheritedSettings;
218 
219             return info;
220         }
221 
222         ArraySerialiseInfo introspect(T, alias MainSymbol, alias Symbol, Serialiser.Settings InheritedSettings)()
223         if(isArray!T && !isSomeString!T)
224         {        
225             static if(hasUDA!(Symbol, InheritSettings))
226                 enum ToInherit = InheritedSettings | getSettings!Symbol;
227             else
228                 enum ToInherit = InheritedSettings;
229 
230             enum settings = getSettings!MainSymbol | getSettings!Symbol | InheritedSettings;
231             
232             auto info = new ArraySerialiseInfo(null);
233             info.settings = getSettings!Symbol;
234             info.inheritedSettings = InheritedSettings;
235 
236             static if(isStaticArray!T)
237                 info.staticArraySize = T.length;
238 
239             static if(ArchiveValue.allowed!(ElementType!T))
240             {
241                 static assert(!(settings & Serialiser.Settings.ArrayAsObject), "The settings 'ArrayAsObject' can only be applied to arrays of structs.");
242 
243                 static if(hasUDA!(Symbol, MainValue))
244                     info.flags |= TypeFlags.IsMainValue;
245                 
246                 info.type = getOrIntrospect!(ElementType!T, MainSymbol, Symbol, ToInherit);
247             }
248             else static if(is(ElementType!T == struct))
249             {
250                 static assert(!hasUDA!(Symbol, MainValue), "Arrays of structs cannot be the main value, they can only be children.");
251 
252                 static if(settings & Serialiser.Settings.ArrayAsObject)
253                     info.arrayAsObjectName = getFieldName!(Symbol, UseArrayBaseType.no);
254 
255                 info.type = getOrIntrospect!(ElementType!T, MainSymbol, Symbol, ToInherit);
256             }
257             else static assert(false, "Unsupported array value type: " ~ T.stringof);
258 
259             return info;
260         }
261 
262         EnumSerialiseInfo introspect(T, alias MainSymbol, alias Symbol, Serialiser.Settings InheritedSettings)()
263         if(is(T == enum))
264         {
265             enum settings = getSettings!Symbol;
266             auto info = EnumSerialiseInfo.make!T;
267             info.settings = settings;
268             info.inheritedSettings = InheritedSettings;
269 
270             static if(hasUDA!(Symbol, Attribute))
271                 info.flags |= TypeFlags.IsAttribute;
272 
273             static assert(!hasUDA!(Symbol, MainValue), "MainValue enums are not supported right now. (only due to laziness)");
274 
275             return info;
276         }
277 
278         StructSerialiseInfo introspect(T, alias MainSymbol, alias Symbol, Serialiser.Settings InheritedSettings)()
279         if(is(T == struct))
280         {        
281             static assert(getSymbolsByUDA!(T, MainValue).length < 2, "There can only be one field marked with @MainValue");
282 
283             auto info = StructSerialiseInfo.make!T;
284             info.settings = getSettings!Symbol;
285             info.inheritedSettings = InheritedSettings;
286             foreach(fieldName; FieldNameTuple!T)
287             {
288                 static if(isPublic!(T, fieldName)
289                         && !hasUDA!(mixin("T."~fieldName), Ignore))
290                 {
291                     mixin("alias FieldAlias = T.%s;".format(fieldName));
292 
293                     static if(isInstanceOf!(Nullable, typeof(FieldAlias)))
294                         alias FieldType = Unqual!(ReturnType!(FieldAlias.get));
295                     else
296                         alias FieldType = typeof(FieldAlias);
297 
298                     static if(hasUDA!(Symbol, InheritSettings))
299                         enum ToInherit = InheritedSettings | getSettings!Symbol;
300                     else
301                         enum ToInherit = InheritedSettings;
302                         
303                     info.fields[fieldName] = TypeField(fieldName, introspect!(FieldType, MainSymbol, FieldAlias, ToInherit)());
304                     static if(isInstanceOf!(Nullable, typeof(FieldAlias)))
305                         info.flags |= TypeFlags.IsOptional;
306                 }
307             }
308             return info;
309         }
310     }
311 }
312 ///
313 version(Jasterialise_Unittests)
314 unittest
315 {
316     import fluent.asserts;
317 
318     struct A
319     {
320         @MainValue
321         int b;
322     }
323     
324     auto info = TypeIntrospect.getInfoFor!A;
325     info.name.should.equal("A");
326     info.as!StructSerialiseInfo.fields.length.should.equal(1);
327 
328     BasicSerialiseInfo field = info.as!StructSerialiseInfo.fields["b"].as!BasicSerialiseInfo;
329     field.should.not.beNull();
330     field.type.should.equal(typeid(int));
331     field.flags.should.equal(TypeFlags.IsMainValue);
332 }
333 
334 // Best I can do at least...
335 private enum isPublic(T, string field) = is(typeof({T t = T.init; auto b = mixin("t."~field);}));