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 | 7 |
8 import 'js_names.dart' show TemporaryId; | 8 import 'js_names.dart' show TemporaryId; |
9 | 9 |
10 /// A synthetic `let*` node, similar to that found in Scheme. | 10 /// A synthetic `let*` node, similar to that found in Scheme. |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
58 var block = toStatement(); | 58 var block = toStatement(); |
59 var s = block.statements; | 59 var s = block.statements; |
60 if (s.length == 1 && s.first is ExpressionStatement) { | 60 if (s.length == 1 && s.first is ExpressionStatement) { |
61 ExpressionStatement es = s.first; | 61 ExpressionStatement es = s.first; |
62 return es.expression; | 62 return es.expression; |
63 } | 63 } |
64 | 64 |
65 return _toInvokedFunction(block); | 65 return _toInvokedFunction(block); |
66 } | 66 } |
67 | 67 |
68 Expression toAssignExpression(Expression left) { | 68 Expression toAssignExpression(Expression left, [String op]) { |
69 if (left is Identifier) { | 69 if (left is Identifier) { |
70 var simple = _simplifyAssignment(left); | 70 return _simplifyAssignment(left, op: op) ?? _toAssign(left, op); |
71 if (simple != null) return simple; | 71 } else if (left is PropertyAccess && |
| 72 left.receiver is This && |
| 73 (left.selector is Identifier || left.selector is LiteralString)) { |
| 74 return _toAssign(left, op); |
| 75 } |
| 76 return super.toAssignExpression(left, op); |
| 77 } |
72 | 78 |
73 var exprs = body.toList(); | 79 Expression _toAssign(Expression left, [String op]) { |
74 exprs.add(exprs.removeLast().toAssignExpression(left)); | 80 var exprs = body.toList(); |
75 return new MetaLet(variables, exprs); | 81 exprs.add(exprs.removeLast().toAssignExpression(left, op)); |
76 } | 82 return new MetaLet(variables, exprs); |
77 return super.toAssignExpression(left); | |
78 } | 83 } |
79 | 84 |
80 Statement toVariableDeclaration(Identifier name) { | 85 Statement toVariableDeclaration(Identifier name) { |
81 var simple = _simplifyAssignment(name, isDeclaration: true); | 86 var simple = _simplifyAssignment(name, isDeclaration: true); |
82 if (simple != null) return simple.toStatement(); | 87 if (simple != null) return simple.toStatement(); |
83 return super.toVariableDeclaration(name); | 88 |
| 89 // We can still optimize something like: |
| 90 // |
| 91 // let x = ((l) => l == null ? null : l.xyz)(some.expr); |
| 92 // |
| 93 // can be transformed to: |
| 94 // |
| 95 // let l = some.expr; |
| 96 // let x = l == null ? null : l.xyz; |
| 97 // |
| 98 // Because `x` is a declaration, we know it is safe to move. |
| 99 // (see also _toAssign) |
| 100 var statements = body |
| 101 .map((e) => |
| 102 e == body.last ? e.toVariableDeclaration(name) : e.toStatement()) |
| 103 .toList(); |
| 104 return _finishStatement(statements); |
84 } | 105 } |
85 | 106 |
86 Expression toExpression() { | 107 Expression toExpression() { |
87 if (_expression != null) return _expression; | 108 if (_expression != null) return _expression; |
88 var block = toReturn(); | 109 var block = toReturn(); |
89 var s = block.statements; | 110 var s = block.statements; |
90 if (s.length == 1 && s.first is Return) { | 111 if (s.length == 1 && s.first is Return) { |
91 Return es = s.first; | 112 Return es = s.first; |
92 return _expression = es.value; | 113 return _expression = es.value; |
93 } | 114 } |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
191 // Otherwise replace it with a temp, which will be assigned once. | 212 // Otherwise replace it with a temp, which will be assigned once. |
192 var temp = new TemporaryId(variable.displayName); | 213 var temp = new TemporaryId(variable.displayName); |
193 substitutions[variable] = temp; | 214 substitutions[variable] = temp; |
194 initializers.add(new VariableInitialization(temp, init)); | 215 initializers.add(new VariableInitialization(temp, init)); |
195 } | 216 } |
196 }); | 217 }); |
197 | 218 |
198 // Interpolate the body. | 219 // Interpolate the body. |
199 node = _substitute(node, substitutions); | 220 node = _substitute(node, substitutions); |
200 if (initializers.isNotEmpty) { | 221 if (initializers.isNotEmpty) { |
| 222 var first = initializers[0]; |
201 node = new Block([ | 223 node = new Block([ |
202 new VariableDeclarationList('let', initializers).toStatement(), | 224 initializers.length == 1 |
| 225 ? first.value.toVariableDeclaration(first.declaration) |
| 226 : new VariableDeclarationList('let', initializers).toStatement(), |
203 node | 227 node |
204 ]); | 228 ]); |
205 } | 229 } |
206 return node; | 230 return node; |
207 } | 231 } |
208 | 232 |
209 /// If we finish with an assignment to an identifier, try to simplify the | 233 /// If we finish with an assignment to an identifier, try to simplify the |
210 /// block. For example: | 234 /// block. For example: |
211 /// | 235 /// |
212 /// ((_) => _.add(1), _.add(2), result = _)([]) | 236 /// result = ((_) => _.add(1), _.add(2), _)([]) |
213 /// | 237 /// |
214 /// Can be transformed to: | 238 /// Can be transformed to: |
215 /// | 239 /// |
216 /// (result = [], result.add(1), result.add(2), result) | 240 /// (result = [], result.add(1), result.add(2), result) |
217 /// | 241 /// |
218 /// However we should not simplify in this case because `result` is read: | 242 /// However we should not simplify in this case because `result` is read: |
219 /// | 243 /// |
220 /// ((_) => _.addAll(result), _.add(2), result = _)([]) | 244 /// result = ((_) => _.addAll(result), _.add(2), _)([]) |
221 /// | 245 /// |
222 MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { | 246 MetaLet _simplifyAssignment(Identifier left, |
| 247 {String op, bool isDeclaration: false}) { |
223 // See if the result value is a let* temporary variable. | 248 // See if the result value is a let* temporary variable. |
224 if (body.last is! MetaLetVariable) return null; | 249 var result = body.last; |
| 250 if (result is MetaLetVariable && variables.containsKey(result)) { |
| 251 // For assignments, make sure the identifier isn't used in body, as that |
| 252 // would change the assignment order and be an invalid optimization. |
| 253 if (!isDeclaration && _IdentFinder.foundIn(left.name, body)) return null; |
225 | 254 |
226 MetaLetVariable result = body.last; | 255 var vars = new Map<MetaLetVariable, Expression>.from(variables); |
227 if (!variables.containsKey(result)) return null; | 256 var value = vars.remove(result); |
| 257 Expression assign; |
| 258 if (isDeclaration) { |
| 259 // Technically, putting one of these in a comma expression is not |
| 260 // legal. However when isDeclaration is true, toStatement will be |
| 261 // called immediately on the MetaLet, which results in legal JS. |
| 262 assign = new VariableDeclarationList( |
| 263 'let', [new VariableInitialization(left, value)]); |
| 264 } else { |
| 265 assign = value.toAssignExpression(left, op); |
| 266 } |
228 | 267 |
229 // Variables declared can't be used inside their initializer, so make | 268 assert(body.isNotEmpty); |
230 // sure we don't transform an assignment into an initializer. | 269 Binary newBody = new Expression.binary([assign]..addAll(body), ','); |
231 // If this already was a declaration, then we know it's legal, so we can | 270 newBody = _substitute(newBody, {result: left}); |
232 // skip the check. | 271 return new MetaLet(vars, newBody.commaToExpressionList(), |
233 if (!isDeclaration) { | 272 statelessResult: statelessResult); |
234 var finder = new _IdentFinder(left.name); | |
235 for (var expr in body) { | |
236 if (finder.found) break; | |
237 expr.accept(finder); | |
238 } | |
239 // If the identifier was used elsewhere, bail, because we're going to | |
240 // change the order of when the assignment happens. | |
241 if (finder.found) return null; | |
242 } | 273 } |
243 | 274 return null; |
244 var vars = new Map<MetaLetVariable, Expression>.from(variables); | |
245 var value = vars.remove(result); | |
246 Expression assign; | |
247 if (isDeclaration) { | |
248 // Technically, putting one of these in a comma expression is not | |
249 // legal. However when isDeclaration is true, toStatement will be | |
250 // called immediately on the MetaLet, which results in legal JS. | |
251 assign = new VariableDeclarationList( | |
252 'let', [new VariableInitialization(left, value)]); | |
253 } else { | |
254 assign = value.toAssignExpression(left); | |
255 } | |
256 | |
257 assert(body.isNotEmpty); | |
258 Binary newBody = new Expression.binary([assign]..addAll(body), ','); | |
259 newBody = _substitute(newBody, {result: left}); | |
260 return new MetaLet(vars, newBody.commaToExpressionList(), | |
261 statelessResult: statelessResult); | |
262 } | 275 } |
263 } | 276 } |
264 | 277 |
265 /// Similar to [Template.instantiate] but works with free variables. | 278 /// Similar to [Template.instantiate] but works with free variables. |
266 Node _substitute(Node tree, Map<MetaLetVariable, Expression> substitutions) { | 279 Node _substitute(Node tree, Map<MetaLetVariable, Expression> substitutions) { |
267 var generator = new InstantiatorGeneratorVisitor(/*forceCopy:*/ false); | 280 var generator = new InstantiatorGeneratorVisitor(/*forceCopy:*/ false); |
268 var instantiator = generator.compile(tree); | 281 var instantiator = generator.compile(tree); |
269 var nodes = new List<MetaLetVariable>.from(generator | 282 var nodes = new List<MetaLetVariable>.from(generator |
270 .analysis.containsInterpolatedNode | 283 .analysis.containsInterpolatedNode |
271 .where((n) => n is MetaLetVariable)); | 284 .where((n) => n is MetaLetVariable)); |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 counts[node] = n == null ? 1 : n + 1; | 323 counts[node] = n == null ? 1 : n + 1; |
311 } | 324 } |
312 } | 325 } |
313 } | 326 } |
314 | 327 |
315 class _IdentFinder extends BaseVisitor { | 328 class _IdentFinder extends BaseVisitor { |
316 final String name; | 329 final String name; |
317 bool found = false; | 330 bool found = false; |
318 _IdentFinder(this.name); | 331 _IdentFinder(this.name); |
319 | 332 |
| 333 static bool foundIn(String name, List<Node> body) { |
| 334 var finder = new _IdentFinder(name); |
| 335 for (var expr in body) { |
| 336 expr.accept(finder); |
| 337 if (finder.found) return true; |
| 338 } |
| 339 return false; |
| 340 } |
| 341 |
320 @override | 342 @override |
321 visitIdentifier(Identifier node) { | 343 visitIdentifier(Identifier node) { |
322 if (node.name == name) found = true; | 344 if (node.name == name) found = true; |
323 } | 345 } |
324 | 346 |
325 @override | 347 @override |
326 visitNode(Node node) { | 348 visitNode(Node node) { |
327 if (!found) super.visitNode(node); | 349 if (!found) super.visitNode(node); |
328 } | 350 } |
329 } | 351 } |
(...skipping 21 matching lines...) Expand all Loading... |
351 if (!_nestedFunction) hasYield = true; | 373 if (!_nestedFunction) hasYield = true; |
352 super.visitYield(node); | 374 super.visitYield(node); |
353 } | 375 } |
354 | 376 |
355 @override | 377 @override |
356 visitNode(Node node) { | 378 visitNode(Node node) { |
357 if (hasYield && hasThis) return; // found both, nothing more to do. | 379 if (hasYield && hasThis) return; // found both, nothing more to do. |
358 super.visitNode(node); | 380 super.visitNode(node); |
359 } | 381 } |
360 } | 382 } |
OLD | NEW |