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 }