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