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 }