1 module funkwerk.stdx.data.json.taggedalgebraic.visit; 2 3 import funkwerk.stdx.data.json.taggedalgebraic.taggedalgebraic; 4 import funkwerk.stdx.data.json.taggedalgebraic.taggedunion; 5 6 import std.meta : anySatisfy; 7 import std.traits : EnumMembers, isInstanceOf; 8 9 10 /** Dispatches the value contained on a `TaggedUnion` or `TaggedAlgebraic` to a 11 set of visitors. 12 13 A visitor can have one of three forms: 14 15 $(UL 16 $(LI function or delegate taking a single typed parameter) 17 $(LI function or delegate taking no parameters) 18 $(LI function or delegate template taking any single parameter) 19 ) 20 21 .... 22 */ 23 template visit(VISITORS...) 24 if (VISITORS.length > 0) 25 { 26 auto visit(TU)(auto ref TU tu) 27 if (isInstanceOf!(TaggedUnion, TU)) 28 { 29 alias val = validateHandlers!(TU, VISITORS); 30 31 final switch (tu.kind) { 32 static foreach (k; EnumMembers!(TU.Kind)) { 33 case k: { 34 static if (isUnitType!(TU.FieldTypes[k])) 35 alias T = void; 36 else alias T = TU.FieldTypes[k]; 37 alias h = selectHandler!(T, VISITORS); 38 static if (is(typeof(h) == typeof(null))) static assert(false, "No visitor defined for type type "~T.stringof); 39 else static if (is(typeof(h) == string)) static assert(false, h); 40 else static if (is(T == void)) return h(); 41 else return h(tu.value!k); 42 } 43 } 44 } 45 } 46 47 auto visit(U)(auto ref TaggedAlgebraic!U ta) 48 { 49 return visit(ta.get!(TaggedUnion!U)); 50 } 51 } 52 53 /// 54 unittest { 55 static if (__VERSION__ >= 2081) { 56 import std.conv : to; 57 58 union U { 59 int number; 60 string text; 61 } 62 alias TU = TaggedUnion!U; 63 64 auto tu = TU.number(42); 65 tu.visit!( 66 (int n) { assert(n == 42); }, 67 (string s) { assert(false); } 68 ); 69 70 assert(tu.visit!((v) => to!int(v)) == 42); 71 72 tu.setText("43"); 73 74 assert(tu.visit!((v) => to!int(v)) == 43); 75 } 76 } 77 78 unittest { 79 // repeat test from TaggedUnion 80 union U { 81 Void none; 82 int count; 83 float length; 84 } 85 TaggedAlgebraic!U u; 86 87 // 88 static assert(is(typeof(u.visit!((int) {}, (float) {}, () {})))); 89 static assert(is(typeof(u.visit!((_) {}, () {})))); 90 static assert(is(typeof(u.visit!((_) {}, (float) {}, () {})))); 91 static assert(is(typeof(u.visit!((float) {}, (_) {}, () {})))); 92 93 static assert(!is(typeof(u.visit!((_) {})))); // missing void handler 94 static assert(!is(typeof(u.visit!(() {})))); // missing value handler 95 96 static assert(!is(typeof(u.visit!((_) {}, () {}, (string) {})))); // invalid typed handler 97 static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, () {})))); // duplicate void handler 98 static assert(!is(typeof(u.visit!((_) {}, () {}, (_) {})))); // duplicate generic handler 99 static assert(!is(typeof(u.visit!((int) {}, (float) {}, (float) {}, () {})))); // duplicate typed handler 100 101 // TODO: error out for superfluous generic handlers 102 //static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler 103 } 104 105 unittest { 106 union U { 107 Void none; 108 int count; 109 float length; 110 } 111 TaggedUnion!U u; 112 113 // 114 static assert(is(typeof(u.visit!((int) {}, (float) {}, () {})))); 115 static assert(is(typeof(u.visit!((_) {}, () {})))); 116 static assert(is(typeof(u.visit!((_) {}, (float) {}, () {})))); 117 static assert(is(typeof(u.visit!((float) {}, (_) {}, () {})))); 118 119 static assert(!is(typeof(u.visit!((_) {})))); // missing void handler 120 static assert(!is(typeof(u.visit!(() {})))); // missing value handler 121 122 static assert(!is(typeof(u.visit!((_) {}, () {}, (string) {})))); // invalid typed handler 123 static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, () {})))); // duplicate void handler 124 static assert(!is(typeof(u.visit!((_) {}, () {}, (_) {})))); // duplicate generic handler 125 static assert(!is(typeof(u.visit!((int) {}, (float) {}, (float) {}, () {})))); // duplicate typed handler 126 127 // TODO: error out for superfluous generic handlers 128 //static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler 129 } 130 131 /** The same as `visit`, except that failure to handle types is checked at runtime. 132 133 Instead of failing to compile, `tryVisit` will throw an `Exception` if none 134 of the handlers is able to handle the value contained in `tu`. 135 */ 136 template tryVisit(VISITORS...) 137 if (VISITORS.length > 0) 138 { 139 auto tryVisit(TU)(auto ref TU tu) 140 if (isInstanceOf!(TaggedUnion, TU)) 141 { 142 final switch (tu.kind) { 143 static foreach (k; EnumMembers!(TU.Kind)) { 144 case k: { 145 static if (isUnitType!(TU.FieldTypes[k])) 146 alias T = void; 147 else alias T = TU.FieldTypes[k]; 148 alias h = selectHandler!(T, VISITORS); 149 static if (is(typeof(h) == typeof(null))) throw new Exception("Type "~T.stringof~" not handled by any visitor."); 150 else static if (is(typeof(h) == string)) static assert(false, h); 151 else static if (is(T == void)) return h(); 152 else return h(tu.value!k); 153 } 154 } 155 } 156 } 157 158 auto tryVisit(U)(auto ref TaggedAlgebraic!U ta) 159 { 160 return tryVisit(ta.get!(TaggedUnion!U)); 161 } 162 } 163 164 /// 165 unittest { 166 import std.exception : assertThrown; 167 168 union U { 169 int number; 170 string text; 171 } 172 alias TU = TaggedUnion!U; 173 174 auto tu = TU.number(42); 175 tu.tryVisit!((int n) { assert(n == 42); }); 176 assertThrown(tu.tryVisit!((string s) { assert(false); })); 177 } 178 179 // repeat from TaggedUnion 180 unittest { 181 import std.exception : assertThrown; 182 183 union U { 184 int number; 185 string text; 186 } 187 alias TA = TaggedAlgebraic!U; 188 189 auto ta = TA(42); 190 ta.tryVisit!((int n) { assert(n == 42); }); 191 assertThrown(ta.tryVisit!((string s) { assert(false); })); 192 } 193 194 195 private template validateHandlers(TU, VISITORS...) 196 { 197 import std.traits : isSomeFunction; 198 199 alias Types = TU.FieldTypes; 200 201 static foreach (int i; 0 .. VISITORS.length) { 202 static assert(!is(VISITORS[i]) || isSomeFunction!(VISITORS[i]), 203 "Visitor at index "~i.stringof~" must be a function/delegate literal: "~VISITORS[i].stringof); 204 static assert(anySatisfy!(matchesType!(VISITORS[i]), Types), 205 "Visitor at index "~i.stringof~" does not match any type of "~TU.FieldTypes.stringof); 206 } 207 } 208 209 private template matchesType(alias fun) { 210 import std.traits : ParameterTypeTuple, isSomeFunction; 211 212 template matchesType(T) { 213 static if (isSomeFunction!fun) { 214 alias Params = ParameterTypeTuple!fun; 215 static if (Params.length == 0 && isUnitType!T) enum matchesType = true; 216 else static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true; 217 else enum matchesType = false; 218 } else static if (!isUnitType!T) { 219 static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) { 220 alias Params = ParameterTypeTuple!(fun!T); 221 static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true; 222 else enum matchesType = false; 223 } else enum matchesType = false; 224 } else enum matchesType = false; 225 } 226 } 227 228 unittest { 229 class C {} 230 alias mt1 = matchesType!((C c) => true); 231 alias mt2 = matchesType!((c) { static assert(!is(typeof(c) == C)); }); 232 static assert(mt1!C); 233 static assert(!mt1!int); 234 static assert(mt2!int); 235 static assert(!mt2!C); 236 } 237 238 private template selectHandler(T, VISITORS...) 239 { 240 import std.traits : ParameterTypeTuple, isSomeFunction; 241 242 template typedIndex(int i, int matched_index = -1) { 243 static if (i < VISITORS.length) { 244 alias fun = VISITORS[i]; 245 static if (isSomeFunction!fun) { 246 alias Params = ParameterTypeTuple!fun; 247 static if (Params.length > 1) enum typedIndex = "Visitor at index "~i.stringof~" must not take more than one parameter."; 248 else static if (Params.length == 0 && is(T == void) || Params.length == 1 && is(T == Params[0])) { 249 static if (matched_index >= 0) enum typedIndex = "Vistor at index "~i.stringof~" conflicts with visitor at index "~matched_index~"."; 250 else enum typedIndex = typedIndex!(i+1, i); 251 } else enum typedIndex = typedIndex!(i+1, matched_index); 252 } else enum typedIndex = typedIndex!(i+1, matched_index); 253 } else enum typedIndex = matched_index; 254 } 255 256 template genericIndex(int i, int matched_index = -1) { 257 static if (i < VISITORS.length) { 258 alias fun = VISITORS[i]; 259 static if (!isSomeFunction!fun) { 260 static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) { 261 static if (ParameterTypeTuple!(fun!T).length == 1) { 262 static if (matched_index >= 0) enum genericIndex = "Only one generic visitor allowed"; 263 else enum genericIndex = genericIndex!(i+1, i); 264 } else enum genericIndex = "Generic visitor at index "~i.stringof~" must have a single parameter."; 265 } else enum genericIndex = "Visitor at index "~i.stringof~" (or its template instantiation with type "~T.stringof~") must be a valid function or delegate."; 266 } else enum genericIndex = genericIndex!(i+1, matched_index); 267 } else enum genericIndex = matched_index; 268 } 269 270 enum typed_index = typedIndex!0; 271 static if (is(T == void)) enum generic_index = -1; 272 else enum generic_index = genericIndex!0; 273 274 static if (is(typeof(typed_index) == string)) enum selectHandler = typed_index; 275 else static if (is(typeof(generic_index == string))) enum selectHandler = generic_index; 276 else static if (typed_index >= 0) alias selectHandler = VISITORS[typed_index]; 277 else static if (generic_index >= 0) alias selectHandler = VISITORS[generic_index]; 278 else enum selectHandler = null; 279 }