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 }