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 }