// Copyright (c) 2016, 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 kernel.ast_to_text; import 'dart:core' hide MapEntry; import 'dart:convert' show json; import '../ast.dart'; import '../import_table.dart'; abstract class Namer { int index = 0; final Map map = {}; String getName(T key) => map.putIfAbsent(key, () => '$prefix${++index}'); String get prefix; } class NormalNamer extends Namer { final String prefix; NormalNamer(this.prefix); } class ConstantNamer extends RecursiveVisitor with Namer { final String prefix; ConstantNamer(this.prefix); String getName(Constant constant) { if (!map.containsKey(constant)) { // Name everything in post-order visit of DAG. constant.visitChildren(this); } return super.getName(constant); } defaultConstantReference(Constant constant) { getName(constant); } } class Disambiguator { final Map namesT = {}; final Map namesU = {}; final Set usedNames = new Set(); String disambiguate(T key1, U key2, String proposeName()) { getNewName() { var proposedName = proposeName(); if (usedNames.add(proposedName)) return proposedName; int i = 2; while (!usedNames.add('$proposedName$i')) { ++i; } return '$proposedName$i'; } if (key1 != null) { String result = namesT[key1]; if (result != null) return result; return namesT[key1] = getNewName(); } if (key2 != null) { String result = namesU[key2]; if (result != null) return result; return namesU[key2] = getNewName(); } throw "Cannot disambiguate"; } } NameSystem globalDebuggingNames = new NameSystem(); String debugLibraryName(Library node) { return node == null ? 'null' : node.name ?? globalDebuggingNames.nameLibrary(node); } String debugClassName(Class node) { return node == null ? 'null' : node.name ?? globalDebuggingNames.nameClass(node); } String debugQualifiedClassName(Class node) { return debugLibraryName(node.enclosingLibrary) + '::' + debugClassName(node); } String debugMemberName(Member node) { return node.name?.name ?? globalDebuggingNames.nameMember(node); } String debugQualifiedMemberName(Member node) { if (node.enclosingClass != null) { return debugQualifiedClassName(node.enclosingClass) + '::' + debugMemberName(node); } else { return debugLibraryName(node.enclosingLibrary) + '::' + debugMemberName(node); } } String debugTypeParameterName(TypeParameter node) { return node.name ?? globalDebuggingNames.nameTypeParameter(node); } String debugQualifiedTypeParameterName(TypeParameter node) { if (node.parent is Class) { return debugQualifiedClassName(node.parent) + '::' + debugTypeParameterName(node); } if (node.parent is Member) { return debugQualifiedMemberName(node.parent) + '::' + debugTypeParameterName(node); } return debugTypeParameterName(node); } String debugVariableDeclarationName(VariableDeclaration node) { return node.name ?? globalDebuggingNames.nameVariable(node); } String debugNodeToString(Node node) { StringBuffer buffer = new StringBuffer(); new Printer(buffer, syntheticNames: globalDebuggingNames).writeNode(node); return '$buffer'; } String componentToString(Component node) { StringBuffer buffer = new StringBuffer(); new Printer(buffer, syntheticNames: new NameSystem()) .writeComponentFile(node); return '$buffer'; } class NameSystem { final Namer variables = new NormalNamer('#t'); final Namer members = new NormalNamer('#m'); final Namer classes = new NormalNamer('#class'); final Namer libraries = new NormalNamer('#lib'); final Namer typeParameters = new NormalNamer('#T'); final Namer labels = new NormalNamer('#L'); final Namer constants = new ConstantNamer('#C'); final Disambiguator prefixes = new Disambiguator(); nameVariable(VariableDeclaration node) => variables.getName(node); nameMember(Member node) => members.getName(node); nameClass(Class node) => classes.getName(node); nameLibrary(Library node) => libraries.getName(node); nameTypeParameter(TypeParameter node) => typeParameters.getName(node); nameSwitchCase(SwitchCase node) => labels.getName(node); nameLabeledStatement(LabeledStatement node) => labels.getName(node); nameConstant(Constant node) => constants.getName(node); final RegExp pathSeparator = new RegExp('[\\/]'); nameLibraryPrefix(Library node, {String proposedName}) { return prefixes.disambiguate(node.reference, node.reference.canonicalName, () { if (proposedName != null) return proposedName; if (node.name != null) return abbreviateName(node.name); if (node.importUri != null) { var path = node.importUri.hasEmptyPath ? '${node.importUri}' : node.importUri.pathSegments.last; if (path.endsWith('.dart')) { path = path.substring(0, path.length - '.dart'.length); } return abbreviateName(path); } return 'L'; }); } nameCanonicalNameAsLibraryPrefix(Reference node, CanonicalName name, {String proposedName}) { return prefixes.disambiguate(node, name, () { if (proposedName != null) return proposedName; CanonicalName canonicalName = name ?? node.canonicalName; if (canonicalName?.name != null) { var path = canonicalName.name; int slash = path.lastIndexOf(pathSeparator); if (slash >= 0) { path = path.substring(slash + 1); } if (path.endsWith('.dart')) { path = path.substring(0, path.length - '.dart'.length); } return abbreviateName(path); } return 'L'; }); } final RegExp punctuation = new RegExp('[.:]'); String abbreviateName(String name) { int dot = name.lastIndexOf(punctuation); if (dot != -1) { name = name.substring(dot + 1); } if (name.length > 4) { return name.substring(0, 3); } return name; } } abstract class Annotator { String annotateVariable(Printer printer, VariableDeclaration node); String annotateReturn(Printer printer, FunctionNode node); String annotateField(Printer printer, Field node); } /// A quick and dirty ambiguous text printer. class Printer extends Visitor { final NameSystem syntheticNames; final StringSink sink; final Annotator annotator; final Map> metadata; ImportTable importTable; int indentation = 0; int column = 0; bool showExternal; bool showOffsets; bool showMetadata; static int SPACE = 0; static int WORD = 1; static int SYMBOL = 2; int state = SPACE; Printer(this.sink, {NameSystem syntheticNames, this.showExternal, this.showOffsets: false, this.showMetadata: false, this.importTable, this.annotator, this.metadata}) : this.syntheticNames = syntheticNames ?? new NameSystem(); Printer createInner(ImportTable importTable, Map> metadata) { return new Printer(sink, importTable: importTable, metadata: metadata, syntheticNames: syntheticNames, annotator: annotator, showExternal: showExternal, showOffsets: showOffsets, showMetadata: showMetadata); } bool shouldHighlight(Node node) { return false; } void startHighlight(Node node) {} void endHighlight(Node node) {} String getLibraryName(Library node) { return node.name ?? syntheticNames.nameLibrary(node); } String getLibraryReference(Library node) { if (node == null) return ''; if (importTable != null && importTable.getImportIndex(node) != -1) { return syntheticNames.nameLibraryPrefix(node); } return getLibraryName(node); } String getClassName(Class node) { return node.name ?? syntheticNames.nameClass(node); } String getClassReference(Class node) { if (node == null) return ''; String name = getClassName(node); String library = getLibraryReference(node.enclosingLibrary); return '$library::$name'; } String getTypedefReference(Typedef node) { if (node == null) return ''; String library = getLibraryReference(node.enclosingLibrary); return '$library::${node.name}'; } static final String emptyNameString = '•'; static final Name emptyName = new Name(emptyNameString); Name getMemberName(Member node) { if (node.name?.name == '') return emptyName; if (node.name != null) return node.name; return new Name(syntheticNames.nameMember(node)); } String getMemberReference(Member node) { if (node == null) return ''; String name = getMemberName(node).name; if (node.parent is Class) { String className = getClassReference(node.parent); return '$className::$name'; } else { String library = getLibraryReference(node.enclosingLibrary); return '$library::$name'; } } String getVariableName(VariableDeclaration node) { return node.name ?? syntheticNames.nameVariable(node); } String getVariableReference(VariableDeclaration node) { if (node == null) return ''; return getVariableName(node); } String getTypeParameterName(TypeParameter node) { return node.name ?? syntheticNames.nameTypeParameter(node); } String getTypeParameterReference(TypeParameter node) { if (node == null) return ''; String name = getTypeParameterName(node); if (node.parent is FunctionNode && node.parent.parent is Member) { String member = getMemberReference(node.parent.parent); return '$member::$name'; } else if (node.parent is Class) { String className = getClassReference(node.parent); return '$className::$name'; } else { return name; // Bound inside a function type. } } void writeComponentProblems(Component component) { writeProblemsAsJson("Problems in component", component.problemsAsJson); } void writeProblemsAsJson(String header, List problemsAsJson) { if (problemsAsJson?.isEmpty == false) { endLine("//"); write("// "); write(header); endLine(":"); endLine("//"); for (String s in problemsAsJson) { Map decoded = json.decode(s); List plainTextFormatted = decoded["plainTextFormatted"]; List lines = plainTextFormatted.join("\n").split("\n"); for (int i = 0; i < lines.length; i++) { write("//"); String trimmed = lines[i].trimRight(); if (trimmed.isNotEmpty) write(" "); endLine(trimmed); } if (lines.isNotEmpty) endLine("//"); } } } void writeLibraryFile(Library library) { writeAnnotationList(library.annotations); writeWord('library'); if (library.name != null) { writeWord(library.name); } endLine(';'); LibraryImportTable imports = new LibraryImportTable(library); Printer inner = createInner(imports, library.enclosingComponent?.metadata); inner.writeStandardLibraryContent(library, outerPrinter: this, importsToPrint: imports); } void printLibraryImportTable(LibraryImportTable imports) { for (var library in imports.importedLibraries) { var importPath = imports.getImportPath(library); if (importPath == "") { var prefix = syntheticNames.nameLibraryPrefix(library, proposedName: 'self'); endLine('import self as $prefix;'); } else { var prefix = syntheticNames.nameLibraryPrefix(library); endLine('import "$importPath" as $prefix;'); } } } void writeStandardLibraryContent(Library library, {Printer outerPrinter, LibraryImportTable importsToPrint}) { outerPrinter ??= this; outerPrinter.writeProblemsAsJson( "Problems in library", library.problemsAsJson); if (importsToPrint != null) { outerPrinter.printLibraryImportTable(importsToPrint); } writeAdditionalExports(library.additionalExports); endLine(); library.dependencies.forEach(writeNode); if (library.dependencies.isNotEmpty) endLine(); library.parts.forEach(writeNode); library.typedefs.forEach(writeNode); library.classes.forEach(writeNode); library.fields.forEach(writeNode); library.procedures.forEach(writeNode); } void writeAdditionalExports(List additionalExports) { if (additionalExports.isEmpty) return; write('additionalExports = ('); bool isFirst = true; for (Reference reference in additionalExports) { if (isFirst) { isFirst = false; } else { write(', '); } var node = reference.node; if (node is Class) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name); } else if (node is Field) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name.name); } else if (node is Procedure) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name.name); } else if (node is Typedef) { Library nodeLibrary = node.enclosingLibrary; String prefix = syntheticNames.nameLibraryPrefix(nodeLibrary); write(prefix + '::' + node.name); } else if (reference.canonicalName != null) { write(reference.canonicalName.toString()); } else { throw new UnimplementedError('${node.runtimeType}'); } endLine(')'); } endLine(); } void writeComponentFile(Component component) { ImportTable imports = new ComponentImportTable(component); var inner = createInner(imports, component.metadata); writeWord('main'); writeSpaced('='); inner.writeMemberReferenceFromReference(component.mainMethodName); endLine(';'); if (showMetadata) { inner.writeMetadata(component); } writeComponentProblems(component); for (var library in component.libraries) { if (library.isExternal) { if (!showExternal) { continue; } writeWord('external'); } if (showMetadata) { inner.writeMetadata(library); } writeAnnotationList(library.annotations); writeWord('library'); if (library.name != null) { writeWord(library.name); } if (library.importUri != null) { writeSpaced('from'); writeWord('"${library.importUri}"'); } var prefix = syntheticNames.nameLibraryPrefix(library); writeSpaced('as'); writeWord(prefix); endLine(' {'); ++inner.indentation; inner.writeStandardLibraryContent(library); --inner.indentation; endLine('}'); } writeConstantTable(component); } void writeConstantTable(Component component) { if (syntheticNames.constants.map.isEmpty) return; ImportTable imports = new ComponentImportTable(component); var inner = createInner(imports, component.metadata); writeWord('constants '); endLine(' {'); ++inner.indentation; for (final Constant constant in syntheticNames.constants.map.keys.toList()) { inner.writeNode(constant); } --inner.indentation; endLine('}'); } int getPrecedence(TreeNode node) { return Precedence.of(node); } void write(String string) { sink.write(string); column += string.length; } void writeSpace([String string = ' ']) { write(string); state = SPACE; } void ensureSpace() { if (state != SPACE) writeSpace(); } void writeSymbol(String string) { write(string); state = SYMBOL; } void writeSpaced(String string) { ensureSpace(); write(string); writeSpace(); } void writeComma([String string = ',']) { write(string); writeSpace(); } void writeWord(String string) { if (string.isEmpty) return; ensureWordBoundary(); write(string); state = WORD; } void ensureWordBoundary() { if (state == WORD) { writeSpace(); } } void writeIndentation() { writeSpace(' ' * indentation); } void writeNode(Node node) { if (node == null) { writeSymbol(""); } else { final highlight = shouldHighlight(node); if (highlight) { startHighlight(node); } if (showOffsets && node is TreeNode) { writeWord("[${node.fileOffset}]"); } if (showMetadata && node is TreeNode) { writeMetadata(node); } node.accept(this); if (highlight) { endHighlight(node); } } } void writeOptionalNode(Node node) { if (node != null) { node.accept(this); } } void writeMetadata(TreeNode node) { if (metadata != null) { for (var md in metadata.values) { final nodeMetadata = md.mapping[node]; if (nodeMetadata != null) { writeWord("[@${md.tag}=${nodeMetadata}]"); } } } } void writeAnnotatedType(DartType type, String annotation) { writeType(type); if (annotation != null) { write('/'); write(annotation); state = WORD; } } void writeType(DartType type) { if (type == null) { write(''); } else { type.accept(this); } } void writeOptionalType(DartType type) { if (type != null) { type.accept(this); } } visitSupertype(Supertype type) { if (type == null) { write(''); } else { writeClassReferenceFromReference(type.className); if (type.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(type.typeArguments, writeType); writeSymbol('>'); } } } visitTypedefType(TypedefType type) { writeTypedefReference(type.typedefNode); if (type.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(type.typeArguments, writeType); writeSymbol('>'); } } void writeModifier(bool isThere, String name) { if (isThere) { writeWord(name); } } void writeName(Name name) { if (name?.name == '') { writeWord(emptyNameString); } else { writeWord(name?.name ?? ''); // TODO: write library name } } void endLine([String string]) { if (string != null) { write(string); } write('\n'); state = SPACE; column = 0; } void writeFunction(FunctionNode function, {name, List initializers, bool terminateLine: true}) { if (name is String) { writeWord(name); } else if (name is Name) { writeName(name); } else { assert(name == null); } writeTypeParameterList(function.typeParameters); writeParameterList(function.positionalParameters, function.namedParameters, function.requiredParameterCount); writeReturnType( function.returnType, annotator?.annotateReturn(this, function)); if (initializers != null && initializers.isNotEmpty) { endLine(); ++indentation; writeIndentation(); writeComma(':'); writeList(initializers, writeNode); --indentation; } if (function.asyncMarker != AsyncMarker.Sync) { writeSpaced(getAsyncMarkerKeyword(function.asyncMarker)); } if (function.dartAsyncMarker != AsyncMarker.Sync && function.dartAsyncMarker != function.asyncMarker) { writeSpaced("/* originally"); writeSpaced(getAsyncMarkerKeyword(function.dartAsyncMarker)); writeSpaced("*/"); } if (function.body != null) { writeFunctionBody(function.body, terminateLine: terminateLine); } else if (terminateLine) { endLine(';'); } } String getAsyncMarkerKeyword(AsyncMarker marker) { switch (marker) { case AsyncMarker.Sync: return 'sync'; case AsyncMarker.SyncStar: return 'sync*'; case AsyncMarker.Async: return 'async'; case AsyncMarker.AsyncStar: return 'async*'; case AsyncMarker.SyncYielding: return 'yielding'; default: return ''; } } void writeFunctionBody(Statement body, {bool terminateLine: true}) { if (body is Block && body.statements.isEmpty) { ensureSpace(); writeSymbol('{}'); state = WORD; if (terminateLine) { endLine(); } } else if (body is Block) { ensureSpace(); endLine('{'); ++indentation; body.statements.forEach(writeNode); --indentation; writeIndentation(); writeSymbol('}'); state = WORD; if (terminateLine) { endLine(); } } else if (body is ReturnStatement && !terminateLine) { writeSpaced('=>'); writeExpression(body.expression); } else { writeBody(body); } } writeFunctionType(FunctionType node, {List typedefPositional, List typedefNamed}) { if (state == WORD) { ensureSpace(); } writeTypeParameterList(node.typeParameters); writeSymbol('('); List positional = node.positionalParameters; bool parametersAnnotated = false; if (typedefPositional != null) { for (VariableDeclaration formal in typedefPositional) { parametersAnnotated = parametersAnnotated || formal.annotations.length > 0; } } if (typedefNamed != null) { for (VariableDeclaration formal in typedefNamed) { parametersAnnotated = parametersAnnotated || formal.annotations.length > 0; } } if (parametersAnnotated && typedefPositional != null) { writeList(typedefPositional.take(node.requiredParameterCount), writeVariableDeclaration); } else { writeList(positional.take(node.requiredParameterCount), writeType); } if (node.requiredParameterCount < positional.length) { if (node.requiredParameterCount > 0) { writeComma(); } writeSymbol('['); if (parametersAnnotated && typedefPositional != null) { writeList(typedefPositional.skip(node.requiredParameterCount), writeVariableDeclaration); } else { writeList(positional.skip(node.requiredParameterCount), writeType); } writeSymbol(']'); } if (node.namedParameters.isNotEmpty) { if (node.positionalParameters.isNotEmpty) { writeComma(); } writeSymbol('{'); if (parametersAnnotated && typedefNamed != null) { writeList(typedefNamed, writeVariableDeclaration); } else { writeList(node.namedParameters, visitNamedType); } writeSymbol('}'); } writeSymbol(')'); writeSpaced('→'); writeType(node.returnType); } void writeBody(Statement body) { if (body is Block) { endLine(' {'); ++indentation; body.statements.forEach(writeNode); --indentation; writeIndentation(); endLine('}'); } else { endLine(); ++indentation; writeNode(body); --indentation; } } void writeReturnType(DartType type, String annotation) { if (type == null) return; writeSpaced('→'); writeAnnotatedType(type, annotation); } void writeTypeParameterList(List typeParameters) { if (typeParameters.isEmpty) return; writeSymbol('<'); writeList(typeParameters, writeNode); writeSymbol('>'); state = WORD; // Ensure space if not followed by another symbol. } void writeParameterList(List positional, List named, int requiredParameterCount) { writeSymbol('('); writeList( positional.take(requiredParameterCount), writeVariableDeclaration); if (requiredParameterCount < positional.length) { if (requiredParameterCount > 0) { writeComma(); } writeSymbol('['); writeList( positional.skip(requiredParameterCount), writeVariableDeclaration); writeSymbol(']'); } if (named.isNotEmpty) { if (positional.isNotEmpty) { writeComma(); } writeSymbol('{'); writeList(named, writeVariableDeclaration); writeSymbol('}'); } writeSymbol(')'); } void writeList(Iterable nodes, void callback(T x), {String separator: ','}) { bool first = true; for (var node in nodes) { if (first) { first = false; } else { writeComma(separator); } callback(node); } } void writeClassReferenceFromReference(Reference reference) { writeWord(getClassReferenceFromReference(reference)); } String getClassReferenceFromReference(Reference reference) { if (reference == null) return ''; if (reference.node != null) return getClassReference(reference.asClass); if (reference.canonicalName != null) return getCanonicalNameString(reference.canonicalName); throw "Neither node nor canonical name found"; } void writeMemberReferenceFromReference(Reference reference) { writeWord(getMemberReferenceFromReference(reference)); } String getMemberReferenceFromReference(Reference reference) { if (reference == null) return ''; if (reference.node != null) return getMemberReference(reference.asMember); if (reference.canonicalName != null) return getCanonicalNameString(reference.canonicalName); throw "Neither node nor canonical name found"; } String getCanonicalNameString(CanonicalName name) { if (name.isRoot) throw 'unexpected root'; if (name.name.startsWith('@')) throw 'unexpected @'; libraryString(CanonicalName lib) { if (lib.reference?.node != null) return getLibraryReference(lib.reference.asLibrary); return syntheticNames.nameCanonicalNameAsLibraryPrefix( lib.reference, lib); } classString(CanonicalName cls) => libraryString(cls.parent) + '::' + cls.name; if (name.parent.isRoot) return libraryString(name); if (name.parent.parent.isRoot) return classString(name); CanonicalName atNode = name.parent; while (!atNode.name.startsWith('@')) atNode = atNode.parent; String parent = ""; if (atNode.parent.parent.isRoot) { parent = libraryString(atNode.parent); } else { parent = classString(atNode.parent); } if (name.name == '') return "$parent::$emptyNameString"; return "$parent::${name.name}"; } void writeTypedefReference(Typedef typedefNode) { writeWord(getTypedefReference(typedefNode)); } void writeVariableReference(VariableDeclaration variable) { final highlight = shouldHighlight(variable); if (highlight) { startHighlight(variable); } writeWord(getVariableReference(variable)); if (highlight) { endHighlight(variable); } } void writeTypeParameterReference(TypeParameter node) { writeWord(getTypeParameterReference(node)); } void writeExpression(Expression node, [int minimumPrecedence]) { final highlight = shouldHighlight(node); if (highlight) { startHighlight(node); } if (showOffsets) writeWord("[${node.fileOffset}]"); bool needsParenteses = false; if (minimumPrecedence != null && getPrecedence(node) < minimumPrecedence) { needsParenteses = true; writeSymbol('('); } writeNode(node); if (needsParenteses) { writeSymbol(')'); } if (highlight) { endHighlight(node); } } void writeAnnotation(Expression node) { writeSymbol('@'); if (node is ConstructorInvocation) { writeMemberReferenceFromReference(node.targetReference); visitArguments(node.arguments); } else { writeExpression(node); } } void writeAnnotationList(List nodes, {bool separateLines: true}) { for (Expression node in nodes) { if (separateLines) { writeIndentation(); } writeAnnotation(node); if (separateLines) { endLine(); } else { writeSpace(); } } } visitLibrary(Library node) {} visitField(Field node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isStatic, 'static'); writeModifier(node.isCovariant, 'covariant'); writeModifier(node.isGenericCovariantImpl, 'generic-covariant-impl'); writeModifier(node.isFinal, 'final'); writeModifier(node.isConst, 'const'); // Only show implicit getter/setter modifiers in cases where they are // out of the ordinary. if (node.isStatic) { writeModifier(node.hasImplicitGetter, '[getter]'); writeModifier(node.hasImplicitSetter, '[setter]'); } else { writeModifier(!node.hasImplicitGetter, '[no-getter]'); if (node.isFinal) { writeModifier(node.hasImplicitSetter, '[setter]'); } else { writeModifier(!node.hasImplicitSetter, '[no-setter]'); } } writeWord('field'); writeSpace(); writeAnnotatedType(node.type, annotator?.annotateField(this, node)); writeName(getMemberName(node)); if (node.initializer != null) { writeSpaced('='); writeExpression(node.initializer); } if ((node.enclosingClass == null && node.enclosingLibrary.fileUri != node.fileUri) || (node.enclosingClass != null && node.enclosingClass.fileUri != node.fileUri)) { writeWord("/* from ${node.fileUri} */"); } endLine(';'); } visitProcedure(Procedure node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isExternal, 'external'); writeModifier(node.isStatic, 'static'); writeModifier(node.isAbstract, 'abstract'); writeModifier(node.isForwardingStub, 'forwarding-stub'); writeModifier(node.isForwardingSemiStub, 'forwarding-semi-stub'); writeModifier(node.isNoSuchMethodForwarder, 'no-such-method-forwarder'); writeWord(procedureKindToString(node.kind)); if ((node.enclosingClass == null && node.enclosingLibrary.fileUri != node.fileUri) || (node.enclosingClass != null && node.enclosingClass.fileUri != node.fileUri)) { writeWord("/* from ${node.fileUri} */"); } writeFunction(node.function, name: getMemberName(node)); } visitConstructor(Constructor node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isExternal, 'external'); writeModifier(node.isConst, 'const'); writeModifier(node.isSynthetic, 'synthetic'); writeWord('constructor'); writeFunction(node.function, name: node.name, initializers: node.initializers); } visitRedirectingFactoryConstructor(RedirectingFactoryConstructor node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isExternal, 'external'); writeModifier(node.isConst, 'const'); writeWord('redirecting_factory'); if (node.name != null) { writeName(node.name); } writeTypeParameterList(node.typeParameters); writeParameterList(node.positionalParameters, node.namedParameters, node.requiredParameterCount); writeSpaced('='); writeMemberReferenceFromReference(node.targetReference); if (node.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); } endLine(';'); } visitClass(Class node) { writeAnnotationList(node.annotations); writeIndentation(); writeModifier(node.isAbstract, 'abstract'); writeWord('class'); writeWord(getClassName(node)); writeTypeParameterList(node.typeParameters); if (node.isMixinApplication) { writeSpaced('='); visitSupertype(node.supertype); writeSpaced('with'); visitSupertype(node.mixedInType); } else if (node.supertype != null) { writeSpaced('extends'); visitSupertype(node.supertype); } if (node.implementedTypes.isNotEmpty) { writeSpaced('implements'); writeList(node.implementedTypes, visitSupertype); } var endLineString = ' {'; if (node.enclosingLibrary.fileUri != node.fileUri) { endLineString += ' // from ${node.fileUri}'; } endLine(endLineString); ++indentation; node.fields.forEach(writeNode); node.constructors.forEach(writeNode); node.procedures.forEach(writeNode); node.redirectingFactoryConstructors.forEach(writeNode); --indentation; writeIndentation(); endLine('}'); } visitTypedef(Typedef node) { writeAnnotationList(node.annotations); writeIndentation(); writeWord('typedef'); writeWord(node.name); writeTypeParameterList(node.typeParameters); writeSpaced('='); if (node.type is FunctionType) { writeFunctionType(node.type, typedefPositional: node.positionalParameters, typedefNamed: node.namedParameters); } else { writeNode(node.type); } endLine(';'); } visitInvalidExpression(InvalidExpression node) { writeWord('invalid-expression'); if (node.message != null) { writeWord('"${escapeString(node.message)}"'); } } visitMethodInvocation(MethodInvocation node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); writeNode(node.arguments); } visitDirectMethodInvocation(DirectMethodInvocation node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.{='); writeMemberReferenceFromReference(node.targetReference); writeSymbol('}'); writeNode(node.arguments); } visitSuperMethodInvocation(SuperMethodInvocation node) { writeWord('super'); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); writeNode(node.arguments); } visitStaticInvocation(StaticInvocation node) { writeModifier(node.isConst, 'const'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitConstructorInvocation(ConstructorInvocation node) { writeWord(node.isConst ? 'const' : 'new'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitNot(Not node) { writeSymbol('!'); writeExpression(node.operand, Precedence.PREFIX); } visitLogicalExpression(LogicalExpression node) { int precedence = Precedence.binaryPrecedence[node.operator]; writeExpression(node.left, precedence); writeSpaced(node.operator); writeExpression(node.right, precedence + 1); } visitConditionalExpression(ConditionalExpression node) { writeExpression(node.condition, Precedence.LOGICAL_OR); ensureSpace(); write('?'); writeStaticType(node.staticType); writeSpace(); writeExpression(node.then); writeSpaced(':'); writeExpression(node.otherwise); } String getEscapedCharacter(int codeUnit) { switch (codeUnit) { case 9: return r'\t'; case 10: return r'\n'; case 11: return r'\v'; case 12: return r'\f'; case 13: return r'\r'; case 34: return r'\"'; case 36: return r'\$'; case 92: return r'\\'; default: if (codeUnit < 32 || codeUnit > 126) { return r'\u' + '$codeUnit'.padLeft(4, '0'); } else { return null; } } } String escapeString(String string) { StringBuffer buffer; for (int i = 0; i < string.length; ++i) { String character = getEscapedCharacter(string.codeUnitAt(i)); if (character != null) { buffer ??= new StringBuffer(string.substring(0, i)); buffer.write(character); } else { buffer?.write(string[i]); } } return buffer == null ? string : buffer.toString(); } visitStringConcatenation(StringConcatenation node) { if (state == WORD) { writeSpace(); } write('"'); for (var part in node.expressions) { if (part is StringLiteral) { writeSymbol(escapeString(part.value)); } else { writeSymbol(r'${'); writeExpression(part); writeSymbol('}'); } } write('"'); state = WORD; } visitListConcatenation(ListConcatenation node) { bool first = true; for (Expression part in node.lists) { if (!first) writeSpaced('+'); writeExpression(part); first = false; } } visitSetConcatenation(SetConcatenation node) { bool first = true; for (Expression part in node.sets) { if (!first) writeSpaced('+'); writeExpression(part); first = false; } } visitMapConcatenation(MapConcatenation node) { bool first = true; for (Expression part in node.maps) { if (!first) writeSpaced('+'); writeExpression(part); first = false; } } visitInstanceCreation(InstanceCreation node) { write('${node.classNode}'); if (node.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); } write(' {'); bool first = true; node.fieldValues.forEach((Reference fieldRef, Expression value) { if (!first) { writeComma(); } write('${fieldRef.asField.name}: '); writeExpression(value); first = false; }); for (AssertStatement assert_ in node.asserts) { if (!first) { writeComma(); } write('assert('); writeExpression(assert_.condition); if (assert_.message != null) { writeComma(); writeExpression(assert_.message); } write(')'); first = false; } for (Expression unusedArgument in node.unusedArguments) { if (!first) { writeComma(); } writeExpression(unusedArgument); first = false; } write('}'); } visitIsExpression(IsExpression node) { writeExpression(node.operand, Precedence.BITWISE_OR); writeSpaced('is'); writeType(node.type); } visitAsExpression(AsExpression node) { writeExpression(node.operand, Precedence.BITWISE_OR); writeSpaced(node.isTypeError ? 'as{TypeError}' : 'as'); writeType(node.type); } visitSymbolLiteral(SymbolLiteral node) { writeSymbol('#'); writeWord(node.value); } visitTypeLiteral(TypeLiteral node) { writeType(node.type); } visitThisExpression(ThisExpression node) { writeWord('this'); } visitRethrow(Rethrow node) { writeWord('rethrow'); } visitThrow(Throw node) { writeWord('throw'); writeSpace(); writeExpression(node.expression); } visitListLiteral(ListLiteral node) { if (node.isConst) { writeWord('const'); writeSpace(); } if (node.typeArgument != null) { writeSymbol('<'); writeType(node.typeArgument); writeSymbol('>'); } writeSymbol('['); writeList(node.expressions, writeNode); writeSymbol(']'); } visitSetLiteral(SetLiteral node) { if (node.isConst) { writeWord('const'); writeSpace(); } if (node.typeArgument != null) { writeSymbol('<'); writeType(node.typeArgument); writeSymbol('>'); } writeSymbol('{'); writeList(node.expressions, writeNode); writeSymbol('}'); } visitMapLiteral(MapLiteral node) { if (node.isConst) { writeWord('const'); writeSpace(); } if (node.keyType != null) { writeSymbol('<'); writeList([node.keyType, node.valueType], writeType); writeSymbol('>'); } writeSymbol('{'); writeList(node.entries, writeNode); writeSymbol('}'); } visitMapEntry(MapEntry node) { writeExpression(node.key); writeComma(':'); writeExpression(node.value); } visitAwaitExpression(AwaitExpression node) { writeWord('await'); writeExpression(node.operand); } visitFunctionExpression(FunctionExpression node) { writeFunction(node.function, terminateLine: false); } visitStringLiteral(StringLiteral node) { writeWord('"${escapeString(node.value)}"'); } visitIntLiteral(IntLiteral node) { writeWord('${node.value}'); } visitDoubleLiteral(DoubleLiteral node) { writeWord('${node.value}'); } visitBoolLiteral(BoolLiteral node) { writeWord('${node.value}'); } visitNullLiteral(NullLiteral node) { writeWord('null'); } visitLet(Let node) { writeWord('let'); writeVariableDeclaration(node.variable); writeSpaced('in'); writeExpression(node.body); } visitBlockExpression(BlockExpression node) { writeSpaced('block'); writeBlockBody(node.body.statements, asExpression: true); writeSymbol(' =>'); writeExpression(node.value); } visitInstantiation(Instantiation node) { writeExpression(node.expression); writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); } visitLoadLibrary(LoadLibrary node) { writeWord('LoadLibrary'); writeSymbol('('); writeWord(node.import.name); writeSymbol(')'); state = WORD; } visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { writeWord('CheckLibraryIsLoaded'); writeSymbol('('); writeWord(node.import.name); writeSymbol(')'); state = WORD; } visitLibraryPart(LibraryPart node) { writeAnnotationList(node.annotations); writeIndentation(); writeWord('part'); writeWord(node.partUri); endLine(";"); } visitLibraryDependency(LibraryDependency node) { writeIndentation(); writeWord(node.isImport ? 'import' : 'export'); var uriString; if (node.importedLibraryReference?.node != null) { uriString = '${node.targetLibrary.importUri}'; } else { uriString = '${node.importedLibraryReference?.canonicalName?.name}'; } writeWord('"$uriString"'); if (node.isDeferred) { writeWord('deferred'); } if (node.name != null) { writeWord('as'); writeWord(node.name); } endLine(';'); } defaultExpression(Expression node) { writeWord('${node.runtimeType}'); } visitVariableGet(VariableGet node) { writeVariableReference(node.variable); if (node.promotedType != null) { writeSymbol('{'); writeNode(node.promotedType); writeSymbol('}'); state = WORD; } } visitVariableSet(VariableSet node) { writeVariableReference(node.variable); writeSpaced('='); writeExpression(node.value); } void writeInterfaceTarget(Name name, Reference target) { if (target != null) { writeSymbol('{'); writeMemberReferenceFromReference(target); writeSymbol('}'); } else { writeName(name); } } void writeStaticType(DartType type) { if (type != null) { writeSymbol('{'); writeType(type); writeSymbol('}'); } } visitPropertyGet(PropertyGet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); } visitPropertySet(PropertySet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); writeSpaced('='); writeExpression(node.value); } visitSuperPropertyGet(SuperPropertyGet node) { writeWord('super'); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); } visitSuperPropertySet(SuperPropertySet node) { writeWord('super'); writeSymbol('.'); writeInterfaceTarget(node.name, node.interfaceTargetReference); writeSpaced('='); writeExpression(node.value); } visitDirectPropertyGet(DirectPropertyGet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.{='); writeMemberReferenceFromReference(node.targetReference); writeSymbol('}'); } visitDirectPropertySet(DirectPropertySet node) { writeExpression(node.receiver, Precedence.PRIMARY); writeSymbol('.{='); writeMemberReferenceFromReference(node.targetReference); writeSymbol('}'); writeSpaced('='); writeExpression(node.value); } visitStaticGet(StaticGet node) { writeMemberReferenceFromReference(node.targetReference); } visitStaticSet(StaticSet node) { writeMemberReferenceFromReference(node.targetReference); writeSpaced('='); writeExpression(node.value); } visitExpressionStatement(ExpressionStatement node) { writeIndentation(); writeExpression(node.expression); endLine(';'); } void writeBlockBody(List statements, {bool asExpression = false}) { if (statements.isEmpty) { asExpression ? writeSymbol('{}') : endLine('{}'); return; } endLine('{'); ++indentation; statements.forEach(writeNode); --indentation; writeIndentation(); asExpression ? writeSymbol('}') : endLine('}'); } visitBlock(Block node) { writeIndentation(); writeBlockBody(node.statements); } visitAssertBlock(AssertBlock node) { writeIndentation(); writeSpaced('assert'); writeBlockBody(node.statements); } visitEmptyStatement(EmptyStatement node) { writeIndentation(); endLine(';'); } visitAssertStatement(AssertStatement node, {bool asExpression = false}) { if (!asExpression) { writeIndentation(); } writeWord('assert'); writeSymbol('('); writeExpression(node.condition); if (node.message != null) { writeComma(); writeExpression(node.message); } if (!asExpression) { endLine(');'); } else { writeSymbol(')'); } } visitLabeledStatement(LabeledStatement node) { writeIndentation(); writeWord(syntheticNames.nameLabeledStatement(node)); endLine(':'); writeNode(node.body); } visitBreakStatement(BreakStatement node) { writeIndentation(); writeWord('break'); writeWord(syntheticNames.nameLabeledStatement(node.target)); endLine(';'); } visitWhileStatement(WhileStatement node) { writeIndentation(); writeSpaced('while'); writeSymbol('('); writeExpression(node.condition); writeSymbol(')'); writeBody(node.body); } visitDoStatement(DoStatement node) { writeIndentation(); writeWord('do'); writeBody(node.body); writeIndentation(); writeSpaced('while'); writeSymbol('('); writeExpression(node.condition); endLine(')'); } visitForStatement(ForStatement node) { writeIndentation(); writeSpaced('for'); writeSymbol('('); writeList(node.variables, writeVariableDeclaration); writeComma(';'); if (node.condition != null) { writeExpression(node.condition); } writeComma(';'); writeList(node.updates, writeExpression); writeSymbol(')'); writeBody(node.body); } visitForInStatement(ForInStatement node) { writeIndentation(); if (node.isAsync) { writeSpaced('await'); } writeSpaced('for'); writeSymbol('('); writeVariableDeclaration(node.variable, useVarKeyword: true); writeSpaced('in'); writeExpression(node.iterable); writeSymbol(')'); writeBody(node.body); } visitSwitchStatement(SwitchStatement node) { writeIndentation(); writeWord('switch'); writeSymbol('('); writeExpression(node.expression); endLine(') {'); ++indentation; node.cases.forEach(writeNode); --indentation; writeIndentation(); endLine('}'); } visitSwitchCase(SwitchCase node) { String label = syntheticNames.nameSwitchCase(node); writeIndentation(); writeWord(label); endLine(':'); for (var expression in node.expressions) { writeIndentation(); writeWord('case'); writeExpression(expression); endLine(':'); } if (node.isDefault) { writeIndentation(); writeWord('default'); endLine(':'); } ++indentation; writeNode(node.body); --indentation; } visitContinueSwitchStatement(ContinueSwitchStatement node) { writeIndentation(); writeWord('continue'); writeWord(syntheticNames.nameSwitchCase(node.target)); endLine(';'); } visitIfStatement(IfStatement node) { writeIndentation(); writeWord('if'); writeSymbol('('); writeExpression(node.condition); writeSymbol(')'); writeBody(node.then); if (node.otherwise != null) { writeIndentation(); writeWord('else'); writeBody(node.otherwise); } } visitReturnStatement(ReturnStatement node) { writeIndentation(); writeWord('return'); if (node.expression != null) { writeSpace(); writeExpression(node.expression); } endLine(';'); } visitTryCatch(TryCatch node) { writeIndentation(); writeWord('try'); writeBody(node.body); node.catches.forEach(writeNode); } visitCatch(Catch node) { writeIndentation(); if (node.guard != null) { writeWord('on'); writeType(node.guard); writeSpace(); } writeWord('catch'); writeSymbol('('); if (node.exception != null) { writeVariableDeclaration(node.exception); } else { writeWord('no-exception-var'); } if (node.stackTrace != null) { writeComma(); writeVariableDeclaration(node.stackTrace); } writeSymbol(')'); writeBody(node.body); } visitTryFinally(TryFinally node) { writeIndentation(); writeWord('try'); writeBody(node.body); writeIndentation(); writeWord('finally'); writeBody(node.finalizer); } visitYieldStatement(YieldStatement node) { writeIndentation(); if (node.isYieldStar) { writeWord('yield*'); } else if (node.isNative) { writeWord('[yield]'); } else { writeWord('yield'); } writeExpression(node.expression); endLine(';'); } visitVariableDeclaration(VariableDeclaration node) { writeIndentation(); writeVariableDeclaration(node, useVarKeyword: true); endLine(';'); } visitFunctionDeclaration(FunctionDeclaration node) { writeAnnotationList(node.variable.annotations); writeIndentation(); writeWord('function'); if (node.function != null) { writeFunction(node.function, name: getVariableName(node.variable)); } else { writeWord(getVariableName(node.variable)); endLine('...;'); } } void writeVariableDeclaration(VariableDeclaration node, {bool useVarKeyword: false}) { if (showOffsets) writeWord("[${node.fileOffset}]"); if (showMetadata) writeMetadata(node); writeAnnotationList(node.annotations, separateLines: false); writeModifier(node.isCovariant, 'covariant'); writeModifier(node.isGenericCovariantImpl, 'generic-covariant-impl'); writeModifier(node.isFinal, 'final'); writeModifier(node.isConst, 'const'); if (node.type != null) { writeAnnotatedType(node.type, annotator?.annotateVariable(this, node)); } if (useVarKeyword && !node.isFinal && !node.isConst && node.type == null) { writeWord('var'); } writeWord(getVariableName(node)); if (node.initializer != null) { writeSpaced('='); writeExpression(node.initializer); } } visitArguments(Arguments node) { if (node.types.isNotEmpty) { writeSymbol('<'); writeList(node.types, writeType); writeSymbol('>'); } writeSymbol('('); var allArgs = >[node.positional, node.named].expand((x) => x); writeList(allArgs, writeNode); writeSymbol(')'); } visitNamedExpression(NamedExpression node) { writeWord(node.name); writeComma(':'); writeExpression(node.value); } defaultStatement(Statement node) { writeIndentation(); endLine('${node.runtimeType}'); } visitInvalidInitializer(InvalidInitializer node) { writeWord('invalid-initializer'); } visitFieldInitializer(FieldInitializer node) { writeMemberReferenceFromReference(node.fieldReference); writeSpaced('='); writeExpression(node.value); } visitSuperInitializer(SuperInitializer node) { writeWord('super'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitRedirectingInitializer(RedirectingInitializer node) { writeWord('this'); writeMemberReferenceFromReference(node.targetReference); writeNode(node.arguments); } visitLocalInitializer(LocalInitializer node) { writeVariableDeclaration(node.variable); } visitAssertInitializer(AssertInitializer node) { visitAssertStatement(node.statement, asExpression: true); } defaultInitializer(Initializer node) { writeIndentation(); endLine(': ${node.runtimeType}'); } visitInvalidType(InvalidType node) { writeWord('invalid-type'); } visitDynamicType(DynamicType node) { writeWord('dynamic'); } visitVoidType(VoidType node) { writeWord('void'); } visitInterfaceType(InterfaceType node) { writeClassReferenceFromReference(node.className); if (node.typeArguments.isNotEmpty) { writeSymbol('<'); writeList(node.typeArguments, writeType); writeSymbol('>'); state = WORD; // Disallow a word immediately after the '>'. } } visitFunctionType(FunctionType node) { writeFunctionType(node); } visitNamedType(NamedType node) { writeWord(node.name); writeSymbol(':'); writeSpace(); writeType(node.type); } visitTypeParameterType(TypeParameterType node) { writeTypeParameterReference(node.parameter); if (node.promotedBound != null) { writeSpace(); writeWord('extends'); writeSpace(); writeType(node.promotedBound); } } visitTypeParameter(TypeParameter node) { writeModifier(node.isGenericCovariantImpl, 'generic-covariant-impl'); writeAnnotationList(node.annotations, separateLines: false); writeWord(getTypeParameterName(node)); writeSpaced('extends'); writeType(node.bound); if (node.defaultType != null) { writeSpaced('='); writeType(node.defaultType); } } visitConstantExpression(ConstantExpression node) { writeWord(syntheticNames.nameConstant(node.constant)); } defaultConstant(Constant node) { final String name = syntheticNames.nameConstant(node); endLine(' $name = $node'); } visitListConstant(ListConstant node) { final String name = syntheticNames.nameConstant(node); write(' $name = '); final String entries = node.entries.map((Constant constant) { return syntheticNames.nameConstant(constant); }).join(', '); endLine('${node.runtimeType}<${node.typeArgument}>($entries)'); } visitSetConstant(SetConstant node) { final String name = syntheticNames.nameConstant(node); write(' $name = '); final String entries = node.entries.map((Constant constant) { return syntheticNames.nameConstant(constant); }).join(', '); endLine('${node.runtimeType}<${node.typeArgument}>($entries)'); } visitMapConstant(MapConstant node) { final String name = syntheticNames.nameConstant(node); write(' $name = '); final String entries = node.entries.map((ConstantMapEntry entry) { final String key = syntheticNames.nameConstant(entry.key); final String value = syntheticNames.nameConstant(entry.value); return '$key: $value'; }).join(', '); endLine( '${node.runtimeType}<${node.keyType}, ${node.valueType}>($entries)'); } visitInstanceConstant(InstanceConstant node) { final String name = syntheticNames.nameConstant(node); write(' $name = '); final sb = new StringBuffer(); sb.write('${node.classNode}'); if (!node.classNode.typeParameters.isEmpty) { sb.write('<'); sb.write(node.typeArguments.map((type) => type.toString()).join(', ')); sb.write('>'); } sb.write(' {'); bool first = true; node.fieldValues.forEach((Reference fieldRef, Constant constant) { final String name = syntheticNames.nameConstant(constant); if (!first) { sb.write(', '); } sb.write('${fieldRef.asField.name}: $name'); first = false; }); sb.write('}'); endLine(sb.toString()); } visitStringConstant(StringConstant node) { final String name = syntheticNames.nameConstant(node); endLine(' $name = "${escapeString(node.value)}"'); } visitUnevaluatedConstant(UnevaluatedConstant node) { final String name = syntheticNames.nameConstant(node); write(' $name = ('); writeExpression(node.expression); endLine(')'); } defaultNode(Node node) { write('<${node.runtimeType}>'); } } class Precedence extends ExpressionVisitor { static final Precedence instance = new Precedence(); static int of(Expression node) => node.accept(instance); static const int EXPRESSION = 1; static const int CONDITIONAL = 2; static const int LOGICAL_NULL_AWARE = 3; static const int LOGICAL_OR = 4; static const int LOGICAL_AND = 5; static const int EQUALITY = 6; static const int RELATIONAL = 7; static const int BITWISE_OR = 8; static const int BITWISE_XOR = 9; static const int BITWISE_AND = 10; static const int SHIFT = 11; static const int ADDITIVE = 12; static const int MULTIPLICATIVE = 13; static const int PREFIX = 14; static const int POSTFIX = 15; static const int TYPE_LITERAL = 19; static const int PRIMARY = 20; static const int CALLEE = 21; static const Map binaryPrecedence = const { '&&': LOGICAL_AND, '||': LOGICAL_OR, '??': LOGICAL_NULL_AWARE, '==': EQUALITY, '!=': EQUALITY, '>': RELATIONAL, '>=': RELATIONAL, '<': RELATIONAL, '<=': RELATIONAL, '|': BITWISE_OR, '^': BITWISE_XOR, '&': BITWISE_AND, '>>': SHIFT, '<<': SHIFT, '+': ADDITIVE, '-': ADDITIVE, '*': MULTIPLICATIVE, '%': MULTIPLICATIVE, '/': MULTIPLICATIVE, '~/': MULTIPLICATIVE, null: EXPRESSION, }; static bool isAssociativeBinaryOperator(int precedence) { return precedence != EQUALITY && precedence != RELATIONAL; } int defaultExpression(Expression node) => EXPRESSION; int visitInvalidExpression(InvalidExpression node) => CALLEE; int visitMethodInvocation(MethodInvocation node) => CALLEE; int visitSuperMethodInvocation(SuperMethodInvocation node) => CALLEE; int visitDirectMethodInvocation(DirectMethodInvocation node) => CALLEE; int visitStaticInvocation(StaticInvocation node) => CALLEE; int visitConstructorInvocation(ConstructorInvocation node) => CALLEE; int visitNot(Not node) => PREFIX; int visitLogicalExpression(LogicalExpression node) => binaryPrecedence[node.operator]; int visitConditionalExpression(ConditionalExpression node) => CONDITIONAL; int visitStringConcatenation(StringConcatenation node) => PRIMARY; int visitIsExpression(IsExpression node) => RELATIONAL; int visitAsExpression(AsExpression node) => RELATIONAL; int visitSymbolLiteral(SymbolLiteral node) => PRIMARY; int visitTypeLiteral(TypeLiteral node) => PRIMARY; int visitThisExpression(ThisExpression node) => CALLEE; int visitRethrow(Rethrow node) => PRIMARY; int visitThrow(Throw node) => EXPRESSION; int visitListLiteral(ListLiteral node) => PRIMARY; int visitSetLiteral(SetLiteral node) => PRIMARY; int visitMapLiteral(MapLiteral node) => PRIMARY; int visitAwaitExpression(AwaitExpression node) => PREFIX; int visitFunctionExpression(FunctionExpression node) => EXPRESSION; int visitStringLiteral(StringLiteral node) => CALLEE; int visitIntLiteral(IntLiteral node) => CALLEE; int visitDoubleLiteral(DoubleLiteral node) => CALLEE; int visitBoolLiteral(BoolLiteral node) => CALLEE; int visitNullLiteral(NullLiteral node) => CALLEE; int visitVariableGet(VariableGet node) => PRIMARY; int visitVariableSet(VariableSet node) => EXPRESSION; int visitPropertyGet(PropertyGet node) => PRIMARY; int visitPropertySet(PropertySet node) => EXPRESSION; int visitSuperPropertyGet(SuperPropertyGet node) => PRIMARY; int visitSuperPropertySet(SuperPropertySet node) => EXPRESSION; int visitDirectPropertyGet(DirectPropertyGet node) => PRIMARY; int visitDirectPropertySet(DirectPropertySet node) => EXPRESSION; int visitStaticGet(StaticGet node) => PRIMARY; int visitStaticSet(StaticSet node) => EXPRESSION; int visitLet(Let node) => EXPRESSION; } String procedureKindToString(ProcedureKind kind) { switch (kind) { case ProcedureKind.Method: return 'method'; case ProcedureKind.Getter: return 'get'; case ProcedureKind.Setter: return 'set'; case ProcedureKind.Operator: return 'operator'; case ProcedureKind.Factory: return 'factory'; } throw 'illegal ProcedureKind: $kind'; } class ExpressionPrinter { final Printer writeer; final int minimumPrecedence; ExpressionPrinter(this.writeer, this.minimumPrecedence); }