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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: sdk/lib/_internal/compiler/implementation/dart_backend/dart_printer.dart
diff --git a/sdk/lib/_internal/compiler/implementation/dart_backend/dart_printer.dart b/sdk/lib/_internal/compiler/implementation/dart_backend/dart_printer.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5672f42df28fe3ae903fb5799fb2e69a62259d79
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/dart_backend/dart_printer.dart
@@ -0,0 +1,1475 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+
+// OPEN DESIGN QUESTIONS:
+
+// Should the AST enforce that variable definitions are hoisted at the top?
+// This would simplify the AST for [For] and [ForIn] and also simplify block
+// flattening.
+// On the other hand, the code gets harder to test because the unparser only
+// works together with the middle-end.
+
+// Should the ${E.toString()} ==> ${E} rewrite be in the unparser?
+// It seems more like a semantic rewrite than a syntactic one.
+// On the other hand, it is really easy to do and costs almost nothing.
+
+// TODO(asgerf): Include metadata.
+// TODO(asgerf): Include cascade operator.
+library dart_printer;
+
+import '../dart2jslib.dart' as dart2js;
+import '../tree/tree.dart' as tree;
+import '../util/characters.dart' as characters;
+
+/// The following nodes correspond to [tree.Send] expressions:
+/// [FieldExpression], [IndexExpression], [Assignment], [Increment],
+/// [CallFunction], [CallMethod], [CallNew], [CallStatic], [UnaryOperator],
+/// [BinaryOperator], and [TypeOperator].
+abstract class Node {}
+
+/// Receiver is an [Expression] or the [SuperReceiver].
+abstract class Receiver extends Node {}
+
+/// Argument is an [Expression] or a [NamedArgument].
+abstract class Argument extends Node {}
+
+abstract class Expression extends Node implements Receiver, Argument {
+ bool get assignable => false;
+}
+
+abstract class Statement extends Node {}
+
+/// Used as receiver in expressions that dispatch to the super class.
+/// 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.
+/// by a [CallMethod] node with [SuperReceiver] as its receiver.
+class SuperReceiver extends Receiver {
+ static final SuperReceiver _instance = new SuperReceiver._create();
+
+ factory SuperReceiver() => _instance;
+ SuperReceiver._create();
+}
+
+/// Named arguments may occur in the argument list of
+/// [CallFunction], [CallMethod], [CallNew], and [CallStatic].
+class NamedArgument extends Argument {
+ final String name;
+ final Expression expression;
+
+ NamedArgument(this.name, this.expression);
+}
+
+class TypeAnnotation extends Node {
+ final String name;
+ final List<TypeAnnotation> typeArguments;
+
+ TypeAnnotation(this.name, [this.typeArguments]);
+
+ 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
+ static final TypeAnnotation INT = new TypeAnnotation("int");
+ static final TypeAnnotation DOUBLE = new TypeAnnotation("double");
+ static final TypeAnnotation BOOL = new TypeAnnotation("bool");
+ static final TypeAnnotation STRING = new TypeAnnotation("String");
+ static final TypeAnnotation DYNAMIC = new TypeAnnotation("dynamic");
+}
+
+// STATEMENTS
+
+
+class Block extends Statement {
+ final List<Statement> statements;
+
+ Block(this.statements);
+}
+
+class Break extends Statement {
+ final String label;
+
+ Break([this.label]);
+}
+
+class Continue extends Statement {
+ final String label;
+
+ Continue([this.label]);
+}
+
+class EmptyStatement extends Statement {
+ static final EmptyStatement _instance = new EmptyStatement._create();
+
+ factory EmptyStatement() => _instance;
+ EmptyStatement._create();
+}
+
+class ExpressionStatement extends Statement {
+ final Expression expression;
+
+ ExpressionStatement(this.expression);
+}
+
+class For extends Statement {
+ final Node initializer;
+ final Expression condition;
+ final List<Expression> updates;
+ final Statement body;
+
+ /// Initializer must be [VariableDefinitions] or [Expression] or null.
+ For(this.initializer, this.condition, this.updates, this.body) {
+ assert(initializer == null
+ || initializer is VariableDefinitions
+ || initializer is Expression);
+ }
+}
+
+class ForIn extends Statement {
+ final Node leftHandValue;
+ final Expression expression;
+ final Statement body;
+
+ /// [leftHandValue] must be [Identifier] or [VariableDefinitions] with
+ /// exactly one definition, and that variable definition must have no
+ /// initializer.
+ ForIn(Node leftHandValue, this.expression, this.body)
+ : this.leftHandValue = leftHandValue {
+ assert(leftHandValue is Identifier
+ || (leftHandValue is VariableDefinitions
+ && leftHandValue.definitions.length == 1
+ && leftHandValue.definitions[0].initializer == null));
+ }
+}
+
+class While extends Statement {
+ final Expression condition;
+ final Statement body;
+
+ While(this.condition, this.body);
+}
+
+class DoWhile extends Statement {
+ final Statement body;
+ final Expression condition;
+
+ DoWhile(this.body, this.condition);
+}
+
+class If extends Statement {
+ final Expression condition;
+ final Statement thenStatement;
+ final Statement elseStatement;
+
+ If(this.condition, this.thenStatement, [this.elseStatement]);
+}
+
+class LabeledStatement extends Statement {
+ 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
+ final Statement statement;
+
+ LabeledStatement(this.label, this.statement);
+}
+
+class Rethrow extends Statement {
+}
+
+class Return extends Statement {
+ final Expression expression;
+
+ Return([this.expression]);
+}
+
+class Switch extends Statement {
+ final Expression expression;
+ final List<SwitchCase> cases;
+
+ Switch(this.expression, this.cases);
+}
+
+/// A sequence of case clauses followed by a sequence of statements.
+/// Represents the default case if [expressions] is null.
+///
+/// NOTE:
+/// Control will never fall through to the following SwitchCase, even if
+/// the list of statements is empty. An empty list of statements will be
+/// unparsed to a semicolon to guarantee this behaviour.
+class SwitchCase extends Node {
+ 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
+ final List<Statement> statements;
+
+ SwitchCase(this.expressions, this.statements);
+ SwitchCase.defaultCase(this.statements) : expressions = null;
+
+ bool get isDefaultCase => expressions == null;
+}
+
+class Try extends Statement {
+ 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.
+ final List<CatchBlock> catchBlocks;
+ final Statement finallyBlock;
+
+ Try(this.tryBlock, this.catchBlocks, [this.finallyBlock]) {
+ assert(catchBlocks.length > 0 || finallyBlock != null);
+ }
+}
+
+class CatchBlock extends Node {
+ final TypeAnnotation onType;
+ final String exceptionVar;
+ final String stackVar;
+ final Statement body;
+
+ /// At least onType or exceptionVar must be given.
+ /// stackVar may only be given if exceptionVar is also given.
+ CatchBlock(this.body, {this.onType, this.exceptionVar, this.stackVar}) {
+ // Must specify at least a type or an exception binding.
+ assert(onType != null || exceptionVar != null);
+
+ // We cannot bind the stack trace without binding the exception too.
+ assert(stackVar == null || exceptionVar != null);
+ }
+}
+
+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.
+ final TypeAnnotation type;
+ final bool isFinal;
+ final bool isConst;
+ final List<VariableDefinition> definitions;
+
+ VariableDefinitions(this.definitions,
+ { this.type,
+ this.isFinal: false,
+ this.isConst: false }) {
+ // Cannot be both final and const.
+ assert(!isFinal || !isConst);
+ }
+}
+
+class VariableDefinition extends Node {
+ final String name;
+ final Expression initializer;
+
+ VariableDefinition(this.name, [this.initializer]);
+}
+
+
+class FunctionStatement extends Statement {
Kevin Millikin (Google) 2014/04/24 13:24:23 FunctionDeclaration?
asgerf 2014/04/25 11:52:44 Done.
+ final TypeAnnotation returnType;
+ final Parameters parameters;
+ final String name;
+ final Statement body;
+
+ FunctionStatement(this.name,
+ this.parameters,
+ this.body,
+ [ this.returnType ]);
+}
+
+class Parameters extends Node {
+ final List<Parameter> requiredParameters;
+ final List<Parameter> optionalParameters;
+ final bool hasNamedParameters;
+
+ Parameters(this.requiredParameters,
+ [ this.optionalParameters,
+ this.hasNamedParameters = false ]);
+
+ Parameters.named(this.requiredParameters, this.optionalParameters)
+ : hasNamedParameters = true;
+
+ Parameters.positional(this.requiredParameters, this.optionalParameters)
+ : hasNamedParameters = false;
+
+ bool get hasOptionalParameters =>
+ optionalParameters != null && optionalParameters.length > 0;
+}
+
+class Parameter extends Node {
+ final String name;
+
+ /// Type of parameter, or return type of function parameter.
+ final TypeAnnotation type;
+
+ final Expression defaultValue;
+
+ /// Parameters to function parameter. Null for non-function parameters.
+ final Parameters parameters;
+
+ Parameter(this.name, {this.type, this.defaultValue})
+ : parameters = null;
+
+ Parameter.function(this.name,
+ TypeAnnotation returnType,
+ this.parameters,
+ [this.defaultValue]) : type = returnType {
+ assert(parameters != null);
+ }
+
+ /// True if this is a function parameter.
+ bool get isFunction => parameters != null;
+
+ // TODO(asgerf): Support modifiers on parameters (final, ...).
+}
+
+// EXPRESSIONS
+
+class FunctionExpression extends Expression {
+ final Parameters parameters;
+ final Statement body;
+
+ FunctionExpression(this.parameters, this.body);
+}
+
+class Conditional extends Expression {
+ final Expression condition;
+ final Expression thenExpression;
+ final Expression elseExpression;
+
+ Conditional(this.condition, this.thenExpression, this.elseExpression);
+}
+
+/// An identifier expression.
+/// The unparser does not concern itself with scoping rules, and it is the
+/// responsibility of the AST creator to ensure that the identifier resolves
+/// to the proper definition.
+class Identifier extends Expression {
+ final String name;
+
+ Identifier(this.name);
+
+ bool get assignable => true;
+}
+
+class Literal extends Expression {
+ final dart2js.PrimitiveConstant value;
+
+ Literal(this.value);
+}
+
+class LiteralList extends Expression {
+ final bool isConst;
+ final TypeAnnotation typeArgument;
+ final List<Expression> values;
+
+ LiteralList(this.values, {this.typeArgument, this.isConst: false});
+}
+
+class LiteralMap extends Expression {
+ final bool isConst;
+ final List<TypeAnnotation> typeArguments;
+ final List<LiteralMapEntry> entries;
+
+ LiteralMap(this.entries, {this.typeArguments, this.isConst: false}) {
+ assert(this.typeArguments == null
+ || this.typeArguments.length == 0
+ || this.typeArguments.length == 2);
+ }
+}
+
+class LiteralMapEntry extends Node {
+ final Expression key;
+ final Expression value;
+
+ LiteralMapEntry(this.key, this.value);
+}
+
+class LiteralSymbol extends Expression {
+ final String id;
+
+ /// [id] should not include the # symbol
+ LiteralSymbol(this.id);
+}
+
+/// StringConcat is used in place of string interpolation and juxtaposition.
+/// Semantically, each subexpression is evaluated and converted to a string
+/// 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.
+/// StringConcat unparses to a string literal, possibly with interpolations.
+/// The unparser will flatten nested StringConcats.
+/// A StringConcat node may have any number of children, including zero and one.
+class StringConcat extends Expression {
+ final List<Expression> expressions;
+
+ StringConcat(this.expressions);
+}
+
+/// Expression of form [:e.f:].
+class FieldExpression extends Expression {
+ final Receiver object;
+ final String fieldName;
+
+ FieldExpression(this.object, this.fieldName);
+
+ bool get assignable => true;
+}
+
+/// Expression of form [:e1[e2]:].
+class IndexExpression extends Expression {
+ final Receiver object;
+ final Expression index;
+
+ IndexExpression(this.object, this.index);
+
+ bool get assignable => true;
+}
+
+/// Expression of form [:e(..):]
+/// Note that if [callee] is a [FieldExpression] this will translate into
+/// [:(e.f)(..):] and not [:e.f(..):]. Use a [CallMethod] to generate
+/// the latter type of expression.
+class CallFunction extends Expression {
+ final Expression callee;
+ final List<Argument> arguments;
+
+ CallFunction(this.callee, this.arguments);
+}
+
+/// Expression of form [:e.f(..):].
+class CallMethod extends Expression {
+ final Receiver object;
+ final String methodName;
+ final List<Argument> arguments;
+
+ CallMethod(this.object, this.methodName, this.arguments);
+}
+
+/// Expression of form [:new T(..):], [:new T.f(..):], [:const T(..):],
+/// or [:const T.f(..):].
+class CallNew extends Expression {
+ final bool isConst;
+ final TypeAnnotation type;
+ final String constructorName;
+ final List<Argument> arguments;
+
+ CallNew(this.type,
+ this.arguments,
+ { this.constructorName,
+ this.isConst: false });
+}
+
+/// Expression of form [:T.f(..):].
+class CallStatic extends Expression {
+ final String className;
+ final String methodName;
+ final List<Argument> arguments;
+
+ CallStatic(this.className, this.methodName, this.arguments);
+}
+
+/// Expression of form [:!e:] or [:-e:] or [:~e:].
+class UnaryOperator extends Expression {
+ final String operatorName;
+ final Receiver operand;
+
+ UnaryOperator(this.operatorName, this.operand) {
+ assert(isUnaryOperator(operatorName));
+ }
+}
+
+/// Expression of form [:e1 + e2:], [:e1 - e2:], etc.
+/// This node also represents application of the logical operators && and ||.
+class BinaryOperator extends Expression {
+ final Receiver left;
+ final String operatorName;
+ final Expression right;
+
+ BinaryOperator(this.left, this.operatorName, this.right) {
+ assert(isBinaryOperator(operatorName));
+ }
+}
+
+/// 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.
+class TypeOperator extends Expression {
+ final Expression expression;
+ final String operatorName;
+ final TypeAnnotation type;
+
+ TypeOperator(this.expression, this.operatorName, this.type) {
+ assert(operatorName == 'is' || operatorName == 'as');
+ }
+}
+
+class Increment extends Expression {
+ final Expression expression;
+ final String operatorName;
+ final bool isPrefix;
+
+ Increment(this.expression, this.operatorName, this.isPrefix) {
+ assert(operatorName == '++' || operatorName == '--');
+ assert(expression.assignable);
+ }
+
+ Increment.prefix(Expression expression, String operator): this(expression,
+ operator, true);
+
+ 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.
+ operator, false);
+}
+
+class Assignment extends Expression {
+ static final _operators =
+ new Set.from(['=', '|=', '^=', '&=', '<<=', '>>=',
+ '+=', '-=', '*=', '/=', '%=', '~/=']);
+
+ final Expression left;
+ final String operatorName;
+ final Expression right;
+
+ Assignment(this.left, this.operatorName, this.right) {
+ assert(_operators.contains(operatorName));
+ assert(left.assignable);
+ }
+}
+
+class Throw extends Expression {
+ final Expression expression;
+
+ Throw(this.expression);
+}
+
+class This extends Expression {
+ static final This _instance = new This._create();
+
+ factory This() => _instance;
+ This._create();
+}
+
+// UNPARSER
+
+bool isUnaryOperator(String op) {
+ return op == '!' || op == '-' || op == '~';
+}
+bool isBinaryOperator(String op) {
+ return Unparser._binaryPrecedence.containsKey(op);
+}
+
+
+const int NEWLINE = 10;
+const int CARRIAGE_RETURN = 13;
+
+int getQuoteCost(tree.StringQuoting quot) {
+ return quot.leftQuoteLength + quot.rightQuoteLength;
+}
+
+/// The unparser will apply the following syntactic rewritings:
+/// Use short-hand function returns:
+/// foo(){return E} ==> foo() => E;
+/// Remove empty else branch:
+/// if (E) S else ; ==> if (E) S
+/// Flatten nested blocks:
+/// {S; {S; S}; S} ==> {S; S; S; S}
+/// Remove empty statements from block:
+/// {S; ; S} ==> {S; S}
+/// Unfold singleton blocks:
+/// {S} ==> S
+/// Empty block to empty statement:
+/// {} ==> ;
+/// Introduce not-equals operator:
+/// !(E == E) ==> E != E
+/// Introduce is-not operator:
+/// !(E is T) ==> E is!T
+/// Remove .toString() from string interpolation (see [StringConcat])
+/// "X ${E.toString()} Y" ==> "X ${E} Y"
+///
+/// The following transformations will NOT be applied here:
+/// Use implicit this:
+/// this.foo ==> foo (preconditions too complex for unparser)
+/// Merge adjacent variable definitions:
+/// var x; var y ==> var x,y; (hoisting will be done elsewhere)
+/// Merge adjacent labels:
+/// foo: bar: S ==> foobar: S (scoping is categorically ignored)
+///
+/// The following transformations might be applied here in the future:
+/// Use implicit dynamic types:
+/// dynamic x = E ==> var x = E
+/// <dynamic>[] ==> []
+class Unparser {
+ StringSink output;
+
+ Unparser(this.output);
+
+ // Precedence levels
+ static const EXPRESSION = 1;
+ static const CONDITIONAL = 2;
+ static const LOGICAL_OR = 3;
+ static const LOGICAL_AND = 4;
+ static const EQUALITY = 6;
+ static const RELATIONAL = 7;
+ static const BITWISE_OR = 8;
+ static const BITWISE_XOR = 9;
+ static const BITWISE_AND = 10;
+ static const SHIFT = 11;
+ static const ADDITIVE = 12;
+ static const MULTIPLICATIVE = 13;
+ static const UNARY = 14;
+ static const POSTFIX_INCREMENT = 15;
+ static const PRIMARY = 20;
+
+ /// Precedence level required for the callee in a [FunctionCall].
+ static const CALLEE = 21;
+
+ static const _binaryPrecedence = const {
+ '&&': LOGICAL_AND,
+ '||': LOGICAL_OR,
+
+ '==': EQUALITY,
+ '!=': EQUALITY,
+
+ '>': RELATIONAL,
+ '>=': RELATIONAL,
+ '<': RELATIONAL,
+ '<=': RELATIONAL,
+
+ '|': BITWISE_OR,
+ '^': BITWISE_XOR,
+ '&': BITWISE_AND,
+
+ '>>': SHIFT,
+ '<<': SHIFT,
+
+ '+': ADDITIVE,
+ '-': ADDITIVE,
+
+ '*': MULTIPLICATIVE,
+ '%': MULTIPLICATIVE,
+ '/': MULTIPLICATIVE,
+ '~/': MULTIPLICATIVE,
+ };
+
+ /// The type of quote used around string literals.
+ static const QUOTE = "'";
+ static const QUOTE_CODE = 39;
+
+ /// Return true if binary operators with the given precedence level are
+ /// (left) associative. False if they are non-associative.
+ static bool isAssociativeBinaryOperator(int precedence) {
+ return precedence != EQUALITY && precedence != RELATIONAL;
+ }
+
+
+ void write(String s) {
+ output.write(s);
+ }
+
+ /// Outputs each element from [items] separated by [separator].
+ /// The actual printing must be performed by the [callback].
+ void writeEach(String separator, Iterable items, void callback(any)) {
+ bool first = true;
+ for (var x in items) {
+ if (first) {
+ first = false;
+ } else {
+ write(separator);
+ }
+ callback(x);
+ }
+ }
+
+ void writeOperator(String operator) {
+ write(" "); // TODO(asgerf): Minimize use of whitespace.
+ write(operator);
+ write(" ");
+ }
+
+ /// Unfolds singleton blocks and returns the inner statement.
+ /// If an empty block is found, the [EmptyStatement] is returned instead.
+ Statement unfoldBlocks(Statement stmt) {
+ while (stmt is Block && stmt.statements.length == 1) {
+ Statement inner = (stmt as Block).statements[0];
+ 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.
+ return stmt; // Do not unfold block with lexical scope.
+ stmt = inner;
+ }
+ if (stmt is Block && stmt.statements.length == 0)
+ return new EmptyStatement();
+ return stmt;
+ }
+
+ void writeArgument(Argument arg) {
+ if (arg is NamedArgument) {
+ write(arg.name);
+ write(':');
+ writeExpression(arg.expression);
+ } else {
+ writeExpression(arg);
+ }
+ }
+
+ /// Prints the expression [e].
+ void writeExpression(Expression e) {
+ writeExp(e, EXPRESSION);
+ }
+
+ /// Prints [e] as an expression with precedence of at least [minPrecedence],
+ /// using parentheses if necessary to raise the precedence level.
+ /// Abusing terminology slightly, the function accepts a [Receiver] which
+ /// may also be the [SuperReceiver] object.
+ void writeExp(Receiver e, int minPrecedence, {beginStmt:false}) {
+ // TODO(asgerf):
+ // 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
+ // on the AST instead of a chain of "if (e is T)" statements?
+ void withPrecedence(int actual, void action()) {
+ if (actual < minPrecedence) {
+ write("(");
+ beginStmt = false;
+ action();
+ write(")");
+ } else {
+ action();
+ }
+ }
+ if (e is SuperReceiver) {
+ write('super');
+ }
+ 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.
+ Statement stmt = unfoldBlocks(e.body);
+ int precedence = stmt is Return ? EXPRESSION : PRIMARY;
+ withPrecedence(precedence, () {
+ writeParameters(e.parameters);
+ if (stmt is Return) {
+ write('=> '); // TODO(asgerf): Minimize use of whitespace.
+ writeExp(stmt.expression, EXPRESSION);
+ } else {
+ writeBlock(stmt);
+ }
+ });
+ }
+ else if (e is Conditional) {
+ withPrecedence(CONDITIONAL, () {
+ writeExp(e.condition, LOGICAL_OR, beginStmt: beginStmt);
+ write(' ? '); // TODO(asgerf): Minimize use of whitespace.
+ writeExp(e.thenExpression, EXPRESSION);
+ write(' : ');
+ writeExp(e.elseExpression, EXPRESSION);
+ });
+ }
+ else if (e is Identifier) {
+ write(e.name);
+ }
+ else if (e is Literal) {
+ if (e.value is dart2js.StringConstant) {
+ writeStringLiteral(e);
+ }
+ else {
+ write(e.value.toString());
+ }
+ }
+ else if (e is LiteralList) {
+ if (e.isConst) {
+ write(' const '); // TODO(asgerf): Minimize use of whitespace.
+ }
+ if (e.typeArgument != null) {
+ write('<');
+ writeType(e.typeArgument);
+ write('>');
+ }
+ write('[');
+ writeEach(',', e.values, writeExpression);
+ write(']');
+ }
+ else if (e is LiteralMap) {
+ // The curly brace can be mistaken for a block statement if we
+ // are at the beginning of a statement.
+ bool needParen = beginStmt;
+ if (e.isConst) {
+ write(' const '); // TODO(asgerf): Minimize use of whitespace.
+ needParen = false;
+ }
+ if (e.typeArguments != null && e.typeArguments.length > 0) {
+ write('<');
+ writeEach(',', e.typeArguments, writeType);
+ write('>');
+ needParen = false;
+ }
+ if (needParen) {
+ write('(');
+ }
+ write('{');
+ writeEach(',', e.entries, (LiteralMapEntry en) {
+ writeExp(en.key, EXPRESSION);
+ write(' : '); // TODO(asgerf): Minimize use of whitespace.
+ writeExp(en.value, EXPRESSION);
+ });
+ write('}');
+ if (needParen) {
+ write(')');
+ }
+ }
+ else if (e is LiteralSymbol) {
+ write('#');
+ write(e.id); // TODO(asgerf): Do we need to escape something here?
+ }
+ else if (e is StringConcat) {
+ writeStringLiteral(e);
+ }
+ else if (e is UnaryOperator) {
+ Receiver operand = e.operand;
+ // !(x == y) ==> x != y.
+ if (e.operatorName == '!' &&
+ operand is BinaryOperator && operand.operatorName == '==') {
+ withPrecedence(EQUALITY, () {
+ writeExp(operand.left, RELATIONAL);
+ writeOperator('!=');
+ writeExp(operand.right, RELATIONAL);
+ });
+ }
+ // !(x is T) ==> x is!T
+ else if (e.operatorName == '!' &&
+ operand is TypeOperator && operand.operatorName == 'is') {
+ withPrecedence(RELATIONAL, () {
+ writeExp(operand.expression, BITWISE_OR);
+ write(' is!'); // TODO(asgerf): Minimize use of whitespace.
+ writeType(operand.type);
+ });
+ }
+ else {
+ withPrecedence(UNARY, () {
+ writeOperator(e.operatorName);
+ writeExp(e.operand, UNARY);
+ });
+ }
+ }
+ else if (e is BinaryOperator) {
+ int precedence = _binaryPrecedence[e.operatorName];
+ withPrecedence(precedence, () {
+ // All binary operators are left-associative or non-associative.
+ // For each operand, we use either the same precedence level as
+ // the current operator, or one higher.
+ int deltaLeft = isAssociativeBinaryOperator(precedence) ? 0 : 1;
+ writeExp(e.left, precedence + deltaLeft, beginStmt: beginStmt);
+ writeOperator(e.operatorName);
+ writeExp(e.right, precedence + 1);
+ });
+ }
+ else if (e is TypeOperator) {
+ withPrecedence(RELATIONAL, () {
+ writeExp(e.expression, BITWISE_OR, beginStmt: beginStmt);
+ write(' ');
+ write(e.operatorName);
+ write(' ');
+ writeType(e.type);
+ });
+ }
+ else if (e is Assignment) {
+ withPrecedence(EXPRESSION, () {
+ writeExp(e.left, PRIMARY, beginStmt: beginStmt);
+ writeOperator(e.operatorName);
+ writeExp(e.right, EXPRESSION);
+ });
+ }
+ else if (e is FieldExpression) {
+ withPrecedence(PRIMARY, () {
+ writeExp(e.object, PRIMARY, beginStmt: beginStmt);
+ write('.');
+ write(e.fieldName);
+ });
+ }
+ else if (e is IndexExpression) {
+ withPrecedence(CALLEE, () {
+ writeExp(e.object, PRIMARY, beginStmt: beginStmt);
+ write('[');
+ writeExp(e.index, EXPRESSION);
+ write(']');
+ });
+ }
+ else if (e is CallFunction) {
+ withPrecedence(CALLEE, () {
+ writeExp(e.callee, CALLEE, beginStmt: beginStmt);
+ write('(');
+ writeEach(',', e.arguments, writeArgument);
+ write(')');
+ });
+ }
+ else if (e is CallMethod) {
+ withPrecedence(CALLEE, () {
+ writeExp(e.object, PRIMARY, beginStmt: beginStmt);
+ write('.');
+ write(e.methodName);
+ write('(');
+ writeEach(',', e.arguments, writeArgument);
+ write(')');
+ });
+ }
+ else if (e is CallNew) {
+ withPrecedence(CALLEE, () {
+ write(' '); // TODO(asgerf): Minimize use of whitespace.
+ write(e.isConst ? 'const ' : 'new ');
+ writeType(e.type);
+ if (e.constructorName != null) {
+ write('.');
+ write(e.constructorName);
+ }
+ write('(');
+ writeEach(',', e.arguments, writeArgument);
+ write(')');
+ });
+ }
+ else if (e is CallStatic) {
+ withPrecedence(CALLEE, () {
+ write(e.className);
+ write('.');
+ write(e.methodName);
+ write('(');
+ writeEach(',', e.arguments, writeArgument);
+ write(')');
+ });
+ }
+ else if (e is Increment) {
+ int precedence = e.isPrefix ? UNARY : POSTFIX_INCREMENT;
+ withPrecedence(precedence, () {
+ if (e.isPrefix) {
+ write(e.operatorName);
+ writeExp(e.expression, PRIMARY);
+ } else {
+ writeExp(e.expression, PRIMARY, beginStmt: beginStmt);
+ write(e.operatorName);
+ }
+ });
+ }
+ else if (e is Throw) {
+ withPrecedence(EXPRESSION, () {
+ write('throw ');
+ writeExp(e.expression, EXPRESSION);
+ });
+ }
+ else if (e is This) {
+ write('this');
+ }
+ else {
+ throw "Unexpected expression: $e";
+ }
+ }
+
+ void writeParameters(Parameters params) {
+ write('(');
+ bool first = true;
+ writeEach(',', params.requiredParameters, (Parameter p) {
+ if (p.type != null) {
+ writeType(p.type);
+ write(' ');
+ }
+ write(p.name);
+ if (p.parameters != null) {
+ writeParameters(p.parameters);
+ }
+ });
+ if (params.hasOptionalParameters) {
+ if (params.requiredParameters.length > 0) {
+ write(',');
+ }
+ write(params.hasNamedParameters ? '{' : '[');
+ writeEach(',', params.optionalParameters, (Parameter p) {
+ if (p.type != null) {
+ writeType(p.type);
+ write(' ');
+ }
+ write(p.name);
+ if (p.parameters != null) {
+ writeParameters(p.parameters);
+ }
+ if (p.defaultValue != null) {
+ write(params.hasNamedParameters ? ':' : '=');
+ writeExp(p.defaultValue, EXPRESSION);
+ }
+ });
+ write(params.hasNamedParameters ? '}' : ']');
+ }
+ write(')');
+ }
+
+ void writeStatement(Statement stmt, {bool shortIf: true}) {
+ stmt = unfoldBlocks(stmt);
+ if (stmt is Block) {
+ write('{');
+ stmt.statements.forEach(writeBlockMember);
+ write('}');
+ }
+ else if (stmt is Break) {
+ write('break');
+ if (stmt.label != null) {
+ write(' ');
+ write(stmt.label);
+ }
+ write(';');
+ }
+ else if (stmt is Continue) {
+ write('continue');
+ if (stmt.label != null) {
+ write(' ');
+ write(stmt.label);
+ }
+ write(';');
+ }
+ else if (stmt is EmptyStatement) {
+ write(';');
+ }
+ else if (stmt is ExpressionStatement) {
+ writeExp(stmt.expression, EXPRESSION, beginStmt:true);
+ write(';');
+ }
+ else if (stmt is For) {
+ write('for(');
+ Node init = stmt.initializer;
+ if (init is Expression) {
+ writeExp(init, EXPRESSION);
+ } else if (init is VariableDefinitions) {
+ writeVariableDefinitions(init);
+ }
+ write(';');
+ if (stmt.condition != null) {
+ writeExp(stmt.condition, EXPRESSION);
+ }
+ write(';');
+ writeEach(',', stmt.updates, writeExpression);
+ write(')');
+ writeStatement(stmt.body, shortIf: shortIf);
+ }
+ else if (stmt is ForIn) {
+ write('for(');
+ Node lhv = stmt.leftHandValue;
+ if (lhv is Identifier) {
+ write(lhv.name);
+ } else {
+ writeVariableDefinitions(lhv as VariableDefinitions);
+ }
+ write(' in ');
+ writeExp(stmt.expression, EXPRESSION);
+ write(')');
+ writeStatement(stmt.body, shortIf: shortIf);
+ }
+ else if (stmt is While) {
+ write('while(');
+ writeExp(stmt.condition, EXPRESSION);
+ write(')');
+ writeStatement(stmt.body, shortIf: shortIf);
+ }
+ else if (stmt is DoWhile) {
+ write('do '); // TODO(asgerf): Minimize use of whitespace.
+ writeStatement(stmt.body);
+ write('while(');
+ writeExp(stmt.condition, EXPRESSION);
+ write(');');
+ }
+ else if (stmt is If) {
+ // if (E) S else ; ==> if (E) S
+ Statement elsePart = unfoldBlocks(stmt.elseStatement);
+ if (elsePart is EmptyStatement) {
+ elsePart = null;
+ }
+ if (!shortIf && elsePart == null) {
+ write('{');
+ }
+ write('if(');
+ writeExp(stmt.condition, EXPRESSION);
+ write(')');
+ writeStatement(stmt.thenStatement, shortIf: elsePart == null);
+ if (elsePart != null) {
+ write('else ');
+ writeStatement(elsePart, shortIf: shortIf);
+ }
+ if (!shortIf && elsePart == null) {
+ write('}');
+ }
+ }
+ else if (stmt is LabeledStatement) {
+ write(stmt.label);
+ write(':');
+ writeStatement(stmt.statement, shortIf: shortIf);
+ }
+ else if (stmt is Rethrow) {
+ write('rethrow;');
+ }
+ else if (stmt is Return) {
+ write('return');
+ if (stmt.expression != null) {
+ write(' ');
+ writeExp(stmt.expression, EXPRESSION);
+ }
+ write(';');
+ }
+ else if (stmt is Switch) {
+ write('switch(');
+ writeExp(stmt.expression, EXPRESSION);
+ write('){');
+ for (SwitchCase caze in stmt.cases) {
+ if (caze.isDefaultCase) {
+ write('default:');
+ } else {
+ for (Expression exp in caze.expressions) {
+ write('case ');
+ writeExp(exp, EXPRESSION);
+ write(':');
+ }
+ }
+ if (caze.statements.isEmpty) {
+ write(';'); // Prevent fall-through.
+ } else {
+ caze.statements.forEach(writeBlockMember);
+ }
+ }
+ write('}');
+ }
+ else if (stmt is Try) {
+ write('try');
+ writeBlock(stmt.tryBlock);
+ for (CatchBlock block in stmt.catchBlocks) {
+ if (block.onType != null) {
+ write('on ');
+ writeType(block.onType);
+ }
+ if (block.exceptionVar != null) {
+ write('catch(');
+ write(block.exceptionVar);
+ if (block.stackVar != null) {
+ write(',');
+ write(block.stackVar);
+ }
+ write(')');
+ }
+ writeBlock(block.body);
+ }
+ if (stmt.finallyBlock != null) {
+ write('finally');
+ writeBlock(stmt.finallyBlock);
+ }
+ }
+ else if (stmt is VariableDefinitions) {
+ writeVariableDefinitions(stmt);
+ write(';');
+ }
+ else if (stmt is FunctionStatement) {
+ if (stmt.returnType != null) {
+ writeType(stmt.returnType);
+ write(' ');
+ }
+ write(stmt.name);
+ writeParameters(stmt.parameters);
+ Statement body = unfoldBlocks(stmt.body);
+ if (body is Return) {
+ write('=> '); // TODO(asgerf): Minimize use of whitespace.
+ writeExp(body.expression, EXPRESSION);
+ write(';');
+ } else {
+ writeBlock(body);
+ }
+ }
+ }
+
+ /// Writes a variable definition statement without the trailing semicolon
+ void writeVariableDefinitions(VariableDefinitions vds) {
+ if (vds.isConst)
+ write('const ');
+ else if (vds.isFinal)
+ write('final ');
+ if (vds.type != null) {
+ writeType(vds.type);
+ write(' ');
+ }
+ if (!vds.isConst && !vds.isFinal && vds.type == null) {
+ write('var ');
+ }
+ writeEach(',', vds.definitions, (VariableDefinition vd) {
+ write(vd.name);
+ if (vd.initializer != null) {
+ write('=');
+ writeExp(vd.initializer, EXPRESSION);
+ }
+ });
+ }
+
+ /// True of statements that introduce variables in the scope of their
+ /// surrounding block. Blocks containing such statements cannot be unfolded.
+ bool definesVariable(Statement s) {
+ return s is VariableDefinitions || s is FunctionStatement;
+ }
+
+ /// Writes the given statement in a context where only blocks are allowed.
+ void writeBlock(Statement stmt) {
+ if (stmt is Block) {
+ writeStatement(stmt);
+ } else {
+ write('{');
+ writeBlockMember(stmt);
+ write('}');
+ }
+ }
+
+ /// Outputs a statement that is a member of a block statement (or a similar
+ /// sequence of statements, such as in switch statement).
+ /// This will flatten blocks and skip empty statement.
+ void writeBlockMember(Statement stmt) {
+ if (stmt is Block && !stmt.statements.any(definesVariable)) {
+ stmt.statements.forEach(writeBlockMember);
+ }
+ else if (stmt is EmptyStatement) {
+ // do nothing
+ }
+ else {
+ writeStatement(stmt);
+ }
+ }
+
+ void writeType(TypeAnnotation type) {
+ write(type.name);
+ if (type.typeArguments != null && type.typeArguments.length > 0) {
+ write('<');
+ writeEach(',', type.typeArguments, writeType);
+ write('>');
+ }
+ }
+
+ void writeStringLiteral(Expression node) {
+ // TODO(asgerf): This might be a bit too expensive. Benchmark.
+ // Flatten the StringConcat tree.
+ List parts = []; // Expression or int (char node)
+ void collectParts(Expression e) {
+ if (e is StringConcat) {
+ e.expressions.forEach(collectParts);
+ } else if (e is Literal && e.value is dart2js.StringConstant) {
+ for (int char in e.value.value) {
+ parts.add(char);
+ }
+ } else if (e is CallMethod &&
+ e.object is Expression && // Do not match super.toString()
+ e.methodName == "toString" &&
+ e.arguments.length == 0) {
+ // ${e.toString()} ==> ${e}
+ collectParts(e.object);
+ } else {
+ parts.add(e);
+ }
+ }
+ collectParts(node);
+
+ // We use a dynamic algorithm to compute the optimal way of printing
+ // the string literal.
+ //
+ // Using string juxtapositions, it is possible to switch from one quoting
+ // to another, e.g. the constant "''''" '""""' uses this trick.
+ //
+ // As we move through the string from left to right, we maintain a strategy
+ // for each StringQuoting Q, denoting the best way to print the current
+ // prefix so that we end with a string literal quoted with Q.
+ // At every step, each strategy is either:
+ // 1) Updated to include the cost of printing the next character.
+ // 2) Abandoned because it is cheaper to use another strategy as prefix,
+ // and then switching quotation using a juxtaposition.
+
+ // Create initial scores for each StringQuoting and index them
+ // into raw/non-raw and single-quote/double-quote.
+ List<OpenStringChunk> best = <OpenStringChunk>[];
+ List<int> raws = <int>[];
+ List<int> nonRaws = <int>[];
+ List<int> sqs = <int>[];
+ List<int> dqs = <int>[];
+ for (tree.StringQuoting q in tree.StringQuoting.mapping) {
+ // Ignore multiline quotings for now. Encoding of line breaks is unclear.
+ // TODO(asgerf): Include multiline quotation schemes.
+ if (q.leftQuoteCharCount >= 3)
+ continue;
+ 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.
+ int index = best.length;
+ best.add(chunk);
+
+ if (q.raw) {
+ raws.add(index);
+ } else {
+ nonRaws.add(index);
+ }
+ if (q.quote == characters.$SQ) {
+ sqs.add(index);
+ } else {
+ dqs.add(index);
+ }
+ }
+
+ /// True if [x] is a letter, digit, or underscore.
+ /// Such characters may not follow a shorthand string interpolation.
+ bool isIdentifierPartNoDollar(dynamic x) {
+ if (x is! int)
+ return false;
+ return (characters.$0 <= x && x <= characters.$9)
+ || (characters.$A <= x && x <= characters.$Z)
+ || (characters.$a <= x && x <= characters.$z)
+ || (x == characters.$_);
+ }
+
+ // Iterate through the string and update the score for each StringQuoting.
+ 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.
+ /// Applies additional cost to each track in [penalized], and considers
+ /// switching from each [penalized] to a [nonPenalized] track.
+ void penalize(List<int> penalized,
+ List<int> nonPenalized,
+ num cost(tree.StringQuoting q)) {
+ for (int j in penalized) {
+ // Check if another track can benefit from switching from this track.
+ for (int k in nonPenalized) {
+ num newCost = best[j].cost
+ + 1 // Whitespace in string juxtaposition
+ + getQuoteCost(best[k].quoting);
+ if (newCost < best[k].cost) {
+ best[k] = new OpenStringChunk(
+ 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.
+ best[k].quoting,
+ newCost);
+ }
+ }
+ best[j].cost += cost(best[j].quoting);
+ }
+ }
+
+ var part = parts[i];
+ if (part is int) {
+ int char = part;
+ switch (char) {
+ case characters.$$:
+ case characters.$BACKSLASH:
+ penalize(nonRaws, raws, (q) => 1);
+ break;
+ case characters.$DQ:
+ penalize(dqs, sqs, (q) => q.raw ? double.INFINITY : 1);
+ break;
+ case characters.$SQ:
+ penalize(sqs, dqs, (q) => q.raw ? double.INFINITY : 1);
+ break;
+ case NEWLINE:
+ case CARRIAGE_RETURN:
+ penalize(raws, nonRaws, (q) => double.INFINITY);
+ break;
+ }
+ } else {
+ // Penalize raw literals for string interpolation.
+ penalize(raws, nonRaws, (q) => double.INFINITY);
+
+ // Splitting a string can sometimes allow us to use a shorthand
+ // string interpolation that would otherwise be illegal.
+ // E.g. "...${foo}x..." -> "...$foo" 'x...'
+ // If are other factors that make splitting advantageous,
+ // we can gain even more by doing the split here.
+ if (part is Identifier &&
+ !part.name.contains(r'$') &&
+ i + 1 < parts.length &&
+ isIdentifierPartNoDollar(parts[i+1])) {
+ for (int j in nonRaws) {
+ for (int k=0; k<best.length; k++) {
+ num newCost = best[j].cost
+ + 1 // Whitespace in string juxtaposition
+ - 2 // Save two curly braces
+ + getQuoteCost(best[k].quoting);
+ if (newCost < best[k].cost) {
+ best[k] = new OpenStringChunk(
+ best[j].end(i+1),
+ best[k].quoting,
+ newCost);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Select the cheapest strategy
+ OpenStringChunk bestChunk = best[0];
+ for (OpenStringChunk chunk in best) {
+ if (chunk.cost < bestChunk.cost) {
+ bestChunk = chunk;
+ }
+ }
+
+ void printChunk(StringChunk chunk) {
+ int startIndex;
+ if (chunk.previous != null) {
+ printChunk(chunk.previous);
+ write(' '); // String juxtaposition requires a space between literals.
+ startIndex = chunk.previous.endIndex;
+ } else {
+ startIndex = 0;
+ }
+ if (chunk.quoting.raw) {
+ write('r');
+ }
+ write(chunk.quoting.quoteChar);
+ bool raw = chunk.quoting.raw;
+ int quoteCode = chunk.quoting.quote;
+ for (int i=startIndex; i<chunk.endIndex; i++) {
+ var part = parts[i];
+ if (part is int) {
+ int char = part;
+ switch (char) {
+ case characters.$$:
+ if (raw)
+ write(r'$');
+ else
+ write(r'\$');
+ break;
+ case characters.$BACKSLASH:
+ if (raw)
+ write(r'\');
+ else
+ write(r'\\');
+ break;
+ case characters.$DQ:
+ if (quoteCode == char) {
+ write(r'\"');
+ } else {
+ write(r'"');
+ }
+ break;
+ case characters.$SQ:
+ if (quoteCode == char) {
+ write(r"\'");
+ } else {
+ write(r"'");
+ }
+ break;
+ case NEWLINE:
+ write(r'\n');
+ break;
+ case CARRIAGE_RETURN:
+ write(r'\r');
+ break;
+ default:
+ write(new String.fromCharCode(char));
+ }
+ } else if (part is Identifier &&
+ !part.name.contains(r'$') &&
+ (i == chunk.endIndex - 1 ||
+ !isIdentifierPartNoDollar(parts[i+1]))) {
+ write(r'$');
+ write(part.name);
+ } else {
+ write(r'${');
+ writeExpression(part);
+ write('}');
+ }
+ }
+ write(chunk.quoting.quoteChar);
+ }
+ printChunk(bestChunk.end(parts.length));
+ }
+
+}
+
+
+/// Strategy for printing a prefix of a string literal.
+/// A chunk represents the substring going from [:previous.endIndex:] to
+/// [endIndex] (or from 0 to [endIndex] if [previous] is null).
+class StringChunk {
+ final StringChunk previous;
+ final tree.StringQuoting quoting;
+ final int endIndex;
+
+ StringChunk(this.previous, this.quoting, this.endIndex);
+}
+
+/// [StringChunk] that has not yet been assigned an [endIndex].
+/// It additionally has a [cost] denoting the number of auxilliary characters
+/// (quotes, spaces, etc) needed to print the literal using this strategy
+class OpenStringChunk {
+ final StringChunk previous;
+ final tree.StringQuoting quoting;
+ num cost;
+
+ OpenStringChunk(this.previous, this.quoting, this.cost);
+
+ StringChunk end(int endIndex) {
+ return new StringChunk(previous, quoting, endIndex);
+ }
+}
« 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