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 }