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 library rewrite_async; | |
6 | |
7 // TODO(sigurdm): Avoid using variables in templates. It could blow up memory | |
8 // use. | |
9 | |
10 import "dart:math" show max; | |
11 import 'dart:collection'; | |
12 | |
13 import "js.dart"; | |
floitsch
2015/02/04 17:06:10
Since you export without a prefix, you should "sho
sigurdm
2015/02/05 14:06:07
Hurray for quick-fixes!
There is a lot of used na
floitsch
2015/02/05 20:17:20
Hm. probably yes.
In other components it's usally
| |
14 import '../util/util.dart'; | |
15 import '../dart2jslib.dart' show DiagnosticListener; | |
16 | |
17 import "../helpers/helpers.dart"; | |
18 | |
19 /// Rewrites a js Fun with async/sync*/async* functions and await and yield | |
20 /// (with dart-like semantics) to an equivalent function without these. | |
21 /// await-for is not handled and must be rewritten before. (Currently handled | |
22 /// in ssa/builder.dart). | |
23 /// When generating the input to this, special care must be taken that | |
24 /// parameters to sync* functions that are mutated in the body must be boxed. | |
25 /// (Currently handled in closure.dart). | |
26 | |
floitsch
2015/02/04 17:06:10
Remove empty line.
sigurdm
2015/02/05 14:06:07
Done.
| |
27 class AsyncRewriter extends NodeVisitor { | |
floitsch
2015/02/04 17:06:09
Add comment that you should look at visitFun, visi
sigurdm
2015/02/05 14:06:07
Done.
| |
28 | |
29 // Local variables are hoisted to the top of the function, so we collect them | |
30 // here. | |
31 List<VariableDeclaration> localVariables = new List<VariableDeclaration>(); | |
32 | |
33 Map<Node, int> continueLabels = new Map<Node, int>(); | |
34 Map<Node, int> breakLabels = new Map<Node, int>(); | |
35 Map<Node, int> finallyLabels = new Map<Node, int>(); | |
36 int returnLabel; | |
37 | |
38 // A stack of all enclosing jump targets (including the function for | |
39 // representing the target of a return. And all enclosing try-blocks that have | |
40 // finally part, so we can ensure all the finally blocks between a jump and | |
41 // its target are run before the jump. | |
42 List<Node> targetsAndTries = new List<Node>(); | |
43 | |
44 List<int> continueStack = new List<int>(); | |
45 List<int> breakStack = new List<int>(); | |
46 List<int> returnStack = new List<int>(); | |
47 | |
48 List<Pair<String, String>> variableRenamings = | |
49 new List<Pair<String, String>>(); | |
50 | |
51 PreTranslationAnalysis analysis; | |
52 | |
53 List<int> errorHandlerLabels = new List<int>(); | |
54 | |
55 final Function safeVariableName; | |
56 | |
57 String resultName; | |
58 String helperName; | |
59 String controllerName; | |
60 String gotoName; | |
61 String handlerName; | |
62 String errorName; | |
63 String nextName; | |
64 String returnValueName; | |
65 String outerLabelName; | |
66 String selfName; | |
67 | |
68 final Expression thenHelper; | |
floitsch
2015/02/04 17:06:09
I believe I have written it somewhere else: this i
floitsch
2015/02/04 17:06:10
Comments on what these are.
floitsch
2015/02/04 17:06:10
I'm not sure it's worth to have only one variable.
sigurdm
2015/02/05 14:06:06
Done.
sigurdm
2015/02/05 14:06:07
Acknowledged.
sigurdm
2015/02/05 14:06:07
Good idea - I also split the "controller" to a con
floitsch
2015/02/05 20:17:20
The more I think about this, I think that we shoul
| |
69 final Expression newController; | |
70 final Expression endOfIteration; | |
71 final Expression newIterable; | |
72 final Expression yieldExpression; | |
73 final Expression yieldStarExpression; | |
74 | |
75 final DiagnosticListener diagnosticListener; | |
76 // For error reporting only. | |
77 Spannable get spannable { | |
78 return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable; | |
79 } | |
80 | |
81 Spannable _spannable; | |
82 | |
83 int _currentLabel = 0; | |
84 | |
85 // The highest temporary variable index currently in use. | |
86 int currentTempVarIndex = 0; | |
87 // The highest temporary variable index ever in use in this function. | |
88 int tempVarHighWaterMark = 0; | |
89 Map<int, Expression> tempVarNames = new Map<int, Expression>(); | |
90 | |
91 AsyncModifier async; | |
92 | |
93 bool get isSync => async == const AsyncModifier.sync(); | |
94 bool get isAsync => async == const AsyncModifier.async(); | |
95 bool get isSyncStar => async == const AsyncModifier.syncStar(); | |
96 bool get isAsyncStar => async == const AsyncModifier.asyncStar(); | |
97 | |
98 AsyncRewriter(this.diagnosticListener, | |
99 spannable, | |
100 {this.thenHelper, | |
101 this.newController, | |
102 this.endOfIteration, | |
103 this.newIterable, | |
104 this.yieldExpression, | |
105 this.yieldStarExpression, | |
106 this.safeVariableName}) | |
107 : _spannable = spannable; | |
108 | |
109 /// Main entry point. | |
110 /// Rewrites a sync*/async/async* function to an equivalent normal function. | |
111 /// | |
112 /// [spannable] can be passed to have a location for error messages. | |
113 Fun rewrite(Fun node, [Spannable spannable]) { | |
114 _spannable = spannable; | |
115 | |
116 async = node.asyncModifier; | |
117 assert(!isSync); | |
118 | |
119 analysis = new PreTranslationAnalysis(unsupported); | |
120 analysis.analyze(node); | |
121 | |
122 // To avoid name collisions with existing names, the fresh names are | |
123 // generated after the analysis. | |
124 resultName = freshName("result"); | |
125 controllerName = freshName("completer"); | |
126 helperName = freshName("helper"); | |
127 gotoName = freshName("goto"); | |
128 handlerName = freshName("handler"); | |
129 errorName = freshName("error"); | |
130 nextName = freshName("next"); | |
131 returnValueName = freshName("returnValue"); | |
132 outerLabelName = freshName("outer"); | |
133 selfName = freshName("self"); | |
134 | |
135 return node.accept(this); | |
136 } | |
137 | |
138 Expression get currentErrorHandler { | |
139 return errorHandlerLabels.isEmpty ? | |
140 new LiteralNull() : | |
141 new LiteralNumber("${errorHandlerLabels.last}"); | |
142 } | |
143 | |
144 int allocateTempVar() { | |
145 assert(tempVarHighWaterMark >= currentTempVarIndex); | |
146 currentTempVarIndex++; | |
147 tempVarHighWaterMark = max(currentTempVarIndex, tempVarHighWaterMark); | |
148 return currentTempVarIndex; | |
149 } | |
150 | |
151 VariableUse useTempVar(int i) { | |
152 return tempVarNames.putIfAbsent(i, () => | |
153 new VariableUse(freshName("temp$i"))); | |
154 } | |
155 | |
156 /// Generates a variable name with [safeVariableName] based on [originalName] | |
157 /// with a suffix to guarantee it does not collide with already used names. | |
158 String freshName(String originalName) { | |
159 String safeName = safeVariableName(originalName); | |
160 String result = safeName; | |
161 int counter = 1; | |
162 while (analysis.usedNames.contains(result)) { | |
163 result = "$safeName$counter"; | |
164 ++counter; | |
165 } | |
166 analysis.usedNames.add(result); | |
167 return result; | |
168 } | |
169 | |
170 /// We collect all the pieces in this map, and output a switch with a case | |
171 /// for each label. | |
172 /// | |
173 /// The order is important, therefore the type is explicitly LinkedHashMap. | |
174 LinkedHashMap<int, List<Statement>> labelledParts = | |
175 new LinkedHashMap<int, List<Statement>>(); | |
176 | |
177 /// Description of each label for readability of the non-minified output. | |
178 Map<int, String> labelComments = new Map<int, String>(); | |
179 | |
180 /// True if the function has any try blocks containing await. | |
181 bool hasTryBlocks = false; | |
182 | |
183 /// True if any return, break or continue passes through a finally. | |
184 bool hasJumpThroughFinally = false; | |
185 | |
186 /// True if the traversion currently is inside a loop or switch for which | |
187 /// [shouldTransform] is false. | |
188 bool insideUntranslatedBreakable = false; | |
189 | |
190 /// True if we use a label to break to the outer switch-statement. | |
191 bool hasJumpThoughOuterLabel = false; | |
192 | |
193 /// Buffer for collecting translated statements belonging to the same switch | |
194 /// case. | |
195 List<Statement> currentStatementBuffer; | |
196 | |
197 // Labels will become cases in the big switch expression, and `goto label` | |
198 // is expressed by assigning to the switch key [gotoName] and breaking out of | |
199 // the switch. | |
200 | |
201 int newLabel([String comment]) { | |
202 int result = _currentLabel; | |
203 _currentLabel++; | |
204 if (comment != null) { | |
205 labelComments[result] = comment; | |
206 } | |
207 return result; | |
208 } | |
209 | |
210 /// Begins outputting statements to a new buffer with label [label]. | |
211 /// Each buffer will end up as its own case part in the big state-switch. | |
212 void beginLabel(int label) { | |
213 assert(!labelledParts.containsKey(label)); | |
214 currentStatementBuffer = new List<Statement>(); | |
215 labelledParts[label] = currentStatementBuffer; | |
216 addStatement(new Comment(labelComments[label])); | |
217 } | |
218 | |
219 /// Returns a statement assigning to the variable named [gotoName]. | |
220 /// This should be followed by a break for the goto to be executed. Use | |
221 /// [gotoWithBreak] or [addGoto] for this. | |
222 Statement setGotoVariable(int label) { | |
223 return new ExpressionStatement( | |
224 new Assignment(new VariableUse(gotoName), new LiteralNumber("$label"))); | |
225 } | |
226 | |
227 /// Return a block that has a goto to [label] including the break. | |
228 /// | |
229 /// Will also insert a comment describing the label if available. | |
230 Block gotoAndBreak(int label) { | |
231 List<Statement> statements = new List<Statement>(); | |
232 if (labelComments.containsKey(label)) { | |
233 statements.add(new Comment("goto ${labelComments[label]}")); | |
234 } | |
235 statements.add(setGotoVariable(label)); | |
236 statements.add(breakStatement()); | |
237 return new Block(statements); | |
238 } | |
239 | |
240 /// Add a goto to [label] including the break. | |
241 /// | |
242 /// Will also insert a comment describing the label if available. | |
243 void addGoto(int label) { | |
244 if (labelComments.containsKey(label)) { | |
245 addStatement(new Comment("goto ${labelComments[label]}")); | |
246 } | |
247 addStatement(setGotoVariable(label)); | |
248 | |
249 addBreak(); | |
250 } | |
251 | |
252 void addStatement(Statement node) { | |
253 currentStatementBuffer.add(node); | |
254 } | |
255 | |
256 void addExpressionStatement(Expression node) { | |
257 addStatement(new ExpressionStatement(node)); | |
258 } | |
259 | |
260 /// True if there is an await or yield in [node] or some subexpression. | |
261 bool shouldTransform(Node node) { | |
262 return analysis.hasAwaitOrYield.contains(node); | |
263 } | |
264 | |
265 void unsupported(Node node) { | |
266 throw new UnsupportedError( | |
267 "Node $node cannot be transformed by the await-sync transformer"); | |
268 } | |
269 | |
270 void unreachable(Node node) { | |
271 diagnosticListener.internalError(spannable, | |
272 "Internal error, trying to visit $node"); | |
273 } | |
274 | |
275 visitStatement(Statement node) { | |
276 node.accept(this); | |
277 } | |
278 | |
279 /// Visits [node] to ensure its sideeffects are performed, but throwing away | |
280 /// the result. | |
281 /// | |
282 /// If the return value of visiting [node] is an expression we can guarantee | |
283 /// has no side effect, it is dropped. | |
284 void visitExpressionIgnoreResult(Expression node) { | |
285 Expression result = node.accept(this); | |
286 if (!(result is Literal || result is VariableUse)) { | |
287 addExpressionStatement(result); | |
288 } | |
289 } | |
290 | |
291 Expression visitExpression(Expression node) { | |
292 return node.accept(this); | |
293 } | |
294 | |
295 /// Emits an assignment of [result] to a temporary variable unless it can | |
296 /// be guaranteed that evaluating [result] has no side-effect, and has | |
297 /// constant value. | |
298 /// Returns the temporary variable or the result itself. | |
299 /// | |
300 /// For example: | |
301 /// | |
302 /// - _storeIfNecessary(someLiteral) returns someLiteral. | |
303 /// - _storeIfNecessary(someVariable) | |
304 /// inserts: var tempX = someVariable | |
305 /// returns: tempX | |
306 /// where tempX is a fresh temporary variable. | |
307 Expression _storeIfNecessary(Expression result) { | |
308 // Note that RegExes, ArrayInitializer and ObjectInitializer are not | |
309 // [Literal]s. | |
310 if (result is Literal) return result; | |
311 Expression tempVar = useTempVar(allocateTempVar()); | |
312 addExpressionStatement(new Assignment(tempVar, result)); | |
313 return tempVar; | |
314 } | |
315 | |
316 withExpression(Expression node, fn(Expression result), {bool store}) { | |
317 int oldTempVarIndex = currentTempVarIndex; | |
318 Expression visited = visitExpression(node); | |
319 if (store) { | |
320 visited = _storeIfNecessary(visited); | |
321 } | |
322 var result = fn(visited); | |
323 currentTempVarIndex = oldTempVarIndex; | |
324 return result; | |
325 } | |
326 | |
327 /// Calls [fn] with the value of evaluating [node1] and [node2]. | |
328 /// | |
329 /// If `shouldTransform(node2)` we store the first expression in a temporary | |
330 /// variable. | |
331 /// | |
332 /// This is because we need to ensure evaluating node1 before visiting node2, | |
333 /// because the evaluation of an await or yield cannot be expressed as | |
334 /// an expression, visiting node2 it will output statements that | |
335 /// might have an influence on the value of node1. | |
336 withExpression2(Expression node1, Expression node2, fn(Expression result1, | |
337 Expression result2)) { | |
338 int oldTempVarIndex = currentTempVarIndex; | |
339 Expression r1 = visitExpression(node1); | |
340 if (shouldTransform(node2)) { | |
341 r1 = _storeIfNecessary(r1); | |
342 } | |
343 Expression r2 = visitExpression(node2); | |
344 var result = fn(r1, r2); | |
345 currentTempVarIndex = oldTempVarIndex; | |
346 return result; | |
347 } | |
348 | |
349 /// Calls [fn] with the value of evaluating all [nodes]. | |
350 /// | |
351 /// All results before the last node where `shouldTransform(node)` we store in | |
352 /// temporary variables. | |
353 /// | |
354 /// See more explanation on [withExpression2]. | |
355 /// | |
356 /// If any of the nodes are null, they are ignored, and a null is passed to | |
357 /// [fn] in that place. | |
358 withExpressions(List<Expression> nodes, fn(List<Expression> results)) { | |
359 int oldTempVarIndex = currentTempVarIndex; | |
360 // Find last occurence of a 'transform' expression in [nodes]. | |
361 // All expressions before that must be stored in temp-vars. | |
362 int lastTransformIndex = 0; | |
363 for (int i = nodes.length - 1; i >= 0; --i) { | |
364 if (nodes[i] == null) continue; | |
365 if (shouldTransform(nodes[i])) { | |
366 lastTransformIndex = i; | |
367 break; | |
368 } | |
369 } | |
370 List<Node> visited = nodes.take(lastTransformIndex).map((Node node) { | |
371 return node == null | |
372 ? null | |
373 : _storeIfNecessary(visitExpression(node)); | |
374 }).toList(); | |
375 visited.addAll(nodes.skip(lastTransformIndex).map((Node node) { | |
376 return node == null | |
377 ? null | |
378 : visitExpression(node); | |
379 })); | |
380 var result = fn(visited); | |
381 currentTempVarIndex = oldTempVarIndex; | |
382 return result; | |
383 } | |
384 | |
385 /// Emits the return block that all returns should jump to. | |
386 /// | |
387 /// Returning from an async method calls the helper with the result. | |
388 /// (the result might been stored in [returnValueName] by some finally block). | |
floitsch
2015/02/04 17:06:09
might have been
sigurdm
2015/02/05 14:06:06
Done.
| |
389 /// | |
390 /// Returning from a sync* will return a [endOfIteration] marker. | |
floitsch
2015/02/04 17:06:10
Returning from a sync* returns an [endOfIteration]
sigurdm
2015/02/05 14:06:06
Done.
| |
391 void addReturn() { | |
floitsch
2015/02/04 17:06:09
Comments don't mention async*
sigurdm
2015/02/05 14:06:06
Done.
| |
392 if (analysis.hasExplicitReturns || isAsyncStar) { | |
393 beginLabel(returnLabel); | |
394 } else { | |
395 addStatement(new Comment("implicit return")); | |
396 } | |
397 switch (async) { | |
floitsch
2015/02/04 17:06:10
How do these guarantee that we go through the fina
sigurdm
2015/02/05 14:06:06
This only generates the return that all returns wi
| |
398 case const AsyncModifier.async(): | |
399 String returnValue = analysis.hasExplicitReturns | |
400 ? returnValueName | |
401 : "null"; | |
402 addStatement(js.statement( | |
403 "return #thenHelper($returnValue, null, $controllerName, null)", | |
404 {"thenHelper": thenHelper})); | |
405 break; | |
406 case const AsyncModifier.syncStar(): | |
407 addStatement(new Return(new Call(endOfIteration, []))); | |
408 break; | |
409 case const AsyncModifier.asyncStar(): | |
410 addStatement(js.statement( | |
411 "return #thenHelper(null, null, $controllerName, null)", | |
412 {"thenHelper": thenHelper})); | |
413 break; | |
414 default: | |
415 throw "Internal error, unexpected asyncmodifier"; | |
416 } | |
417 } | |
418 | |
419 /// Initializing a controller. (This is a Completer for async, | |
floitsch
2015/02/04 17:06:10
Initializes the controller/completer.
sigurdm
2015/02/05 14:06:07
The comment here was wrong - I have tried to rewri
floitsch
2015/02/05 20:17:20
better.
| |
420 /// a StreamController for async* and nothing for a sync*). | |
421 /// The controller is be passed to each call to [thenHelper]. | |
422 Statement generateInitializer() { | |
423 switch (async) { | |
424 case const AsyncModifier.async(): | |
425 return js.statement( | |
426 "return #thenHelper(null, $helperName, $controllerName, null);", | |
427 {"thenHelper": thenHelper}); | |
428 case const AsyncModifier.syncStar(): | |
429 throw "Not handled here"; | |
430 case const AsyncModifier.asyncStar(): | |
431 return js.statement( | |
432 "return #thenHelper(null, $helperName, $controllerName, null);", | |
433 {"thenHelper": thenHelper}); default: | |
434 throw "Internal error, unexpected asyncmodifier"; | |
435 } | |
436 } | |
437 | |
438 @override | |
439 visitFun(Fun node) { | |
floitsch
2015/02/04 17:06:09
Add long comment on how you translate from one to
sigurdm
2015/02/05 14:06:06
Done.
| |
440 if (isSync) return node; | |
441 | |
442 beginLabel(newLabel("Function start")); | |
443 // AsyncStar needs a returnlabel for its handling of cancelation. See | |
444 // [visitYield]. | |
floitsch
2015/02/04 12:31:30
visitDartYield
sigurdm
2015/02/05 14:06:07
Done.
| |
445 returnLabel = analysis.hasExplicitReturns || isAsyncStar | |
446 ? newLabel("Return") | |
447 : null; | |
448 Statement body = node.body; | |
449 targetsAndTries.add(node); | |
450 visitStatement(body); | |
451 targetsAndTries.removeLast(); | |
452 addReturn(); | |
453 | |
454 List<SwitchClause> clauses = labelledParts.keys.map((label) { | |
455 return new Case( | |
456 new LiteralNumber("$label"), | |
457 new Block(labelledParts[label])); | |
458 }).toList(); | |
459 Statement helperBody = new Switch(new VariableUse(gotoName), clauses); | |
460 if (hasJumpThoughOuterLabel) { | |
461 helperBody = js.statement("$outerLabelName: #", [helperBody]); | |
462 } | |
463 if (hasTryBlocks) { | |
464 helperBody = js.statement(""" | |
465 try { | |
466 #body | |
467 } catch ($errorName){ | |
468 if ($handlerName === null) | |
469 throw $errorName; | |
470 $resultName = $errorName; | |
471 $gotoName = $handlerName; | |
472 }""", {"body": helperBody}); | |
473 } | |
474 List<VariableInitialization> inits = <VariableInitialization>[]; | |
475 | |
476 VariableInitialization makeInit(String name, Expression initValue) { | |
477 return new VariableInitialization(new VariableDeclaration(name), | |
478 initValue); | |
479 } | |
480 | |
481 inits.add(makeInit(gotoName, new LiteralNumber("0"))); | |
482 if (isAsync || isAsyncStar) { | |
483 inits.add(makeInit(controllerName, newController)); | |
484 } | |
485 if (hasTryBlocks) { | |
486 inits.add(makeInit(handlerName, new LiteralNull())); | |
487 } | |
488 if (hasJumpThroughFinally) { | |
489 inits.add(makeInit(nextName, null)); | |
490 } | |
491 if (analysis.hasExplicitReturns && isAsync) { | |
492 inits.add(makeInit(returnValueName, null)); | |
493 } | |
494 if (analysis.hasThis) { | |
495 inits.add(makeInit(selfName, new This())); | |
496 } | |
497 inits.addAll(localVariables.map( | |
498 (VariableDeclaration decl) => new VariableInitialization(decl, null))); | |
499 inits.addAll(new Iterable.generate(tempVarHighWaterMark, | |
500 (int i) => makeInit(useTempVar(i + 1).name, null))); | |
501 VariableDeclarationList varDecl = new VariableDeclarationList(inits); | |
502 if (isSyncStar) { | |
503 return js(""" | |
504 function (#params) { | |
505 return new #newIterable(function () { | |
506 #varDecl; | |
507 return function $helperName($resultName) { | |
508 while (true) | |
509 #helperBody; | |
510 }; | |
511 }); | |
512 } | |
513 """, {"params": node.params, | |
floitsch
2015/02/04 17:06:10
move "params" into the next line or align the othe
sigurdm
2015/02/05 14:06:07
Done.
| |
514 "helperBody": helperBody, | |
floitsch
2015/02/04 17:06:10
In a follow-up CL: rename this to something else "
sigurdm
2015/02/05 14:06:07
Acknowledged.
| |
515 "varDecl": varDecl, | |
516 "newIterable": newIterable}); | |
517 } | |
518 return js(""" | |
519 function (#params) { | |
520 #varDecl; | |
521 function $helperName($resultName) { | |
522 while (true) | |
523 #helperBody; | |
524 } | |
525 #init; | |
526 }""", {"params": node.params, | |
527 "helperBody": helperBody, | |
528 "varDecl": varDecl, | |
529 "init": generateInitializer()}); | |
530 } | |
531 | |
532 @override | |
533 visitAccess(PropertyAccess node) { | |
534 return withExpression2( | |
535 node.receiver, | |
536 node.selector, | |
537 (receiver, selector) => new PropertyAccess(receiver, selector)); | |
538 } | |
539 | |
540 @override | |
541 visitArrayHole(ArrayHole node) { | |
542 return node; | |
543 } | |
544 | |
545 @override | |
546 visitArrayInitializer(ArrayInitializer node) { | |
547 return withExpressions(node.elements, (elements) { | |
548 return new ArrayInitializer(elements); | |
549 }); | |
550 } | |
551 | |
552 @override | |
553 visitAssignment(Assignment node) { | |
554 if (!shouldTransform(node)) { | |
555 return new Assignment.compound(visitExpression(node.leftHandSide), | |
556 node.op, | |
557 visitExpression(node.value)); | |
558 } | |
559 Expression leftHandSide = node.leftHandSide; | |
560 if (leftHandSide is VariableUse) { | |
561 return withExpression(node.value, (Expression value) { | |
562 return new Assignment(leftHandSide, value); | |
563 }, store: false); | |
564 } else if (leftHandSide is PropertyAccess) { | |
565 return withExpressions( | |
566 [leftHandSide.receiver, leftHandSide.selector, node.value], | |
567 (evaluated) { | |
568 return new Assignment.compound( | |
569 new PropertyAccess(evaluated[0], evaluated[1]), | |
570 node.op, | |
571 evaluated[2]); | |
572 }); | |
573 } else { | |
574 throw "Unexpected assignment left hand side $leftHandSide"; | |
575 } | |
576 } | |
577 | |
578 @override | |
579 Expression visitAwait(Await node) { | |
floitsch
2015/02/04 17:06:09
Add comment explaining what you do here.
Ideally w
sigurdm
2015/02/05 14:06:07
I refer to the visitFun documentation.
| |
580 int afterWait = newLabel("returning from await."); | |
581 withExpression(node.expression, (Expression expression) { | |
582 addStatement(setGotoVariable(afterWait)); | |
583 if (errorHandlerLabels.isEmpty) { | |
584 addStatement(js.statement(""" | |
585 return #thenHelper(#expression, $helperName, $controllerName, null); | |
586 """, {"thenHelper": thenHelper, "expression": expression})); | |
587 } else { | |
588 addStatement(js.statement(""" | |
589 return #thenHelper(#expression, $helperName, $controllerName, | |
590 function($errorName) { | |
591 $gotoName = #currentHandler; | |
592 $helperName($errorName); | |
593 });""", {"thenHelper": thenHelper, | |
594 "expression": expression, | |
595 "currentHandler": currentErrorHandler})); | |
596 } | |
597 }, store: false); | |
598 beginLabel(afterWait); | |
599 return new VariableUse(resultName); | |
600 } | |
601 | |
602 /// Checks if [node] is the variable named [resultName]. | |
603 /// | |
604 /// [resultName] is used to hold the result of a transformed computation | |
605 /// for example the result of awaiting, or the result of a conditional or | |
606 /// short-circuiting expression. | |
607 /// If the subexpression of some transformed node already is transformed and | |
608 /// visiting it returns [resultName], we can avoid reduntdantly assigning it | |
609 /// to itself again. | |
610 bool isResult(Expression node) { | |
611 return node is VariableUse && node.name == resultName; | |
612 } | |
613 | |
614 @override | |
615 Expression visitBinary(Binary node) { | |
616 if (shouldTransform(node.right) && | |
617 (node.op == "||" || node.op == "&&")) { | |
618 int thenLabel = newLabel("then"); | |
619 int joinLabel = newLabel("join"); | |
620 withExpression(node.left, (Expression left) { | |
621 Statement assignLeft = isResult(left) | |
622 ? new Block.empty() | |
623 : new ExpressionStatement( | |
624 new Assignment(new VariableUse(resultName), left)); | |
625 if (node.op == "||") { | |
626 addStatement(new If(left, gotoAndBreak(thenLabel), assignLeft)); | |
627 } else { | |
628 assert(node.op == "&&"); | |
629 addStatement(new If(left, assignLeft, gotoAndBreak(thenLabel))); | |
630 } | |
631 }, store: true); | |
632 addGoto(joinLabel); | |
633 beginLabel(thenLabel); | |
634 withExpression(node.right, (Expression value) { | |
635 if (!isResult(value)) { | |
636 addExpressionStatement( | |
637 new Assignment(new VariableUse(resultName), value)); | |
638 } | |
639 }, store: false); | |
640 beginLabel(joinLabel); | |
641 return new VariableUse(resultName); | |
642 } | |
643 | |
644 return withExpression2( | |
645 node.left, | |
646 node.right, | |
647 (left, right) => new Binary(node.op, left, right)); | |
648 } | |
649 | |
650 @override | |
651 visitBlob(Blob node) { | |
652 return node; | |
653 } | |
654 | |
655 @override | |
656 void visitBlock(Block node) { | |
657 for (Statement statement in node.statements) { | |
658 visitStatement(statement); | |
659 } | |
660 } | |
661 | |
662 @override | |
663 void visitBreak(Break node) { | |
664 Node target = analysis.targets[node]; | |
665 if (!shouldTransform(target)) { | |
666 addStatement(node); | |
667 return; | |
668 } | |
669 translateJump(target, breakLabels[target]); | |
670 } | |
671 | |
672 @override | |
673 Expression visitCall(Call node) { | |
674 bool storeTarget = node.arguments.any(shouldTransform); | |
675 return withExpression(node.target, (target) { | |
676 return withExpressions(node.arguments, (List<Expression> arguments) { | |
677 return new Call(target, arguments); | |
678 }); | |
679 }, store: storeTarget); | |
680 } | |
681 | |
682 @override | |
683 visitCase(Case node) { | |
684 return unreachable(node); | |
685 } | |
686 | |
687 @override | |
688 visitCatch(Catch node) { | |
689 return unreachable(node); | |
690 } | |
691 | |
692 @override | |
693 visitComment(Comment node) { | |
694 addStatement(node); | |
695 } | |
696 | |
697 @override | |
698 Expression visitConditional(Conditional node) { | |
699 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { | |
700 return withExpression(node.condition, (Expression condition) { | |
701 return new Conditional(condition, node.then, node.otherwise); | |
702 }); | |
703 } | |
704 int thenLabel = newLabel("then"); | |
705 int joinLabel = newLabel("join"); | |
706 int elseLabel = newLabel("else"); | |
707 withExpression(node.condition, (Expression condition) { | |
708 addExpressionStatement( | |
709 new Assignment( | |
710 new VariableUse(gotoName), | |
711 new Conditional( | |
712 condition, | |
713 new LiteralNumber("$thenLabel"), | |
714 new LiteralNumber("$elseLabel")))); | |
715 }, store: false); | |
716 addBreak(); | |
717 beginLabel(thenLabel); | |
718 withExpression(node.then, (Expression value) { | |
719 if (!isResult(value)) { | |
720 addExpressionStatement( | |
721 new Assignment(new VariableUse(resultName), value)); | |
722 } | |
723 }, store: false); | |
724 addGoto(joinLabel); | |
725 beginLabel(elseLabel); | |
726 withExpression(node.otherwise, (Expression value) { | |
727 if (!isResult(value)) { | |
728 addExpressionStatement( | |
729 new Assignment(new VariableUse(resultName), value)); | |
730 } | |
731 }, store: false); | |
732 beginLabel(joinLabel); | |
733 return new VariableUse(resultName); | |
734 } | |
735 | |
736 @override | |
737 visitContinue(Continue node) { | |
738 Node target = analysis.targets[node]; | |
739 if (!shouldTransform(target)) { | |
740 addStatement(node); | |
741 return; | |
742 } | |
743 translateJump(target, continueLabels[target]); | |
744 } | |
745 | |
746 /// Returns a break statement that exits the big switch statement. | |
747 Statement breakStatement() { | |
748 if (insideUntranslatedBreakable) { | |
749 hasJumpThoughOuterLabel = true; | |
750 return new Break(outerLabelName); | |
751 } else { | |
752 return new Break(null); | |
753 } | |
754 } | |
755 | |
756 /// Convenience wrapper around [breakStatement]. | |
757 void addBreak() { | |
758 addStatement(breakStatement()); | |
759 } | |
760 | |
761 /// Common code for handling break, continue, return. | |
762 /// | |
763 /// It is necessary to run all nesting finally-handlers between the jump and | |
764 /// the target. For that we use [nextName] as a stack of places to go. | |
765 void translateJump(Node target, int targetLabel) { | |
766 // Compute a stack of all the 'finally' nodes we must visit before the jump. | |
767 // The bottom of the stack is the label where the jump goes to. | |
768 List<int> jumpStack = new List<int>(); | |
769 for (Node node in targetsAndTries.reversed) { | |
770 if (node is Try) { | |
771 assert(node.finallyPart != null); | |
772 jumpStack.add(finallyLabels[node]); | |
773 } else if (node == target) { | |
774 jumpStack.add(targetLabel); | |
775 break; | |
776 } | |
777 // Ignore other nodes. | |
778 } | |
779 jumpStack = jumpStack.reversed.toList(); | |
780 // We jump directly to the top of the stack, so take it off now. | |
781 int firstTarget = jumpStack.removeLast(); | |
782 if (jumpStack.isNotEmpty) { | |
783 hasJumpThroughFinally = true; | |
784 Expression jsJumpStack = new ArrayInitializer( | |
785 jumpStack.map((int label) => new LiteralNumber("$label")) | |
786 .toList()); | |
787 addStatement(js.statement("$nextName = #", [jsJumpStack])); | |
788 } | |
789 addGoto(firstTarget); | |
790 } | |
791 | |
792 @override | |
793 visitDefault(Default node) => unreachable(node); | |
794 | |
795 @override | |
796 visitDo(Do node) { | |
797 if (!shouldTransform(node)) { | |
798 bool oldInsideUntranslatedBreakable = insideUntranslatedBreakable; | |
799 insideUntranslatedBreakable = true; | |
800 withExpression(node.condition, (Expression condition) { | |
801 addStatement(new Do(translateInBlock(node.body), condition)); | |
802 }, store: false); | |
803 insideUntranslatedBreakable = oldInsideUntranslatedBreakable; | |
804 return; | |
805 } | |
806 int startLabel = newLabel("do body"); | |
807 | |
808 int continueLabel = newLabel("do condition"); | |
809 continueLabels[node] = continueLabel; | |
810 | |
811 int afterLabel = newLabel("after do"); | |
812 breakLabels[node] = afterLabel; | |
813 | |
814 beginLabel(startLabel); | |
815 | |
816 targetsAndTries.add(node); | |
817 visitStatement(node.body); | |
818 targetsAndTries.removeLast(); | |
819 | |
820 beginLabel(continueLabel); | |
821 withExpression(node.condition, (Expression condition) { | |
822 addStatement(new If.noElse(condition, | |
823 gotoAndBreak(startLabel))); | |
824 }, store: false); | |
825 beginLabel(afterLabel); | |
826 } | |
827 | |
828 @override | |
829 visitEmptyStatement(EmptyStatement node) => node; | |
830 | |
831 visitExpressionInStatementContext(Expression node) { | |
832 if (node is VariableDeclarationList) { | |
833 // Treat VariableDeclarationList as a statement. | |
834 visitVariableDeclarationList(node); | |
835 } else { | |
836 visitExpressionIgnoreResult(node); | |
837 } | |
838 } | |
839 | |
840 @override | |
841 visitExpressionStatement(ExpressionStatement node) { | |
842 visitExpressionInStatementContext(node.expression); | |
843 } | |
844 | |
845 @override | |
846 visitFor(For node) { | |
847 if (!shouldTransform(node) ) { | |
848 bool oldInsideUntranslated = insideUntranslatedBreakable; | |
849 insideUntranslatedBreakable = true; | |
850 // Note that node.init, node.condition, node.update all can be null, but | |
851 // withExpressions handles that. | |
852 withExpressions([node.init, node.condition, node.update], | |
853 (List<Expression> transformed) { | |
854 addStatement(new For(transformed[0], | |
855 transformed[1], | |
856 transformed[2], | |
857 translateInBlock(node.body))); | |
858 }); | |
859 insideUntranslatedBreakable = oldInsideUntranslated; | |
860 return; | |
861 } | |
862 | |
863 if (node.init != null) { | |
864 visitExpressionInStatementContext(node.init); | |
865 } | |
866 int startLabel = newLabel("for condition"); | |
867 // If there is no update, continuing the loop is the same as going to the | |
868 // start. | |
869 int continueLabel = (node.update == null) | |
870 ? startLabel | |
871 : newLabel("for update"); | |
872 continueLabels[node] = continueLabel; | |
873 int afterLabel = newLabel("after for"); | |
874 breakLabels[node] = afterLabel; | |
875 beginLabel(startLabel); | |
876 Expression condition = node.condition; | |
877 if (condition != null && | |
878 !(condition is LiteralBool && condition.value == true)) { | |
879 // No condition is the same as true. We don't have to check. | |
880 withExpression(node.condition, (Expression condition) { | |
881 addStatement(new If.noElse(new Prefix("!", condition), | |
882 gotoAndBreak(afterLabel))); | |
883 }, store: false); | |
884 } | |
885 targetsAndTries.add(node); | |
886 visitStatement(node.body); | |
887 targetsAndTries.removeLast(); | |
888 if (node.update != null) { | |
889 beginLabel(continueLabel); | |
890 visitExpressionIgnoreResult(node.update); | |
891 } | |
892 addGoto(startLabel); | |
893 beginLabel(afterLabel); | |
894 } | |
895 | |
896 @override | |
897 visitForIn(ForIn node) { | |
898 // The dart output currently never uses for-in loops. | |
899 throw "Javascript for-in not implemented yet in the await transformation"; | |
900 } | |
901 | |
902 @override | |
903 visitFunctionDeclaration(FunctionDeclaration node) { | |
904 return node; | |
905 } | |
906 | |
907 // Only used for code where `!shouldTransform(node)`. | |
908 Block translateInBlock(Statement node) { | |
909 assert(!shouldTransform(node)); | |
910 List<Statement> oldBuffer = currentStatementBuffer; | |
911 currentStatementBuffer = new List(); | |
912 List<Statement> resultBuffer = currentStatementBuffer; | |
913 visitStatement(node); | |
914 currentStatementBuffer = oldBuffer; | |
915 return new Block(resultBuffer); | |
916 } | |
917 | |
918 @override | |
919 visitIf(If node) { | |
920 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { | |
921 withExpression(node.condition, (Expression condition) { | |
922 addStatement( | |
923 new If( | |
924 condition, | |
925 translateInBlock(node.then), | |
926 translateInBlock(node.otherwise))); | |
927 }, store: false); | |
928 return; | |
929 } | |
930 int thenLabel = newLabel("then"); | |
931 int joinLabel = newLabel("join"); | |
932 int elseLabel = | |
933 node.otherwise is EmptyStatement ? joinLabel : newLabel("else"); | |
934 | |
935 withExpression(node.condition, (Expression condition) { | |
936 addExpressionStatement( | |
937 new Assignment( | |
938 new VariableUse(gotoName), | |
939 new Conditional( | |
940 condition, | |
941 new LiteralNumber("$thenLabel"), | |
942 new LiteralNumber("$elseLabel")))); | |
943 }, store: false); | |
944 addBreak(); | |
945 beginLabel(thenLabel); | |
946 visitStatement(node.then); | |
947 if (node.otherwise is! EmptyStatement) { | |
948 addGoto(joinLabel); | |
949 beginLabel(elseLabel); | |
950 visitStatement(node.otherwise); | |
951 } | |
952 beginLabel(joinLabel); | |
953 } | |
954 | |
955 @override | |
956 visitInterpolatedExpression(InterpolatedExpression node) => unsupported(node); | |
957 | |
958 @override | |
959 visitInterpolatedLiteral(InterpolatedLiteral node) => unsupported(node); | |
960 | |
961 @override | |
962 visitInterpolatedParameter(InterpolatedParameter node) => unsupported(node); | |
963 | |
964 @override | |
965 visitInterpolatedSelector(InterpolatedSelector node) => unsupported(node); | |
966 | |
967 @override | |
968 visitInterpolatedStatement(InterpolatedStatement node) => unsupported(node); | |
969 | |
970 @override | |
971 visitLabeledStatement(LabeledStatement node) { | |
972 if (!shouldTransform(node)) { | |
973 addStatement( | |
974 new LabeledStatement(node.label, translateInBlock(node.body))); | |
975 return; | |
976 } | |
977 int breakLabel = newLabel("break ${node.label}"); | |
978 int continueLabel = newLabel("continue ${node.label}"); | |
979 breakLabels[node] = breakLabel; | |
980 continueLabels[node] = continueLabel; | |
981 | |
982 beginLabel(continueLabel); | |
983 targetsAndTries.add(node); | |
984 visitStatement(node.body); | |
985 targetsAndTries.removeLast(); | |
986 beginLabel(breakLabel); | |
987 } | |
988 | |
989 @override | |
990 visitLiteralBool(LiteralBool node) => node; | |
991 | |
992 @override | |
993 visitLiteralExpression(LiteralExpression node) => unsupported(node); | |
994 | |
995 @override | |
996 visitLiteralNull(LiteralNull node) => node; | |
997 | |
998 @override | |
999 visitLiteralNumber(LiteralNumber node) => node; | |
1000 | |
1001 @override | |
1002 visitLiteralStatement(LiteralStatement node) => unsupported(node); | |
1003 | |
1004 @override | |
1005 visitLiteralString(LiteralString node) => node; | |
1006 | |
1007 @override | |
1008 visitNamedFunction(NamedFunction node) { | |
1009 throw new UnimplementedError("Internal error: " | |
1010 "Named functions are not handled yet by the javascript async " | |
1011 "transform"); | |
1012 } | |
1013 | |
1014 @override | |
1015 visitNew(New node) { | |
1016 bool storeTarget = node.arguments.any(shouldTransform); | |
1017 return withExpression(node.target, (target) { | |
1018 return withExpressions(node.arguments, (List<Expression> arguments) { | |
1019 return new New(target, arguments); | |
1020 }); | |
1021 }, store: storeTarget); | |
1022 } | |
1023 | |
1024 @override | |
1025 visitObjectInitializer(ObjectInitializer node) { | |
1026 return withExpressions( | |
1027 node.properties.map((Property property) => property.value).toList(), | |
1028 (List<Node> values) { | |
1029 List<Property> properties = new List.generate(values.length, (int i) { | |
1030 return new Property(node.properties[i].name, values[i]); | |
1031 }); | |
1032 return new ObjectInitializer(properties); | |
1033 }); | |
1034 } | |
1035 | |
1036 @override | |
1037 visitParameter(Parameter node) => unreachable(node); | |
1038 | |
1039 @override | |
1040 visitPostfix(Postfix node) { | |
1041 if (node.op == "++" || node.op == "--") { | |
1042 Expression argument = node.argument; | |
1043 if (argument is VariableUse) { | |
1044 return new Postfix(node.op, argument); | |
1045 } else if (argument is PropertyAccess) { | |
1046 return withExpression2(argument.receiver, argument.selector, | |
1047 (receiver, selector) { | |
1048 return new Postfix(node.op, new PropertyAccess(receiver, selector)); | |
1049 }); | |
1050 } else { | |
1051 throw "Unexpected postfix ${node.op} " | |
1052 "operator argument ${node.argument}"; | |
1053 } | |
1054 } | |
1055 return withExpression( | |
1056 node.argument, | |
1057 (Expression argument) => new Postfix(node.op, argument), | |
1058 store: false); | |
1059 } | |
1060 | |
1061 @override | |
1062 visitPrefix(Prefix node) { | |
1063 if (node.op == "++" || node.op == "--") { | |
1064 Expression argument = node.argument; | |
1065 if (argument is VariableUse) { | |
1066 return new Prefix(node.op, argument); | |
1067 } else if (argument is PropertyAccess) { | |
1068 return withExpression2(argument.receiver, argument.selector, | |
1069 (receiver, selector) { | |
1070 return new Prefix(node.op, new PropertyAccess(receiver, selector)); | |
1071 }); | |
1072 } else { | |
1073 throw "Unexpected prefix ${node.op} operator " | |
1074 "argument ${node.argument}"; | |
1075 } | |
1076 } | |
1077 return withExpression( | |
1078 node.argument, | |
1079 (Expression argument) => new Prefix(node.op, argument), | |
1080 store: false); | |
1081 } | |
1082 | |
1083 @override | |
1084 visitProgram(Program node) => throw "Unsupported"; | |
1085 | |
1086 @override | |
1087 visitProperty(Property node) { | |
1088 return withExpression( | |
1089 node.value, | |
1090 (Expression value) => new Property(node.name, value), | |
1091 store: false); | |
1092 } | |
1093 | |
1094 @override | |
1095 visitRegExpLiteral(RegExpLiteral node) => node; | |
1096 | |
1097 @override | |
1098 visitReturn(Return node) { | |
1099 assert(node.value == null || !isSyncStar && !isAsyncStar); | |
1100 Node target = analysis.targets[node]; | |
1101 if (node.value != null) { | |
1102 withExpression(node.value, (Expression value) { | |
1103 addStatement(js.statement("$returnValueName = #", [value])); | |
1104 }, store: false); | |
1105 } | |
1106 translateJump(target, returnLabel); | |
1107 } | |
1108 | |
1109 @override | |
1110 visitSwitch(Switch node) { | |
1111 if (!node.cases.any(shouldTransform)) { | |
1112 // If only the key has an await, translation can be simplified. | |
1113 bool oldInsideUntranslated = insideUntranslatedBreakable; | |
1114 insideUntranslatedBreakable = true; | |
1115 withExpression(node.key, (Expression key) { | |
1116 List<SwitchClause> cases = node.cases.map((SwitchClause clause) { | |
1117 if (clause is Case) { | |
1118 return new Case(clause.expression, translateInBlock(clause.body)); | |
1119 } else if (clause is Default) { | |
1120 return new Default(translateInBlock(clause.body)); | |
1121 } | |
1122 }).toList(); | |
1123 addStatement(new Switch(key, cases)); | |
1124 }, store: false); | |
1125 insideUntranslatedBreakable = oldInsideUntranslated; | |
1126 return; | |
1127 } | |
1128 int before = newLabel("switch"); | |
1129 int after = newLabel("after switch"); | |
1130 breakLabels[node] = after; | |
1131 | |
1132 beginLabel(before); | |
1133 List<int> labels = new List<int>(node.cases.length); | |
1134 | |
1135 if (!node.cases.every( | |
1136 (SwitchClause x) => !(x is Case && shouldTransform(x.expression)))) { | |
1137 int defaultIndex = null; // Null means no default was found. | |
1138 // If there is an await in one of the keys, we have to use a chain of ifs. | |
1139 | |
1140 withExpression(node.key, (Expression key) { | |
1141 int i = 0; | |
1142 for (SwitchClause clause in node.cases) { | |
1143 if (clause is Default) { | |
1144 // The goto for the default case is added after all non-default | |
1145 // clauses have been handled. | |
1146 defaultIndex = i; | |
1147 labels[i] = newLabel("default"); | |
1148 continue; | |
1149 } else if (clause is Case) { | |
1150 labels[i] = newLabel("case"); | |
1151 withExpression(clause.expression, (expression) { | |
1152 addStatement( | |
1153 new If.noElse(new Binary("===", key, expression), | |
1154 gotoAndBreak(labels[i]))); | |
1155 }, store: false); | |
1156 } | |
1157 i++; | |
1158 } | |
1159 }, store: true); | |
1160 | |
1161 if (defaultIndex == null) { | |
1162 addGoto(after); | |
1163 } else { | |
1164 addGoto(labels[defaultIndex]); | |
1165 } | |
1166 | |
1167 } else { | |
1168 bool hasDefault = false; | |
1169 int i = 0; | |
1170 List<SwitchClause> clauses = new List<SwitchClause>(); | |
1171 for (SwitchClause clause in node.cases) { | |
1172 if (clause is Case) { | |
1173 labels[i] = newLabel("case"); | |
1174 clauses.add(new Case(clause.expression, gotoAndBreak(labels[i]))); | |
1175 } else if (i is Default) { | |
1176 labels[i] = newLabel("default"); | |
1177 clauses.add(new Default(gotoAndBreak(labels[i]))); | |
1178 hasDefault = true; | |
1179 } else { | |
1180 diagnosticListener.internalError(spannable, | |
1181 "Unknown clause type $clause"); | |
1182 } | |
1183 i++; | |
1184 } | |
1185 withExpression(node.key, (Expression key) { | |
1186 addStatement(new Switch(key, clauses)); | |
1187 }, store: false); | |
1188 if (!hasDefault) { | |
1189 addGoto(after); | |
1190 } | |
1191 } | |
1192 | |
1193 targetsAndTries.add(node); | |
1194 for (int i = 0; i < labels.length; i++) { | |
1195 beginLabel(labels[i]); | |
1196 visitStatement(node.cases[i].body); | |
1197 } | |
1198 beginLabel(after); | |
1199 targetsAndTries.removeLast(); | |
1200 } | |
1201 | |
1202 @override | |
1203 visitThis(This node) { | |
1204 return new VariableUse(selfName); | |
1205 } | |
1206 | |
1207 @override | |
1208 visitThrow(Throw node) { | |
1209 withExpression(node.expression, (Expression expression) { | |
1210 addStatement(new Throw(expression)); | |
1211 }, store: false); | |
1212 } | |
1213 | |
1214 setHandler() { | |
1215 addExpressionStatement( | |
1216 new Assignment(new VariableUse(handlerName), currentErrorHandler)); | |
1217 } | |
1218 | |
1219 @override | |
1220 void visitTry(Try node) { | |
1221 if (!shouldTransform(node)) { | |
1222 Block body = translateInBlock(node.body); | |
1223 Catch catchPart = (node.catchPart == null) | |
1224 ? null | |
1225 : new Catch(node.catchPart.declaration, | |
1226 translateInBlock(node.catchPart.body)); | |
1227 Block finallyPart = (node.finallyPart == null) | |
1228 ? null | |
1229 : translateInBlock(node.finallyPart); | |
1230 addStatement(new Try(body, catchPart, finallyPart)); | |
1231 return; | |
1232 } | |
1233 hasTryBlocks = true; | |
1234 int handlerLabel = newLabel("catch"); | |
1235 int finallyLabel = newLabel("finally"); | |
1236 int afterFinallyLabel = newLabel("after finally"); | |
1237 errorHandlerLabels.add(handlerLabel); | |
1238 // We set the error handler here. It must be cleared on every path out. | |
1239 // Normal and error exit. | |
1240 setHandler(); | |
1241 if (node.finallyPart != null) { | |
1242 finallyLabels[node] = finallyLabel; | |
1243 targetsAndTries.add(node); | |
1244 } | |
1245 visitStatement(node.body); | |
1246 errorHandlerLabels.removeLast(); | |
1247 setHandler(); | |
1248 addStatement(js.statement("$nextName = [#];", | |
1249 [new LiteralNumber("$afterFinallyLabel")])); | |
1250 if (node.finallyPart != null) { | |
1251 addGoto(finallyLabel); | |
1252 } else { | |
1253 addGoto(afterFinallyLabel); | |
1254 } | |
1255 beginLabel(handlerLabel); | |
1256 if (node.catchPart != null) { | |
1257 setHandler(); | |
1258 // The catch declaration name can shadow outer variables, so we need | |
1259 // a fresh name to avoid collisions. | |
1260 String errorRename = freshName(node.catchPart.declaration.name); | |
1261 localVariables.add(new VariableDeclaration(errorRename)); | |
1262 variableRenamings.add( | |
1263 new Pair(node.catchPart.declaration.name, errorRename)); | |
1264 addExpressionStatement( | |
1265 new Assignment(new VariableUse(errorRename), | |
1266 new VariableUse(resultName))); | |
1267 visitStatement(node.catchPart.body); | |
1268 variableRenamings.removeLast(); | |
1269 } | |
1270 if (node.finallyPart != null) { | |
1271 targetsAndTries.removeLast(); | |
1272 // This belongs to the catch-part, but is only needed if there is a | |
1273 // finally therefore it is in this branch. | |
1274 addStatement(js.statement("$nextName = [#];", | |
1275 [new LiteralNumber("$afterFinallyLabel")])); | |
1276 beginLabel(finallyLabel); | |
1277 visitStatement(node.finallyPart); | |
1278 addStatement(new Comment("// goto the next finally handler")); | |
1279 addStatement(js.statement("$gotoName = $nextName.pop();")); | |
1280 addBreak(); | |
1281 } | |
1282 beginLabel(afterFinallyLabel); | |
1283 } | |
1284 | |
1285 @override | |
1286 visitVariableDeclaration(VariableDeclaration node) { | |
1287 throw "TODO"; | |
1288 } | |
1289 | |
1290 @override | |
1291 void visitVariableDeclarationList(VariableDeclarationList node) { | |
1292 // Declaration of local variables is hoisted outside the helper but the | |
1293 // initialization is done here. | |
1294 for (VariableInitialization initialization in node.declarations) { | |
1295 VariableDeclaration declaration = initialization.declaration; | |
1296 localVariables.add(declaration); | |
1297 if (initialization.value != null) { | |
1298 withExpression(initialization.value, (Expression value) { | |
1299 addStatement( | |
1300 new ExpressionStatement(new Assignment(declaration, value))); | |
1301 }, store: false); | |
1302 } | |
1303 } | |
1304 } | |
1305 | |
1306 @override | |
1307 visitVariableInitialization(VariableInitialization node) { | |
1308 // These should be handled by visitVariableDeclarationList. | |
1309 throw "Unexpected variableInitialization"; | |
1310 } | |
1311 | |
1312 @override | |
1313 visitVariableUse(VariableUse node) { | |
1314 String renaming = variableRenamings.lastWhere( | |
1315 (Pair renaming) => renaming.a == node.name, | |
1316 orElse: () => new Pair(null, null)).b; | |
1317 if (renaming == null) return node; | |
1318 return new VariableUse(renaming); | |
1319 } | |
1320 | |
1321 @override | |
1322 visitWhile(While node) { | |
1323 if (!shouldTransform(node)) { | |
1324 bool oldInsideUntranslated = insideUntranslatedBreakable; | |
1325 insideUntranslatedBreakable = true; | |
1326 withExpression(node.condition, (Expression condition) { | |
1327 addStatement(new While(condition, translateInBlock(node.body))); | |
1328 }, store: false); | |
1329 insideUntranslatedBreakable = oldInsideUntranslated; | |
1330 return; | |
1331 } | |
1332 int continueLabel = newLabel("while condition"); | |
1333 continueLabels[node] = continueLabel; | |
1334 beginLabel(continueLabel); | |
1335 | |
1336 int afterLabel = newLabel("after while"); | |
1337 breakLabels[node] = afterLabel; | |
1338 Expression condition = node.condition; | |
1339 // If the condition is `true`, we don't need a test. | |
1340 if (!(condition is LiteralBool && condition.value == true)) { | |
1341 withExpression(node.condition, (Expression condition) { | |
1342 addStatement(new If.noElse(new Prefix("!", condition), | |
1343 gotoAndBreak(afterLabel))); | |
1344 }, store: false); | |
1345 } | |
1346 targetsAndTries.add(node); | |
1347 visitStatement(node.body); | |
1348 targetsAndTries.removeLast(); | |
1349 addGoto(continueLabel); | |
1350 beginLabel(afterLabel); | |
1351 } | |
1352 | |
1353 @override | |
1354 visitDartYield(DartYield node) { | |
floitsch
2015/02/04 17:06:10
Add doc what's happening here.
Ideally with exampl
sigurdm
2015/02/05 14:06:07
Done - no examples though
| |
1355 assert(isSyncStar || isAsyncStar); | |
1356 if (isSyncStar) { | |
1357 int label = newLabel("after yield"); | |
1358 withExpression(node.expression, (Expression expression) { | |
1359 // We don't do a break here for the goto, but instead a return below. | |
1360 addStatement(setGotoVariable(label)); | |
1361 if (node.hasStar) { | |
1362 addStatement(new Return(new Call(yieldStarExpression, [expression]))); | |
1363 } else { | |
1364 addStatement(new Return(expression)); | |
1365 } | |
1366 beginLabel(label); | |
1367 }, store: false); | |
1368 } else { | |
1369 int label = newLabel("after yield"); | |
1370 withExpression(node.expression, (Expression expression) { | |
1371 // We don't do a break here for the goto, but instead a return below. | |
1372 addStatement(setGotoVariable(label)); | |
1373 // Find all the finally blocks that should be performed if the stream is | |
1374 // canceled during the yield. | |
1375 // At the bottom of the stack is the return label. | |
1376 List<int> enclosingFinallyLabels = <int>[returnLabel]; | |
1377 enclosingFinallyLabels.addAll(targetsAndTries | |
1378 .where((Node node) => node is Try) | |
1379 .map((Try node) => finallyLabels[node])); | |
1380 int destinationOnCancel = enclosingFinallyLabels.removeLast(); | |
1381 addStatement(js.statement(""" | |
1382 return #thenHelper(#yieldExpression(#expression), | |
1383 $helperName, $controllerName, function () { | |
1384 if (#notEmptyFinallyList) { | |
floitsch
2015/02/04 17:06:10
remove {}.
(It's stupid, but otherwise they stay i
sigurdm
2015/02/05 14:06:07
Done.
| |
1385 $nextName = #finallyList; | |
1386 } | |
1387 $gotoName = #destinationOnCancel; | |
1388 $helperName(); | |
1389 });""", | |
1390 {"thenHelper": thenHelper, | |
1391 "yieldExpression": node.hasStar | |
1392 ? yieldStarExpression | |
1393 : yieldExpression, | |
1394 "expression": expression, | |
1395 "notEmptyFinallyList": enclosingFinallyLabels.isNotEmpty, | |
1396 "finallyList": new ArrayInitializer(enclosingFinallyLabels.map( | |
1397 (int label) => new LiteralNumber("$label")).toList()), | |
floitsch
2015/02/04 17:06:10
js.number(label)
sigurdm
2015/02/05 14:06:07
Done (here and elsewhere)
| |
1398 "destinationOnCancel": new LiteralNumber("$destinationOnCancel") | |
floitsch
2015/02/04 17:06:10
js.number(destinationOnCancel)
sigurdm
2015/02/05 14:06:07
Done.
| |
1399 })); | |
1400 beginLabel(label); | |
1401 }, store: false); | |
1402 } | |
1403 } | |
1404 } | |
1405 | |
1406 /// Finds out | |
1407 /// | |
1408 /// - which expressions have yield or await nested in them. | |
1409 /// - targets of jumps | |
1410 /// - a set of used names. | |
1411 /// - if any [This]-expressions are used. | |
1412 class PreTranslationAnalysis extends NodeVisitor<bool> { | |
1413 Set<Node> hasAwaitOrYield = new Set<Node>(); | |
1414 | |
1415 Map<Node, Node> targets = new Map<Node, Node>(); | |
1416 List<Node> loopsAndSwitches = new List<Node>(); | |
1417 List<LabeledStatement> labelledStatements = new List<LabeledStatement>(); | |
1418 Set<String> usedNames = new Set<String>(); | |
1419 | |
1420 bool hasExplicitReturns = false; | |
1421 | |
1422 bool hasThis = false; | |
1423 | |
1424 // The function currently being analyzed. | |
1425 Fun currentFunction; | |
1426 | |
1427 // For error messages. | |
1428 final Function unsupported; | |
1429 | |
1430 PreTranslationAnalysis(this.unsupported); | |
1431 | |
1432 bool visit(Node node) { | |
1433 bool containsAwait = node.accept(this); | |
1434 if (containsAwait) { | |
1435 hasAwaitOrYield.add(node); | |
1436 } | |
1437 return containsAwait; | |
1438 } | |
1439 | |
1440 analyze(Fun node) { | |
1441 currentFunction = node; | |
1442 node.params.forEach(visit); | |
1443 visit(node.body); | |
1444 } | |
1445 | |
1446 @override | |
1447 bool visitAccess(PropertyAccess node) { | |
1448 bool receiver = visit(node.receiver); | |
1449 bool selector = visit(node.selector); | |
1450 return receiver || selector; | |
1451 } | |
1452 | |
1453 @override | |
1454 bool visitArrayHole(ArrayHole node) { | |
1455 return false; | |
1456 } | |
1457 | |
1458 @override | |
1459 bool visitArrayInitializer(ArrayInitializer node) { | |
1460 bool containsAwait = false; | |
1461 for (Expression element in node.elements) { | |
1462 if (visit(element)) containsAwait = true; | |
1463 } | |
1464 return containsAwait; | |
1465 } | |
1466 | |
1467 @override | |
1468 bool visitAssignment(Assignment node) { | |
1469 bool leftHandSide = visit(node.leftHandSide); | |
1470 bool value = (node.value == null) ? false : visit(node.value); | |
1471 return leftHandSide || value; | |
1472 } | |
1473 | |
1474 @override | |
1475 bool visitAwait(Await node) { | |
1476 visit(node.expression); | |
1477 return true; | |
1478 } | |
1479 | |
1480 @override | |
1481 bool visitBinary(Binary node) { | |
1482 bool left = visit(node.left); | |
1483 bool right = visit(node.right); | |
1484 return left || right; | |
1485 } | |
1486 | |
1487 @override | |
1488 bool visitBlob(Blob node) { | |
1489 return false; | |
1490 } | |
1491 | |
1492 @override | |
1493 bool visitBlock(Block node) { | |
1494 bool containsAwait = false; | |
1495 for (Statement statement in node.statements) { | |
1496 if (visit(statement)) containsAwait = true; | |
1497 } | |
1498 return containsAwait; | |
1499 } | |
1500 | |
1501 @override | |
1502 bool visitBreak(Break node) { | |
1503 if (node.targetLabel != null) { | |
1504 targets[node] = labelledStatements.lastWhere( | |
1505 (LabeledStatement statement) => statement.label == node.targetLabel); | |
1506 } else { | |
1507 targets[node] = loopsAndSwitches.last; | |
1508 } | |
1509 return false; | |
1510 } | |
1511 | |
1512 @override | |
1513 bool visitCall(Call node) { | |
1514 bool containsAwait = visit(node.target); | |
1515 for (Expression argument in node.arguments) { | |
1516 if (visit(argument)) containsAwait = true; | |
1517 } | |
1518 return containsAwait; | |
1519 } | |
1520 | |
1521 @override | |
1522 bool visitCase(Case node) { | |
1523 bool expression = visit(node.expression); | |
1524 bool body = visit(node.body); | |
1525 return expression || body; | |
1526 } | |
1527 | |
1528 @override | |
1529 bool visitCatch(Catch node) { | |
1530 bool declaration = visit(node.declaration); | |
1531 bool body = visit(node.body); | |
1532 return declaration || body; | |
1533 } | |
1534 | |
1535 @override | |
1536 bool visitComment(Comment node) { | |
1537 return false; | |
1538 } | |
1539 | |
1540 @override | |
1541 bool visitConditional(Conditional node) { | |
1542 bool condition = visit(node.condition); | |
1543 bool then = visit(node.then); | |
1544 bool otherwise = visit(node.otherwise); | |
1545 return condition || then || otherwise; | |
1546 } | |
1547 | |
1548 @override | |
1549 bool visitContinue(Continue node) { | |
1550 if (node.targetLabel != null) { | |
1551 targets[node] = labelledStatements.lastWhere( | |
1552 (LabeledStatement stm) => stm.label == node.targetLabel); | |
1553 } else { | |
1554 targets[node] = | |
1555 loopsAndSwitches.lastWhere((Node node) => node is! Switch); | |
1556 } | |
1557 assert(targets[node] is Loop || targets[node] is LabeledStatement); | |
1558 return false; | |
1559 } | |
1560 | |
1561 @override | |
1562 bool visitDefault(Default node) { | |
1563 return visit(node.body); | |
1564 } | |
1565 | |
1566 @override | |
1567 bool visitDo(Do node) { | |
1568 loopsAndSwitches.add(node); | |
1569 bool body = visit(node.body); | |
1570 bool condition = visit(node.condition); | |
1571 loopsAndSwitches.removeLast(); | |
1572 return body || condition; | |
1573 } | |
1574 | |
1575 @override | |
1576 bool visitEmptyStatement(EmptyStatement node) { | |
1577 return false; | |
1578 } | |
1579 | |
1580 @override | |
1581 bool visitExpressionStatement(ExpressionStatement node) { | |
1582 return visit(node.expression); | |
1583 } | |
1584 | |
1585 @override | |
1586 bool visitFor(For node) { | |
1587 bool init = (node.init == null) ? false : visit(node.init); | |
1588 bool condition = (node.condition == null) ? false : visit(node.condition); | |
1589 bool update = (node.update == null) ? false : visit(node.update); | |
1590 loopsAndSwitches.add(node); | |
1591 bool body = visit(node.body); | |
1592 loopsAndSwitches.removeLast(); | |
1593 return init || condition || update || body; | |
1594 } | |
1595 | |
1596 @override | |
1597 bool visitForIn(ForIn node) { | |
1598 bool object = visit(node.object); | |
1599 loopsAndSwitches.add(node); | |
1600 bool body = visit(node.body); | |
1601 loopsAndSwitches.removeLast(); | |
1602 return object || body; | |
1603 } | |
1604 | |
1605 @override | |
1606 bool visitFun(Fun node) { | |
1607 return false; | |
1608 } | |
1609 | |
1610 @override | |
1611 bool visitFunctionDeclaration(FunctionDeclaration node) { | |
1612 return false; | |
1613 } | |
1614 | |
1615 @override | |
1616 bool visitIf(If node) { | |
1617 bool condition = visit(node.condition); | |
1618 bool then = visit(node.then); | |
1619 bool otherwise = visit(node.otherwise); | |
1620 return condition || then || otherwise; | |
1621 } | |
1622 | |
1623 @override | |
1624 bool visitInterpolatedExpression(InterpolatedExpression node) { | |
1625 return unsupported(node); | |
1626 } | |
1627 | |
1628 @override | |
1629 bool visitInterpolatedLiteral(InterpolatedLiteral node) { | |
1630 return unsupported(node); | |
1631 } | |
1632 | |
1633 @override | |
1634 bool visitInterpolatedParameter(InterpolatedParameter node) { | |
1635 return unsupported(node); | |
1636 } | |
1637 | |
1638 @override | |
1639 bool visitInterpolatedSelector(InterpolatedSelector node) { | |
1640 return unsupported(node); | |
1641 } | |
1642 | |
1643 @override | |
1644 bool visitInterpolatedStatement(InterpolatedStatement node) { | |
1645 return unsupported(node); | |
1646 } | |
1647 | |
1648 @override | |
1649 bool visitLabeledStatement(LabeledStatement node) { | |
1650 usedNames.add(node.label); | |
1651 labelledStatements.add(node); | |
1652 bool containsAwait = visit(node.body); | |
1653 labelledStatements.removeLast(); | |
1654 return containsAwait; | |
1655 } | |
1656 | |
1657 @override | |
1658 bool visitLiteralBool(LiteralBool node) { | |
1659 return false; | |
1660 } | |
1661 | |
1662 @override | |
1663 bool visitLiteralExpression(LiteralExpression node) { | |
1664 return unsupported(node); | |
1665 } | |
1666 | |
1667 @override | |
1668 bool visitLiteralNull(LiteralNull node) { | |
1669 return false; | |
1670 } | |
1671 | |
1672 @override | |
1673 bool visitLiteralNumber(LiteralNumber node) { | |
1674 return false; | |
1675 } | |
1676 | |
1677 @override | |
1678 bool visitLiteralStatement(LiteralStatement node) { | |
1679 return unsupported(node); | |
1680 } | |
1681 | |
1682 @override | |
1683 bool visitLiteralString(LiteralString node) { | |
1684 return false; | |
1685 } | |
1686 | |
1687 @override | |
1688 bool visitNamedFunction(NamedFunction node) { | |
1689 return false; | |
1690 } | |
1691 | |
1692 @override | |
1693 bool visitNew(New node) { | |
1694 return visitCall(node); | |
1695 } | |
1696 | |
1697 @override | |
1698 bool visitObjectInitializer(ObjectInitializer node) { | |
1699 bool containsAwait = false; | |
1700 for (Property property in node.properties) { | |
1701 if (visit(property)) containsAwait = true; | |
1702 } | |
1703 return containsAwait; | |
1704 } | |
1705 | |
1706 @override | |
1707 bool visitParameter(Parameter node) { | |
1708 usedNames.add(node.name); | |
1709 return false; | |
1710 } | |
1711 | |
1712 @override | |
1713 bool visitPostfix(Postfix node) { | |
1714 return visit(node.argument); | |
1715 } | |
1716 | |
1717 @override | |
1718 bool visitPrefix(Prefix node) { | |
1719 return visit(node.argument); | |
1720 } | |
1721 | |
1722 @override | |
1723 bool visitProgram(Program node) { | |
1724 throw "Unexpected"; | |
1725 } | |
1726 | |
1727 @override | |
1728 bool visitProperty(Property node) { | |
1729 return visit(node.value); | |
1730 } | |
1731 | |
1732 @override | |
1733 bool visitRegExpLiteral(RegExpLiteral node) { | |
1734 return false; | |
1735 } | |
1736 | |
1737 @override | |
1738 bool visitReturn(Return node) { | |
1739 hasExplicitReturns = true; | |
1740 targets[node] = currentFunction; | |
1741 if (node.value == null) return false; | |
1742 return visit(node.value); | |
1743 } | |
1744 | |
1745 @override | |
1746 bool visitSwitch(Switch node) { | |
1747 loopsAndSwitches.add(node); | |
1748 bool result = visit(node.key); | |
1749 for (SwitchClause clause in node.cases) { | |
1750 if (visit(clause)) result = true; | |
1751 } | |
1752 loopsAndSwitches.removeLast(); | |
1753 return result; | |
1754 } | |
1755 | |
1756 @override | |
1757 bool visitThis(This node) { | |
1758 hasThis = true; | |
1759 return false; | |
1760 } | |
1761 | |
1762 @override | |
1763 bool visitThrow(Throw node) { | |
1764 return visit(node.expression); | |
1765 } | |
1766 | |
1767 @override | |
1768 bool visitTry(Try node) { | |
1769 bool body = visit(node.body); | |
1770 bool catchPart = (node.catchPart == null) ? false : visit(node.catchPart); | |
1771 bool finallyPart = (node.finallyPart == null) | |
1772 ? false | |
1773 : visit(node.finallyPart); | |
1774 return body || catchPart || finallyPart; | |
1775 } | |
1776 | |
1777 @override | |
1778 bool visitVariableDeclaration(VariableDeclaration node) { | |
1779 usedNames.add(node.name); | |
1780 return false; | |
1781 } | |
1782 | |
1783 @override | |
1784 bool visitVariableDeclarationList(VariableDeclarationList node) { | |
1785 bool result = false; | |
1786 for (VariableInitialization init in node.declarations) { | |
1787 if (visit(init)) result = true; | |
1788 } | |
1789 return result; | |
1790 } | |
1791 | |
1792 @override | |
1793 bool visitVariableInitialization(VariableInitialization node) { | |
1794 return visitAssignment(node); | |
1795 } | |
1796 | |
1797 @override | |
1798 bool visitVariableUse(VariableUse node) { | |
1799 usedNames.add(node.name); | |
1800 return false; | |
1801 } | |
1802 | |
1803 @override | |
1804 bool visitWhile(While node) { | |
1805 loopsAndSwitches.add(node); | |
1806 bool condition = visit(node.condition); | |
1807 bool body = visit(node.body); | |
1808 loopsAndSwitches.removeLast(); | |
1809 return condition || body; | |
1810 } | |
1811 | |
1812 @override | |
1813 bool visitDartYield(DartYield node) { | |
1814 visit(node.expression); | |
1815 return true; | |
1816 } | |
1817 } | |
OLD | NEW |