1 /++
2 $(H1 Mutable YAML value)
3 
4 This module contains a single alias definition and doesn't provide YAML serialization API.
5 
6 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
7 Authors: Ilia Ki 
8 Macros:
9 +/
10 module mir.algebraic_alias.yaml;
11 
12 import mir.serde: serdeLikeStruct, serdeProxy;
13 
14 import mir.algebraic:
15     algVerbose,
16     algMeta,
17     algTransp,
18     Algebraic,
19     This;
20 
21 import mir.exception: MirException;
22 
23 ///
24 public import mir.annotated: Annotated;
25 ///
26 public import mir.lob: Blob;
27 ///
28 public import mir.timestamp: Timestamp;
29 ///
30 public import mir.parse: ParsePosition;
31 
32 private alias AliasSeq(T...) = T;
33 
34 ///Scalar styles.
35 enum YamlScalarStyle : ubyte
36 {
37     /// Invalid (uninitialized) style
38     none,
39     /// `|` (Literal block style)
40     literal,
41     /// `>` (Folded block style)
42     folded,
43     /// Plain scalar
44     plain,
45     /// Single quoted scalar
46     singleQuoted,
47     /// Double quoted scalar
48     doubleQuoted
49 }
50 
51 ///Collection styles.
52 enum YamlCollectionStyle : ubyte
53 {
54     /// Invalid (uninitialized) style
55     none,
56     /// Block style.
57     block,
58     /// Flow style.
59     flow
60 }
61 
62 /++
63 Definition union for $(LREF YamlAlgebraic).
64 +/
65 union Yaml_
66 {
67     ///
68     typeof(null) null_;
69     ///
70     bool boolean;
71     ///
72     long integer;
73     ///
74     double float_;
75     ///
76     immutable(char)[] string;
77     ///
78     Blob blob;
79     ///
80     Timestamp timestamp;
81     /// Self alias in array.
82     This[] array;
83     /// Self alias in $(MREF mir,string_map).
84     YamlMap object;
85     /// Self alias in $(MREF mir,annotated).
86     Annotated!This annotated;
87 
88 @algMeta:
89     ///
90     immutable(char)[] tag;
91 @algTransp:
92     ///
93     YamlCollectionStyle collectionStyle;
94     ///
95     YamlScalarStyle scalarStyle;
96     ///
97     @algVerbose ParsePosition startMark;
98 }
99 
100 /++
101 YAML tagged algebraic alias.
102 
103 The example below shows only the basic features. Advanced API to work with algebraic types can be found at $(GMREF mir-core, mir,algebraic).
104 See also $(MREF mir,string_map) - ordered string-value associative array.
105 +/
106 alias YamlAlgebraic = Algebraic!Yaml_;
107 
108 /++
109 YAML map representation.
110 
111 The implementation preserves order and allows duplicated keys.
112 +/
113 @serdeLikeStruct
114 @serdeProxy!YamlAlgebraic
115 struct YamlMap
116 {
117     /++
118     +/
119     YamlPair[] pairs;
120 
121     ///
122     this(YamlPair[] pairs) @safe pure nothrow @nogc
123     {
124         this.pairs = pairs;
125     }
126 
127     size_t length() scope const @property @safe pure nothrow @nogc
128     {
129         return pairs.length;
130     }
131 
132     static foreach (V; AliasSeq!(YamlAlgebraic.AllowedTypes[1 .. $], YamlAlgebraic, int))
133     {
134         static foreach (K; AliasSeq!(YamlAlgebraic.AllowedTypes[1 .. $], YamlAlgebraic, int))
135         ///
136         this(K[] keys, V[] values) @safe pure nothrow
137             in(keys.length == values.length)
138         {
139             import mir.ndslice.topology: zip, map;
140             import mir.array.allocation: array;
141             this.pairs = keys.zip(values).map!YamlPair.array;
142         }
143 
144         ref YamlAlgebraic opIndexAssign(V value, string key) @safe pure return scope nothrow
145         {
146             if (auto valuePtr = key in this)
147                 return *valuePtr = value;
148             pairs ~= YamlPair(key, value);
149             return pairs[$ - 1].value;
150         }
151 
152         ref YamlAlgebraic opIndexAssign(V value, scope const(char)[] key) @safe pure return scope nothrow
153         {
154             if (auto valuePtr = key in this)
155                 return *valuePtr = value;
156             pairs ~= YamlPair(key.idup, value);
157             return pairs[$ - 1].value;
158         }
159 
160         ref YamlAlgebraic opIndexAssign(V value, YamlAlgebraic key) @safe pure return scope nothrow
161         {
162             if (auto valuePtr = key in this)
163                 return *valuePtr = value;
164             pairs ~= YamlPair(key, value);
165             return pairs[$ - 1].value;
166         }
167     }
168 
169     /++
170     +/
171     this(K, V)(K[V] associativeArray)
172     {
173         import mir.ndslice.topology: map;
174         import mir.array.allocation: array;
175         this.pairs = associativeArray
176             .byKeyValue
177             .map!(kv => YamlPair(kv.key, kv.value))
178             .array;
179     }
180 
181     /++
182     Returns: the first value associated with the provided key.
183     +/
184     inout(YamlAlgebraic)* _opIn(scope const char[] key) @safe pure nothrow @nogc inout return scope
185     {
186         foreach (ref pair; pairs)
187             if (pair.key == key)
188                 return &pair.value;
189         return null;
190     }
191 
192     /// ditto
193     inout(YamlAlgebraic)* _opIn(scope const YamlAlgebraic key) @safe pure nothrow @nogc inout return scope
194     {
195         foreach (ref pair; pairs)
196             if (pair.key == key)
197                 return &pair.value;
198         return null;
199     }
200 
201     alias opBinaryRight(string op : "in") = _opIn;
202 
203     /++
204     Returns: the first value associated with the provided key.
205     +/
206     ref inout(YamlAlgebraic) opIndex(scope const char[] key) @safe pure inout return scope
207     {
208         if (auto valuePtr = key in this)
209             return *valuePtr;
210         throw new MirException("YamlMap: can't find key '", key, "'");
211     }
212 
213     /// ditto
214     ref inout(YamlAlgebraic) opIndex(scope const YamlAlgebraic key) @safe pure inout return scope
215     {
216         if (key._is!string)
217             return this[key.get!string];
218         if (auto valuePtr = key in this)
219             return *valuePtr;
220         import mir.format: stringBuf, getData;
221         auto buf = stringBuf;
222         key.toString(buf);
223         throw new MirException("YamlMap: can't find key ", buf << getData);
224     }
225 
226     bool opEquals(scope const typeof(this) rhs) scope const @safe pure nothrow @nogc
227     {
228         return pairs == rhs.pairs;
229     }
230 
231     int opCmp(scope const typeof(this) rhs) scope const @safe pure nothrow @nogc
232     {
233         return __cmp(pairs, rhs.pairs);
234     }
235 
236     ///
237     auto byKeyValue() @trusted return scope pure nothrow @nogc
238     {
239         import mir.ndslice.slice: sliced;
240         return pairs.sliced;
241     }
242 
243     /// ditto
244     auto byKeyValue() const @trusted return scope pure nothrow @nogc
245     {
246         import mir.ndslice.slice: sliced;
247         return pairs.sliced;
248     }
249 
250     static import mir.functional;
251 
252     ///
253     auto opIndex() @trusted return scope pure nothrow @nogc
254     {
255         import mir.ndslice.slice: sliced;
256         return sliced(cast(mir.functional.Tuple!(YamlAlgebraic, YamlAlgebraic)[]) pairs);
257     }
258 
259     /// ditto
260     auto opIndex() const @trusted return scope pure nothrow @nogc
261     {
262         import mir.ndslice.slice: sliced;
263         return sliced(cast(const mir.functional.Tuple!(YamlAlgebraic, YamlAlgebraic)[]) pairs);
264     }
265 }
266 
267 ///
268 version(mir_ion_test)
269 unittest
270 {
271     YamlMap map = ["a" : 1];
272     assert(map["a"] == 1);
273     map[1.YamlAlgebraic] = "a";
274     map["a"] = 3;
275     map["a"].get!long++;
276     map["a"].get!"integer" += 3;
277     map["a"].integer += 3;
278     assert(map["a"] == 10);
279     assert(map[1.YamlAlgebraic] == "a");
280 
281     // foreach iteration
282     long sum;
283     foreach (ref key, ref value; map)
284         if (key == "a")
285             sum += value.get!long;
286     assert(sum == 10);
287 }
288 
289 /++
290 +/
291 struct YamlPair
292 {
293     ///
294     YamlAlgebraic key;
295     ///
296     YamlAlgebraic value;
297 
298     static foreach (K; AliasSeq!(YamlAlgebraic.AllowedTypes, YamlAlgebraic, int))
299     static foreach (V; AliasSeq!(YamlAlgebraic.AllowedTypes, YamlAlgebraic, int))
300     ///
301     this(K key, V value) @safe pure nothrow @nogc
302     {
303         static if (is(K == YamlAlgebraic))
304             this.key = key;
305         else
306             this.key.__ctor(key);
307         static if (is(V == YamlAlgebraic))
308             this.value = value;
309         else
310             this.value.__ctor(value);
311     }
312 
313     ///
314     int opCmp(ref scope const typeof(this) rhs) scope const @safe pure nothrow @nogc
315     {
316         if (auto d = key.opCmp(rhs.key))
317             return d;
318         return value.opCmp(rhs.value);
319     }
320 
321     ///
322     int opCmp(scope const typeof(this) rhs) scope const @safe pure nothrow @nogc
323     {
324         return this.opCmp(rhs);
325     }
326 }
327 
328 ///
329 version(mir_ion_test)
330 unittest
331 {
332     import mir.ndslice.topology: map;
333     import mir.array.allocation: array;
334 
335     YamlAlgebraic value;
336 
337     // Default
338     assert(value.isNull);
339     assert(value.kind == YamlAlgebraic.Kind.null_);
340 
341     // Boolean
342     value = true;
343 
344     assert(!value.isNull);
345     assert(value == true);
346     assert(value.kind == YamlAlgebraic.Kind.boolean);
347     assert(value.boolean == true);
348     assert(value.get!bool == true);
349     assert(value.get!(YamlAlgebraic.Kind.boolean) == true);
350 
351     // Null
352     value = null;
353     assert(value.isNull);
354     assert(value == null);
355     assert(value.kind == YamlAlgebraic.Kind.null_);
356     assert(value.null_ == null);
357     assert(value.get!(typeof(null)) == null);
358     assert(value.get!(YamlAlgebraic.Kind.null_) == null);
359 
360     // String
361     value = "s";
362     assert(value.kind == YamlAlgebraic.Kind..string);
363     assert(value == "s");
364     assert(value..string == "s");
365     assert(value.get!string == "s");
366     assert(value.get!(YamlAlgebraic.Kind..string) == "s");
367 
368     // Integer
369     value = 4;
370     assert(value.kind == YamlAlgebraic.Kind.integer);
371     assert(value == 4);
372     assert(value != 4.0);
373     assert(value.integer == 4);
374 
375     // Float
376     value = 3.0;
377     assert(value.kind == YamlAlgebraic.Kind.float_);
378     assert(value != 3);
379     assert(value == 3.0);
380     assert(value.float_ == 3.0);
381     assert(value.get!double == 3.0);
382     assert(value.get!(YamlAlgebraic.Kind.float_) == 3.0);
383 
384     // Array
385     YamlAlgebraic[] arr = [0, 1, 2, 3, 4].map!YamlAlgebraic.array;
386 
387     value = arr;
388     assert(value.kind == YamlAlgebraic.Kind.array);
389     assert(value == arr);
390     assert(value.array[3] == 3);
391 
392     // Object
393     value = [1 : "a"].YamlAlgebraic;
394     assert(value.kind == YamlAlgebraic.Kind.object);
395     assert(value.object.pairs == [YamlPair(1, "a")]);
396     assert(value.object[1.YamlAlgebraic] == "a");
397 
398     assert(value == value);
399     assert(value <= value);
400     assert(value >= value);
401 }