Index: main.d ================================================================== --- main.d +++ main.d @@ -22,27 +22,21 @@ int nextlineno = 1; this() { ctx = createGlobalContext(); } bool tryRun( string s ) { - nextlineno ++; + scope(failure) + { buf = ""; lineno = nextlineno; } + buf ~= s; - try { - AST a = parseString(buf, "", lineno); - buf = ""; - lineno = nextlineno; - lastVal = eval(a, ctx); - } catch( LexException ) { - // always EOF exception, so wait next - return false; - } catch( ParseException e ) { - if( find(e.msg, "EOF")!="" ) // ultra ad-hoc - return false; - buf = ""; - lineno = nextlineno; - throw e; - } + nextlineno ++; + try + { lastVal = eval(parseString(buf, "", lineno), ctx); } + catch( UnexpectedEOF ) + { return false; } // wait + buf = ""; + lineno = nextlineno; return true; } bool singleInteraction() { Index: polemy/eval.d ================================================================== --- polemy/eval.d +++ polemy/eval.d @@ -175,15 +175,10 @@ 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_nothrow( evalString(`print("Hello, world!");`) ); - assert_nothrow( evalString(`print(fun(){});`) ); -} unittest { assert_eq( evalString(`let x=1; let y=(let x=2); x`).val, new IntValue(BigInt(1)) ); assert_eq( evalString(`let x=1; let y=(let x=2;fun(){x}); y()`).val, new IntValue(BigInt(2)) ); } @@ -195,24 +190,20 @@ assert_throw!Error( evalString(`let x=1; let _ = (@a x=2;2); @a(x)`) ); } unittest { - assert_nothrow( evalString(`var fac = fun(x){ - 1; - }; - print(fac(3));`)); - assert_nothrow( evalString(`var fac = fun(x){ + assert_eq( evalString(`var fac = fun(x){ if(x) { x*fac(x-1); } else { 1; }; }; - print(fac(10));`)); - assert_nothrow( evalString(`var fib = fun(x){ + fac(10);`).val, new IntValue(BigInt(10*9*8*5040))); + assert_eq( evalString(`var fib = fun(x){ if(x<2) { 1; } else { fib(x-1) + fib(x-2); }; }; - print(fib(10));`)); + fib(10);`).val, new IntValue(BigInt(89))); } Index: polemy/lex.d ================================================================== --- polemy/lex.d +++ polemy/lex.d @@ -9,16 +9,26 @@ import std.file : readText; import std.ctype : isspace, isalnum; /// Exception from this module -class LexException : Exception +/*mixin*/ +template ExceptionWithPosition() { 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; } +} + +class UnexpectedEOF : Exception +{ + mixin ExceptionWithPosition; +} + +class LexException : Exception +{ + mixin ExceptionWithPosition; }; /// Represents a position in a source code class LexPosition @@ -160,11 +170,11 @@ string readQuoted(const LexPosition pos){char[] buf; return readQuoted(pos,buf);} string readQuoted(const LexPosition pos, ref char[] buf) { if( reader.empty ) - throw genex!LexException(pos, "EOF found while lexing a quoted-string"); + throw genex!UnexpectedEOF(pos, "Quoted string not terminated"); dchar c = reader.front; reader.popFront; if( c == '"' ) return assumeUnique(buf); if( c == '\\' && !reader.empty ) { @@ -291,11 +301,11 @@ assert_eq( lexf.front.pos.column, 1 ); } unittest { - assert_throw!LexException( lexerFromString(`"`) ); + assert_throw!UnexpectedEOF( lexerFromString(`"`) ); } unittest { auto lex = lexerFromString(`my # comment should`~"\r\n"~`# hey!! Index: polemy/parse.d ================================================================== --- polemy/parse.d +++ polemy/parse.d @@ -11,19 +11,13 @@ /// Exception from this module class ParseException : Exception { - 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; } + mixin ExceptionWithPosition; } -private auto createException(Lexer)(Lexer lex, string msg) - { return new ParseException(lex.empty?null:lex.front.pos, msg); } - /// Entry points of this module auto parseString(S, T...)(S str, T fn_ln_cn) { return parserFromString(str, fn_ln_cn).parse(); } @@ -48,47 +42,47 @@ { AST parse() { auto e = Body(); if( !lex.empty ) - throw createException(lex, "input is not ended but parser came to the end"); + throw genex!ParseException(currentPosition(), "parsing ended but some tokens left"); return e; } AST Body() { - if( lex.empty || !lex.front.quoted && lex.front.str=="}" ) + if( lex.empty || !lex.front.quoted && ["}",")","]"].canFind(lex.front.str) ) return doNothingExpression(); auto saved = lex.save; auto pos = lex.front.pos; string kwd = lex.front.str; if( tryEat("let") || tryEat("var") || tryEat("def") || tryEat("@") ) { if( kwd == "@" ) { - kwd ~= eatId("after @"); + kwd ~= eatId("after @",true); if( tryEat("(") ) { lex = saved; goto asExpression; } } immutable LexPosition varpos = (lex.empty ? null : lex.front.pos); - string var = eatId("after "~kwd); + string var = eatId("after "~kwd,true); eat("=", "after "~kwd); kwd = (kwd[0]=='@' ? kwd : ""); // "let, var, def ==> neutral layer" auto e = E(0); - if( tryEat(";") && !lex.empty && (lex.front.quoted || (lex.front.str!="}" && lex.front.str!=")")) ) + if( tryEat(";") && !lex.empty && (lex.front.quoted || !["}",")","]"].canFind(lex.front.str)) ) return new LetExpression(pos, var, kwd, e, Body()); else return new LetExpression(pos, var, kwd, e, new VarExpression(varpos, var)); } else { asExpression: auto e = E(0); if( tryEat(";") && !lex.empty && (lex.front.quoted || (lex.front.str!="}" && lex.front.str!=")")) ) - return new LetExpression(pos, "_", "@val", e, Body()); + return new LetExpression(pos, "_", "", e, Body()); else return e; } } @@ -133,17 +127,18 @@ } } AST Funcall() { - auto e = BaseExpression(); + auto e = BaseExpression(); while( tryEat("(") ) { + auto pos = currentPosition(); AST[] args; while( !tryEat(")") ) { if( lex.empty ) - throw createException(lex,"Unexpected EOF"); + throw genex!UnexpectedEOF(pos,"Closing ')' for arguments not found"); args ~= E(0); if( !tryEat(",") ) { eat(")", "after function parameters"); break; } @@ -154,11 +149,11 @@ } AST BaseExpression() { if( lex.empty ) - throw createException(lex, "Reached EOF when tried to parse an expression"); + throw genex!UnexpectedEOF(currentPosition(), "Reached EOF when tried to parse an expression"); auto pos = lex.front.pos; if( lex.front.quoted ) { scope(exit) lex.popFront; @@ -231,13 +226,17 @@ Lexer lex; this(Lexer lex) { this.lex = lex; } void eat(string kwd, lazy string msg) { - if( !tryEat(kwd) ) - throw createException(lex, "'"~kwd~"' is expected "~msg~" but '" - ~(lex.empty ? "EOF" : lex.front.str)~"' occured"); + if( !tryEat(kwd) ) + if( lex.empty ) + throw genex!UnexpectedEOF( + currentPosition(), sprintf!"%s is expected for %s but not found"(kwd,msg)); + else + throw genex!ParseException( + currentPosition(), sprintf!"%s is expected for %s but not found"(kwd,msg)); } bool tryEat(string kwd) { if( lex.empty || lex.front.quoted || lex.front.str!=kwd ) @@ -244,27 +243,33 @@ return false; lex.popFront; return true; } - string eatId(lazy string msg) + string eatId(lazy string msg, bool allowQuoted=false) { - if( lex.empty || lex.front.quoted ) - throw createException(lex, "identifier is expected but not found "~msg); - string id = lex.front.str; - lex.popFront; - return id; + if( lex.empty ) + throw genex!UnexpectedEOF(currentPosition(), "identifier is expected but not found "~msg); + if( !allowQuoted && lex.front.quoted ) + throw genex!ParseException(currentPosition(), "identifier is expected but not found "~msg); + scope(exit) lex.popFront; + return lex.front.str; } bool isNumber(string s) { return find!(`a<'0'||'9'> "~e.toString()); } + onAssertErrorMsg(fn, ln, msg.length ? msg : "not thrown"); } /// Unittest helper that asserts an expression must not throw anything auto assert_nothrow(T, string fn=__FILE__, size_t ln=__LINE__)(lazy T t, string msg="") { try { return t(); } catch(Throwable e) - { onAssertErrorMsg(fn, ln, msg.length ? msg : "exception ["~e.toString()~"]"); } + { onAssertErrorMsg(fn, ln, msg.length ? msg : "bad exception\n >> "~e.toString()); } assert(false); } unittest { @@ -43,11 +43,11 @@ 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()) ); + assert_throw!AssertError( assert_throw!AssertError(error()) ); } /// Unittest helpers asserting two values are in some relation ==, !=, <, <=, >, >= template assertOp(string op)