From 4e38e3a96c650bec4a8ec1573acb24dcfe0260ba Mon Sep 17 00:00:00 2001 From: vsavkin Date: Tue, 11 Nov 2014 16:54:15 -0800 Subject: [PATCH] feat(change_detector): add support for method calls --- modules/change_detection/src/parser/ast.js | 2 +- modules/change_detection/src/record.js | 39 ++++++++++++------- modules/change_detection/src/watch_group.js | 28 +++++++++++-- .../test/change_detector_spec.js | 26 +++++++++++++ 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/modules/change_detection/src/parser/ast.js b/modules/change_detection/src/parser/ast.js index c2a7cbbb99..b2c86f46e6 100644 --- a/modules/change_detection/src/parser/ast.js +++ b/modules/change_detection/src/parser/ast.js @@ -315,7 +315,7 @@ export class MethodCall extends AST { } export class FunctionCall extends AST { - @FIELD('final receiver:AST') + @FIELD('final target:AST') @FIELD('final closureMap:ClosureMap') @FIELD('final args:List') constructor(target:AST, closureMap:ClosureMap, args:List) { diff --git a/modules/change_detection/src/record.js b/modules/change_detection/src/record.js index 262cb85038..1b52d14499 100644 --- a/modules/change_detection/src/record.js +++ b/modules/change_detection/src/record.js @@ -6,7 +6,9 @@ import {ClosureMap} from 'change_detection/parser/closure_map'; var _fresh = new Object(); export const PROTO_RECORD_CONST = 'const'; -export const PROTO_RECORD_FUNC = 'func'; +export const PROTO_RECORD_PURE_FUNCTION = 'func'; +export const PROTO_RECORD_CLOSURE = 'closure'; +export const PROTO_RECORD_METHOD = 'method'; export const PROTO_RECORD_PROPERTY = 'property'; /** @@ -97,11 +99,20 @@ export class Record { this.mode = MODE_STATE_CONST; this.funcOrValue = protoRecord.funcOrValue; - } else if (protoRecord.recordType === PROTO_RECORD_FUNC) { - this.mode = MODE_STATE_INVOKE_FUNCTION; + } else if (protoRecord.recordType === PROTO_RECORD_PURE_FUNCTION) { + this.mode = MODE_STATE_INVOKE_PURE_FUNCTION; this.funcOrValue = protoRecord.funcOrValue; this.args = ListWrapper.createFixedSize(protoRecord.arity); + } else if (protoRecord.recordType === PROTO_RECORD_METHOD) { + this.mode = MODE_STATE_INVOKE_METHOD; + this.funcOrValue = protoRecord.funcOrValue; + this.args = ListWrapper.createFixedSize(protoRecord.arity); + + } else if (protoRecord.recordType === PROTO_RECORD_CLOSURE) { + this.mode = MODE_STATE_INVOKE_CLOSURE; + this.args = ListWrapper.createFixedSize(protoRecord.arity); + } else if (protoRecord.recordType === PROTO_RECORD_PROPERTY) { this.mode = MODE_STATE_PROPERTY; this.funcOrValue = protoRecord.funcOrValue; @@ -138,7 +149,13 @@ export class Record { case MODE_STATE_PROPERTY: return this.funcOrValue(this.context); - case MODE_STATE_INVOKE_FUNCTION: + case MODE_STATE_INVOKE_METHOD: + return this.funcOrValue(this.context, this.args); + + case MODE_STATE_INVOKE_CLOSURE: + return FunctionWrapper.apply(this.context, this.args); + + case MODE_STATE_INVOKE_PURE_FUNCTION: return FunctionWrapper.apply(this.funcOrValue, this.args); case MODE_STATE_CONST: @@ -147,9 +164,6 @@ export class Record { case MODE_STATE_MARKER: throw new BaseException('MODE_STATE_MARKER not implemented'); - case MODE_STATE_INVOKE_METHOD: - throw new BaseException('MODE_STATE_INVOKE_METHOD not implemented'); - case MODE_STATE_MAP: throw new BaseException('MODE_STATE_MAP not implemented'); @@ -183,18 +197,17 @@ const MODE_STATE_MARKER = 0x0000; /// _context[_protoRecord.propname] => _getter(_context) const MODE_STATE_PROPERTY = 0x0001; -/// _context(_arguments) -const MODE_STATE_INVOKE_FUNCTION = 0x0002; -/// _getter(_context, _arguments) +const MODE_STATE_INVOKE_PURE_FUNCTION = 0x0002; const MODE_STATE_INVOKE_METHOD = 0x0003; +const MODE_STATE_INVOKE_CLOSURE = 0x0004; /// _context is Map => _previousValue is MapChangeRecord -const MODE_STATE_MAP = 0x0004; +const MODE_STATE_MAP = 0x0005; /// _context is Array/List/Iterable => _previousValue = ListChangeRecord -const MODE_STATE_LIST = 0x0005; +const MODE_STATE_LIST = 0x0006; /// _context is number/string -const MODE_STATE_CONST = 0x0006; +const MODE_STATE_CONST = 0x0007; function isSame(a, b) { if (a instanceof String && b instanceof String) return a == b; diff --git a/modules/change_detection/src/watch_group.js b/modules/change_detection/src/watch_group.js index 84911b0dd3..deacb5db46 100644 --- a/modules/change_detection/src/watch_group.js +++ b/modules/change_detection/src/watch_group.js @@ -1,7 +1,9 @@ -import {ProtoRecord, Record, PROTO_RECORD_CONST, PROTO_RECORD_FUNC, PROTO_RECORD_PROPERTY} from './record'; +import {ProtoRecord, Record, PROTO_RECORD_CONST, PROTO_RECORD_PURE_FUNCTION, + PROTO_RECORD_PROPERTY, PROTO_RECORD_METHOD, PROTO_RECORD_CLOSURE} from './record'; import {FIELD, IMPLEMENTS, isBlank, isPresent, int, toBool, autoConvertAdd, BaseException} from 'facade/lang'; import {ListWrapper} from 'facade/collection'; -import {AST, AccessMember, ImplicitReceiver, AstVisitor, LiteralPrimitive, Binary, Formatter} from './parser/ast'; +import {AST, AccessMember, ImplicitReceiver, AstVisitor, LiteralPrimitive, + Binary, Formatter, MethodCall, FunctionCall} from './parser/ast'; export class ProtoWatchGroup { @FIELD('headRecord:ProtoRecord') @@ -167,7 +169,7 @@ class ProtoRecordCreator { } visitBinary(ast:Binary, dest) { - var record = this.construct(PROTO_RECORD_FUNC, _operationToFunction(ast.operation), 2, dest); + var record = this.construct(PROTO_RECORD_PURE_FUNCTION, _operationToFunction(ast.operation), 2, dest); ast.left.visit(this, new Destination(record, 0)); ast.right.visit(this, new Destination(record, 1)); @@ -183,13 +185,31 @@ class ProtoRecordCreator { visitFormatter(ast:Formatter, dest) { var formatter = this.protoWatchGroup.formatters[ast.name]; - var record = this.construct(PROTO_RECORD_FUNC, formatter, ast.allArgs.length, dest); + var record = this.construct(PROTO_RECORD_PURE_FUNCTION, formatter, ast.allArgs.length, dest); for (var i = 0; i < ast.allArgs.length; ++i) { ast.allArgs[i].visit(this, new Destination(record, i)); } this.add(record); } + visitMethodCall(ast:MethodCall, dest) { + var record = this.construct(PROTO_RECORD_METHOD, ast.fn, ast.args.length, dest); + ast.receiver.visit(this, new Destination(record, null)); + for (var i = 0; i < ast.args.length; ++i) { + ast.args[i].visit(this, new Destination(record, i)); + } + this.add(record); + } + + visitFunctionCall(ast:FunctionCall, dest) { + var record = this.construct(PROTO_RECORD_CLOSURE, null, ast.args.length, dest); + ast.target.visit(this, new Destination(record, null)); + for (var i = 0; i < ast.args.length; ++i) { + ast.args[i].visit(this, new Destination(record, i)); + } + this.add(record); + } + createRecordsFromAST(ast:AST, memento){ ast.visit(this, memento); } diff --git a/modules/change_detection/test/change_detector_spec.js b/modules/change_detection/test/change_detector_spec.js index 00c82c4ba5..5961ad4b2c 100644 --- a/modules/change_detection/test/change_detector_spec.js +++ b/modules/change_detection/test/change_detector_spec.js @@ -69,6 +69,22 @@ export function main() { .toEqual(['address.city=Grenoble']); }); + it("should support method calls", () => { + var person = new Person('Victor'); + expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']); + }); + + it("should support function calls", () => { + var td = new TestData(() => (a) => a); + expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']); + }); + + it("should support chained method calls", () => { + var person = new Person('Victor'); + var td = new TestData(person); + expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']); + }); + it("should support literals", () => { expect(executeWatch('const', '10')).toEqual(['const=10']); }); @@ -123,6 +139,10 @@ class Person { this.address = address; } + sayHi(m) { + return `Hi, ${m}`; + } + toString():string { var address = this.address == null ? '' : ' address=' + this.address.toString(); @@ -140,6 +160,12 @@ class Address { } } +class TestData { + constructor(a) { + this.a = a; + } +} + class LoggingDispatcher extends WatchGroupDispatcher { constructor() { this.log = null;