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