From 7b777b1f71080b2788550a7654837ff5c8b02c22 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 5 Nov 2014 15:38:44 -0800 Subject: [PATCH] feat(Parser): add support for method invocations --- modules/change_detection/src/parser/ast.js | 35 ++++++++++++++- .../src/parser/closure_map.dart | 5 +++ .../src/parser/closure_map.es6 | 4 ++ modules/change_detection/src/parser/parser.js | 44 +++++++++++++++---- .../test/parser/parser_spec.js | 22 +++++++--- 5 files changed, 95 insertions(+), 15 deletions(-) diff --git a/modules/change_detection/src/parser/ast.js b/modules/change_detection/src/parser/ast.js index 78c7660019..f31b3b4104 100644 --- a/modules/change_detection/src/parser/ast.js +++ b/modules/change_detection/src/parser/ast.js @@ -1,5 +1,6 @@ import {FIELD, toBool, autoConvertAdd, isBlank, FunctionWrapper, BaseException} from "facade/lang"; import {List, Map, ListWrapper, MapWrapper} from "facade/collection"; +import {ClosureMap} from "./closure_map"; export class AST { eval(context) { @@ -221,8 +222,6 @@ export class Binary extends AST { case '-' : return left - right; case '*' : return left * right; case '/' : return left / right; - // This exists only in Dart, TODO(rado) figure out whether to support it. - // case '~/' : return left ~/ right; case '%' : return left % right; case '==' : return left == right; case '!=' : return left != right; @@ -263,6 +262,38 @@ export class Assignment extends AST { } } +export class MethodCall extends AST { + @FIELD('final receiver:AST') + @FIELD('final fn:Function') + @FIELD('final args:List') + constructor(receiver:AST, fn:Function, args:List) { + this.receiver = receiver; + this.fn = fn; + this.args = args; + } + + eval(context) { + var obj = this.receiver.eval(context); + return this.fn(obj, evalList(context, this.args)); + } +} + +export class FunctionCall extends AST { + @FIELD('final receiver:AST') + @FIELD('final closureMap:ClosureMap') + @FIELD('final args:List') + constructor(target:AST, closureMap:ClosureMap, args:List) { + this.target = target; + this.closureMap = closureMap; + this.args = args; + } + + eval(context) { + var obj = this.target.eval(context); + return FunctionWrapper.apply(obj, evalList(context, this.args)); + } +} + //INTERFACE export class AstVisitor { visitImplicitReceiver(ast:ImplicitReceiver) {} diff --git a/modules/change_detection/src/parser/closure_map.dart b/modules/change_detection/src/parser/closure_map.dart index c1a9800cb3..615ee34b27 100644 --- a/modules/change_detection/src/parser/closure_map.dart +++ b/modules/change_detection/src/parser/closure_map.dart @@ -12,4 +12,9 @@ class ClosureMap { var symbol = new Symbol(name); return (receiver, value) => reflect(receiver).setField(symbol, value).reflectee; } + + Function fn(String name) { + var symbol = new Symbol(name); + return (receiver, posArgs) => reflect(receiver).invoke(symbol, posArgs).reflectee; + } } diff --git a/modules/change_detection/src/parser/closure_map.es6 b/modules/change_detection/src/parser/closure_map.es6 index 2babeb64d8..1bb6c39aa6 100644 --- a/modules/change_detection/src/parser/closure_map.es6 +++ b/modules/change_detection/src/parser/closure_map.es6 @@ -8,4 +8,8 @@ export class ClosureMap { setter(name:string) { return new Function('o', 'v', 'return o.' + name + ' = v;'); } + + fn(name:string) { + return new Function('o', 'pos', 'return o.' + name + '.apply(o, pos);'); + } } diff --git a/modules/change_detection/src/parser/parser.js b/modules/change_detection/src/parser/parser.js index 9d30a34899..aff7042826 100644 --- a/modules/change_detection/src/parser/parser.js +++ b/modules/change_detection/src/parser/parser.js @@ -1,6 +1,7 @@ import {FIELD, int, isBlank, BaseException, StringWrapper} from 'facade/lang'; import {ListWrapper, List} from 'facade/collection'; -import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET, $COMMA, $LBRACE, $RBRACE} from './lexer'; +import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET, + $COMMA, $LBRACE, $RBRACE, $LPAREN, $RPAREN} from './lexer'; import {ClosureMap} from './closure_map'; import { AST, @@ -16,7 +17,9 @@ import { Chain, KeyedAccess, LiteralArray, - LiteralMap + LiteralMap, + MethodCall, + FunctionCall } from './ast'; var _implicitReceiver = new ImplicitReceiver(); @@ -273,21 +276,26 @@ class _ParseAST { } else if (this.optionalOperator('!')) { return new PrefixNot(this.parsePrefix()); } else { - return this.parseAccessOrCallMember(); + return this.parseCallChain(); } } - parseAccessOrCallMember():AST { + parseCallChain():AST { var result = this.parsePrimary(); while (true) { if (this.optionalCharacter($PERIOD)) { - result = this.parseFieldRead(result); + result = this.parseAccessorOrMethodCall(result); } else if (this.optionalCharacter($LBRACKET)) { var key = this.parseExpression(); this.expectCharacter($RBRACKET); result = new KeyedAccess(result, key); + } else if (this.optionalCharacter($LPAREN)) { + var args = this.parseCallArguments(); + this.expectCharacter($RPAREN); + result = new FunctionCall(result, this.closureMap, args); + } else { return result; } @@ -316,7 +324,7 @@ class _ParseAST { return this.parseLiteralMap(); } else if (this.next.isIdentifier()) { - return this.parseFieldRead(_implicitReceiver); + return this.parseAccessorOrMethodCall(_implicitReceiver); } else if (this.next.isNumber()) { var value = this.next.toNumber(); @@ -362,9 +370,29 @@ class _ParseAST { return new LiteralMap(keys, values); } - parseFieldRead(receiver):AST { + parseAccessorOrMethodCall(receiver):AST { var id = this.expectIdentifierOrKeyword(); - return new FieldRead(receiver, id, this.closureMap.getter(id), this.closureMap.setter(id)); + + if (this.optionalCharacter($LPAREN)) { + var args = this.parseCallArguments(); + this.expectCharacter($RPAREN); + var fn = this.closureMap.fn(id); + return new MethodCall(receiver, fn, args); + + } else { + var getter = this.closureMap.getter(id); + var setter = this.closureMap.setter(id); + return new FieldRead(receiver, id, getter, setter); + } + } + + parseCallArguments() { + if (this.next.isCharacter($RPAREN)) return []; + var positionals = []; + do { + ListWrapper.push(positionals, this.parseExpression()); + } while (this.optionalCharacter($COMMA)) + return positionals; } error(message:string, index:int = null) { diff --git a/modules/change_detection/test/parser/parser_spec.js b/modules/change_detection/test/parser/parser_spec.js index b03c9b87d9..7e218937f5 100644 --- a/modules/change_detection/test/parser/parser_spec.js +++ b/modules/change_detection/test/parser/parser_spec.js @@ -7,13 +7,14 @@ import {Formatter, LiteralPrimitive} from 'change_detection/parser/ast'; import {ClosureMap} from 'change_detection/parser/closure_map'; class TestData { - constructor(a, b) { + constructor(a, b, fnReturnValue) { this.a = a; this.b = b; + this.fnReturnValue = fnReturnValue; } - constant() { - return "constant"; + fn() { + return this.fnReturnValue; } add(a, b) { @@ -28,8 +29,8 @@ class ContextWithErrors { } export function main() { - function td(a = 0, b = 0) { - return new TestData(a, b); + function td(a = 0, b = 0, fnReturnValue = "constant") { + return new TestData(a, b, fnReturnValue); } function createParser() { @@ -183,6 +184,17 @@ export function main() { expectEvalError("5=4").toThrowError(new RegExp("Expression 5 is not assignable")); }); + it("should evaluate method calls", () => { + expectEval("fn()", td(0,0, "constant")).toEqual("constant"); + expectEval("add(1,2)").toEqual(3); + expectEval("a.add(1,2)", td(td())).toEqual(3); + expectEval("fn().add(1,2)", td(0,0,td())).toEqual(3); + }); + + it("should evaluate function calls", () => { + expectEval("fn()(1,2)", td(0, 0, (a,b) => a + b)).toEqual(3); + }); + it('should evaluate array', () => { expectEval("[1][0]").toEqual(1); expectEval("[[1]][0][0]").toEqual(1);