Skip to content

Commit ac060ed

Browse files
committed
feat(Parser): add support for arrays and maps
1 parent 8cc008b commit ac060ed

File tree

6 files changed

+160
-16
lines changed

6 files changed

+160
-16
lines changed

modules/change_detection/src/parser/ast.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {FIELD, toBool, autoConvertAdd, isBlank, FunctionWrapper, BaseException} from "facade/lang";
2-
import {List, ListWrapper} from "facade/collection";
2+
import {List, Map, ListWrapper, MapWrapper} from "facade/collection";
33

44
export class AST {
55
eval(context) {
@@ -89,6 +89,32 @@ export class KeyedAccess extends AST {
8989
this.obj = obj;
9090
this.key = key;
9191
}
92+
eval(context) {
93+
var obj = this.obj.eval(context);
94+
var key = this.key.eval(context);
95+
96+
if (obj instanceof Map) {
97+
return MapWrapper.get(obj, key);
98+
} else if (obj instanceof List) {
99+
return ListWrapper.get(obj, key);
100+
} else {
101+
throw new BaseException(`Cannot access ${key} on ${obj}`);
102+
}
103+
}
104+
assign(context, value) {
105+
var obj = this.obj.eval(context);
106+
var key = this.key.eval(context);
107+
108+
if (obj instanceof Map) {
109+
MapWrapper.set(obj, key, value);
110+
} else if (obj instanceof List) {
111+
ListWrapper.set(obj, key, value);
112+
} else {
113+
throw new BaseException(`Cannot access ${key} on ${obj}`);
114+
}
115+
return value;
116+
}
117+
92118
}
93119

94120
export class Formatter extends AST {
@@ -120,6 +146,36 @@ export class LiteralPrimitive extends AST {
120146
}
121147
}
122148

149+
export class LiteralArray extends AST {
150+
@FIELD('final expressions:List')
151+
constructor(expressions:List) {
152+
this.expressions = expressions;
153+
}
154+
eval(context) {
155+
return ListWrapper.map(this.expressions, (e) => e.eval(context));
156+
}
157+
visit(visitor) {
158+
visitor.visitLiteralArray(this);
159+
}
160+
}
161+
162+
export class LiteralMap extends AST {
163+
@FIELD('final keys:List')
164+
@FIELD('final values:List')
165+
constructor(keys:List, values:List) {
166+
this.keys = keys;
167+
this.values = values;
168+
}
169+
170+
eval(context) {
171+
var res = MapWrapper.create();
172+
for(var i = 0; i < this.keys.length; ++i) {
173+
MapWrapper.set(res, this.keys[i], this.values[i].eval(context));
174+
}
175+
return res;
176+
}
177+
}
178+
123179
export class Binary extends AST {
124180
@FIELD('final operation:string')
125181
@FIELD('final left:AST')
@@ -203,6 +259,7 @@ export class AstVisitor {
203259
visitLiteralPrimitive(ast:LiteralPrimitive) {}
204260
visitFormatter(ast:Formatter) {}
205261
visitAssignment(ast:Assignment) {}
262+
visitLiteralArray(ast:LiteralArray) {}
206263
}
207264

208265
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]];

modules/change_detection/src/parser/lexer.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ const $A = 65, $B = 66, $C = 67, $D = 68, $E = 69, $F = 70, $G = 71, $H = 72,
157157
$Q = 81, $R = 82, $S = 83, $T = 84, $U = 85, $V = 86, $W = 87, $X = 88,
158158
$Y = 89, $Z = 90;
159159

160-
const $LBRACKET = 91;
161-
const $BACKSLASH = 92;
162-
const $RBRACKET = 93;
160+
export const $LBRACKET = 91;
161+
export const $BACKSLASH = 92;
162+
export const $RBRACKET = 93;
163163
const $CARET = 94;
164164
const $_ = 95;
165165

@@ -168,9 +168,9 @@ const $a = 97, $b = 98, $c = 99, $d = 100, $e = 101, $f = 102, $g = 103,
168168
$o = 111, $p = 112, $q = 113, $r = 114, $s = 115, $t = 116, $u = 117,
169169
$v = 118, $w = 119, $x = 120, $y = 121, $z = 122;
170170

171-
const $LBRACE = 123;
172-
const $BAR = 124;
173-
const $RBRACE = 125;
171+
export const $LBRACE = 123;
172+
export const $BAR = 124;
173+
export const $RBRACE = 125;
174174
const $TILDE = 126;
175175
const $NBSP = 160;
176176

modules/change_detection/src/parser/parser.js

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {FIELD, int, isBlank, BaseException} from 'facade/lang';
1+
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} from './lexer';
3+
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET, $COMMA, $LBRACE, $RBRACE} from './lexer';
44
import {ClosureMap} from './closure_map';
55
import {
66
AST,
@@ -14,7 +14,9 @@ import {
1414
Formatter,
1515
Assignment,
1616
Chain,
17-
KeyedAccess
17+
KeyedAccess,
18+
LiteralArray,
19+
LiteralMap
1820
} from './ast';
1921

2022
var _implicitReceiver = new ImplicitReceiver();
@@ -80,7 +82,7 @@ class _ParseAST {
8082

8183
expectCharacter(code:int) {
8284
if (this.optionalCharacter(code)) return;
83-
this.error(`Missing expected ${code}`);
85+
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
8486
}
8587

8688

@@ -107,6 +109,15 @@ class _ParseAST {
107109
return n.toString();
108110
}
109111

112+
expectIdentifierOrKeywordOrString():string {
113+
var n = this.next;
114+
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
115+
this.error(`Unexpected token ${n}, expected identifier, or keyword, or string`)
116+
}
117+
this.advance();
118+
return n.toString();
119+
}
120+
110121
parseChain():AST {
111122
var exprs = [];
112123
while (this.index < this.tokens.length) {
@@ -142,11 +153,6 @@ class _ParseAST {
142153
var result = this.parseConditional();
143154

144155
while (this.next.isOperator('=')) {
145-
//if (!backend.isAssignable(result)) {
146-
// int end = (index < tokens.length) ? next.index : input.length;
147-
// String expression = input.substring(start, end);
148-
// error('Expression $expression is not assignable');
149-
// }
150156
this.expectOperator('=');
151157
result = new Assignment(result, this.parseConditional());
152158
}
@@ -269,6 +275,12 @@ class _ParseAST {
269275
while (true) {
270276
if (this.optionalCharacter($PERIOD)) {
271277
result = this.parseFieldRead(result);
278+
279+
} else if (this.optionalCharacter($LBRACKET)) {
280+
var key = this.parseExpression();
281+
this.expectCharacter($RBRACKET);
282+
result = new KeyedAccess(result, key);
283+
272284
} else {
273285
return result;
274286
}
@@ -288,6 +300,14 @@ class _ParseAST {
288300
this.advance();
289301
return new LiteralPrimitive(false);
290302

303+
} else if (this.optionalCharacter($LBRACKET)) {
304+
var elements = this.parseExpressionList($RBRACKET);
305+
this.expectCharacter($RBRACKET);
306+
return new LiteralArray(elements);
307+
308+
} else if (this.next.isCharacter($LBRACE)) {
309+
return this.parseLiteralMap();
310+
291311
} else if (this.next.isIdentifier()) {
292312
return this.parseFieldRead(_implicitReceiver);
293313

@@ -309,6 +329,32 @@ class _ParseAST {
309329
}
310330
}
311331

332+
parseExpressionList(terminator:int):List {
333+
var result = [];
334+
if (!this.next.isCharacter(terminator)) {
335+
do {
336+
ListWrapper.push(result, this.parseExpression());
337+
} while (this.optionalCharacter($COMMA));
338+
}
339+
return result;
340+
}
341+
342+
parseLiteralMap() {
343+
var keys = [];
344+
var values = [];
345+
this.expectCharacter($LBRACE);
346+
if (!this.optionalCharacter($RBRACE)) {
347+
do {
348+
var key = this.expectIdentifierOrKeywordOrString();
349+
ListWrapper.push(keys, key);
350+
this.expectCharacter($COLON);
351+
ListWrapper.push(values, this.parseExpression());
352+
} while (this.optionalCharacter($COMMA));
353+
this.expectCharacter($RBRACE);
354+
}
355+
return new LiteralMap(keys, values);
356+
}
357+
312358
parseFieldRead(receiver):AST {
313359
var id = this.expectIdentifierOrKeyword();
314360
return new FieldRead(receiver, id, this.closureMap.getter(id), this.closureMap.setter(id));

modules/change_detection/test/parser/parser_spec.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
22
import {BaseException, isBlank} from 'facade/lang';
3+
import {MapWrapper} from 'facade/collection';
34
import {Parser} from 'change_detection/parser/parser';
45
import {Lexer} from 'change_detection/parser/lexer';
56
import {Formatter, LiteralPrimitive} from 'change_detection/parser/ast';
@@ -164,6 +165,39 @@ export function main() {
164165
expectEval("a=123; b=234", context).toEqual(234);
165166
expect(context.a).toEqual(123);
166167
expect(context.b).toEqual(234);
168+
169+
context = td([100]);
170+
expectEval('a[0] = 200', context).toEqual(200);
171+
expect(context.a[0]).toEqual(200);
172+
173+
context = td(MapWrapper.createFromPairs([["key", 100]]));
174+
expectEval('a["key"] = 200', context).toEqual(200);
175+
expect(MapWrapper.get(context.a, "key")).toEqual(200);
176+
177+
context = td([MapWrapper.createFromPairs([["key", 100]])]);
178+
expectEval('a[0]["key"] = 200', context).toEqual(200);
179+
expect(MapWrapper.get(context.a[0], "key")).toEqual(200);
180+
});
181+
182+
it('should evaluate array', () => {
183+
expectEval("[1][0]").toEqual(1);
184+
expectEval("[[1]][0][0]").toEqual(1);
185+
expectEval("[]").toEqual([]);
186+
expectEval("[].length").toEqual(0);
187+
expectEval("[1, 2].length").toEqual(2);
188+
});
189+
190+
it("should error when unfinished exception", () => {
191+
expectEvalError('a[0 = 200').toThrowError(new RegExp("Missing expected ]"));
192+
});
193+
194+
it('should evaluate map', () => {
195+
expectEval("{}").toEqual(MapWrapper.create());
196+
expectEval("{a:'b'}").toEqual(MapWrapper.createFromPairs([["a", "b"]]));
197+
expectEval("{'a':'b'}").toEqual(MapWrapper.createFromPairs([["a", "b"]]));
198+
expectEval("{\"a\":'b'}").toEqual(MapWrapper.createFromPairs([["a", "b"]]));
199+
expectEval("{\"a\":'b'}['a']").toEqual("b");
200+
expectEval("{\"a\":'b'}['invalid']").not.toBeDefined();
167201
});
168202

169203
describe("parseBinding", () => {

modules/facade/src/collection.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ export 'dart:core' show Map, List, Set;
55

66
class MapWrapper {
77
static HashMap create() => new HashMap();
8+
static HashMap createFromPairs(List pairs) {
9+
return pairs.fold({}, (m, p){
10+
m[p[0]] = p[1];
11+
return m;
12+
});
13+
}
814
static get(m, k) => m[k];
915
static void set(m, k, v){ m[k] = v; }
1016
static contains(m, k) => m.containsKey(k);

modules/facade/src/collection.es6

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export var Set = window.Set;
66

77
export class MapWrapper {
88
static create():Map { return new Map(); }
9+
static createFromPairs(pairs:List):Map { return new Map(pairs); }
910
static get(m, k) { return m.get(k); }
1011
static set(m, k, v) { m.set(k,v); }
1112
static contains(m, k) { return m.has(k); }

0 commit comments

Comments
 (0)