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

Commit 6381f72

Browse files
committed
feat(parser): Add context to parser error messages.
1 parent 61b1ec8 commit 6381f72

File tree

2 files changed

+72
-16
lines changed

2 files changed

+72
-16
lines changed

lib/parser.dart

+22-15
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,14 @@ class Parser {
486486
List<Token> tokens = Parser.lex(text);
487487
Token token;
488488

489+
parserError(String s, [Token t]) {
490+
if (t == null && !tokens.isEmpty) t = tokens[0];
491+
String location = t == null ?
492+
'the end of the expression' :
493+
'at column ${t.index + 1} in';
494+
return 'Parser Error: $s $location [$text]';
495+
}
496+
evalError(String s) => 'Eval Error: $s while evaling [$text]';
489497

490498
Token peekToken() {
491499
if (tokens.length == 0)
@@ -531,7 +539,7 @@ class Parser {
531539

532540
ParsedFn consume(e1){
533541
if (expect(e1) == null) {
534-
throw "not impl consume error";
542+
throw parserError("Missing expected $e1");
535543
//throwError("is unexpected, expecting [" + e1 + "]", peek());
536544
}
537545
}
@@ -553,8 +561,7 @@ class Parser {
553561
Token token = expect();
554562
primary = token.primaryFn;
555563
if (primary == null) {
556-
throw "not impl error";
557-
//throwError("not a primary expression", token);
564+
throw parserError("Internal Angular Error: Unreachable code A.");
558565
}
559566
}
560567

@@ -571,7 +578,7 @@ class Parser {
571578
context = primary;
572579
primary = fieldAccess(primary);
573580
} else {
574-
throw "Impossible.. what?";
581+
throw parserError("Internal Angular Error: Unreachable code B.");
575582
}
576583
}
577584
stopSavingTokens(ts);
@@ -662,14 +669,14 @@ class Parser {
662669
// =========================
663670

664671
ParsedFn assignment() {
672+
var ts = saveTokens();
665673
var left = logicalOR();
674+
stopSavingTokens(ts);
666675
var right;
667676
var token;
668677
if ((token = expect('=')) != null) {
669678
if (!left.assignable) {
670-
throw "not impl bad assignment error";
671-
// throwError("implies assignment but [" +
672-
// text.substring(0, token.index) + "] can not be assigned to", token);
679+
throw parserError('Expression ${tokensText(ts)} is not assignable', token);
673680
}
674681
right = logicalOR();
675682
return new ParsedFn((scope, locals) =>
@@ -688,9 +695,9 @@ class Parser {
688695
var left = expression();
689696
var token;
690697
while(true) {
691-
if ((token = expect('|') != null)) {
698+
if ((token = expect('|')) != null) {
692699
//left = binaryFn(left, token.fn, filter());
693-
throw "not impl filter";
700+
throw parserError("Filters are not implemented", token);
694701
} else {
695702
return left;
696703
}
@@ -733,10 +740,10 @@ class Parser {
733740
}
734741
var userFn = fn(self, locals);
735742
if (userFn == null) {
736-
throw "Undefined function $fnName";
743+
throw evalError("Undefined function $fnName");
737744
}
738745
if (userFn is! Function) {
739-
throw "$fnName is not a function";
746+
throw evalError("$fnName is not a function");
740747
}
741748
return relaxFnApply(userFn, args);
742749
});
@@ -768,7 +775,7 @@ class Parser {
768775
} else if (o is Map) {
769776
return o[i.toString()]; // toString dangerous?
770777
}
771-
throw "not impl odd object access";
778+
throw evalError("Attempted field access on a non-list, non-map");
772779
}
773780

774781
setField(o, i, v) {
@@ -779,7 +786,7 @@ class Parser {
779786
} else if (o is Map) {
780787
o[i.toString()] = v; // toString dangerous?
781788
} else {
782-
throw "not impl odd object access";
789+
throw evalError("Attempting to set a field on a non-list, non-map");
783790
}
784791
return v;
785792
}
@@ -791,7 +798,7 @@ class Parser {
791798
var o = obj(self, locals),
792799
v, p;
793800

794-
if (o == null) return throw "not impl null obj"; // null
801+
if (o == null) return throw evalError('Accessing null object');
795802

796803
v = getField(o, i);
797804

@@ -846,7 +853,7 @@ class Parser {
846853
ParsedFn value = statements();
847854

848855
if (tokens.length != 0) {
849-
throw "not impl, error msg $tokens";
856+
throw parserError("Unconsumed token ${tokens[0].text}");
850857
}
851858
return value;
852859
}

test/parser_spec.dart

+50-1
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,52 @@ main() {
315315
expect(eval("'str ' + 4 + 4")).toEqual("str 44");
316316
});
317317

318+
expectEval(String expr) => expect(() => eval(expr));
319+
320+
// PARSER ERRORS
321+
322+
it('should throw a reasonable error for unconsumed tokens', () {
323+
expectEval(")").toThrow('Parser Error: Unconsumed token ) at column 1 in [)]');
324+
});
325+
326+
it('should throw a "not implemented" error for filters', () {
327+
expectEval("4|a").toThrow(
328+
'Parser Error: Filters are not implemented at column 2 in [4|a]');
329+
});
330+
331+
it('should throw on missing expected token', () {
332+
expectEval("a(b").toThrow('Parser Error: Missing expected ) the end of the expression [a(b]');
333+
});
334+
335+
it('should throw on bad assignment', () {
336+
expectEval("5=4").toThrow('Parser Error: Expression 5 is not assignable at column 2 in [5=4]');
337+
expectEval("array[5=4]").toThrow('Parser Error: Expression 5 is not assignable at column 8 in [array[5=4]]');
338+
});
339+
340+
// EVAL ERRORS
341+
342+
it('should throw on null object field access', () {
343+
expectEval("null[3]").toThrow(
344+
"Eval Error: Accessing null object while evaling [null[3]]");
345+
});
346+
347+
it('should throw on non-list, non-map field access', () {
348+
expectEval("6[3]").toThrow('Eval Error: Attempted field access on a non-list, non-map while evaling [6[3]]');
349+
expectEval("6[3]=2").toThrow('Eval Error: Attempting to set a field on a non-list, non-map while evaling [6[3]=2');
350+
});
351+
352+
353+
354+
it('should throw on undefined functions', () {
355+
expectEval("notAFn()").toThrow('Eval Error: Undefined function notAFn while evaling [notAFn()]');
356+
});
357+
358+
it('should throw on not-function function calls', () {
359+
expectEval("4()").toThrow('Eval Error: 4 is not a function while evaling [4()]');
360+
});
361+
362+
363+
318364
//// ==== IMPORTED ITs
319365
320366
it('should parse expressions', () {
@@ -382,11 +428,14 @@ main() {
382428
});
383429

384430
it('should evaluate assignments', () {
385-
scope = {'g': 4};
431+
scope = {'g': 4, 'arr': [3,4]};
386432

387433
expect(eval("a=12")).toEqual(12);
388434
expect(scope["a"]).toEqual(12);
389435

436+
expect(eval("arr[c=1]")).toEqual(4);
437+
expect(scope["c"]).toEqual(1);
438+
390439
expect(eval("x.y.z=123;")).toEqual(123);
391440
expect(scope["x"]["y"]["z"]).toEqual(123);
392441

0 commit comments

Comments
 (0)