feat(parser): add support for formatters
This commit is contained in:
parent
8a829d346b
commit
00bc9e5d56
@ -1,4 +1,5 @@
|
|||||||
import {FIELD, toBool, autoConvertAdd} from "facade/lang";
|
import {FIELD, toBool, autoConvertAdd, isBlank, FunctionWrapper, BaseException} from "facade/lang";
|
||||||
|
import {List, ListWrapper} from "facade/collection";
|
||||||
|
|
||||||
export class AST {
|
export class AST {
|
||||||
eval(context, formatters) {
|
eval(context, formatters) {
|
||||||
@ -50,6 +51,28 @@ export class FieldRead extends AST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Formatter extends AST {
|
||||||
|
constructor(exp:AST, name:string, args:List) {
|
||||||
|
this.exp = exp;
|
||||||
|
this.name = name;
|
||||||
|
this.args = args;
|
||||||
|
this.allArgs = ListWrapper.concat([exp], args);
|
||||||
|
}
|
||||||
|
|
||||||
|
eval(context, formatters) {
|
||||||
|
var formatter = formatters[this.name];
|
||||||
|
if (isBlank(formatter)) {
|
||||||
|
throw new BaseException(`No formatter '${this.name}' found!`);
|
||||||
|
}
|
||||||
|
var evaledArgs = evalList(context, this.allArgs, formatters);
|
||||||
|
return FunctionWrapper.apply(formatter, evaledArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
visit(visitor) {
|
||||||
|
visitor.visitFormatter(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class LiteralPrimitive extends AST {
|
export class LiteralPrimitive extends AST {
|
||||||
@FIELD('final value')
|
@FIELD('final value')
|
||||||
constructor(value) {
|
constructor(value) {
|
||||||
@ -128,4 +151,15 @@ export class AstVisitor {
|
|||||||
visitBinary(ast:Binary) {}
|
visitBinary(ast:Binary) {}
|
||||||
visitPrefixNot(ast:PrefixNot) {}
|
visitPrefixNot(ast:PrefixNot) {}
|
||||||
visitLiteralPrimitive(ast:LiteralPrimitive) {}
|
visitLiteralPrimitive(ast:LiteralPrimitive) {}
|
||||||
|
visitFormatter(ast:Formatter) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]];
|
||||||
|
function evalList(context, exps:List, formatters){
|
||||||
|
var length = exps.length;
|
||||||
|
var result = _evalListCache[length];
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
result[i] = exps[i].eval(context, formatters);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
@ -1,9 +1,18 @@
|
|||||||
import {FIELD, int, isBlank} from 'facade/lang';
|
import {FIELD, int, isBlank} from 'facade/lang';
|
||||||
import {ListWrapper, List} from 'facade/collection';
|
import {ListWrapper, List} from 'facade/collection';
|
||||||
import {Lexer, EOF, Token, $PERIOD, $COLON} from './lexer';
|
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON} from './lexer';
|
||||||
import {ClosureMap} from './closure_map';
|
import {ClosureMap} from './closure_map';
|
||||||
import {AST, ImplicitReceiver, FieldRead, LiteralPrimitive, Expression,
|
import {
|
||||||
Binary, PrefixNot, Conditional} from './ast';
|
AST,
|
||||||
|
ImplicitReceiver,
|
||||||
|
FieldRead,
|
||||||
|
LiteralPrimitive,
|
||||||
|
Expression,
|
||||||
|
Binary,
|
||||||
|
PrefixNot,
|
||||||
|
Conditional,
|
||||||
|
Formatter
|
||||||
|
} from './ast';
|
||||||
|
|
||||||
var _implicitReceiver = new ImplicitReceiver();
|
var _implicitReceiver = new ImplicitReceiver();
|
||||||
|
|
||||||
@ -70,12 +79,35 @@ class _ParseAST {
|
|||||||
|
|
||||||
parseChain():AST {
|
parseChain():AST {
|
||||||
var exprs = [];
|
var exprs = [];
|
||||||
|
var isChain = false;
|
||||||
while (this.index < this.tokens.length) {
|
while (this.index < this.tokens.length) {
|
||||||
ListWrapper.push(exprs, this.parseConditional());
|
var expr = this.parseFormatter();
|
||||||
|
ListWrapper.push(exprs, expr);
|
||||||
|
|
||||||
|
while (this.optionalCharacter($SEMICOLON)) {
|
||||||
|
isChain = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChain && expr instanceof Formatter) {
|
||||||
|
this.error('Cannot have a formatter in a chain');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ListWrapper.first(exprs);
|
return ListWrapper.first(exprs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parseFormatter() {
|
||||||
|
var result = this.parseExpression();
|
||||||
|
while (this.optionalOperator("|")) {
|
||||||
|
var name = this.parseIdentifier();
|
||||||
|
var args = ListWrapper.create();
|
||||||
|
while (this.optionalCharacter($COLON)) {
|
||||||
|
ListWrapper.push(args, this.parseExpression());
|
||||||
|
}
|
||||||
|
result = new Formatter(result, name, args);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
parseExpression() {
|
parseExpression() {
|
||||||
return this.parseConditional();
|
return this.parseConditional();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
|
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||||
|
import {BaseException} from 'facade/lang';
|
||||||
import {Parser} from 'change_detection/parser/parser';
|
import {Parser} from 'change_detection/parser/parser';
|
||||||
import {Lexer} from 'change_detection/parser/lexer';
|
import {Lexer} from 'change_detection/parser/lexer';
|
||||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||||
@ -12,7 +13,7 @@ class TestData {
|
|||||||
|
|
||||||
class ContextWithErrors {
|
class ContextWithErrors {
|
||||||
get boo() {
|
get boo() {
|
||||||
throw new Error("boo to you");
|
throw new BaseException("boo to you");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +148,40 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('formatters', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
formatters = {
|
||||||
|
"uppercase": (s) => s.toUpperCase(),
|
||||||
|
"lowercase": (s) => s.toLowerCase(),
|
||||||
|
"increment": (a,b) => a + b
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call a formatter', () => {
|
||||||
|
expectEval("'Foo'|uppercase").toEqual("FOO");
|
||||||
|
expectEval("'fOo'|uppercase|lowercase").toEqual("foo");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call a formatter with arguments', () => {
|
||||||
|
expectEval("1|increment:2").toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when invalid formatter', () => {
|
||||||
|
expectEvalError("1|nonexistent").toThrowError('No formatter \'nonexistent\' found!');
|
||||||
|
});;
|
||||||
|
|
||||||
|
it('should not allow formatters in a chain', () => {
|
||||||
|
expectEvalError("1;'World'|hello").
|
||||||
|
toThrowError(new RegExp('Cannot have a formatter in a chain'));
|
||||||
|
expectEvalError("'World'|hello;1").
|
||||||
|
toThrowError(new RegExp('Cannot have a formatter in a chain'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("error handling", () => {
|
describe("error handling", () => {
|
||||||
it('should throw on incorrect ternary operator syntax', () => {
|
it('should throw on incorrect ternary operator syntax', () => {
|
||||||
expectEvalError("true?1").toThrowError(new RegExp('Parser Error: Conditional expression true\\?1 requires all 3 expressions'));
|
expectEvalError("true?1").
|
||||||
|
toThrowError(new RegExp('Parser Error: Conditional expression true\\?1 requires all 3 expressions'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass exceptions', () => {
|
it('should pass exceptions', () => {
|
||||||
|
@ -46,6 +46,7 @@ class ListWrapper {
|
|||||||
static last(List list) => list.last;
|
static last(List list) => list.last;
|
||||||
static List reversed(List list) => list.reversed.toList();
|
static List reversed(List list) => list.reversed.toList();
|
||||||
static void push(List l, e) { l.add(e); }
|
static void push(List l, e) { l.add(e); }
|
||||||
|
static List concat(List a, List b) {a.addAll(b); return a;}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SetWrapper {
|
class SetWrapper {
|
||||||
|
@ -79,6 +79,7 @@ export class ListWrapper {
|
|||||||
var a = ListWrapper.clone(array);
|
var a = ListWrapper.clone(array);
|
||||||
return a.reverse();
|
return a.reverse();
|
||||||
}
|
}
|
||||||
|
static concat(a, b) {return a.concat(b);}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SetWrapper {
|
export class SetWrapper {
|
||||||
|
@ -103,3 +103,18 @@ class RegExpMatcherWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FunctionWrapper {
|
||||||
|
static apply(Function fn, posArgs) {
|
||||||
|
return Function.apply(fn, posArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseException extends Error {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
BaseException(this.message);
|
||||||
|
|
||||||
|
String toString() {
|
||||||
|
return this.message;
|
||||||
|
}
|
||||||
|
}
|
@ -137,3 +137,18 @@ export class RegExpMatcherWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class FunctionWrapper {
|
||||||
|
static apply(fn:Function, posArgs) {
|
||||||
|
return fn.apply(null, posArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseException extends Error {
|
||||||
|
constructor(message){
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString():String {
|
||||||
|
return this.message;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user