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