feat(Parser): add support for arrays and maps

This commit is contained in:
vsavkin 2014-11-05 13:48:36 -08:00
parent 8cc008bda1
commit ac060ed405
6 changed files with 160 additions and 16 deletions

View File

@ -1,5 +1,5 @@
import {FIELD, toBool, autoConvertAdd, isBlank, FunctionWrapper, BaseException} from "facade/lang";
import {List, ListWrapper} from "facade/collection";
import {List, Map, ListWrapper, MapWrapper} from "facade/collection";
export class AST {
eval(context) {
@ -89,6 +89,32 @@ export class KeyedAccess extends AST {
this.obj = obj;
this.key = key;
}
eval(context) {
var obj = this.obj.eval(context);
var key = this.key.eval(context);
if (obj instanceof Map) {
return MapWrapper.get(obj, key);
} else if (obj instanceof List) {
return ListWrapper.get(obj, key);
} else {
throw new BaseException(`Cannot access ${key} on ${obj}`);
}
}
assign(context, value) {
var obj = this.obj.eval(context);
var key = this.key.eval(context);
if (obj instanceof Map) {
MapWrapper.set(obj, key, value);
} else if (obj instanceof List) {
ListWrapper.set(obj, key, value);
} else {
throw new BaseException(`Cannot access ${key} on ${obj}`);
}
return value;
}
}
export class Formatter extends AST {
@ -120,6 +146,36 @@ export class LiteralPrimitive extends AST {
}
}
export class LiteralArray extends AST {
@FIELD('final expressions:List')
constructor(expressions:List) {
this.expressions = expressions;
}
eval(context) {
return ListWrapper.map(this.expressions, (e) => e.eval(context));
}
visit(visitor) {
visitor.visitLiteralArray(this);
}
}
export class LiteralMap extends AST {
@FIELD('final keys:List')
@FIELD('final values:List')
constructor(keys:List, values:List) {
this.keys = keys;
this.values = values;
}
eval(context) {
var res = MapWrapper.create();
for(var i = 0; i < this.keys.length; ++i) {
MapWrapper.set(res, this.keys[i], this.values[i].eval(context));
}
return res;
}
}
export class Binary extends AST {
@FIELD('final operation:string')
@FIELD('final left:AST')
@ -203,6 +259,7 @@ export class AstVisitor {
visitLiteralPrimitive(ast:LiteralPrimitive) {}
visitFormatter(ast:Formatter) {}
visitAssignment(ast:Assignment) {}
visitLiteralArray(ast:LiteralArray) {}
}
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]];

View File

@ -157,9 +157,9 @@ const $A = 65, $B = 66, $C = 67, $D = 68, $E = 69, $F = 70, $G = 71, $H = 72,
$Q = 81, $R = 82, $S = 83, $T = 84, $U = 85, $V = 86, $W = 87, $X = 88,
$Y = 89, $Z = 90;
const $LBRACKET = 91;
const $BACKSLASH = 92;
const $RBRACKET = 93;
export const $LBRACKET = 91;
export const $BACKSLASH = 92;
export const $RBRACKET = 93;
const $CARET = 94;
const $_ = 95;
@ -168,9 +168,9 @@ const $a = 97, $b = 98, $c = 99, $d = 100, $e = 101, $f = 102, $g = 103,
$o = 111, $p = 112, $q = 113, $r = 114, $s = 115, $t = 116, $u = 117,
$v = 118, $w = 119, $x = 120, $y = 121, $z = 122;
const $LBRACE = 123;
const $BAR = 124;
const $RBRACE = 125;
export const $LBRACE = 123;
export const $BAR = 124;
export const $RBRACE = 125;
const $TILDE = 126;
const $NBSP = 160;

View File

@ -1,6 +1,6 @@
import {FIELD, int, isBlank, BaseException} from 'facade/lang';
import {FIELD, int, isBlank, BaseException, StringWrapper} from 'facade/lang';
import {ListWrapper, List} from 'facade/collection';
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET} from './lexer';
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET, $COMMA, $LBRACE, $RBRACE} from './lexer';
import {ClosureMap} from './closure_map';
import {
AST,
@ -14,7 +14,9 @@ import {
Formatter,
Assignment,
Chain,
KeyedAccess
KeyedAccess,
LiteralArray,
LiteralMap
} from './ast';
var _implicitReceiver = new ImplicitReceiver();
@ -80,7 +82,7 @@ class _ParseAST {
expectCharacter(code:int) {
if (this.optionalCharacter(code)) return;
this.error(`Missing expected ${code}`);
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
}
@ -107,6 +109,15 @@ class _ParseAST {
return n.toString();
}
expectIdentifierOrKeywordOrString():string {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
this.error(`Unexpected token ${n}, expected identifier, or keyword, or string`)
}
this.advance();
return n.toString();
}
parseChain():AST {
var exprs = [];
while (this.index < this.tokens.length) {
@ -142,11 +153,6 @@ class _ParseAST {
var result = this.parseConditional();
while (this.next.isOperator('=')) {
//if (!backend.isAssignable(result)) {
// int end = (index < tokens.length) ? next.index : input.length;
// String expression = input.substring(start, end);
// error('Expression $expression is not assignable');
// }
this.expectOperator('=');
result = new Assignment(result, this.parseConditional());
}
@ -269,6 +275,12 @@ class _ParseAST {
while (true) {
if (this.optionalCharacter($PERIOD)) {
result = this.parseFieldRead(result);
} else if (this.optionalCharacter($LBRACKET)) {
var key = this.parseExpression();
this.expectCharacter($RBRACKET);
result = new KeyedAccess(result, key);
} else {
return result;
}
@ -288,6 +300,14 @@ class _ParseAST {
this.advance();
return new LiteralPrimitive(false);
} else if (this.optionalCharacter($LBRACKET)) {
var elements = this.parseExpressionList($RBRACKET);
this.expectCharacter($RBRACKET);
return new LiteralArray(elements);
} else if (this.next.isCharacter($LBRACE)) {
return this.parseLiteralMap();
} else if (this.next.isIdentifier()) {
return this.parseFieldRead(_implicitReceiver);
@ -309,6 +329,32 @@ class _ParseAST {
}
}
parseExpressionList(terminator:int):List {
var result = [];
if (!this.next.isCharacter(terminator)) {
do {
ListWrapper.push(result, this.parseExpression());
} while (this.optionalCharacter($COMMA));
}
return result;
}
parseLiteralMap() {
var keys = [];
var values = [];
this.expectCharacter($LBRACE);
if (!this.optionalCharacter($RBRACE)) {
do {
var key = this.expectIdentifierOrKeywordOrString();
ListWrapper.push(keys, key);
this.expectCharacter($COLON);
ListWrapper.push(values, this.parseExpression());
} while (this.optionalCharacter($COMMA));
this.expectCharacter($RBRACE);
}
return new LiteralMap(keys, values);
}
parseFieldRead(receiver):AST {
var id = this.expectIdentifierOrKeyword();
return new FieldRead(receiver, id, this.closureMap.getter(id), this.closureMap.setter(id));

View File

@ -1,5 +1,6 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
import {BaseException, isBlank} from 'facade/lang';
import {MapWrapper} from 'facade/collection';
import {Parser} from 'change_detection/parser/parser';
import {Lexer} from 'change_detection/parser/lexer';
import {Formatter, LiteralPrimitive} from 'change_detection/parser/ast';
@ -164,6 +165,39 @@ export function main() {
expectEval("a=123; b=234", context).toEqual(234);
expect(context.a).toEqual(123);
expect(context.b).toEqual(234);
context = td([100]);
expectEval('a[0] = 200', context).toEqual(200);
expect(context.a[0]).toEqual(200);
context = td(MapWrapper.createFromPairs([["key", 100]]));
expectEval('a["key"] = 200', context).toEqual(200);
expect(MapWrapper.get(context.a, "key")).toEqual(200);
context = td([MapWrapper.createFromPairs([["key", 100]])]);
expectEval('a[0]["key"] = 200', context).toEqual(200);
expect(MapWrapper.get(context.a[0], "key")).toEqual(200);
});
it('should evaluate array', () => {
expectEval("[1][0]").toEqual(1);
expectEval("[[1]][0][0]").toEqual(1);
expectEval("[]").toEqual([]);
expectEval("[].length").toEqual(0);
expectEval("[1, 2].length").toEqual(2);
});
it("should error when unfinished exception", () => {
expectEvalError('a[0 = 200').toThrowError(new RegExp("Missing expected ]"));
});
it('should evaluate map', () => {
expectEval("{}").toEqual(MapWrapper.create());
expectEval("{a:'b'}").toEqual(MapWrapper.createFromPairs([["a", "b"]]));
expectEval("{'a':'b'}").toEqual(MapWrapper.createFromPairs([["a", "b"]]));
expectEval("{\"a\":'b'}").toEqual(MapWrapper.createFromPairs([["a", "b"]]));
expectEval("{\"a\":'b'}['a']").toEqual("b");
expectEval("{\"a\":'b'}['invalid']").not.toBeDefined();
});
describe("parseBinding", () => {

View File

@ -5,6 +5,12 @@ export 'dart:core' show Map, List, Set;
class MapWrapper {
static HashMap create() => new HashMap();
static HashMap createFromPairs(List pairs) {
return pairs.fold({}, (m, p){
m[p[0]] = p[1];
return m;
});
}
static get(m, k) => m[k];
static void set(m, k, v){ m[k] = v; }
static contains(m, k) => m.containsKey(k);

View File

@ -6,6 +6,7 @@ export var Set = window.Set;
export class MapWrapper {
static create():Map { return new Map(); }
static createFromPairs(pairs:List):Map { return new Map(pairs); }
static get(m, k) { return m.get(k); }
static set(m, k, v) { m.set(k,v); }
static contains(m, k) { return m.has(k); }