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

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: Minor change: removed stack dump from test. 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
« no previous file with comments | « no previous file | tests/compiler/dart2js/dart_printer_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
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");
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 [VariableDeclarations] or [Expression] or null.
118 For(this.initializer, this.condition, this.updates, this.body) {
119 assert(initializer == null
120 || initializer is VariableDeclarations
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 [VariableDeclarations] 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 VariableDeclarations
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;
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;
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 /// A try statement. The try, catch and finally blocks will automatically
205 /// be printed inside a block statement if necessary.
206 class Try extends Statement {
207 final Statement tryBlock;
208 final List<CatchBlock> catchBlocks;
209 final Statement finallyBlock;
210
211 Try(this.tryBlock, this.catchBlocks, [this.finallyBlock]) {
212 assert(catchBlocks.length > 0 || finallyBlock != null);
213 }
214 }
215
216 class CatchBlock extends Node {
217 final TypeAnnotation onType;
218 final String exceptionVar;
219 final String stackVar;
220 final Statement body;
221
222 /// At least onType or exceptionVar must be given.
223 /// stackVar may only be given if exceptionVar is also given.
224 CatchBlock(this.body, {this.onType, this.exceptionVar, this.stackVar}) {
225 // Must specify at least a type or an exception binding.
226 assert(onType != null || exceptionVar != null);
227
228 // We cannot bind the stack trace without binding the exception too.
229 assert(stackVar == null || exceptionVar != null);
230 }
231 }
232
233 class VariableDeclarations extends Statement {
234 final TypeAnnotation type;
235 final bool isFinal;
236 final bool isConst;
237 final List<VariableDeclaration> definitions;
238
239 VariableDeclarations(this.definitions,
240 { this.type,
241 this.isFinal: false,
242 this.isConst: false }) {
243 // Cannot be both final and const.
244 assert(!isFinal || !isConst);
245 }
246 }
247
248 class VariableDeclaration extends Node {
249 final String name;
250 final Expression initializer;
251
252 VariableDeclaration(this.name, [this.initializer]);
253 }
254
255
256 class FunctionDeclaration extends Statement {
257 final TypeAnnotation returnType;
258 final Parameters parameters;
259 final String name;
260 final Statement body;
261
262 FunctionDeclaration(this.name,
263 this.parameters,
264 this.body,
265 [ this.returnType ]);
266 }
267
268 class Parameters extends Node {
269 final List<Parameter> requiredParameters;
270 final List<Parameter> optionalParameters;
271 final bool hasNamedParameters;
272
273 Parameters(this.requiredParameters,
274 [ this.optionalParameters,
275 this.hasNamedParameters = false ]);
276
277 Parameters.named(this.requiredParameters, this.optionalParameters)
278 : hasNamedParameters = true;
279
280 Parameters.positional(this.requiredParameters, this.optionalParameters)
281 : hasNamedParameters = false;
282
283 bool get hasOptionalParameters =>
284 optionalParameters != null && optionalParameters.length > 0;
285 }
286
287 class Parameter extends Node {
288 final String name;
289
290 /// Type of parameter, or return type of function parameter.
291 final TypeAnnotation type;
292
293 final Expression defaultValue;
294
295 /// Parameters to function parameter. Null for non-function parameters.
296 final Parameters parameters;
297
298 Parameter(this.name, {this.type, this.defaultValue})
299 : parameters = null;
300
301 Parameter.function(this.name,
302 TypeAnnotation returnType,
303 this.parameters,
304 [this.defaultValue]) : type = returnType {
305 assert(parameters != null);
306 }
307
308 /// True if this is a function parameter.
309 bool get isFunction => parameters != null;
310
311 // TODO(asgerf): Support modifiers on parameters (final, ...).
312 }
313
314 // EXPRESSIONS
315
316 class FunctionExpression extends Expression {
317 final Parameters parameters;
318 final Statement body;
319
320 FunctionExpression(this.parameters, this.body);
321 }
322
323 class Conditional extends Expression {
324 final Expression condition;
325 final Expression thenExpression;
326 final Expression elseExpression;
327
328 Conditional(this.condition, this.thenExpression, this.elseExpression);
329 }
330
331 /// An identifier expression.
332 /// The unparser does not concern itself with scoping rules, and it is the
333 /// responsibility of the AST creator to ensure that the identifier resolves
334 /// to the proper definition.
335 class Identifier extends Expression {
336 final String name;
337
338 Identifier(this.name);
339
340 bool get assignable => true;
341 }
342
343 class Literal extends Expression {
344 final dart2js.PrimitiveConstant value;
345
346 Literal(this.value);
347 }
348
349 class LiteralList extends Expression {
350 final bool isConst;
351 final TypeAnnotation typeArgument;
352 final List<Expression> values;
353
354 LiteralList(this.values, {this.typeArgument, this.isConst: false});
355 }
356
357 class LiteralMap extends Expression {
358 final bool isConst;
359 final List<TypeAnnotation> typeArguments;
360 final List<LiteralMapEntry> entries;
361
362 LiteralMap(this.entries, {this.typeArguments, this.isConst: false}) {
363 assert(this.typeArguments == null
364 || this.typeArguments.length == 0
365 || this.typeArguments.length == 2);
366 }
367 }
368
369 class LiteralMapEntry extends Node {
370 final Expression key;
371 final Expression value;
372
373 LiteralMapEntry(this.key, this.value);
374 }
375
376 class LiteralSymbol extends Expression {
377 final String id;
378
379 /// [id] should not include the # symbol
380 LiteralSymbol(this.id);
381 }
382
383 /// StringConcat is used in place of string interpolation and juxtaposition.
384 /// Semantically, each subexpression is evaluated and converted to a string
385 /// by `toString()`. These string are then concatenated and returned.
386 /// StringConcat unparses to a string literal, possibly with interpolations.
387 /// The unparser will flatten nested StringConcats.
388 /// A StringConcat node may have any number of children, including zero and one.
389 class StringConcat extends Expression {
390 final List<Expression> expressions;
391
392 StringConcat(this.expressions);
393 }
394
395 /// Expression of form `e.f`.
396 class FieldExpression extends Expression {
397 final Receiver object;
398 final String fieldName;
399
400 FieldExpression(this.object, this.fieldName);
401
402 bool get assignable => true;
403 }
404
405 /// Expression of form `e1[e2]`.
406 class IndexExpression extends Expression {
407 final Receiver object;
408 final Expression index;
409
410 IndexExpression(this.object, this.index);
411
412 bool get assignable => true;
413 }
414
415 /// Expression of form `e(..)`
416 /// Note that if [callee] is a [FieldExpression] this will translate into
417 /// `(e.f)(..)` and not `e.f(..)`. Use a [CallMethod] to generate
418 /// the latter type of expression.
419 class CallFunction extends Expression {
420 final Expression callee;
421 final List<Argument> arguments;
422
423 CallFunction(this.callee, this.arguments);
424 }
425
426 /// Expression of form `e.f(..)`.
427 class CallMethod extends Expression {
428 final Receiver object;
429 final String methodName;
430 final List<Argument> arguments;
431
432 CallMethod(this.object, this.methodName, this.arguments);
433 }
434
435 /// Expression of form `new T(..)`, `new T.f(..)`, `const T(..)`,
436 /// or `const T.f(..)`.
437 class CallNew extends Expression {
438 final bool isConst;
439 final TypeAnnotation type;
440 final String constructorName;
441 final List<Argument> arguments;
442
443 CallNew(this.type,
444 this.arguments,
445 { this.constructorName,
446 this.isConst: false });
447 }
448
449 /// Expression of form `T.f(..)`.
450 class CallStatic extends Expression {
451 final String className;
452 final String methodName;
453 final List<Argument> arguments;
454
455 CallStatic(this.className, this.methodName, this.arguments);
456 }
457
458 /// Expression of form `!e` or `-e` or `~e`.
459 class UnaryOperator extends Expression {
460 final String operatorName;
461 final Receiver operand;
462
463 UnaryOperator(this.operatorName, this.operand) {
464 assert(isUnaryOperator(operatorName));
465 }
466 }
467
468 /// Expression of form `e1 + e2`, `e1 - e2`, etc.
469 /// This node also represents application of the logical operators && and ||.
470 class BinaryOperator extends Expression {
471 final Receiver left;
472 final String operatorName;
473 final Expression right;
474
475 BinaryOperator(this.left, this.operatorName, this.right) {
476 assert(isBinaryOperator(operatorName));
477 }
478 }
479
480 /// Expression of form `e is T` or `e is! T` or `e as T`.
481 class TypeOperator extends Expression {
482 final Expression expression;
483 final String operatorName;
484 final TypeAnnotation type;
485
486 TypeOperator(this.expression, this.operatorName, this.type) {
487 assert(operatorName == 'is'
488 || operatorName == 'as'
489 || operatorName == 'is!');
490 }
491 }
492
493 class Increment extends Expression {
494 final Expression expression;
495 final String operatorName;
496 final bool isPrefix;
497
498 Increment(this.expression, this.operatorName, this.isPrefix) {
499 assert(operatorName == '++' || operatorName == '--');
500 assert(expression.assignable);
501 }
502
503 Increment.prefix(Expression expression, String operator)
504 : this(expression, operator, true);
505
506 Increment.postfix(Expression expression, String operator)
507 : this(expression, operator, false);
508 }
509
510 class Assignment extends Expression {
511 static final _operators =
512 new Set.from(['=', '|=', '^=', '&=', '<<=', '>>=',
513 '+=', '-=', '*=', '/=', '%=', '~/=']);
514
515 final Expression left;
516 final String operatorName;
517 final Expression right;
518
519 Assignment(this.left, this.operatorName, this.right) {
520 assert(_operators.contains(operatorName));
521 assert(left.assignable);
522 }
523 }
524
525 class Throw extends Expression {
526 final Expression expression;
527
528 Throw(this.expression);
529 }
530
531 class This extends Expression {
532 static final This _instance = new This._create();
533
534 factory This() => _instance;
535 This._create();
536 }
537
538 // UNPARSER
539
540 bool isUnaryOperator(String op) {
541 return op == '!' || op == '-' || op == '~';
542 }
543 bool isBinaryOperator(String op) {
544 return Unparser._binaryPrecedence.containsKey(op);
545 }
546
547
548 const int NEWLINE = 10;
549 const int CARRIAGE_RETURN = 13;
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)) {
677 return stmt; // Do not unfold block with lexical scope.
678 }
679 stmt = inner;
680 }
681 if (stmt is Block && stmt.statements.length == 0)
682 return new EmptyStatement();
683 return stmt;
684 }
685
686 void writeArgument(Argument arg) {
687 if (arg is NamedArgument) {
688 write(arg.name);
689 write(':');
690 writeExpression(arg.expression);
691 } else {
692 writeExpression(arg);
693 }
694 }
695
696 /// Prints the expression [e].
697 void writeExpression(Expression e) {
698 writeExp(e, EXPRESSION);
699 }
700
701 /// Prints [e] as an expression with precedence of at least [minPrecedence],
702 /// using parentheses if necessary to raise the precedence level.
703 /// Abusing terminology slightly, the function accepts a [Receiver] which
704 /// may also be the [SuperReceiver] object.
705 void writeExp(Receiver e, int minPrecedence, {beginStmt:false}) {
706 // TODO(asgerf):
707 // Would there be a significant speedup using a Visitor or a method
708 // on the AST instead of a chain of "if (e is T)" statements?
709 void withPrecedence(int actual, void action()) {
710 if (actual < minPrecedence) {
711 write("(");
712 beginStmt = false;
713 action();
714 write(")");
715 } else {
716 action();
717 }
718 }
719 if (e is SuperReceiver) {
720 write('super');
721 } else if (e is FunctionExpression) {
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 } else if (e is Conditional) {
734 withPrecedence(CONDITIONAL, () {
735 writeExp(e.condition, LOGICAL_OR, beginStmt: beginStmt);
736 write(' ? '); // TODO(asgerf): Minimize use of whitespace.
737 writeExp(e.thenExpression, EXPRESSION);
738 write(' : ');
739 writeExp(e.elseExpression, EXPRESSION);
740 });
741 } else if (e is Identifier) {
742 write(e.name);
743 } else if (e is Literal) {
744 if (e.value is dart2js.StringConstant) {
745 writeStringLiteral(e);
746 }
747 else {
748 write(e.value.toString());
749 }
750 } else if (e is LiteralList) {
751 if (e.isConst) {
752 write(' const '); // TODO(asgerf): Minimize use of whitespace.
753 }
754 if (e.typeArgument != null) {
755 write('<');
756 writeType(e.typeArgument);
757 write('>');
758 }
759 write('[');
760 writeEach(',', e.values, writeExpression);
761 write(']');
762 }
763 else if (e is LiteralMap) {
764 // The curly brace can be mistaken for a block statement if we
765 // are at the beginning of a statement.
766 bool needParen = beginStmt;
767 if (e.isConst) {
768 write(' const '); // TODO(asgerf): Minimize use of whitespace.
769 needParen = false;
770 }
771 if (e.typeArguments != null && e.typeArguments.length > 0) {
772 write('<');
773 writeEach(',', e.typeArguments, writeType);
774 write('>');
775 needParen = false;
776 }
777 if (needParen) {
778 write('(');
779 }
780 write('{');
781 writeEach(',', e.entries, (LiteralMapEntry en) {
782 writeExp(en.key, EXPRESSION);
783 write(' : '); // TODO(asgerf): Minimize use of whitespace.
784 writeExp(en.value, EXPRESSION);
785 });
786 write('}');
787 if (needParen) {
788 write(')');
789 }
790 } else if (e is LiteralSymbol) {
791 write('#');
792 write(e.id); // TODO(asgerf): Do we need to escape something here?
793 } else if (e is StringConcat) {
794 writeStringLiteral(e);
795 } else if (e is UnaryOperator) {
796 Receiver operand = e.operand;
797 // !(x == y) ==> x != y.
798 if (e.operatorName == '!' &&
799 operand is BinaryOperator && operand.operatorName == '==') {
800 withPrecedence(EQUALITY, () {
801 writeExp(operand.left, RELATIONAL);
802 writeOperator('!=');
803 writeExp(operand.right, RELATIONAL);
804 });
805 }
806 // !(x is T) ==> x is!T
807 else if (e.operatorName == '!' &&
808 operand is TypeOperator && operand.operatorName == 'is') {
809 withPrecedence(RELATIONAL, () {
810 writeExp(operand.expression, BITWISE_OR);
811 write(' is!'); // TODO(asgerf): Minimize use of whitespace.
812 writeType(operand.type);
813 });
814 }
815 else {
816 withPrecedence(UNARY, () {
817 writeOperator(e.operatorName);
818 writeExp(e.operand, UNARY);
819 });
820 }
821 } else if (e is BinaryOperator) {
822 int precedence = _binaryPrecedence[e.operatorName];
823 withPrecedence(precedence, () {
824 // All binary operators are left-associative or non-associative.
825 // For each operand, we use either the same precedence level as
826 // the current operator, or one higher.
827 int deltaLeft = isAssociativeBinaryOperator(precedence) ? 0 : 1;
828 writeExp(e.left, precedence + deltaLeft, beginStmt: beginStmt);
829 writeOperator(e.operatorName);
830 writeExp(e.right, precedence + 1);
831 });
832 } else if (e is TypeOperator) {
833 withPrecedence(RELATIONAL, () {
834 writeExp(e.expression, BITWISE_OR, beginStmt: beginStmt);
835 write(' ');
836 write(e.operatorName);
837 write(' ');
838 writeType(e.type);
839 });
840 } else if (e is Assignment) {
841 withPrecedence(EXPRESSION, () {
842 writeExp(e.left, PRIMARY, beginStmt: beginStmt);
843 writeOperator(e.operatorName);
844 writeExp(e.right, EXPRESSION);
845 });
846 } else if (e is FieldExpression) {
847 withPrecedence(PRIMARY, () {
848 writeExp(e.object, PRIMARY, beginStmt: beginStmt);
849 write('.');
850 write(e.fieldName);
851 });
852 } else if (e is IndexExpression) {
853 withPrecedence(CALLEE, () {
854 writeExp(e.object, PRIMARY, beginStmt: beginStmt);
855 write('[');
856 writeExp(e.index, EXPRESSION);
857 write(']');
858 });
859 } else if (e is CallFunction) {
860 withPrecedence(CALLEE, () {
861 writeExp(e.callee, CALLEE, beginStmt: beginStmt);
862 write('(');
863 writeEach(',', e.arguments, writeArgument);
864 write(')');
865 });
866 } else if (e is CallMethod) {
867 withPrecedence(CALLEE, () {
868 writeExp(e.object, PRIMARY, beginStmt: beginStmt);
869 write('.');
870 write(e.methodName);
871 write('(');
872 writeEach(',', e.arguments, writeArgument);
873 write(')');
874 });
875 } else if (e is CallNew) {
876 withPrecedence(CALLEE, () {
877 write(' '); // TODO(asgerf): Minimize use of whitespace.
878 write(e.isConst ? 'const ' : 'new ');
879 writeType(e.type);
880 if (e.constructorName != null) {
881 write('.');
882 write(e.constructorName);
883 }
884 write('(');
885 writeEach(',', e.arguments, writeArgument);
886 write(')');
887 });
888 } else if (e is CallStatic) {
889 withPrecedence(CALLEE, () {
890 write(e.className);
891 write('.');
892 write(e.methodName);
893 write('(');
894 writeEach(',', e.arguments, writeArgument);
895 write(')');
896 });
897 } else if (e is Increment) {
898 int precedence = e.isPrefix ? UNARY : POSTFIX_INCREMENT;
899 withPrecedence(precedence, () {
900 if (e.isPrefix) {
901 write(e.operatorName);
902 writeExp(e.expression, PRIMARY);
903 } else {
904 writeExp(e.expression, PRIMARY, beginStmt: beginStmt);
905 write(e.operatorName);
906 }
907 });
908 } else if (e is Throw) {
909 withPrecedence(EXPRESSION, () {
910 write('throw ');
911 writeExp(e.expression, EXPRESSION);
912 });
913 } else if (e is This) {
914 write('this');
915 } else {
916 throw "Unexpected expression: $e";
917 }
918 }
919
920 void writeParameters(Parameters params) {
921 write('(');
922 bool first = true;
923 writeEach(',', params.requiredParameters, (Parameter p) {
924 if (p.type != null) {
925 writeType(p.type);
926 write(' ');
927 }
928 write(p.name);
929 if (p.parameters != null) {
930 writeParameters(p.parameters);
931 }
932 });
933 if (params.hasOptionalParameters) {
934 if (params.requiredParameters.length > 0) {
935 write(',');
936 }
937 write(params.hasNamedParameters ? '{' : '[');
938 writeEach(',', params.optionalParameters, (Parameter p) {
939 if (p.type != null) {
940 writeType(p.type);
941 write(' ');
942 }
943 write(p.name);
944 if (p.parameters != null) {
945 writeParameters(p.parameters);
946 }
947 if (p.defaultValue != null) {
948 write(params.hasNamedParameters ? ':' : '=');
949 writeExp(p.defaultValue, EXPRESSION);
950 }
951 });
952 write(params.hasNamedParameters ? '}' : ']');
953 }
954 write(')');
955 }
956
957 void writeStatement(Statement stmt, {bool shortIf: true}) {
958 stmt = unfoldBlocks(stmt);
959 if (stmt is Block) {
960 write('{');
961 stmt.statements.forEach(writeBlockMember);
962 write('}');
963 } else if (stmt is Break) {
964 write('break');
965 if (stmt.label != null) {
966 write(' ');
967 write(stmt.label);
968 }
969 write(';');
970 } else if (stmt is Continue) {
971 write('continue');
972 if (stmt.label != null) {
973 write(' ');
974 write(stmt.label);
975 }
976 write(';');
977 } else if (stmt is EmptyStatement) {
978 write(';');
979 } else if (stmt is ExpressionStatement) {
980 writeExp(stmt.expression, EXPRESSION, beginStmt:true);
981 write(';');
982 } else if (stmt is For) {
983 write('for(');
984 Node init = stmt.initializer;
985 if (init is Expression) {
986 writeExp(init, EXPRESSION);
987 } else if (init is VariableDeclarations) {
988 writeVariableDefinitions(init);
989 }
990 write(';');
991 if (stmt.condition != null) {
992 writeExp(stmt.condition, EXPRESSION);
993 }
994 write(';');
995 writeEach(',', stmt.updates, writeExpression);
996 write(')');
997 writeStatement(stmt.body, shortIf: shortIf);
998 } else if (stmt is ForIn) {
999 write('for(');
1000 Node lhv = stmt.leftHandValue;
1001 if (lhv is Identifier) {
1002 write(lhv.name);
1003 } else {
1004 writeVariableDefinitions(lhv as VariableDeclarations);
1005 }
1006 write(' in ');
1007 writeExp(stmt.expression, EXPRESSION);
1008 write(')');
1009 writeStatement(stmt.body, shortIf: shortIf);
1010 } else if (stmt is While) {
1011 write('while(');
1012 writeExp(stmt.condition, EXPRESSION);
1013 write(')');
1014 writeStatement(stmt.body, shortIf: shortIf);
1015 } else if (stmt is DoWhile) {
1016 write('do '); // TODO(asgerf): Minimize use of whitespace.
1017 writeStatement(stmt.body);
1018 write('while(');
1019 writeExp(stmt.condition, EXPRESSION);
1020 write(');');
1021 } else if (stmt is If) {
1022 // if (E) S else ; ==> if (E) S
1023 Statement elsePart = unfoldBlocks(stmt.elseStatement);
1024 if (elsePart is EmptyStatement) {
1025 elsePart = null;
1026 }
1027 if (!shortIf && elsePart == null) {
1028 write('{');
1029 }
1030 write('if(');
1031 writeExp(stmt.condition, EXPRESSION);
1032 write(')');
1033 writeStatement(stmt.thenStatement, shortIf: elsePart == null);
1034 if (elsePart != null) {
1035 write('else ');
1036 writeStatement(elsePart, shortIf: shortIf);
1037 }
1038 if (!shortIf && elsePart == null) {
1039 write('}');
1040 }
1041 } else if (stmt is LabeledStatement) {
1042 write(stmt.label);
1043 write(':');
1044 writeStatement(stmt.statement, shortIf: shortIf);
1045 } else if (stmt is Rethrow) {
1046 write('rethrow;');
1047 } else if (stmt is Return) {
1048 write('return');
1049 if (stmt.expression != null) {
1050 write(' ');
1051 writeExp(stmt.expression, EXPRESSION);
1052 }
1053 write(';');
1054 } else if (stmt is Switch) {
1055 write('switch(');
1056 writeExp(stmt.expression, EXPRESSION);
1057 write('){');
1058 for (SwitchCase caze in stmt.cases) {
1059 if (caze.isDefaultCase) {
1060 write('default:');
1061 } else {
1062 for (Expression exp in caze.expressions) {
1063 write('case ');
1064 writeExp(exp, EXPRESSION);
1065 write(':');
1066 }
1067 }
1068 if (caze.statements.isEmpty) {
1069 write(';'); // Prevent fall-through.
1070 } else {
1071 caze.statements.forEach(writeBlockMember);
1072 }
1073 }
1074 write('}');
1075 } else if (stmt is Try) {
1076 write('try');
1077 writeBlock(stmt.tryBlock);
1078 for (CatchBlock block in stmt.catchBlocks) {
1079 if (block.onType != null) {
1080 write('on ');
1081 writeType(block.onType);
1082 }
1083 if (block.exceptionVar != null) {
1084 write('catch(');
1085 write(block.exceptionVar);
1086 if (block.stackVar != null) {
1087 write(',');
1088 write(block.stackVar);
1089 }
1090 write(')');
1091 }
1092 writeBlock(block.body);
1093 }
1094 if (stmt.finallyBlock != null) {
1095 write('finally');
1096 writeBlock(stmt.finallyBlock);
1097 }
1098 } else if (stmt is VariableDeclarations) {
1099 writeVariableDefinitions(stmt);
1100 write(';');
1101 } else if (stmt is FunctionDeclaration) {
1102 if (stmt.returnType != null) {
1103 writeType(stmt.returnType);
1104 write(' ');
1105 }
1106 write(stmt.name);
1107 writeParameters(stmt.parameters);
1108 Statement body = unfoldBlocks(stmt.body);
1109 if (body is Return) {
1110 write('=> '); // TODO(asgerf): Minimize use of whitespace.
1111 writeExp(body.expression, EXPRESSION);
1112 write(';');
1113 } else {
1114 writeBlock(body);
1115 }
1116 } else {
1117 throw "Unexpected statement: $stmt";
1118 }
1119 }
1120
1121 /// Writes a variable definition statement without the trailing semicolon
1122 void writeVariableDefinitions(VariableDeclarations vds) {
1123 if (vds.isConst)
1124 write('const ');
1125 else if (vds.isFinal)
1126 write('final ');
1127 if (vds.type != null) {
1128 writeType(vds.type);
1129 write(' ');
1130 }
1131 if (!vds.isConst && !vds.isFinal && vds.type == null) {
1132 write('var ');
1133 }
1134 writeEach(',', vds.definitions, (VariableDeclaration vd) {
1135 write(vd.name);
1136 if (vd.initializer != null) {
1137 write('=');
1138 writeExp(vd.initializer, EXPRESSION);
1139 }
1140 });
1141 }
1142
1143 /// True of statements that introduce variables in the scope of their
1144 /// surrounding block. Blocks containing such statements cannot be unfolded.
1145 bool definesVariable(Statement s) {
1146 return s is VariableDeclarations || s is FunctionDeclaration;
1147 }
1148
1149 /// Writes the given statement in a context where only blocks are allowed.
1150 void writeBlock(Statement stmt) {
1151 if (stmt is Block) {
1152 writeStatement(stmt);
1153 } else {
1154 write('{');
1155 writeBlockMember(stmt);
1156 write('}');
1157 }
1158 }
1159
1160 /// Outputs a statement that is a member of a block statement (or a similar
1161 /// sequence of statements, such as in switch statement).
1162 /// This will flatten blocks and skip empty statement.
1163 void writeBlockMember(Statement stmt) {
1164 if (stmt is Block && !stmt.statements.any(definesVariable)) {
1165 stmt.statements.forEach(writeBlockMember);
1166 } else if (stmt is EmptyStatement) {
1167 // do nothing
1168 } else {
1169 writeStatement(stmt);
1170 }
1171 }
1172
1173 void writeType(TypeAnnotation type) {
1174 write(type.name);
1175 if (type.typeArguments != null && type.typeArguments.length > 0) {
1176 write('<');
1177 writeEach(',', type.typeArguments, writeType);
1178 write('>');
1179 }
1180 }
1181
1182 void writeStringLiteral(Expression node) {
1183 // TODO(asgerf): This might be a bit too expensive. Benchmark.
1184 // Flatten the StringConcat tree.
1185 List parts = []; // Expression or int (char node)
1186 void collectParts(Expression e) {
1187 if (e is StringConcat) {
1188 e.expressions.forEach(collectParts);
1189 } else if (e is Literal && e.value is dart2js.StringConstant) {
1190 for (int char in e.value.value) {
1191 parts.add(char);
1192 }
1193 } else if (e is CallMethod &&
1194 e.object is Expression && // Do not match super.toString()
1195 e.methodName == "toString" &&
1196 e.arguments.length == 0) {
1197 // ${e.toString()} ==> ${e}
1198 collectParts(e.object);
1199 } else {
1200 parts.add(e);
1201 }
1202 }
1203 collectParts(node);
1204
1205 // We use a dynamic algorithm to compute the optimal way of printing
1206 // the string literal.
1207 //
1208 // Using string juxtapositions, it is possible to switch from one quoting
1209 // to another, e.g. the constant "''''" '""""' uses this trick.
1210 //
1211 // As we move through the string from left to right, we maintain a strategy
1212 // for each StringQuoting Q, denoting the best way to print the current
1213 // prefix so that we end with a string literal quoted with Q.
1214 // At every step, each strategy is either:
1215 // 1) Updated to include the cost of printing the next character.
1216 // 2) Abandoned because it is cheaper to use another strategy as prefix,
1217 // and then switching quotation using a juxtaposition.
1218
1219 int getQuoteCost(tree.StringQuoting quot) {
1220 return quot.leftQuoteLength + quot.rightQuoteLength;
1221 }
1222
1223 // Create initial scores for each StringQuoting and index them
1224 // into raw/non-raw and single-quote/double-quote.
1225 List<OpenStringChunk> best = <OpenStringChunk>[];
1226 List<int> raws = <int>[];
1227 List<int> nonRaws = <int>[];
1228 List<int> sqs = <int>[];
1229 List<int> dqs = <int>[];
1230 for (tree.StringQuoting q in tree.StringQuoting.mapping) {
1231 // Ignore multiline quotings for now. Encoding of line breaks is unclear.
1232 // TODO(asgerf): Include multiline quotation schemes.
1233 if (q.leftQuoteCharCount >= 3)
1234 continue;
1235 OpenStringChunk chunk = new OpenStringChunk(null, q, getQuoteCost(q));
1236 int index = best.length;
1237 best.add(chunk);
1238
1239 if (q.raw) {
1240 raws.add(index);
1241 } else {
1242 nonRaws.add(index);
1243 }
1244 if (q.quote == characters.$SQ) {
1245 sqs.add(index);
1246 } else {
1247 dqs.add(index);
1248 }
1249 }
1250
1251 /// True if [x] is a letter, digit, or underscore.
1252 /// Such characters may not follow a shorthand string interpolation.
1253 bool isIdentifierPartNoDollar(dynamic x) {
1254 if (x is! int)
1255 return false;
1256 return (characters.$0 <= x && x <= characters.$9)
1257 || (characters.$A <= x && x <= characters.$Z)
1258 || (characters.$a <= x && x <= characters.$z)
1259 || (x == characters.$_);
1260 }
1261
1262 /// Applies additional cost to each track in [penalized], and considers
1263 /// switching from each [penalized] to a [nonPenalized] track.
1264 void penalize(List<int> penalized,
1265 List<int> nonPenalized,
1266 int endIndex,
1267 num cost(tree.StringQuoting q)) {
1268 for (int j in penalized) {
1269 // Check if another track can benefit from switching from this track.
1270 for (int k in nonPenalized) {
1271 num newCost = best[j].cost
1272 + 1 // Whitespace in string juxtaposition
1273 + getQuoteCost(best[k].quoting);
1274 if (newCost < best[k].cost) {
1275 best[k] = new OpenStringChunk(
1276 best[j].end(endIndex),
1277 best[k].quoting,
1278 newCost);
1279 }
1280 }
1281 best[j].cost += cost(best[j].quoting);
1282 }
1283 }
1284
1285 // Iterate through the string and update the score for each StringQuoting.
1286 for (int i = 0; i < parts.length; i++) {
1287 var part = parts[i];
1288 if (part is int) {
1289 int char = part;
1290 switch (char) {
1291 case characters.$$:
1292 case characters.$BACKSLASH:
1293 penalize(nonRaws, raws, i, (q) => 1);
1294 break;
1295 case characters.$DQ:
1296 penalize(dqs, sqs, i, (q) => q.raw ? double.INFINITY : 1);
1297 break;
1298 case characters.$SQ:
1299 penalize(sqs, dqs, i, (q) => q.raw ? double.INFINITY : 1);
1300 break;
1301 case NEWLINE:
1302 case CARRIAGE_RETURN:
1303 penalize(raws, nonRaws, i, (q) => double.INFINITY);
1304 break;
1305 }
1306 } else {
1307 // Penalize raw literals for string interpolation.
1308 penalize(raws, nonRaws, i, (q) => double.INFINITY);
1309
1310 // Splitting a string can sometimes allow us to use a shorthand
1311 // string interpolation that would otherwise be illegal.
1312 // E.g. "...${foo}x..." -> "...$foo" 'x...'
1313 // If are other factors that make splitting advantageous,
1314 // we can gain even more by doing the split here.
1315 if (part is Identifier &&
1316 !part.name.contains(r'$') &&
1317 i + 1 < parts.length &&
1318 isIdentifierPartNoDollar(parts[i+1])) {
1319 for (int j in nonRaws) {
1320 for (int k = 0; k < best.length; k++) {
1321 num newCost = best[j].cost
1322 + 1 // Whitespace in string juxtaposition
1323 - 2 // Save two curly braces
1324 + getQuoteCost(best[k].quoting);
1325 if (newCost < best[k].cost) {
1326 best[k] = new OpenStringChunk(
1327 best[j].end(i+1),
1328 best[k].quoting,
1329 newCost);
1330 }
1331 }
1332 }
1333 }
1334 }
1335 }
1336
1337 // Select the cheapest strategy
1338 OpenStringChunk bestChunk = best[0];
1339 for (OpenStringChunk chunk in best) {
1340 if (chunk.cost < bestChunk.cost) {
1341 bestChunk = chunk;
1342 }
1343 }
1344
1345 void printChunk(StringChunk chunk) {
1346 int startIndex;
1347 if (chunk.previous != null) {
1348 printChunk(chunk.previous);
1349 write(' '); // String juxtaposition requires a space between literals.
1350 startIndex = chunk.previous.endIndex;
1351 } else {
1352 startIndex = 0;
1353 }
1354 if (chunk.quoting.raw) {
1355 write('r');
1356 }
1357 write(chunk.quoting.quoteChar);
1358 bool raw = chunk.quoting.raw;
1359 int quoteCode = chunk.quoting.quote;
1360 for (int i=startIndex; i<chunk.endIndex; i++) {
1361 var part = parts[i];
1362 if (part is int) {
1363 int char = part;
1364 switch (char) {
1365 case characters.$$:
1366 if (raw)
1367 write(r'$');
1368 else
1369 write(r'\$');
1370 break;
1371 case characters.$BACKSLASH:
1372 if (raw)
1373 write(r'\');
1374 else
1375 write(r'\\');
1376 break;
1377 case characters.$DQ:
1378 if (quoteCode == char) {
1379 write(r'\"');
1380 } else {
1381 write(r'"');
1382 }
1383 break;
1384 case characters.$SQ:
1385 if (quoteCode == char) {
1386 write(r"\'");
1387 } else {
1388 write(r"'");
1389 }
1390 break;
1391 case NEWLINE:
1392 write(r'\n');
1393 break;
1394 case CARRIAGE_RETURN:
1395 write(r'\r');
1396 break;
1397 default:
1398 write(new String.fromCharCode(char));
1399 }
1400 } else if (part is Identifier &&
1401 !part.name.contains(r'$') &&
1402 (i == chunk.endIndex - 1 ||
1403 !isIdentifierPartNoDollar(parts[i+1]))) {
1404 write(r'$');
1405 write(part.name);
1406 } else {
1407 write(r'${');
1408 writeExpression(part);
1409 write('}');
1410 }
1411 }
1412 write(chunk.quoting.quoteChar);
1413 }
1414 printChunk(bestChunk.end(parts.length));
1415 }
1416
1417 }
1418
1419
1420 /// Strategy for printing a prefix of a string literal.
1421 /// A chunk represents the substring going from [:previous.endIndex:] to
1422 /// [endIndex] (or from 0 to [endIndex] if [previous] is null).
1423 class StringChunk {
1424 final StringChunk previous;
1425 final tree.StringQuoting quoting;
1426 final int endIndex;
1427
1428 StringChunk(this.previous, this.quoting, this.endIndex);
1429 }
1430
1431 /// [StringChunk] that has not yet been assigned an [endIndex].
1432 /// It additionally has a [cost] denoting the number of auxilliary characters
1433 /// (quotes, spaces, etc) needed to print the literal using this strategy
1434 class OpenStringChunk {
1435 final StringChunk previous;
1436 final tree.StringQuoting quoting;
1437 num cost;
1438
1439 OpenStringChunk(this.previous, this.quoting, this.cost);
1440
1441 StringChunk end(int endIndex) {
1442 return new StringChunk(previous, quoting, endIndex);
1443 }
1444 }
OLDNEW
« no previous file with comments | « no previous file | tests/compiler/dart2js/dart_printer_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698