perf(CD): Special cased interpolation in AST, Parser, and CD

This commit is contained in:
Misko Hevery
2015-01-08 16:17:56 -08:00
parent ee99a5a02b
commit 3b34ef43b1
8 changed files with 128 additions and 52 deletions

View File

@ -249,6 +249,23 @@ export class LiteralMap extends AST {
}
}
export class Interpolation extends AST {
strings:List;
expressions:List;
constructor(strings:List, expressions:List) {
this.strings = strings;
this.expressions = expressions;
}
eval(context) {
throw new Error("unsuported");
}
visit(visitor, args) {
visitor.visitInterpolation(this, args);
}
}
export class Binary extends AST {
operation:string;
left:AST;

View File

@ -1,4 +1,4 @@
import {FIELD, int, isBlank, isPresent, BaseException, StringWrapper} from 'facade/lang';
import {FIELD, int, isBlank, isPresent, BaseException, StringWrapper, RegExpWrapper} from 'facade/lang';
import {ListWrapper, List} from 'facade/collection';
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET,
$COMMA, $LBRACE, $RBRACE, $LPAREN, $RPAREN} from './lexer';
@ -19,6 +19,7 @@ import {
KeyedAccess,
LiteralArray,
LiteralMap,
Interpolation,
MethodCall,
FunctionCall,
TemplateBindings,
@ -27,6 +28,9 @@ import {
} from './ast';
var _implicitReceiver = new ImplicitReceiver();
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
var QUOTE_REGEXP = RegExpWrapper.create("'");
export class Parser {
_lexer:Lexer;
@ -52,6 +56,29 @@ export class Parser {
var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
}
parseInterpolation(input:string, location:any):ASTWithSource {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length <= 1) {
return null;
}
var strings = [];
var expressions = [];
for (var i=0; i<parts.length; i++) {
var part = parts[i];
if (i%2 === 0) {
// fixed string
ListWrapper.push(strings, part);
} else {
var tokens = this._lexer.tokenize(part);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
ListWrapper.push(expressions, ast);
}
}
return new ASTWithSource(new Interpolation(strings, expressions), input, location);
}
}
class _ParseAST {

View File

@ -14,6 +14,7 @@ import {
Formatter,
FunctionCall,
ImplicitReceiver,
Interpolation,
KeyedAccess,
LiteralArray,
LiteralMap,
@ -116,6 +117,11 @@ class ProtoOperationsCreator {
return 0;
}
visitInterpolation(ast:Interpolation) {
var args = this._visitAll(ast.expressions);
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Interpolate()", _interpolationFn(ast.strings), args, 0);
}
visitLiteralPrimitive(ast:LiteralPrimitive) {
return this._addRecord(RECORD_TYPE_CONST, null, ast.value, [], 0);
}
@ -274,4 +280,35 @@ function _cond(cond, trueVal, falseVal) {return cond ? trueVal :
function _keyedAccess(obj, args) {
return obj[args[0]];
}
}
function s(v) {
return isPresent(v) ? '' + v : '';
}
function _interpolationFn(strings:List) {
var length = strings.length;
var i = -1;
var c0 = length > ++i ? strings[i] : null;
var c1 = length > ++i ? strings[i] : null;
var c2 = length > ++i ? strings[i] : null;
var c3 = length > ++i ? strings[i] : null;
var c4 = length > ++i ? strings[i] : null;
var c5 = length > ++i ? strings[i] : null;
var c6 = length > ++i ? strings[i] : null;
var c7 = length > ++i ? strings[i] : null;
var c8 = length > ++i ? strings[i] : null;
var c9 = length > ++i ? strings[i] : null;
switch (length - 1) {
case 1: return (a1) => c0 + s(a1) + c1;
case 2: return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
case 3: return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3;
case 4: return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4;
case 5: return (a1, a2, a3, a4, a5) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5;
case 6: return (a1, a2, a3, a4, a5, a6) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6;
case 7: return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7;
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8;
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8 + s(a9) + c9;
default: throw new BaseException(`Does not support more than 9 expressions`);
}
}

View File

@ -47,6 +47,10 @@ export function main() {
return createParser().parseTemplateBindings(text, location);
}
function parseInterpolation(text, location = null) {
return createParser().parseInterpolation(text, location);
}
function expectEval(text, passedInContext = null) {
var c = isBlank(passedInContext) ? td() : passedInContext;
return expect(parseAction(text).eval(c));
@ -494,6 +498,27 @@ export function main() {
expect(bindings[0].expression.location).toEqual('location');
});
});
describe('parseInterpolation', () => {
it('should return null if no interpolation', () => {
expect(parseInterpolation('nothing')).toBe(null);
});
it('should parse no prefix/suffix interpolation', () => {
var ast = parseInterpolation('{{a}}').ast;
expect(ast.strings).toEqual(['', '']);
expect(ast.expressions.length).toEqual(1);
expect(ast.expressions[0].name).toEqual('a');
});
it('should parse prefix/suffix with multiple interpolation', () => {
var ast = parseInterpolation('before{{a}}middle{{b}}after').ast;
expect(ast.strings).toEqual(['before', 'middle', 'after']);
expect(ast.expressions.length).toEqual(2);
expect(ast.expressions[0].name).toEqual('a');
expect(ast.expressions[1].name).toEqual('b');
});
});
});
}