@@ -7,9 +7,11 @@ module tricks.tricks; import tricks.test; import std.array : appender; import std.format : formattedWrite; -import core.exception : AssertError; +import core.exception; +import std.traits; +import std.typetuple; /// Simple Wrapper for std.format.doFormat string sprintf(string fmt, T...)(T params) @@ -207,4 +209,178 @@ mixin SimpleConstructor; mixin SimpleCompare; mixin SimpleToString; } + +/// Simple PatternMatcher + +/*mixin*/ +template SimplePatternMatch() +{ + SPM_Return!(PP) match(string fn=__FILE__, size_t ln=__LINE__, PP...)(PP pts) + { + foreach(i,_; pts) + { + alias pts[i] pt; // bug? pts[i]-->pt do not work + static if(__traits(compiles, SPM_isMatchTag(pt))) + { + if( auto v = cast(pt.dynamicType)this ) + return pt(v.tupleof); + } + else + static if(__traits(compiles, SPM_isMatchAny(pt))) + { + return pt(); + } + else + { + if( auto v = cast(SPM_PTT!(pt)[0])this ) + return pt(v); + } + } + SPM_throwAssertError(fn, ln, "pattern matching failure"); + assert(false); + } +} + +/// Pattern case clause + +SPM_MatchTag!(T, fn) when(T, alias fn)() +{ + SPM_MatchTag!(T, fn) m; + return m; +} + +/// Pattern case clause + +SPM_MatchAny!(fn) otherwise(alias fn)() +{ + SPM_MatchAny!(fn) m; + return m; +} + +// implementation detail of SimplePatternMatch + +void SPM_throwAssertError(T...)(T t) { core.exception.onAssertErrorMsg(t); } + +struct SPM_MatchTag(T, alias fn) +{ + alias T dynamicType; + auto opCall(typeof(T.tupleof) s) { return fn(s); } +} + +struct SPM_MatchAny(alias fn) +{ + auto opCall() { return fn(); } +} + +template SPM_PTT(alias p) +{ + alias ParameterTypeTuple!(p) SPM_PTT; +} + +template SPM_Each(P) +{ + static if(__traits(compiles, SPM_isMatchTag(P.init))) + alias typeof(P(P.dynamicType.tupleof)) SPM_Each; + else + static if(__traits(compiles, SPM_isMatchAny(P.init))) + alias typeof(P()) SPM_Each; + else + alias ReturnType!(P) SPM_Each; +} + +template SPM_aVoid(T:void, TS...) { alias SPM_aVoid!(TS) SPM_aVoid; } +template SPM_aVoid(T, TS...) { alias TypeTuple!(T,SPM_aVoid!(TS)) SPM_aVoid; } +template SPM_aVoid() { alias TypeTuple!() SPM_aVoid; } + +template SPM_Return(PP...) +{ + alias CommonType!(SPM_aVoid!(staticMap!(SPM_Each, PP))) SPM_Return; +} + +void SPM_isMatchTag(T,alias fn)(SPM_MatchTag!(T,fn)){} +void SPM_isMatchAny(alias fn)(SPM_MatchAny!(fn)){} + +unittest +{ + static abstract class Base { + mixin SimplePatternMatch; + } + class D1 : Base { + int x; + real y; + mixin SimpleConstructor; + } + class D2 : Base { + string s; + mixin SimpleConstructor; + } + class D3 : Base { + int[int]m; + mixin SimpleConstructor; + } + + Base d1 = new D1(1, 2.3); + Base d2 = new D2("foobar"); + Base d3 = new D3(null); (cast(D3)d3).m[1]=10; + + // normal dispatch + assert_eq( d1.match( + (D1 x){return 1;}, + (D2 x){return 2;}, + ), 1); + assert_eq( d2.match( + (D1 x){return 1;}, + (D2 x){return 2;}, + ), 2); + assert_throw!AssertError( d3.match( + (D1 x){return 1;}, + (D2 x){return 2;}, + )); + assert_eq( d3.match( + (D1 x){return 1;}, + (D2 x){return 2;}, + (Base x){return 3;}, + ), 3); + assert_eq( d2.match( + (D1 x){return 1;}, + (D2 x){return 2;}, + (Base x){return 3;}, + ), 2); + assert_eq( d2.match( + (D1 x){return 1;}, + (Base x){return 3;}, + (D2 x){return 2;}, + ), 3); + + // member decomposing match + assert_eq( d1.match( + when!(D1, (x, y){return x + cast(int)y;}), + when!(D2, (x){return x.length;}), + when!(D3, (x){return x[1];}), + ), 3); + assert_eq( d2.match( + when!(D1, (x, y){return x + cast(int)y;}), + when!(D2, (x){return x.length;}), + when!(D3, (x){return x[1];}), + ), 6); + assert_eq( d3.match( + when!(D1, (x, y){return x + cast(int)y;}), + when!(D2, (x){return x.length;}), + when!(D3, (x){return x[1];}), + ), 10); + assert_throw!AssertError( d3.match( + when!(D1, (x, y){return x + cast(int)y;}), + when!(D2, (x){return x.length;}), + )); + assert_eq( d2.match( + when!(D1, (x, y){return x + cast(int)y;}), + when!(D2, (x){return x.length;}), + otherwise!({return 999;}), + ), 6); + assert_eq( d2.match( + when!(D1, (x, y){return x + cast(int)y;}), + otherwise!({return 999;}), + when!(D2, (x){return x.length;}), + ), 999); +}