Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

feat(parser): Add context to parser error messages. #53

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions lib/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,14 @@ class Parser {
List<Token> 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)
Expand Down Expand Up @@ -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());
}
}
Expand All @@ -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.");
}
}

Expand All @@ -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);
Expand Down Expand Up @@ -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) =>
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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);

Expand Down Expand Up @@ -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;
}
Expand Down
51 changes: 50 additions & 1 deletion test/parser_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand Down Expand Up @@ -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);

Expand Down