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 }