diff --git a/lib/parser.dart b/lib/parser.dart index 4299adeda..658c4d3bb 100644 --- a/lib/parser.dart +++ b/lib/parser.dart @@ -486,6 +486,14 @@ class Parser { List tokens = Parser.lex(text); Token token; + parserError(String s, [Token t]) { + if (t == null && !tokens.isEmpty) t = tokens[0]; + String location = t == null ? + 'the end of the expression' : + 'at column ${t.index + 1} in'; + return 'Parser Error: $s $location [$text]'; + } + evalError(String s) => 'Eval Error: $s while evaling [$text]'; Token peekToken() { if (tokens.length == 0) @@ -531,7 +539,7 @@ class Parser { ParsedFn consume(e1){ if (expect(e1) == null) { - throw "not impl consume error"; + throw parserError("Missing expected $e1"); //throwError("is unexpected, expecting [" + e1 + "]", peek()); } } @@ -553,8 +561,7 @@ class Parser { Token token = expect(); primary = token.primaryFn; if (primary == null) { - throw "not impl error"; - //throwError("not a primary expression", token); + throw parserError("Internal Angular Error: Unreachable code A."); } } @@ -571,7 +578,7 @@ class Parser { context = primary; primary = fieldAccess(primary); } else { - throw "Impossible.. what?"; + throw parserError("Internal Angular Error: Unreachable code B."); } } stopSavingTokens(ts); @@ -662,14 +669,14 @@ class Parser { // ========================= ParsedFn assignment() { + var ts = saveTokens(); var left = logicalOR(); + stopSavingTokens(ts); var right; var token; if ((token = expect('=')) != null) { if (!left.assignable) { - throw "not impl bad assignment error"; -// throwError("implies assignment but [" + -// text.substring(0, token.index) + "] can not be assigned to", token); + throw parserError('Expression ${tokensText(ts)} is not assignable', token); } right = logicalOR(); return new ParsedFn((scope, locals) => @@ -688,9 +695,9 @@ class Parser { var left = expression(); var token; while(true) { - if ((token = expect('|') != null)) { + if ((token = expect('|')) != null) { //left = binaryFn(left, token.fn, filter()); - throw "not impl filter"; + throw parserError("Filters are not implemented", token); } else { return left; } @@ -733,10 +740,10 @@ class Parser { } var userFn = fn(self, locals); if (userFn == null) { - throw "Undefined function $fnName"; + throw evalError("Undefined function $fnName"); } if (userFn is! Function) { - throw "$fnName is not a function"; + throw evalError("$fnName is not a function"); } return relaxFnApply(userFn, args); }); @@ -768,7 +775,7 @@ class Parser { } else if (o is Map) { return o[i.toString()]; // toString dangerous? } - throw "not impl odd object access"; + throw evalError("Attempted field access on a non-list, non-map"); } setField(o, i, v) { @@ -779,7 +786,7 @@ class Parser { } else if (o is Map) { o[i.toString()] = v; // toString dangerous? } else { - throw "not impl odd object access"; + throw evalError("Attempting to set a field on a non-list, non-map"); } return v; } @@ -791,7 +798,7 @@ class Parser { var o = obj(self, locals), v, p; - if (o == null) return throw "not impl null obj"; // null + if (o == null) return throw evalError('Accessing null object'); v = getField(o, i); @@ -846,7 +853,7 @@ class Parser { ParsedFn value = statements(); if (tokens.length != 0) { - throw "not impl, error msg $tokens"; + throw parserError("Unconsumed token ${tokens[0].text}"); } return value; } diff --git a/test/parser_spec.dart b/test/parser_spec.dart index 3d1aad0a4..08cdec685 100644 --- a/test/parser_spec.dart +++ b/test/parser_spec.dart @@ -315,6 +315,52 @@ main() { expect(eval("'str ' + 4 + 4")).toEqual("str 44"); }); + expectEval(String expr) => expect(() => eval(expr)); + + // PARSER ERRORS + + it('should throw a reasonable error for unconsumed tokens', () { + expectEval(")").toThrow('Parser Error: Unconsumed token ) at column 1 in [)]'); + }); + + it('should throw a "not implemented" error for filters', () { + expectEval("4|a").toThrow( + 'Parser Error: Filters are not implemented at column 2 in [4|a]'); + }); + + it('should throw on missing expected token', () { + expectEval("a(b").toThrow('Parser Error: Missing expected ) the end of the expression [a(b]'); + }); + + it('should throw on bad assignment', () { + expectEval("5=4").toThrow('Parser Error: Expression 5 is not assignable at column 2 in [5=4]'); + expectEval("array[5=4]").toThrow('Parser Error: Expression 5 is not assignable at column 8 in [array[5=4]]'); + }); + + // EVAL ERRORS + + it('should throw on null object field access', () { + expectEval("null[3]").toThrow( + "Eval Error: Accessing null object while evaling [null[3]]"); + }); + + it('should throw on non-list, non-map field access', () { + expectEval("6[3]").toThrow('Eval Error: Attempted field access on a non-list, non-map while evaling [6[3]]'); + expectEval("6[3]=2").toThrow('Eval Error: Attempting to set a field on a non-list, non-map while evaling [6[3]=2'); + }); + + + + it('should throw on undefined functions', () { + expectEval("notAFn()").toThrow('Eval Error: Undefined function notAFn while evaling [notAFn()]'); + }); + + it('should throw on not-function function calls', () { + expectEval("4()").toThrow('Eval Error: 4 is not a function while evaling [4()]'); + }); + + + //// ==== IMPORTED ITs it('should parse expressions', () { @@ -382,11 +428,14 @@ main() { }); it('should evaluate assignments', () { - scope = {'g': 4}; + scope = {'g': 4, 'arr': [3,4]}; expect(eval("a=12")).toEqual(12); expect(scope["a"]).toEqual(12); + expect(eval("arr[c=1]")).toEqual(4); + expect(scope["c"]).toEqual(1); + expect(eval("x.y.z=123;")).toEqual(123); expect(scope["x"]["y"]["z"]).toEqual(123);