1 module universal.core.match; 2 3 import std.typetuple; 4 import std.functional; 5 import universal.meta; 6 import universal.core.product; 7 import universal.core.apply; 8 9 alias adjoin = universal.core.product.adjoin; 10 enum isFuncOrString(alias a) = isString!a || isParameterized!a; 11 12 /* 13 This module provides 3 ways to do compile-time pattern matching on a set of arguments. 14 It can function like an explicit overload set defined at the point of application 15 with a few extra capabilities. 16 17 These template functions take a compile-time list of function-like aliases (patterns) 18 and attempt to apply them to the run-time arguments. 19 20 matchOne resolves to the first pattern which compiles 21 matchAny resolves to a tuple containing all patterns which compile 22 canMatch resolves to a tuple containing bools stating whether each pattern compiled 23 24 they lift the pattern with universal.apply before attempting to apply the arguments, 25 so a pattern that tests true for a given set of arguments 26 may require that the first argument be expanded, if its a tuple 27 28 in matchAny and canMatch, the patterns maybe be interleaved with strings, 29 resulting in the returned tuple having named fields 30 canMatch can be used in this way to implement a concise, anonymous trait check 31 32 */ 33 34 template matchOne(patterns...) if(allSatisfy!(isParameterized, patterns)) 35 { 36 template matchOne(A...) 37 { 38 template tryPattern(uint i = 0) 39 { 40 static if(__traits(compiles, apply!(patterns[i])(A.init))) 41 alias tryPattern = apply!(patterns[i]); 42 else 43 alias tryPattern = tryPattern!(i+1); 44 } 45 template tryPattern(uint i : patterns.length) 46 { 47 static assert(0, 48 "couldn't match "~A.stringof 49 ~" to any "~patterns.stringof 50 ); 51 } 52 auto matchOne(A a) { return tryPattern(a); } 53 } 54 } 55 template matchAny(patterns...) if(allSatisfy!(isFuncOrString, patterns)) 56 { 57 template matchAny(A...) 58 { 59 alias names = Filter!(isString, patterns); 60 alias funcs = Filter!(isParameterized, patterns); 61 62 template tryPattern(uint i) 63 { 64 static if(__traits(compiles, apply!(funcs[i])(A.init))) 65 { 66 auto pattern(A a) { return a.apply!(funcs[i]); } 67 68 static if(names.length == funcs.length) 69 alias tryPattern = TypeTuple!(names[i], pattern); 70 else 71 alias tryPattern = pattern; 72 } 73 else 74 alias tryPattern = TypeTuple!(); 75 } 76 auto matchAny(A a) 77 { 78 import std.range : iota; 79 80 return a.adjoin!(staticMap!( 81 tryPattern, aliasSeqOf!(patterns.length.iota) 82 )); 83 } 84 } 85 } 86 template canMatch(patterns...) if(allSatisfy!(isFuncOrString, patterns)) 87 { 88 template canMatch(A...) 89 { 90 alias names = Filter!(isString, patterns); 91 alias funcs = Filter!(isParameterized, patterns); 92 93 template tryPattern(uint i) 94 { 95 static if(__traits(compiles, apply!(funcs[i])(A.init))) 96 enum tryPattern = true; 97 else 98 enum tryPattern = false; 99 } 100 101 auto canMatch(A a) 102 { 103 import std.range : iota; 104 import std.typecons; 105 106 return tuple!names(staticMap!( 107 tryPattern, aliasSeqOf!(funcs.length.iota) 108 )); 109 } 110 } 111 } 112 113 @("EXAMPLES") unittest 114 { 115 import std.typecons; 116 117 assert( 118 1.matchOne!( 119 s => s.ptr, 120 s => s * 3, 121 s => s + 1, 122 ) == 3 123 ); 124 125 assert( 126 1.matchAny!( 127 s => s.ptr, 128 s => s * 3, 129 s => s + 1, 130 ) == tuple(3,2) 131 ); 132 133 with( 134 1.matchAny!( 135 q{ptr}, s => s.ptr, 136 q{mul}, s => s * 3, 137 q{add}, s => s + 1, 138 ) 139 ) 140 assert( 141 !is(typeof(ptr)) 142 && mul == 3 143 && add == 2 144 ); 145 146 assert( 147 "hi".canMatch!( 148 q{len}, s => s.ptr, 149 q{mul}, s => s * 3, 150 q{add}, s => s + 1, 151 ) == tuple(true, false, false) 152 ); 153 154 with( 155 "hi".canMatch!( 156 q{ptr}, s => s.ptr, 157 q{mul}, s => s * 3, 158 q{add}, s => s + 1, 159 ) 160 ) 161 assert(ptr && !(mul) && !(add)); 162 }