diff --git a/lib/parser.dart b/lib/parser.dart index 6117b8c9f..b66fff273 100644 --- a/lib/parser.dart +++ b/lib/parser.dart @@ -500,6 +500,16 @@ class Parser { return null; } + /** + * Token savers are synchronous lists that allows Parser functions to + * access the tokens parsed during some amount of time. They are useful + * for printing helpful debugging messages. + */ + List> tokenSavers = []; + List saveTokens() { var n = []; tokenSavers.add(n); return n; } + stopSavingTokens(x) { if (!tokenSavers.remove(x)) { throw "bad token saver"; } return x; } + tokensText(List x) => x.map((x) => x.text).join(); + Token expect([String e1, String e2, String e3, String e4]){ Token token = peek(e1, e2, e3, e4); if (token != null) { @@ -507,7 +517,8 @@ class Parser { // if (json && !token.json) { // throwError("is not valid json", token); // } - tokens.removeAt(0); + var consumed = tokens.removeAt(0); + tokenSavers.forEach((ts) => ts.add(consumed)); return token; } return null; @@ -520,17 +531,12 @@ class Parser { } } - - - - var filterChain = null; var functionCall, arrayDeclaration, objectIndex, fieldAccess, object; - - ParsedFn primary() { var primary; + var ts = saveTokens(); if (expect('(') != null) { primary = filterChain(); consume(')'); @@ -539,7 +545,7 @@ class Parser { } else if (expect('{') != null) { primary = object(); } else { - var token = expect(); + Token token = expect(); primary = token.primaryFn; if (primary == null) { throw "not impl error"; @@ -551,7 +557,7 @@ class Parser { var next, context; while ((next = expect('(', '[', '.')) != null) { if (next.text == '(') { - primary = functionCall(primary); + primary = functionCall(primary, tokensText(ts.sublist(0, ts.length-1))); context = null; } else if (next.text == '[') { context = primary; @@ -563,6 +569,7 @@ class Parser { throw "Impossible.. what?"; } } + stopSavingTokens(ts); return primary; } @@ -706,7 +713,7 @@ class Parser { } } - functionCall = (fn) { + functionCall = (fn, fnName) { var argsFn = []; if (peekToken().text != ')') { do { @@ -720,6 +727,12 @@ class Parser { args.add(argsFn[i](self, locals)); } var userFn = fn(self, locals); + if (userFn == null) { + throw "Undefined function $fnName"; + } + if (userFn is! Function) { + throw "$fnName is not a function"; + } return relaxFnApply(userFn, args); }); }; diff --git a/test/parser_spec.dart b/test/parser_spec.dart index 7b122249f..63f60cac8 100644 --- a/test/parser_spec.dart +++ b/test/parser_spec.dart @@ -252,7 +252,6 @@ main() { }); }); - describe('parse', () { var scope; eval(String text) => Parser.parse(text)(scope, null); @@ -588,19 +587,49 @@ main() { expect(Parser.parse('str')(data)).toEqual('dole'); }); - it('should support map getters from superclass', () { - InheritedMapData mapData = new InheritedMapData(); - expect(Parser.parse('notmixed')(mapData)).toEqual('mapped-notmixed'); - }); + it('should support map getters from superclass', () { + InheritedMapData mapData = new InheritedMapData(); + expect(Parser.parse('notmixed')(mapData)).toEqual('mapped-notmixed'); + }); + + it('should support map getters from mixins', () { + MixedMapData data = new MixedMapData(); + expect(Parser.parse('str')(data)).toEqual('mapped-str'); + }); + + it('should gracefully handle bad containsKey', () { + expect(Parser.parse('str')(new BadContainsKeys())).toEqual('member'); + }); - it('should support map getters from mixins', () { - MixedMapData data = new MixedMapData(); - expect(Parser.parse('str')(data)).toEqual('mapped-str'); - }); + it('should parse functions for object indices', () { + expect(Parser.parse('a[x()]()')({'a': [()=>6], 'x': () => 0})).toEqual(6); + }); - it('should gracefully handle bad containsKey', () { - expect(Parser.parse('str')(new BadContainsKeys())).toEqual('member'); - }); + it('should fail gracefully when missing a function', () { + expect(() { + Parser.parse('doesNotExist()')({}); + }).toThrow('Undefined function doesNotExist'); + + expect(() { + Parser.parse('exists(doesNotExist())')({'exists': () => true}); + }).toThrow('Undefined function doesNotExist'); + + expect(() { + Parser.parse('doesNotExists(exists())')({'exists': () => true}); + }).toThrow('Undefined function doesNotExist'); + + expect(() { + Parser.parse('a[0]()')({'a': [4]}); + }).toThrow('a[0] is not a function'); + + expect(() { + Parser.parse('a[x()]()')({'a': [4], 'x': () => 0}); + }).toThrow('a[x()] is not a function'); + + expect(() { + Parser.parse('{}()')({}); + }).toThrow('{} is not a function'); + }); }); describe('assignable', () {