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