Index: lib/src/codegen/js_metalet.dart |
diff --git a/lib/src/codegen/js_metalet.dart b/lib/src/codegen/js_metalet.dart |
deleted file mode 100644 |
index 02d7842fcd8671d684763840d7972f8817627a0c..0000000000000000000000000000000000000000 |
--- a/lib/src/codegen/js_metalet.dart |
+++ /dev/null |
@@ -1,326 +0,0 @@ |
-// Copyright (c) 2015, 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. |
- |
-// TODO(jmesserly): import from its own package |
-import '../js/js_ast.dart'; |
-import '../js/precedence.dart'; |
- |
-import 'js_names.dart' show TemporaryId; |
- |
-/// A synthetic `let*` node, similar to that found in Scheme. |
-/// |
-/// For example, postfix increment can be desugared as: |
-/// |
-/// // psuedocode mix of Scheme and JS: |
-/// (let* (x1=expr1, x2=expr2, t=x1[x2]) { x1[x2] = t + 1; t }) |
-/// |
-/// [MetaLet] will simplify itself automatically when [toExpression], |
-/// [toStatement], [toReturn], or [toYieldStatement] is called. |
-/// |
-/// * variables used once will be inlined. |
-/// * if used in a statement context they can emit as blocks. |
-/// * if return value is not used it can be eliminated, see [statelessResult]. |
-/// * if there are no variables, the codegen will be simplified. |
-/// |
-/// Because this deals with JS AST nodes, it is not aware of any Dart semantics |
-/// around statelessness (such as `final` variables). [variables] should not |
-/// be created for these Dart expressions. |
-/// |
-class MetaLet extends Expression { |
- /// Creates a temporary to contain the value of [expr]. The temporary can be |
- /// used multiple times in the resulting expression. For example: |
- /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will |
- /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. |
- /// |
- /// If the expression does not end up using `x` more than once, or if those |
- /// expressions can be treated as [stateless] (e.g. they are non-mutated |
- /// variables), then the resulting code will be simplified automatically. |
- final Map<String, Expression> variables; |
- |
- /// A list of expressions in the body. |
- /// The last value should represent the returned value. |
- final List<Expression> body; |
- |
- /// True if the final expression in [body] can be skipped in [toStatement]. |
- final bool statelessResult; |
- |
- /// We run [toExpression] implicitly when the JS AST is visited, to get the |
- /// transformation to happen before the tree is printed. |
- /// This happens multiple times, so ensure the expression form is cached. |
- Expression _expression; |
- |
- MetaLet(this.variables, this.body, {this.statelessResult: false}); |
- |
- /// Returns an expression that ignores the result. This is a cross between |
- /// [toExpression] and [toStatement]. Used for C-style for-loop updaters, |
- /// which is an expression syntactically, but functions more like a statement. |
- Expression toVoidExpression() { |
- var block = toStatement(); |
- var s = block.statements; |
- if (s.length == 1 && s.first is ExpressionStatement) { |
- ExpressionStatement es = s.first; |
- return es.expression; |
- } |
- |
- return _toInvokedFunction(block); |
- } |
- |
- Expression toAssignExpression(Expression left) { |
- if (left is Identifier) { |
- var simple = _simplifyAssignment(left); |
- if (simple != null) return simple; |
- |
- var exprs = body.toList(); |
- exprs.add(exprs.removeLast().toAssignExpression(left)); |
- return new MetaLet(variables, exprs); |
- } |
- return super.toAssignExpression(left); |
- } |
- |
- Statement toVariableDeclaration(Identifier name) { |
- var simple = _simplifyAssignment(name, isDeclaration: true); |
- if (simple != null) return simple.toStatement(); |
- return super.toVariableDeclaration(name); |
- } |
- |
- Expression toExpression() { |
- if (_expression != null) return _expression; |
- var block = toReturn(); |
- var s = block.statements; |
- if (s.length == 1 && s.first is Return) { |
- Return es = s.first; |
- return _expression = es.value; |
- } |
- // Wrap it in an immediately called function to get in expression context. |
- return _expression = _toInvokedFunction(block); |
- } |
- |
- Block toStatement() { |
- // Skip return value if not used. |
- var statements = body.map((e) => e.toStatement()).toList(); |
- if (statelessResult) statements.removeLast(); |
- return _finishStatement(statements); |
- } |
- |
- Block toReturn() { |
- var statements = body |
- .map((e) => e == body.last ? e.toReturn() : e.toStatement()) |
- .toList(); |
- return _finishStatement(statements); |
- } |
- |
- Block toYieldStatement({bool star: false}) { |
- var statements = body |
- .map((e) => |
- e == body.last ? e.toYieldStatement(star: star) : e.toStatement()) |
- .toList(); |
- return _finishStatement(statements); |
- } |
- |
- accept(NodeVisitor visitor) { |
- // TODO(jmesserly): we special case vistors from js_ast.Template, because it |
- // doesn't know about MetaLet. Should we integrate directly? |
- if (visitor is InstantiatorGeneratorVisitor) { |
- return _templateVisitMetaLet(visitor); |
- } else if (visitor is InterpolatedNodeAnalysis) { |
- return visitor.visitNode(this); |
- } else { |
- return toExpression().accept(visitor); |
- } |
- } |
- |
- void visitChildren(NodeVisitor visitor) { |
- // TODO(jmesserly): we special case vistors from js_ast.Template, because it |
- // doesn't know about MetaLet. Should we integrate directly? |
- if (visitor is InterpolatedNodeAnalysis || |
- visitor is InstantiatorGeneratorVisitor) { |
- variables.values.forEach((v) => v.accept(visitor)); |
- body.forEach((v) => v.accept(visitor)); |
- } else { |
- toExpression().visitChildren(visitor); |
- } |
- } |
- |
- /// This generates as either a comma expression or a call. |
- int get precedenceLevel => variables.isEmpty ? EXPRESSION : CALL; |
- |
- /// Patch to pretend [Template] supports visitMetaLet. |
- Instantiator _templateVisitMetaLet(InstantiatorGeneratorVisitor visitor) { |
- var valueInstantiators = variables.values.map(visitor.visit); |
- var bodyInstantiators = body.map(visitor.visit); |
- |
- return (args) => new MetaLet( |
- new Map.fromIterables( |
- variables.keys, valueInstantiators.map((i) => i(args))), |
- bodyInstantiators.map((i) => i(args)).toList(), |
- statelessResult: statelessResult); |
- } |
- |
- Expression _toInvokedFunction(Statement block) { |
- var finder = new _YieldFinder(); |
- block.accept(finder); |
- if (!finder.hasYield) { |
- return new Call(new ArrowFun([], block), []); |
- } |
- // If we have a yield, it's more tricky. We'll create a `function*`, which |
- // we `yield*` to immediately invoke. We also may need to bind this: |
- Expression fn = new Fun([], block, isGenerator: true); |
- if (finder.hasThis) fn = js.call('#.bind(this)', fn); |
- return new Yield(new Call(fn, []), star: true); |
- } |
- |
- Block _finishStatement(List<Statement> statements) { |
- var params = <TemporaryId>[]; |
- var values = <Expression>[]; |
- var block = _build(params, values, new Block(statements)); |
- if (params.isEmpty) return block; |
- |
- var vars = []; |
- for (int i = 0; i < params.length; i++) { |
- vars.add(new VariableInitialization(params[i], values[i])); |
- } |
- |
- return new Block(<Statement>[ |
- new VariableDeclarationList('let', vars).toStatement(), |
- block |
- ]); |
- } |
- |
- Node _build(List<TemporaryId> params, List<Expression> values, Node node) { |
- // Visit the tree and count how many times each temp was used. |
- var counter = new _VariableUseCounter(); |
- node.accept(counter); |
- // Also count the init expressions. |
- for (var init in variables.values) init.accept(counter); |
- |
- var substitutions = {}; |
- _substitute(node) => new Template(null, node).safeCreate(substitutions); |
- |
- variables.forEach((name, init) { |
- // Since this is let*, subsequent variables can refer to previous ones, |
- // so we need to substitute here. |
- init = _substitute(init); |
- int n = counter.counts[name]; |
- if (n == null || n < 2) { |
- substitutions[name] = _substitute(init); |
- } else { |
- params.add(substitutions[name] = new TemporaryId(name)); |
- values.add(init); |
- } |
- }); |
- |
- // Interpolate the body: |
- // Replace interpolated exprs with their value, if it only occurs once. |
- // Otherwise replace it with a temp, which will be assigned once. |
- return _substitute(node); |
- } |
- |
- /// If we finish with an assignment to an identifier, try to simplify the |
- /// block. For example: |
- /// |
- /// ((_) => _.add(1), _.add(2), result = _)([]) |
- /// |
- /// Can be transformed to: |
- /// |
- /// (result = [], result.add(1), result.add(2), result) |
- /// |
- /// However we should not simplify in this case because `result` is read: |
- /// |
- /// ((_) => _.addAll(result), _.add(2), result = _)([]) |
- /// |
- MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { |
- // See if the result value is a let* temporary variable. |
- if (body.last is! InterpolatedExpression) return null; |
- |
- InterpolatedExpression last = body.last; |
- String name = last.nameOrPosition; |
- if (!variables.containsKey(name)) return null; |
- |
- // Variables declared can't be used inside their initializer, so make |
- // sure we don't transform an assignment into an initializer. |
- // If this already was a declaration, then we know it's legal, so we can |
- // skip the check. |
- if (!isDeclaration) { |
- var finder = new _IdentFinder(left.name); |
- for (var expr in body) { |
- if (finder.found) break; |
- expr.accept(finder); |
- } |
- // If the identifier was used elsewhere, bail, because we're going to |
- // change the order of when the assignment happens. |
- if (finder.found) return null; |
- } |
- |
- var vars = new Map<String, Expression>.from(variables); |
- var value = vars.remove(name); |
- Expression assign; |
- if (isDeclaration) { |
- // Technically, putting one of these in a comma expression is not |
- // legal. However when isDeclaration is true, toStatement will be |
- // called immediately on the MetaLet, which results in legal JS. |
- assign = new VariableDeclarationList( |
- 'let', [new VariableInitialization(left, value)]); |
- } else { |
- assign = value.toAssignExpression(left); |
- } |
- |
- var newBody = new Expression.binary([assign]..addAll(body), ','); |
- Binary comma = new Template(null, newBody).safeCreate({name: left}); |
- return new MetaLet(vars, comma.commaToExpressionList(), |
- statelessResult: statelessResult); |
- } |
-} |
- |
-class _VariableUseCounter extends BaseVisitor { |
- final counts = <String, int>{}; |
- @override |
- visitInterpolatedExpression(InterpolatedExpression node) { |
- int n = counts[node.nameOrPosition]; |
- counts[node.nameOrPosition] = n == null ? 1 : n + 1; |
- } |
-} |
- |
-class _IdentFinder extends BaseVisitor { |
- final String name; |
- bool found = false; |
- _IdentFinder(this.name); |
- |
- @override |
- visitIdentifier(Identifier node) { |
- if (node.name == name) found = true; |
- } |
- |
- @override |
- visitNode(Node node) { |
- if (!found) super.visitNode(node); |
- } |
-} |
- |
-class _YieldFinder extends BaseVisitor { |
- bool hasYield = false; |
- bool hasThis = false; |
- bool _nestedFunction = false; |
- @override |
- visitThis(This node) { |
- hasThis = true; |
- } |
- |
- @override |
- visitFunctionExpression(FunctionExpression node) { |
- var savedNested = _nestedFunction; |
- _nestedFunction = true; |
- super.visitFunctionExpression(node); |
- _nestedFunction = savedNested; |
- } |
- |
- @override |
- visitYield(Yield node) { |
- if (!_nestedFunction) hasYield = true; |
- } |
- |
- @override |
- visitNode(Node node) { |
- if (!hasYield) super.visitNode(node); |
- } |
-} |