Skip to content

Commit 7b777b1

Browse files
committed
feat(Parser): add support for method invocations
1 parent 977bc77 commit 7b777b1

File tree

5 files changed

+95
-15
lines changed

5 files changed

+95
-15
lines changed

modules/change_detection/src/parser/ast.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {FIELD, toBool, autoConvertAdd, isBlank, FunctionWrapper, BaseException} from "facade/lang";
22
import {List, Map, ListWrapper, MapWrapper} from "facade/collection";
3+
import {ClosureMap} from "./closure_map";
34

45
export class AST {
56
eval(context) {
@@ -221,8 +222,6 @@ export class Binary extends AST {
221222
case '-' : return left - right;
222223
case '*' : return left * right;
223224
case '/' : return left / right;
224-
// This exists only in Dart, TODO(rado) figure out whether to support it.
225-
// case '~/' : return left ~/ right;
226225
case '%' : return left % right;
227226
case '==' : return left == right;
228227
case '!=' : return left != right;
@@ -263,6 +262,38 @@ export class Assignment extends AST {
263262
}
264263
}
265264

265+
export class MethodCall extends AST {
266+
@FIELD('final receiver:AST')
267+
@FIELD('final fn:Function')
268+
@FIELD('final args:List')
269+
constructor(receiver:AST, fn:Function, args:List) {
270+
this.receiver = receiver;
271+
this.fn = fn;
272+
this.args = args;
273+
}
274+
275+
eval(context) {
276+
var obj = this.receiver.eval(context);
277+
return this.fn(obj, evalList(context, this.args));
278+
}
279+
}
280+
281+
export class FunctionCall extends AST {
282+
@FIELD('final receiver:AST')
283+
@FIELD('final closureMap:ClosureMap')
284+
@FIELD('final args:List')
285+
constructor(target:AST, closureMap:ClosureMap, args:List) {
286+
this.target = target;
287+
this.closureMap = closureMap;
288+
this.args = args;
289+
}
290+
291+
eval(context) {
292+
var obj = this.target.eval(context);
293+
return FunctionWrapper.apply(obj, evalList(context, this.args));
294+
}
295+
}
296+
266297
//INTERFACE
267298
export class AstVisitor {
268299
visitImplicitReceiver(ast:ImplicitReceiver) {}

modules/change_detection/src/parser/closure_map.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,9 @@ class ClosureMap {
1212
var symbol = new Symbol(name);
1313
return (receiver, value) => reflect(receiver).setField(symbol, value).reflectee;
1414
}
15+
16+
Function fn(String name) {
17+
var symbol = new Symbol(name);
18+
return (receiver, posArgs) => reflect(receiver).invoke(symbol, posArgs).reflectee;
19+
}
1520
}

modules/change_detection/src/parser/closure_map.es6

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ export class ClosureMap {
88
setter(name:string) {
99
return new Function('o', 'v', 'return o.' + name + ' = v;');
1010
}
11+
12+
fn(name:string) {
13+
return new Function('o', 'pos', 'return o.' + name + '.apply(o, pos);');
14+
}
1115
}

modules/change_detection/src/parser/parser.js

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {FIELD, int, isBlank, BaseException, StringWrapper} from 'facade/lang';
22
import {ListWrapper, List} from 'facade/collection';
3-
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET, $COMMA, $LBRACE, $RBRACE} from './lexer';
3+
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET,
4+
$COMMA, $LBRACE, $RBRACE, $LPAREN, $RPAREN} from './lexer';
45
import {ClosureMap} from './closure_map';
56
import {
67
AST,
@@ -16,7 +17,9 @@ import {
1617
Chain,
1718
KeyedAccess,
1819
LiteralArray,
19-
LiteralMap
20+
LiteralMap,
21+
MethodCall,
22+
FunctionCall
2023
} from './ast';
2124

2225
var _implicitReceiver = new ImplicitReceiver();
@@ -273,21 +276,26 @@ class _ParseAST {
273276
} else if (this.optionalOperator('!')) {
274277
return new PrefixNot(this.parsePrefix());
275278
} else {
276-
return this.parseAccessOrCallMember();
279+
return this.parseCallChain();
277280
}
278281
}
279282

280-
parseAccessOrCallMember():AST {
283+
parseCallChain():AST {
281284
var result = this.parsePrimary();
282285
while (true) {
283286
if (this.optionalCharacter($PERIOD)) {
284-
result = this.parseFieldRead(result);
287+
result = this.parseAccessorOrMethodCall(result);
285288

286289
} else if (this.optionalCharacter($LBRACKET)) {
287290
var key = this.parseExpression();
288291
this.expectCharacter($RBRACKET);
289292
result = new KeyedAccess(result, key);
290293

294+
} else if (this.optionalCharacter($LPAREN)) {
295+
var args = this.parseCallArguments();
296+
this.expectCharacter($RPAREN);
297+
result = new FunctionCall(result, this.closureMap, args);
298+
291299
} else {
292300
return result;
293301
}
@@ -316,7 +324,7 @@ class _ParseAST {
316324
return this.parseLiteralMap();
317325

318326
} else if (this.next.isIdentifier()) {
319-
return this.parseFieldRead(_implicitReceiver);
327+
return this.parseAccessorOrMethodCall(_implicitReceiver);
320328

321329
} else if (this.next.isNumber()) {
322330
var value = this.next.toNumber();
@@ -362,9 +370,29 @@ class _ParseAST {
362370
return new LiteralMap(keys, values);
363371
}
364372

365-
parseFieldRead(receiver):AST {
373+
parseAccessorOrMethodCall(receiver):AST {
366374
var id = this.expectIdentifierOrKeyword();
367-
return new FieldRead(receiver, id, this.closureMap.getter(id), this.closureMap.setter(id));
375+
376+
if (this.optionalCharacter($LPAREN)) {
377+
var args = this.parseCallArguments();
378+
this.expectCharacter($RPAREN);
379+
var fn = this.closureMap.fn(id);
380+
return new MethodCall(receiver, fn, args);
381+
382+
} else {
383+
var getter = this.closureMap.getter(id);
384+
var setter = this.closureMap.setter(id);
385+
return new FieldRead(receiver, id, getter, setter);
386+
}
387+
}
388+
389+
parseCallArguments() {
390+
if (this.next.isCharacter($RPAREN)) return [];
391+
var positionals = [];
392+
do {
393+
ListWrapper.push(positionals, this.parseExpression());
394+
} while (this.optionalCharacter($COMMA))
395+
return positionals;
368396
}
369397

370398
error(message:string, index:int = null) {

modules/change_detection/test/parser/parser_spec.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import {Formatter, LiteralPrimitive} from 'change_detection/parser/ast';
77
import {ClosureMap} from 'change_detection/parser/closure_map';
88

99
class TestData {
10-
constructor(a, b) {
10+
constructor(a, b, fnReturnValue) {
1111
this.a = a;
1212
this.b = b;
13+
this.fnReturnValue = fnReturnValue;
1314
}
1415

15-
constant() {
16-
return "constant";
16+
fn() {
17+
return this.fnReturnValue;
1718
}
1819

1920
add(a, b) {
@@ -28,8 +29,8 @@ class ContextWithErrors {
2829
}
2930

3031
export function main() {
31-
function td(a = 0, b = 0) {
32-
return new TestData(a, b);
32+
function td(a = 0, b = 0, fnReturnValue = "constant") {
33+
return new TestData(a, b, fnReturnValue);
3334
}
3435

3536
function createParser() {
@@ -183,6 +184,17 @@ export function main() {
183184
expectEvalError("5=4").toThrowError(new RegExp("Expression 5 is not assignable"));
184185
});
185186

187+
it("should evaluate method calls", () => {
188+
expectEval("fn()", td(0,0, "constant")).toEqual("constant");
189+
expectEval("add(1,2)").toEqual(3);
190+
expectEval("a.add(1,2)", td(td())).toEqual(3);
191+
expectEval("fn().add(1,2)", td(0,0,td())).toEqual(3);
192+
});
193+
194+
it("should evaluate function calls", () => {
195+
expectEval("fn()(1,2)", td(0, 0, (a,b) => a + b)).toEqual(3);
196+
});
197+
186198
it('should evaluate array', () => {
187199
expectEval("[1][0]").toEqual(1);
188200
expectEval("[[1]][0][0]").toEqual(1);

0 commit comments

Comments
 (0)