The OpenD Programming Language

1 /++
2 $(H1 Transformation utilities for JSON-like values)
3 
4 See_also: JSON libraries $(MIR_PACKAGE mir-ion) and $(MIR_PACKAGE asdf);
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.transform;
11 
12 import mir.algebraic: Algebraic, tryVisit, visit, optionalVisit;
13 import mir.functional: naryFun;
14 private alias AliasSeq(T...) = T;
15 
16 /++
17 Transforms algebraics leafs recursively in place,
18 ensuring that all leaf types are handled by the visiting functions.
19 
20 Recursion is done for `This[]`, `StringMap!This`, `This[string]`, and `Annotated!This` types.
21 +/
22 alias transformLeafs(visitors...) = transformLeafsImpl!(visit, naryFun!visitors);
23 
24 ///
25 version(mir_test)
26 unittest
27 {
28     import mir.format: text;
29     import mir.algebraic_alias.json;
30     JsonAlgebraic value = ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, null.JsonAlgebraic].JsonAlgebraic];
31  
32     // converts all leavs to a text form
33     value.transformLeafs!text;
34     assert(value == ["key" : ["str".JsonAlgebraic, "2.32".JsonAlgebraic, "null".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic);
35     
36     value = ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, true.JsonAlgebraic].JsonAlgebraic].JsonAlgebraic;
37 
38     /// converts only bool values
39     value.transformLeafs!(
40         (bool b) => b.text,
41         v => v, // other values are copied as is
42     );
43 
44     assert(value == ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, "true".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic);
45 }
46 
47 /++
48 Behaves as $(LREF transformLeafs) but doesn't enforce at compile time that all types can be handled by the visiting functions.
49 
50 Throws: Exception if `naryFun!visitors` can't be called with provided arguments
51 +/
52 alias tryTransformLeafs(visitors...) = transformLeafsImpl!(tryVisit, naryFun!visitors);
53 
54 ///
55 version(mir_test)
56 unittest
57 {
58     import mir.format: text;
59     import mir.algebraic_alias.json;
60     JsonAlgebraic value = ["key" : [true.JsonAlgebraic, 100.JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic];
61  
62     // converts long and double numbers to a text form, bool values is converted to `long`
63     value.tryTransformLeafs!((double v) => v.text, (long v) => v.text);
64     assert(value == ["key" : ["1".JsonAlgebraic, "100".JsonAlgebraic, "2.32".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic);
65 }
66 
67 /++
68 Behaves as $(LREF transformLeafs) but doesn't enforce at compile time that all types can be handled by the visiting functions.
69 
70 The function ignores leafs that can't be handled by the visiting functions.
71 +/
72 alias optionalTransformLeafs(visitors...) = transformLeafsImpl!(optionalVisit, naryFun!visitors);
73 
74 ///
75 version(mir_test)
76 unittest
77 {
78     import mir.format: text;
79     import mir.algebraic_alias.json;
80     JsonAlgebraic value = ["key" : [null.JsonAlgebraic, true.JsonAlgebraic, 100.JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic];
81  
82     // converts long numbers to a text form, ignores other types
83     value.optionalTransformLeafs!(
84         (long v) => v.text,
85         (bool b) => b, // needs special overload for bool to get rid of implicit converion to long/double
86     );
87     assert(value == ["key" : [null.JsonAlgebraic, true.JsonAlgebraic, "100".JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic].JsonAlgebraic);
88 }
89 
90 ///
91 template transformLeafsImpl(alias handler, alias visitor)
92 {
93     ///
94     ref Algebraic!Types transformLeafsImpl(Types...)(ref return Algebraic!Types value)
95     {
96         import core.lifetime: move;
97         import mir.algebraic: visit;
98         import mir.annotated: Annotated;
99         import mir.string_map: StringMap;
100         alias T = Algebraic!Types;
101         static if (is(T.AllowedTypes[0] == typeof(null)))
102         {
103             enum nullCompiles = __traits(compiles, value = visitor(null));
104             static if (nullCompiles || __traits(isSame, handler, visit))
105             {
106                 alias nullHandler = (typeof(null)) {
107                     static if (nullCompiles)
108                         value = visitor(null);
109                     else
110                         assert(0, "Null " ~ T.stringof);
111                 };
112             }
113             else
114             {
115                 alias nullHandler = AliasSeq!();
116             }
117         }
118         else
119         {
120             alias nullHandler = AliasSeq!();
121         }
122         handler!(
123             (T[] v) {
124                 foreach (ref e; v)
125                    transformLeafsImpl(e);
126             },
127             (StringMap!T v) {
128                 foreach (ref e; v.values)
129                    transformLeafsImpl(e);
130             },
131             (T[string] v) {
132                 foreach (key, ref e; v)
133                    transformLeafsImpl(e);
134             },
135             (Annotated!T v) {
136                 transformLeafsImpl(v.value);
137             },
138             nullHandler,
139             (ref v) { // auto for typeof(null) support
140                 static if (__traits(compiles, value = visitor(move(v))))
141                     value = visitor(move(v));
142                 else
143                     value = visitor(v);
144             }
145         )(value);
146         return value;
147     }
148 
149     /// ditto
150     Algebraic!Types transformLeafsImpl(Types...)(Algebraic!Types value)
151     {
152         import core.lifetime: move;
153         transformLeafsImpl(value);
154         return move(value);
155     }
156 }