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