1 2 // Copyright Luna & Cospec 2019. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 module wsf.ast.tag; 8 import taggedalgebraic.taggedunion; 9 import std.traits; 10 import wsf.ast.parser; 11 import wsf.ast.builder; 12 13 14 private alias TagValue = TaggedUnion!WSFTagValue; 15 private union WSFTagValue { 16 ubyte byte_; 17 ushort short_; 18 uint int_; 19 ulong long_; 20 double double_; 21 bool bool_; 22 string string_; 23 Tag[string] compound_; 24 Tag[] array_; 25 } 26 27 /** 28 The kind of WSF tag 29 */ 30 enum TagKind { 31 byte_ = TagValue.Kind.byte_, 32 short_ = TagValue.Kind.short_, 33 int_ = TagValue.Kind.int_, 34 long_ = TagValue.Kind.long_, 35 double_ = TagValue.Kind.double_, 36 bool_ = TagValue.Kind.bool_, 37 string_ = TagValue.Kind.string_, 38 compound_ = TagValue.Kind.compound_, 39 array_ = TagValue.Kind.array_ 40 } 41 42 class Tag { 43 private: 44 TagValue value; 45 46 public: 47 48 /** 49 Parse from WSF file 50 */ 51 static Tag parseFile(string file) { 52 import wsf.streams.filestream : FileStream; 53 import std.stdio : File; 54 FileStream stream = new FileStream(File(file, "r")); 55 scope(exit) stream.close(); 56 return parse(stream); 57 } 58 59 /** 60 Parse from WSF file 61 */ 62 void buildFile(string file) { 63 import wsf.streams.filestream : FileStream; 64 import std.stdio : File; 65 FileStream stream = new FileStream(File(file, "w")); 66 scope(exit) stream.close(); 67 return build(this, stream); 68 } 69 70 /** 71 Gets wether this tag is a compound 72 */ 73 @property 74 bool isKindCompound() { 75 return kind() == TagKind.compound_; 76 } 77 78 /** 79 Gets wether this tag is an array 80 */ 81 @property 82 bool isKindArray() { 83 return kind() == TagKind.array_; 84 } 85 86 /** 87 Gets wether this tag is a value tag (not a compound or array) 88 */ 89 @property 90 bool isKindValue() { 91 return !isKindCompound() && !isKindArray(); 92 } 93 94 this() { } 95 96 this(T)(T value) if (isFloatingPoint!T) { 97 this.value = cast(double)value; 98 } 99 100 this(T)(T value) if (isIntegral!T || is(T == bool) || is(T : string)) { 101 this.value = value; 102 } 103 104 this(T)(T value) if (is(T : Tag[])) { 105 this.value = Tag[].init; 106 } 107 108 this(T)(T value) if (is(T : Tag[string])) { 109 this.value = cast(Tag[string])value; 110 } 111 112 this(T)(T value) if (is(T : TagValue)) { 113 this.value = value; 114 } 115 116 this(T)(T value) if (is(T : Tag)) { 117 this.value = value.value; 118 } 119 120 /** 121 Construct all other arrays 122 */ 123 this(T)(T value) if (isArray!T && !is(T : Tag[]) && !is(T : string)) { 124 this.value = cast(Tag[])[]; 125 foreach(tagval; value) { 126 this.value.array_Value ~= new Tag(tagval); 127 } 128 } 129 130 static { 131 Tag emptyCompound() { return new Tag(cast(Tag[string])null); } 132 Tag emptyArray() { return new Tag(cast(Tag[])[]); } 133 } 134 135 /** 136 foreach for Tag param 137 */ 138 int opApply(int delegate(ref Tag) operations) { 139 size_t result = 0; 140 141 if (kind == TagKind.array_) { 142 foreach(Tag tag; this.value.array_Value) { 143 result = operations(tag); 144 145 if (result) { 146 break; 147 } 148 } 149 } else if (kind == TagKind.compound_) { 150 foreach(Tag tag; this.value.compound_Value) { 151 result = operations(tag); 152 153 if (result) { 154 break; 155 } 156 } 157 } else { 158 throw new Exception("Tag was neither an array or a compound."); 159 } 160 161 162 163 return 0; 164 } 165 166 /** 167 foreach for int and Tag param 168 */ 169 int opApply(int delegate(ref size_t, ref Tag) operations) { 170 size_t result = 0; 171 172 foreach(size_t i, Tag tag; this.value.array_Value) { 173 result = operations(i, tag); 174 175 if (result) { 176 break; 177 } 178 } 179 180 return 0; 181 } 182 183 /** 184 foreach for int and Tag param 185 */ 186 int opApply(int delegate(ref string, ref Tag) operations) { 187 size_t result = 0; 188 189 foreach(string i, Tag tag; this.value.compound_Value) { 190 result = operations(i, tag); 191 192 if (result) { 193 break; 194 } 195 } 196 197 return 0; 198 } 199 200 ref Tag opIndex(T)(T index) { 201 static if (is(T : string)) { 202 if (kind != TagKind.compound_) throw new Exception("Tag is not a compound!"); 203 return this.value.compound_Value[index]; 204 } else static if (isNumeric!T) { 205 if (kind != TagKind.array_) throw new Exception("Tag is not an array!"); 206 return this.value.array_Value[index]; 207 } else { 208 throw new Exception("Type is not indexable!"); 209 } 210 } 211 212 Tag* opBinaryRight(string op = "in")(string index) { 213 if (kind != TagKind.compound_) throw new Exception("Tag is not a compound!"); 214 return index in this.value.compound_Value; 215 } 216 217 /** 218 Assign value at index 219 */ 220 void opIndexAssign(T, Y)(T value, Y index) { 221 static if (is(Y : string)) { 222 if (kind != TagKind.compound_) throw new Exception("Tag is not a compound!"); 223 this.value.compound_Value[index] = new Tag(value); 224 } else static if (isNumeric!Y) { 225 if (kind != TagKind.array_) throw new Exception("Tag is not an array!"); 226 this.value.array_Value[index] = new Tag(value); 227 } else { 228 throw new Exception("Type is not indexable!"); 229 } 230 } 231 232 /** 233 Assign value at index 234 */ 235 void opOpAssign(string op = "~=", T)(T value) { 236 if (kind == TagKind.array_) { 237 static if (is (T : Tag)) { 238 this.value.array_Value ~= value; 239 } else { 240 this.value.array_Value ~= new Tag(value); 241 } 242 return; 243 } 244 static if (is(T : string)) { 245 if (kind == TagKind.string_) { 246 this.value.string_Value ~= value; 247 } 248 return; 249 } 250 throw new Exception("Type not appendable! (not array or string)"); 251 } 252 253 /** 254 Gets the kind of the object 255 */ 256 TagKind kind() { 257 return cast(TagKind)value.kind; 258 } 259 260 /** 261 Gets the value of the object 262 */ 263 inout(T) get(T)() inout @property @trusted { 264 static if (is(T == byte) || is(T == ubyte)) { 265 return cast(T)value.byte_Value; 266 } else static if (is(T == short) || is(T == ushort)) { 267 return cast(T)value.short_Value; 268 } else static if (is(T == int) || is(T == uint)) { 269 return cast(T)value.int_Value; 270 } else static if (is(T == long) || is(T == ulong)) { 271 return cast(T)(value.long_Value); 272 } else static if (isFloatingPoint!T) { 273 return cast(T)value.double_Value; 274 } else static if (isBoolean!T) { 275 return cast(T)value.bool_Value; 276 } else static if (is(T == Tag[])) { 277 return cast(T)value.array_Value; 278 } else static if (is(T == Tag[string])) { 279 return cast(T)value.compound_Value; 280 } else static if (is(T == string)) { 281 return cast(T)value.string_Value; 282 } else static if (is(T == enum)) { 283 return cast(T)this.get!(OriginalType!T); 284 } else { 285 assert(0, "Unable to handle type!"); 286 } 287 } 288 289 /** 290 Gets the compound's sequence 291 */ 292 ref Tag[] seq() { 293 if (kind != TagKind.compound_) throw new Exception("Tag is not a compound!"); 294 if ("seq" !in value.compound_Value) { 295 this["seq"] = Tag.emptyArray(); 296 } 297 return this["seq"].value.array_Value; 298 } 299 300 /** 301 Gets the array for this tag 302 */ 303 ref Tag[] array() { 304 if (kind != TagKind.array_) throw new Exception("Tag is not an array!"); 305 return this.value.array_Value; 306 } 307 308 /** 309 Gets the compound for this tag 310 */ 311 ref Tag[string] compound() { 312 if (kind != TagKind.compound_) throw new Exception("Tag is not a compound!"); 313 return this.value.compound_Value; 314 } 315 316 @property 317 size_t length() { 318 if (kind == TagKind.array_) { 319 return get!(Tag[]).length; 320 } 321 if (kind == TagKind.compound_) { 322 return get!(Tag[string]).length; 323 } 324 return 0; 325 } 326 327 override 328 string toString() { 329 import std.conv : text; 330 switch(kind) { 331 case TagKind.compound_: return this.get!(Tag[string]).text; 332 case TagKind.array_: return this.get!(Tag[]).text; 333 case TagKind.double_: return this.get!double.text; 334 case TagKind.string_: return "\""~this.get!string~"\""; 335 case TagKind.bool_: return this.get!bool.text; 336 case TagKind.byte_: return (cast(long)this.get!byte).text; 337 case TagKind.short_: return (cast(long)this.get!short).text; 338 case TagKind.int_: return (cast(long)this.get!int).text; 339 case TagKind.long_: return (cast(long)this.get!long).text; 340 default: return value.text; 341 } 342 } 343 }