Index: polemy/ast.d ================================================================== --- polemy/ast.d +++ polemy/ast.d @@ -1,85 +1,85 @@ -/** - * Authors: k.inaba - * License: NYSL 0.9982 http://www.kmonos.net/nysl/ - * - * Syntax tree for Polemy programming language. - */ -module polemy.ast; -import polemy._common; -import polemy.lex : LexPosition; - -alias Statement[] Program; - -abstract class Statement -{ - immutable LexPosition pos; - mixin SimpleConstructor; -} - -class DeclStatement : Statement -{ - string var; - Expression expr; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} - -class ExprStatement : Statement -{ - Expression expr; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} - -abstract class Expression -{ - immutable LexPosition pos; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} - -class StrLiteralExpression : Expression -{ - string data; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} - -class IntLiteralExpression : Expression -{ - BigInt data; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} - -class VarExpression : Expression -{ - string var; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} - -class AssignExpression : Expression -{ - Expression lhs; - Expression rhs; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} - -class FuncallExpression : Expression -{ - Expression fun; - Expression[] args; - this(immutable LexPosition pos, Expression fun, Expression[] args...) - { super(pos); this.fun=fun; this.args=args.dup; } - mixin SimpleCompare; // do not take "pos" into account -} - -class FunLiteralExpression : Expression -{ - string[] params; - Program funbody; - mixin SimpleConstructor; - mixin SimpleCompare; // do not take "pos" into account -} +/** + * Authors: k.inaba + * License: NYSL 0.9982 http://www.kmonos.net/nysl/ + * + * Syntax tree for Polemy programming language. + */ +module polemy.ast; +import polemy._common; +import polemy.lex : LexPosition; + +alias Statement[] Program; + +abstract class Statement +{ + immutable LexPosition pos; + mixin SimpleConstructor; +} + +class DeclStatement : Statement +{ + string var; + Expression expr; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} + +class ExprStatement : Statement +{ + Expression expr; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} + +abstract class Expression +{ + immutable LexPosition pos; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} + +class StrLiteralExpression : Expression +{ + string data; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} + +class IntLiteralExpression : Expression +{ + BigInt data; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} + +class VarExpression : Expression +{ + string var; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} + +class AssignExpression : Expression +{ + Expression lhs; + Expression rhs; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} + +class FuncallExpression : Expression +{ + Expression fun; + Expression[] args; + this(immutable LexPosition pos, Expression fun, Expression[] args...) + { super(pos); this.fun=fun; this.args=args.dup; } + mixin SimpleCompare; // do not take "pos" into account +} + +class FunLiteralExpression : Expression +{ + string[] params; + Program funbody; + mixin SimpleConstructor; + mixin SimpleCompare; // do not take "pos" into account +} Index: polemy/eval.d ================================================================== --- polemy/eval.d +++ polemy/eval.d @@ -1,232 +1,230 @@ -/** - * Authors: k.inaba - * License: NYSL 0.9982 http://www.kmonos.net/nysl/ - * - * Evaluator for Polemy programming language. - */ -module polemy.eval; -import polemy._common; -import polemy.lex : LexPosition; -import polemy.ast; -import polemy.parse; -import polemy.value; -import std.typecons; -import std.stdio; - -Context createGlobalContext() -{ - auto ctx = new Context; - ctx.add("+", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( args.length != 2 ) - throw new PolemyRuntimeException("+ takes two arguments!! ["~to!string(pos)~"]"); - if( auto x = cast(IntValue)args[0] ) - if( auto y = cast(IntValue)args[1] ) - return new IntValue(x.data+y.data); - throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); - })); - ctx.add("-", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( args.length != 2 ) - throw new PolemyRuntimeException("- takes two arguments!! ["~to!string(pos)~"]"); - if( auto x = cast(IntValue)args[0] ) - if( auto y = cast(IntValue)args[1] ) - return new IntValue(x.data-y.data); - throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); - })); - ctx.add("*", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( args.length != 2 ) - throw new PolemyRuntimeException("* takes two arguments!! ["~to!string(pos)~"]"); - if( auto x = cast(IntValue)args[0] ) - if( auto y = cast(IntValue)args[1] ) - return new IntValue(x.data*y.data); - throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); - })); - ctx.add("/", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( args.length != 2 ) - throw new PolemyRuntimeException("/ takes two arguments!! ["~to!string(pos)~"]"); - if( auto x = cast(IntValue)args[0] ) - if( auto y = cast(IntValue)args[1] ) - return new IntValue(x.data/y.data); - throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); - })); - ctx.add("<", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( args.length != 2 ) - throw new PolemyRuntimeException("< takes two arguments!! ["~to!string(pos)~"]"); - if( auto x = cast(IntValue)args[0] ) - if( auto y = cast(IntValue)args[1] ) - return new IntValue(BigInt(to!int(x.data < y.data))); - throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); - })); - ctx.add(">", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( args.length != 2 ) - throw new PolemyRuntimeException("> takes two arguments!! ["~to!string(pos)~"]"); - if( auto x = cast(IntValue)args[0] ) - if( auto y = cast(IntValue)args[1] ) - return new IntValue(BigInt(to!int(x.data>y.data))); - throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); - })); - ctx.add("print", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - foreach(a; args) - write(a); - writeln(""); - return new UndefinedValue; - })); - ctx.add("if", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( args.length != 3 ) - throw new PolemyRuntimeException("if takes three arguments!! ["~to!string(pos)~"]"); - if( auto x = cast(IntValue)args[0] ) - if( auto ft = cast(FunValue)args[1] ) - if( auto fe = cast(FunValue)args[2] ) - return (x.data == 0 ? fe : ft).call(pos,[]); - throw new PolemyRuntimeException("type mismatch in if ["~to!string(pos)~"]"); - })); - return ctx; -} - -Tuple!(Value,"val",Context,"ctx") evalString(T...)(T params) -{ - return eval( parserFromString(params).parseProgram() ); -} - -Tuple!(Value,"val",Context,"ctx") evalFile(T...)(T params) -{ - return eval( parserFromFile(params).parseProgram() ); -} - -Tuple!(Value,"val",Context,"ctx") eval(Program prog) -{ - Context ctx = createGlobalContext(); - return typeof(return)(eval(prog, ctx), ctx); -} - -Value eval(Program prog, Context ctx) -{ - Value v = new UndefinedValue; - foreach(s; prog) - v = eval(s, ctx); - return v; -} - -Value eval(Statement _s, Context ctx) -{ - if( auto s = cast(DeclStatement)_s ) - { - auto v = eval(s.expr, ctx); - ctx.add(s.var, v); - return v; - } - else - if( auto s = cast(ExprStatement)_s ) - { - return eval(s.expr, ctx); - } - throw new PolemyRuntimeException(sprintf!"Unknown Kind of Statement %s at [%s]"(typeid(_s), _s.pos)); -} - -Value eval(Expression _e, Context ctx) -{ - if( auto e = cast(StrLiteralExpression)_e ) - { - return new StrValue(e.data); - } - else - if( auto e = cast(IntLiteralExpression)_e ) - { - return new IntValue(e.data); - } - else - if( auto e = cast(VarExpression)_e ) - { - return ctx[e.var]; - } - else - if( auto e = cast(AssignExpression)_e ) - { - if( auto ev = cast(VarExpression)e.lhs ) - { - Value r = eval(e.rhs, ctx); - ctx[ev.var] = r; - return r; - } - throw new PolemyRuntimeException(sprintf!"Lhs of assignment must be a variable: %s"(e.pos)); - } - else - if( auto e = cast(FuncallExpression)_e ) - { - Value _f = eval(e.fun, ctx); - if( auto f = cast(FunValue)_f ) { - Value[] args; - foreach(a; e.args) - args ~= eval(a, ctx); - return f.call(e.pos, args); - } else - throw new PolemyRuntimeException(sprintf!"Non-funcion is applied at [%s]"(e.pos)); - } - else - if( auto e = cast(FunLiteralExpression)_e ) - { - return new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ - if( e.params.length != args.length ) - throw new PolemyRuntimeException(sprintf!"Argument Number Mismatch (%d required but %d given) at [%s]" - (e.params.length, args.length, e.pos)); - Context ctxNeo = new Context(ctx); - foreach(i,p; e.params) - ctxNeo.add(p, args[i]); - return eval(e.funbody, ctxNeo); - }); - } - throw new PolemyRuntimeException(sprintf!"Unknown Kind of Expression %s at [%s]"(typeid(_e), _e.pos)); -} - -/* -unittest -{ - auto r = evalString(`var x = 21; x = x + x*x;`); - assert( r.val == new IntValue(BigInt(21+21*21)) ); - assert( r.ctx["x"] == new IntValue(BigInt(21+21*21)) ); - assert( !collectException(r.ctx["x"]) ); - assert( collectException(r.ctx["y"]) ); -} -unittest -{ - assert( collectException(evalString(`var x = 21; x = x + x*y;`)) ); - assert( collectException(evalString(`x=1;`)) ); -} -unittest -{ - auto r = evalString(`var x = fun(a){1+a;}(2);`); - assert( r.ctx["x"] == new IntValue(BigInt(3)) ); - assert( r.val == new IntValue(BigInt(3)) ); -} -unittest -{ - auto r = evalString(`var x = 1; var f = fun(){x=x+1;}; f(); f(); f();`); - assert( r.ctx["x"] == new IntValue(BigInt(4)) ); - assert( r.val == new IntValue(BigInt(4)) ); -} -unittest -{ - evalString(`print("Hello, world!");`); - evalString(`print(fun(){});`); -} -unittest -{ - evalString(`var fac = fun(x){ - 1; - }; - print(fac(3));`); - evalString(`var fac = fun(x){ - if(x) - { x*fac(x-1); } - else - { 1; }; - }; - print(fac(10));`); - evalString(`var fib = fun(x){ - if(x<2) - { 1; } - else - { fib(x-1) + fib(x-2); }; - }; - print(fib(10));`); -} -*/ +/** + * Authors: k.inaba + * License: NYSL 0.9982 http://www.kmonos.net/nysl/ + * + * Evaluator for Polemy programming language. + */ +module polemy.eval; +import polemy._common; +import polemy.lex : LexPosition; +import polemy.ast; +import polemy.parse; +import polemy.value; +import std.typecons; +import std.stdio; + +Context createGlobalContext() +{ + auto ctx = new Context; + ctx.add("+", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( args.length != 2 ) + throw new PolemyRuntimeException("+ takes two arguments!! ["~to!string(pos)~"]"); + if( auto x = cast(IntValue)args[0] ) + if( auto y = cast(IntValue)args[1] ) + return new IntValue(x.data+y.data); + throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); + })); + ctx.add("-", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( args.length != 2 ) + throw new PolemyRuntimeException("- takes two arguments!! ["~to!string(pos)~"]"); + if( auto x = cast(IntValue)args[0] ) + if( auto y = cast(IntValue)args[1] ) + return new IntValue(x.data-y.data); + throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); + })); + ctx.add("*", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( args.length != 2 ) + throw new PolemyRuntimeException("* takes two arguments!! ["~to!string(pos)~"]"); + if( auto x = cast(IntValue)args[0] ) + if( auto y = cast(IntValue)args[1] ) + return new IntValue(x.data*y.data); + throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); + })); + ctx.add("/", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( args.length != 2 ) + throw new PolemyRuntimeException("/ takes two arguments!! ["~to!string(pos)~"]"); + if( auto x = cast(IntValue)args[0] ) + if( auto y = cast(IntValue)args[1] ) + return new IntValue(x.data/y.data); + throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); + })); + ctx.add("<", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( args.length != 2 ) + throw new PolemyRuntimeException("< takes two arguments!! ["~to!string(pos)~"]"); + if( auto x = cast(IntValue)args[0] ) + if( auto y = cast(IntValue)args[1] ) + return new IntValue(BigInt(to!int(x.data < y.data))); + throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); + })); + ctx.add(">", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( args.length != 2 ) + throw new PolemyRuntimeException("> takes two arguments!! ["~to!string(pos)~"]"); + if( auto x = cast(IntValue)args[0] ) + if( auto y = cast(IntValue)args[1] ) + return new IntValue(BigInt(to!int(x.data>y.data))); + throw new PolemyRuntimeException("cannot add non-integers ["~to!string(pos)~"]"); + })); + ctx.add("print", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + foreach(a; args) + write(a); + writeln(""); + return new UndefinedValue; + })); + ctx.add("if", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( args.length != 3 ) + throw new PolemyRuntimeException("if takes three arguments!! ["~to!string(pos)~"]"); + if( auto x = cast(IntValue)args[0] ) + if( auto ft = cast(FunValue)args[1] ) + if( auto fe = cast(FunValue)args[2] ) + return (x.data == 0 ? fe : ft).call(pos,[]); + throw new PolemyRuntimeException("type mismatch in if ["~to!string(pos)~"]"); + })); + return ctx; +} + +Tuple!(Value,"val",Context,"ctx") evalString(T...)(T params) +{ + return eval( parserFromString(params).parseProgram() ); +} + +Tuple!(Value,"val",Context,"ctx") evalFile(T...)(T params) +{ + return eval( parserFromFile(params).parseProgram() ); +} + +Tuple!(Value,"val",Context,"ctx") eval(Program prog) +{ + Context ctx = createGlobalContext(); + return typeof(return)(eval(prog, ctx), ctx); +} + +Value eval(Program prog, Context ctx) +{ + Value v = new UndefinedValue; + foreach(s; prog) + v = eval(s, ctx); + return v; +} + +Value eval(Statement _s, Context ctx) +{ + if( auto s = cast(DeclStatement)_s ) + { + auto v = eval(s.expr, ctx); + ctx.add(s.var, v); + return v; + } + else + if( auto s = cast(ExprStatement)_s ) + { + return eval(s.expr, ctx); + } + throw new PolemyRuntimeException(sprintf!"Unknown Kind of Statement %s at [%s]"(typeid(_s), _s.pos)); +} + +Value eval(Expression _e, Context ctx) +{ + if( auto e = cast(StrLiteralExpression)_e ) + { + return new StrValue(e.data); + } + else + if( auto e = cast(IntLiteralExpression)_e ) + { + return new IntValue(e.data); + } + else + if( auto e = cast(VarExpression)_e ) + { + return ctx[e.var]; + } + else + if( auto e = cast(AssignExpression)_e ) + { + if( auto ev = cast(VarExpression)e.lhs ) + { + Value r = eval(e.rhs, ctx); + ctx[ev.var] = r; + return r; + } + throw new PolemyRuntimeException(sprintf!"Lhs of assignment must be a variable: %s"(e.pos)); + } + else + if( auto e = cast(FuncallExpression)_e ) + { + Value _f = eval(e.fun, ctx); + if( auto f = cast(FunValue)_f ) { + Value[] args; + foreach(a; e.args) + args ~= eval(a, ctx); + return f.call(e.pos, args); + } else + throw new PolemyRuntimeException(sprintf!"Non-funcion is applied at [%s]"(e.pos)); + } + else + if( auto e = cast(FunLiteralExpression)_e ) + { + return new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + if( e.params.length != args.length ) + throw new PolemyRuntimeException(sprintf!"Argument Number Mismatch (%d required but %d given) at [%s]" + (e.params.length, args.length, e.pos)); + Context ctxNeo = new Context(ctx); + foreach(i,p; e.params) + ctxNeo.add(p, args[i]); + return eval(e.funbody, ctxNeo); + }); + } + throw new PolemyRuntimeException(sprintf!"Unknown Kind of Expression %s at [%s]"(typeid(_e), _e.pos)); +} + +unittest +{ + auto r = evalString(`var x = 21; x = x + x*x;`); + assert( r.val == new IntValue(BigInt(21+21*21)) ); + assert( r.ctx["x"] == new IntValue(BigInt(21+21*21)) ); + assert( !collectException(r.ctx["x"]) ); + assert( collectException(r.ctx["y"]) ); +} +unittest +{ + assert( collectException(evalString(`var x = 21; x = x + x*y;`)) ); + assert( collectException(evalString(`x=1;`)) ); +} +unittest +{ + auto r = evalString(`var x = fun(a){1+a;}(2);`); + assert( r.ctx["x"] == new IntValue(BigInt(3)) ); + assert( r.val == new IntValue(BigInt(3)) ); +} +unittest +{ + auto r = evalString(`var x = 1; var f = fun(){x=x+1;}; f(); f(); f();`); + assert( r.ctx["x"] == new IntValue(BigInt(4)) ); + assert( r.val == new IntValue(BigInt(4)) ); +} +unittest +{ + evalString(`print("Hello, world!");`); + evalString(`print(fun(){});`); +} +unittest +{ + evalString(`var fac = fun(x){ + 1; + }; + print(fac(3));`); + evalString(`var fac = fun(x){ + if(x) + { x*fac(x-1); } + else + { 1; }; + }; + print(fac(10));`); + evalString(`var fib = fun(x){ + if(x<2) + { 1; } + else + { fib(x-1) + fib(x-2); }; + }; + print(fib(10));`); +} Index: polemy/lex.d ================================================================== --- polemy/lex.d +++ polemy/lex.d @@ -1,394 +1,416 @@ -/** - * Authors: k.inaba - * License: NYSL 0.9982 http://www.kmonos.net/nysl/ - * - * Lexer for Polemy programming language. - */ +/** + * Authors: k.inaba + * License: NYSL 0.9982 http://www.kmonos.net/nysl/ + * + * Lexer for Polemy programming language. + */ module polemy.lex; import polemy._common; +import std.file : readText; +import std.ctype : isspace, isalnum; -import std.file : readText; -import std.string : munch; -import std.ctype; +/// Exception from this module +class LexException : Exception +{ + this( const LexPosition pos, string msg ) + { super(sprintf!"%s [%s]"(msg, pos)); this.pos = pos; } + const LexPosition pos; +}; + /// Represents a position in a source code - + class LexPosition { immutable string filename; /// name of the source file immutable int lineno; /// line number, 1, 2, ... - immutable int column; /// column, 1, 2, ... - - override string toString() const - { return sprintf!"%s:%d:%d"(filename, lineno, column); } + immutable int column; /// column, 1, 2, ... - mixin SimpleConstructor; + override string toString() const + { return sprintf!"%s:%d:%d"(filename, lineno, column); } + + mixin SimpleConstructor; mixin SimpleCompare; } unittest { - auto p = new LexPosition("hello.cpp", 123, 45); - auto q = new LexPosition("hello.cpp", 123, 46); + auto p = new LexPosition("hello.cpp", 123, 45); + auto q = new LexPosition("hello.cpp", 123, 46); assert_eq( p.filename, "hello.cpp" ); assert_eq( p.lineno, 123 ); - assert_eq( p.column, 45 ); - assert_eq( to!string(p), "hello.cpp:123:45" ); - assert_lt( p, q ); - assert_ne( p, q ); + assert_eq( p.column, 45 ); + assert_eq( to!string(p), "hello.cpp:123:45" ); + assert_lt( p, q ); + assert_ne( p, q ); assert( !__traits(compiles, new LexPosition) ); assert( !__traits(compiles, p.filename="foo") ); assert( !__traits(compiles, p.lineno =789) ); assert( !__traits(compiles, p.column =222) ); } - + /// Represents a lexer token - + class Token -{ +{ immutable LexPosition pos; /// Position where the token occurred in the source - immutable string str; /// The token string itself - immutable bool quoted; /// Was it a "quoted" token or unquoted? - - mixin SimpleConstructor; + immutable string str; /// The token string itself + immutable bool quoted; /// Was it a "quoted" token or unquoted? + + mixin SimpleConstructor; mixin SimpleCompare; } unittest { auto p = new immutable(LexPosition)("hello.cpp", 123, 45); auto t = new Token(p, "class", false); - auto u = new Token(p, "class", true); + auto u = new Token(p, "class", true); assert_eq( t.pos, p ); - assert_eq( t.str, "class" ); - assert( !t.quoted ); - assert_eq( t, new Token(p, "class", false) ); - assert_lt( t, new Token(p, "struct", false) ); - assert_ne( t, u ); - assert( u.quoted ); + assert_eq( t.str, "class" ); + assert( !t.quoted ); + assert_eq( t, new Token(p, "class", false) ); + assert_lt( t, new Token(p, "struct", false) ); + assert_ne( t, u ); + assert( u.quoted ); assert( !__traits(compiles, new Token) ); assert( !__traits(compiles, t.pos=p) ); - assert( !__traits(compiles, t.str=789) ); - assert( !__traits(compiles, t.quoted=true) ); + assert( !__traits(compiles, t.str=789) ); + assert( !__traits(compiles, t.quoted=true) ); +} + +/// Named Construtor for Lexer + +auto lexerFromFile(T...)( string filename, T rest ) +{ + return lexerFromString( std.file.readText(filename), filename, rest ); +} + +/// Named Construtor for Lexer + +auto 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 users have to know is that this is a forward range of Tokens) -/// Named Construtor for Lexer +alias LexerT!(PositionedReader!string) Lexer; + +/// Lexer Implementation -Lexer lexerFromFile(T...)( string filename, T rest ) -{ - return lexerFromString( std.file.readText(filename), filename, rest ); -} - -/// Named Construtor for Lexer - -Lexer lexerFromString( string str, string filename="", int lineno=1, int column=1 ) -{ - return new Lexer(str, filename, lineno, column); -} - -/// Lexer is a forward range of Tokens - -class Lexer -{ +class LexerT(Reader) + if( isForwardRange!(Reader) && is(ElementType!(Reader) == dchar) ) +{ /// Range primitive bool empty() /*@property*/ { return current is null; } - /// Range primitive + /// Range primitive Token front() /*@property*/ { return std.exception.enforce(current, "Lexer has already reached the end"); } - /// Range primitive + /// Range primitive void popFront() /*@property*/ { - std.exception.enforce(current, "Lexer has already reached the end"); + std.exception.enforce(current, "Lexer has already reached the end"); current = readNext(); } - /// Range primitive - Lexer save() /*@property*/ + /// Range primitive + typeof(this) save() /*@property*/ { - return new Lexer(this.tupleof); + return new typeof(this)(reader.save, current); } private: // implementation - - string buffer; - string filename; - int lineno; - int column; + + Reader reader; Token current; invariant() - { - assert( buffer.empty || !std.ctype.isspace(buffer[0]) ); - } - - this( string buffer, string filename, int lineno, int column, Token current=null ) { - this.buffer = buffer; - this.filename = filename; - this.lineno = lineno; - this.column = column; - skipws(); - this.current = (current is null ? readNext() : current); - } - - void skipws() - { - bool progress = false; - do - { - string ws = buffer.munch(" \t"); - column += ws.length; - progress = !ws.empty; - while( !buffer.empty && (buffer[0]=='\r' || buffer[0]=='\n') ) - { - progress = true; - if( buffer[0] == '\n' ) - buffer = buffer[1..$]; - else // if( buffer.front == '\r' ) - { - buffer = buffer[1..$]; - if( !buffer.empty && buffer[0]=='\n' ) - buffer = buffer[1..$]; - } - lineno ++; - column = 1; - } - }while( progress ); - } - - char readChar() - { - scope(exit) { - buffer = buffer[1..$]; - column ++; + assert( reader.empty || !std.ctype.isspace(reader.front) ); + } + + this( Reader reader, Token current = null ) + { + this.reader = reader; + readWhile!isSpace(); + this.current = (current is null ? readNext() : current); + } + + 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 !find("()[]{};", c).empty; } + bool isMSymbol (dchar c) { return isSymbol(c) && !isSSymbol(c); } + bool isLetter (dchar c) { return !isSpace(c) && !isSymbol(c); } + } + + string readQuoted(const LexPosition pos){char[] buf; return readQuoted(pos,buf);} + string readQuoted(const LexPosition pos, ref char[] buf) + { + if( reader.empty ) + throw new LexException(pos, "EOF found while lexing a quoted-string"); + dchar c = reader.front; + reader.popFront; + if( c == '"' ) + return assumeUnique(buf); + if( c == '\\' && !reader.empty ) { + if( reader.front=='"' ) { + reader.popFront; + return readQuoted(pos,buf ~= '\"'); + } + if( reader.front=='\\' ) { + reader.popFront; + return readQuoted(pos,buf ~= '\\'); + } } - return buffer[0]; + return readQuoted(pos,buf ~= c); } - /// This is the main lexing routine - Token readNext() + string readWhile(alias fn)() { - if( buffer.empty ) - return null; - scope(exit) - skipws(); + char[] buf; + for(; !reader.empty && fn(reader.front); reader.popFront) + buf ~= reader.front; + return assumeUnique(buf); + } - if( isSymbol(buffer[0]) ) + Token readNext() + { + if( reader.empty ) + return null; + scope(success) + readWhile!isSpace(); + if( reader.front == '#' ) // comment + { + reader = find(reader, '\n'); + readWhile!isSpace(); + return readNext(); + } + else if( reader.front == '"' ) // quoted + { + auto pos = reader.currentPosition(); + reader.popFront; + return new Token(pos, readQuoted(pos), true); + } + else if( isSSymbol(reader.front) ) // paren { - if( buffer[0] == '#' ) - { - // skip comment - while( !buffer.empty && (buffer[0]!='\n' && buffer[0]!='\r') ) - readChar(); - skipws(); - return readNext(); - } - else if( buffer[0] == '"' ) - { - // string literal - auto pos = currentPosition(); - string lit; - readChar(); - while( !buffer.empty && buffer[0]!='"' ) - { - // read one char - char c = readChar(); - if( c == '\\' ) - { - if( !buffer.empty && (buffer[0]=='\\' || buffer[0]=='"') ) - lit ~= readChar(); - else - lit ~= c; - } - else if( c == '\n' ) - { - lit ~= c; - lineno++; - column = 1; - } - else if( c == '\r' ) - { - if( !buffer.empty && buffer[0]=='\n' ) - readChar(); - lit ~= '\n'; - lineno++; - column = 1; - } - else - lit ~= c; - } - if( !buffer.empty ) - readChar(); - return new Token(pos, lit, true); - } - else - { - // normal symbol - auto pos = currentPosition(); - auto str = ""~readChar(); - return new Token(pos, str, false); - } + auto pos = reader.currentPosition(); + string s; s~=reader.front; reader.popFront; + return new Token(pos, s, false); + } + else if( isMSymbol(reader.front) ) // symbol + { + auto pos = reader.currentPosition(); + return new Token(pos, readWhile!isMSymbol(), false); } else { - auto pos = currentPosition(); - int i = 0; - while( i \n conversion. + +private +struct PositionedReader(CharSeq) + if( isForwardRange!(CharSeq) && is(ElementType!(CharSeq) == dchar) ) { -//!! be sure to run the unittest on the root of the source directory - auto lexf = lexerFromFile("polemy/lex.d"); - lexf = find!`a.str == "module"`(lexf); - assert_eq( lexf.front.str, "module" ); - assert_eq( lexf.front.pos.filename, "polemy/lex.d" ); - assert_eq( lexf.front.pos.lineno, 7 ); - assert_eq( lexf.front.pos.column, 1 ); - lexf.popFront; - assert_eq( lexf.front.str, "polemy" ); - assert_eq( lexf.front.pos.lineno, 7 ); - assert_eq( lexf.front.pos.column, 8 ); - lexf.popFront; - assert_eq( lexf.front.str, "." ); - lexf.popFront; - assert_eq( lexf.front.str, "lex" ); - lexf.popFront; - assert_eq( lexf.front.str, ";" ); - lexf.popFront; - assert_eq( lexf.front.str, "import" ); - assert_eq( lexf.front.pos.lineno, 8 ); - assert_eq( lexf.front.pos.column, 1 ); + CharSeq buffer; + string filename; + int lineno; + int column; + + /// Range primitive + bool empty() /*@property*/ + { + return buffer.empty; + } + + /// Range primitive + dchar front() /*@property*/ + { + dchar c = buffer.front; + return (c=='\r' ? '\n' : c); + } + + /// Range primitive + void popFront() /*@property*/ + { + dchar c = buffer.front; + buffer.popFront; + if( c=='\r' ) + { + if( !buffer.empty && buffer.front=='\n' ) + buffer.popFront; + c = '\n'; + } + if( c=='\n' ) + { + lineno ++; + column = 1; + } + else + column ++; + } + + /// Range primitive + typeof(this) save() /*@property*/ + { + return this; + } + + /// Get the current position + immutable(LexPosition) currentPosition() const + { + return new immutable(LexPosition)(filename, lineno, column); + } } unittest { - auto lex = lexerFromString(`my # comment should`~"\r\n"~`# hey!! -be ignored. -hahaha"hihihi""hu\\\"huhu"#123 aa -123 aa "aaa`~"\r\n"~`bbb # 123`~"\r\n"~`eee" -zzz -`); - Token[] ts = std.array.array(lex); - assert_eq( ts[0].str, "my" ); - assert_eq( ts[0].pos.lineno, 1 ); - assert( !ts[0].quoted ); - assert_eq( ts[1].str, "be" ); - assert_eq( ts[1].pos.lineno, 3 ); - assert( !ts[1].quoted ); - assert_eq( ts[2].str, "ignored" ); - assert( !ts[2].quoted ); - assert_eq( ts[3].str, "." ); - assert( !ts[3].quoted ); - assert_eq( ts[4].str, "hahaha" ); - assert_eq( ts[4].pos.lineno, 4 ); - assert( !ts[4].quoted ); - assert_eq( ts[5].str, "hihihi" ); - assert_eq( ts[5].pos.lineno, 4 ); - assert( ts[5].quoted ); - assert_eq( ts[6].str, `hu\"huhu` ); - assert_eq( ts[6].pos.lineno, 4 ); - assert( ts[6].quoted ); - assert_eq( ts[7].str, "123" ); - assert_eq( ts[7].pos.lineno, 5 ); - assert_eq( ts[8].str, "aa" ); - assert_eq( ts[9].pos.lineno, 5 ); - assert_eq( ts[9].str, "aaa\nbbb # 123\neee" ); - assert( ts[9].quoted ); - assert_eq( ts[10].pos.lineno, 8 ); - assert( !ts[10].quoted ); - assert_eq( ts.length, 11 ); + assert( isForwardRange!(PositionedReader!string) ); + assert( is(ElementType!(PositionedReader!string) == dchar) ); } Index: polemy/parse.d ================================================================== --- polemy/parse.d +++ polemy/parse.d @@ -1,370 +1,368 @@ -/** - * Authors: k.inaba - * License: NYSL 0.9982 http://www.kmonos.net/nysl/ - * - * Parser for Polemy programming language - */ -module polemy.parse; -import polemy._common; -import polemy.lex; -import polemy.ast; - -/// Parsing Failure - -class ParserException : Exception -{ -private: - this(string msg) { super(msg); } - static ParserException create(Lexer)(Lexer lex, string msg) - { - return new ParserException(sprintf!"[%s] %s"( - lex.empty ? "EOF" : to!string(lex.front.pos), msg)); - } -} - -/// Named Constructor of Parser - -auto parserFromLexer(Lexer)(Lexer lex) -{ - return new Parser!Lexer(lex); -} - -/// Named Constructor of Parser (just fwd to lexerFromString) - -auto parserFromString(T...)(T params) -{ - return parserFromLexer(polemy.lex.lexerFromString(params)); -} - -/// Named Constructor of Parser (just fwd to lexerFromFile) - -auto parserFromFile(T...)(T params) -{ - return parserFromLexer(polemy.lex.lexerFromFile(params)); -} - -/// Parser - -class Parser(Lexer) -{ - this(Lexer lex) - { - this.lex = lex; - } - - Program parseProgram() - { - Program p = parseStatements(); - if( !lex.empty ) { - auto e = ParserException.create(lex, "cannot reach eof"); - throw e; - } - return p; - } - - Program parseStatements() - { - Program p; - while( !lex.empty && (lex.front.quoted || lex.front.str!="}") ) - p ~= parseStatement(); - return p; - } - - Statement parseStatement() - { - auto saved = lex.save; - scope(failure) lex = saved; - - if( lex.empty ) - throw new ParserException("EOF during parsing a statement"); - auto pos = lex.front.pos; - - if( !lex.front.quoted && lex.front.str=="var" ) - { - // "var" Var "=" Expression ";" - lex.popFront; - string var = lex.front.str; - lex.popFront; - eat("=", "for variable declaration"); - auto parsed = new DeclStatement(pos, var, parseExpression()); - eat(";", "after variable declaration"); - return parsed; - } - else - { - // Expression ";" - auto parsed = new ExprStatement(pos, parseExpression()); - eat(";", "after statement"); - return parsed; - } - } - - Expression parseExpression() - { - auto saved = lex.save; - scope(failure) lex = saved; - return parseE(0); - } - - // [TODO] multi-char operators are not supported by the lexer... - static immutable string[][] operator_perferences = [ - ["="], - ["or"], - ["and"], - ["!="], - ["=="], - ["<","<=",">",">="], - ["|"], - ["^"], - ["&"], - ["<<", ">>"], - ["+","-"], - ["*","/","%"] - ]; - - Expression parseE(int level = 0) - { - if( operator_perferences.length <= level ) - return parseBaseExpression(); - else - { - auto ops = operator_perferences[level]; - auto e = parseE(level+1); - seq: - while( !lex.empty ) - { - auto pos = lex.front.pos; - foreach(op; ops) - if( tryEat(op) ) - { - if( op == "=" ) // right assoc - return new AssignExpression(e.pos, e, parseE(level)); - else - e = new FuncallExpression(e.pos, new VarExpression(pos, op), e, parseE(level+1)); - continue seq; - } - break; - } - return e; - } - } - - Expression parseBaseExpression() - { - if( lex.empty ) - throw new ParserException("EOF during parsing an expression"); - auto pos = lex.front.pos; - Expression e = parseBaseBaseExpression(); - while( tryEat("(") ) // funcall - { - Expression[] args; - while( !tryEat(")") ) { - if( lex.empty ) { - auto ex = ParserException.create(lex,"Unexpected EOF"); - throw ex; - } - args ~= parseE(); - if( !tryEat(",") ) { - eat(")", "after function parameters"); - break; - } - } - e = new FuncallExpression(pos, e, args); - } - return e; - } - - Expression parseBaseBaseExpression() - { - if( lex.empty ) - throw new ParserException("EOF during parsing an expression"); - auto pos = lex.front.pos; - - if( lex.front.quoted ) - { - scope(exit) lex.popFront; - return new StrLiteralExpression(pos, lex.front.str); - } - if( isNumber(lex.front.str) ) // is_number - { - scope(exit) lex.popFront; - return new IntLiteralExpression(pos, BigInt(cast(string)lex.front.str)); - } - if( tryEat("(") ) - { - auto e = parseE(); - eat(")", "after parenthesized expression"); - return e; - } - if( tryEat("if") ) - { - eat("(", "after if"); - auto cond = parseE(); - eat(")", "after if condition"); - auto thenPos = lex.front.pos; - eat("{", "after if condition"); - Statement[] th = parseStatements(); - eat("}", "after if-then body"); - Statement[] el; - auto elsePos = lex.front.pos; - if( tryEat("else") ) { - eat("{", "after else"); - el = parseStatements(); - eat("}", "after else body"); - } - return new FuncallExpression(pos, - new VarExpression(pos, "if"), - cond, - new FunLiteralExpression(thenPos, [], th), - new FunLiteralExpression(elsePos, [], el) - ); - } - - if( tryEat("fun") ) - { - eat("(", "after fun"); - string[] params; - while(!tryEat(")")) - { - if( lex.empty ) { - auto e = ParserException.create(lex,"Unexpected EOF"); - throw e; - } - if( lex.front.quoted ) { - auto e = ParserException.create(lex,"Identifier Expected for parameters"); - throw e; - } - params ~= lex.front.str; - lex.popFront; - if( !tryEat(",") ) { - eat(")", "after function parameters"); - break; - } - } - eat("{", "after function parameters"); - Statement[] funbody; - while(!tryEat("}")) { - if( lex.empty ) { - auto e = ParserException.create(lex,"Unexpected EOF"); - throw e; - } - funbody ~= parseStatement(); - } - return new FunLiteralExpression(pos, params, funbody); - } - scope(exit) lex.popFront; - return new VarExpression(pos, lex.front.str); - } - -private: - Lexer lex; - - void eat(string kwd, lazy string msg) - { - if( !tryEat(kwd) ) - { - auto e = ParserException.create(lex, "'"~kwd~"' is expected "~msg~" but '" - ~(lex.empty ? "EOF" : lex.front.str)~"' occured"); - throw e; - } - } - - bool tryEat(string kwd) - { - if( lex.empty || lex.front.quoted || lex.front.str!=kwd ) - return false; - lex.popFront; - return true; - } - - bool isNumber(string s) - { - return find!(`a<'0'||'9'",">="], + ["|"], + ["^"], + ["&"], + ["<<", ">>"], + ["+","-"], + ["*","/","%"] + ]; + + Expression parseE(int level = 0) + { + if( operator_perferences.length <= level ) + return parseBaseExpression(); + else + { + auto ops = operator_perferences[level]; + auto e = parseE(level+1); + seq: + while( !lex.empty ) + { + auto pos = lex.front.pos; + foreach(op; ops) + if( tryEat(op) ) + { + if( op == "=" ) // right assoc + return new AssignExpression(e.pos, e, parseE(level)); + else + e = new FuncallExpression(e.pos, new VarExpression(pos, op), e, parseE(level+1)); + continue seq; + } + break; + } + return e; + } + } + + Expression parseBaseExpression() + { + if( lex.empty ) + throw new ParserException("EOF during parsing an expression"); + auto pos = lex.front.pos; + Expression e = parseBaseBaseExpression(); + while( tryEat("(") ) // funcall + { + Expression[] args; + while( !tryEat(")") ) { + if( lex.empty ) { + auto ex = ParserException.create(lex,"Unexpected EOF"); + throw ex; + } + args ~= parseE(); + if( !tryEat(",") ) { + eat(")", "after function parameters"); + break; + } + } + e = new FuncallExpression(pos, e, args); + } + return e; + } + + Expression parseBaseBaseExpression() + { + if( lex.empty ) + throw new ParserException("EOF during parsing an expression"); + auto pos = lex.front.pos; + + if( lex.front.quoted ) + { + scope(exit) lex.popFront; + return new StrLiteralExpression(pos, lex.front.str); + } + if( isNumber(lex.front.str) ) // is_number + { + scope(exit) lex.popFront; + return new IntLiteralExpression(pos, BigInt(cast(string)lex.front.str)); + } + if( tryEat("(") ) + { + auto e = parseE(); + eat(")", "after parenthesized expression"); + return e; + } + if( tryEat("if") ) + { + eat("(", "after if"); + auto cond = parseE(); + eat(")", "after if condition"); + auto thenPos = lex.front.pos; + eat("{", "after if condition"); + Statement[] th = parseStatements(); + eat("}", "after if-then body"); + Statement[] el; + auto elsePos = lex.front.pos; + if( tryEat("else") ) { + eat("{", "after else"); + el = parseStatements(); + eat("}", "after else body"); + } + return new FuncallExpression(pos, + new VarExpression(pos, "if"), + cond, + new FunLiteralExpression(thenPos, [], th), + new FunLiteralExpression(elsePos, [], el) + ); + } + + if( tryEat("fun") ) + { + eat("(", "after fun"); + string[] params; + while(!tryEat(")")) + { + if( lex.empty ) { + auto e = ParserException.create(lex,"Unexpected EOF"); + throw e; + } + if( lex.front.quoted ) { + auto e = ParserException.create(lex,"Identifier Expected for parameters"); + throw e; + } + params ~= lex.front.str; + lex.popFront; + if( !tryEat(",") ) { + eat(")", "after function parameters"); + break; + } + } + eat("{", "after function parameters"); + Statement[] funbody; + while(!tryEat("}")) { + if( lex.empty ) { + auto e = ParserException.create(lex,"Unexpected EOF"); + throw e; + } + funbody ~= parseStatement(); + } + return new FunLiteralExpression(pos, params, funbody); + } + scope(exit) lex.popFront; + return new VarExpression(pos, lex.front.str); + } + +private: + Lexer lex; + + void eat(string kwd, lazy string msg) + { + if( !tryEat(kwd) ) + { + auto e = ParserException.create(lex, "'"~kwd~"' is expected "~msg~" but '" + ~(lex.empty ? "EOF" : lex.front.str)~"' occured"); + throw e; + } + } + + bool tryEat(string kwd) + { + if( lex.empty || lex.front.quoted || lex.front.str!=kwd ) + return false; + lex.popFront; + return true; + } + + bool isNumber(string s) + { + return find!(`a<'0'||'9', >= - -template assertOp(string op) -{ - void assertOp(A, B, string fn=__FILE__, int ln=__LINE__)(A a, B b, string msg="") - { - try { - if( mixin("a"~op~"b") ) return; - } catch(Throwable e) { - onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e)); - } - onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"%s !%s %s"(a,op,b)); - } -} - -alias assertOp!(`==`) assert_eq; -alias assertOp!(`!=`) assert_ne; -alias assertOp!(`<`) assert_lt; -alias assertOp!(`<=`) assert_le; -alias assertOp!(`>`) assert_gt; -alias assertOp!(`>=`) assert_ge; - -unittest -{ - assert_nothrow( assert_eq("foo", "foo") ); - assert_nothrow( assert_ne("foo", "bar") ); - assert_nothrow( assert_lt("bar", "foo") ); - assert_nothrow( assert_le("bar", "foo") ); - assert_nothrow( assert_le("bar", "bar") ); - assert_nothrow( assert_gt("foo", "bar") ); - assert_nothrow( assert_ge("foo", "bar") ); - assert_nothrow( assert_ge("bar", "bar") ); - - assert_throw!AssertError( assert_eq("foo", "bar") ); - assert_throw!AssertError( assert_ne("foo", "foo") ); - assert_throw!AssertError( assert_lt("foo", "foo") ); - assert_throw!AssertError( assert_lt("foo", "bar") ); - assert_throw!AssertError( assert_le("foo", "bar") ); - assert_throw!AssertError( assert_gt("bar", "bar") ); - assert_throw!AssertError( assert_gt("bar", "foo") ); - assert_throw!AssertError( assert_ge("bar", "foo") ); - - class Temp { bool opEquals(int x){return x/x==x;} } - assert_throw!AssertError( assert_eq(new Temp, 0) ); - assert_nothrow ( assert_eq(new Temp, 1) ); - assert_throw!AssertError( assert_eq(new Temp, 2) ); -} - -/* [Todo] is there any way to clearnly implement "assert_compiles" and "assert_not_compile"? */ - -/// Mixing-in the bean constructor for a class - -template SimpleConstructor() -{ - static if( is(typeof(super) == Object) || super.tupleof.length==0 ) - this( typeof(this.tupleof) params ) - { - static if(this.tupleof.length>0) - this.tupleof = params; - } - else - this( typeof(super.tupleof) ps, typeof(this.tupleof) params ) - { - // including (only) the direct super class members - // may not always be a desirable choice, but should work for many cases - super(ps); - static if(this.tupleof.length>0) - this.tupleof = params; - } -} - -unittest -{ - class Temp - { - int x; - string y; - mixin SimpleConstructor; - } - assert_eq( (new Temp(1,"foo")).x, 1 ); - assert_eq( (new Temp(1,"foo")).y, "foo" ); - assert( !__traits(compiles, new Temp) ); - assert( !__traits(compiles, new Temp(1)) ); - assert( !__traits(compiles, new Temp("foo",1)) ); - - class Tomp : Temp - { - real z; - mixin SimpleConstructor; - } - assert_eq( (new Tomp(1,"foo",2.5)).x, 1 ); - assert_eq( (new Tomp(1,"foo",2.5)).y, "foo" ); - assert_eq( (new Tomp(1,"foo",2.5)).z, 2.5 ); - assert( !__traits(compiles, new Tomp(3.14)) ); - - // shiyo- desu. Don't use in this way. - // Tamp tries to call new Tomp(real) (because it only sees Tomp's members), - // but it fails because Tomp takes (int,string,real). - assert( !__traits(compiles, { - class Tamp : Tomp - { - mixin SimpleConstructor; - } - }) ); -} - -/// Mixing-in the MOST-DERIVED-member-wise comparator for a class - -template SimpleCompare() -{ - override bool opEquals(Object rhs_) const - { - if( auto rhs = cast(typeof(this))rhs_ ) - { - foreach(i,_; this.tupleof) - if( this.tupleof[i] != rhs.tupleof[i] ) - return false; - return true; - } - assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_))); - } - - override hash_t toHash() const - { - hash_t h = 0; - foreach(mem; this.tupleof) - h += typeid(mem).getHash(&mem); - return h; - } - - override int opCmp(Object rhs_) const - { - if( auto rhs = cast(typeof(this))rhs_ ) - { - foreach(i,_; this.tupleof) - if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i])) - return c; - return 0; - } - assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_))); - } -} - -unittest -{ - class Temp - { - int x; - string y; - mixin SimpleConstructor; - mixin SimpleCompare; - } - assert_eq( new Temp(1,"foo"), new Temp(1,"foo") ); - assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash ); - assert_ne( new Temp(1,"foo"), new Temp(2,"foo") ); - assert_ne( new Temp(1,"foo"), new Temp(1,"bar") ); - assert_gt( new Temp(1,"foo"), new Temp(1,"bar") ); - assert_lt( new Temp(1,"foo"), new Temp(2,"bar") ); - assert_ge( new Temp(1,"foo"), new Temp(1,"foo") ); - - class TempDummy - { - int x; - string y; - mixin SimpleConstructor; - mixin SimpleCompare; - } - assert_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") ); - assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") ); -} +/** + * Authors: k.inaba + * License: NYSL 0.9982 http://www.kmonos.net/nysl/ + * + * Common tricks and utilities for programming in D. + */ +module polemy.tricks; +import std.array : appender; +import std.format : formattedWrite; +import core.exception : onAssertErrorMsg, AssertError; + +/// Simple Wrapper for std.format.doFormat + +string sprintf(string fmt, T...)(T params) +{ + auto writer = appender!string(); + formattedWrite(writer, fmt, params); + return writer.data; +} + +unittest +{ + assert( sprintf!"%s == %d"("1+2", 3) == "1+2 == 3" ); + assert( sprintf!"%s == %04d"("1+2", 3) == "1+2 == 0003" ); +} + +/// Unittest helper that asserts an expression must throw something + +void assert_throw(ExceptionType, T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="") +{ + try { + t(); + } catch(ExceptionType) { + return; + } catch(Throwable e) { + onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e)); + } + onAssertErrorMsg(fn, ln, msg.length ? msg : "no execption"); +} + +/// Unittest helper that asserts an expression must not throw anything + +void assert_nothrow(T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="") +{ + try { + t(); + } catch(Throwable e) { + onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e)); + } +} + +unittest +{ + auto error = {throw new Error("hello");}; + auto nothing = (){}; + auto assertError = {assert(0);}; + + assert_nothrow ( assert_nothrow(nothing()) ); + assert_throw!AssertError( assert_nothrow(error()) ); + assert_throw!AssertError( assert_nothrow(assertError()) ); + + assert_nothrow ( assert_throw!Error(error()) ); + assert_throw!AssertError( assert_throw!Error(nothing()) ); + assert_nothrow ( assert_throw!Error(assertError()) ); + assert_throw!AssertError( assert_throw!AssertError(error()) ); +} + +/// Unittest helpers asserting two values are in some relation ==, !=, <, <=, >, >= + +template assertOp(string op) +{ + void assertOp(A, B, string fn=__FILE__, int ln=__LINE__)(A a, B b, string msg="") + { + try { + if( mixin("a"~op~"b") ) return; + } catch(Throwable e) { + onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"exception [%s]"(e)); + } + onAssertErrorMsg(fn, ln, msg.length ? msg : sprintf!"%s !%s %s"(a,op,b)); + } +} + +alias assertOp!(`==`) assert_eq; +alias assertOp!(`!=`) assert_ne; +alias assertOp!(`<`) assert_lt; +alias assertOp!(`<=`) assert_le; +alias assertOp!(`>`) assert_gt; +alias assertOp!(`>=`) assert_ge; + +unittest +{ + assert_nothrow( assert_eq(1, 1) ); + assert_nothrow( assert_ne(1, 0) ); + assert_nothrow( assert_lt(0, 1) ); + assert_nothrow( assert_le(0, 1) ); + assert_nothrow( assert_le(0, 0) ); + assert_nothrow( assert_gt(1, 0) ); + assert_nothrow( assert_ge(1, 0) ); + assert_nothrow( assert_ge(0, 0) ); + + assert_throw!AssertError( assert_eq(1, 0) ); + assert_throw!AssertError( assert_ne(1, 1) ); + assert_throw!AssertError( assert_lt(1, 1) ); + assert_throw!AssertError( assert_lt(1, 0) ); + assert_throw!AssertError( assert_le(1, 0) ); + assert_throw!AssertError( assert_gt(0, 0) ); + assert_throw!AssertError( assert_gt(0, 1) ); + assert_throw!AssertError( assert_ge(0, 1) ); + + class Temp { bool opEquals(int x){return x/x==x;} } + assert_throw!AssertError( assert_eq(new Temp, 0) ); + assert_nothrow ( assert_eq(new Temp, 1) ); + assert_throw!AssertError( assert_eq(new Temp, 2) ); +} + +/* [Todo] is there any way to clearnly implement "assert_compiles" and "assert_not_compile"? */ + +/// Mixing-in the bean constructor for a class + +template SimpleConstructor() +{ + static if( is(typeof(super) == Object) || super.tupleof.length==0 ) + this( typeof(this.tupleof) params ) + { + static if(this.tupleof.length>0) + this.tupleof = params; + } + else + this( typeof(super.tupleof) ps, typeof(this.tupleof) params ) + { + // including (only) the direct super class members + // may not always be a desirable choice, but should work for many cases + super(ps); + static if(this.tupleof.length>0) + this.tupleof = params; + } +} + +unittest +{ + class Temp + { + int x; + string y; + mixin SimpleConstructor; + } + assert_eq( (new Temp(1,"foo")).x, 1 ); + assert_eq( (new Temp(1,"foo")).y, "foo" ); + assert( !__traits(compiles, new Temp) ); + assert( !__traits(compiles, new Temp(1)) ); + assert( !__traits(compiles, new Temp("foo",1)) ); + + class Tomp : Temp + { + real z; + mixin SimpleConstructor; + } + assert_eq( (new Tomp(1,"foo",2.5)).x, 1 ); + assert_eq( (new Tomp(1,"foo",2.5)).y, "foo" ); + assert_eq( (new Tomp(1,"foo",2.5)).z, 2.5 ); + assert( !__traits(compiles, new Tomp(3.14)) ); + + // shiyo- desu. Don't use in this way. + // Tamp tries to call new Tomp(real) (because it only sees Tomp's members), + // but it fails because Tomp takes (int,string,real). + assert( !__traits(compiles, { + class Tamp : Tomp + { + mixin SimpleConstructor; + } + }) ); +} + +/// Mixing-in the MOST-DERIVED-member-wise comparator for a class + +template SimpleCompare() +{ + override bool opEquals(Object rhs_) const + { + if( auto rhs = cast(typeof(this))rhs_ ) + { + foreach(i,_; this.tupleof) + if( this.tupleof[i] != rhs.tupleof[i] ) + return false; + return true; + } + assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_))); + } + + override hash_t toHash() const + { + hash_t h = 0; + foreach(mem; this.tupleof) + h += typeid(mem).getHash(&mem); + return h; + } + + override int opCmp(Object rhs_) const + { + if( auto rhs = cast(typeof(this))rhs_ ) + { + foreach(i,_; this.tupleof) + if(auto c = typeid(_).compare(&this.tupleof[i],&rhs.tupleof[i])) + return c; + return 0; + } + assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_))); + } +} + +unittest +{ + class Temp + { + int x; + string y; + mixin SimpleConstructor; + mixin SimpleCompare; + } + assert_eq( new Temp(1,"foo"), new Temp(1,"foo") ); + assert_eq( (new Temp(1,"foo")).toHash, (new Temp(1,"foo")).toHash ); + assert_ne( new Temp(1,"foo"), new Temp(2,"foo") ); + assert_ne( new Temp(1,"foo"), new Temp(1,"bar") ); + assert_gt( new Temp(1,"foo"), new Temp(1,"bar") ); + assert_lt( new Temp(1,"foo"), new Temp(2,"bar") ); + assert_ge( new Temp(1,"foo"), new Temp(1,"foo") ); + + class TempDummy + { + int x; + string y; + mixin SimpleConstructor; + mixin SimpleCompare; + } + assert_throw!AssertError( new Temp(1,"foo") == new TempDummy(1,"foo") ); + assert_throw!AssertError( new Temp(1,"foo") <= new TempDummy(1,"foo") ); +}