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

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

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

Powered by Google App Engine
This is Rietveld 408576698