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