Index: polemy/ast.d ================================================================== --- polemy/ast.d +++ polemy/ast.d @@ -10,11 +10,12 @@ /// abstract class AST { immutable LexPosition pos; - mixin SimpleConstructor; + mixin SimpleConstructor; + mixin SimplePatternMatch; } /// class StrLiteral : AST { Index: polemy/eval.d ================================================================== --- polemy/eval.d +++ polemy/eval.d @@ -107,69 +107,68 @@ } /// Entry point of this module /// If splitCtx = true, then inner variable declaration do not overwrite ctx. /// lay is the layer ID for evaluation (standard value semantics uses "@v"). - -Value eval(AST _e, Table ctx, bool splitCtx, Layer lay) -{ - if( auto e = cast(StrLiteral)_e ) - { - return new StrValue(e.data); - } - else - if( auto e = cast(IntLiteral)_e ) - { - return new IntValue(e.data); - } - else - if( auto e = cast(VarExpression)_e ) - { - return ctx.get(e.var, lay, e.pos); - } - else - if( auto e = cast(LayeredExpression)_e ) - { - return eval(e.expr, ctx, false, e.lay); - } - else - if( auto e = cast(LetExpression)_e ) - { - // for letrec, we need this, but should avoid overwriting???? - // ctx.set(e.var, "@v", new UndefinedValue, e.pos); - Value v = eval(e.init, ctx, true, lay); - if(splitCtx) - ctx = new Table(ctx, Table.Kind.NotPropagateSet); - ctx.set(e.var, (e.layer.length ? e.layer : lay), v, e.pos); - return eval(e.expr, ctx, false, lay); - } - else - if( auto e = cast(FuncallExpression)_e ) - { - Value _f = eval(e.fun, ctx, true, lay); - if( auto f = cast(FunValue)_f ) { - Value[] args; - foreach(a; e.args) - args ~= eval(a, ctx, true, lay); - return f.call(e.pos, lay, args); - } else +import std.typetuple; +Value eval(AST e, Table ctx, bool splitCtx, Layer lay) +{ + return e.match( + (StrLiteral e) + { + return new StrValue(e.data); + }, + (IntLiteral e) + { + return new IntValue(e.data); + }, + (VarExpression e) + { + return ctx.get(e.var, lay, e.pos); + }, + (LayeredExpression e) + { + return eval(e.expr, ctx, false, e.lay); + }, + (LetExpression e) + { + // for letrec, we need this, but should avoid overwriting???? + // ctx.set(e.var, "@v", new UndefinedValue, e.pos); + Value v = eval(e.init, ctx, true, lay); + if(splitCtx) + ctx = new Table(ctx, Table.Kind.NotPropagateSet); + ctx.set(e.var, (e.layer.length ? e.layer : lay), v, e.pos); + return eval(e.expr, ctx, false, lay); + }, + (FuncallExpression e) + { + Value _f = eval(e.fun, ctx, true, lay); + if( auto f = cast(FunValue)_f ) { + Value[] args; + foreach(a; e.args) + args ~= eval(a, ctx, true, lay); + return f.call(e.pos, lay, args); + } throw genex!RuntimeException(e.pos, "Non-funcion is applied"); - } - else - if( auto e = cast(FunLiteral)_e ) - { - return new FunValue(delegate Value(immutable LexPosition pos, string lay, Value[] args){ - if( e.params.length != args.length ) - throw genex!RuntimeException(e.pos, sprintf!"Argument Number Mismatch (%d required but %d given)" - (e.params.length, args.length)); - Table ctxNeo = new Table(ctx, Table.Kind.NotPropagateSet); - foreach(i,p; e.params) - ctxNeo.set(p.name, lay, args[i]); - return eval(e.funbody, ctxNeo, true, lay); - }); - } - throw genex!RuntimeException(_e.pos, sprintf!"Unknown Kind of Expression %s"(typeid(_e))); + }, + (FunLiteral e) + { + return new FunValue(delegate Value(immutable LexPosition pos, string lay, Value[] args){ + if( e.params.length != args.length ) + throw genex!RuntimeException(e.pos, sprintf!"Argument Number Mismatch (%d required but %d given)" + (e.params.length, args.length)); + Table ctxNeo = new Table(ctx, Table.Kind.NotPropagateSet); + foreach(i,p; e.params) + ctxNeo.set(p.name, lay, args[i]); + return eval(e.funbody, ctxNeo, true, lay); + }); + }, + delegate Value (AST e) + { + throw genex!RuntimeException(e.pos, sprintf!"Unknown Kind of Expression %s"(typeid(e))); + } + ); } unittest { auto r = assert_nothrow( evalString(`var x = 21; x + x*x;`) ); Index: polemy/lex.d ================================================================== --- polemy/lex.d +++ polemy/lex.d @@ -15,23 +15,25 @@ const LexPosition pos; this( const LexPosition pos, string msg, string file=null, size_t line=0, Throwable next=null ) { super(sprintf!"[%s] %s"(pos, msg), file, line, next); this.pos = pos; } } -/// +/// Thrown when encountered an EOF in the middle of a lexical token + class UnexpectedEOF : Exception { mixin ExceptionWithPosition; } -/// +/// Thrown when encountered a lexical error + class LexException : Exception { mixin ExceptionWithPosition; }; -/// Represents a position in a source code +/// Represents a position in source codes class LexPosition { immutable string filename; /// name of the source file immutable int lineno; /// 1-origin @@ -95,33 +97,33 @@ assert( !__traits(compiles, t.quoted=true) ); } /// Named Construtors for Lexer -Lexer lexerFromFile(T...)( string filename, T rest ) +Lexer lexerFromFile(T...)( string filename, T ln_cn ) { - return lexerFromString( std.file.readText(filename), filename, rest ); + return lexerFromString( std.file.readText(filename), filename, ln_cn ); } -/// Named Construtors for Lexer +/// Named Construtor for Lexer LexerT!(PositionedReader!CharSeq) /* ddoc doesn't recognize auto return... bugzilla:2581 */ lexerFromString(CharSeq)( CharSeq str, string filename="", int lineno=1, int column=1 ) { return new LexerT!(PositionedReader!CharSeq)( PositionedReader!CharSeq(str, filename, lineno, column) ); } -/// Standard Lexer Type (all you have to know is that this is a forward range of Tokens) +/// Standard Lexer Type (all you have to know is that this is a forward range of Tokens!) alias LexerT!(PositionedReader!string) Lexer; /// Lexer Implementation class LexerT(Reader) - if( isForwardRange!(Reader) && is(ElementType!(Reader) == dchar) ) + if( isForwardRange!(Reader) && is(ElementType!(Reader)==dchar) ) { /// Range primitive bool empty() /*@property*/ { return current is null; @@ -151,21 +153,22 @@ Reader reader; Token current; invariant() { - assert( reader.empty || !std.ctype.isspace(reader.front) ); + assert( reader.empty || !isSpace(reader.front) ); } this( Reader reader, Token current = null ) { this.reader = reader; readWhile!isSpace(); this.current = (current is null ? readNext() : current); } - public static { + public static + { bool isSpace (dchar c) { return std.ctype.isspace(c)!=0; } bool isSymbol (dchar c) { return 0x21<=c && c<=0x7f && !std.ctype.isalnum(c) && c!='_' && c!='\''; } bool isSSymbol (dchar c) { return "()[]{};@".canFind(c); } bool isMSymbol (dchar c) { return isSymbol(c) && !isSSymbol(c) && c!='"' && c!='#'; } bool isLetter (dchar c) { return !isSpace(c) && !isSymbol(c); } @@ -376,13 +379,12 @@ } /// Forward range for reader character by character, /// keeping track of position information and caring \r\n -> \n conversion. -private struct PositionedReader(CharSeq) - if( isForwardRange!(CharSeq) && is(ElementType!(CharSeq) == dchar) ) + if( isForwardRange!(CharSeq) && is(ElementType!(CharSeq)==dchar) ) { CharSeq buffer; string filename; int lineno; int column; Index: tricks/tricks.d ================================================================== --- tricks/tricks.d +++ tricks/tricks.d @@ -6,11 +6,13 @@ */ 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) { @@ -206,5 +208,179 @@ { 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); +}