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

Side by Side Diff: sdk/lib/_internal/compiler/implementation/dart_backend/backend_ast_nodes.dart

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698