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

Commit 5026444

Browse files
committed
feat(parser): Better error messages for bad functions.
1 parent ca003b2 commit 5026444

File tree

2 files changed

+64
-22
lines changed

2 files changed

+64
-22
lines changed

lib/parser.dart

+23-10
Original file line numberDiff line numberDiff line change
@@ -500,14 +500,25 @@ class Parser {
500500
return null;
501501
}
502502

503+
/**
504+
* Token savers are synchronous lists that allows Parser functions to
505+
* access the tokens parsed during some amount of time. They are useful
506+
* for printing helpful debugging messages.
507+
*/
508+
List<List<Token>> tokenSavers = [];
509+
List<Token> saveTokens() { var n = []; tokenSavers.add(n); return n; }
510+
stopSavingTokens(x) { if (!tokenSavers.remove(x)) { throw 'bad token saver'; } return x; }
511+
tokensText(List x) => x.map((x) => x.text).join();
512+
503513
Token expect([String e1, String e2, String e3, String e4]){
504514
Token token = peek(e1, e2, e3, e4);
505515
if (token != null) {
506516
// TODO json
507517
// if (json && !token.json) {
508518
// throwError("is not valid json", token);
509519
// }
510-
tokens.removeAt(0);
520+
var consumed = tokens.removeAt(0);
521+
tokenSavers.forEach((ts) => ts.add(consumed));
511522
return token;
512523
}
513524
return null;
@@ -520,17 +531,12 @@ class Parser {
520531
}
521532
}
522533

523-
524-
525-
526-
527534
var filterChain = null;
528535
var functionCall, arrayDeclaration, objectIndex, fieldAccess, object;
529536

530-
531-
532537
ParsedFn primary() {
533538
var primary;
539+
var ts = saveTokens();
534540
if (expect('(') != null) {
535541
primary = filterChain();
536542
consume(')');
@@ -539,7 +545,7 @@ class Parser {
539545
} else if (expect('{') != null) {
540546
primary = object();
541547
} else {
542-
var token = expect();
548+
Token token = expect();
543549
primary = token.primaryFn;
544550
if (primary == null) {
545551
throw "not impl error";
@@ -551,7 +557,7 @@ class Parser {
551557
var next, context;
552558
while ((next = expect('(', '[', '.')) != null) {
553559
if (next.text == '(') {
554-
primary = functionCall(primary);
560+
primary = functionCall(primary, tokensText(ts.sublist(0, ts.length - 1)));
555561
context = null;
556562
} else if (next.text == '[') {
557563
context = primary;
@@ -563,6 +569,7 @@ class Parser {
563569
throw "Impossible.. what?";
564570
}
565571
}
572+
stopSavingTokens(ts);
566573
return primary;
567574
}
568575

@@ -706,7 +713,7 @@ class Parser {
706713
}
707714
}
708715

709-
functionCall = (fn) {
716+
functionCall = (fn, fnName) {
710717
var argsFn = [];
711718
if (peekToken().text != ')') {
712719
do {
@@ -720,6 +727,12 @@ class Parser {
720727
args.add(argsFn[i](self, locals));
721728
}
722729
var userFn = fn(self, locals);
730+
if (userFn == null) {
731+
throw "Undefined function $fnName";
732+
}
733+
if (userFn is! Function) {
734+
throw "$fnName is not a function";
735+
}
723736
return relaxFnApply(userFn, args);
724737
});
725738
};

test/parser_spec.dart

+41-12
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ main() {
252252
});
253253
});
254254

255-
256255
describe('parse', () {
257256
var scope;
258257
eval(String text) => Parser.parse(text)(scope, null);
@@ -588,19 +587,49 @@ main() {
588587
expect(Parser.parse('str')(data)).toEqual('dole');
589588
});
590589

591-
it('should support map getters from superclass', () {
592-
InheritedMapData mapData = new InheritedMapData();
593-
expect(Parser.parse('notmixed')(mapData)).toEqual('mapped-notmixed');
594-
});
590+
it('should support map getters from superclass', () {
591+
InheritedMapData mapData = new InheritedMapData();
592+
expect(Parser.parse('notmixed')(mapData)).toEqual('mapped-notmixed');
593+
});
594+
595+
it('should support map getters from mixins', () {
596+
MixedMapData data = new MixedMapData();
597+
expect(Parser.parse('str')(data)).toEqual('mapped-str');
598+
});
599+
600+
it('should gracefully handle bad containsKey', () {
601+
expect(Parser.parse('str')(new BadContainsKeys())).toEqual('member');
602+
});
595603

596-
it('should support map getters from mixins', () {
597-
MixedMapData data = new MixedMapData();
598-
expect(Parser.parse('str')(data)).toEqual('mapped-str');
599-
});
604+
it('should parse functions for object indices', () {
605+
expect(Parser.parse('a[x()]()')({'a': [()=>6], 'x': () => 0})).toEqual(6);
606+
});
600607

601-
it('should gracefully handle bad containsKey', () {
602-
expect(Parser.parse('str')(new BadContainsKeys())).toEqual('member');
603-
});
608+
it('should fail gracefully when missing a function', () {
609+
expect(() {
610+
Parser.parse('doesNotExist()')({});
611+
}).toThrow('Undefined function doesNotExist');
612+
613+
expect(() {
614+
Parser.parse('exists(doesNotExist())')({'exists': () => true});
615+
}).toThrow('Undefined function doesNotExist');
616+
617+
expect(() {
618+
Parser.parse('doesNotExists(exists())')({'exists': () => true});
619+
}).toThrow('Undefined function doesNotExist');
620+
621+
expect(() {
622+
Parser.parse('a[0]()')({'a': [4]});
623+
}).toThrow('a[0] is not a function');
624+
625+
expect(() {
626+
Parser.parse('a[x()]()')({'a': [4], 'x': () => 0});
627+
}).toThrow('a[x()] is not a function');
628+
629+
expect(() {
630+
Parser.parse('{}()')({});
631+
}).toThrow('{} is not a function');
632+
});
604633
});
605634

606635
describe('assignable', () {

0 commit comments

Comments
 (0)