OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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 backend_ast_nodes; | |
6 | |
7 import '../constants/values.dart' as values; | |
8 import '../dart_types.dart' as types; | |
9 import '../elements/elements.dart' as elements; | |
10 import '../tree/tree.dart' as tree; | |
11 import '../util/characters.dart' as characters; | |
12 | |
13 /// The following nodes correspond to [tree.Send] expressions: | |
14 /// [FieldExpression], [IndexExpression], [Assignment], [Increment], | |
15 /// [CallFunction], [CallMethod], [CallNew], [CallStatic], [UnaryOperator], | |
16 /// [BinaryOperator], and [TypeOperator]. | |
17 abstract class Node {} | |
18 | |
19 /// Receiver is an [Expression] or the [SuperReceiver]. | |
20 abstract class Receiver extends Node {} | |
21 | |
22 /// Argument is an [Expression] or a [NamedArgument]. | |
23 abstract class Argument extends Node {} | |
24 | |
25 abstract class Expression extends Node implements Receiver, Argument { | |
26 bool get assignable => false; | |
27 } | |
28 | |
29 abstract class RootNode extends Node { | |
30 elements.Element get element; | |
31 } | |
32 | |
33 class FieldDefinition extends RootNode { | |
34 final elements.Element element; | |
35 final Expression initializer; | |
36 FieldDefinition(this.element, this.initializer); | |
37 } | |
38 | |
39 abstract class Statement extends Node {} | |
40 | |
41 /// Used as receiver in expressions that dispatch to the super class. | |
42 /// For instance, an expression such as `super.f()` is represented | |
43 /// by a [CallMethod] node with [SuperReceiver] as its receiver. | |
44 class SuperReceiver extends Receiver { | |
45 static final SuperReceiver _instance = new SuperReceiver._create(); | |
46 | |
47 factory SuperReceiver() => _instance; | |
48 SuperReceiver._create(); | |
49 } | |
50 | |
51 /// Named arguments may occur in the argument list of | |
52 /// [CallFunction], [CallMethod], [CallNew], and [CallStatic]. | |
53 class NamedArgument extends Argument { | |
54 final String name; | |
55 final Expression expression; | |
56 | |
57 NamedArgument(this.name, this.expression); | |
58 } | |
59 | |
60 class TypeAnnotation extends Node { | |
61 final String name; | |
62 final List<TypeAnnotation> typeArguments; | |
63 | |
64 types.DartType dartType; | |
65 | |
66 TypeAnnotation(this.name, [this.typeArguments = const <TypeAnnotation>[]]); | |
67 } | |
68 | |
69 // STATEMENTS | |
70 | |
71 class Block extends Statement { | |
72 final List<Statement> statements; | |
73 | |
74 Block(this.statements); | |
75 } | |
76 | |
77 class Break extends Statement { | |
78 final String label; | |
79 | |
80 Break([this.label]); | |
81 } | |
82 | |
83 class Continue extends Statement { | |
84 final String label; | |
85 | |
86 Continue([this.label]); | |
87 } | |
88 | |
89 class EmptyStatement extends Statement { | |
90 static final EmptyStatement _instance = new EmptyStatement._create(); | |
91 | |
92 factory EmptyStatement() => _instance; | |
93 EmptyStatement._create(); | |
94 } | |
95 | |
96 class ExpressionStatement extends Statement { | |
97 final Expression expression; | |
98 | |
99 ExpressionStatement(this.expression); | |
100 } | |
101 | |
102 class For extends Statement { | |
103 final Node initializer; | |
104 final Expression condition; | |
105 final List<Expression> updates; | |
106 final Statement body; | |
107 | |
108 /// Initializer must be [VariableDeclarations] or [Expression] or null. | |
109 For(this.initializer, this.condition, this.updates, this.body) { | |
110 assert(initializer == null || | |
111 initializer is VariableDeclarations || | |
112 initializer is Expression); | |
113 } | |
114 } | |
115 | |
116 class ForIn extends Statement { | |
117 final Node leftHandValue; | |
118 final Expression expression; | |
119 final Statement body; | |
120 | |
121 /// [leftHandValue] must be [Identifier] or [VariableDeclarations] with | |
122 /// exactly one definition, and that variable definition must have no | |
123 /// initializer. | |
124 ForIn(Node leftHandValue, this.expression, this.body) | |
125 : this.leftHandValue = leftHandValue { | |
126 assert(leftHandValue is Identifier || | |
127 (leftHandValue is VariableDeclarations && | |
128 leftHandValue.declarations.length == 1 && | |
129 leftHandValue.declarations[0].initializer == null)); | |
130 } | |
131 } | |
132 | |
133 class While extends Statement { | |
134 final Expression condition; | |
135 final Statement body; | |
136 | |
137 While(this.condition, this.body); | |
138 } | |
139 | |
140 class DoWhile extends Statement { | |
141 final Statement body; | |
142 final Expression condition; | |
143 | |
144 DoWhile(this.body, this.condition); | |
145 } | |
146 | |
147 class If extends Statement { | |
148 final Expression condition; | |
149 final Statement thenStatement; | |
150 final Statement elseStatement; | |
151 | |
152 If(this.condition, this.thenStatement, [this.elseStatement]); | |
153 } | |
154 | |
155 class LabeledStatement extends Statement { | |
156 final String label; | |
157 final Statement statement; | |
158 | |
159 LabeledStatement(this.label, this.statement); | |
160 } | |
161 | |
162 class Rethrow extends Statement {} | |
163 | |
164 class Return extends Statement { | |
165 final Expression expression; | |
166 | |
167 Return([this.expression]); | |
168 } | |
169 | |
170 class Switch extends Statement { | |
171 final Expression expression; | |
172 final List<SwitchCase> cases; | |
173 | |
174 Switch(this.expression, this.cases); | |
175 } | |
176 | |
177 /// A sequence of case clauses followed by a sequence of statements. | |
178 /// Represents the default case if [expressions] is null. | |
179 /// | |
180 /// NOTE: | |
181 /// Control will never fall through to the following SwitchCase, even if | |
182 /// the list of statements is empty. An empty list of statements will be | |
183 /// unparsed to a semicolon to guarantee this behaviour. | |
184 class SwitchCase extends Node { | |
185 final List<Expression> expressions; | |
186 final List<Statement> statements; | |
187 | |
188 SwitchCase(this.expressions, this.statements); | |
189 SwitchCase.defaultCase(this.statements) : expressions = null; | |
190 | |
191 bool get isDefaultCase => expressions == null; | |
192 } | |
193 | |
194 /// A try statement. The try, catch and finally blocks will automatically | |
195 /// be printed inside a block statement if necessary. | |
196 class Try extends Statement { | |
197 final Statement tryBlock; | |
198 final List<CatchBlock> catchBlocks; | |
199 final Statement finallyBlock; | |
200 | |
201 Try(this.tryBlock, this.catchBlocks, [this.finallyBlock]) { | |
202 assert(catchBlocks.length > 0 || finallyBlock != null); | |
203 } | |
204 } | |
205 | |
206 class CatchBlock extends Node { | |
207 final TypeAnnotation onType; | |
208 final VariableDeclaration exceptionVar; | |
209 final VariableDeclaration stackVar; | |
210 final Statement body; | |
211 | |
212 /// At least onType or exceptionVar must be given. | |
213 /// stackVar may only be given if exceptionVar is also given. | |
214 CatchBlock(this.body, {this.onType, this.exceptionVar, this.stackVar}) { | |
215 // Must specify at least a type or an exception binding. | |
216 assert(onType != null || exceptionVar != null); | |
217 | |
218 // We cannot bind the stack trace without binding the exception too. | |
219 assert(stackVar == null || exceptionVar != null); | |
220 } | |
221 } | |
222 | |
223 class VariableDeclarations extends Statement { | |
224 final TypeAnnotation type; | |
225 final bool isFinal; | |
226 final bool isConst; | |
227 final List<VariableDeclaration> declarations; | |
228 | |
229 VariableDeclarations(this.declarations, | |
230 {this.type, this.isFinal: false, this.isConst: false}) { | |
231 // Cannot be both final and const. | |
232 assert(!isFinal || !isConst); | |
233 } | |
234 } | |
235 | |
236 class VariableDeclaration extends Node { | |
237 final String name; | |
238 final Expression initializer; | |
239 | |
240 elements.Element element; | |
241 | |
242 VariableDeclaration(this.name, [this.initializer]); | |
243 } | |
244 | |
245 class FunctionDeclaration extends Statement { | |
246 final FunctionExpression function; | |
247 | |
248 TypeAnnotation get returnType => function.returnType; | |
249 Parameters get parameters => function.parameters; | |
250 String get name => function.name; | |
251 Statement get body => function.body; | |
252 | |
253 FunctionDeclaration(this.function); | |
254 } | |
255 | |
256 class Parameters extends Node { | |
257 final List<Parameter> requiredParameters; | |
258 final List<Parameter> optionalParameters; | |
259 final bool hasNamedParameters; | |
260 | |
261 Parameters(this.requiredParameters, | |
262 [this.optionalParameters, this.hasNamedParameters = false]); | |
263 | |
264 Parameters.named(this.requiredParameters, this.optionalParameters) | |
265 : hasNamedParameters = true; | |
266 | |
267 Parameters.positional(this.requiredParameters, this.optionalParameters) | |
268 : hasNamedParameters = false; | |
269 | |
270 bool get hasOptionalParameters => | |
271 optionalParameters != null && optionalParameters.length > 0; | |
272 } | |
273 | |
274 class Parameter extends Node { | |
275 final String name; | |
276 | |
277 /// Type of parameter, or return type of function parameter. | |
278 final TypeAnnotation type; | |
279 | |
280 Expression defaultValue; | |
281 | |
282 /// Parameters to function parameter. Null for non-function parameters. | |
283 final Parameters parameters; | |
284 | |
285 elements.FormalElement element; | |
286 | |
287 Parameter(this.name, {this.type, this.defaultValue}) : parameters = null; | |
288 | |
289 Parameter.function(this.name, TypeAnnotation returnType, this.parameters, | |
290 [this.defaultValue]) | |
291 : type = returnType { | |
292 assert(parameters != null); | |
293 } | |
294 | |
295 /// True if this is a function parameter. | |
296 bool get isFunction => parameters != null; | |
297 } | |
298 | |
299 // EXPRESSIONS | |
300 | |
301 abstract class Initializer extends Expression {} | |
302 | |
303 class FieldInitializer extends Initializer { | |
304 final elements.FieldElement element; | |
305 final Expression body; | |
306 | |
307 FieldInitializer(this.element, this.body); | |
308 } | |
309 | |
310 class SuperInitializer extends Initializer { | |
311 final elements.ConstructorElement target; | |
312 final List<Argument> arguments; | |
313 | |
314 SuperInitializer(this.target, this.arguments); | |
315 } | |
316 | |
317 class FunctionExpression extends Expression implements RootNode { | |
318 final TypeAnnotation returnType; | |
319 String name; | |
320 final Parameters parameters; | |
321 final Statement body; | |
322 final bool isGetter; | |
323 final bool isSetter; | |
324 | |
325 elements.FunctionElement element; | |
326 | |
327 FunctionExpression(this.parameters, this.body, | |
328 {this.name, | |
329 this.returnType, | |
330 this.isGetter: false, | |
331 this.isSetter: false}) { | |
332 // Function must have a name if it has a return type | |
333 assert(returnType == null || name != null); | |
334 } | |
335 } | |
336 | |
337 class ConstructorDefinition extends FunctionExpression { | |
338 final List<Initializer> initializers; | |
339 final bool isConst; | |
340 | |
341 ConstructorDefinition(Parameters parameters, Statement body, | |
342 this.initializers, String name, this.isConst) | |
343 : super(parameters, body, name: name); | |
344 } | |
345 | |
346 class Conditional extends Expression { | |
347 final Expression condition; | |
348 final Expression thenExpression; | |
349 final Expression elseExpression; | |
350 | |
351 Conditional(this.condition, this.thenExpression, this.elseExpression); | |
352 } | |
353 | |
354 /// An identifier expression. | |
355 /// The unparser does not concern itself with scoping rules, and it is the | |
356 /// responsibility of the AST creator to ensure that the identifier resolves | |
357 /// to the proper definition. | |
358 /// For the time being, this class is also used to reference static fields and | |
359 /// top-level variables that are qualified with a class and/or library name, | |
360 /// assuming the [element] is set. This is likely to change when the old backend | |
361 /// is replaced. | |
362 class Identifier extends Expression { | |
363 final String name; | |
364 | |
365 elements.Element element; | |
366 | |
367 Identifier(this.name); | |
368 | |
369 bool get assignable => true; | |
370 } | |
371 | |
372 class Literal extends Expression { | |
373 final values.PrimitiveConstantValue value; | |
374 | |
375 Literal(this.value); | |
376 } | |
377 | |
378 class LiteralList extends Expression { | |
379 final bool isConst; | |
380 final TypeAnnotation typeArgument; | |
381 final List<Expression> values; | |
382 | |
383 LiteralList(this.values, {this.typeArgument, this.isConst: false}); | |
384 } | |
385 | |
386 class LiteralMap extends Expression { | |
387 final bool isConst; | |
388 final List<TypeAnnotation> typeArguments; | |
389 final List<LiteralMapEntry> entries; | |
390 | |
391 LiteralMap(this.entries, {this.typeArguments, this.isConst: false}) { | |
392 assert(this.typeArguments == null || | |
393 this.typeArguments.length == 0 || | |
394 this.typeArguments.length == 2); | |
395 } | |
396 } | |
397 | |
398 class LiteralMapEntry extends Node { | |
399 final Expression key; | |
400 final Expression value; | |
401 | |
402 LiteralMapEntry(this.key, this.value); | |
403 } | |
404 | |
405 class LiteralSymbol extends Expression { | |
406 final String id; | |
407 | |
408 /// [id] should not include the # symbol | |
409 LiteralSymbol(this.id); | |
410 } | |
411 | |
412 /// A type literal. This is distinct from [Identifier] since the unparser | |
413 /// needs to this distinguish a static invocation from a method invocation | |
414 /// on a type literal. | |
415 class LiteralType extends Expression { | |
416 final String name; | |
417 | |
418 types.DartType type; | |
419 | |
420 LiteralType(this.name); | |
421 } | |
422 | |
423 /// Reference to a type variable. | |
424 /// This is distinct from [Identifier] since the unparser needs to this | |
425 /// distinguish a function invocation `T()` from a type variable invocation | |
426 /// `(T)()` (the latter is invalid, but must be generated anyway). | |
427 class ReifyTypeVar extends Expression { | |
428 final String name; | |
429 | |
430 elements.TypeVariableElement element; | |
431 | |
432 ReifyTypeVar(this.name); | |
433 } | |
434 | |
435 /// StringConcat is used in place of string interpolation and juxtaposition. | |
436 /// Semantically, each subexpression is evaluated and converted to a string | |
437 /// by `toString()`. These string are then concatenated and returned. | |
438 /// StringConcat unparses to a string literal, possibly with interpolations. | |
439 /// The unparser will flatten nested StringConcats. | |
440 /// A StringConcat node may have any number of children, including zero and one. | |
441 class StringConcat extends Expression { | |
442 final List<Expression> expressions; | |
443 | |
444 StringConcat(this.expressions); | |
445 } | |
446 | |
447 /// Expression of form `e.f`. | |
448 class FieldExpression extends Expression { | |
449 final Receiver object; | |
450 final String fieldName; | |
451 | |
452 FieldExpression(this.object, this.fieldName); | |
453 | |
454 bool get assignable => true; | |
455 } | |
456 | |
457 /// Expression of form `e1[e2]`. | |
458 class IndexExpression extends Expression { | |
459 final Receiver object; | |
460 final Expression index; | |
461 | |
462 IndexExpression(this.object, this.index); | |
463 | |
464 bool get assignable => true; | |
465 } | |
466 | |
467 /// Expression of form `e(..)` | |
468 /// Note that if [callee] is a [FieldExpression] this will translate into | |
469 /// `(e.f)(..)` and not `e.f(..)`. Use a [CallMethod] to generate | |
470 /// the latter type of expression. | |
471 class CallFunction extends Expression { | |
472 final Expression callee; | |
473 final List<Argument> arguments; | |
474 | |
475 CallFunction(this.callee, this.arguments); | |
476 } | |
477 | |
478 /// Expression of form `e.f(..)`. | |
479 class CallMethod extends Expression { | |
480 final Receiver object; | |
481 final String methodName; | |
482 final List<Argument> arguments; | |
483 | |
484 CallMethod(this.object, this.methodName, this.arguments); | |
485 } | |
486 | |
487 /// Expression of form `new T(..)`, `new T.f(..)`, `const T(..)`, | |
488 /// or `const T.f(..)`. | |
489 class CallNew extends Expression { | |
490 final bool isConst; | |
491 final TypeAnnotation type; | |
492 final String constructorName; | |
493 final List<Argument> arguments; | |
494 | |
495 elements.FunctionElement constructor; | |
496 types.DartType dartType; | |
497 | |
498 CallNew(this.type, this.arguments, | |
499 {this.constructorName, this.isConst: false}); | |
500 } | |
501 | |
502 /// Expression of form `T.f(..)`. | |
503 class CallStatic extends Expression { | |
504 final String className; | |
505 final String methodName; | |
506 final List<Argument> arguments; | |
507 | |
508 elements.Element element; | |
509 | |
510 CallStatic(this.className, this.methodName, this.arguments); | |
511 } | |
512 | |
513 /// Expression of form `!e` or `-e` or `~e`. | |
514 class UnaryOperator extends Expression { | |
515 final String operatorName; | |
516 final Receiver operand; | |
517 | |
518 UnaryOperator(this.operatorName, this.operand) { | |
519 assert(isUnaryOperator(operatorName)); | |
520 } | |
521 } | |
522 | |
523 /// Expression of form `e1 + e2`, `e1 - e2`, etc. | |
524 /// This node also represents application of the logical operators && and ||. | |
525 class BinaryOperator extends Expression { | |
526 final Receiver left; | |
527 final String operator; | |
528 final Expression right; | |
529 | |
530 BinaryOperator(this.left, this.operator, this.right) { | |
531 assert(isBinaryOperator(operator)); | |
532 } | |
533 } | |
534 | |
535 /// Expression of form `e is T` or `e is! T` or `e as T`. | |
536 class TypeOperator extends Expression { | |
537 final Expression expression; | |
538 final String operator; | |
539 final TypeAnnotation type; | |
540 | |
541 TypeOperator(this.expression, this.operator, this.type) { | |
542 assert(operator == 'is' || operator == 'as' || operator == 'is!'); | |
543 } | |
544 } | |
545 | |
546 class Increment extends Expression { | |
547 final Expression expression; | |
548 final String operator; | |
549 final bool isPrefix; | |
550 | |
551 Increment(this.expression, this.operator, this.isPrefix) { | |
552 assert(operator == '++' || operator == '--'); | |
553 assert(expression.assignable); | |
554 } | |
555 | |
556 Increment.prefix(Expression expression, String operator) | |
557 : this(expression, operator, true); | |
558 | |
559 Increment.postfix(Expression expression, String operator) | |
560 : this(expression, operator, false); | |
561 } | |
562 | |
563 class Assignment extends Expression { | |
564 static final _operators = new Set.from([ | |
565 '=', | |
566 '|=', | |
567 '^=', | |
568 '&=', | |
569 '<<=', | |
570 '>>=', | |
571 '+=', | |
572 '-=', | |
573 '*=', | |
574 '/=', | |
575 '%=', | |
576 '~/=' | |
577 ]); | |
578 | |
579 final Expression left; | |
580 final String operator; | |
581 final Expression right; | |
582 | |
583 Assignment(this.left, this.operator, this.right) { | |
584 assert(_operators.contains(operator)); | |
585 assert(left.assignable); | |
586 } | |
587 } | |
588 | |
589 class Throw extends Expression { | |
590 final Expression expression; | |
591 | |
592 Throw(this.expression); | |
593 } | |
594 | |
595 class This extends Expression { | |
596 static final This _instance = new This._create(); | |
597 | |
598 factory This() => _instance; | |
599 This._create(); | |
600 } | |
601 | |
602 // UNPARSER | |
603 | |
604 bool isUnaryOperator(String op) { | |
605 return op == '!' || op == '-' || op == '~'; | |
606 } | |
607 | |
608 bool isBinaryOperator(String op) { | |
609 return BINARY_PRECEDENCE.containsKey(op); | |
610 } | |
611 | |
612 /// True if the given operator can be converted to a compound assignment. | |
613 bool isCompoundableOperator(String op) { | |
614 switch (BINARY_PRECEDENCE[op]) { | |
615 case BITWISE_OR: | |
616 case BITWISE_XOR: | |
617 case BITWISE_AND: | |
618 case SHIFT: | |
619 case ADDITIVE: | |
620 case MULTIPLICATIVE: | |
621 return true; | |
622 default: | |
623 return false; | |
624 } | |
625 } | |
626 | |
627 // Precedence levels | |
628 const int EXPRESSION = 1; | |
629 const int CONDITIONAL = 2; | |
630 const int LOGICAL_OR = 3; | |
631 const int LOGICAL_AND = 4; | |
632 const int EQUALITY = 6; | |
633 const int RELATIONAL = 7; | |
634 const int BITWISE_OR = 8; | |
635 const int BITWISE_XOR = 9; | |
636 const int BITWISE_AND = 10; | |
637 const int SHIFT = 11; | |
638 const int ADDITIVE = 12; | |
639 const int MULTIPLICATIVE = 13; | |
640 const int UNARY = 14; | |
641 const int POSTFIX_INCREMENT = 15; | |
642 const int TYPE_LITERAL = 19; | |
643 const int PRIMARY = 20; | |
644 | |
645 /// Precedence level required for the callee in a [FunctionCall]. | |
646 const int CALLEE = 21; | |
647 | |
648 const Map<String, int> BINARY_PRECEDENCE = const { | |
649 '&&': LOGICAL_AND, | |
650 '||': LOGICAL_OR, | |
651 '==': EQUALITY, | |
652 '!=': EQUALITY, | |
653 '>': RELATIONAL, | |
654 '>=': RELATIONAL, | |
655 '<': RELATIONAL, | |
656 '<=': RELATIONAL, | |
657 '|': BITWISE_OR, | |
658 '^': BITWISE_XOR, | |
659 '&': BITWISE_AND, | |
660 '>>': SHIFT, | |
661 '<<': SHIFT, | |
662 '+': ADDITIVE, | |
663 '-': ADDITIVE, | |
664 '*': MULTIPLICATIVE, | |
665 '%': MULTIPLICATIVE, | |
666 '/': MULTIPLICATIVE, | |
667 '~/': MULTIPLICATIVE, | |
668 }; | |
669 | |
670 /// Return true if binary operators with the given precedence level are | |
671 /// (left) associative. False if they are non-associative. | |
672 bool isAssociativeBinaryOperator(int precedence) { | |
673 return precedence != EQUALITY && precedence != RELATIONAL; | |
674 } | |
675 | |
676 /// True if [x] is a letter, digit, or underscore. | |
677 /// Such characters may not follow a shorthand string interpolation. | |
678 bool isIdentifierPartNoDollar(dynamic x) { | |
679 if (x is! int) { | |
680 return false; | |
681 } | |
682 return (characters.$0 <= x && x <= characters.$9) || | |
683 (characters.$A <= x && x <= characters.$Z) || | |
684 (characters.$a <= x && x <= characters.$z) || | |
685 (x == characters.$_); | |
686 } | |
687 | |
688 /// The unparser will apply the following syntactic rewritings: | |
689 /// Use short-hand function returns: | |
690 /// foo(){return E} ==> foo() => E; | |
691 /// Remove empty else branch: | |
692 /// if (E) S else ; ==> if (E) S | |
693 /// Flatten nested blocks: | |
694 /// {S; {S; S}; S} ==> {S; S; S; S} | |
695 /// Remove empty statements from block: | |
696 /// {S; ; S} ==> {S; S} | |
697 /// Unfold singleton blocks: | |
698 /// {S} ==> S | |
699 /// Empty block to empty statement: | |
700 /// {} ==> ; | |
701 /// Introduce not-equals operator: | |
702 /// !(E == E) ==> E != E | |
703 /// Introduce is-not operator: | |
704 /// !(E is T) ==> E is!T | |
705 /// | |
706 /// The following transformations will NOT be applied here: | |
707 /// Use implicit this: | |
708 /// this.foo ==> foo (preconditions too complex for unparser) | |
709 /// Merge adjacent variable definitions: | |
710 /// var x; var y ==> var x,y; (hoisting will be done elsewhere) | |
711 /// Merge adjacent labels: | |
712 /// foo: bar: S ==> foobar: S (scoping is categorically ignored) | |
713 /// | |
714 /// The following transformations might be applied here in the future: | |
715 /// Use implicit dynamic types: | |
716 /// dynamic x = E ==> var x = E | |
717 /// <dynamic>[] ==> [] | |
718 class Unparser { | |
719 StringSink output; | |
720 | |
721 Unparser(this.output); | |
722 | |
723 void write(String s) { | |
724 output.write(s); | |
725 } | |
726 | |
727 /// Outputs each element from [items] separated by [separator]. | |
728 /// The actual printing must be performed by the [callback]. | |
729 void writeEach(String separator, Iterable items, void callback(any)) { | |
730 bool first = true; | |
731 for (var x in items) { | |
732 if (first) { | |
733 first = false; | |
734 } else { | |
735 write(separator); | |
736 } | |
737 callback(x); | |
738 } | |
739 } | |
740 | |
741 void writeOperator(String operator) { | |
742 write(" "); | |
743 write(operator); | |
744 write(" "); | |
745 } | |
746 | |
747 /// Unfolds singleton blocks and returns the inner statement. | |
748 /// If an empty block is found, the [EmptyStatement] is returned instead. | |
749 Statement unfoldBlocks(Statement stmt) { | |
750 while (stmt is Block && stmt.statements.length == 1) { | |
751 Statement inner = (stmt as Block).statements[0]; | |
752 if (definesVariable(inner)) { | |
753 return stmt; // Do not unfold block with lexical scope. | |
754 } | |
755 stmt = inner; | |
756 } | |
757 if (stmt is Block && stmt.statements.length == 0) | |
758 return new EmptyStatement(); | |
759 return stmt; | |
760 } | |
761 | |
762 void writeArgument(Argument arg) { | |
763 if (arg is NamedArgument) { | |
764 write(arg.name); | |
765 write(':'); | |
766 writeExpression(arg.expression); | |
767 } else { | |
768 writeExpression(arg); | |
769 } | |
770 } | |
771 | |
772 /// Prints the expression [e]. | |
773 void writeExpression(Expression e) { | |
774 writeExp(e, EXPRESSION); | |
775 } | |
776 | |
777 /// Prints [e] as an expression with precedence of at least [minPrecedence], | |
778 /// using parentheses if necessary to raise the precedence level. | |
779 /// Abusing terminology slightly, the function accepts a [Receiver] which | |
780 /// may also be the [SuperReceiver] object. | |
781 void writeExp(Receiver e, int minPrecedence, {beginStmt: false}) { | |
782 void withPrecedence(int actual, void action()) { | |
783 if (actual < minPrecedence) { | |
784 write("("); | |
785 beginStmt = false; | |
786 action(); | |
787 write(")"); | |
788 } else { | |
789 action(); | |
790 } | |
791 } | |
792 if (e is SuperReceiver) { | |
793 write('super'); | |
794 } else if (e is FunctionExpression) { | |
795 assert(!e.isGetter && !e.isSetter); | |
796 Statement stmt = unfoldBlocks(e.body); | |
797 int precedence = stmt is Return ? EXPRESSION : PRIMARY; | |
798 withPrecedence(precedence, () { | |
799 // A named function expression at the beginning of a statement | |
800 // can be mistaken for a function declaration. | |
801 // (Note: Functions with a return type also have a name) | |
802 bool needParen = beginStmt && e.name != null; | |
803 if (needParen) { | |
804 write('('); | |
805 } | |
806 if (e.returnType != null) { | |
807 writeType(e.returnType); | |
808 write(' '); | |
809 } | |
810 if (e.name != null) { | |
811 write(e.name); | |
812 } | |
813 writeParameters(e.parameters); | |
814 if (stmt is Return) { | |
815 write('=> '); | |
816 writeExp(stmt.expression, EXPRESSION); | |
817 } else { | |
818 writeBlock(stmt); | |
819 } | |
820 if (needParen) { | |
821 write(')'); | |
822 } | |
823 }); | |
824 } else if (e is Conditional) { | |
825 withPrecedence(CONDITIONAL, () { | |
826 writeExp(e.condition, LOGICAL_OR, beginStmt: beginStmt); | |
827 write(' ? '); | |
828 writeExp(e.thenExpression, EXPRESSION); | |
829 write(' : '); | |
830 writeExp(e.elseExpression, EXPRESSION); | |
831 }); | |
832 } else if (e is Identifier) { | |
833 write(e.name); | |
834 } else if (e is Literal) { | |
835 if (e.value.isString) { | |
836 writeStringLiteral(e); | |
837 } else if (e.value.isDouble) { | |
838 double v = e.value.primitiveValue; | |
839 if (v == double.INFINITY) { | |
840 withPrecedence(MULTIPLICATIVE, () { | |
841 write('1/0.0'); | |
842 }); | |
843 } else if (v == double.NEGATIVE_INFINITY) { | |
844 withPrecedence(MULTIPLICATIVE, () { | |
845 write('-1/0.0'); | |
846 }); | |
847 } else if (v.isNaN) { | |
848 withPrecedence(MULTIPLICATIVE, () { | |
849 write('0/0.0'); | |
850 }); | |
851 } else { | |
852 write(v.toString()); | |
853 } | |
854 } else { | |
855 write(e.value.toDartText()); | |
856 } | |
857 } else if (e is LiteralList) { | |
858 if (e.isConst) { | |
859 write(' const '); | |
860 } | |
861 if (e.typeArgument != null) { | |
862 write('<'); | |
863 writeType(e.typeArgument); | |
864 write('>'); | |
865 } | |
866 write('['); | |
867 writeEach(',', e.values, writeExpression); | |
868 write(']'); | |
869 } else if (e is LiteralMap) { | |
870 // The curly brace can be mistaken for a block statement if we | |
871 // are at the beginning of a statement. | |
872 bool needParen = beginStmt; | |
873 if (e.isConst) { | |
874 write(' const '); | |
875 needParen = false; | |
876 } | |
877 if (e.typeArguments.length > 0) { | |
878 write('<'); | |
879 writeEach(',', e.typeArguments, writeType); | |
880 write('>'); | |
881 needParen = false; | |
882 } | |
883 if (needParen) { | |
884 write('('); | |
885 } | |
886 write('{'); | |
887 writeEach(',', e.entries, (LiteralMapEntry en) { | |
888 writeExp(en.key, EXPRESSION); | |
889 write(' : '); | |
890 writeExp(en.value, EXPRESSION); | |
891 }); | |
892 write('}'); | |
893 if (needParen) { | |
894 write(')'); | |
895 } | |
896 } else if (e is LiteralSymbol) { | |
897 write('#'); | |
898 write(e.id); | |
899 } else if (e is LiteralType) { | |
900 withPrecedence(TYPE_LITERAL, () { | |
901 write(e.name); | |
902 }); | |
903 } else if (e is ReifyTypeVar) { | |
904 withPrecedence(PRIMARY, () { | |
905 write(e.name); | |
906 }); | |
907 } else if (e is StringConcat) { | |
908 writeStringLiteral(e); | |
909 } else if (e is UnaryOperator) { | |
910 Receiver operand = e.operand; | |
911 // !(x == y) ==> x != y. | |
912 if (e.operatorName == '!' && | |
913 operand is BinaryOperator && | |
914 operand.operator == '==') { | |
915 withPrecedence(EQUALITY, () { | |
916 writeExp(operand.left, RELATIONAL); | |
917 writeOperator('!='); | |
918 writeExp(operand.right, RELATIONAL); | |
919 }); | |
920 } | |
921 // !(x is T) ==> x is!T | |
922 else if (e.operatorName == '!' && | |
923 operand is TypeOperator && | |
924 operand.operator == 'is') { | |
925 withPrecedence(RELATIONAL, () { | |
926 writeExp(operand.expression, BITWISE_OR, beginStmt: beginStmt); | |
927 write(' is!'); | |
928 writeType(operand.type); | |
929 }); | |
930 } else { | |
931 withPrecedence(UNARY, () { | |
932 writeOperator(e.operatorName); | |
933 writeExp(e.operand, UNARY); | |
934 }); | |
935 } | |
936 } else if (e is BinaryOperator) { | |
937 int precedence = BINARY_PRECEDENCE[e.operator]; | |
938 withPrecedence(precedence, () { | |
939 // All binary operators are left-associative or non-associative. | |
940 // For each operand, we use either the same precedence level as | |
941 // the current operator, or one higher. | |
942 int deltaLeft = isAssociativeBinaryOperator(precedence) ? 0 : 1; | |
943 writeExp(e.left, precedence + deltaLeft, beginStmt: beginStmt); | |
944 writeOperator(e.operator); | |
945 writeExp(e.right, precedence + 1); | |
946 }); | |
947 } else if (e is TypeOperator) { | |
948 withPrecedence(RELATIONAL, () { | |
949 writeExp(e.expression, BITWISE_OR, beginStmt: beginStmt); | |
950 write(' '); | |
951 write(e.operator); | |
952 write(' '); | |
953 writeType(e.type); | |
954 }); | |
955 } else if (e is Assignment) { | |
956 withPrecedence(EXPRESSION, () { | |
957 writeExp(e.left, PRIMARY, beginStmt: beginStmt); | |
958 writeOperator(e.operator); | |
959 writeExp(e.right, EXPRESSION); | |
960 }); | |
961 } else if (e is FieldExpression) { | |
962 withPrecedence(PRIMARY, () { | |
963 writeExp(e.object, PRIMARY, beginStmt: beginStmt); | |
964 write('.'); | |
965 write(e.fieldName); | |
966 }); | |
967 } else if (e is IndexExpression) { | |
968 withPrecedence(CALLEE, () { | |
969 writeExp(e.object, PRIMARY, beginStmt: beginStmt); | |
970 write('['); | |
971 writeExp(e.index, EXPRESSION); | |
972 write(']'); | |
973 }); | |
974 } else if (e is CallFunction) { | |
975 withPrecedence(CALLEE, () { | |
976 writeExp(e.callee, CALLEE, beginStmt: beginStmt); | |
977 write('('); | |
978 writeEach(',', e.arguments, writeArgument); | |
979 write(')'); | |
980 }); | |
981 } else if (e is CallMethod) { | |
982 withPrecedence(CALLEE, () { | |
983 writeExp(e.object, PRIMARY, beginStmt: beginStmt); | |
984 write('.'); | |
985 write(e.methodName); | |
986 write('('); | |
987 writeEach(',', e.arguments, writeArgument); | |
988 write(')'); | |
989 }); | |
990 } else if (e is CallNew) { | |
991 withPrecedence(CALLEE, () { | |
992 write(' '); | |
993 write(e.isConst ? 'const ' : 'new '); | |
994 writeType(e.type); | |
995 if (e.constructorName != null) { | |
996 write('.'); | |
997 write(e.constructorName); | |
998 } | |
999 write('('); | |
1000 writeEach(',', e.arguments, writeArgument); | |
1001 write(')'); | |
1002 }); | |
1003 } else if (e is CallStatic) { | |
1004 withPrecedence(CALLEE, () { | |
1005 write(e.className); | |
1006 write('.'); | |
1007 write(e.methodName); | |
1008 write('('); | |
1009 writeEach(',', e.arguments, writeArgument); | |
1010 write(')'); | |
1011 }); | |
1012 } else if (e is Increment) { | |
1013 int precedence = e.isPrefix ? UNARY : POSTFIX_INCREMENT; | |
1014 withPrecedence(precedence, () { | |
1015 if (e.isPrefix) { | |
1016 write(e.operator); | |
1017 writeExp(e.expression, PRIMARY); | |
1018 } else { | |
1019 writeExp(e.expression, PRIMARY, beginStmt: beginStmt); | |
1020 write(e.operator); | |
1021 } | |
1022 }); | |
1023 } else if (e is Throw) { | |
1024 withPrecedence(EXPRESSION, () { | |
1025 write('throw '); | |
1026 writeExp(e.expression, EXPRESSION); | |
1027 }); | |
1028 } else if (e is This) { | |
1029 write('this'); | |
1030 } else { | |
1031 throw "Unexpected expression: $e"; | |
1032 } | |
1033 } | |
1034 | |
1035 void writeParameters(Parameters params) { | |
1036 write('('); | |
1037 writeEach(',', params.requiredParameters, (Parameter p) { | |
1038 if (p.type != null) { | |
1039 writeType(p.type); | |
1040 write(' '); | |
1041 } | |
1042 write(p.name); | |
1043 if (p.parameters != null) { | |
1044 writeParameters(p.parameters); | |
1045 } | |
1046 }); | |
1047 if (params.hasOptionalParameters) { | |
1048 if (params.requiredParameters.length > 0) { | |
1049 write(','); | |
1050 } | |
1051 write(params.hasNamedParameters ? '{' : '['); | |
1052 writeEach(',', params.optionalParameters, (Parameter p) { | |
1053 if (p.type != null) { | |
1054 writeType(p.type); | |
1055 write(' '); | |
1056 } | |
1057 write(p.name); | |
1058 if (p.parameters != null) { | |
1059 writeParameters(p.parameters); | |
1060 } | |
1061 if (p.defaultValue != null) { | |
1062 write(params.hasNamedParameters ? ':' : '='); | |
1063 writeExp(p.defaultValue, EXPRESSION); | |
1064 } | |
1065 }); | |
1066 write(params.hasNamedParameters ? '}' : ']'); | |
1067 } | |
1068 write(')'); | |
1069 } | |
1070 | |
1071 void writeStatement(Statement stmt, {bool shortIf: true}) { | |
1072 stmt = unfoldBlocks(stmt); | |
1073 if (stmt is Block) { | |
1074 write('{'); | |
1075 stmt.statements.forEach(writeBlockMember); | |
1076 write('}'); | |
1077 } else if (stmt is Break) { | |
1078 write('break'); | |
1079 if (stmt.label != null) { | |
1080 write(' '); | |
1081 write(stmt.label); | |
1082 } | |
1083 write(';'); | |
1084 } else if (stmt is Continue) { | |
1085 write('continue'); | |
1086 if (stmt.label != null) { | |
1087 write(' '); | |
1088 write(stmt.label); | |
1089 } | |
1090 write(';'); | |
1091 } else if (stmt is EmptyStatement) { | |
1092 write(';'); | |
1093 } else if (stmt is ExpressionStatement) { | |
1094 writeExp(stmt.expression, EXPRESSION, beginStmt: true); | |
1095 write(';'); | |
1096 } else if (stmt is For) { | |
1097 write('for('); | |
1098 Node init = stmt.initializer; | |
1099 if (init is Expression) { | |
1100 writeExp(init, EXPRESSION); | |
1101 } else if (init is VariableDeclarations) { | |
1102 writeVariableDefinitions(init); | |
1103 } | |
1104 write(';'); | |
1105 if (stmt.condition != null) { | |
1106 writeExp(stmt.condition, EXPRESSION); | |
1107 } | |
1108 write(';'); | |
1109 writeEach(',', stmt.updates, writeExpression); | |
1110 write(')'); | |
1111 writeStatement(stmt.body, shortIf: shortIf); | |
1112 } else if (stmt is ForIn) { | |
1113 write('for('); | |
1114 Node lhv = stmt.leftHandValue; | |
1115 if (lhv is Identifier) { | |
1116 write(lhv.name); | |
1117 } else { | |
1118 writeVariableDefinitions(lhv as VariableDeclarations); | |
1119 } | |
1120 write(' in '); | |
1121 writeExp(stmt.expression, EXPRESSION); | |
1122 write(')'); | |
1123 writeStatement(stmt.body, shortIf: shortIf); | |
1124 } else if (stmt is While) { | |
1125 write('while('); | |
1126 writeExp(stmt.condition, EXPRESSION); | |
1127 write(')'); | |
1128 writeStatement(stmt.body, shortIf: shortIf); | |
1129 } else if (stmt is DoWhile) { | |
1130 write('do '); | |
1131 writeStatement(stmt.body); | |
1132 write('while('); | |
1133 writeExp(stmt.condition, EXPRESSION); | |
1134 write(');'); | |
1135 } else if (stmt is If) { | |
1136 // if (E) S else ; ==> if (E) S | |
1137 Statement elsePart = unfoldBlocks(stmt.elseStatement); | |
1138 if (elsePart is EmptyStatement) { | |
1139 elsePart = null; | |
1140 } | |
1141 if (!shortIf && elsePart == null) { | |
1142 write('{'); | |
1143 } | |
1144 write('if('); | |
1145 writeExp(stmt.condition, EXPRESSION); | |
1146 write(')'); | |
1147 writeStatement(stmt.thenStatement, shortIf: elsePart == null); | |
1148 if (elsePart != null) { | |
1149 write('else '); | |
1150 writeStatement(elsePart, shortIf: shortIf); | |
1151 } | |
1152 if (!shortIf && elsePart == null) { | |
1153 write('}'); | |
1154 } | |
1155 } else if (stmt is LabeledStatement) { | |
1156 write(stmt.label); | |
1157 write(':'); | |
1158 writeStatement(stmt.statement, shortIf: shortIf); | |
1159 } else if (stmt is Rethrow) { | |
1160 write('rethrow;'); | |
1161 } else if (stmt is Return) { | |
1162 write('return'); | |
1163 if (stmt.expression != null) { | |
1164 write(' '); | |
1165 writeExp(stmt.expression, EXPRESSION); | |
1166 } | |
1167 write(';'); | |
1168 } else if (stmt is Switch) { | |
1169 write('switch('); | |
1170 writeExp(stmt.expression, EXPRESSION); | |
1171 write('){'); | |
1172 for (SwitchCase caze in stmt.cases) { | |
1173 if (caze.isDefaultCase) { | |
1174 write('default:'); | |
1175 } else { | |
1176 for (Expression exp in caze.expressions) { | |
1177 write('case '); | |
1178 writeExp(exp, EXPRESSION); | |
1179 write(':'); | |
1180 } | |
1181 } | |
1182 if (caze.statements.isEmpty) { | |
1183 write(';'); // Prevent fall-through. | |
1184 } else { | |
1185 caze.statements.forEach(writeBlockMember); | |
1186 } | |
1187 } | |
1188 write('}'); | |
1189 } else if (stmt is Try) { | |
1190 write('try'); | |
1191 writeBlock(stmt.tryBlock); | |
1192 for (CatchBlock block in stmt.catchBlocks) { | |
1193 if (block.onType != null) { | |
1194 write('on '); | |
1195 writeType(block.onType); | |
1196 } | |
1197 if (block.exceptionVar != null) { | |
1198 write('catch('); | |
1199 write(block.exceptionVar.name); | |
1200 if (block.stackVar != null) { | |
1201 write(','); | |
1202 write(block.stackVar.name); | |
1203 } | |
1204 write(')'); | |
1205 } | |
1206 writeBlock(block.body); | |
1207 } | |
1208 if (stmt.finallyBlock != null) { | |
1209 write('finally'); | |
1210 writeBlock(stmt.finallyBlock); | |
1211 } | |
1212 } else if (stmt is VariableDeclarations) { | |
1213 writeVariableDefinitions(stmt); | |
1214 write(';'); | |
1215 } else if (stmt is FunctionDeclaration) { | |
1216 assert(!stmt.function.isGetter && !stmt.function.isSetter); | |
1217 if (stmt.returnType != null) { | |
1218 writeType(stmt.returnType); | |
1219 write(' '); | |
1220 } | |
1221 write(stmt.name); | |
1222 writeParameters(stmt.parameters); | |
1223 Statement body = unfoldBlocks(stmt.body); | |
1224 if (body is Return) { | |
1225 write('=> '); | |
1226 writeExp(body.expression, EXPRESSION); | |
1227 write(';'); | |
1228 } else { | |
1229 writeBlock(body); | |
1230 } | |
1231 } else { | |
1232 throw "Unexpected statement: $stmt"; | |
1233 } | |
1234 } | |
1235 | |
1236 /// Writes a variable definition statement without the trailing semicolon | |
1237 void writeVariableDefinitions(VariableDeclarations vds) { | |
1238 if (vds.isConst) | |
1239 write('const '); | |
1240 else if (vds.isFinal) write('final '); | |
1241 if (vds.type != null) { | |
1242 writeType(vds.type); | |
1243 write(' '); | |
1244 } | |
1245 if (!vds.isConst && !vds.isFinal && vds.type == null) { | |
1246 write('var '); | |
1247 } | |
1248 writeEach(',', vds.declarations, (VariableDeclaration vd) { | |
1249 write(vd.name); | |
1250 if (vd.initializer != null) { | |
1251 write('='); | |
1252 writeExp(vd.initializer, EXPRESSION); | |
1253 } | |
1254 }); | |
1255 } | |
1256 | |
1257 /// True of statements that introduce variables in the scope of their | |
1258 /// surrounding block. Blocks containing such statements cannot be unfolded. | |
1259 static bool definesVariable(Statement s) { | |
1260 return s is VariableDeclarations || s is FunctionDeclaration; | |
1261 } | |
1262 | |
1263 /// Writes the given statement in a context where only blocks are allowed. | |
1264 void writeBlock(Statement stmt) { | |
1265 if (stmt is Block) { | |
1266 writeStatement(stmt); | |
1267 } else { | |
1268 write('{'); | |
1269 writeBlockMember(stmt); | |
1270 write('}'); | |
1271 } | |
1272 } | |
1273 | |
1274 /// Outputs a statement that is a member of a block statement (or a similar | |
1275 /// sequence of statements, such as in switch statement). | |
1276 /// This will flatten blocks and skip empty statement. | |
1277 void writeBlockMember(Statement stmt) { | |
1278 if (stmt is Block && !stmt.statements.any(definesVariable)) { | |
1279 stmt.statements.forEach(writeBlockMember); | |
1280 } else if (stmt is EmptyStatement) { | |
1281 // do nothing | |
1282 } else { | |
1283 writeStatement(stmt); | |
1284 } | |
1285 } | |
1286 | |
1287 void writeType(TypeAnnotation type) { | |
1288 write(type.name); | |
1289 if (type.typeArguments != null && type.typeArguments.length > 0) { | |
1290 write('<'); | |
1291 writeEach(',', type.typeArguments, writeType); | |
1292 write('>'); | |
1293 } | |
1294 } | |
1295 | |
1296 /// A list of string quotings that the printer may use to quote strings. | |
1297 // Ignore multiline quotings for now. Would need to make sure that no | |
1298 // newline (potentially prefixed by whitespace) follows the quoting. | |
1299 static const _QUOTINGS = const <tree.StringQuoting>[ | |
1300 const tree.StringQuoting(characters.$DQ, raw: false, leftQuoteLength: 1), | |
1301 const tree.StringQuoting(characters.$DQ, raw: true, leftQuoteLength: 1), | |
1302 const tree.StringQuoting(characters.$SQ, raw: false, leftQuoteLength: 1), | |
1303 const tree.StringQuoting(characters.$SQ, raw: true, leftQuoteLength: 1), | |
1304 ]; | |
1305 | |
1306 static StringLiteralOutput analyzeStringLiteral(Expression node) { | |
1307 // Flatten the StringConcat tree. | |
1308 List parts = []; // Expression or int (char node) | |
1309 void collectParts(Expression e) { | |
1310 if (e is StringConcat) { | |
1311 e.expressions.forEach(collectParts); | |
1312 } else if (e is Literal && e.value.isString) { | |
1313 for (int char in e.value.primitiveValue) { | |
1314 parts.add(char); | |
1315 } | |
1316 } else { | |
1317 parts.add(e); | |
1318 } | |
1319 } | |
1320 collectParts(node); | |
1321 | |
1322 // We use a dynamic algorithm to compute the optimal way of printing | |
1323 // the string literal. | |
1324 // | |
1325 // Using string juxtapositions, it is possible to switch from one quoting | |
1326 // to another, e.g. the constant "''''" '""""' uses this trick. | |
1327 // | |
1328 // As we move through the string from left to right, we maintain a strategy | |
1329 // for each StringQuoting Q, denoting the best way to print the current | |
1330 // prefix so that we end with a string literal quoted with Q. | |
1331 // At every step, each strategy is either: | |
1332 // 1) Updated to include the cost of printing the next character. | |
1333 // 2) Abandoned because it is cheaper to use another strategy as prefix, | |
1334 // and then switching quotation using a juxtaposition. | |
1335 | |
1336 int getQuoteCost(tree.StringQuoting quot) { | |
1337 return quot.leftQuoteLength + quot.rightQuoteLength; | |
1338 } | |
1339 | |
1340 // Create initial scores for each StringQuoting and index them | |
1341 // into raw/non-raw and single-quote/double-quote. | |
1342 List<OpenStringChunk> best = <OpenStringChunk>[]; | |
1343 List<int> raws = <int>[]; | |
1344 List<int> nonRaws = <int>[]; | |
1345 List<int> sqs = <int>[]; | |
1346 List<int> dqs = <int>[]; | |
1347 for (tree.StringQuoting q in _QUOTINGS) { | |
1348 OpenStringChunk chunk = new OpenStringChunk(null, q, getQuoteCost(q)); | |
1349 int index = best.length; | |
1350 best.add(chunk); | |
1351 | |
1352 if (q.raw) { | |
1353 raws.add(index); | |
1354 } else { | |
1355 nonRaws.add(index); | |
1356 } | |
1357 if (q.quote == characters.$SQ) { | |
1358 sqs.add(index); | |
1359 } else { | |
1360 dqs.add(index); | |
1361 } | |
1362 } | |
1363 | |
1364 /// Applies additional cost to each track in [penalized], and considers | |
1365 /// switching from each [penalized] to a [nonPenalized] track. | |
1366 void penalize(List<int> penalized, List<int> nonPenalized, int endIndex, | |
1367 num cost(tree.StringQuoting q)) { | |
1368 for (int j in penalized) { | |
1369 // Check if another track can benefit from switching from this track. | |
1370 for (int k in nonPenalized) { | |
1371 num newCost = best[j].cost + | |
1372 1 // Whitespace in string juxtaposition | |
1373 + | |
1374 getQuoteCost(best[k].quoting); | |
1375 if (newCost < best[k].cost) { | |
1376 best[k] = new OpenStringChunk( | |
1377 best[j].end(endIndex), best[k].quoting, newCost); | |
1378 } | |
1379 } | |
1380 best[j].cost += cost(best[j].quoting); | |
1381 } | |
1382 } | |
1383 | |
1384 // Iterate through the string and update the score for each StringQuoting. | |
1385 for (int i = 0; i < parts.length; i++) { | |
1386 var part = parts[i]; | |
1387 if (part is int) { | |
1388 int char = part; | |
1389 switch (char) { | |
1390 case characters.$$: | |
1391 case characters.$BACKSLASH: | |
1392 penalize(nonRaws, raws, i, (q) => 1); | |
1393 break; | |
1394 case characters.$DQ: | |
1395 penalize(dqs, sqs, i, (q) => q.raw ? double.INFINITY : 1); | |
1396 break; | |
1397 case characters.$SQ: | |
1398 penalize(sqs, dqs, i, (q) => q.raw ? double.INFINITY : 1); | |
1399 break; | |
1400 case characters.$LF: | |
1401 case characters.$CR: | |
1402 case characters.$FF: | |
1403 case characters.$BS: | |
1404 case characters.$VTAB: | |
1405 case characters.$TAB: | |
1406 case characters.$EOF: | |
1407 penalize(raws, nonRaws, i, (q) => double.INFINITY); | |
1408 break; | |
1409 } | |
1410 } else { | |
1411 // Penalize raw literals for string interpolation. | |
1412 penalize(raws, nonRaws, i, (q) => double.INFINITY); | |
1413 | |
1414 // Splitting a string can sometimes allow us to use a shorthand | |
1415 // string interpolation that would otherwise be illegal. | |
1416 // E.g. "...${foo}x..." -> "...$foo" 'x...' | |
1417 // If are other factors that make splitting advantageous, | |
1418 // we can gain even more by doing the split here. | |
1419 if (part is Identifier && | |
1420 !part.name.contains(r'$') && | |
1421 i + 1 < parts.length && | |
1422 isIdentifierPartNoDollar(parts[i + 1])) { | |
1423 for (int j in nonRaws) { | |
1424 for (int k = 0; k < best.length; k++) { | |
1425 num newCost = best[j].cost + | |
1426 1 // Whitespace in string juxtaposition | |
1427 - | |
1428 2 // Save two curly braces | |
1429 + | |
1430 getQuoteCost(best[k].quoting); | |
1431 if (newCost < best[k].cost) { | |
1432 best[k] = new OpenStringChunk( | |
1433 best[j].end(i + 1), best[k].quoting, newCost); | |
1434 } | |
1435 } | |
1436 } | |
1437 } | |
1438 } | |
1439 } | |
1440 | |
1441 // Select the cheapest strategy | |
1442 OpenStringChunk bestChunk = best[0]; | |
1443 for (OpenStringChunk chunk in best) { | |
1444 if (chunk.cost < bestChunk.cost) { | |
1445 bestChunk = chunk; | |
1446 } | |
1447 } | |
1448 | |
1449 return new StringLiteralOutput(parts, bestChunk.end(parts.length)); | |
1450 } | |
1451 | |
1452 void writeStringLiteral(Expression node) { | |
1453 StringLiteralOutput output = analyzeStringLiteral(node); | |
1454 List parts = output.parts; | |
1455 void printChunk(StringChunk chunk) { | |
1456 int startIndex; | |
1457 if (chunk.previous != null) { | |
1458 printChunk(chunk.previous); | |
1459 write(' '); // String juxtaposition requires a space between literals. | |
1460 startIndex = chunk.previous.endIndex; | |
1461 } else { | |
1462 startIndex = 0; | |
1463 } | |
1464 if (chunk.quoting.raw) { | |
1465 write('r'); | |
1466 } | |
1467 write(chunk.quoting.quoteChar); | |
1468 bool raw = chunk.quoting.raw; | |
1469 int quoteCode = chunk.quoting.quote; | |
1470 for (int i = startIndex; i < chunk.endIndex; i++) { | |
1471 var part = parts[i]; | |
1472 if (part is int) { | |
1473 int char = part; | |
1474 write(getEscapedCharacter(char, quoteCode, raw)); | |
1475 } else if (part is Identifier && | |
1476 !part.name.contains(r'$') && | |
1477 (i == chunk.endIndex - 1 || | |
1478 !isIdentifierPartNoDollar(parts[i + 1]))) { | |
1479 write(r'$'); | |
1480 write(part.name); | |
1481 } else { | |
1482 write(r'${'); | |
1483 writeExpression(part); | |
1484 write('}'); | |
1485 } | |
1486 } | |
1487 write(chunk.quoting.quoteChar); | |
1488 } | |
1489 printChunk(output.chunk); | |
1490 } | |
1491 | |
1492 static String getEscapedCharacter(int char, int quoteCode, bool raw) { | |
1493 switch (char) { | |
1494 case characters.$$: | |
1495 return raw ? r'$' : r'\$'; | |
1496 case characters.$BACKSLASH: | |
1497 return raw ? r'\' : r'\\'; | |
1498 case characters.$DQ: | |
1499 return quoteCode == char ? r'\"' : r'"'; | |
1500 case characters.$SQ: | |
1501 return quoteCode == char ? r"\'" : r"'"; | |
1502 case characters.$LF: | |
1503 return r'\n'; | |
1504 case characters.$CR: | |
1505 return r'\r'; | |
1506 case characters.$FF: | |
1507 return r'\f'; | |
1508 case characters.$BS: | |
1509 return r'\b'; | |
1510 case characters.$TAB: | |
1511 return r'\t'; | |
1512 case characters.$VTAB: | |
1513 return r'\v'; | |
1514 case characters.$EOF: | |
1515 return r'\x00'; | |
1516 default: | |
1517 return new String.fromCharCode(char); | |
1518 } | |
1519 } | |
1520 } | |
1521 | |
1522 /// The contents of a string literal together with a strategy for printing it. | |
1523 class StringLiteralOutput { | |
1524 /// Mix of [Expression] and `int`. Each expression is a string interpolation, | |
1525 /// and each `int` is the character code of a character in a string literal. | |
1526 final List parts; | |
1527 final StringChunk chunk; | |
1528 | |
1529 StringLiteralOutput(this.parts, this.chunk); | |
1530 } | |
1531 | |
1532 /// Strategy for printing a prefix of a string literal. | |
1533 /// A chunk represents the substring going from [:previous.endIndex:] to | |
1534 /// [endIndex] (or from 0 to [endIndex] if [previous] is null). | |
1535 class StringChunk { | |
1536 final StringChunk previous; | |
1537 final tree.StringQuoting quoting; | |
1538 final int endIndex; | |
1539 | |
1540 StringChunk(this.previous, this.quoting, this.endIndex); | |
1541 } | |
1542 | |
1543 /// [StringChunk] that has not yet been assigned an [endIndex]. | |
1544 /// It additionally has a [cost] denoting the number of auxilliary characters | |
1545 /// (quotes, spaces, etc) needed to print the literal using this strategy | |
1546 class OpenStringChunk { | |
1547 final StringChunk previous; | |
1548 final tree.StringQuoting quoting; | |
1549 num cost; | |
1550 | |
1551 OpenStringChunk(this.previous, this.quoting, this.cost); | |
1552 | |
1553 StringChunk end(int endIndex) { | |
1554 return new StringChunk(previous, quoting, endIndex); | |
1555 } | |
1556 } | |
OLD | NEW |