blob: 94024d3b2e8ba03c79cabdd0f3e30aeca9d57cce [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of js;
class TemplateManager {
Map<String, Template> expressionTemplates = new Map<String, Template>();
Map<String, Template> statementTemplates = new Map<String, Template>();
TemplateManager();
// TODO(18886): Remove this function once the memory-leak in the VM is fixed.
void clear() {
expressionTemplates.clear();
statementTemplates.clear();
}
Template lookupExpressionTemplate(String source) {
return expressionTemplates[source];
}
Template defineExpressionTemplate(String source, Node ast) {
Template template =
new Template(source, ast, isExpression: true, forceCopy: false);
expressionTemplates[source] = template;
return template;
}
Template lookupStatementTemplate(String source) {
return statementTemplates[source];
}
Template defineStatementTemplate(String source, Node ast) {
Template template =
new Template(source, ast, isExpression: false, forceCopy: false);
statementTemplates[source] = template;
return template;
}
}
/**
* A Template is created with JavaScript AST containing placeholders (interface
* InterpolatedNode). The [instantiate] method creates an AST that looks like
* the original with the placeholders replaced by the arguments to
* [instantiate].
*/
class Template {
final String source;
final bool isExpression;
final bool forceCopy;
final Node ast;
Instantiator instantiator;
int positionalArgumentCount = -1;
// TODO(sra): Named arguments.
Template(this.source, this.ast,
{this.isExpression: true, this.forceCopy: false}) {
_compile();
}
Template.withExpressionResult(this.ast)
: source = null, isExpression = true, forceCopy = false {
assert(ast is Expression);
assert(_checkNoPlaceholders());
positionalArgumentCount = 0;
instantiator = (arguments) => ast;
}
Template.withStatementResult(this.ast)
: source = null, isExpression = false, forceCopy = false {
assert(ast is Statement);
assert(_checkNoPlaceholders());
positionalArgumentCount = 0;
instantiator = (arguments) => ast;
}
bool _checkNoPlaceholders() {
InstantiatorGeneratorVisitor generator =
new InstantiatorGeneratorVisitor(false);
generator.compile(ast);
return generator.analysis.count == 0;
}
void _compile() {
InstantiatorGeneratorVisitor generator =
new InstantiatorGeneratorVisitor(forceCopy);
instantiator = generator.compile(ast);
positionalArgumentCount = generator.analysis.count;
}
Node instantiate(List arguments) {
if (arguments is List) {
if (arguments.length != positionalArgumentCount) {
throw 'Wrong number of template arguments, given ${arguments.length}, '
'expected $positionalArgumentCount';
}
return instantiator(arguments);
}
// TODO(sra): Add named placeholders and a Map of arguments.
throw new UnimplementedError('Template arguments must be a list');
}
}
/**
* An Instantiator is a Function that generates a JS AST tree or List of
* trees. [arguments] is a List for positional templates, or (TODO) Map for
* named templates.
*/
typedef Node Instantiator(var arguments);
/**
* InstantiatorGeneratorVisitor compiles a template. This class compiles a tree
* containing [InterpolatedNode]s into a function that will create a copy of the
* tree with the interpolated nodes substituted with provided values.
*/
class InstantiatorGeneratorVisitor implements NodeVisitor<Instantiator> {
final bool forceCopy;
InterpolatedNodeAnalysis analysis = new InterpolatedNodeAnalysis();
/**
* The entire tree is cloned if [forceCopy] is true.
*/
InstantiatorGeneratorVisitor(this.forceCopy);
Instantiator compile(Node node) {
analysis.visit(node);
Instantiator result = visit(node);
return result;
}
static error(String message) {
throw message;
}
static Instantiator same(Node node) => (arguments) => node;
static Node makeNull(arguments) => null;
Instantiator visit(Node node) {
if (forceCopy || analysis.containsInterpolatedNodes(node)) {
return node.accept(this);
}
return same(node);
}
Instantiator visitNullable(Node node) {
if (node == null) return makeNull;
return visit(node);
}
Instantiator visitSplayable(Node node) {
// TODO(sra): Process immediate [InterpolatedNode]s, permitting splaying.
return visit(node);
}
Instantiator visitNode(Node node) {
throw 'Unimplemented InstantiatorGeneratorVisitor for $node';
}
static RegExp identiferRE = new RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
static Expression convertStringToVariableUse(String value) {
assert(identiferRE.hasMatch(value));
return new VariableUse(value);
}
Instantiator visitInterpolatedExpression(InterpolatedExpression node) {
int position = node.name;
return (arguments) {
var value = arguments[position];
if (value is Expression) return value;
if (value is String) return convertStringToVariableUse(value);;
error('Interpolated value #$position is not an Expression: $value');
};
}
Instantiator visitSplayableExpression(Node node) {
if (node is InterpolatedExpression) {
int position = node.name;
return (arguments) {
var value = arguments[position];
Expression toExpression(item) {
if (item is Expression) return item;
if (item is String) return convertStringToVariableUse(item);
return error('Interpolated value #$position is not '
'an Expression or List of Expressions: $value');
}
if (value is Iterable) return value.map(toExpression);
return toExpression(value);
};
}
return visit(node);
}
Instantiator visitInterpolatedLiteral(InterpolatedLiteral node) {
int position = node.name;
return (arguments) {
var value = arguments[position];
if (value is Literal) return value;
error('Interpolated value #$position is not a Literal: $value');
};
}
Instantiator visitInterpolatedParameter(InterpolatedParameter node) {
int position = node.name;
return (arguments) {
var value = arguments[position];
Parameter toParameter(item) {
if (item is Parameter) return item;
if (item is String) return new Parameter(item);
error('Interpolated value #$position is not a Parameter or '
'List of Parameters: $value');
}
if (value is Iterable) return value.map(toParameter);
return toParameter(value);
};
}
Instantiator visitInterpolatedSelector(InterpolatedSelector node) {
// A selector is an expression, as in `a[selector]`.
// A String argument converted into a LiteralString, so `a.#` with argument
// 'foo' generates `a["foo"]` which prints as `a.foo`.
int position = node.name;
return (arguments) {
var value = arguments[position];
if (value is Expression) return value;
if (value is String) return new LiteralString('"$value"');
error('Interpolated value #$position is not a selector: $value');
};
}
Instantiator visitInterpolatedStatement(InterpolatedStatement node) {
int position = node.name;
return (arguments) {
var value = arguments[position];
if (value is Node) return value.toStatement();
error('Interpolated value #$position is not a Statement: $value');
};
}
Instantiator visitSplayableStatement(Node node) {
if (node is InterpolatedStatement) {
int position = node.name;
return (arguments) {
var value = arguments[position];
Statement toStatement(item) {
if (item is Statement) return item;
if (item is Expression) return item.toStatement();;
error('Interpolated value #$position is not '
'a Statement or List of Statements: $value');
}
if (value is Iterable) return value.map(toStatement);
return toStatement(value);
};
}
return visit(node);
}
Instantiator visitProgram(Program node) {
List instantiators = node.body.map(visitSplayableStatement).toList();
return (arguments) {
List<Statement> statements = <Statement>[];
void add(node) {
if (node is EmptyStatement) return;
if (node is Iterable) {
statements.addAll(node);
} else {
statements.add(node.toStatement());
}
}
for (Instantiator instantiator in instantiators) {
add(instantiator(arguments));
}
return new Program(statements);
};
}
Instantiator visitBlock(Block node) {
List instantiators = node.statements.map(visitSplayableStatement).toList();
return (arguments) {
List<Statement> statements = <Statement>[];
void add(node) {
if (node is EmptyStatement) return;
if (node is Iterable) {
statements.addAll(node);
} else if (node is Block) {
statements.addAll(node.statements);
} else {
statements.add(node.toStatement());
}
}
for (Instantiator instantiator in instantiators) {
add(instantiator(arguments));
}
return new Block(statements);
};
}
Instantiator visitExpressionStatement(ExpressionStatement node) {
Instantiator buildExpression = visit(node.expression);
return (arguments) {
return buildExpression(arguments).toStatement();
};
}
Instantiator visitEmptyStatement(EmptyStatement node) =>
(arguments) => new EmptyStatement();
Instantiator visitIf(If node) {
if (node.condition is InterpolatedExpression) {
return visitIfConditionalCompilation(node);
} else {
return visitIfNormal(node);
}
}
Instantiator visitIfConditionalCompilation(If node) {
// Special version of visitInterpolatedExpression that permits bools.
compileCondition(InterpolatedExpression node) {
int position = node.name;
return (arguments) {
var value = arguments[position];
if (value is bool) return value;
if (value is Expression) return value;
if (value is String) return convertStringToVariableUse(value);;
error('Interpolated value #$position is not an Expression: $value');
};
}
var makeCondition = compileCondition(node.condition);
Instantiator makeThen = visit(node.then);
Instantiator makeOtherwise = visit(node.otherwise);
return (arguments) {
var condition = makeCondition(arguments);
if (condition is bool) {
if (condition == true) {
return makeThen(arguments);
} else {
return makeOtherwise(arguments);
}
}
return new If(
condition,
makeThen(arguments),
makeOtherwise(arguments));
};
}
Instantiator visitIfNormal(If node) {
Instantiator makeCondition = visit(node.condition);
Instantiator makeThen = visit(node.then);
Instantiator makeOtherwise = visit(node.otherwise);
return (arguments) {
return new If(
makeCondition(arguments),
makeThen(arguments),
makeOtherwise(arguments));
};
}
Instantiator visitFor(For node) {
Instantiator makeInit = visitNullable(node.init);
Instantiator makeCondition = visitNullable(node.condition);
Instantiator makeUpdate = visitNullable(node.update);
Instantiator makeBody = visit(node.body);
return (arguments) {
return new For(
makeInit(arguments), makeCondition(arguments), makeUpdate(arguments),
makeBody(arguments));
};
}
Instantiator visitForIn(ForIn node) {
Instantiator makeLeftHandSide = visit(node.leftHandSide);
Instantiator makeObject = visit(node.object);
Instantiator makeBody = visit(node.body);
return (arguments) {
return new ForIn(
makeLeftHandSide(arguments),
makeObject(arguments),
makeBody(arguments));
};
}
TODO(String name) {
throw new UnimplementedError('$this.$name');
}
Instantiator visitWhile(While node) => TODO('visitWhile');
Instantiator visitDo(Do node) => TODO('visitDo');
Instantiator visitContinue(Continue node) =>
(arguments) => new Continue(node.targetLabel);
Instantiator visitBreak(Break node) =>
(arguments) => new Break(node.targetLabel);
Instantiator visitReturn(Return node) {
Instantiator makeExpression = visitNullable(node.value);
return (arguments) => new Return(makeExpression(arguments));
}
Instantiator visitThrow(Throw node) {
Instantiator makeExpression = visit(node.expression);
return (arguments) => new Throw(makeExpression(arguments));
}
Instantiator visitTry(Try node) {
Instantiator makeBody = visit(node.body);
Instantiator makeCatch = visitNullable(node.catchPart);
Instantiator makeFinally = visitNullable(node.finallyPart);
return (arguments) => new Try(
makeBody(arguments), makeCatch(arguments), makeFinally(arguments));
}
Instantiator visitCatch(Catch node) {
Instantiator makeDeclaration = visit(node.declaration);
Instantiator makeBody = visit(node.body);
return (arguments) => new Catch(
makeDeclaration(arguments), makeBody(arguments));
}
Instantiator visitSwitch(Switch node) => TODO('visitSwitch');
Instantiator visitCase(Case node) => TODO('visitCase');
Instantiator visitDefault(Default node) => TODO('visitDefault');
Instantiator visitFunctionDeclaration(FunctionDeclaration node) {
Instantiator makeName = visit(node.name);
Instantiator makeFunction = visit(node.function);
return (arguments) =>
new FunctionDeclaration(makeName(arguments), makeFunction(arguments));
}
Instantiator visitLabeledStatement(LabeledStatement node) =>
TODO('visitLabeledStatement');
Instantiator visitLiteralStatement(LiteralStatement node) =>
TODO('visitLiteralStatement');
Instantiator visitBlob(Blob node) =>
TODO('visitBlob');
Instantiator visitLiteralExpression(LiteralExpression node) =>
TODO('visitLiteralExpression');
Instantiator visitVariableDeclarationList(VariableDeclarationList node) {
List<Instantiator> declarationMakers =
node.declarations.map(visit).toList();
return (arguments) {
List<VariableInitialization> declarations = <VariableInitialization>[];
for (Instantiator instantiator in declarationMakers) {
var result = instantiator(arguments);
declarations.add(result);
}
return new VariableDeclarationList(declarations);
};
}
Instantiator visitAssignment(Assignment node) {
Instantiator makeLeftHandSide = visit(node.leftHandSide);
String op = node.op;
Instantiator makeValue = visitNullable(node.value);
return (arguments) {
return new Assignment.compound(
makeLeftHandSide(arguments),
op,
makeValue(arguments));
};
}
Instantiator visitVariableInitialization(VariableInitialization node) {
Instantiator makeDeclaration = visit(node.declaration);
Instantiator makeValue = visitNullable(node.value);
return (arguments) {
return new VariableInitialization(
makeDeclaration(arguments), makeValue(arguments));
};
}
Instantiator visitConditional(Conditional cond) {
Instantiator makeCondition = visit(cond.condition);
Instantiator makeThen = visit(cond.then);
Instantiator makeOtherwise = visit(cond.otherwise);
return (arguments) => new Conditional(
makeCondition(arguments),
makeThen(arguments),
makeOtherwise(arguments));
}
Instantiator visitNew(New node) =>
handleCallOrNew(node, (target, arguments) => new New(target, arguments));
Instantiator visitCall(Call node) =>
handleCallOrNew(node, (target, arguments) => new Call(target, arguments));
Instantiator handleCallOrNew(Call node, finish(target, arguments)) {
Instantiator makeTarget = visit(node.target);
Iterable<Instantiator> argumentMakers =
node.arguments.map(visitSplayableExpression).toList();
// TODO(sra): Avoid copying call arguments if no interpolation or forced
// copying.
return (arguments) {
Node target = makeTarget(arguments);
List<Expression> callArguments = <Expression>[];
for (Instantiator instantiator in argumentMakers) {
var result = instantiator(arguments);
if (result is Iterable) {
callArguments.addAll(result);
} else {
callArguments.add(result);
}
}
return finish(target, callArguments.toList(growable: false));
};
}
Instantiator visitBinary(Binary node) {
Instantiator makeLeft = visit(node.left);
Instantiator makeRight = visit(node.right);
String op = node.op;
return (arguments) =>
new Binary(op, makeLeft(arguments), makeRight(arguments));
}
Instantiator visitPrefix(Prefix node) {
Instantiator makeOperand = visit(node.argument);
String op = node.op;
return (arguments) => new Prefix(op, makeOperand(arguments));
}
Instantiator visitPostfix(Postfix node) {
Instantiator makeOperand = visit(node.argument);
String op = node.op;
return (arguments) => new Postfix(op, makeOperand(arguments));
}
Instantiator visitVariableUse(VariableUse node) =>
(arguments) => new VariableUse(node.name);
Instantiator visitThis(This node) => (arguments) => new This();
Instantiator visitVariableDeclaration(VariableDeclaration node) =>
(arguments) => new VariableDeclaration(node.name);
Instantiator visitParameter(Parameter node) =>
(arguments) => new Parameter(node.name);
Instantiator visitAccess(PropertyAccess node) {
Instantiator makeReceiver = visit(node.receiver);
Instantiator makeSelector = visit(node.selector);
return (arguments) =>
new PropertyAccess(makeReceiver(arguments), makeSelector(arguments));
}
Instantiator visitNamedFunction(NamedFunction node) {
Instantiator makeDeclaration = visit(node.name);
Instantiator makeFunction = visit(node.function);
return (arguments) =>
new NamedFunction(makeDeclaration(arguments), makeFunction(arguments));
}
Instantiator visitFun(Fun node) {
List<Instantiator> paramMakers = node.params.map(visitSplayable).toList();
Instantiator makeBody = visit(node.body);
// TODO(sra): Avoid copying params if no interpolation or forced copying.
return (arguments) {
List<Parameter> params = <Parameter>[];
for (Instantiator instantiator in paramMakers) {
var result = instantiator(arguments);
if (result is Iterable) {
params.addAll(result);
} else {
params.add(result);
}
}
Statement body = makeBody(arguments);
return new Fun(params, body);
};
}
Instantiator visitLiteralBool(LiteralBool node) =>
(arguments) => new LiteralBool(node.value);
Instantiator visitLiteralString(LiteralString node) =>
(arguments) => new LiteralString(node.value);
Instantiator visitLiteralNumber(LiteralNumber node) =>
(arguments) => new LiteralNumber(node.value);
Instantiator visitLiteralNull(LiteralNull node) =>
(arguments) => new LiteralNull();
Instantiator visitArrayInitializer(ArrayInitializer node) {
// Assume array has no missing elements.
// TODO(sra): Implement splicing?
List<Instantiator> elementMakers = node.elements
.map((ArrayElement element) => visit(element.value))
.toList();
return (arguments) {
List<ArrayElement> elements = <ArrayElement>[];
void add(Expression value) {
elements.add(new ArrayElement(elements.length, value));
}
for (Instantiator instantiator in elementMakers) {
var result = instantiator(arguments);
add(result);
}
return new ArrayInitializer(elements.length, elements);
};
}
Instantiator visitArrayElement(ArrayElement node) {
throw 'Should not get here'; // Handled in visitArrayInitializer.
}
Instantiator visitObjectInitializer(ObjectInitializer node) {
List<Instantiator> propertyMakers =
node.properties.map(visitSplayable).toList();
bool isOneLiner = node.isOneLiner;
return (arguments) {
List<Property> properties = <Property>[];
for (Instantiator instantiator in propertyMakers) {
var result = instantiator(arguments);
if (result is Iterable) {
properties.addAll(result);
} else {
properties.add(result);
}
}
return new ObjectInitializer(properties, isOneLiner: isOneLiner);
};
}
Instantiator visitProperty(Property node) {
Instantiator makeName = visit(node.name);
Instantiator makeValue = visit(node.value);
return (arguments) {
return new Property(makeName(arguments), makeValue(arguments));
};
}
Instantiator visitRegExpLiteral(RegExpLiteral node) =>
(arguments) => new RegExpLiteral(node.pattern);
Instantiator visitComment(Comment node) => TODO('visitComment');
}
/**
* InterpolatedNodeAnalysis extract [InterpolatedNode]s from AST.
*/
class InterpolatedNodeAnalysis extends BaseVisitor {
final Set<Node> containsInterpolatedNode = new Set<Node>();
final List<InterpolatedNode> interpolatedNodes = <InterpolatedNode>[];
int count = 0;
InterpolatedNodeAnalysis();
bool containsInterpolatedNodes(Node node) =>
containsInterpolatedNode.contains(node);
void visit(Node node) {
node.accept(this);
}
void visitNode(Node node) {
int before = count;
node.visitChildren(this);
if (count != before) containsInterpolatedNode.add(node);
return null;
}
visitInterpolatedNode(InterpolatedNode node) {
interpolatedNodes.add(node);
containsInterpolatedNode.add(node);
++count;
}
}