Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(207)

Side by Side Diff: lib/src/codegen/js_metalet.dart

Issue 1069493002: implement opassign, fix bugs in pre/postfix, introduce a let* helper (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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=expr1[expr2]) { x1[x2] = t + 1; t })
Leaf 2015/04/08 04:08:34 t=x1[x2] ?
Jennifer Messerly 2015/04/08 16:35:46 Good catch! Yes :)
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 Expression _expression;
51 Statement _statement;
52 Statement _return;
53
54 JSMetaLet(this.variables, this.body, {this.statelessResult: false});
55
56 /// Returns an expression that ignores the result. This is similar to
57 /// [toStatement] but returns an expression.
58 Expression toVoidExpression() {
59 if (!statelessResult) return this;
60 // Return a new MetaLet.
61 // This allows [toStatement] and [toReturn] to compose nicely.
62 return new JSMetaLet(variables, body.toList()..removeLast());
63 }
64
65 Expression toAssignExpression(Expression left) {
66 if (left is Identifier) {
67 var simple = _simplifyAssignment(left);
68 if (simple != null) return simple;
69
70 var exprs = body.toList();
71 exprs.add(exprs.removeLast().toAssignExpression(left));
72 return new JSMetaLet(variables, exprs);
73 }
74 return super.toAssignExpression(left);
75 }
76
77 Statement toVariableDeclaration(Identifier name) {
78 var simple = _simplifyAssignment(name, isDeclaration: true);
79 if (simple != null) return simple.toStatement();
80 return super.toVariableDeclaration(name);
81 }
82
83 Expression toExpression() {
84 if (_expression != null) return _expression;
85
86 var params = [];
87 var values = [];
88 var letBody = _build(params, values, new Expression.binary(body, ','));
89
90 // Note that [precedenceLevel] must be aware of this possibility, because
91 // comma expression has different precedence from call.
92 if (params.isEmpty) return _expression = letBody;
93
94 // Assign parameters inside the body, to get sequential let* behavior
Jennifer Messerly 2015/04/07 22:09:52 for a single variable we could emit: (x => x
Leaf 2015/04/08 04:08:34 I like the former, but I suspect it's because I'm
95 // (subsequent bindings can refer to previous ones), and also to be a bit
96 // more readable (lexical order matches source order).
97 var vars = [];
98 for (int i = 0; i < params.length; i++) {
99 vars.add(new Assignment(params[i], values[i]));
100 }
101 letBody = new Binary(',', new Expression.binary(vars, ','), letBody);
102
103 // Convert the comma back to a block, for readability.
104 if (vars.length + body.length >= 4) letBody = letBody.toReturn();
105
106 return new Call(new ArrowFun(params, letBody), []);
107 }
108
109 Statement toStatement() {
110 if (_statement != null) return _statement;
Leaf 2015/04/08 04:08:34 I didn't follow the need for caching. Do these re
Jennifer Messerly 2015/04/08 16:35:46 That's fair. It's only really needed for toExpress
111
112 // Skip return value if not used.
113 var statements = body.map((e) => e.toStatement()).toList();
114 if (statelessResult) statements.removeLast();
115 return _statement = _finishStatement(statements);
116 }
117
118 Statement toReturn() {
119 if (_return != null) return _return;
120 var statements = body
121 .map((e) => e == body.last ? e.toReturn() : e.toStatement())
122 .toList();
123 return _return = _finishStatement(statements);
124 }
125
126 accept(NodeVisitor visitor) => toExpression().accept(visitor);
127
128 void visitChildren(NodeVisitor visitor) {
129 toExpression().visitChildren(visitor);
130 }
131
132 /// This generates as either a comma expression or a call.
133 int get precedenceLevel => variables.isEmpty ? EXPRESSION : CALL;
134
135 Statement _finishStatement(List<Statement> statements) {
136 var params = [];
137 var values = [];
138 var block = _build(params, values, new Block(statements));
139 if (params.isEmpty) return _return = block;
140
141 var vars = [];
142 for (int i = 0; i < params.length; i++) {
143 vars.add(new VariableInitialization(params[i], values[i]));
144 }
145
146 return new Block(
147 [new VariableDeclarationList('let', vars).toStatement(), block]);
148 }
149
150 Node _build(List<JSTemporary> params, List<Expression> values, Node node) {
151 // Visit the tree and count how many times each temp was used.
152 var counter = new _VariableUseCounter();
153 node.accept(counter);
154 // Also count the init expressions.
155 for (var init in variables.values) init.accept(counter);
156
157 var substitutions = {};
158 _substitute(node) => new Template(null, node).safeCreate(substitutions);
159
160 variables.forEach((name, init) {
161 // Since this is let*, subsequent variables can refer to previous ones,
162 // so we need to substitute here.
163 init = _substitute(init);
164 int n = counter.counts[name];
165 if (n == null || n < 2) {
166 substitutions[name] = _substitute(init);
167 } else {
168 params.add(substitutions[name] = new JSTemporary(name));
169 values.add(init);
170 }
171 });
172
173 // Interpolate the body:
174 // Replace interpolated exprs with their value, if it only occurs once.
175 // Otherwise replace it with a temp, which will be assigned once.
176 return _substitute(node);
177 }
178
179 /// If we finish with an assignment to an identifier, try to simplify the
180 /// block. For example:
181 ///
182 /// ((_) => _.add(1), _.add(2), result = _)([])
183 ///
184 /// Can be transformed to:
185 ///
186 /// (result = [], result.add(1), result.add(2), result)
187 ///
188 /// However we should not simplify in this case because `result` is read:
189 ///
190 /// ((_) => _.addAll(result), _.add(2), result = _)([])
191 ///
192 JSMetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) {
193 // See if the result value is a let* temporary variable.
194 if (body.last is! InterpolatedExpression) return null;
195
196 InterpolatedExpression last = body.last;
197 String name = last.nameOrPosition;
198 if (!variables.containsKey(name)) return null;
199
200 // Variables declared can't be used inside their initializer.
201 if (!isDeclaration) {
202 var finder = new _IdentFinder(left.name);
203 for (var expr in body) {
204 if (finder.found) break;
205 expr.accept(finder);
206 }
207 // If the identifier was used elsewhere, bail, because we're going to chan ge
Jennifer Messerly 2015/04/07 22:00:28 oops, long line.
208 // the order of when the assignment happens.
209 if (finder.found) return null;
210 }
211
212 var vars = new Map<String, Expression>.from(variables);
213 var value = vars.remove(name);
214 Expression assign;
215 if (isDeclaration) {
216 // Technically, putting one of these in a comma expression is not
217 // legal. However when isDeclaration is true, toStatement will be
218 // called immediately on the JSMetaLet, which results in legal JS.
219 assign = new VariableDeclarationList(
220 'let', [new VariableInitialization(left, value)]);
221 } else {
222 assign = value.toAssignExpression(left);
223 }
224
225 var newBody = new Expression.binary([assign]..addAll(body), ',');
226 Binary comma = new Template(null, newBody).safeCreate({name: left});
227 return new JSMetaLet(vars, comma.commaToExpressionList(),
228 statelessResult: statelessResult);
229 }
230 }
231
232 class _VariableUseCounter extends BaseVisitor {
233 final counts = <String, int>{};
234 visitInterpolatedExpression(InterpolatedExpression node) {
235 int n = counts[node.nameOrPosition];
236 counts[node.nameOrPosition] = n == null ? 1 : n + 1;
237 }
238 }
239
240 class _IdentFinder extends BaseVisitor {
241 final String name;
242 bool found = false;
243 _IdentFinder(this.name);
244
245 visitIdentifier(Identifier node) {
246 if (node.name == name) found = true;
247 }
248 visitNode(Node node) {
249 if (!found) super.visitNode(node);
250 }
251 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698