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 // TODO(jmesserly): import from its own package | |
6 import '../js/js_ast.dart'; | |
7 import '../js/precedence.dart'; | |
8 | |
9 import 'js_names.dart' show TemporaryId; | |
10 | |
11 /// A synthetic `let*` node, similar to that found in Scheme. | |
12 /// | |
13 /// For example, postfix increment can be desugared as: | |
14 /// | |
15 /// // psuedocode mix of Scheme and JS: | |
16 /// (let* (x1=expr1, x2=expr2, t=x1[x2]) { x1[x2] = t + 1; t }) | |
17 /// | |
18 /// [MetaLet] will simplify itself automatically when [toExpression], | |
19 /// [toStatement], [toReturn], or [toYieldStatement] is called. | |
20 /// | |
21 /// * variables used once will be inlined. | |
22 /// * if used in a statement context they can emit as blocks. | |
23 /// * if return value is not used it can be eliminated, see [statelessResult]. | |
24 /// * if there are no variables, the codegen will be simplified. | |
25 /// | |
26 /// Because this deals with JS AST nodes, it is not aware of any Dart semantics | |
27 /// around statelessness (such as `final` variables). [variables] should not | |
28 /// be created for these Dart expressions. | |
29 /// | |
30 class MetaLet extends Expression { | |
31 /// Creates a temporary to contain the value of [expr]. The temporary can be | |
32 /// used multiple times in the resulting expression. For example: | |
33 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will | |
34 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. | |
35 /// | |
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 | |
38 /// variables), then the resulting code will be simplified automatically. | |
39 final Map<String, Expression> variables; | |
40 | |
41 /// A list of expressions in the body. | |
42 /// The last value should represent the returned value. | |
43 final List<Expression> body; | |
44 | |
45 /// True if the final expression in [body] can be skipped in [toStatement]. | |
46 final bool statelessResult; | |
47 | |
48 /// We run [toExpression] implicitly when the JS AST is visited, to get the | |
49 /// transformation to happen before the tree is printed. | |
50 /// This happens multiple times, so ensure the expression form is cached. | |
51 Expression _expression; | |
52 | |
53 MetaLet(this.variables, this.body, {this.statelessResult: false}); | |
54 | |
55 /// Returns an expression that ignores the result. This is a cross between | |
56 /// [toExpression] and [toStatement]. Used for C-style for-loop updaters, | |
57 /// which is an expression syntactically, but functions more like a statement. | |
58 Expression toVoidExpression() { | |
59 var block = toStatement(); | |
60 var s = block.statements; | |
61 if (s.length == 1 && s.first is ExpressionStatement) { | |
62 ExpressionStatement es = s.first; | |
63 return es.expression; | |
64 } | |
65 | |
66 return _toInvokedFunction(block); | |
67 } | |
68 | |
69 Expression toAssignExpression(Expression left) { | |
70 if (left is Identifier) { | |
71 var simple = _simplifyAssignment(left); | |
72 if (simple != null) return simple; | |
73 | |
74 var exprs = body.toList(); | |
75 exprs.add(exprs.removeLast().toAssignExpression(left)); | |
76 return new MetaLet(variables, exprs); | |
77 } | |
78 return super.toAssignExpression(left); | |
79 } | |
80 | |
81 Statement toVariableDeclaration(Identifier name) { | |
82 var simple = _simplifyAssignment(name, isDeclaration: true); | |
83 if (simple != null) return simple.toStatement(); | |
84 return super.toVariableDeclaration(name); | |
85 } | |
86 | |
87 Expression toExpression() { | |
88 if (_expression != null) return _expression; | |
89 var block = toReturn(); | |
90 var s = block.statements; | |
91 if (s.length == 1 && s.first is Return) { | |
92 Return es = s.first; | |
93 return _expression = es.value; | |
94 } | |
95 // Wrap it in an immediately called function to get in expression context. | |
96 return _expression = _toInvokedFunction(block); | |
97 } | |
98 | |
99 Block toStatement() { | |
100 // Skip return value if not used. | |
101 var statements = body.map((e) => e.toStatement()).toList(); | |
102 if (statelessResult) statements.removeLast(); | |
103 return _finishStatement(statements); | |
104 } | |
105 | |
106 Block toReturn() { | |
107 var statements = body | |
108 .map((e) => e == body.last ? e.toReturn() : e.toStatement()) | |
109 .toList(); | |
110 return _finishStatement(statements); | |
111 } | |
112 | |
113 Block toYieldStatement({bool star: false}) { | |
114 var statements = body | |
115 .map((e) => | |
116 e == body.last ? e.toYieldStatement(star: star) : e.toStatement()) | |
117 .toList(); | |
118 return _finishStatement(statements); | |
119 } | |
120 | |
121 accept(NodeVisitor visitor) { | |
122 // TODO(jmesserly): we special case vistors from js_ast.Template, because it | |
123 // doesn't know about MetaLet. Should we integrate directly? | |
124 if (visitor is InstantiatorGeneratorVisitor) { | |
125 return _templateVisitMetaLet(visitor); | |
126 } else if (visitor is InterpolatedNodeAnalysis) { | |
127 return visitor.visitNode(this); | |
128 } else { | |
129 return toExpression().accept(visitor); | |
130 } | |
131 } | |
132 | |
133 void visitChildren(NodeVisitor visitor) { | |
134 // TODO(jmesserly): we special case vistors from js_ast.Template, because it | |
135 // doesn't know about MetaLet. Should we integrate directly? | |
136 if (visitor is InterpolatedNodeAnalysis || | |
137 visitor is InstantiatorGeneratorVisitor) { | |
138 variables.values.forEach((v) => v.accept(visitor)); | |
139 body.forEach((v) => v.accept(visitor)); | |
140 } else { | |
141 toExpression().visitChildren(visitor); | |
142 } | |
143 } | |
144 | |
145 /// This generates as either a comma expression or a call. | |
146 int get precedenceLevel => variables.isEmpty ? EXPRESSION : CALL; | |
147 | |
148 /// Patch to pretend [Template] supports visitMetaLet. | |
149 Instantiator _templateVisitMetaLet(InstantiatorGeneratorVisitor visitor) { | |
150 var valueInstantiators = variables.values.map(visitor.visit); | |
151 var bodyInstantiators = body.map(visitor.visit); | |
152 | |
153 return (args) => new MetaLet( | |
154 new Map.fromIterables( | |
155 variables.keys, valueInstantiators.map((i) => i(args))), | |
156 bodyInstantiators.map((i) => i(args)).toList(), | |
157 statelessResult: statelessResult); | |
158 } | |
159 | |
160 Expression _toInvokedFunction(Statement block) { | |
161 var finder = new _YieldFinder(); | |
162 block.accept(finder); | |
163 if (!finder.hasYield) { | |
164 return new Call(new ArrowFun([], block), []); | |
165 } | |
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: | |
168 Expression fn = new Fun([], block, isGenerator: true); | |
169 if (finder.hasThis) fn = js.call('#.bind(this)', fn); | |
170 return new Yield(new Call(fn, []), star: true); | |
171 } | |
172 | |
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. | |
192 var counter = new _VariableUseCounter(); | |
193 node.accept(counter); | |
194 // Also count the init expressions. | |
195 for (var init in variables.values) init.accept(counter); | |
196 | |
197 var substitutions = {}; | |
198 _substitute(node) => new Template(null, node).safeCreate(substitutions); | |
199 | |
200 variables.forEach((name, init) { | |
201 // Since this is let*, subsequent variables can refer to previous ones, | |
202 // so we need to substitute here. | |
203 init = _substitute(init); | |
204 int n = counter.counts[name]; | |
205 if (n == null || n < 2) { | |
206 substitutions[name] = _substitute(init); | |
207 } else { | |
208 params.add(substitutions[name] = new TemporaryId(name)); | |
209 values.add(init); | |
210 } | |
211 }); | |
212 | |
213 // Interpolate the body: | |
214 // Replace interpolated exprs with their value, if it only occurs once. | |
215 // Otherwise replace it with a temp, which will be assigned once. | |
216 return _substitute(node); | |
217 } | |
218 | |
219 /// If we finish with an assignment to an identifier, try to simplify the | |
220 /// block. For example: | |
221 /// | |
222 /// ((_) => _.add(1), _.add(2), result = _)([]) | |
223 /// | |
224 /// Can be transformed to: | |
225 /// | |
226 /// (result = [], result.add(1), result.add(2), result) | |
227 /// | |
228 /// However we should not simplify in this case because `result` is read: | |
229 /// | |
230 /// ((_) => _.addAll(result), _.add(2), result = _)([]) | |
231 /// | |
232 MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { | |
233 // See if the result value is a let* temporary variable. | |
234 if (body.last is! InterpolatedExpression) return null; | |
235 | |
236 InterpolatedExpression last = body.last; | |
237 String name = last.nameOrPosition; | |
238 if (!variables.containsKey(name)) return null; | |
239 | |
240 // Variables declared can't be used inside their initializer, so make | |
241 // 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 | |
243 // skip the check. | |
244 if (!isDeclaration) { | |
245 var finder = new _IdentFinder(left.name); | |
246 for (var expr in body) { | |
247 if (finder.found) break; | |
248 expr.accept(finder); | |
249 } | |
250 // If the identifier was used elsewhere, bail, because we're going to | |
251 // change the order of when the assignment happens. | |
252 if (finder.found) return null; | |
253 } | |
254 | |
255 var vars = new Map<String, Expression>.from(variables); | |
256 var value = vars.remove(name); | |
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); | |
266 } | |
267 | |
268 var newBody = new Expression.binary([assign]..addAll(body), ','); | |
269 Binary comma = new Template(null, newBody).safeCreate({name: left}); | |
270 return new MetaLet(vars, comma.commaToExpressionList(), | |
271 statelessResult: statelessResult); | |
272 } | |
273 } | |
274 | |
275 class _VariableUseCounter extends BaseVisitor { | |
276 final counts = <String, int>{}; | |
277 @override | |
278 visitInterpolatedExpression(InterpolatedExpression node) { | |
279 int n = counts[node.nameOrPosition]; | |
280 counts[node.nameOrPosition] = n == null ? 1 : n + 1; | |
281 } | |
282 } | |
283 | |
284 class _IdentFinder extends BaseVisitor { | |
285 final String name; | |
286 bool found = false; | |
287 _IdentFinder(this.name); | |
288 | |
289 @override | |
290 visitIdentifier(Identifier node) { | |
291 if (node.name == name) found = true; | |
292 } | |
293 | |
294 @override | |
295 visitNode(Node node) { | |
296 if (!found) super.visitNode(node); | |
297 } | |
298 } | |
299 | |
300 class _YieldFinder extends BaseVisitor { | |
301 bool hasYield = false; | |
302 bool hasThis = false; | |
303 bool _nestedFunction = false; | |
304 @override | |
305 visitThis(This node) { | |
306 hasThis = true; | |
307 } | |
308 | |
309 @override | |
310 visitFunctionExpression(FunctionExpression node) { | |
311 var savedNested = _nestedFunction; | |
312 _nestedFunction = true; | |
313 super.visitFunctionExpression(node); | |
314 _nestedFunction = savedNested; | |
315 } | |
316 | |
317 @override | |
318 visitYield(Yield node) { | |
319 if (!_nestedFunction) hasYield = true; | |
320 } | |
321 | |
322 @override | |
323 visitNode(Node node) { | |
324 if (!hasYield) super.visitNode(node); | |
325 } | |
326 } | |
OLD | NEW |