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