| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library dev_compiler.src.codegen.js_metalet; |
| 6 |
| 7 // TODO(jmesserly): import from its own package |
| 8 import 'package:dev_compiler/src/js/js_ast.dart'; |
| 9 import 'package:dev_compiler/src/js/precedence.dart'; |
| 10 |
| 11 import 'js_names.dart' show JSTemporary; |
| 12 |
| 13 /// A synthetic `let*` node, similar to that found in Scheme. |
| 14 /// |
| 15 /// For example, postfix increment can be desugared as: |
| 16 /// |
| 17 /// // psuedocode mix of Scheme and JS: |
| 18 /// (let* (x1=expr1, x2=expr2, t=x1[x2]) { x1[x2] = t + 1; t }) |
| 19 /// |
| 20 /// [JSMetaLet] will simplify itself automatically when [toExpression], |
| 21 /// [toStatement], or [toReturn] is called. |
| 22 /// |
| 23 /// * variables used once will be inlined. |
| 24 /// * if used in a statement context they can emit as blocks. |
| 25 /// * if return value is not used it can be eliminated, see [statelessResult]. |
| 26 /// * if there are no variables, the codegen will be simplified. |
| 27 /// |
| 28 /// Because this deals with JS AST nodes, it is not aware of any Dart semantics |
| 29 /// around statelessness (such as `final` variables). [variables] should not |
| 30 /// be created for these Dart expressions. |
| 31 /// |
| 32 class JSMetaLet extends Expression { |
| 33 /// Creates a temporary to contain the value of [expr]. The temporary can be |
| 34 /// used multiple times in the resulting expression. For example: |
| 35 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will |
| 36 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. |
| 37 /// |
| 38 /// If the expression does not end up using `x` more than once, or if those |
| 39 /// expressions can be treated as [stateless] (e.g. they are non-mutated |
| 40 /// variables), then the resulting code will be simplified automatically. |
| 41 final Map<String, Expression> variables; |
| 42 |
| 43 /// A list of expressions in the body. |
| 44 /// Conceptually this is like a comma expression: the last value is returned. |
| 45 final List<Expression> body; |
| 46 |
| 47 /// True if the final expression in [body] can be skipped in [toStatement]. |
| 48 final bool statelessResult; |
| 49 |
| 50 /// We run [toExpression] implicitly when the JS AST is visited, to get the |
| 51 /// transformation to happen before the tree is printed. |
| 52 /// This happens multiple times, so ensure the expression form is cached. |
| 53 Expression _expression; |
| 54 |
| 55 JSMetaLet(this.variables, this.body, {this.statelessResult: false}); |
| 56 |
| 57 /// Returns an expression that ignores the result. This is a cross between |
| 58 /// [toExpression] and [toStatement]. Used for C-style for-loop updaters, |
| 59 /// which is an expression syntactically, but functions more like a statement. |
| 60 Expression toVoidExpression() { |
| 61 var block = toStatement(); |
| 62 var s = block.statements; |
| 63 if (s.length == 1 && s.first is ExpressionStatement) { |
| 64 ExpressionStatement es = s.first; |
| 65 return es.expression; |
| 66 } |
| 67 return new Call(new ArrowFun([], block), []); |
| 68 } |
| 69 |
| 70 Expression toAssignExpression(Expression left) { |
| 71 if (left is Identifier) { |
| 72 var simple = _simplifyAssignment(left); |
| 73 if (simple != null) return simple; |
| 74 |
| 75 var exprs = body.toList(); |
| 76 exprs.add(exprs.removeLast().toAssignExpression(left)); |
| 77 return new JSMetaLet(variables, exprs); |
| 78 } |
| 79 return super.toAssignExpression(left); |
| 80 } |
| 81 |
| 82 Statement toVariableDeclaration(Identifier name) { |
| 83 var simple = _simplifyAssignment(name, isDeclaration: true); |
| 84 if (simple != null) return simple.toStatement(); |
| 85 return super.toVariableDeclaration(name); |
| 86 } |
| 87 |
| 88 Expression toExpression() { |
| 89 if (_expression != null) return _expression; |
| 90 var block = toReturn(); |
| 91 var s = block.statements; |
| 92 if (s.length == 1 && s.first is Return) { |
| 93 Return es = s.first; |
| 94 return _expression = es.value; |
| 95 } |
| 96 // Wrap it in an immediately called function to get in expression context. |
| 97 // TODO(jmesserly): |
| 98 return _expression = new Call(new ArrowFun([], block), []); |
| 99 } |
| 100 |
| 101 Block toStatement() { |
| 102 // Skip return value if not used. |
| 103 var statements = body.map((e) => e.toStatement()).toList(); |
| 104 if (statelessResult) statements.removeLast(); |
| 105 return _finishStatement(statements); |
| 106 } |
| 107 |
| 108 Block toReturn() { |
| 109 var statements = body |
| 110 .map((e) => e == body.last ? e.toReturn() : e.toStatement()) |
| 111 .toList(); |
| 112 return _finishStatement(statements); |
| 113 } |
| 114 |
| 115 accept(NodeVisitor visitor) => toExpression().accept(visitor); |
| 116 |
| 117 void visitChildren(NodeVisitor visitor) { |
| 118 toExpression().visitChildren(visitor); |
| 119 } |
| 120 |
| 121 /// This generates as either a comma expression or a call. |
| 122 int get precedenceLevel => variables.isEmpty ? EXPRESSION : CALL; |
| 123 |
| 124 Block _finishStatement(List<Statement> statements) { |
| 125 var params = []; |
| 126 var values = []; |
| 127 var block = _build(params, values, new Block(statements)); |
| 128 if (params.isEmpty) return block; |
| 129 |
| 130 var vars = []; |
| 131 for (int i = 0; i < params.length; i++) { |
| 132 vars.add(new VariableInitialization(params[i], values[i])); |
| 133 } |
| 134 |
| 135 return new Block( |
| 136 [new VariableDeclarationList('let', vars).toStatement(), block]); |
| 137 } |
| 138 |
| 139 Node _build(List<JSTemporary> params, List<Expression> values, Node node) { |
| 140 // Visit the tree and count how many times each temp was used. |
| 141 var counter = new _VariableUseCounter(); |
| 142 node.accept(counter); |
| 143 // Also count the init expressions. |
| 144 for (var init in variables.values) init.accept(counter); |
| 145 |
| 146 var substitutions = {}; |
| 147 _substitute(node) => new Template(null, node).safeCreate(substitutions); |
| 148 |
| 149 variables.forEach((name, init) { |
| 150 // Since this is let*, subsequent variables can refer to previous ones, |
| 151 // so we need to substitute here. |
| 152 init = _substitute(init); |
| 153 int n = counter.counts[name]; |
| 154 if (n == null || n < 2) { |
| 155 substitutions[name] = _substitute(init); |
| 156 } else { |
| 157 params.add(substitutions[name] = new JSTemporary(name)); |
| 158 values.add(init); |
| 159 } |
| 160 }); |
| 161 |
| 162 // Interpolate the body: |
| 163 // Replace interpolated exprs with their value, if it only occurs once. |
| 164 // Otherwise replace it with a temp, which will be assigned once. |
| 165 return _substitute(node); |
| 166 } |
| 167 |
| 168 /// If we finish with an assignment to an identifier, try to simplify the |
| 169 /// block. For example: |
| 170 /// |
| 171 /// ((_) => _.add(1), _.add(2), result = _)([]) |
| 172 /// |
| 173 /// Can be transformed to: |
| 174 /// |
| 175 /// (result = [], result.add(1), result.add(2), result) |
| 176 /// |
| 177 /// However we should not simplify in this case because `result` is read: |
| 178 /// |
| 179 /// ((_) => _.addAll(result), _.add(2), result = _)([]) |
| 180 /// |
| 181 JSMetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { |
| 182 // See if the result value is a let* temporary variable. |
| 183 if (body.last is! InterpolatedExpression) return null; |
| 184 |
| 185 InterpolatedExpression last = body.last; |
| 186 String name = last.nameOrPosition; |
| 187 if (!variables.containsKey(name)) return null; |
| 188 |
| 189 // Variables declared can't be used inside their initializer. |
| 190 if (!isDeclaration) { |
| 191 var finder = new _IdentFinder(left.name); |
| 192 for (var expr in body) { |
| 193 if (finder.found) break; |
| 194 expr.accept(finder); |
| 195 } |
| 196 // If the identifier was used elsewhere, bail, because we're going to |
| 197 // change the order of when the assignment happens. |
| 198 if (finder.found) return null; |
| 199 } |
| 200 |
| 201 var vars = new Map<String, Expression>.from(variables); |
| 202 var value = vars.remove(name); |
| 203 Expression assign; |
| 204 if (isDeclaration) { |
| 205 // Technically, putting one of these in a comma expression is not |
| 206 // legal. However when isDeclaration is true, toStatement will be |
| 207 // called immediately on the JSMetaLet, which results in legal JS. |
| 208 assign = new VariableDeclarationList( |
| 209 'let', [new VariableInitialization(left, value)]); |
| 210 } else { |
| 211 assign = value.toAssignExpression(left); |
| 212 } |
| 213 |
| 214 var newBody = new Expression.binary([assign]..addAll(body), ','); |
| 215 Binary comma = new Template(null, newBody).safeCreate({name: left}); |
| 216 return new JSMetaLet(vars, comma.commaToExpressionList(), |
| 217 statelessResult: statelessResult); |
| 218 } |
| 219 } |
| 220 |
| 221 class _VariableUseCounter extends BaseVisitor { |
| 222 final counts = <String, int>{}; |
| 223 visitInterpolatedExpression(InterpolatedExpression node) { |
| 224 int n = counts[node.nameOrPosition]; |
| 225 counts[node.nameOrPosition] = n == null ? 1 : n + 1; |
| 226 } |
| 227 } |
| 228 |
| 229 class _IdentFinder extends BaseVisitor { |
| 230 final String name; |
| 231 bool found = false; |
| 232 _IdentFinder(this.name); |
| 233 |
| 234 visitIdentifier(Identifier node) { |
| 235 if (node.name == name) found = true; |
| 236 } |
| 237 visitNode(Node node) { |
| 238 if (!found) super.visitNode(node); |
| 239 } |
| 240 } |
| OLD | NEW |