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 n.accept(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.format : format; 276 import std.traits : EnumMembers; 277 278 string s; 279 s ~= "final switch(c.kind) {\n"; 280 foreach (k; [EnumMembers!Kind]) { 281 s ~= format!"case Kind.%1$s: v.visit(cast(%1$s) c); break;\n"(k); 282 } 283 s ~= "}"; 284 return s; 285 } 286 287 static if (__traits(hasMember, VisitorT, "visitPush")) 288 v.visitPush(n); 289 foreach (c; n.children) { 290 mixin(mixinSwitch); 291 } 292 293 static if (__traits(hasMember, VisitorT, "visitPop")) 294 v.visitPop(); 295 } 296 297 /// All nodes that a visitor must be able to handle. 298 // must be sorted such that the leaf nodes are at the top. 299 // dfmt off 300 alias Nodes = AliasSeq!( 301 BinaryOp, 302 Block, 303 Branch, 304 Call, 305 Condition, 306 Expr, 307 Function, 308 Node, 309 OpAdd, 310 OpAnd, 311 OpAndBitwise, 312 OpAssign, 313 OpAssignAdd, 314 OpAssignAndBitwise, 315 OpAssignDiv, 316 OpAssignMod, 317 OpAssignMul, 318 OpAssignOrBitwise, 319 OpAssignSub, 320 OpDiv, 321 OpEqual, 322 OpGreater, 323 OpGreaterEq, 324 OpLess, 325 OpLessEq, 326 OpMod, 327 OpMul, 328 OpNegate, 329 OpNotEqual, 330 OpOr, 331 OpOrBitwise, 332 OpSub, 333 Operator, 334 Return, 335 Statement, 336 TranslationUnit, 337 UnaryOp, 338 VarDecl, 339 VarRef, 340 ); 341 342 // It should be possible to generate the enum from Nodes. How? 343 enum Kind { 344 BinaryOp, 345 Block, 346 Branch, 347 Call, 348 Condition, 349 Expr, 350 Function, 351 Node, 352 OpAdd, 353 OpAnd, 354 OpAndBitwise, 355 OpAssign, 356 OpAssignAdd, 357 OpAssignAndBitwise, 358 OpAssignDiv, 359 OpAssignMod, 360 OpAssignMul, 361 OpAssignOrBitwise, 362 OpAssignSub, 363 OpDiv, 364 OpEqual, 365 OpGreater, 366 OpGreaterEq, 367 OpLess, 368 OpLessEq, 369 OpMod, 370 OpMul, 371 OpNegate, 372 OpNotEqual, 373 OpOr, 374 OpOrBitwise, 375 OpSub, 376 Operator, 377 Return, 378 Statement, 379 TranslationUnit, 380 UnaryOp, 381 VarDecl, 382 VarRef, 383 } 384 385 bool isExpression(Kind k) @safe pure nothrow @nogc { 386 with (Kind) { 387 return k.among( 388 BinaryOp, 389 Call, 390 Condition, 391 Expr, 392 OpAdd, 393 OpAnd, 394 OpAndBitwise, 395 OpAssign, 396 OpDiv, 397 OpEqual, 398 OpGreater, 399 OpGreaterEq, 400 OpLess, 401 OpLessEq, 402 OpMod, 403 OpMul, 404 OpNegate, 405 OpNotEqual, 406 OpOr, 407 OpOrBitwise, 408 OpSub, 409 Return, 410 UnaryOp, 411 VarDecl, 412 VarRef, 413 ) != 0; 414 } 415 } 416 // dfmt on 417 418 interface Visitor { 419 static foreach (N; Nodes) { 420 void visit(N); 421 } 422 } 423 424 // TODO: implement a breath first. 425 class DepthFirstVisitor : Visitor { 426 static foreach (N; Nodes) { 427 override void visit(N n) { 428 n.accept(this); 429 } 430 } 431 } 432 433 class TranslationUnit : Node { 434 mixin NodeKind; 435 } 436 437 class Statement : Node { 438 mixin NodeKind; 439 } 440 441 class Expr : Node { 442 mixin NodeKind; 443 } 444 445 /// A function definition. 446 class Function : Node { 447 mixin NodeKind; 448 449 /// If the function has a return type it is associated with this expression. 450 Return return_; 451 } 452 453 /// A function call. 454 class Call : Expr { 455 mixin NodeKind; 456 } 457 458 /// The operator itself in a binary operator expression. 459 class Operator : Node { 460 mixin NodeKind; 461 } 462 463 /** A block of code such such as a local scope enclosed by brackets, `{}`. 464 * 465 * It is intended to be possible to delete it. But it may need to be further 466 * analyzed for e.g. `Return` nodes. 467 */ 468 class Block : Node { 469 mixin NodeKind; 470 } 471 472 /** The code for one of the branches resulting from a condition. 473 * 474 * It can be the branches in a if-else statement or a case branch for languages 475 * such as C/C++. 476 * 477 * The important aspect is that the branch is not an expression. It can't be 478 * evaluated to a value of a type. 479 */ 480 class Branch : Node { 481 mixin NodeKind; 482 483 // The inside of a branch node wherein code can be injected. 484 Block inside; 485 } 486 487 /// Results in the bottom type or up. 488 class Return : Expr { 489 mixin NodeKind; 490 } 491 492 /// A condition wraps "something" which always evaluates to a boolean. 493 class Condition : Expr { 494 mixin NodeKind; 495 } 496 497 class VarDecl : Expr { 498 mixin NodeKind; 499 bool isConst; 500 } 501 502 class VarRef : Expr { 503 mixin NodeKind; 504 // should always refer to something 505 VarDecl to; 506 507 this(VarDecl to) { 508 this.to = to; 509 } 510 511 invariant { 512 assert(to !is null); 513 } 514 } 515 516 class UnaryOp : Expr { 517 mixin NodeKind; 518 519 Operator operator; 520 Expr expr; 521 522 this() { 523 } 524 525 this(Operator op, Expr expr) { 526 this.operator = op; 527 this.expr = expr; 528 } 529 } 530 531 class OpNegate : UnaryOp { 532 mixin NodeKind; 533 } 534 535 class BinaryOp : Expr { 536 mixin NodeKind; 537 538 Operator operator; 539 Expr lhs; 540 Expr rhs; 541 542 this() { 543 } 544 545 this(Operator op, Expr lhs, Expr rhs) { 546 this.operator = op; 547 this.lhs = lhs; 548 this.rhs = rhs; 549 } 550 } 551 552 class OpAssign : BinaryOp { 553 mixin NodeKind; 554 } 555 556 class OpAssignAdd : BinaryOp { 557 mixin NodeKind; 558 } 559 560 class OpAssignSub : BinaryOp { 561 mixin NodeKind; 562 } 563 564 class OpAssignMul : BinaryOp { 565 mixin NodeKind; 566 } 567 568 class OpAssignDiv : BinaryOp { 569 mixin NodeKind; 570 } 571 572 class OpAssignMod : BinaryOp { 573 mixin NodeKind; 574 } 575 576 class OpAssignAndBitwise : BinaryOp { 577 mixin NodeKind; 578 } 579 580 class OpAssignOrBitwise : BinaryOp { 581 mixin NodeKind; 582 } 583 584 class OpAdd : BinaryOp { 585 mixin NodeKind; 586 } 587 588 class OpSub : BinaryOp { 589 mixin NodeKind; 590 } 591 592 class OpMul : BinaryOp { 593 mixin NodeKind; 594 } 595 596 class OpDiv : BinaryOp { 597 mixin NodeKind; 598 } 599 600 class OpMod : BinaryOp { 601 mixin NodeKind; 602 } 603 604 class OpAnd : BinaryOp { 605 mixin NodeKind; 606 } 607 608 class OpAndBitwise : BinaryOp { 609 mixin NodeKind; 610 } 611 612 class OpOr : BinaryOp { 613 mixin NodeKind; 614 } 615 616 class OpOrBitwise : BinaryOp { 617 mixin NodeKind; 618 } 619 620 class OpEqual : BinaryOp { 621 mixin NodeKind; 622 } 623 624 class OpLess : BinaryOp { 625 mixin NodeKind; 626 } 627 628 class OpGreater : BinaryOp { 629 mixin NodeKind; 630 } 631 632 class OpLessEq : BinaryOp { 633 mixin NodeKind; 634 } 635 636 class OpGreaterEq : BinaryOp { 637 mixin NodeKind; 638 } 639 640 class OpNotEqual : BinaryOp { 641 mixin NodeKind; 642 } 643 644 RetT makeId(RetT, T)(T data) { 645 import dextool.hash : makeCrc64Iso; 646 647 auto a = makeCrc64Iso(cast(const(ubyte)[]) data); 648 return RetT(a.c0); 649 } 650 651 RetT makeUniqueId(RetT)() { 652 import std.random : uniform; 653 654 return RetT(uniform(long.min, long.max)); 655 } 656 657 class Type { 658 Range range; 659 660 this() { 661 this(Range.makeInf); 662 } 663 664 this(Range r) { 665 this.range = r; 666 } 667 668 TypeKind kind() const { 669 return TypeKind.bottom; 670 } 671 } 672 673 final class DiscreteType : Type { 674 this(Range r) { 675 super(r); 676 } 677 678 override TypeKind kind() const { 679 return TypeKind.discrete; 680 } 681 } 682 683 final class ContinuesType : Type { 684 this(Range r) { 685 super(r); 686 } 687 688 override TypeKind kind() const { 689 return TypeKind.continues; 690 } 691 } 692 693 final class UnorderedType : Type { 694 this(Range r) { 695 super(r); 696 } 697 698 override TypeKind kind() const { 699 return TypeKind.unordered; 700 } 701 } 702 703 final class BooleanType : Type { 704 this(Range r) { 705 super(r); 706 } 707 708 override TypeKind kind() const { 709 return TypeKind.boolean; 710 } 711 } 712 713 enum TypeKind { 714 // It can be anything, practically useless for mutation testing because it 715 // doesn't provide any logic that can be used to e.g. generate 716 // "intelligent" ROR mutants. 717 bottom, 718 /// integers, enums 719 discrete, 720 /// floating point values 721 continues, 722 /// no order exists between values in the type thus unable to do ROR 723 unordered, 724 /// 725 boolean, 726 } 727 728 struct Value { 729 import std.traits : TemplateArgsOf; 730 731 static struct NegInf { 732 } 733 734 static struct PosInf { 735 } 736 737 static struct Int { 738 // TODO: change to BigInt? 739 long value; 740 } 741 742 static struct Bool { 743 bool value; 744 } 745 746 alias Value = SumType!(NegInf, PosInf, Int, Bool); 747 Value value; 748 749 static foreach (T; TemplateArgsOf!Value) { 750 this(T a) { 751 value = Value(a); 752 } 753 } 754 755 string toString() @safe pure const { 756 auto buf = appender!string; 757 toString(buf); 758 return buf.data; 759 } 760 761 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 762 import std.conv : to; 763 import std.range : put; 764 765 value.match!((const NegInf a) => put(w, "-inf"), (const PosInf a) => put(w, 766 "+inf"), (const Int a) => put(w, a.value.to!string), 767 (const Bool a) => put(w, a.value.to!string)); 768 } 769 } 770 771 struct Range { 772 static makeInf() { 773 return Range(Value(Value.NegInf.init), Value(Value.PosInf.init)); 774 } 775 776 static makeBoolean() { 777 return Range(Value(Value.Bool(false)), Value(Value.Bool(true))); 778 } 779 780 Value low; 781 Value up; 782 783 this(Value low, Value up) { 784 this.low = low; 785 this.up = up; 786 } 787 788 enum CompareResult { 789 onLowerBound, 790 onUpperBound, 791 // the value and the range fully overlap each other. This happens when 792 // the range is only one value. 793 overlap, 794 inside, 795 outside 796 } 797 798 CompareResult compare(Value v) { 799 CompareResult negInf() { 800 return low.value.match!((Value.NegInf a) => CompareResult.onLowerBound, 801 (Value.PosInf a) => CompareResult.outside, 802 (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside); 803 } 804 805 CompareResult posInf() { 806 return up.value.match!((Value.NegInf a) => CompareResult.onUpperBound, 807 (Value.PosInf a) => CompareResult.outside, 808 (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside); 809 } 810 811 CompareResult value(long v) { 812 const l = low.value.match!((Value.NegInf a) => CompareResult.inside, 813 (Value.PosInf a) => CompareResult.outside, (Value.Int a) { 814 if (a.value < v) 815 return CompareResult.inside; 816 if (a.value == v) 817 return CompareResult.onLowerBound; 818 return CompareResult.outside; 819 }, (Value.Bool a) => CompareResult.outside); 820 821 const u = up.value.match!((Value.NegInf a) => CompareResult.outside, 822 (Value.PosInf a) => CompareResult.inside, (Value.Int a) { 823 if (a.value > v) 824 return CompareResult.inside; 825 if (a.value == v) 826 return CompareResult.onUpperBound; 827 return CompareResult.outside; 828 }, (Value.Bool a) => CompareResult.outside); 829 830 if (l == CompareResult.inside && u == CompareResult.inside) 831 return CompareResult.inside; 832 if (l == CompareResult.onLowerBound && u == CompareResult.onUpperBound) 833 return CompareResult.overlap; 834 if (l == CompareResult.onLowerBound) 835 return l; 836 if (u == CompareResult.onUpperBound) 837 return u; 838 assert(l == CompareResult.outside || u == CompareResult.outside); 839 return CompareResult.outside; 840 } 841 842 CompareResult boolean(bool v) { 843 // TODO: fix this 844 return CompareResult.outside; 845 } 846 847 return v.value.match!((Value.NegInf a) => negInf, 848 (Value.PosInf a) => posInf, (Value.Int a) => value(a.value), 849 (Value.Bool a) => boolean(a.value)); 850 } 851 852 string toString() @safe pure const { 853 auto buf = appender!string; 854 toString(buf); 855 return buf.data; 856 } 857 858 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 859 import std.range : put; 860 861 put(w, "["); 862 low.toString(w); 863 put(w, ":"); 864 up.toString(w); 865 put(w, "]"); 866 } 867 } 868 869 struct TypeId { 870 ulong value; 871 872 size_t toHash() @safe pure nothrow const @nogc { 873 return value.hashOf; 874 } 875 } 876 877 TypeId makeTypeId(T)(T data) { 878 return makeId!TypeId(data); 879 } 880 881 TypeId makeUniqueTypeId() { 882 return makeUniqueId!TypeId; 883 } 884 885 struct Types { 886 Type[TypeId] types; 887 888 void require(TypeId id, Type t) { 889 if (id !in types) { 890 types[id] = t; 891 } 892 } 893 894 void set(TypeId id, Type t) { 895 types[id] = t; 896 } 897 898 Type get(TypeId id) { 899 if (auto v = id in types) { 900 return *v; 901 } 902 return null; 903 } 904 905 bool hasId(TypeId id) { 906 return (id in types) !is null; 907 } 908 909 string toString() @safe const { 910 auto buf = appender!string; 911 toString(buf); 912 return buf.data; 913 } 914 915 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 916 import std.format : formattedWrite; 917 import std.range : put; 918 919 foreach (kv; types.byKeyValue) { 920 formattedWrite(w, "%X:%s:%s", kv.key.value, kv.value.kind, kv.value.range); 921 put(w, "\n"); 922 } 923 } 924 } 925 926 class Symbol { 927 Value value; 928 929 this() { 930 this(Value(Value.PosInf.init)); 931 } 932 933 this(Value v) { 934 this.value = v; 935 } 936 937 SymbolKind kind() const { 938 return SymbolKind.unknown; 939 } 940 } 941 942 final class DiscretSymbol : Symbol { 943 this(Value r) { 944 super(r); 945 } 946 947 override SymbolKind kind() const { 948 return SymbolKind.discret; 949 } 950 } 951 952 final class ContinuesSymbol : Symbol { 953 this(Value r) { 954 super(r); 955 } 956 957 override SymbolKind kind() const { 958 return SymbolKind.continues; 959 } 960 } 961 962 final class BooleanSymbol : Symbol { 963 this(Value r) { 964 super(r); 965 } 966 967 override SymbolKind kind() const { 968 return SymbolKind.boolean; 969 } 970 } 971 972 enum SymbolKind { 973 /// the symbol wasn't able to evaluate to something useful 974 unknown, 975 /// integers, enums 976 discret, 977 /// floating point values 978 continues, 979 /// 980 boolean, 981 } 982 983 struct SymbolId { 984 ulong value; 985 986 size_t toHash() @safe pure nothrow const @nogc { 987 return value.hashOf; 988 } 989 } 990 991 SymbolId makeSymbolId(T)(T data) { 992 return makeId!SymbolId(data); 993 } 994 995 SymbolId makeUniqueSymbolId() { 996 return makeUniqueId!SymbolId; 997 } 998 999 struct Symbols { 1000 Symbol[SymbolId] symbols; 1001 1002 void require(SymbolId id, Symbol s) { 1003 if (id !in symbols) { 1004 symbols[id] = s; 1005 } 1006 } 1007 1008 void set(SymbolId id, Symbol s) { 1009 symbols[id] = s; 1010 } 1011 1012 Symbol get(SymbolId id) { 1013 if (auto v = id in symbols) { 1014 return *v; 1015 } 1016 return null; 1017 } 1018 1019 bool hasId(SymbolId id) { 1020 return (id in symbols) !is null; 1021 } 1022 1023 string toString() @safe const { 1024 auto buf = appender!string; 1025 toString(buf); 1026 return buf.data; 1027 } 1028 1029 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 1030 foreach (kv; symbols.byKeyValue) { 1031 formattedWrite(w, "%X:%s:%s\n", kv.key.value, kv.value.kind, kv.value.value); 1032 } 1033 } 1034 } 1035 1036 private: 1037 1038 mixin template NodeKind() { 1039 override Kind kind() const { 1040 import std.traits : Unqual; 1041 1042 mixin("return Kind." ~ Unqual!(typeof(this)).stringof ~ ";"); 1043 } 1044 }