Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(942)

Unified Diff: sdk/lib/_internal/compiler/implementation/js/template.dart

Issue 237583014: JS templates (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: cleanup Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: sdk/lib/_internal/compiler/implementation/js/template.dart
diff --git a/sdk/lib/_internal/compiler/implementation/js/template.dart b/sdk/lib/_internal/compiler/implementation/js/template.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e20e7b65569e2d2fab1f265c4f6a36e9ea85a37d
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/js/template.dart
@@ -0,0 +1,674 @@
+// 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();
+
+
+ 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;
+ /*
floitsch 2014/04/22 16:11:18 debug code.
sra1 2014/04/23 02:33:50 Done.
+ var n = ((expressionTemplates.length == prev) ? '-' : '${expressionTemplates.length}')
+ .padLeft(5);
+ print('E $n $source');
+ */
+ 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;
+ /*
floitsch 2014/04/22 16:11:18 ditto.
sra1 2014/04/23 02:33:50 Done.
sra1 2014/04/23 02:33:50 Done.
+ var n = ((statementTemplates.length == prev) ? '-' : '${statementTemplates.length}')
+ .padLeft(5);
+ print(' S $n $source');
+ */
+ return template;
+ }
+}
+
+/**
+ */
floitsch 2014/04/22 16:11:18 missing doc.
sra1 2014/04/23 02:33:50 Done.
+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;
+ }
+
+ 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 tree containing [InterpolatedNode]s
floitsch 2014/04/22 16:11:18 This class compiles ...
sra1 2014/04/23 02:33:50 Done.
+ * 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]*$');
+
+ Instantiator visitInterpolatedExpression(InterpolatedExpression node) {
+ int position = node.name;
+ return (arguments) {
+ var value = arguments[position];
+ if (value is Expression) return value;
+ if (value is String) {
+ if (!identiferRE.hasMatch(value)) error("Not identifier: '$value'");
+ assert(identiferRE.hasMatch(value));
floitsch 2014/04/22 16:11:18 remove assert.
sra1 2014/04/23 02:33:50 Done.
+ return new VariableUse(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) {
+ assert(identiferRE.hasMatch(item));
+ return new VariableUse(item);
+ }
+ 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 (${value.runtimeType})');
+ };
+ }
+
+ 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 (a ${value.runtimeType})');
+ }
+ 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;
floitsch 2014/04/22 16:11:18 So we only allow "#" to be a bool or an Expression
sra1 2014/04/23 02:33:50 That has been fixed while you were reviewing (and
+ 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.runtimeType}.$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();
floitsch 2014/04/22 16:11:18 long line.
+ return (arguments) {
+ List<VariableInitialization> declarations = <VariableInitialization>[];
+ for (Instantiator instantiator in declarationMakers) {
+ var result = instantiator(arguments);
+ declarations.add(result);
+ }
+ return new VariableDeclarationList(declarations);
+ };
+ }
+
+ Instantiator visitSequence(Sequence node) => TODO('visitSequence');
+
+ Instantiator visitAssignment(Assignment node) {
+ Instantiator makeLeftHandSide = visit(node.leftHandSide);
+ Instantiator makeCompoundTarget = visitNullable(node.compoundTarget);
+ Instantiator makeValue = visitNullable(node.value);
+ return (arguments) {
+ return new Assignment._internal(
+ makeLeftHandSide(arguments),
+ makeCompoundTarget(arguments),
+ 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();
+
+ 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);
+ };
+ }
+
+ 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): 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.
floitsch 2014/04/22 16:11:18 make this an internal error.
sra1 2014/04/23 02:33:50 Is there a way to signal an internal error without
floitsch 2014/04/23 07:29:41 Apparently not :( If you have a location you coul
ahe 2014/04/23 08:38:15 If you don't have a location, just throw an except
+ }
+
+ 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;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698