1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 A language independent AST specific for generating mutants both via the plain
11 source code manipulation but also mutant schematas.
12 */
13 module dextool.plugin.mutate.backend.analyze.ast;
14 
15 import logger = std.experimental.logger;
16 import std.algorithm : map, filter, among;
17 import std.array : Appender, appender, empty;
18 import std.exception : collectException;
19 import std.format : formattedWrite, format;
20 import std.meta : AliasSeq;
21 import std.range : isOutputRange;
22 
23 import my.optional;
24 import sumtype;
25 
26 import dextool.type : AbsolutePath, Path;
27 
28 static import dextool.plugin.mutate.backend.type;
29 
30 @safe:
31 
32 struct Ast {
33     import std.experimental.allocator.mallocator : Mallocator;
34     import my.alloc.class_;
35 
36     /// The language the mutation AST is based on.
37     dextool.plugin.mutate.backend.type.Language lang;
38 
39     Location[Node] locs;
40 
41     // a node can have a type
42     TypeId[Node] nodeTypes;
43     Types types;
44 
45     // a node can have been resolved to a symbolic value.
46     SymbolId[Node] nodeSymbols;
47     Symbols symbols;
48 
49     Node root;
50 
51     // Change the path and thus string in the saved locations to reduce the used memory.
52     Dedup!AbsolutePath paths;
53 
54     private {
55         Bundle!Mallocator bundle;
56     }
57 
58     ~this() nothrow {
59         release;
60     }
61 
62     T make(T, Args...)(auto ref Args args) {
63         import std.functional : forward;
64 
65         return bundle.make!T(forward!args);
66     }
67 
68     /// Release all nodes by destroying them and releasing the memory
69     void release() nothrow @trusted {
70         if (!bundle.empty) {
71             if (auto v = root in locs)
72                 logger.tracef("released AST for %s with objects %s", v.file,
73                         bundle.length).collectException;
74             else
75                 logger.tracef("released AST with %s objects", bundle.length).collectException;
76         }
77         bundle.release;
78 
79         paths = typeof(paths).init;
80         nodeTypes = null;
81         nodeSymbols = null;
82         locs = null;
83     }
84 
85     void releaseCache() {
86         paths.release;
87     }
88 
89     void accept(VisitorT)(VisitorT v) {
90         v.visit(root);
91     }
92 
93     void put(Node n, Location l) {
94         // TODO: deduplicate the path because it will otherwise take up so much
95         // memory.....
96         l.file = paths.dedup(l.file);
97         locs[n] = l;
98     }
99 
100     void put(Node n, TypeId id) {
101         nodeTypes[n] = id;
102     }
103 
104     void put(Node n, SymbolId id) {
105         nodeSymbols[n] = id;
106     }
107 
108     Location location(Node n) {
109         if (auto v = n in locs) {
110             return *v;
111         }
112         return Location.init;
113     }
114 
115     Type type(Node n) {
116         if (auto v = n in nodeTypes) {
117             return types.get(*v);
118         }
119         return null;
120     }
121 
122     Optional!TypeId typeId(Node n) {
123         if (auto v = n in nodeTypes) {
124             return some(*v);
125         }
126         return none!TypeId;
127     }
128 
129     Symbol symbol(Node n) {
130         if (auto v = n in nodeSymbols) {
131             return symbols.get(*v);
132         }
133         return null;
134     }
135 
136     string toString() @safe {
137         auto buf = appender!string;
138         toString(buf);
139         return buf.data;
140     }
141 
142     void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) {
143         import std.range : put;
144 
145         formattedWrite(w, "Source language: %s\n", lang);
146 
147         auto res = () @trusted {
148             auto dump = new AstPrintVisitor(&this);
149             this.accept(dump);
150             return dump.buf.data;
151         }();
152         put(w, res);
153 
154         put(w, "Types:");
155         put(w, "\n");
156         types.toString(w);
157         put(w, "Symbols:");
158         put(w, "\n");
159         symbols.toString(w);
160     }
161 }
162 
163 class AstPrintVisitor : DepthFirstVisitor {
164     import std.range : put, repeat;
165 
166     Appender!string buf;
167     int depth;
168     int prevDepth;
169     char[] indent;
170     Ast* ast;
171 
172     this(Ast* ast) {
173         this.ast = ast;
174     }
175 
176     void toBuf(Node n) {
177         import std.conv : to;
178         import colorlog : color, Color;
179 
180         if (depth == 0) {
181             indent = null;
182         } else if (depth == prevDepth) {
183         } else if (depth > prevDepth) {
184             const diff = (depth - prevDepth - 1) * 2;
185             if (indent.length == 0) {
186             } else if (indent[$ - 2] == '`') {
187                 indent[$ - 1] = ' ';
188                 indent[$ - 2] = ' ';
189             } else {
190                 indent[$ - 1] = ' ';
191             }
192 
193             foreach (_; 0 .. diff)
194                 indent ~= "  ";
195 
196             if (n.children.length <= 1)
197                 indent ~= "`-";
198             else
199                 indent ~= "|-";
200         } else {
201             const diff = (prevDepth - depth) * 2;
202             indent = indent[0 .. $ - diff];
203 
204             if (indent.length != 0 && indent[$ - 2 .. $] == "| ") {
205                 indent[$ - 1] = '-';
206             }
207         }
208         put(buf, indent);
209 
210         void printNode() {
211             auto bl = () {
212                 if (n.blacklist)
213                     return " blacklist".color(Color.magenta).toString;
214                 return "";
215             }();
216             auto schemaBl = () {
217                 if (n.schemaBlacklist)
218                     return " !schema".color(Color.magenta).toString;
219                 return "";
220             }();
221             formattedWrite(buf, "%s %s%s%s",
222                     n.kind.to!string.color(Color.lightGreen),
223                     n.id.to!string.color(Color.lightYellow), bl, schemaBl);
224         }
225 
226         void printTypeSymbol(Node n) {
227             if (auto tyId = n in ast.nodeTypes) {
228                 auto ty = ast.types.get(*tyId);
229                 formattedWrite(buf, " %X", tyId.value);
230             }
231             if (auto syId = n in ast.nodeSymbols) {
232                 auto sy = ast.symbols.get(*syId);
233                 formattedWrite(buf, " %X:%s", syId.value, sy.value);
234             }
235         }
236 
237         switch (n.kind) {
238         case Kind.Function:
239             printNode;
240             printTypeSymbol((cast(Function) n).return_);
241             break;
242         case Kind.VarDecl:
243             printNode;
244             if ((cast(VarDecl) n).isConst) {
245                 put(buf, " const");
246             }
247             break;
248         default:
249             printNode;
250             if (isExpression(n.kind)) {
251                 printTypeSymbol(n);
252             }
253         }
254 
255         if (auto tmp = cast(BinaryOp) n) {
256             if (tmp.lhs !is null)
257                 formattedWrite(buf, " l:%s", tmp.lhs.id);
258             if (tmp.rhs !is null)
259                 formattedWrite(buf, " r:%s", tmp.rhs.id);
260         }
261 
262         if (auto l = ast.location(n)) {
263             formattedWrite(buf, " <%s:%s>", l.file.color(Color.cyan), l.posToString);
264         }
265         put(buf, "\n");
266 
267         prevDepth = depth;
268     }
269 
270     static foreach (N; Nodes) {
271         override void visit(N n) {
272             toBuf(n);
273             ++depth;
274 
275             auto op = () @trusted {
276                 if (auto v = cast(BinaryOp) n) {
277                     return v.operator;
278                 } else if (auto v = cast(UnaryOp) n) {
279                     return v.operator;
280                 }
281                 return null;
282             }();
283             if (op !is null) {
284                 visit(op);
285             }
286 
287             accept(n, this);
288             --depth;
289         }
290     }
291 }
292 
293 /// The interval in bytes that the node occupy. It is a closed->open set.
294 alias Interval = dextool.plugin.mutate.backend.type.Offset;
295 alias SourceLoc = dextool.plugin.mutate.backend.type.SourceLoc;
296 alias SourceLocRange = dextool.plugin.mutate.backend.type.SourceLocRange;
297 
298 struct Location {
299     import std.range : isOutputRange;
300 
301     AbsolutePath file;
302     Interval interval;
303     SourceLocRange sloc;
304 
305     this(Path f, Interval i, SourceLocRange s) {
306         file = f;
307         interval = i;
308         sloc = s;
309     }
310 
311     T opCast(T : bool)() @safe pure const nothrow {
312         return !file.empty;
313     }
314 
315     // Convert only the position in the file to a string.
316     string posToString() @safe const {
317         return format!"[%s:%s-%s:%s]:[%s:%s]"(sloc.begin.line, sloc.begin.column,
318                 sloc.end.line, sloc.end.column, interval.begin, interval.end);
319     }
320 
321     string toString() @safe const {
322         auto buf = appender!string;
323         toString(buf);
324         return buf.data;
325     }
326 
327     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
328         import std.format : formattedWrite;
329 
330         formattedWrite!"%s:%s"(w, file, posToString);
331     }
332 }
333 
334 private ulong uniqueNodeId() {
335     static ulong next;
336     return next++;
337 }
338 
339 abstract class Node {
340     Kind kind() const;
341     ulong id() @safe pure nothrow const @nogc;
342 
343     Node[] children;
344 
345     /** If the node is blacklisted from being mutated. This is for example when
346      * the node covers a C macro.
347      */
348     bool blacklist;
349 
350     /** If the node should not be part of mutant schemata because it is highly
351      * likely to introduce compilation errors. It is for example likely when
352      * operators are overloaded.
353      */
354     bool schemaBlacklist;
355 
356     bool opEquals(Kind k) {
357         return kind == k;
358     }
359 
360     override bool opEquals(Object o) {
361         auto rhs = cast(const Node) o;
362         return (rhs && (id == rhs.id));
363     }
364 
365     override size_t toHash() @safe pure nothrow const @nogc scope {
366         return id.hashOf();
367     }
368 }
369 
370 /**
371  * It is optional to add the members visitPush/visitPop to push/pop the nodes that are visited.
372  * The parent will always have been the last pushed.
373  */
374 void accept(VisitorT)(Node n, VisitorT v) {
375     static string mixinSwitch() {
376         import std.conv : text;
377         import std.traits : EnumMembers;
378 
379         string s;
380         s ~= "final switch(c.kind) {\n";
381         foreach (kind; [EnumMembers!Kind]) {
382             const k = text(kind);
383             s ~= format!"case Kind." ~ k ~ ": v.visit(cast(" ~ k ~ ") c); break;\n";
384         }
385         s ~= "}";
386         return s;
387     }
388 
389     static if (__traits(hasMember, VisitorT, "visitPush"))
390         v.visitPush(n);
391     foreach (c; n.children) {
392         mixin(mixinSwitch);
393     }
394 
395     static if (__traits(hasMember, VisitorT, "visitPop"))
396         v.visitPop(n);
397 }
398 
399 /// All nodes that a visitor must be able to handle.
400 // must be sorted such that the leaf nodes are at the top.
401 // dfmt off
402 alias Nodes = AliasSeq!(
403     BinaryOp,
404     Block,
405     Branch,
406     BranchBundle,
407     Call,
408     Condition,
409     Constructor,
410     Expr,
411     Function,
412     Loop,
413     Node,
414     OpAdd,
415     OpAnd,
416     OpAndBitwise,
417     OpAssign,
418     OpAssignAdd,
419     OpAssignAndBitwise,
420     OpAssignDiv,
421     OpAssignMod,
422     OpAssignMul,
423     OpAssignOrBitwise,
424     OpAssignSub,
425     OpDiv,
426     OpEqual,
427     OpGreater,
428     OpGreaterEq,
429     OpLess,
430     OpLessEq,
431     OpMod,
432     OpMul,
433     OpNegate,
434     OpNotEqual,
435     OpOr,
436     OpOrBitwise,
437     OpSub,
438     Operator,
439     Poision,
440     Return,
441     Statement,
442     TranslationUnit,
443     UnaryOp,
444     VarDecl,
445     VarRef,
446 );
447 
448 // It should be possible to generate the enum from Nodes. How?
449 enum Kind {
450     BinaryOp,
451     Block,
452     Branch,
453     BranchBundle,
454     Call,
455     Condition,
456     Constructor,
457     Expr,
458     Function,
459     Loop,
460     Node,
461     OpAdd,
462     OpAnd,
463     OpAndBitwise,
464     OpAssign,
465     OpAssignAdd,
466     OpAssignAndBitwise,
467     OpAssignDiv,
468     OpAssignMod,
469     OpAssignMul,
470     OpAssignOrBitwise,
471     OpAssignSub,
472     OpDiv,
473     OpEqual,
474     OpGreater,
475     OpGreaterEq,
476     OpLess,
477     OpLessEq,
478     OpMod,
479     OpMul,
480     OpNegate,
481     OpNotEqual,
482     OpOr,
483     OpOrBitwise,
484     OpSub,
485     Operator,
486     Poision,
487     Return,
488     Statement,
489     TranslationUnit,
490     UnaryOp,
491     VarDecl,
492     VarRef,
493 }
494 
495 alias ExpressionKind = AliasSeq!(
496     Kind.BinaryOp,
497     Kind.Call,
498     Kind.Condition,
499     Kind.Constructor,
500     Kind.Expr,
501     Kind.OpAdd,
502     Kind.OpAnd,
503     Kind.OpAndBitwise,
504     Kind.OpAssign,
505     Kind.OpDiv,
506     Kind.OpEqual,
507     Kind.OpGreater,
508     Kind.OpGreaterEq,
509     Kind.OpLess,
510     Kind.OpLessEq,
511     Kind.OpMod,
512     Kind.OpMul,
513     Kind.OpNegate,
514     Kind.OpNotEqual,
515     Kind.OpOr,
516     Kind.OpOrBitwise,
517     Kind.OpSub,
518     Kind.Return,
519     Kind.UnaryOp,
520     Kind.VarDecl,
521     Kind.VarRef,
522 );
523 // dfmt on
524 
525 bool isExpression(Kind k) @safe pure nothrow @nogc {
526     return k.among(ExpressionKind) != 0;
527 }
528 
529 interface Visitor {
530     static foreach (N; Nodes) {
531         void visit(N);
532     }
533 }
534 
535 // TODO: implement a breath first.
536 class DepthFirstVisitor : Visitor {
537     int visitDepth;
538 
539     void visitPush(Node n) {
540     }
541 
542     void visitPop(Node n) {
543     }
544 
545     static foreach (N; Nodes) {
546         override void visit(N n) {
547             ++visitDepth;
548             accept(n, this);
549             --visitDepth;
550         }
551     }
552 }
553 
554 /** A phantom node that carry semantic information about its children. It
555  * "poisons" all children.
556  */
557 class Poision : Node {
558     mixin(nodeImpl!(typeof(this)));
559 }
560 
561 class TranslationUnit : Node {
562     mixin(nodeImpl!(typeof(this)));
563 }
564 
565 class Statement : Node {
566     mixin(nodeImpl!(typeof(this)));
567 }
568 
569 class Loop : Node {
570     mixin(nodeImpl!(typeof(this)));
571 }
572 
573 class Expr : Node {
574     mixin(nodeImpl!(typeof(this)));
575 }
576 
577 /// A function definition.
578 class Function : Node {
579     mixin(nodeImpl!(typeof(this)));
580 
581     /// If the function has a return type it is associated with this expression.
582     Return return_;
583 }
584 
585 /// A constructor for a variable.
586 class Constructor : Expr {
587     mixin(nodeImpl!(typeof(this)));
588 }
589 
590 /// A function call.
591 class Call : Expr {
592     mixin(nodeImpl!(typeof(this)));
593 }
594 
595 /// The operator itself in a binary operator expression.
596 class Operator : Node {
597     mixin(nodeImpl!(typeof(this)));
598 }
599 
600 /** A block of code such such as a local scope enclosed by brackets, `{}`.
601  *
602  * It is intended to be possible to delete it. But it may need to be further
603  * analyzed for e.g. `Return` nodes.
604  */
605 class Block : Node {
606     mixin(nodeImpl!(typeof(this)));
607 }
608 
609 /** Multiple branches are contained in the bundle that can be e.g. deleted.
610  *
611  * This can, in C/C++, be either a whole if-statement or switch.
612  */
613 class BranchBundle : Node {
614     mixin(nodeImpl!(typeof(this)));
615 }
616 
617 /** The code for one of the branches resulting from a condition.
618  *
619  * It can be the branches in a if-else statement or a case branch for languages
620  * such as C/C++.
621  *
622  * The important aspect is that the branch is not an expression. It can't be
623  * evaluated to a value of a type.
624  */
625 class Branch : Node {
626     mixin(nodeImpl!(typeof(this)));
627 
628     // The inside of a branch node wherein code can be injected.
629     Block inside;
630 }
631 
632 /// Results in the bottom type or up.
633 class Return : Expr {
634     mixin(nodeImpl!(typeof(this)));
635 }
636 
637 /// A condition wraps "something" which always evaluates to a boolean.
638 class Condition : Expr {
639     mixin(nodeImpl!(typeof(this)));
640 }
641 
642 class VarDecl : Expr {
643     mixin(nodeImpl!(typeof(this)));
644     bool isConst;
645 }
646 
647 class VarRef : Expr {
648     mixin(nodeImpl!(typeof(this)));
649     // should always refer to something
650     VarDecl to;
651 
652     this(VarDecl to) {
653         this();
654         this.to = to;
655     }
656 
657     invariant {
658         assert(to !is null);
659     }
660 }
661 
662 class UnaryOp : Expr {
663     mixin(nodeImpl!(typeof(this)));
664 
665     Operator operator;
666     Expr expr;
667 
668     this(Operator op, Expr expr) {
669         this();
670         this.operator = op;
671         this.expr = expr;
672     }
673 }
674 
675 class OpNegate : UnaryOp {
676     mixin(nodeImpl!(typeof(this)));
677 }
678 
679 class BinaryOp : Expr {
680     mixin(nodeImpl!(typeof(this)));
681 
682     Operator operator;
683     Expr lhs;
684     Expr rhs;
685 
686     this(Operator op, Expr lhs, Expr rhs) {
687         this();
688         this.operator = op;
689         this.lhs = lhs;
690         this.rhs = rhs;
691     }
692 }
693 
694 class OpAssign : BinaryOp {
695     mixin(nodeImpl!(typeof(this)));
696 }
697 
698 class OpAssignAdd : BinaryOp {
699     mixin(nodeImpl!(typeof(this)));
700 }
701 
702 class OpAssignSub : BinaryOp {
703     mixin(nodeImpl!(typeof(this)));
704 }
705 
706 class OpAssignMul : BinaryOp {
707     mixin(nodeImpl!(typeof(this)));
708 }
709 
710 class OpAssignDiv : BinaryOp {
711     mixin(nodeImpl!(typeof(this)));
712 }
713 
714 class OpAssignMod : BinaryOp {
715     mixin(nodeImpl!(typeof(this)));
716 }
717 
718 class OpAssignAndBitwise : BinaryOp {
719     mixin(nodeImpl!(typeof(this)));
720 }
721 
722 class OpAssignOrBitwise : BinaryOp {
723     mixin(nodeImpl!(typeof(this)));
724 }
725 
726 class OpAdd : BinaryOp {
727     mixin(nodeImpl!(typeof(this)));
728 }
729 
730 class OpSub : BinaryOp {
731     mixin(nodeImpl!(typeof(this)));
732 }
733 
734 class OpMul : BinaryOp {
735     mixin(nodeImpl!(typeof(this)));
736 }
737 
738 class OpDiv : BinaryOp {
739     mixin(nodeImpl!(typeof(this)));
740 }
741 
742 class OpMod : BinaryOp {
743     mixin(nodeImpl!(typeof(this)));
744 }
745 
746 class OpAnd : BinaryOp {
747     mixin(nodeImpl!(typeof(this)));
748 }
749 
750 class OpAndBitwise : BinaryOp {
751     mixin(nodeImpl!(typeof(this)));
752 }
753 
754 class OpOr : BinaryOp {
755     mixin(nodeImpl!(typeof(this)));
756 }
757 
758 class OpOrBitwise : BinaryOp {
759     mixin(nodeImpl!(typeof(this)));
760 }
761 
762 class OpEqual : BinaryOp {
763     mixin(nodeImpl!(typeof(this)));
764 }
765 
766 class OpLess : BinaryOp {
767     mixin(nodeImpl!(typeof(this)));
768 }
769 
770 class OpGreater : BinaryOp {
771     mixin(nodeImpl!(typeof(this)));
772 }
773 
774 class OpLessEq : BinaryOp {
775     mixin(nodeImpl!(typeof(this)));
776 }
777 
778 class OpGreaterEq : BinaryOp {
779     mixin(nodeImpl!(typeof(this)));
780 }
781 
782 class OpNotEqual : BinaryOp {
783     mixin(nodeImpl!(typeof(this)));
784 }
785 
786 RetT makeId(RetT, T)(T data) {
787     import my.hash : makeCrc64Iso;
788 
789     auto a = makeCrc64Iso(cast(const(ubyte)[]) data);
790     return RetT(a.c0);
791 }
792 
793 RetT makeUniqueId(RetT)() {
794     import std.random : uniform;
795 
796     return RetT(uniform(long.min, long.max));
797 }
798 
799 class Type {
800     private const ulong id_;
801 
802     Range range;
803 
804     this() {
805         this(Range.makeInf);
806     }
807 
808     this(Range r) {
809         this.range = r;
810         id_ = uniqueNodeId;
811     }
812 
813     TypeKind kind() const {
814         return TypeKind.bottom;
815     }
816 
817     ulong id() @safe pure nothrow const @nogc {
818         return id_;
819     }
820 
821     override bool opEquals(Object o) {
822         auto rhs = cast(const Type) o;
823         return (rhs && (id == rhs.id));
824     }
825 
826     override size_t toHash() @safe pure nothrow const @nogc scope {
827         return id.hashOf();
828     }
829 }
830 
831 final class DiscreteType : Type {
832     this(Range r) {
833         super(r);
834     }
835 
836     override TypeKind kind() const {
837         return TypeKind.discrete;
838     }
839 }
840 
841 final class ContinuesType : Type {
842     this(Range r) {
843         super(r);
844     }
845 
846     override TypeKind kind() const {
847         return TypeKind.continues;
848     }
849 }
850 
851 final class UnorderedType : Type {
852     this(Range r) {
853         super(r);
854     }
855 
856     override TypeKind kind() const {
857         return TypeKind.unordered;
858     }
859 }
860 
861 final class BooleanType : Type {
862     this(Range r) {
863         super(r);
864     }
865 
866     override TypeKind kind() const {
867         return TypeKind.boolean;
868     }
869 }
870 
871 final class VoidType : Type {
872     this() {
873         super();
874     }
875 
876     override TypeKind kind() const {
877         return TypeKind.top;
878     }
879 }
880 
881 enum TypeKind {
882     // It can be anything, practically useless for mutation testing because it
883     // doesn't provide any logic that can be used to e.g. generate
884     // "intelligent" ROR mutants.
885     bottom,
886     /// integers, enums
887     discrete,
888     /// floating point values
889     continues,
890     /// no order exists between values in the type thus unable to do ROR
891     unordered,
892     ///
893     boolean,
894     /// a top type is nothing
895     top,
896 }
897 
898 struct Value {
899     import std.traits : TemplateArgsOf;
900 
901     static struct NegInf {
902     }
903 
904     static struct PosInf {
905     }
906 
907     static struct Int {
908         // TODO: change to BigInt?
909         long value;
910     }
911 
912     static struct Bool {
913         bool value;
914     }
915 
916     alias Value = SumType!(NegInf, PosInf, Int, Bool);
917     Value value;
918 
919     static foreach (T; TemplateArgsOf!Value) {
920         this(T a) {
921             value = Value(a);
922         }
923     }
924 
925     string toString() @safe pure const {
926         auto buf = appender!string;
927         toString(buf);
928         return buf.data;
929     }
930 
931     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
932         import std.conv : to;
933         import std.range : put;
934 
935         value.match!((const NegInf a) => put(w, "-inf"), (const PosInf a) => put(w,
936                 "+inf"), (const Int a) => put(w, a.value.to!string),
937                 (const Bool a) => put(w, a.value.to!string));
938     }
939 }
940 
941 struct Range {
942     static makeInf() {
943         return Range(Value(Value.NegInf.init), Value(Value.PosInf.init));
944     }
945 
946     static makeBoolean() {
947         return Range(Value(Value.Bool(false)), Value(Value.Bool(true)));
948     }
949 
950     Value low;
951     Value up;
952 
953     this(Value low, Value up) {
954         this.low = low;
955         this.up = up;
956     }
957 
958     enum CompareResult {
959         onLowerBound,
960         onUpperBound,
961         // the value and the range fully overlap each other. This happens when
962         // the range is only one value.
963         overlap,
964         inside,
965         outside
966     }
967 
968     CompareResult compare(Value v) {
969         CompareResult negInf() {
970             return low.value.match!((Value.NegInf a) => CompareResult.onLowerBound,
971                     (Value.PosInf a) => CompareResult.outside,
972                     (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside);
973         }
974 
975         CompareResult posInf() {
976             return up.value.match!((Value.NegInf a) => CompareResult.onUpperBound,
977                     (Value.PosInf a) => CompareResult.outside,
978                     (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside);
979         }
980 
981         CompareResult value(long v) {
982             const l = low.value.match!((Value.NegInf a) => CompareResult.inside,
983                     (Value.PosInf a) => CompareResult.outside, (Value.Int a) {
984                 if (a.value < v)
985                     return CompareResult.inside;
986                 if (a.value == v)
987                     return CompareResult.onLowerBound;
988                 return CompareResult.outside;
989             }, (Value.Bool a) => CompareResult.outside);
990 
991             const u = up.value.match!((Value.NegInf a) => CompareResult.outside,
992                     (Value.PosInf a) => CompareResult.inside, (Value.Int a) {
993                 if (a.value > v)
994                     return CompareResult.inside;
995                 if (a.value == v)
996                     return CompareResult.onUpperBound;
997                 return CompareResult.outside;
998             }, (Value.Bool a) => CompareResult.outside);
999 
1000             if (l == CompareResult.inside && u == CompareResult.inside)
1001                 return CompareResult.inside;
1002             if (l == CompareResult.onLowerBound && u == CompareResult.onUpperBound)
1003                 return CompareResult.overlap;
1004             if (l == CompareResult.onLowerBound)
1005                 return l;
1006             if (u == CompareResult.onUpperBound)
1007                 return u;
1008             assert(l == CompareResult.outside || u == CompareResult.outside);
1009             return CompareResult.outside;
1010         }
1011 
1012         CompareResult boolean(bool v) {
1013             // TODO: fix this
1014             return CompareResult.outside;
1015         }
1016 
1017         return v.value.match!((Value.NegInf a) => negInf,
1018                 (Value.PosInf a) => posInf, (Value.Int a) => value(a.value),
1019                 (Value.Bool a) => boolean(a.value));
1020     }
1021 
1022     string toString() @safe pure const {
1023         auto buf = appender!string;
1024         toString(buf);
1025         return buf.data;
1026     }
1027 
1028     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
1029         import std.range : put;
1030 
1031         put(w, "[");
1032         low.toString(w);
1033         put(w, ":");
1034         up.toString(w);
1035         put(w, "]");
1036     }
1037 }
1038 
1039 struct TypeId {
1040     ulong value;
1041 
1042     size_t toHash() @safe pure nothrow const @nogc {
1043         return value.hashOf;
1044     }
1045 }
1046 
1047 TypeId makeTypeId(T)(T data) {
1048     return makeId!TypeId(data);
1049 }
1050 
1051 TypeId makeUniqueTypeId() {
1052     return makeUniqueId!TypeId;
1053 }
1054 
1055 struct Types {
1056     Type[TypeId] types;
1057 
1058     void require(TypeId id, Type t) {
1059         if (id !in types) {
1060             types[id] = t;
1061         }
1062     }
1063 
1064     void set(TypeId id, Type t) {
1065         types[id] = t;
1066     }
1067 
1068     Type get(TypeId id) {
1069         if (auto v = id in types) {
1070             return *v;
1071         }
1072         return null;
1073     }
1074 
1075     bool hasId(TypeId id) {
1076         return (id in types) !is null;
1077     }
1078 
1079     string toString() @safe const {
1080         auto buf = appender!string;
1081         toString(buf);
1082         return buf.data;
1083     }
1084 
1085     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
1086         import std.format : formattedWrite;
1087         import std.range : put;
1088 
1089         foreach (kv; types.byKeyValue) {
1090             formattedWrite(w, "%X:%s:%s", kv.key.value, kv.value.kind, kv.value.range);
1091             put(w, "\n");
1092         }
1093     }
1094 }
1095 
1096 class Symbol {
1097     Value value;
1098 
1099     this() {
1100         this(Value(Value.PosInf.init));
1101     }
1102 
1103     this(Value v) {
1104         this.value = v;
1105     }
1106 
1107     SymbolKind kind() const {
1108         return SymbolKind.unknown;
1109     }
1110 }
1111 
1112 final class DiscretSymbol : Symbol {
1113     this(Value r) {
1114         super(r);
1115     }
1116 
1117     override SymbolKind kind() const {
1118         return SymbolKind.discret;
1119     }
1120 }
1121 
1122 final class ContinuesSymbol : Symbol {
1123     this(Value r) {
1124         super(r);
1125     }
1126 
1127     override SymbolKind kind() const {
1128         return SymbolKind.continues;
1129     }
1130 }
1131 
1132 final class BooleanSymbol : Symbol {
1133     this(Value r) {
1134         super(r);
1135     }
1136 
1137     override SymbolKind kind() const {
1138         return SymbolKind.boolean;
1139     }
1140 }
1141 
1142 enum SymbolKind {
1143     /// the symbol wasn't able to evaluate to something useful
1144     unknown,
1145     /// integers, enums
1146     discret,
1147     /// floating point values
1148     continues,
1149     ///
1150     boolean,
1151 }
1152 
1153 struct SymbolId {
1154     ulong value;
1155 
1156     size_t toHash() @safe pure nothrow const @nogc {
1157         return value.hashOf;
1158     }
1159 }
1160 
1161 SymbolId makeSymbolId(T)(T data) {
1162     return makeId!SymbolId(data);
1163 }
1164 
1165 SymbolId makeUniqueSymbolId() {
1166     return makeUniqueId!SymbolId;
1167 }
1168 
1169 struct Symbols {
1170     Symbol[SymbolId] symbols;
1171 
1172     void require(SymbolId id, Symbol s) {
1173         if (id !in symbols) {
1174             symbols[id] = s;
1175         }
1176     }
1177 
1178     void set(SymbolId id, Symbol s) {
1179         symbols[id] = s;
1180     }
1181 
1182     Symbol get(SymbolId id) {
1183         if (auto v = id in symbols) {
1184             return *v;
1185         }
1186         return null;
1187     }
1188 
1189     bool hasId(SymbolId id) {
1190         return (id in symbols) !is null;
1191     }
1192 
1193     string toString() @safe const {
1194         auto buf = appender!string;
1195         toString(buf);
1196         return buf.data;
1197     }
1198 
1199     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
1200         foreach (kv; symbols.byKeyValue) {
1201             formattedWrite(w, "%X:%s:%s\n", kv.key.value, kv.value.kind, kv.value.value);
1202         }
1203     }
1204 }
1205 
1206 /// Breath first visit of nodes.
1207 struct BreathFirstRange {
1208     import my.container.vector;
1209 
1210     Vector!Node nodes;
1211 
1212     this(Node n) {
1213         nodes.put(n);
1214     }
1215 
1216     Node front() @safe pure nothrow {
1217         assert(!empty, "Can't get front of an empty range");
1218         return nodes.front;
1219     }
1220 
1221     void popFront() @safe pure nothrow {
1222         assert(!empty, "Can't pop front of an empty range");
1223         nodes.put(nodes.front.children);
1224         nodes.popFront;
1225     }
1226 
1227     bool empty() @safe pure nothrow const @nogc {
1228         return nodes.empty;
1229     }
1230 }
1231 
1232 private:
1233 
1234 string nodeImpl(T)() {
1235     return `
1236     private const ulong id_;
1237 
1238     override ulong id() @safe pure nothrow const @nogc { return id_; }
1239 
1240     override Kind kind() const {
1241         return Kind.` ~ T.stringof ~ `;
1242     }
1243 
1244     this() {
1245         id_ = uniqueNodeId;
1246     }`;
1247 }
1248 
1249 string typeImpl() {
1250     return `
1251     private const ulong id_;
1252     override ulong id() @safe pure nothrow const @nogc { return id_; }
1253     this() {
1254         id_ = uniqueNodeId;
1255     }`;
1256 }
1257 
1258 /// Dedup the paths to reduce the required memory.
1259 struct Dedup(T) {
1260     T[ulong] cache;
1261     /// Number of deduplications.
1262     long count;
1263     /// ".length" accumulated of items deduplicated.
1264     long lengthAccum;
1265 
1266     T dedup(T p) {
1267         import std.traits : hasMember;
1268 
1269         const cs = p.toHash;
1270         if (auto v = cs in cache) {
1271             ++count;
1272             static if (hasMember!(T, "length"))
1273                 lengthAccum += p.length;
1274             return *v;
1275         }
1276 
1277         cache[cs] = p;
1278         return p;
1279     }
1280 
1281     void release() {
1282         cache = typeof(cache).init;
1283     }
1284 }