1 /**
2   * Additional functions for working with UniNode library
3   *
4   * Copyright:
5   *     Copyright (c) 2018, Maxim Tyapkin.
6   * Authors:
7   *     Maxim Tyapkin
8   * License:
9   *     This software is licensed under the terms of the BSD 3-clause license.
10   *     The full terms of the license can be found in the LICENSE.md file.
11   */
12 
13 module djinja.uninode;
14 
15 public
16 {
17     import uninode.core;
18     import uninode.serialization :
19                 serialize = serializeToUniNode,
20                 deserialize = deserializeUniNode;
21 }
22 
23 private
24 {
25     import std.array : array;
26     import std.algorithm : among, map, sort;
27     import std.conv : to;
28     import std.format: fmt = format;
29     import std.typecons : Tuple, tuple;
30 
31     import djinja.lexer;
32     import djinja.exception : JinjaRenderException,
33                               assertJinja = assertJinjaRender;
34 }
35 
36 
37 bool isNumericNode(ref UniNode n)
38 {
39     return cast(bool)n.kind.among!(
40             UniNode.Kind.integer,
41             UniNode.Kind.uinteger,
42             UniNode.Kind.floating
43         );
44 }
45 
46 
47 bool isIntNode(ref UniNode n)
48 {
49     return cast(bool)n.kind.among!(
50             UniNode.Kind.integer,
51             UniNode.Kind.uinteger
52         );
53 }
54 
55 
56 bool isFloatNode(ref UniNode n)
57 {
58     return n.kind == UniNode.Kind.floating;
59 }
60 
61 
62 bool isIterableNode(ref UniNode n)
63 {
64     return cast(bool)n.kind.among!(
65             UniNode.Kind.array,
66             UniNode.Kind.object,
67             UniNode.Kind.text
68         );
69 }
70 
71 void toIterableNode(ref UniNode n)
72 {
73     switch (n.kind) with (UniNode.Kind)
74     {
75         case array:
76             return;
77         case text:
78             n = UniNode(n.get!string.map!(a => UniNode(cast(string)[a])).array);
79             return;
80         case object:
81             UniNode[] arr;
82             foreach (key, val; n.get!(UniNode[string]))
83                 arr ~= UniNode([UniNode(key), val]);
84             n = UniNode(arr);
85             return;
86         default:
87             throw new JinjaRenderException("Can't implicity convert type %s to iterable".fmt(n.kind));
88     }
89 }
90 
91 void toCommonNumType(ref UniNode n1, ref UniNode n2)
92 {
93     assertJinja(n1.isNumericNode, "Not a numeric type of %s".fmt(n1));
94     assertJinja(n2.isNumericNode, "Not a numeric type of %s".fmt(n2));
95 
96     if (n1.isIntNode && n2.isFloatNode)
97     {
98         n1 = UniNode(n1.get!long.to!double);
99         return;
100     }
101 
102     if (n1.isFloatNode && n2.isIntNode)
103     {
104         n2 = UniNode(n2.get!long.to!double);
105         return;
106     }
107 }
108 
109 
110 void toCommonCmpType(ref UniNode n1, ref UniNode n2)
111 {
112    if (n1.isNumericNode && n2.isNumericNode)
113    {
114        toCommonNumType(n1, n2);
115        return;
116    }
117    if (n1.kind != n2.kind)
118        throw new JinjaRenderException("Not comparable types %s and %s".fmt(n1.kind, n2.kind));
119 }
120 
121 
122 void toBoolType(ref UniNode n)
123 {
124     switch (n.kind) with (UniNode.Kind)
125     {
126         case boolean:
127             return;
128         case integer:
129         case uinteger:
130             n = UniNode(n.get!long != 0);
131             return;
132         case floating:
133             n = UniNode(n.get!double != 0);
134             return;
135         case text:
136             n = UniNode(n.get!string.length > 0);
137             return;
138         case array:
139         case object:
140             n = UniNode(n.length > 0);
141             return;
142         case nil:
143             n = UniNode(false);
144             return;
145         default:
146             throw new JinjaRenderException("Can't cast type %s to bool".fmt(n.kind));
147     }
148 }
149 
150 
151 void toStringType(ref UniNode n)
152 {
153     import std.algorithm : map;
154     import std..string : join;
155 
156     string getString(UniNode n)
157     {
158         bool quotes = n.kind == UniNode.Kind.text;
159         n.toStringType;
160         if (quotes)
161             return "'" ~ n.get!string ~ "'";
162         else
163             return n.get!string;
164     }
165 
166     string doSwitch()
167     {
168         final switch (n.kind) with (UniNode.Kind)
169         {
170             case nil:      return "";
171             case boolean:  return n.get!bool.to!string;
172             case integer:  return n.get!long.to!string;
173             case uinteger: return n.get!ulong.to!string;
174             case floating: return n.get!double.to!string;
175             case text:     return n.get!string;
176             case raw:      return n.get!(ubyte[]).to!string;
177             case array:    return "["~n.get!(UniNode[]).map!(a => getString(a)).join(", ").to!string~"]";
178             case object:
179                 string[] results;
180                 Tuple!(string, UniNode)[] sorted = [];
181                 foreach (string key, ref value; n)
182                     results ~= key ~ ": " ~ getString(value);
183                 return "{" ~ results.join(", ").to!string ~ "}";
184         }
185     }
186 
187     n = UniNode(doSwitch());
188 }
189 
190 
191 string getAsString(UniNode n)
192 {
193     n.toStringType;
194     return n.get!string;
195 }
196 
197 
198 void checkNodeType(ref UniNode n, UniNode.Kind kind, Position pos)
199 {
200     if (n.kind != kind)
201         assertJinja(0, "Unexpected expression type `%s`, expected `%s`".fmt(n.kind, kind), pos);
202 }
203 
204 
205 
206 UniNode unary(string op)(UniNode lhs)
207     if (op.among!(Operator.Plus,
208                  Operator.Minus)
209     )
210 {
211     assertJinja(lhs.isNumericNode, "Expected int got %s".fmt(lhs.kind));
212 
213     if (lhs.isIntNode)
214         return UniNode(mixin(op ~ "lhs.get!long"));
215     else
216         return UniNode(mixin(op ~ "lhs.get!double"));
217 }
218 
219 
220 
221 UniNode unary(string op)(UniNode lhs)
222     if (op == Operator.Not)
223 {
224     lhs.toBoolType;
225     return UniNode(!lhs.get!bool);
226 }
227 
228 
229 
230 UniNode binary(string op)(UniNode lhs, UniNode rhs)
231     if (op.among!(Operator.Plus,
232                  Operator.Minus,
233                  Operator.Mul)
234     )
235 {
236     toCommonNumType(lhs, rhs);
237     if (lhs.isIntNode)
238         return UniNode(mixin("lhs.get!long" ~ op ~ "rhs.get!long"));
239     else
240         return UniNode(mixin("lhs.get!double" ~ op ~ "rhs.get!double"));
241 }
242 
243 
244 
245 UniNode binary(string op)(UniNode lhs, UniNode rhs)
246     if (op == Operator.DivInt)
247 {
248     assertJinja(lhs.isIntNode, "Expected int got %s".fmt(lhs.kind));
249     assertJinja(rhs.isIntNode, "Expected int got %s".fmt(rhs.kind));
250     return UniNode(lhs.get!long / rhs.get!long);
251 }
252 
253 
254 
255 UniNode binary(string op)(UniNode lhs, UniNode rhs)
256     if (op == Operator.DivFloat
257         || op == Operator.Rem)
258 {
259     toCommonNumType(lhs, rhs);
260 
261     if (lhs.isIntNode)
262     {
263         assertJinja(rhs.get!long != 0, "Division by zero!");
264         return UniNode(mixin("lhs.get!long" ~ op ~ "rhs.get!long"));
265     }
266     else
267     {
268         assertJinja(rhs.get!double != 0, "Division by zero!");
269         return UniNode(mixin("lhs.get!double" ~ op ~ "rhs.get!double"));
270     }
271 }
272 
273 
274 
275 UniNode binary(string op)(UniNode lhs, UniNode rhs)
276     if (op == Operator.Pow)
277 {
278     toCommonNumType(lhs, rhs);
279     if (lhs.isIntNode)
280         return UniNode(lhs.get!long ^^ rhs.get!long);
281     else
282         return UniNode(lhs.get!double ^^ rhs.get!double);
283 }
284 
285 
286 
287 UniNode binary(string op)(UniNode lhs, UniNode rhs)
288     if (op.among!(Operator.Eq, Operator.NotEq))
289 {
290     toCommonCmpType(lhs, rhs);
291     return UniNode(mixin("lhs" ~ op ~ "rhs"));
292 }
293 
294 
295 
296 UniNode binary(string op)(UniNode lhs, UniNode rhs)
297     if (op.among!(Operator.Less,
298                   Operator.LessEq,
299                   Operator.Greater,
300                   Operator.GreaterEq)
301        )
302 {
303     toCommonCmpType(lhs, rhs);
304     switch (lhs.kind) with (UniNode.Kind)
305     {
306         case integer:
307         case uinteger:
308             return UniNode(mixin("lhs.get!long" ~ op ~ "rhs.get!long"));
309         case floating:
310             return UniNode(mixin("lhs.get!double" ~ op ~ "rhs.get!double"));
311         case text:
312             return UniNode(mixin("lhs.get!string" ~ op ~ "rhs.get!string"));
313         default:
314             throw new JinjaRenderException("Not comparable type %s".fmt(lhs.kind));
315     }
316 }
317 
318 
319 
320 UniNode binary(string op)(UniNode lhs, UniNode rhs)
321     if (op == Operator.Or)
322 {
323     lhs.toBoolType;
324     rhs.toBoolType;
325     return UniNode(lhs.get!bool || rhs.get!bool);
326 }
327 
328 
329 
330 UniNode binary(string op)(UniNode lhs, UniNode rhs)
331     if (op == Operator.And)
332 {
333     lhs.toBoolType;
334     rhs.toBoolType;
335     return UniNode(lhs.get!bool && rhs.get!bool);
336 }
337 
338 
339 
340 UniNode binary(string op)(UniNode lhs, UniNode rhs)
341     if (op == Operator.Concat)
342 {
343     lhs.toStringType;
344     rhs.toStringType;
345     return UniNode(lhs.get!string ~ rhs.get!string);
346 }
347 
348 
349 
350 UniNode binary(string op)(UniNode lhs, UniNode rhs)
351     if (op == Operator.In)
352 {
353     import std.algorithm.searching : countUntil;
354 
355     switch (rhs.kind) with (UniNode.Kind)
356     {
357         case array:
358             foreach(val; rhs)
359             {
360                 if (val == lhs)
361                     return UniNode(true);
362             }
363             return UniNode(false);
364         case object:
365             if (lhs.kind != UniNode.Kind.text)
366                 return UniNode(false);
367             return UniNode(cast(bool)(lhs.get!string in rhs));
368         case text:
369             if (lhs.kind != UniNode.Kind.text)
370                 return UniNode(false);
371             return UniNode(rhs.get!string.countUntil(lhs.get!string) >= 0);
372         default:
373             return UniNode(false);
374     }
375 }