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 mixin(mixinSwitch("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 private enum Property { 347 blacklist = 0x1, 348 schemaBlacklist = 0x2, 349 covBlacklist = 0x4, 350 context = 0x8 351 } 352 353 private ubyte prop; 354 355 Kind kind() const; 356 ulong id() @safe pure nothrow const @nogc scope; 357 358 Node[] children; 359 360 /** If the node is blacklisted from being mutated. This is for example when 361 * the node covers a C macro. 362 */ 363 bool blacklist() { 364 return Property.blacklist & prop; 365 } 366 367 void blacklist(bool x) { 368 if (x) 369 prop |= Property.blacklist; 370 else 371 prop &= ~Property.blacklist; 372 } 373 374 /** If the node should not be part of mutant schemata because it is highly 375 * likely to introduce compilation errors. It is for example likely when 376 * operators are overloaded. 377 */ 378 bool schemaBlacklist() { 379 return (Property.schemaBlacklist & prop) != 0; 380 } 381 382 void schemaBlacklist(bool x) { 383 if (x) 384 prop |= Property.schemaBlacklist; 385 else 386 prop &= ~Property.schemaBlacklist; 387 } 388 389 /** Block nodes that have a high probability of failing from being coverage instrumented. 390 * 391 */ 392 bool covBlacklist() { 393 return (Property.covBlacklist & prop) != 0; 394 } 395 396 void covBlacklist(bool x) { 397 if (x) 398 prop |= Property.covBlacklist; 399 else 400 prop &= ~Property.covBlacklist; 401 } 402 403 bool context() { 404 return (Property.context & prop) != 0; 405 } 406 407 void context(bool x) { 408 if (x) 409 prop |= Property.context; 410 else 411 prop &= ~Property.context; 412 } 413 414 bool opEquals(Kind k) { 415 return kind == k; 416 } 417 418 override bool opEquals(Object o) { 419 auto rhs = cast(const Node) o; 420 return (rhs && (id == rhs.id)); 421 } 422 423 override size_t toHash() @safe pure nothrow const @nogc scope { 424 return id.hashOf(); 425 } 426 } 427 428 private string mixinSwitch(string nodeName) { 429 import std.conv : text; 430 import std.traits : EnumMembers; 431 432 string s; 433 s ~= "final switch(" ~ nodeName ~ ".kind) {\n"; 434 foreach (kind; [EnumMembers!Kind]) { 435 const k = text(kind); 436 s ~= format!"case Kind." ~ k ~ ": v.visit(cast(" ~ k ~ ") " ~ nodeName ~ "); break;\n"; 437 } 438 s ~= "}"; 439 return s; 440 } 441 442 /** 443 * It is optional to add the members visitPush/visitPop to push/pop the nodes that are visited. 444 * The parent will always have been the last pushed. 445 */ 446 void accept(VisitorT)(Node n, VisitorT v) { 447 static if (__traits(hasMember, VisitorT, "visitPush")) 448 v.visitPush(n); 449 foreach (c; n.children) { 450 mixin(mixinSwitch("c")); 451 } 452 453 static if (__traits(hasMember, VisitorT, "visitPop")) 454 v.visitPop(n); 455 } 456 457 /// All nodes that a visitor must be able to handle. 458 // must be sorted such that the leaf nodes are at the top. 459 // dfmt off 460 alias Nodes = AliasSeq!( 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 Poison, 498 Return, 499 Statement, 500 TranslationUnit, 501 UnaryOp, 502 VarDecl, 503 VarRef, 504 Literal, 505 ); 506 507 // It should be possible to generate the enum from Nodes. How? 508 enum Kind { 509 BinaryOp, 510 Block, 511 Branch, 512 BranchBundle, 513 Call, 514 Condition, 515 Constructor, 516 Expr, 517 Function, 518 Literal, 519 Loop, 520 Node, 521 OpAdd, 522 OpAnd, 523 OpAndBitwise, 524 OpAssign, 525 OpAssignAdd, 526 OpAssignAndBitwise, 527 OpAssignDiv, 528 OpAssignMod, 529 OpAssignMul, 530 OpAssignOrBitwise, 531 OpAssignSub, 532 OpDiv, 533 OpEqual, 534 OpGreater, 535 OpGreaterEq, 536 OpLess, 537 OpLessEq, 538 OpMod, 539 OpMul, 540 OpNegate, 541 OpNotEqual, 542 OpOr, 543 OpOrBitwise, 544 OpSub, 545 Operator, 546 Poison, 547 Return, 548 Statement, 549 TranslationUnit, 550 UnaryOp, 551 VarDecl, 552 VarRef, 553 } 554 555 alias ExpressionKind = AliasSeq!( 556 Kind.BinaryOp, 557 Kind.Call, 558 Kind.Condition, 559 Kind.Constructor, 560 Kind.Expr, 561 Kind.OpAdd, 562 Kind.OpAnd, 563 Kind.OpAndBitwise, 564 Kind.OpAssign, 565 Kind.OpDiv, 566 Kind.OpEqual, 567 Kind.OpGreater, 568 Kind.OpGreaterEq, 569 Kind.OpLess, 570 Kind.OpLessEq, 571 Kind.OpMod, 572 Kind.OpMul, 573 Kind.OpNegate, 574 Kind.OpNotEqual, 575 Kind.OpOr, 576 Kind.OpOrBitwise, 577 Kind.OpSub, 578 Kind.Return, 579 Kind.UnaryOp, 580 Kind.VarDecl, 581 Kind.VarRef, 582 Kind.Literal, 583 ); 584 // dfmt on 585 586 bool isExpression(Kind k) @safe pure nothrow @nogc { 587 return k.among(ExpressionKind) != 0; 588 } 589 590 interface Visitor { 591 static foreach (N; Nodes) { 592 void visit(N); 593 } 594 } 595 596 // TODO: implement a breath first. 597 class DepthFirstVisitor : Visitor { 598 int visitDepth; 599 600 void visitPush(Node n) { 601 } 602 603 void visitPop(Node n) { 604 } 605 606 static foreach (N; Nodes) { 607 override void visit(N n) { 608 ++visitDepth; 609 accept(n, this); 610 --visitDepth; 611 } 612 } 613 } 614 615 /** A phantom node that carry semantic information about its children. It 616 * "poisons" all children. 617 */ 618 class Poison : Node { 619 mixin(nodeImpl!(typeof(this))); 620 } 621 622 class TranslationUnit : Node { 623 mixin(nodeImpl!(typeof(this))); 624 } 625 626 class Statement : Node { 627 mixin(nodeImpl!(typeof(this))); 628 } 629 630 class Loop : Node { 631 mixin(nodeImpl!(typeof(this))); 632 } 633 634 class Expr : Node { 635 mixin(nodeImpl!(typeof(this))); 636 } 637 638 /// A function definition. 639 class Function : Node { 640 mixin(nodeImpl!(typeof(this))); 641 642 /// If the function has a return type it is associated with this expression. 643 Return return_; 644 } 645 646 /// A constructor for a variable. 647 class Constructor : Expr { 648 mixin(nodeImpl!(typeof(this))); 649 } 650 651 /// A function call. 652 class Call : Expr { 653 mixin(nodeImpl!(typeof(this))); 654 } 655 656 /// A constant. 657 class Literal : Expr { 658 mixin(nodeImpl!(typeof(this))); 659 } 660 661 /// The operator itself in a binary operator expression. 662 class Operator : Node { 663 mixin(nodeImpl!(typeof(this))); 664 } 665 666 /** A block of code such such as a local scope enclosed by brackets, `{}`. 667 * 668 * It is intended to be possible to delete it. But it may need to be further 669 * analyzed for e.g. `Return` nodes. 670 */ 671 class Block : Node { 672 mixin(nodeImpl!(typeof(this))); 673 } 674 675 /** Multiple branches are contained in the bundle that can be e.g. deleted. 676 * 677 * This can, in C/C++, be either a whole if-statement or switch. 678 */ 679 class BranchBundle : Node { 680 mixin(nodeImpl!(typeof(this))); 681 } 682 683 /** The code for one of the branches resulting from a condition. 684 * 685 * It can be the branches in a if-else statement or a case branch for languages 686 * such as C/C++. 687 * 688 * The important aspect is that the branch is not an expression. It can't be 689 * evaluated to a value of a type. 690 */ 691 class Branch : Node { 692 mixin(nodeImpl!(typeof(this))); 693 694 // The inside of a branch node wherein code can be injected. 695 Block inside; 696 } 697 698 /// Results in the bottom type or up. 699 class Return : Expr { 700 mixin(nodeImpl!(typeof(this))); 701 } 702 703 /// A condition wraps "something" which always evaluates to a boolean. 704 class Condition : Expr { 705 mixin(nodeImpl!(typeof(this))); 706 } 707 708 class VarDecl : Expr { 709 mixin(nodeImpl!(typeof(this))); 710 bool isConst; 711 } 712 713 class VarRef : Expr { 714 mixin(nodeImpl!(typeof(this))); 715 // should always refer to something 716 VarDecl to; 717 718 this(VarDecl to) { 719 this(); 720 this.to = to; 721 } 722 723 invariant { 724 assert(to !is null); 725 } 726 } 727 728 class UnaryOp : Expr { 729 mixin(nodeImpl!(typeof(this))); 730 731 Operator operator; 732 Expr expr; 733 734 this(Operator op, Expr expr) { 735 this(); 736 this.operator = op; 737 this.expr = expr; 738 } 739 } 740 741 class OpNegate : UnaryOp { 742 mixin(nodeImpl!(typeof(this))); 743 } 744 745 class BinaryOp : Expr { 746 mixin(nodeImpl!(typeof(this))); 747 748 Operator operator; 749 Expr lhs; 750 Expr rhs; 751 752 this(Operator op, Expr lhs, Expr rhs) { 753 this(); 754 this.operator = op; 755 this.lhs = lhs; 756 this.rhs = rhs; 757 } 758 } 759 760 class OpAssign : BinaryOp { 761 mixin(nodeImpl!(typeof(this))); 762 } 763 764 class OpAssignAdd : BinaryOp { 765 mixin(nodeImpl!(typeof(this))); 766 } 767 768 class OpAssignSub : BinaryOp { 769 mixin(nodeImpl!(typeof(this))); 770 } 771 772 class OpAssignMul : BinaryOp { 773 mixin(nodeImpl!(typeof(this))); 774 } 775 776 class OpAssignDiv : BinaryOp { 777 mixin(nodeImpl!(typeof(this))); 778 } 779 780 class OpAssignMod : BinaryOp { 781 mixin(nodeImpl!(typeof(this))); 782 } 783 784 class OpAssignAndBitwise : BinaryOp { 785 mixin(nodeImpl!(typeof(this))); 786 } 787 788 class OpAssignOrBitwise : BinaryOp { 789 mixin(nodeImpl!(typeof(this))); 790 } 791 792 class OpAdd : BinaryOp { 793 mixin(nodeImpl!(typeof(this))); 794 } 795 796 class OpSub : BinaryOp { 797 mixin(nodeImpl!(typeof(this))); 798 } 799 800 class OpMul : BinaryOp { 801 mixin(nodeImpl!(typeof(this))); 802 } 803 804 class OpDiv : BinaryOp { 805 mixin(nodeImpl!(typeof(this))); 806 } 807 808 class OpMod : BinaryOp { 809 mixin(nodeImpl!(typeof(this))); 810 } 811 812 class OpAnd : BinaryOp { 813 mixin(nodeImpl!(typeof(this))); 814 } 815 816 class OpAndBitwise : BinaryOp { 817 mixin(nodeImpl!(typeof(this))); 818 } 819 820 class OpOr : BinaryOp { 821 mixin(nodeImpl!(typeof(this))); 822 } 823 824 class OpOrBitwise : BinaryOp { 825 mixin(nodeImpl!(typeof(this))); 826 } 827 828 class OpEqual : BinaryOp { 829 mixin(nodeImpl!(typeof(this))); 830 } 831 832 class OpLess : BinaryOp { 833 mixin(nodeImpl!(typeof(this))); 834 } 835 836 class OpGreater : BinaryOp { 837 mixin(nodeImpl!(typeof(this))); 838 } 839 840 class OpLessEq : BinaryOp { 841 mixin(nodeImpl!(typeof(this))); 842 } 843 844 class OpGreaterEq : BinaryOp { 845 mixin(nodeImpl!(typeof(this))); 846 } 847 848 class OpNotEqual : BinaryOp { 849 mixin(nodeImpl!(typeof(this))); 850 } 851 852 RetT makeId(RetT, T)(T data) { 853 import my.hash : makeCrc64Iso; 854 855 auto a = makeCrc64Iso(cast(const(ubyte)[]) data); 856 return RetT(a.c0); 857 } 858 859 RetT makeUniqueId(RetT)() { 860 import std.random : uniform; 861 862 return RetT(uniform(long.min, long.max)); 863 } 864 865 class Type { 866 private const ulong id_; 867 868 Range range; 869 870 this() { 871 this(Range.makeInf); 872 } 873 874 this(Range r) { 875 this.range = r; 876 id_ = uniqueNodeId; 877 } 878 879 TypeKind kind() const { 880 return TypeKind.bottom; 881 } 882 883 ulong id() @safe pure nothrow const @nogc scope { 884 return id_; 885 } 886 887 override bool opEquals(Object o) { 888 auto rhs = cast(const Type) o; 889 return (rhs && (id == rhs.id)); 890 } 891 892 override size_t toHash() @safe pure nothrow const @nogc scope { 893 return id.hashOf(); 894 } 895 } 896 897 final class DiscreteType : Type { 898 this(Range r) { 899 super(r); 900 } 901 902 override TypeKind kind() const { 903 return TypeKind.discrete; 904 } 905 } 906 907 final class ContinuesType : Type { 908 this(Range r) { 909 super(r); 910 } 911 912 override TypeKind kind() const { 913 return TypeKind.continues; 914 } 915 } 916 917 final class UnorderedType : Type { 918 this(Range r) { 919 super(r); 920 } 921 922 override TypeKind kind() const { 923 return TypeKind.unordered; 924 } 925 } 926 927 final class BooleanType : Type { 928 this(Range r) { 929 super(r); 930 } 931 932 override TypeKind kind() const { 933 return TypeKind.boolean; 934 } 935 } 936 937 final class VoidType : Type { 938 this() { 939 super(); 940 } 941 942 override TypeKind kind() const { 943 return TypeKind.top; 944 } 945 } 946 947 enum TypeKind { 948 // It can be anything, practically useless for mutation testing because it 949 // doesn't provide any logic that can be used to e.g. generate 950 // "intelligent" ROR mutants. 951 bottom, 952 /// integers, enums 953 discrete, 954 /// floating point values 955 continues, 956 /// no order exists between values in the type thus unable to do ROR 957 unordered, 958 /// 959 boolean, 960 /// a top type is nothing 961 top, 962 } 963 964 struct Value { 965 import std.traits : TemplateArgsOf; 966 967 static struct NegInf { 968 } 969 970 static struct PosInf { 971 } 972 973 static struct Int { 974 // TODO: change to BigInt? 975 long value; 976 } 977 978 static struct Bool { 979 bool value; 980 } 981 982 alias Value = SumType!(NegInf, PosInf, Int, Bool); 983 Value value; 984 985 static foreach (T; TemplateArgsOf!Value) { 986 this(T a) { 987 value = Value(a); 988 } 989 } 990 991 string toString() @safe pure const { 992 auto buf = appender!string; 993 toString(buf); 994 return buf.data; 995 } 996 997 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 998 import std.conv : to; 999 import std.range : put; 1000 1001 value.match!((const NegInf a) => put(w, "-inf"), (const PosInf a) => put(w, 1002 "+inf"), (const Int a) => put(w, a.value.to!string), 1003 (const Bool a) => put(w, a.value.to!string)); 1004 } 1005 } 1006 1007 struct Range { 1008 static makeInf() { 1009 return Range(Value(Value.NegInf.init), Value(Value.PosInf.init)); 1010 } 1011 1012 static makeBoolean() { 1013 return Range(Value(Value.Bool(false)), Value(Value.Bool(true))); 1014 } 1015 1016 Value low; 1017 Value up; 1018 1019 this(Value low, Value up) { 1020 this.low = low; 1021 this.up = up; 1022 } 1023 1024 enum CompareResult { 1025 onLowerBound, 1026 onUpperBound, 1027 // the value and the range fully overlap each other. This happens when 1028 // the range is only one value. 1029 overlap, 1030 inside, 1031 outside 1032 } 1033 1034 CompareResult compare(Value v) { 1035 CompareResult negInf() { 1036 return low.value.match!((Value.NegInf a) => CompareResult.onLowerBound, 1037 (Value.PosInf a) => CompareResult.outside, 1038 (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside); 1039 } 1040 1041 CompareResult posInf() { 1042 return up.value.match!((Value.NegInf a) => CompareResult.onUpperBound, 1043 (Value.PosInf a) => CompareResult.outside, 1044 (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside); 1045 } 1046 1047 CompareResult value(long v) { 1048 const l = low.value.match!((Value.NegInf a) => CompareResult.inside, 1049 (Value.PosInf a) => CompareResult.outside, (Value.Int a) { 1050 if (a.value < v) 1051 return CompareResult.inside; 1052 if (a.value == v) 1053 return CompareResult.onLowerBound; 1054 return CompareResult.outside; 1055 }, (Value.Bool a) => CompareResult.outside); 1056 1057 const u = up.value.match!((Value.NegInf a) => CompareResult.outside, 1058 (Value.PosInf a) => CompareResult.inside, (Value.Int a) { 1059 if (a.value > v) 1060 return CompareResult.inside; 1061 if (a.value == v) 1062 return CompareResult.onUpperBound; 1063 return CompareResult.outside; 1064 }, (Value.Bool a) => CompareResult.outside); 1065 1066 if (l == CompareResult.inside && u == CompareResult.inside) 1067 return CompareResult.inside; 1068 if (l == CompareResult.onLowerBound && u == CompareResult.onUpperBound) 1069 return CompareResult.overlap; 1070 if (l == CompareResult.onLowerBound) 1071 return l; 1072 if (u == CompareResult.onUpperBound) 1073 return u; 1074 assert(l == CompareResult.outside || u == CompareResult.outside); 1075 return CompareResult.outside; 1076 } 1077 1078 CompareResult boolean(bool v) { 1079 // TODO: fix this 1080 return CompareResult.outside; 1081 } 1082 1083 return v.value.match!((Value.NegInf a) => negInf, 1084 (Value.PosInf a) => posInf, (Value.Int a) => value(a.value), 1085 (Value.Bool a) => boolean(a.value)); 1086 } 1087 1088 string toString() @safe pure const { 1089 auto buf = appender!string; 1090 toString(buf); 1091 return buf.data; 1092 } 1093 1094 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 1095 import std.range : put; 1096 1097 put(w, "["); 1098 low.toString(w); 1099 put(w, ":"); 1100 up.toString(w); 1101 put(w, "]"); 1102 } 1103 } 1104 1105 struct TypeId { 1106 ulong value; 1107 1108 size_t toHash() @safe pure nothrow const @nogc { 1109 return value.hashOf; 1110 } 1111 } 1112 1113 TypeId makeTypeId(T)(T data) { 1114 return makeId!TypeId(data); 1115 } 1116 1117 TypeId makeUniqueTypeId() { 1118 return makeUniqueId!TypeId; 1119 } 1120 1121 struct Types { 1122 Type[TypeId] types; 1123 1124 void require(TypeId id, Type t) { 1125 if (id !in types) { 1126 types[id] = t; 1127 } 1128 } 1129 1130 void set(TypeId id, Type t) { 1131 types[id] = t; 1132 } 1133 1134 Type get(TypeId id) { 1135 if (auto v = id in types) { 1136 return *v; 1137 } 1138 return null; 1139 } 1140 1141 bool hasId(TypeId id) { 1142 return (id in types) !is null; 1143 } 1144 1145 string toString() @safe const { 1146 auto buf = appender!string; 1147 toString(buf); 1148 return buf.data; 1149 } 1150 1151 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 1152 import std.format : formattedWrite; 1153 import std.range : put; 1154 1155 foreach (kv; types.byKeyValue) { 1156 formattedWrite(w, "%X:%s:%s", kv.key.value, kv.value.kind, kv.value.range); 1157 put(w, "\n"); 1158 } 1159 } 1160 } 1161 1162 class Symbol { 1163 Value value; 1164 1165 this() { 1166 this(Value(Value.PosInf.init)); 1167 } 1168 1169 this(Value v) { 1170 this.value = v; 1171 } 1172 1173 SymbolKind kind() const { 1174 return SymbolKind.unknown; 1175 } 1176 } 1177 1178 final class DiscretSymbol : Symbol { 1179 this(Value r) { 1180 super(r); 1181 } 1182 1183 override SymbolKind kind() const { 1184 return SymbolKind.discret; 1185 } 1186 } 1187 1188 final class ContinuesSymbol : Symbol { 1189 this(Value r) { 1190 super(r); 1191 } 1192 1193 override SymbolKind kind() const { 1194 return SymbolKind.continues; 1195 } 1196 } 1197 1198 final class BooleanSymbol : Symbol { 1199 this(Value r) { 1200 super(r); 1201 } 1202 1203 override SymbolKind kind() const { 1204 return SymbolKind.boolean; 1205 } 1206 } 1207 1208 enum SymbolKind { 1209 /// the symbol wasn't able to evaluate to something useful 1210 unknown, 1211 /// integers, enums 1212 discret, 1213 /// floating point values 1214 continues, 1215 /// 1216 boolean, 1217 } 1218 1219 struct SymbolId { 1220 ulong value; 1221 1222 size_t toHash() @safe pure nothrow const @nogc { 1223 return value.hashOf; 1224 } 1225 } 1226 1227 SymbolId makeSymbolId(T)(T data) { 1228 return makeId!SymbolId(data); 1229 } 1230 1231 SymbolId makeUniqueSymbolId() { 1232 return makeUniqueId!SymbolId; 1233 } 1234 1235 struct Symbols { 1236 Symbol[SymbolId] symbols; 1237 1238 void require(SymbolId id, Symbol s) { 1239 if (id !in symbols) { 1240 symbols[id] = s; 1241 } 1242 } 1243 1244 void set(SymbolId id, Symbol s) { 1245 symbols[id] = s; 1246 } 1247 1248 Symbol get(SymbolId id) { 1249 if (auto v = id in symbols) { 1250 return *v; 1251 } 1252 return null; 1253 } 1254 1255 bool hasId(SymbolId id) { 1256 return (id in symbols) !is null; 1257 } 1258 1259 string toString() @safe const { 1260 auto buf = appender!string; 1261 toString(buf); 1262 return buf.data; 1263 } 1264 1265 void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) { 1266 foreach (kv; symbols.byKeyValue) { 1267 formattedWrite(w, "%X:%s:%s\n", kv.key.value, kv.value.kind, kv.value.value); 1268 } 1269 } 1270 } 1271 1272 /// Breath first visit of nodes. 1273 struct BreathFirstRange { 1274 import my.container.vector; 1275 1276 Vector!Node nodes; 1277 1278 this(Node n) { 1279 nodes.put(n); 1280 } 1281 1282 Node front() @safe pure nothrow { 1283 assert(!empty, "Can't get front of an empty range"); 1284 return nodes.front; 1285 } 1286 1287 void popFront() @safe pure nothrow { 1288 assert(!empty, "Can't pop front of an empty range"); 1289 nodes.put(nodes.front.children); 1290 nodes.popFront; 1291 } 1292 1293 bool empty() @safe pure nothrow const @nogc { 1294 return nodes.empty; 1295 } 1296 } 1297 1298 private: 1299 1300 string nodeImpl(T)() { 1301 return ` 1302 private const ulong id_; 1303 1304 override ulong id() @safe pure nothrow const @nogc scope { return id_; } 1305 1306 override Kind kind() const { 1307 return Kind.` ~ T.stringof ~ `; 1308 } 1309 1310 this() { 1311 id_ = uniqueNodeId; 1312 }`; 1313 } 1314 1315 string typeImpl() { 1316 return ` 1317 private const ulong id_; 1318 override ulong id() @safe pure nothrow const @nogc scope { return id_; } 1319 this() { 1320 id_ = uniqueNodeId; 1321 }`; 1322 } 1323 1324 /// Dedup the paths to reduce the required memory. 1325 struct Dedup(T) { 1326 T[ulong] cache; 1327 /// Number of deduplications. 1328 long count; 1329 /// ".length" accumulated of items deduplicated. 1330 long lengthAccum; 1331 1332 T dedup(T p) { 1333 import std.traits : hasMember; 1334 1335 const cs = p.toHash; 1336 if (auto v = cs in cache) { 1337 ++count; 1338 static if (hasMember!(T, "length")) 1339 lengthAccum += p.length; 1340 return *v; 1341 } 1342 1343 cache[cs] = p; 1344 return p; 1345 } 1346 1347 void release() { 1348 cache = typeof(cache).init; 1349 } 1350 }