feat(Parser): add support for method invocations

This commit is contained in:
vsavkin 2014-11-05 15:38:44 -08:00
parent 977bc77c96
commit 7b777b1f71
5 changed files with 95 additions and 15 deletions

View File

@ -1,5 +1,6 @@
import {FIELD, toBool, autoConvertAdd, isBlank, FunctionWrapper, BaseException} from "facade/lang"; import {FIELD, toBool, autoConvertAdd, isBlank, FunctionWrapper, BaseException} from "facade/lang";
import {List, Map, ListWrapper, MapWrapper} from "facade/collection"; import {List, Map, ListWrapper, MapWrapper} from "facade/collection";
import {ClosureMap} from "./closure_map";
export class AST { export class AST {
eval(context) { eval(context) {
@ -221,8 +222,6 @@ export class Binary extends AST {
case '-' : return left - right; case '-' : return left - right;
case '*' : return left * right; 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; 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 //INTERFACE
export class AstVisitor { export class AstVisitor {
visitImplicitReceiver(ast:ImplicitReceiver) {} visitImplicitReceiver(ast:ImplicitReceiver) {}

View File

@ -12,4 +12,9 @@ class ClosureMap {
var symbol = new Symbol(name); var symbol = new Symbol(name);
return (receiver, value) => reflect(receiver).setField(symbol, value).reflectee; 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;
}
} }

View File

@ -8,4 +8,8 @@ export class ClosureMap {
setter(name:string) { setter(name:string) {
return new Function('o', 'v', 'return o.' + name + ' = v;'); return new Function('o', 'v', 'return o.' + name + ' = v;');
} }
fn(name:string) {
return new Function('o', 'pos', 'return o.' + name + '.apply(o, pos);');
}
} }

View File

@ -1,6 +1,7 @@
import {FIELD, int, isBlank, BaseException, StringWrapper} from 'facade/lang'; import {FIELD, int, isBlank, BaseException, StringWrapper} from 'facade/lang';
import {ListWrapper, List} from 'facade/collection'; 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 {ClosureMap} from './closure_map';
import { import {
AST, AST,
@ -16,7 +17,9 @@ import {
Chain, Chain,
KeyedAccess, KeyedAccess,
LiteralArray, LiteralArray,
LiteralMap LiteralMap,
MethodCall,
FunctionCall
} from './ast'; } from './ast';
var _implicitReceiver = new ImplicitReceiver(); var _implicitReceiver = new ImplicitReceiver();
@ -273,21 +276,26 @@ class _ParseAST {
} else if (this.optionalOperator('!')) { } else if (this.optionalOperator('!')) {
return new PrefixNot(this.parsePrefix()); return new PrefixNot(this.parsePrefix());
} else { } else {
return this.parseAccessOrCallMember(); return this.parseCallChain();
} }
} }
parseAccessOrCallMember():AST { parseCallChain():AST {
var result = this.parsePrimary(); var result = this.parsePrimary();
while (true) { while (true) {
if (this.optionalCharacter($PERIOD)) { if (this.optionalCharacter($PERIOD)) {
result = this.parseFieldRead(result); result = this.parseAccessorOrMethodCall(result);
} else if (this.optionalCharacter($LBRACKET)) { } else if (this.optionalCharacter($LBRACKET)) {
var key = this.parseExpression(); var key = this.parseExpression();
this.expectCharacter($RBRACKET); this.expectCharacter($RBRACKET);
result = new KeyedAccess(result, key); 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 { } else {
return result; return result;
} }
@ -316,7 +324,7 @@ class _ParseAST {
return this.parseLiteralMap(); return this.parseLiteralMap();
} else if (this.next.isIdentifier()) { } else if (this.next.isIdentifier()) {
return this.parseFieldRead(_implicitReceiver); return this.parseAccessorOrMethodCall(_implicitReceiver);
} else if (this.next.isNumber()) { } else if (this.next.isNumber()) {
var value = this.next.toNumber(); var value = this.next.toNumber();
@ -362,9 +370,29 @@ class _ParseAST {
return new LiteralMap(keys, values); return new LiteralMap(keys, values);
} }
parseFieldRead(receiver):AST { parseAccessorOrMethodCall(receiver):AST {
var id = this.expectIdentifierOrKeyword(); 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) { error(message:string, index:int = null) {

View File

@ -7,13 +7,14 @@ import {Formatter, LiteralPrimitive} from 'change_detection/parser/ast';
import {ClosureMap} from 'change_detection/parser/closure_map'; import {ClosureMap} from 'change_detection/parser/closure_map';
class TestData { class TestData {
constructor(a, b) { constructor(a, b, fnReturnValue) {
this.a = a; this.a = a;
this.b = b; this.b = b;
this.fnReturnValue = fnReturnValue;
} }
constant() { fn() {
return "constant"; return this.fnReturnValue;
} }
add(a, b) { add(a, b) {
@ -28,8 +29,8 @@ class ContextWithErrors {
} }
export function main() { export function main() {
function td(a = 0, b = 0) { function td(a = 0, b = 0, fnReturnValue = "constant") {
return new TestData(a, b); return new TestData(a, b, fnReturnValue);
} }
function createParser() { function createParser() {
@ -183,6 +184,17 @@ export function main() {
expectEvalError("5=4").toThrowError(new RegExp("Expression 5 is not assignable")); 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', () => { it('should evaluate array', () => {
expectEval("[1][0]").toEqual(1); expectEval("[1][0]").toEqual(1);
expectEval("[[1]][0][0]").toEqual(1); expectEval("[[1]][0][0]").toEqual(1);