| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // TODO(jmesserly): import from its own package | 5 // TODO(jmesserly): import from its own package |
| 6 import '../js_ast/js_ast.dart'; | 6 import '../js_ast/js_ast.dart'; |
| 7 import '../js_ast/precedence.dart'; | 7 import '../js_ast/precedence.dart'; |
| 8 | 8 |
| 9 import 'js_names.dart' show TemporaryId; | 9 import 'js_names.dart' show TemporaryId; |
| 10 | 10 |
| (...skipping 18 matching lines...) Expand all Loading... |
| 29 /// | 29 /// |
| 30 class MetaLet extends Expression { | 30 class MetaLet extends Expression { |
| 31 /// Creates a temporary to contain the value of [expr]. The temporary can be | 31 /// Creates a temporary to contain the value of [expr]. The temporary can be |
| 32 /// used multiple times in the resulting expression. For example: | 32 /// used multiple times in the resulting expression. For example: |
| 33 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will | 33 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will |
| 34 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. | 34 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. |
| 35 /// | 35 /// |
| 36 /// If the expression does not end up using `x` more than once, or if those | 36 /// If the expression does not end up using `x` more than once, or if those |
| 37 /// expressions can be treated as [stateless] (e.g. they are non-mutated | 37 /// expressions can be treated as [stateless] (e.g. they are non-mutated |
| 38 /// variables), then the resulting code will be simplified automatically. | 38 /// variables), then the resulting code will be simplified automatically. |
| 39 final Map<String, Expression> variables; | 39 final Map<MetaLetVariable, Expression> variables; |
| 40 | 40 |
| 41 /// A list of expressions in the body. | 41 /// A list of expressions in the body. |
| 42 /// The last value should represent the returned value. | 42 /// The last value should represent the returned value. |
| 43 final List<Expression> body; | 43 final List<Expression> body; |
| 44 | 44 |
| 45 /// True if the final expression in [body] can be skipped in [toStatement]. | 45 /// True if the final expression in [body] can be skipped in [toStatement]. |
| 46 final bool statelessResult; | 46 final bool statelessResult; |
| 47 | 47 |
| 48 /// We run [toExpression] implicitly when the JS AST is visited, to get the | 48 /// We run [toExpression] implicitly when the JS AST is visited, to get the |
| 49 /// transformation to happen before the tree is printed. | 49 /// transformation to happen before the tree is printed. |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 164 return new Call(new ArrowFun([], block), []); | 164 return new Call(new ArrowFun([], block), []); |
| 165 } | 165 } |
| 166 // If we have a yield, it's more tricky. We'll create a `function*`, which | 166 // If we have a yield, it's more tricky. We'll create a `function*`, which |
| 167 // we `yield*` to immediately invoke. We also may need to bind this: | 167 // we `yield*` to immediately invoke. We also may need to bind this: |
| 168 Expression fn = new Fun([], block, isGenerator: true); | 168 Expression fn = new Fun([], block, isGenerator: true); |
| 169 if (finder.hasThis) fn = js.call('#.bind(this)', fn); | 169 if (finder.hasThis) fn = js.call('#.bind(this)', fn); |
| 170 return new Yield(new Call(fn, []), star: true); | 170 return new Yield(new Call(fn, []), star: true); |
| 171 } | 171 } |
| 172 | 172 |
| 173 Block _finishStatement(List<Statement> statements) { | 173 Block _finishStatement(List<Statement> statements) { |
| 174 var params = <TemporaryId>[]; | |
| 175 var values = <Expression>[]; | |
| 176 var block = _build(params, values, new Block(statements)); | |
| 177 if (params.isEmpty) return block; | |
| 178 | |
| 179 var vars = []; | |
| 180 for (int i = 0; i < params.length; i++) { | |
| 181 vars.add(new VariableInitialization(params[i], values[i])); | |
| 182 } | |
| 183 | |
| 184 return new Block(<Statement>[ | |
| 185 new VariableDeclarationList('let', vars).toStatement(), | |
| 186 block | |
| 187 ]); | |
| 188 } | |
| 189 | |
| 190 Node _build(List<TemporaryId> params, List<Expression> values, Node node) { | |
| 191 // Visit the tree and count how many times each temp was used. | 174 // Visit the tree and count how many times each temp was used. |
| 192 var counter = new _VariableUseCounter(); | 175 var counter = new _VariableUseCounter(); |
| 176 var node = new Block(statements); |
| 193 node.accept(counter); | 177 node.accept(counter); |
| 194 // Also count the init expressions. | 178 // Also count the init expressions. |
| 195 for (var init in variables.values) init.accept(counter); | 179 for (var init in variables.values) init.accept(counter); |
| 196 | 180 |
| 197 var substitutions = {}; | 181 var initializers = <VariableInitialization>[]; |
| 198 _substitute(node) => new Template(null, node).safeCreate(substitutions); | 182 var substitutions = <MetaLetVariable, Expression>{}; |
| 199 | 183 variables.forEach((variable, init) { |
| 200 variables.forEach((name, init) { | |
| 201 // Since this is let*, subsequent variables can refer to previous ones, | 184 // Since this is let*, subsequent variables can refer to previous ones, |
| 202 // so we need to substitute here. | 185 // so we need to substitute here. |
| 203 init = _substitute(init); | 186 init = _substitute(init, substitutions); |
| 204 int n = counter.counts[name]; | 187 int n = counter.counts[variable]; |
| 205 if (n == null || n < 2) { | 188 if (n == 1) { |
| 206 substitutions[name] = _substitute(init); | 189 // Replace interpolated exprs with their value, if it only occurs once. |
| 190 substitutions[variable] = init; |
| 207 } else { | 191 } else { |
| 208 params.add(substitutions[name] = new TemporaryId(name)); | 192 // Otherwise replace it with a temp, which will be assigned once. |
| 209 values.add(init); | 193 var temp = new TemporaryId(variable.displayName); |
| 194 substitutions[variable] = temp; |
| 195 initializers.add(new VariableInitialization(temp, init)); |
| 210 } | 196 } |
| 211 }); | 197 }); |
| 212 | 198 |
| 213 // Interpolate the body: | 199 // Interpolate the body. |
| 214 // Replace interpolated exprs with their value, if it only occurs once. | 200 node = _substitute(node, substitutions); |
| 215 // Otherwise replace it with a temp, which will be assigned once. | 201 if (initializers.isNotEmpty) { |
| 216 return _substitute(node); | 202 node = new Block([ |
| 203 new VariableDeclarationList('let', initializers).toStatement(), |
| 204 node |
| 205 ]); |
| 206 } |
| 207 return node; |
| 217 } | 208 } |
| 218 | 209 |
| 219 /// If we finish with an assignment to an identifier, try to simplify the | 210 /// If we finish with an assignment to an identifier, try to simplify the |
| 220 /// block. For example: | 211 /// block. For example: |
| 221 /// | 212 /// |
| 222 /// ((_) => _.add(1), _.add(2), result = _)([]) | 213 /// ((_) => _.add(1), _.add(2), result = _)([]) |
| 223 /// | 214 /// |
| 224 /// Can be transformed to: | 215 /// Can be transformed to: |
| 225 /// | 216 /// |
| 226 /// (result = [], result.add(1), result.add(2), result) | 217 /// (result = [], result.add(1), result.add(2), result) |
| 227 /// | 218 /// |
| 228 /// However we should not simplify in this case because `result` is read: | 219 /// However we should not simplify in this case because `result` is read: |
| 229 /// | 220 /// |
| 230 /// ((_) => _.addAll(result), _.add(2), result = _)([]) | 221 /// ((_) => _.addAll(result), _.add(2), result = _)([]) |
| 231 /// | 222 /// |
| 232 MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { | 223 MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { |
| 233 // See if the result value is a let* temporary variable. | 224 // See if the result value is a let* temporary variable. |
| 234 if (body.last is! InterpolatedExpression) return null; | 225 if (body.last is! MetaLetVariable) return null; |
| 235 | 226 |
| 236 InterpolatedExpression last = body.last; | 227 MetaLetVariable result = body.last; |
| 237 String name = last.nameOrPosition; | 228 if (!variables.containsKey(result)) return null; |
| 238 if (!variables.containsKey(name)) return null; | |
| 239 | 229 |
| 240 // Variables declared can't be used inside their initializer, so make | 230 // Variables declared can't be used inside their initializer, so make |
| 241 // sure we don't transform an assignment into an initializer. | 231 // sure we don't transform an assignment into an initializer. |
| 242 // If this already was a declaration, then we know it's legal, so we can | 232 // If this already was a declaration, then we know it's legal, so we can |
| 243 // skip the check. | 233 // skip the check. |
| 244 if (!isDeclaration) { | 234 if (!isDeclaration) { |
| 245 var finder = new _IdentFinder(left.name); | 235 var finder = new _IdentFinder(left.name); |
| 246 for (var expr in body) { | 236 for (var expr in body) { |
| 247 if (finder.found) break; | 237 if (finder.found) break; |
| 248 expr.accept(finder); | 238 expr.accept(finder); |
| 249 } | 239 } |
| 250 // If the identifier was used elsewhere, bail, because we're going to | 240 // If the identifier was used elsewhere, bail, because we're going to |
| 251 // change the order of when the assignment happens. | 241 // change the order of when the assignment happens. |
| 252 if (finder.found) return null; | 242 if (finder.found) return null; |
| 253 } | 243 } |
| 254 | 244 |
| 255 var vars = new Map<String, Expression>.from(variables); | 245 var vars = new Map<MetaLetVariable, Expression>.from(variables); |
| 256 var value = vars.remove(name); | 246 var value = vars.remove(result); |
| 257 Expression assign; | 247 Expression assign; |
| 258 if (isDeclaration) { | 248 if (isDeclaration) { |
| 259 // Technically, putting one of these in a comma expression is not | 249 // Technically, putting one of these in a comma expression is not |
| 260 // legal. However when isDeclaration is true, toStatement will be | 250 // legal. However when isDeclaration is true, toStatement will be |
| 261 // called immediately on the MetaLet, which results in legal JS. | 251 // called immediately on the MetaLet, which results in legal JS. |
| 262 assign = new VariableDeclarationList( | 252 assign = new VariableDeclarationList( |
| 263 'let', [new VariableInitialization(left, value)]); | 253 'let', [new VariableInitialization(left, value)]); |
| 264 } else { | 254 } else { |
| 265 assign = value.toAssignExpression(left); | 255 assign = value.toAssignExpression(left); |
| 266 } | 256 } |
| 267 | 257 |
| 268 var newBody = new Expression.binary([assign]..addAll(body), ','); | 258 var newBody = new Expression.binary([assign]..addAll(body), ','); |
| 269 Binary comma = new Template(null, newBody).safeCreate({name: left}); | 259 newBody = _substitute(newBody, {result: left}); |
| 270 return new MetaLet(vars, comma.commaToExpressionList(), | 260 return new MetaLet(vars, newBody.commaToExpressionList(), |
| 271 statelessResult: statelessResult); | 261 statelessResult: statelessResult); |
| 272 } | 262 } |
| 273 } | 263 } |
| 274 | 264 |
| 265 /// Similar to [Template.instantiate] but works with free variables. |
| 266 Node _substitute(Node tree, Map<MetaLetVariable, Expression> substitutions) { |
| 267 var generator = new InstantiatorGeneratorVisitor(/*forceCopy:*/ false); |
| 268 var instantiator = generator.compile(tree); |
| 269 var nodes = new List<MetaLetVariable>.from(generator |
| 270 .analysis.containsInterpolatedNode |
| 271 .where((n) => n is MetaLetVariable)); |
| 272 if (nodes.isEmpty) return tree; |
| 273 |
| 274 return instantiator(new Map.fromIterable(nodes, |
| 275 key: (v) => (v as MetaLetVariable).nameOrPosition, |
| 276 value: (v) => substitutions[v] ?? v)); |
| 277 } |
| 278 |
| 279 /// A temporary variable used in a [MetaLet]. |
| 280 /// |
| 281 /// Each instance of this class represents a fresh variable. The same object |
| 282 /// should be used everywhere to refer to the same variable. Different variables |
| 283 /// with the same name are different, and will be renamed later on, if needed. |
| 284 /// |
| 285 /// These variables will be replaced when the `let*` is complete, depending on |
| 286 /// how often they occur and whether they can be optimized away. See [MetaLet] |
| 287 /// for more information. |
| 288 /// |
| 289 /// This class should never reach our final JS code. |
| 290 class MetaLetVariable extends InterpolatedExpression { |
| 291 /// The suggested display name of this variable. |
| 292 /// |
| 293 /// This name should not be used |
| 294 final String displayName; |
| 295 |
| 296 /// Compute fresh IDs to avoid |
| 297 static int _uniqueId = 0; |
| 298 |
| 299 MetaLetVariable(String displayName) |
| 300 : displayName = displayName, |
| 301 super(displayName + '@${++_uniqueId}'); |
| 302 } |
| 303 |
| 275 class _VariableUseCounter extends BaseVisitor { | 304 class _VariableUseCounter extends BaseVisitor { |
| 276 final counts = <String, int>{}; | 305 final counts = <MetaLetVariable, int>{}; |
| 277 @override | 306 @override |
| 278 visitInterpolatedExpression(InterpolatedExpression node) { | 307 visitInterpolatedExpression(InterpolatedExpression node) { |
| 279 int n = counts[node.nameOrPosition]; | 308 if (node is MetaLetVariable) { |
| 280 counts[node.nameOrPosition] = n == null ? 1 : n + 1; | 309 int n = counts[node]; |
| 310 counts[node] = n == null ? 1 : n + 1; |
| 311 } |
| 281 } | 312 } |
| 282 } | 313 } |
| 283 | 314 |
| 284 class _IdentFinder extends BaseVisitor { | 315 class _IdentFinder extends BaseVisitor { |
| 285 final String name; | 316 final String name; |
| 286 bool found = false; | 317 bool found = false; |
| 287 _IdentFinder(this.name); | 318 _IdentFinder(this.name); |
| 288 | 319 |
| 289 @override | 320 @override |
| 290 visitIdentifier(Identifier node) { | 321 visitIdentifier(Identifier node) { |
| (...skipping 26 matching lines...) Expand all Loading... |
| 317 @override | 348 @override |
| 318 visitYield(Yield node) { | 349 visitYield(Yield node) { |
| 319 if (!_nestedFunction) hasYield = true; | 350 if (!_nestedFunction) hasYield = true; |
| 320 } | 351 } |
| 321 | 352 |
| 322 @override | 353 @override |
| 323 visitNode(Node node) { | 354 visitNode(Node node) { |
| 324 if (!hasYield) super.visitNode(node); | 355 if (!hasYield) super.visitNode(node); |
| 325 } | 356 } |
| 326 } | 357 } |
| OLD | NEW |