Index: sdk/lib/_internal/compiler/implementation/dart_backend/backend_ast_nodes.dart |
diff --git a/sdk/lib/_internal/compiler/implementation/dart_backend/backend_ast_nodes.dart b/sdk/lib/_internal/compiler/implementation/dart_backend/backend_ast_nodes.dart |
deleted file mode 100644 |
index 9048405f357a9a5e2644ad3e0d5dc3fa1b62d43d..0000000000000000000000000000000000000000 |
--- a/sdk/lib/_internal/compiler/implementation/dart_backend/backend_ast_nodes.dart |
+++ /dev/null |
@@ -1,1544 +0,0 @@ |
-// 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. |
- |
-library backend_ast_nodes; |
- |
-import '../constants/values.dart' as values; |
-import '../dart_types.dart' as types; |
-import '../elements/elements.dart' as elements; |
-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 |
-/// 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; |
- |
- types.DartType dartType; |
- |
- TypeAnnotation(this.name, [this.typeArguments = const <TypeAnnotation>[]]); |
-} |
- |
-// 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 [VariableDeclarations] or [Expression] or null. |
- For(this.initializer, this.condition, this.updates, this.body) { |
- assert(initializer == null |
- || initializer is VariableDeclarations |
- || initializer is Expression); |
- } |
-} |
- |
-class ForIn extends Statement { |
- final Node leftHandValue; |
- final Expression expression; |
- final Statement body; |
- |
- /// [leftHandValue] must be [Identifier] or [VariableDeclarations] 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 VariableDeclarations |
- && leftHandValue.declarations.length == 1 |
- && leftHandValue.declarations[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; |
- 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; |
- final List<Statement> statements; |
- |
- SwitchCase(this.expressions, this.statements); |
- SwitchCase.defaultCase(this.statements) : expressions = null; |
- |
- bool get isDefaultCase => expressions == null; |
-} |
- |
-/// A try statement. The try, catch and finally blocks will automatically |
-/// be printed inside a block statement if necessary. |
-class Try extends Statement { |
- final Statement tryBlock; |
- 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 VariableDeclarations extends Statement { |
- final TypeAnnotation type; |
- final bool isFinal; |
- final bool isConst; |
- final List<VariableDeclaration> declarations; |
- |
- VariableDeclarations(this.declarations, |
- { this.type, |
- this.isFinal: false, |
- this.isConst: false }) { |
- // Cannot be both final and const. |
- assert(!isFinal || !isConst); |
- } |
-} |
- |
-class VariableDeclaration extends Node { |
- final String name; |
- final Expression initializer; |
- |
- elements.Element element; |
- |
- VariableDeclaration(this.name, [this.initializer]); |
-} |
- |
- |
-class FunctionDeclaration extends Statement { |
- final FunctionExpression function; |
- |
- TypeAnnotation get returnType => function.returnType; |
- Parameters get parameters => function.parameters; |
- String get name => function.name; |
- Statement get body => function.body; |
- |
- FunctionDeclaration(this.function); |
-} |
- |
-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; |
- |
- Expression defaultValue; |
- |
- /// Parameters to function parameter. Null for non-function parameters. |
- final Parameters parameters; |
- |
- elements.FormalElement element; |
- |
- 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; |
-} |
- |
-// EXPRESSIONS |
- |
-class FunctionExpression extends Expression { |
- final TypeAnnotation returnType; |
- String name; |
- final Parameters parameters; |
- final Statement body; |
- final bool isGetter; |
- final bool isSetter; |
- |
- elements.FunctionElement element; |
- |
- FunctionExpression(this.parameters, |
- this.body, |
- { this.name, |
- this.returnType, |
- this.isGetter: false, |
- this.isSetter: false }) { |
- // Function must have a name if it has a return type |
- assert(returnType == null || name != null); |
- } |
-} |
- |
-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. |
-/// For the time being, this class is also used to reference static fields and |
-/// top-level variables that are qualified with a class and/or library name, |
-/// assuming the [element] is set. This is likely to change when the old backend |
-/// is replaced. |
-class Identifier extends Expression { |
- final String name; |
- |
- elements.Element element; |
- |
- Identifier(this.name); |
- |
- bool get assignable => true; |
-} |
- |
-class Literal extends Expression { |
- final values.PrimitiveConstantValue 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); |
-} |
- |
-/// A type literal. This is distinct from [Identifier] since the unparser |
-/// needs to this distinguish a static invocation from a method invocation |
-/// on a type literal. |
-class LiteralType extends Expression { |
- final String name; |
- |
- types.DartType type; |
- |
- LiteralType(this.name); |
-} |
- |
-/// Reference to a type variable. |
-/// This is distinct from [Identifier] since the unparser needs to this |
-/// distinguish a function invocation `T()` from a type variable invocation |
-/// `(T)()` (the latter is invalid, but must be generated anyway). |
-class ReifyTypeVar extends Expression { |
- final String name; |
- |
- elements.TypeVariableElement element; |
- |
- ReifyTypeVar(this.name); |
-} |
- |
-/// 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. |
-/// 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; |
- |
- elements.FunctionElement constructor; |
- types.DartType dartType; |
- |
- 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; |
- |
- elements.Element element; |
- |
- 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 operator; |
- final Expression right; |
- |
- BinaryOperator(this.left, this.operator, this.right) { |
- assert(isBinaryOperator(operator)); |
- } |
-} |
- |
-/// Expression of form `e is T` or `e is! T` or `e as T`. |
-class TypeOperator extends Expression { |
- final Expression expression; |
- final String operator; |
- final TypeAnnotation type; |
- |
- TypeOperator(this.expression, this.operator, this.type) { |
- assert(operator == 'is' |
- || operator == 'as' |
- || operator == 'is!'); |
- } |
-} |
- |
-class Increment extends Expression { |
- final Expression expression; |
- final String operator; |
- final bool isPrefix; |
- |
- Increment(this.expression, this.operator, this.isPrefix) { |
- assert(operator == '++' || operator == '--'); |
- assert(expression.assignable); |
- } |
- |
- Increment.prefix(Expression expression, String operator) |
- : this(expression, operator, true); |
- |
- Increment.postfix(Expression expression, String operator) |
- : this(expression, operator, false); |
-} |
- |
-class Assignment extends Expression { |
- static final _operators = |
- new Set.from(['=', '|=', '^=', '&=', '<<=', '>>=', |
- '+=', '-=', '*=', '/=', '%=', '~/=']); |
- |
- final Expression left; |
- final String operator; |
- final Expression right; |
- |
- Assignment(this.left, this.operator, this.right) { |
- assert(_operators.contains(operator)); |
- 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 BINARY_PRECEDENCE.containsKey(op); |
-} |
-/// True if the given operator can be converted to a compound assignment. |
-bool isCompoundableOperator(String op) { |
- switch (BINARY_PRECEDENCE[op]) { |
- case BITWISE_OR: |
- case BITWISE_XOR: |
- case BITWISE_AND: |
- case SHIFT: |
- case ADDITIVE: |
- case MULTIPLICATIVE: |
- return true; |
- default: |
- return false; |
- } |
-} |
- |
- |
-// Precedence levels |
-const int EXPRESSION = 1; |
-const int CONDITIONAL = 2; |
-const int LOGICAL_OR = 3; |
-const int LOGICAL_AND = 4; |
-const int EQUALITY = 6; |
-const int RELATIONAL = 7; |
-const int BITWISE_OR = 8; |
-const int BITWISE_XOR = 9; |
-const int BITWISE_AND = 10; |
-const int SHIFT = 11; |
-const int ADDITIVE = 12; |
-const int MULTIPLICATIVE = 13; |
-const int UNARY = 14; |
-const int POSTFIX_INCREMENT = 15; |
-const int TYPE_LITERAL = 19; |
-const int PRIMARY = 20; |
- |
-/// Precedence level required for the callee in a [FunctionCall]. |
-const int CALLEE = 21; |
- |
-const Map<String,int> BINARY_PRECEDENCE = 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, |
-}; |
- |
-/// Return true if binary operators with the given precedence level are |
-/// (left) associative. False if they are non-associative. |
-bool isAssociativeBinaryOperator(int precedence) { |
- return precedence != EQUALITY && precedence != RELATIONAL; |
-} |
- |
-/// 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.$_); |
-} |
- |
-/// 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 |
-/// |
-/// 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); |
- |
- |
- 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(sigurdm,kmillikin): 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)) { |
- 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(kmillikin,sigurdm): it might be faster to use a Visitor. |
- 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) { |
- assert(!e.isGetter && !e.isSetter); |
- Statement stmt = unfoldBlocks(e.body); |
- int precedence = stmt is Return ? EXPRESSION : PRIMARY; |
- withPrecedence(precedence, () { |
- // A named function expression at the beginning of a statement |
- // can be mistaken for a function declaration. |
- // (Note: Functions with a return type also have a name) |
- bool needParen = beginStmt && e.name != null; |
- if (needParen) { |
- write('('); |
- } |
- if (e.returnType != null) { |
- writeType(e.returnType); |
- write(' '); |
- } |
- if (e.name != null) { |
- write(e.name); |
- } |
- writeParameters(e.parameters); |
- // TODO(sigurdm,kmillikin): Print {} for "return null;" |
- if (stmt is Return) { |
- write('=> '); |
- writeExp(stmt.expression, EXPRESSION); |
- } else { |
- writeBlock(stmt); |
- } |
- if (needParen) { |
- write(')'); |
- } |
- }); |
- } else if (e is Conditional) { |
- withPrecedence(CONDITIONAL, () { |
- writeExp(e.condition, LOGICAL_OR, beginStmt: beginStmt); |
- write(' ? '); |
- 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.isString) { |
- writeStringLiteral(e); |
- } |
- else if (e.value.isDouble) { |
- double v = e.value.primitiveValue; |
- if (v == double.INFINITY) { |
- withPrecedence(MULTIPLICATIVE, () { |
- write('1/0.0'); |
- }); |
- } else if (v == double.NEGATIVE_INFINITY) { |
- withPrecedence(MULTIPLICATIVE, () { |
- write('-1/0.0'); |
- }); |
- } else if (v.isNaN) { |
- withPrecedence(MULTIPLICATIVE, () { |
- write('0/0.0'); |
- }); |
- } else { |
- write(v.toString()); |
- } |
- } else { |
- // TODO(sigurdm): Use [ConstExp] to generate valid code for any |
- // constant. |
- write(e.value.unparse()); |
- } |
- } else if (e is LiteralList) { |
- if (e.isConst) { |
- write(' const '); |
- } |
- 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 '); |
- needParen = false; |
- } |
- if (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(' : '); |
- writeExp(en.value, EXPRESSION); |
- }); |
- write('}'); |
- if (needParen) { |
- write(')'); |
- } |
- } else if (e is LiteralSymbol) { |
- write('#'); |
- write(e.id); |
- } else if (e is LiteralType) { |
- withPrecedence(TYPE_LITERAL, () { |
- write(e.name); |
- }); |
- } else if (e is ReifyTypeVar) { |
- withPrecedence(PRIMARY, () { |
- write(e.name); |
- }); |
- } 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.operator == '==') { |
- 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.operator == 'is') { |
- withPrecedence(RELATIONAL, () { |
- writeExp(operand.expression, BITWISE_OR, beginStmt: beginStmt); |
- write(' is!'); |
- writeType(operand.type); |
- }); |
- } |
- else { |
- withPrecedence(UNARY, () { |
- writeOperator(e.operatorName); |
- writeExp(e.operand, UNARY); |
- }); |
- } |
- } else if (e is BinaryOperator) { |
- int precedence = BINARY_PRECEDENCE[e.operator]; |
- 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.operator); |
- writeExp(e.right, precedence + 1); |
- }); |
- } else if (e is TypeOperator) { |
- withPrecedence(RELATIONAL, () { |
- writeExp(e.expression, BITWISE_OR, beginStmt: beginStmt); |
- write(' '); |
- write(e.operator); |
- write(' '); |
- writeType(e.type); |
- }); |
- } else if (e is Assignment) { |
- withPrecedence(EXPRESSION, () { |
- writeExp(e.left, PRIMARY, beginStmt: beginStmt); |
- writeOperator(e.operator); |
- 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(' '); |
- 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.operator); |
- writeExp(e.expression, PRIMARY); |
- } else { |
- writeExp(e.expression, PRIMARY, beginStmt: beginStmt); |
- write(e.operator); |
- } |
- }); |
- } 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 VariableDeclarations) { |
- 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 VariableDeclarations); |
- } |
- 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 '); |
- 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 VariableDeclarations) { |
- writeVariableDefinitions(stmt); |
- write(';'); |
- } else if (stmt is FunctionDeclaration) { |
- assert(!stmt.function.isGetter && !stmt.function.isSetter); |
- if (stmt.returnType != null) { |
- writeType(stmt.returnType); |
- write(' '); |
- } |
- write(stmt.name); |
- writeParameters(stmt.parameters); |
- Statement body = unfoldBlocks(stmt.body); |
- if (body is Return) { |
- write('=> '); |
- writeExp(body.expression, EXPRESSION); |
- write(';'); |
- } else { |
- writeBlock(body); |
- } |
- } else { |
- throw "Unexpected statement: $stmt"; |
- } |
- } |
- |
- /// Writes a variable definition statement without the trailing semicolon |
- void writeVariableDefinitions(VariableDeclarations 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.declarations, (VariableDeclaration 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. |
- static bool definesVariable(Statement s) { |
- return s is VariableDeclarations || s is FunctionDeclaration; |
- } |
- |
- /// 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('>'); |
- } |
- } |
- |
- /// A list of string quotings that the printer may use to quote strings. |
- // Ignore multiline quotings for now. Would need to make sure that no |
- // newline (potentially prefixed by whitespace) follows the quoting. |
- // TODO(sigurdm,kmillikin): Include multiline quotation schemes. |
- static const _QUOTINGS = const <tree.StringQuoting>[ |
- const tree.StringQuoting(characters.$DQ, raw: false, leftQuoteLength: 1), |
- const tree.StringQuoting(characters.$DQ, raw: true, leftQuoteLength: 1), |
- const tree.StringQuoting(characters.$SQ, raw: false, leftQuoteLength: 1), |
- const tree.StringQuoting(characters.$SQ, raw: true, leftQuoteLength: 1), |
- ]; |
- |
- static StringLiteralOutput analyzeStringLiteral(Expression node) { |
- // TODO(sigurdm,kmillikin): 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.isString) { |
- for (int char in e.value.primitiveValue) { |
- parts.add(char); |
- } |
- } 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. |
- |
- int getQuoteCost(tree.StringQuoting quot) { |
- return quot.leftQuoteLength + quot.rightQuoteLength; |
- } |
- |
- // 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 _QUOTINGS) { |
- OpenStringChunk chunk = new OpenStringChunk(null, q, getQuoteCost(q)); |
- 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); |
- } |
- } |
- |
- |
- /// 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, |
- int endIndex, |
- 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(endIndex), |
- best[k].quoting, |
- newCost); |
- } |
- } |
- best[j].cost += cost(best[j].quoting); |
- } |
- } |
- |
- // Iterate through the string and update the score for each StringQuoting. |
- for (int i = 0; i < parts.length; i++) { |
- var part = parts[i]; |
- if (part is int) { |
- int char = part; |
- switch (char) { |
- case characters.$$: |
- case characters.$BACKSLASH: |
- penalize(nonRaws, raws, i, (q) => 1); |
- break; |
- case characters.$DQ: |
- penalize(dqs, sqs, i, (q) => q.raw ? double.INFINITY : 1); |
- break; |
- case characters.$SQ: |
- penalize(sqs, dqs, i, (q) => q.raw ? double.INFINITY : 1); |
- break; |
- case characters.$LF: |
- case characters.$CR: |
- case characters.$FF: |
- case characters.$BS: |
- case characters.$VTAB: |
- case characters.$TAB: |
- case characters.$EOF: |
- penalize(raws, nonRaws, i, (q) => double.INFINITY); |
- break; |
- } |
- } else { |
- // Penalize raw literals for string interpolation. |
- penalize(raws, nonRaws, i, (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; |
- } |
- } |
- |
- return new StringLiteralOutput(parts, bestChunk.end(parts.length)); |
- } |
- |
- void writeStringLiteral(Expression node) { |
- StringLiteralOutput output = analyzeStringLiteral(node); |
- List parts = output.parts; |
- 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; |
- write(getEscapedCharacter(char, quoteCode, raw)); |
- } 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(output.chunk); |
- } |
- |
- static String getEscapedCharacter(int char, int quoteCode, bool raw) { |
- switch (char) { |
- case characters.$$: |
- return raw ? r'$' : r'\$'; |
- case characters.$BACKSLASH: |
- return raw ? r'\' : r'\\'; |
- case characters.$DQ: |
- return quoteCode == char ? r'\"' : r'"'; |
- case characters.$SQ: |
- return quoteCode == char ? r"\'" : r"'"; |
- case characters.$LF: |
- return r'\n'; |
- case characters.$CR: |
- return r'\r'; |
- case characters.$FF: |
- return r'\f'; |
- case characters.$BS: |
- return r'\b'; |
- case characters.$TAB: |
- return r'\t'; |
- case characters.$VTAB: |
- return r'\v'; |
- case characters.$EOF: |
- return r'\x00'; |
- default: |
- return new String.fromCharCode(char); |
- } |
- } |
- |
-} |
- |
-/// The contents of a string literal together with a strategy for printing it. |
-class StringLiteralOutput { |
- /// Mix of [Expression] and `int`. Each expression is a string interpolation, |
- /// and each `int` is the character code of a character in a string literal. |
- final List parts; |
- final StringChunk chunk; |
- |
- StringLiteralOutput(this.parts, this.chunk); |
-} |
- |
- |
-/// 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); |
- } |
-} |