| 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);
|
| - }
|
| -}
|
|
|