feat(Parser): add support for arrays and maps
This commit is contained in:
parent
8cc008bda1
commit
ac060ed405
|
@ -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]];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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); }
|
||||
|
|
Loading…
Reference in New Issue