1 /** A tagged union on top of `std.variant.Algebraic` 2 3 The C API implements operand values as regular unions, enabling (mis)interpretation of the stored value in any of the 4 encompassed types. This implementation provides a safer union that throws when interpreting the union's value in a 5 different type than it was stored as. 6 */ 7 module capstone.utils.tagged_union; 8 9 import std.meta: AliasSeq, staticMap; 10 import std.format: format; 11 import std.exception: enforce, basicExceptionCtors, assertThrown, assertNotThrown; 12 import std.variant: Algebraic; 13 14 private enum bool distinctFieldNames(names...) = __traits(compiles,{ 15 static foreach (name; names) 16 static if (is(typeof(name) : string)) 17 mixin("enum int" ~ name ~ " = 0;"); 18 }); 19 20 private enum bool distinctTypeNames(names...) = __traits(compiles,{ 21 static foreach (name; names) 22 static if (is(name)) 23 mixin("enum int" ~ name.mangleof ~ " = 0;"); 24 }); 25 26 unittest{ 27 assert(distinctFieldNames!("asdf","asd",int,"asdg")); 28 assert(!distinctFieldNames!("asdf","asd","asdf")); 29 assert(distinctTypeNames!(int,long,string,"string",uint)); 30 assert(!distinctTypeNames!(int,long,uint,int)); 31 } 32 33 /** Constructs a `TaggedUnion` with the specifiedn types and identifiers 34 35 In contrast to a plain `std.variant.Algebraic`, this one is not only parameterised by types but also by identifiers. 36 This enables access of the stored value via the identifiers - just as for regular unions. 37 */ 38 template TaggedUnion(Specs...) if (distinctFieldNames!Specs && distinctTypeNames!Specs){ 39 template FieldSpec(T, string s){ 40 alias Type = T; 41 alias name = s; 42 } 43 44 template parseSpecs(Specs...){ 45 static if(Specs.length==0){ 46 alias parseSpecs = AliasSeq!(); 47 }else static if(is(Specs[0])){ 48 alias parseSpecs = AliasSeq!(FieldSpec!(Specs[0..2]), parseSpecs!(Specs[2..$])); 49 }else{ 50 static assert(0, "Attempted to instantiate TaggedUnion with an invalid argument: " ~ Specs[0].stringof); 51 } 52 53 } 54 55 alias fieldSpecs = parseSpecs!Specs; 56 alias extractType(alias spec) = spec.Type; 57 alias extractName(alias spec) = spec.name; 58 alias Types = staticMap!(extractType, fieldSpecs); 59 alias names = staticMap!(extractName, fieldSpecs); 60 61 struct TaggedUnion{ 62 private Algebraic!Types wrapped; 63 64 // Generate constructors 65 static foreach(T; Types){ 66 this(T val){wrapped = val;} 67 } 68 69 // Generate getter (by type) 70 @property auto get(InnerType)() const { 71 return wrapped.get!(InnerType); 72 } 73 74 // Generate getters (by identifier) 75 static foreach(spec; fieldSpecs){ 76 mixin("@property auto " ~ spec.name ~"() const { 77 enforce!TaggedUnionException(wrapped.type == typeid(spec.Type), 78 \"Trying to read a '\" ~ spec.Type.stringof ~ \"' but a '\" ~ wrapped.type.toString ~ \"' is stored\"); 79 return wrapped.get!(spec.Type); 80 }"); 81 } 82 83 // Generate setters 84 static foreach(i, spec; fieldSpecs){ 85 mixin("@property auto " ~ spec.name ~ "(spec.Type val){wrapped = val;}"); 86 } 87 } 88 } 89 /// 90 unittest{ 91 import capstone.x86; 92 93 alias SafeOpValue = TaggedUnion!(X86Register, "reg", long, "imm"); 94 auto safeVal = SafeOpValue(X86Register.eip); 95 assertThrown(safeVal.imm); // cannot access the `long` since a `X86Register` is currently stored 96 safeVal.imm = 42; 97 assertNotThrown(safeVal.imm); // can access the `long` now 98 assertNotThrown(safeVal.get!long); // also accessible by type 99 100 // Corresponding operations on a regular union go unnoticed 101 union UnsafeOpValue{X86Register reg; long imm;} 102 auto unsafeVal = UnsafeOpValue(X86Register.eip); 103 assertNotThrown(unsafeVal.imm); 104 } 105 106 /// Thrown on misuse of `TaggedUnion` 107 class TaggedUnionException : Exception{ 108 /// 109 mixin basicExceptionCtors; 110 }