feat(Parser): add support for method invocations
This commit is contained in:
parent
977bc77c96
commit
7b777b1f71
|
@ -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) {}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue