blob: bba37f415bce378784879968db1a7d54f026bdfc [file] [log] [blame]
// 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 dart_printer_test;
import "package:expect/expect.dart";
import 'package:compiler/implementation/dart_backend/dart_printer.dart';
import 'package:compiler/implementation/scanner/scannerlib.dart';
import 'package:compiler/implementation/source_file.dart';
import 'package:compiler/implementation/dart2jslib.dart';
import 'package:compiler/implementation/tree/tree.dart' show DartString;
import 'dart:mirrors';
import 'package:compiler/implementation/tree/tree.dart' as tree;
import 'package:compiler/implementation/string_validator.dart';
import 'package:compiler/implementation/dart_backend/dart_tree_printer.dart' show TreePrinter;
/// For debugging the [AstBuilder] stack. Prints information about [x].
void show(x) {
StringBuffer buf = new StringBuffer();
Unparser unparser = new Unparser(buf);
void unparse(x) {
if (x is Expression)
unparser.writeExpression(x);
else if (x is TypeAnnotation)
unparser.writeType(x);
else if (x is Statement)
unparser.writeStatement(x);
else if (x is List) {
buf.write('[');
bool first = true;
for (var y in x) {
if (first)
first = false;
else
buf.write(', ');
unparse(y);
}
buf.write(']');
}
}
unparse(x);
print("${x.runtimeType}: ${buf.toString()}");
}
class PrintDiagnosticListener implements DiagnosticListener {
void log(message) {
print(message);
}
void internalError(Spannable spannable, message) {
print(message);
}
SourceSpan spanFromSpannable(Spannable node) {
return new SourceSpan(null, 0, 0);
}
void reportFatalError(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
print(errorCode);
throw new Error();
}
void reportError(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
print(errorCode);
}
void reportWarning(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
print(errorCode);
}
void reportHint(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
print(errorCode);
}
void reportInfo(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
print(errorCode);
}
withCurrentElement(element, f()) {
f();
}
}
class AstBuilder extends Listener {
final List stack = [];
final StringValidator stringValidator
= new StringValidator(new PrintDiagnosticListener());
String asName(e) {
if (e is Identifier)
return e.name;
else if (e == null)
return null;
else
throw 'Expression is not a name: ${e.runtimeType}';
}
TypeAnnotation asType(x) {
if (x is TypeAnnotation)
return x;
if (x is Identifier)
return new TypeAnnotation(x.name);
if (x == null)
return null;
else
throw "Not a type: ${x.runtimeType}";
}
Parameter asParameter(x) {
if (x is Parameter)
return x;
if (x is Identifier)
return new Parameter(x.name);
else
throw "Not a parameter: ${x.runtimeType}";
}
void push(node) {
stack.add(node);
}
dynamic peek() {
return stack.last;
}
dynamic pop([coerce(x) = null]) {
var x = stack.removeLast();
if (coerce != null)
return coerce(x);
else
return x;
}
List popList(int count, [List result, coerce(x) = null]) {
if (result == null)
result = <Node>[];
for (int i=0; i<count; i++) {
var x = stack[stack.length-count+i];
if (coerce != null) {
x = coerce(x);
}
result.add(x);
}
stack.removeRange(stack.length-count, stack.length);
return result;
}
popTypeAnnotation() {
List<TypeAnnotation> args = pop();
if (args == null)
return null;
String name = pop(asName);
return new TypeAnnotation(name, args);
}
// EXPRESSIONS
endCascade() {
throw "Cascade not supported yet";
}
endIdentifierList(int count) {
push(popList(count, <Identifier>[]));
}
endTypeList(int count) {
push(popList(count, <TypeAnnotation>[], asType));
}
beginLiteralString(Token token) {
String source = token.value;
tree.StringQuoting quoting = StringValidator.quotingFromString(source);
push(quoting);
push(token); // collect token at the end
}
handleStringPart(Token token) {
push(token); // collect token at the end
}
endLiteralString(int interpCount) {
List parts = popList(2 * interpCount + 1, []);
tree.StringQuoting quoting = pop();
List<Expression> members = <Expression>[];
for (var i=0; i<parts.length; i++) {
var part = parts[i];
if (part is Expression) {
members.add(part);
} else {
assert(part is Token);
DartString str = stringValidator.validateInterpolationPart(
part as Token,
quoting,
isFirst: i == 0,
isLast: i == parts.length - 1);
members.add(new Literal(new StringConstant(str)));
}
}
push(new StringConcat(members));
}
handleStringJuxtaposition(int litCount) {
push(new StringConcat(popList(litCount, <Expression>[])));
}
endArguments(int count, begin, end) {
push(popList(count, <Argument>[]));
}
handleNoArguments(token) {
push(null);
}
handleNoTypeArguments(token) {
push(<TypeAnnotation>[]);
}
endTypeArguments(int count, t, y) {
List<TypeAnnotation> args = <TypeAnnotation>[];
for (var i=0; i<count; i++) {
args.add(popTypeAnnotation());
}
push(args.reversed.toList(growable:false));
}
handleVoidKeyword(token) {
push(new Identifier("void"));
push(<TypeAnnotation>[]); // prepare for popTypeAnnotation
}
handleQualified(Token period) {
String last = pop(asName);
String first = pop(asName);
push(new Identifier('$first.$last'));
}
endSend(t) {
List<Argument> arguments = pop();
if (arguments == null)
return; // not a function call
Expression selector = pop();
push(new CallFunction(selector, arguments));
}
endThrowExpression(t, tt) {
push(new Throw(pop()));
}
handleAssignmentExpression(Token token) {
Expression right = pop();
Expression left = pop();
push(new Assignment(left, token.value, right));
}
handleBinaryExpression(Token token) {
Expression right = pop();
Receiver left = pop();
String tokenString = token.stringValue;
if (tokenString == '.') {
if (right is CallFunction) {
String name = (right.callee as Identifier).name;
push(new CallMethod(left, name, right.arguments));
} else {
push(new FieldExpression(left, (right as Identifier).name));
}
} else {
push(new BinaryOperator(left, tokenString, right));
}
}
handleConditionalExpression(question, colon) {
Expression elseExpression = pop();
Expression thenExpression = pop();
Expression condition = pop();
push(new Conditional(condition, thenExpression, elseExpression));
}
handleIdentifier(Token t) {
push(new Identifier(t.value));
}
handleOperator(t) {
push(new Identifier(t.value));
}
handleIndexedExpression(open, close) {
Expression index = pop();
Receiver object = pop();
push(new IndexExpression(object, index));
}
handleIsOperator(operathor, not, endToken) {
TypeAnnotation type = popTypeAnnotation();
Expression exp = pop();
TypeOperator r = new TypeOperator(exp, 'is', type);
if (not != null) {
push(new UnaryOperator('!', r));
} else {
push(r);
}
}
handleAsOperator(operathor, endToken) {
TypeAnnotation type = popTypeAnnotation();
Expression exp = pop();
push(new TypeOperator(exp, 'as', type));
}
handleLiteralBool(Token t) {
bool value = t.value == 'true';
push(new Literal(value ? new TrueConstant() : new FalseConstant()));
}
handleLiteralDouble(t) {
push(new Literal(new DoubleConstant(double.parse(t.value))));
}
handleLiteralInt(Token t) {
push(new Literal(new IntConstant(int.parse(t.value))));
}
handleLiteralNull(t) {
push(new Literal(new NullConstant()));
}
endLiteralSymbol(Token hash, int idCount) {
List<Identifier> ids = popList(idCount, <Identifier>[]);
push(new LiteralSymbol(ids.map((id) => id.name).join('.')));
}
handleLiteralList(int count, begin, constKeyword, end) {
List<Expression> exps = popList(count, <Expression>[]);
List<TypeAnnotation> types = pop();
assert(types.length <= 1);
push(new LiteralList(exps,
isConst: constKeyword != null,
typeArgument: types.length == 0 ? null : types[0]
));
}
handleLiteralMap(int count, begin, constKeyword, end) {
List<LiteralMapEntry> entries = popList(count, <LiteralMapEntry>[]);
List<TypeAnnotation> types = pop();
assert(types.length == 0 || types.length == 2);
push(new LiteralMap(entries,
isConst: constKeyword != null,
typeArguments: types
));
}
endLiteralMapEntry(colon, endToken) {
Expression value = pop();
Expression key = pop();
push(new LiteralMapEntry(key,value));
}
handleNamedArgument(colon) {
Expression exp = pop();
Identifier name = pop();
push(new NamedArgument(name.name, exp));
}
endConstructorReference(Token start, Token period, Token end) {
if (period == null) {
push(null); // indicate missing constructor name
}
}
handleNewExpression(t) {
List<Argument> args = pop();
String constructorName = pop(asName);
TypeAnnotation type = popTypeAnnotation();
push(new CallNew(type, args, constructorName: constructorName));
}
handleConstExpression(t) {
List<Argument> args = pop();
String constructorName = pop(asName);
TypeAnnotation type = popTypeAnnotation();
push(new CallNew(type, args, constructorName: constructorName,
isConst:true));
}
handleParenthesizedExpression(t) {
// do nothing, just leave expression on top of stack
}
handleSuperExpression(t) {
push(new SuperReceiver());
}
handleThisExpression(t) {
push(new This());
}
handleUnaryPostfixAssignmentExpression(Token t) {
push(new Increment.postfix(pop(), t.value));
}
handleUnaryPrefixAssignmentExpression(Token t) {
push(new Increment.prefix(pop(), t.value));
}
handleUnaryPrefixExpression(Token t) {
push(new UnaryOperator(t.value, pop()));
}
handleFunctionTypedFormalParameter(tok) {
// handled in endFormalParameter
}
endFormalParameter(thisKeyword) {
Expression defaultValue = null;
var x = pop();
if (x is DefaultValue) {
defaultValue = x.expression;
x = pop();
}
if (x is Parameters) {
String name = pop(asName);
TypeAnnotation returnType = popTypeAnnotation();
push(new Parameter.function(name, returnType, x, defaultValue));
} else {
String name = asName(x);
TypeAnnotation type = popTypeAnnotation();
push(new Parameter(name, type:type, defaultValue:defaultValue));
}
}
handleValuedFormalParameter(eq, tok) {
push(new DefaultValue(pop()));
}
endOptionalFormalParameters(int count, begin, end) {
bool isNamed = end.value == '}';
push(popList(count, <Parameter>[], asParameter));
push(isNamed); // Indicate optional parameters to endFormalParameters.
}
endFormalParameters(count, begin, end) {
if (count == 0) {
push(new Parameters([]));
return;
}
var last = pop(); // Detect if optional parameters are present.
if (last is bool) { // See endOptionalFormalParameters.
List<Parameter> optional = pop();
List<Parameter> required = popList(count-1, <Parameter>[], asParameter);
push(new Parameters(required, optional, last));
} else {
// No optional parameters.
List<Parameter> required = popList(count-1, <Parameter>[], asParameter);
required.add(last);
push(new Parameters(required));
}
}
handleNoFormalParameters(tok) {
push(new Parameters([]));
}
endUnamedFunction(t) {
Statement body = pop();
Parameters parameters = pop();
push(new FunctionExpression(parameters, body));
}
handleNoType(Token token) {
push(null);
}
endReturnStatement(bool hasExpression, begin, end) {
// This is also called for functions whose body is "=> expression"
if (hasExpression) {
push(new Return(pop()));
} else {
push(new Return());
}
}
endExpressionStatement(Token token) {
push(new ExpressionStatement(pop()));
}
endDoWhileStatement(Token doKeyword, Token whileKeyword, Token end) {
Expression condition = pop();
Statement body = pop();
push(new DoWhile(body, condition));
}
endWhileStatement(Token whileKeyword, Token end) {
Statement body = pop();
Expression condition = pop();
push(new While(condition, body));
}
endBlock(int count, Token begin, Token end) {
push(new Block(popList(count, <Statement>[])));
}
endRethrowStatement(Token throwToken, Token endToken) {
push(new Rethrow());
}
endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) {
Statement finallyBlock = null;
if (finallyKeyword != null) {
finallyBlock = pop();
}
List<CatchBlock> catchBlocks = popList(catchCount, <CatchBlock>[]);
Statement tryBlock = pop();
push(new Try(tryBlock, catchBlocks, finallyBlock));
}
void handleCatchBlock(Token onKeyword, Token catchKeyword) {
Statement block = pop();
String exceptionVar = null;
String stackVar = null;
if (catchKeyword != null) {
Parameters params = pop();
exceptionVar = params.requiredParameters[0].name;
if (params.requiredParameters.length > 1) {
stackVar = params.requiredParameters[1].name;
}
}
TypeAnnotation type = onKeyword == null ? null : pop();
push(new CatchBlock(block,
onType: type,
exceptionVar: exceptionVar,
stackVar: stackVar
));
}
endSwitchStatement(Token switchKeyword, Token end) {
List<SwitchCase> cases = pop();
Expression expression = pop();
push(new Switch(expression, cases));
}
endSwitchBlock(int caseCount, Token begin, Token end) {
push(popList(caseCount, <SwitchCase>[]));
}
handleSwitchCase(int labelCount, int caseCount, Token defaultKeyword,
int statementCount, Token first, Token end) {
List<Statement> statements = popList(statementCount, <Statement>[]);
List<Expression> cases = popList(caseCount, <Expression>[]);
if (defaultKeyword != null) {
cases = null;
}
push(new SwitchCase(cases, statements));
}
handleCaseMatch(Token caseKeyword, Token colon) {
// do nothing, leave case expression on stack
}
handleBreakStatement(bool hasTarget, Token breakKeyword, Token end) {
String target = hasTarget ? pop(asName) : null;
push(new Break(target));
}
handleContinueStatement(bool hasTarget, Token continueKeyword, Token end) {
String target = hasTarget ? pop(asName) : null;
push(new Continue(target));
}
handleEmptyStatement(Token token) {
push(new EmptyStatement());
}
VariableDeclaration asVariableDeclaration(x) {
if (x is VariableDeclaration)
return x;
if (x is Identifier)
return new VariableDeclaration(x.name);
throw "Not a variable definition: ${x.runtimeType}";
}
endVariablesDeclaration(int count, Token end) {
List<VariableDeclaration> variables =
popList(count, <VariableDeclaration>[], asVariableDeclaration);
TypeAnnotation type = popTypeAnnotation();
push(new VariableDeclarations(variables,
type: type,
isFinal: false, // TODO(asgerf): Parse modifiers.
isConst: false
));
}
endInitializer(Token assign) {
Expression init = pop();
String name = pop(asName);
push(new VariableDeclaration(name, init));
}
endIfStatement(Token ifToken, Token elseToken) {
Statement elsePart = (elseToken == null) ? null : pop();
Statement thenPart = pop();
Expression condition = pop();
push(new If(condition, thenPart, elsePart));
}
endForStatement(int updateCount, Token begin, Token end) {
Statement body = pop();
List<Expression> updates = popList(updateCount, <Expression>[]);
ExpressionStatement condition = pop(); // parsed as expression statement
Expression exp = condition == null ? null : condition.expression;
Node initializer = pop();
push(new For(initializer, exp, updates, body));
}
handleNoExpression(Token token) {
push(null);
}
endForIn(Token begin, Token inKeyword, Token end) {
Statement body = pop();
Expression exp = pop();
Node declaredIdentifier = pop();
push(new ForIn(declaredIdentifier, exp, body));
}
handleAssertStatement(Token assertKeyword, Token semicolonToken) {
Expression exp = pop();
Expression call = new CallFunction(new Identifier("assert"), [exp]);
push(new ExpressionStatement(call));
}
endLabeledStatement(int labelCount) {
Statement statement = pop();
for (int i=0; i<labelCount; i++) {
String label = pop(asName);
statement = new LabeledStatement(label, statement);
}
push(statement);
}
endFunctionDeclaration(Token end) {
Statement body = pop();
Parameters parameters = pop();
String name = pop(asName);
TypeAnnotation returnType = popTypeAnnotation();
push(new FunctionDeclaration(new FunctionExpression(parameters, body,
name: name,
returnType: returnType)));
}
endFunctionBody(int count, Token begin, Token end) {
push(new Block(popList(count, <Statement>[])));
}
}
class DefaultValue {
final Expression expression;
DefaultValue(this.expression);
}
/// Compares ASTs for structural equality.
void checkDeepEqual(x, y) {
if (x is List && y is List) {
if (x.length != y.length)
return;
for (var i=0; i<x.length; i++) {
checkDeepEqual(x[i], y[i]);
}
}
else if (x is Node && y is Node) {
if (x.runtimeType != y.runtimeType)
throw new Error();
InstanceMirror xm = reflect(x);
InstanceMirror ym = reflect(y);
for (Symbol name in xm.type.instanceMembers.keys) {
if (reflectClass(Object).declarations.containsKey(name)) {
continue; // do not check things from Object, such as hashCode
}
MethodMirror mm = xm.type.instanceMembers[name];
if (mm.isGetter) {
var xv = xm.getField(name).reflectee;
var yv = ym.getField(name).reflectee;
checkDeepEqual(xv,yv);
}
}
}
else if (x is PrimitiveConstant && y is PrimitiveConstant) {
checkDeepEqual(x.value, y.value);
}
else if (x is DartString && y is DartString) {
if (x.slowToString() != y.slowToString()) {
throw new Error();
}
}
else {
if (x != y) {
throw new Error();
}
}
}
Expression parseExpression(String code) {
SourceFile file = new StringSourceFile('', code);
Scanner scan = new Scanner(file);
Token tok = scan.tokenize();
AstBuilder builder = new AstBuilder();
Parser parser = new Parser(builder);
tok = parser.parseExpression(tok);
if (builder.stack.length != 1 || tok.kind != EOF_TOKEN) {
throw "Parse error in $code";
}
return builder.pop();
}
Statement parseStatement(String code) {
SourceFile file = new StringSourceFile('', code);
Scanner scan = new Scanner(file);
Token tok = scan.tokenize();
AstBuilder builder = new AstBuilder();
Parser parser = new Parser(builder);
tok = parser.parseStatement(tok);
if (builder.stack.length != 1 || tok.kind != EOF_TOKEN) {
throw "Parse error in $code";
}
return builder.pop();
}
String unparseExpression(Expression exp) {
StringBuffer buf = new StringBuffer();
new Unparser(buf).writeExpression(exp);
return buf.toString();
}
String unparseStatement(Statement stmt) {
StringBuffer buf = new StringBuffer();
new Unparser(buf).writeStatement(stmt);
return buf.toString();
}
/// Converts [exp] to an instance of the frontend AST and unparses that.
String frontUnparseExpression(Expression exp) {
tree.Node node = new TreePrinter().makeExpression(exp);
return tree.unparse(node);
}
/// Converts [stmt] to an instance of the frontend AST and unparses that.
String frontUnparseStatement(Statement stmt) {
tree.Node node = new TreePrinter().makeStatement(stmt);
return tree.unparse(node);
}
/// Parses [code], unparses the resulting AST, then parses the unparsed text.
/// The ASTs from the first and second parse are then compared for structural
/// equality. Alternatively, if [expected] is not an empty string, the second
/// parse must match the AST of parsing [expected].
void checkFn(String code, String expected, Function parse, Function unparse) {
var firstParse = parse(code);
String unparsed = unparse(firstParse);
try {
var secondParse = parse(unparsed);
var baseline = expected == "" ? firstParse : parse(expected);
checkDeepEqual(baseline, secondParse);
} catch (e, stack) {
Expect.fail('"$code" was unparsed as "$unparsed"');
}
}
void checkExpression(String code, [String expected="", String expected2=""]) {
checkFn(code, expected, parseExpression, unparseExpression);
checkFn(code, expected2, parseExpression, frontUnparseExpression);
}
void checkStatement(String code, [String expected="", String expected2=""]) {
checkFn(code, expected, parseStatement, unparseStatement);
checkFn(code, expected2, parseStatement, frontUnparseStatement);
}
void debugTokens(String code) {
SourceFile file = new StringSourceFile('', code);
Scanner scan = new Scanner(file);
Token tok = scan.tokenize();
while (tok.next != tok) {
print(tok.toString());
tok = tok.next;
}
}
void main() {
// To check if these tests are effective, one should manually alter
// something in [Unparser] and see if a test fails.
checkExpression(" a + b + c");
checkExpression("(a + b) + c");
checkExpression(" a + (b + c)");
checkExpression(" a + b - c");
checkExpression("(a + b) - c");
checkExpression(" a + (b - c)");
checkExpression(" a - b + c");
checkExpression("(a - b) + c");
checkExpression(" a - (b + c)");
checkExpression(" a * b + c");
checkExpression("(a * b) + c");
checkExpression(" a * (b + c)");
checkExpression(" a + b * c");
checkExpression("(a + b) * c");
checkExpression(" a + (b * c)");
checkExpression(" a * b * c");
checkExpression("(a * b) * c");
checkExpression(" a * (b * c)");
checkExpression("a is T");
checkExpression("a is! T");
checkExpression("!(a is T)");
checkExpression("a is T.x");
checkExpression("a is! T.x");
checkExpression("!(a is T.x)");
checkExpression("!(a is T).x");
checkExpression("a as T.x");
checkExpression("(a as T).x");
checkExpression("a == b");
checkExpression("a != b");
checkExpression("!(a == b)", "a != b");
checkExpression("a && b ? c : d");
checkExpression("(a && b) ? c : d");
checkExpression("a && (b ? c : d)");
checkExpression("a || b ? c : d");
checkExpression("(a || b) ? c : d");
checkExpression("a || (b ? c : d)");
checkExpression(" a ? b : c && d");
checkExpression(" a ? b : (c && d)");
checkExpression("(a ? b : c) && d");
checkExpression(" a ? b : c = d");
checkExpression(" a ? b : (c = d)");
checkExpression("(a == b) == c");
checkExpression("a == (b == c)");
checkExpression(" a < b == c");
checkExpression("(a < b) == c");
checkExpression(" a < (b == c)");
checkExpression(" a == b < c");
checkExpression("(a == b) < c");
checkExpression(" a == (b < c)");
checkExpression("x.f()");
checkExpression("(x.f)()");
checkExpression("x.f()()");
checkExpression("(x.f)()()");
checkExpression("x.f().g()");
checkExpression("(x.f)().g()");
checkExpression("x.f()");
checkExpression("x.f(1 + 2)");
checkExpression("x.f(1 + 2, 3 + 4)");
checkExpression("x.f(1 + 2, foo:3 + 4)");
checkExpression("x.f(1 + 2, foo:3 + 4, bar: 5)");
checkExpression("x.f(foo:3 + 4)");
checkExpression("x.f(foo:3 + 4, bar: 5)");
checkExpression("x.f.g.h");
checkExpression("(x.f).g.h");
checkExpression("(x.f.g).h");
checkExpression(" a = b + c");
checkExpression(" a = (b + c)");
checkExpression("(a = b) + c");
checkExpression("a + (b = c)");
checkExpression("dx * dx + dy * dy < r * r",
"((dx * dx) + (dy * dy)) < (r * r)");
checkExpression("mid = left + right << 1",
"mid = ((left + right) << 1)");
checkExpression("a + b % c * -d ^ e - f ~/ x & ++y / z++ | w > a ? b : c");
checkExpression("a + b % c * -d ^ (e - f) ~/ x & ++y / z++ | w > a ? b : c");
checkExpression("'foo'");
checkExpression("'foo' 'bar'", "'foobar'");
checkExpression("{}.length");
checkExpression("{x: 1+2}.length");
checkExpression("<String,int>{}.length");
checkExpression("<String,int>{x: 1+2}.length");
checkExpression("[].length");
checkExpression("[1+2].length");
checkExpression("<num>[].length");
checkExpression("<num>[1+2].length");
checkExpression("x + -y");
checkExpression("x + --y");
checkExpression("x++ + y");
checkExpression("x + ++y");
checkExpression("x-- - y");
checkExpression("x-- - -y");
checkExpression("x - --y");
checkExpression("x && !y");
checkExpression("!x && y");
checkExpression("!(x && y)");
checkExpression(" super + 1 * 2");
checkExpression("(super + 1) * 2");
checkExpression(" super + (1 * 2)");
checkExpression("x + -super");
checkExpression("x-- - -super");
checkExpression("x - -super");
checkExpression("x && !super");
checkExpression("super.f(1, 2) + 3");
checkExpression("super.f + 3");
checkExpression(r"'foo\nbar'");
checkExpression(r"'foo\r\nbar'");
checkExpression(r"'foo\rbar'");
checkExpression(r"'foo\'bar'");
checkExpression(r"""'foo"bar'""");
checkExpression(r"r'foo\nbar'");
checkExpression("''");
checkExpression("r''");
var sq = "'";
var dq = '"';
checkExpression("'$dq$dq' \"$sq$sq\"");
checkExpression("'$dq$dq$dq$dq' \"$sq$sq$sq$sq\"");
checkExpression(r"'\$\$\$\$\$\$\$\$\$'");
checkExpression("'$dq$dq$dq' '\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n' \"$sq$sq$sq\"");
checkExpression("'$dq$dq$dq' '\\r\\r\\r\\r\\r\\r\\r\\r\\r\\r' \"$sq$sq$sq\"");
checkExpression("'$dq$dq$dq' '\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n' \"$sq$sq$sq\"");
checkExpression(r"'$foo'");
checkExpression(r"'${foo}x'");
checkExpression(r"'${foo}x\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'");
checkExpression(r"'abc' '${foo}' r'\\\\\\\'");
checkExpression(r"'${$x}'");
checkExpression(r"'${$x}y'");
checkExpression("null + null");
checkExpression("(x) => x",
'',
'(x){return x;}');
checkStatement("fn(x) => x;",
'',
'fn(x){return x;}');
checkExpression("throw x");
checkStatement("throw x;");
checkStatement("var x, y, z;");
checkStatement("final x, y, z;");
checkStatement("dynamic x, y, z;");
checkStatement("String x, y, z;");
checkStatement("List<int> x, y, z;");
checkStatement("final dynamic x, y, z;");
checkStatement("final String x, y, z;");
checkStatement("final List<int> x, y, z;");
checkStatement("var x = y, z;");
checkStatement("var x, y = z;");
checkStatement("var x = y = z;");
// Note: We sometimes have to pass an expected string to account for
// block flattening which does not preserve structural AST equality
checkStatement("if (x) if (y) foo(); else bar(); ");
checkStatement("if (x) { if (y) foo(); } else bar(); ");
checkStatement("if (x) { if (y) foo(); else bar(); }",
"if (x) if (y) foo(); else bar(); ");
checkStatement("if (x) while (y) if (z) foo(); else bar(); ");
checkStatement("if (x) while (y) { if (z) foo(); } else bar(); ");
checkStatement("if (x) while (y) { if (z) foo(); else bar(); }",
"if (x) while (y) if (z) foo(); else bar(); ");
checkStatement("{var x = 1; {var x = 2;} return x;}");
checkStatement("{var x = 1; {x = 2;} return x;}",
"{var x = 1; x = 2; return x;}",
"{var x = 1; x = 2; return x;}");
checkStatement("if (x) {var x = 1;}");
checkStatement("({'foo': 1}).bar();");
checkStatement("({'foo': 1}).length;");
checkStatement("({'foo': 1}).length + 1;");
checkStatement("({'foo': 1})['foo'].toString();");
checkStatement("({'foo': 1})['foo'] = 3;");
checkStatement("({'foo': 1}['foo']());");
checkStatement("({'foo': 1}['foo'])();");
checkStatement("({'foo': 1})['foo'].x++;");
checkStatement("({'foo': 1}) is Map;");
checkStatement("({'foo': 1}) as Map;");
checkStatement("({'foo': 1}) is util.Map;");
checkStatement("({'foo': 1}) + 1;");
checkStatement("[1].bar();");
checkStatement("1.bar();");
checkStatement("'foo'.bar();");
checkStatement("do while(x); while (y);");
checkStatement("{do; while(x); while (y);}");
checkStatement('switch(x) { case 1: case 2: return y; }');
checkStatement('switch(x) { default: return y; }');
checkStatement('switch(x) { case 1: x=y; default: return y; }');
checkStatement('switch(x) { case 1: x=y; y=z; break; default: return y; }');
}