Index: .poseidon ================================================================== --- .poseidon +++ .poseidon @@ -31,14 +31,16 @@ d2stacktrace\dbghelp.d d2stacktrace\stacktrace.d main.d polemy\_common.d polemy\ast.d + polemy\eval.d polemy\lex.d polemy\parse.d - polemy\tricks.d polemy\value.d + tricks\test.d + tricks\tricks.d Index: build.bat ================================================================== --- build.bat +++ build.bat @@ -1,6 +1,6 @@ @setlocal ENABLEDELAYEDEXPANSION @set ARGS= -@for %%I in (main.d polemy\*.d) do @set ARGS=!ARGS! %%I +@for %%I in (main.d polemy\*.d tricks\*.d) do @set ARGS=!ARGS! %%I @if not exist bin mkdir bin @echo dmd -ofbin\polemy.exe -O -release -inline %ARGS% @dmd -ofbin\polemy.exe -O -release -inline %ARGS% Index: d2stacktrace/stacktrace.d ================================================================== --- d2stacktrace/stacktrace.d +++ d2stacktrace/stacktrace.d @@ -238,11 +238,11 @@ static Throwable.TraceInfo TraceHandler(void* ptr){ // modified by k.inaba to avoid a throw inside std.demangle.demangle // not quite thread safe Runtime.traceHandler(&core.runtime.defaultTraceHandler); scope(exit) Runtime.traceHandler(&TraceHandler); - + StackTrace trace = new StackTrace(); return trace.GetCallstack(); } public: @@ -341,11 +341,11 @@ memcpy(symName.ptr,Symbol.Name.ptr,symName.length); string symString = ""; if(symName[0] == 'D') symString = "_"; symString ~= symName; - + string demangeledName = demangle(symString); lineStr ~= demangeledName; DWORD zeichen = 0; if(Dbghelp.SymGetLineFromAddr64(hProcess,stackframe.AddrPC.Offset,&zeichen,&Line) == TRUE){ Index: polemy/_common.d ================================================================== --- polemy/_common.d +++ polemy/_common.d @@ -9,6 +9,7 @@ public import std.range; public import std.algorithm; public import std.conv : to; public import std.bigint; public import std.exception; -public import polemy.tricks; +public import tricks.tricks; +public import tricks.test; Index: polemy/ast.d ================================================================== --- polemy/ast.d +++ polemy/ast.d @@ -1,6 +1,6 @@ -/** +/** * Authors: k.inaba * License: NYSL 0.9982 http://www.kmonos.net/nysl/ * * Syntax tree for Polemy programming language. */ Index: polemy/eval.d ================================================================== --- polemy/eval.d +++ polemy/eval.d @@ -1,6 +1,6 @@ -/** +/** * Authors: k.inaba * License: NYSL 0.9982 http://www.kmonos.net/nysl/ * * Evaluator for Polemy programming language. */ @@ -11,146 +11,123 @@ 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){ +Table createGlobalContext() +{ + auto ctx = new Table; + // [TODO] autogenerate these typechecks + ctx.set("+", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ if( args.length != 2 ) - throw new PolemyRuntimeException("+ takes two arguments!! ["~to!string(pos)~"]"); + throw new RuntimeException(pos, "+ takes two arguments!!"); 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)~"]"); + throw new RuntimeException(pos, "cannot add non-integers"); })); - ctx.add("-", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + ctx.set("-", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ if( args.length != 2 ) - throw new PolemyRuntimeException("- takes two arguments!! ["~to!string(pos)~"]"); + throw new RuntimeException(pos, "- takes two arguments!!"); 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)~"]"); + throw new RuntimeException(pos, "cannot subtract non-integers"); })); - ctx.add("*", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + ctx.set("*", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ if( args.length != 2 ) - throw new PolemyRuntimeException("* takes two arguments!! ["~to!string(pos)~"]"); + throw new RuntimeException(pos, "* takes two arguments!!"); 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)~"]"); + throw new RuntimeException(pos, "cannot multiply non-integers"); })); - ctx.add("/", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + ctx.set("/", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ if( args.length != 2 ) - throw new PolemyRuntimeException("/ takes two arguments!! ["~to!string(pos)~"]"); + throw new RuntimeException(pos, "/ takes two arguments!!"); 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)~"]"); + throw new RuntimeException(pos, "cannot divide non-integers"); })); - ctx.add("<", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + ctx.set("<", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ if( args.length != 2 ) - throw new PolemyRuntimeException("< takes two arguments!! ["~to!string(pos)~"]"); + throw new RuntimeException(pos, "< takes two arguments!!"); 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)~"]"); + throw new RuntimeException(pos, "cannot compare non-integers"); })); - ctx.add(">", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + ctx.set(">", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ if( args.length != 2 ) - throw new PolemyRuntimeException("> takes two arguments!! ["~to!string(pos)~"]"); + throw new RuntimeException(pos, "> takes two arguments!!"); 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)~"]"); + throw new RuntimeException(pos, "cannot compare non-integers"); })); - ctx.add("print", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + ctx.set("print", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ foreach(a; args) write(a); writeln(""); - return new UndefinedValue; + return new IntValue(BigInt(178)); })); - ctx.add("if", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ + ctx.set("if", "@val", new FunValue(delegate Value(immutable LexPosition pos, Value[] args){ if( args.length != 3 ) - throw new PolemyRuntimeException("if takes three arguments!! ["~to!string(pos)~"]"); + throw new RuntimeException(pos, "if takes three arguments!!"); 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)~"]"); + throw new RuntimeException(pos, "type mismatch in if"); })); return ctx; } -Tuple!(Value,"val",Context,"ctx") evalString(T...)(T params) +/// Entry point of this module + +Tuple!(Value,"val",Table,"ctx") evalString(S,T...)(S str, T fn_ln_cn) { - return eval( parserFromString(params).parseProgram() ); + return eval( polemy.parse.parseString(str, fn_ln_cn) ); } -Tuple!(Value,"val",Context,"ctx") evalFile(T...)(T params) +Tuple!(Value,"val",Table,"ctx") evalFile(S, T...)(S filenae, T ln_cn) { - return eval( parserFromFile(params).parseProgram() ); + return eval( polemy.parse.parseFile(filename, ln_cn) ); } -Tuple!(Value,"val",Context,"ctx") eval(Program prog) +Tuple!(Value,"val",Table,"ctx") eval(AST e) { - Context ctx = createGlobalContext(); - return typeof(return)(eval(prog, ctx), ctx); + Table ctx = createGlobalContext(); + return typeof(return)(eval(e, 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) +Value eval(AST _e, Table ctx, bool splitCtx = true) { - 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 ) + if( auto e = cast(StrLiteral)_e ) { return new StrValue(e.data); } else - if( auto e = cast(IntLiteralExpression)_e ) + if( auto e = cast(IntLiteral)_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(VarExpression)_e ) + { + return ctx.get(e.var, "@val", e.pos); + } + else + if( auto e = cast(LetExpression)_e ) + { + // for letrec, we need this, but should avoid overwriting???? + // ctx.set(e.var, "@val", new UndefinedValue, e.pos); + Value v = eval(e.init, ctx, true); + ctx.set(e.var, "@val", v, e.pos); + return eval(e.expr, ctx); + } else if( auto e = cast(FuncallExpression)_e ) { Value _f = eval(e.fun, ctx); if( auto f = cast(FunValue)_f ) { @@ -157,74 +134,65 @@ 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)); + throw new RuntimeException(e.pos, "Non-funcion is applied"); } else - if( auto e = cast(FunLiteralExpression)_e ) + if( auto e = cast(FunLiteral)_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); + throw new 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.add(p, args[i]); + ctxNeo.set(p, "@val", args[i]); return eval(e.funbody, ctxNeo); }); } - throw new PolemyRuntimeException(sprintf!"Unknown Kind of Expression %s at [%s]"(typeid(_e), _e.pos)); + throw new RuntimeException(_e.pos, sprintf!"Unknown Kind of Expression %s"(typeid(_e))); } - + 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"]) ); +{ + auto r = assert_nothrow( evalString(`var x = 21; x + x*x;`) ); + assert_eq( r.val, new IntValue(BigInt(21+21*21)) ); + assert_eq( r.ctx.get("x","@val"), new IntValue(BigInt(21)) ); + assert_nothrow( r.ctx.get("x","@val") ); + assert_throw!RuntimeException( r.ctx.get("y","@val") ); } +unittest +{ + auto r = assert_nothrow( evalString(`var x = 21; var x = x + x*x;`) ); + assert_eq( r.val, new IntValue(BigInt(21+21*21)) ); + assert_eq( r.ctx.get("x","@val"), new IntValue(BigInt(21+21*21)) ); + assert_nothrow( r.ctx.get("x","@val") ); + assert_throw!RuntimeException( r.ctx.get("y","@val") ); +} unittest { - assert( collectException(evalString(`var x = 21; x = x + x*y;`)) ); - assert( collectException(evalString(`x=1;`)) ); + assert_nothrow( evalString(`print("Hello, world!");`) ); + assert_nothrow( evalString(`print(fun(){});`) ); } 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){ + assert_nothrow( evalString(`var fac = fun(x){ 1; }; - print(fac(3));`); - evalString(`var fac = fun(x){ + print(fac(3));`)); + assert_nothrow( evalString(`var fac = fun(x){ if(x) { x*fac(x-1); } else { 1; }; }; - print(fac(10));`); - evalString(`var fib = fun(x){ + print(fac(10));`)); + assert_nothrow( evalString(`var fib = fun(x){ if(x<2) { 1; } else { fib(x-1) + fib(x-2); }; }; - print(fib(10));`); + print(fib(10));`)); } Index: polemy/lex.d ================================================================== --- polemy/lex.d +++ polemy/lex.d @@ -13,27 +13,25 @@ class LexException : Exception { const LexPosition pos; - private this( const LexPosition pos, string msg ) - { super(sprintf!"%s [%s]"(msg, pos)); this.pos = pos; } -}; - + this( const LexPosition pos, string msg, string file="", int line=0 ) + { super(sprintf!"[%s] %s"(pos, msg), file, line); this.pos = pos; } +}; + /// Represents a position in a source code class LexPosition { immutable string filename; /// name of the source file immutable int lineno; /// 1-origin immutable int column; /// 1-origin - + + mixin SimpleClass; override string toString() const { return sprintf!"%s:%d:%d"(filename, lineno, column); } - - mixin SimpleConstructor; - mixin SimpleCompare; static immutable LexPosition dummy; static this(){ dummy = new immutable(LexPosition)("",0,0); } } @@ -61,13 +59,11 @@ { 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; - mixin SimpleCompare; - mixin SimpleToString; + mixin SimpleClass; } unittest { auto p = new immutable(LexPosition)("hello.cpp", 123, 45); Index: polemy/parse.d ================================================================== --- polemy/parse.d +++ polemy/parse.d @@ -20,11 +20,11 @@ } private auto createException(Lexer)(Lexer lex, string msg) { return new ParseException(lex.empty?null:lex.front.pos, msg); } -/// Entry point of this module +/// Entry points of this module auto parseString(S, T...)(S str, T fn_ln_cn) { return parserFromString(str, fn_ln_cn).parse(); } auto parseFile(S, T...)(S filename,T ln_cn) DELETED polemy/tricks.d Index: polemy/tricks.d ================================================================== --- polemy/tricks.d +++ polemy/tricks.d @@ -1,283 +0,0 @@ -/** - * 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 - -/*mixin*/ -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 - -/*mixin*/ -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") ); -} - -/// Mixing-in a simple toString method - -/*mixin*/ -template SimpleToString() -{ - override string toString() - { - string str = sprintf!"%s("(typeof(this).stringof); - foreach(i,mem; this.tupleof) - { - if(i) str ~= ","; - static if( is(typeof(mem) == std.bigint.BigInt) ) - str ~= std.bigint.toDecimalString(mem); - else - str ~= sprintf!"%s"(mem); - } - return str ~ ")"; - } -} - -version(unittest) import std.bigint; -unittest -{ - class Temp - { - int x; - string y; - BigInt z; - mixin SimpleConstructor; - mixin SimpleToString; - } - assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" ); -} - -/// Everything is in - -/*mixin*/ -template SimpleClass() -{ - mixin SimpleConstructor; - mixin SimpleCompare; - mixin SimpleToString; -} Index: polemy/value.d ================================================================== --- polemy/value.d +++ polemy/value.d @@ -1,6 +1,6 @@ -/** +/** * Authors: k.inaba * License: NYSL 0.9982 http://www.kmonos.net/nysl/ * * Runtime data structures for Polemy programming language. */ @@ -13,12 +13,11 @@ class RuntimeException : Exception { const LexPosition pos; this( const LexPosition pos, string msg ) - { super(sprintf!"%s [%s]"(msg, pos)); this.pos = pos; } - this( string msg ) { super(msg); this.pos = null; } + { super(sprintf!"%s at [%s]"(msg, pos)); this.pos = pos; } } /// Runtime values of Polemy abstract class Value @@ -65,22 +64,23 @@ enum Kind {PropagateSet, NotPropagateSet}; this( Table proto=null, Kind k = Kind.PropagateSet ) { this.prototype = proto; this.kind = k; } - void set(string i, Layer lay, Value v) + void set(string i, Layer lay, Value v, in LexPosition pos=null) { - if( !setIfExist(i, lay, v) ) - data[i][lay] = v; + if( setIfExist(i, lay, v) ) + return; + data[i][lay] = v; } - Value get(string i, Layer lay) + Value get(string i, Layer lay, in LexPosition pos=null) { if( i in data ) return data[i][lay]; if( prototype is null ) - throw new RuntimeException(sprintf!"variable %s not found"(i)); + throw new RuntimeException(pos, sprintf!"variable %s not found"(i)); return prototype.get(i, lay); } private: Table prototype; ADDED tricks/test.d Index: tricks/test.d ================================================================== --- tricks/test.d +++ tricks/test.d @@ -0,0 +1,98 @@ +/** + * Authors: k.inaba + * License: NYSL 0.9982 http://www.kmonos.net/nysl/ + * + * Unittest helpers. + */ +module tricks.test; +import std.conv : to; +import core.exception; + +/// 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 : "exception ["~e.toString()~"]"); } + onAssertErrorMsg(fn, ln, msg.length ? msg : "no execption"); +} + +/// Unittest helper that asserts an expression must not throw anything + +auto assert_nothrow(T, string fn=__FILE__, int ln=__LINE__)(lazy T t, string msg="") +{ + try + { return t(); } + catch(Throwable e) + { onAssertErrorMsg(fn, ln, msg.length ? msg : "exception ["~e.toString()~"]"); } + assert(false); +} + +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 : "exception ["~e.toString()~"]"); } + onAssertErrorMsg(fn, ln, msg.length ? msg : to!string(a)~" !"~op~to!string(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"? */ ADDED tricks/tricks.d Index: tricks/tricks.d ================================================================== --- tricks/tricks.d +++ tricks/tricks.d @@ -0,0 +1,210 @@ +/** + * Authors: k.inaba + * License: NYSL 0.9982 http://www.kmonos.net/nysl/ + * + * Common tricks and utilities for programming in D. + */ +module tricks.tricks; +import tricks.test; +import std.array : appender; +import std.format : formattedWrite; +import core.exception : 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" ); +} + +/// Create an exception with automatically completed filename and lineno information + +auto genex(ExceptionType, string fn=__FILE__, int ln=__LINE__, T...)(T params) +{ + static if( T.length > 0 && is(T[$-1] : Throwable) ) + return new ExceptionType(params[0..$-1], fn, ln, params[$-1]); + else + return new ExceptionType(params, fn, ln); +} + +unittest +{ + assert_ne( genex!Exception("msg").file, "" ); + assert_ne( genex!Exception("msg").line, 0 ); + assert_ne( genex!Exception("msg",new Exception("bar")).next, Exception.init ); +} + +/// Mixing-in the bean constructor for a class + +/*mixin*/ +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 + +/*mixin*/ +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") ); +} + +/// Mixing-in a simple toString method + +/*mixin*/ +template SimpleToString() +{ + override string toString() + { + string str = sprintf!"%s("(typeof(this).stringof); + foreach(i,mem; this.tupleof) + { + if(i) str ~= ","; + static if( is(typeof(mem) == std.bigint.BigInt) ) + str ~= std.bigint.toDecimalString(mem); + else + str ~= sprintf!"%s"(mem); + } + return str ~ ")"; + } +} + +version(unittest) import std.bigint; +unittest +{ + class Temp + { + int x; + string y; + BigInt z; + mixin SimpleConstructor; + mixin SimpleToString; + } + assert_eq( (new Temp(1,"foo",BigInt(42))).toString(), "Temp(1,foo,42)" ); +} + +/// Everything is in + +/*mixin*/ +template SimpleClass() +{ + mixin SimpleConstructor; + mixin SimpleCompare; + mixin SimpleToString; +}