feat(change_detector): add support for method calls

This commit is contained in:
vsavkin 2014-11-11 16:54:15 -08:00
parent dcd905ae85
commit 4e38e3a96c
4 changed files with 77 additions and 18 deletions

View File

@ -315,7 +315,7 @@ export class MethodCall extends AST {
} }
export class FunctionCall extends AST { export class FunctionCall extends AST {
@FIELD('final receiver:AST') @FIELD('final target:AST')
@FIELD('final closureMap:ClosureMap') @FIELD('final closureMap:ClosureMap')
@FIELD('final args:List') @FIELD('final args:List')
constructor(target:AST, closureMap:ClosureMap, args:List) { constructor(target:AST, closureMap:ClosureMap, args:List) {

View File

@ -6,7 +6,9 @@ import {ClosureMap} from 'change_detection/parser/closure_map';
var _fresh = new Object(); var _fresh = new Object();
export const PROTO_RECORD_CONST = 'const'; 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'; export const PROTO_RECORD_PROPERTY = 'property';
/** /**
@ -97,11 +99,20 @@ export class Record {
this.mode = MODE_STATE_CONST; this.mode = MODE_STATE_CONST;
this.funcOrValue = protoRecord.funcOrValue; this.funcOrValue = protoRecord.funcOrValue;
} else if (protoRecord.recordType === PROTO_RECORD_FUNC) { } else if (protoRecord.recordType === PROTO_RECORD_PURE_FUNCTION) {
this.mode = MODE_STATE_INVOKE_FUNCTION; this.mode = MODE_STATE_INVOKE_PURE_FUNCTION;
this.funcOrValue = protoRecord.funcOrValue; this.funcOrValue = protoRecord.funcOrValue;
this.args = ListWrapper.createFixedSize(protoRecord.arity); 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) { } else if (protoRecord.recordType === PROTO_RECORD_PROPERTY) {
this.mode = MODE_STATE_PROPERTY; this.mode = MODE_STATE_PROPERTY;
this.funcOrValue = protoRecord.funcOrValue; this.funcOrValue = protoRecord.funcOrValue;
@ -138,7 +149,13 @@ export class Record {
case MODE_STATE_PROPERTY: case MODE_STATE_PROPERTY:
return this.funcOrValue(this.context); 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); return FunctionWrapper.apply(this.funcOrValue, this.args);
case MODE_STATE_CONST: case MODE_STATE_CONST:
@ -147,9 +164,6 @@ export class Record {
case MODE_STATE_MARKER: case MODE_STATE_MARKER:
throw new BaseException('MODE_STATE_MARKER not implemented'); 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: case MODE_STATE_MAP:
throw new BaseException('MODE_STATE_MAP not implemented'); throw new BaseException('MODE_STATE_MAP not implemented');
@ -183,18 +197,17 @@ const MODE_STATE_MARKER = 0x0000;
/// _context[_protoRecord.propname] => _getter(_context) /// _context[_protoRecord.propname] => _getter(_context)
const MODE_STATE_PROPERTY = 0x0001; const MODE_STATE_PROPERTY = 0x0001;
/// _context(_arguments) const MODE_STATE_INVOKE_PURE_FUNCTION = 0x0002;
const MODE_STATE_INVOKE_FUNCTION = 0x0002;
/// _getter(_context, _arguments)
const MODE_STATE_INVOKE_METHOD = 0x0003; const MODE_STATE_INVOKE_METHOD = 0x0003;
const MODE_STATE_INVOKE_CLOSURE = 0x0004;
/// _context is Map => _previousValue is MapChangeRecord /// _context is Map => _previousValue is MapChangeRecord
const MODE_STATE_MAP = 0x0004; const MODE_STATE_MAP = 0x0005;
/// _context is Array/List/Iterable => _previousValue = ListChangeRecord /// _context is Array/List/Iterable => _previousValue = ListChangeRecord
const MODE_STATE_LIST = 0x0005; const MODE_STATE_LIST = 0x0006;
/// _context is number/string /// _context is number/string
const MODE_STATE_CONST = 0x0006; const MODE_STATE_CONST = 0x0007;
function isSame(a, b) { function isSame(a, b) {
if (a instanceof String && b instanceof String) return a == b; if (a instanceof String && b instanceof String) return a == b;

View File

@ -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 {FIELD, IMPLEMENTS, isBlank, isPresent, int, toBool, autoConvertAdd, BaseException} from 'facade/lang';
import {ListWrapper} from 'facade/collection'; 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 { export class ProtoWatchGroup {
@FIELD('headRecord:ProtoRecord') @FIELD('headRecord:ProtoRecord')
@ -167,7 +169,7 @@ class ProtoRecordCreator {
} }
visitBinary(ast:Binary, dest) { 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.left.visit(this, new Destination(record, 0));
ast.right.visit(this, new Destination(record, 1)); ast.right.visit(this, new Destination(record, 1));
@ -183,13 +185,31 @@ class ProtoRecordCreator {
visitFormatter(ast:Formatter, dest) { visitFormatter(ast:Formatter, dest) {
var formatter = this.protoWatchGroup.formatters[ast.name]; 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) { for (var i = 0; i < ast.allArgs.length; ++i) {
ast.allArgs[i].visit(this, new Destination(record, i)); ast.allArgs[i].visit(this, new Destination(record, i));
} }
this.add(record); 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){ createRecordsFromAST(ast:AST, memento){
ast.visit(this, memento); ast.visit(this, memento);
} }

View File

@ -69,6 +69,22 @@ export function main() {
.toEqual(['address.city=Grenoble']); .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", () => { it("should support literals", () => {
expect(executeWatch('const', '10')).toEqual(['const=10']); expect(executeWatch('const', '10')).toEqual(['const=10']);
}); });
@ -123,6 +139,10 @@ class Person {
this.address = address; this.address = address;
} }
sayHi(m) {
return `Hi, ${m}`;
}
toString():string { toString():string {
var address = this.address == null ? '' : ' address=' + this.address.toString(); 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 { class LoggingDispatcher extends WatchGroupDispatcher {
constructor() { constructor() {
this.log = null; this.log = null;