Chromium Code Reviews| Index: tests/compiler/dart2js/dart_printer_test.dart |
| diff --git a/tests/compiler/dart2js/dart_printer_test.dart b/tests/compiler/dart2js/dart_printer_test.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c57b97c8136ff2dc81c443ca028a56cd51a7bab9 |
| --- /dev/null |
| +++ b/tests/compiler/dart2js/dart_printer_test.dart |
| @@ -0,0 +1,940 @@ |
| +library dart_printer_test; |
| + |
| +import '../../../sdk/lib/_internal/compiler/implementation/dart_backend/dart_printer.dart'; |
| +import '../../../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.dart'; |
| +import '../../../sdk/lib/_internal/compiler/implementation/source_file.dart'; |
| +import '../../../sdk/lib/_internal/compiler/implementation/dart2jslib.dart'; |
| +import '../../../sdk/lib/_internal/compiler/implementation/tree/tree.dart' show DartString; |
| +import 'dart:mirrors'; |
| +import '../../../sdk/lib/_internal/compiler/implementation/tree/tree.dart' as tree; |
| +import '../../../sdk/lib/_internal/compiler/implementation/string_validator.dart'; |
| + |
| +/// 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()); |
| + } |
| + |
| + |
| + VariableDefinition asVariableDefinition(x) { |
| + if (x is VariableDefinition) |
| + return x; |
| + if (x is Identifier) |
| + return new VariableDefinition(x.name); |
| + throw "Not a variable definition: ${x.runtimeType}"; |
| + } |
| + |
| + endVariablesDeclaration(int count, Token end) { |
| + List<VariableDefinition> variables = |
| + popList(count, <VariableDefinition>[], asVariableDefinition); |
| + TypeAnnotation type = popTypeAnnotation(); |
| + push(new VariableDefinitions(variables, |
| + type: type, |
| + isFinal: false, // TODO(asgerf): Parse modifiers. |
| + isConst: false |
| + )); |
| + } |
| + |
| + endInitializer(Token assign) { |
| + Expression init = pop(); |
| + String name = pop(asName); |
| + push(new VariableDefinition(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 FunctionStatement(name, parameters, body, 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(); |
| +} |
| + |
| +/// 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) { |
| + String unparsed = ""; |
| + try { |
| + var firstParse = parse(code); |
| + unparsed = unparse(firstParse); |
| + var secondParse = parse(unparsed); |
| + var baseline = expected == "" ? firstParse : parse(expected); |
| + checkDeepEqual(baseline, secondParse); |
|
Kevin Millikin (Google)
2014/04/24 13:27:25
I guess this should be Expect.isTrue(checkDeepEqua
|
| + } catch (e, stack) { |
| + print("INPUT: ${code}"); |
| + print("OUTPUT: ${unparsed}"); |
| + print("ERROR: $e"); |
| + if (e is! String) |
| + print(stack); |
| + print("-----------"); |
| + } |
| +} |
| + |
| +void checkExpression(String code, [String expected=""]) { |
| + checkFn(code, expected, parseExpression, unparseExpression); |
| +} |
| +void checkStatement(String code, [String expected=""]) { |
| + checkFn(code, expected, parseStatement, unparseStatement); |
| +} |
| + |
| +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'"); |
| + |
| + 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;}"); |
| + |
| + 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);}"); |
| + |
| +} |
| + |