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);}));