| // 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 fasta.outline_builder; |
| |
| import 'package:kernel/ast.dart' show AsyncMarker, ProcedureKind; |
| |
| import '../parser/parser.dart' show FormalParameterType, optional; |
| |
| import '../scanner/token.dart' show Token; |
| |
| import '../util/link.dart' show Link; |
| |
| import '../combinator.dart' show Combinator; |
| |
| import '../errors.dart' show internalError; |
| |
| import '../builder/builder.dart'; |
| |
| import '../modifier.dart' show Modifier; |
| |
| import 'source_library_builder.dart' show SourceLibraryBuilder; |
| |
| import 'unhandled_listener.dart' show NullValue, Unhandled, UnhandledListener; |
| |
| import '../parser/error_kind.dart' show ErrorKind; |
| |
| import '../parser/dart_vm_native.dart' |
| show removeNativeClause, skipNativeClause; |
| |
| import '../operator.dart' show Operator, operatorFromString, operatorToString; |
| |
| import '../quote.dart' show unescapeString; |
| |
| enum MethodBody { |
| Abstract, |
| Regular, |
| RedirectingFactoryBody, |
| } |
| |
| AsyncMarker asyncMarkerFromTokens(Token asyncToken, Token starToken) { |
| if (asyncToken == null || identical(asyncToken.stringValue, "sync")) { |
| if (starToken == null) { |
| return AsyncMarker.Sync; |
| } else { |
| assert(identical(starToken.stringValue, "*")); |
| return AsyncMarker.SyncStar; |
| } |
| } else if (identical(asyncToken.stringValue, "async")) { |
| if (starToken == null) { |
| return AsyncMarker.Async; |
| } else { |
| assert(identical(starToken.stringValue, "*")); |
| return AsyncMarker.AsyncStar; |
| } |
| } else { |
| return internalError("Unknown async modifier: $asyncToken"); |
| } |
| } |
| |
| class OutlineBuilder extends UnhandledListener { |
| final SourceLibraryBuilder library; |
| |
| final bool isDartLibrary; |
| |
| String nativeMethodName; |
| |
| OutlineBuilder(SourceLibraryBuilder library) |
| : library = library, |
| isDartLibrary = library.uri.scheme == "dart"; |
| |
| @override |
| Uri get uri => library.fileUri; |
| |
| @override |
| void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { |
| debugEvent("Metadata"); |
| List arguments = pop(); |
| String postfix = popIfNotNull(periodBeforeName); |
| List<TypeBuilder> typeArguments = pop(); |
| if (arguments == null) { |
| String expression = pop(); |
| push(new MetadataBuilder.fromExpression( |
| expression, postfix, library, beginToken.charOffset)); |
| } else { |
| String typeName = pop(); |
| push(new MetadataBuilder.fromConstructor( |
| library.addConstructorReference( |
| typeName, typeArguments, postfix, beginToken.next.charOffset), |
| arguments, |
| library, |
| beginToken.charOffset)); |
| } |
| } |
| |
| @override |
| void endHide(Token hideKeyword) { |
| debugEvent("Hide"); |
| List<String> names = pop(); |
| push(new Combinator.hide(names, hideKeyword.charOffset, library.fileUri)); |
| } |
| |
| @override |
| void endShow(Token showKeyword) { |
| debugEvent("Show"); |
| List<String> names = pop(); |
| push(new Combinator.show(names, showKeyword.charOffset, library.fileUri)); |
| } |
| |
| @override |
| void endCombinators(int count) { |
| debugEvent("Combinators"); |
| push(popList(count) ?? NullValue.Combinators); |
| } |
| |
| @override |
| void endExport(Token exportKeyword, Token semicolon) { |
| debugEvent("Export"); |
| List<Combinator> combinators = pop(); |
| Unhandled conditionalUris = pop(); |
| String uri = pop(); |
| List<MetadataBuilder> metadata = pop(); |
| if (uri != null) { |
| library.addExport(metadata, uri, conditionalUris, combinators, |
| exportKeyword.charOffset); |
| } |
| checkEmpty(exportKeyword.charOffset); |
| } |
| |
| @override |
| void endImport(Token importKeyword, Token deferredKeyword, Token asKeyword, |
| Token semicolon) { |
| debugEvent("endImport"); |
| List<Combinator> combinators = pop(); |
| String prefix = popIfNotNull(asKeyword); |
| Unhandled conditionalUris = pop(); |
| String uri = pop(); |
| List<MetadataBuilder> metadata = pop(); |
| if (uri != null) { |
| library.addImport( |
| metadata, |
| uri, |
| conditionalUris, |
| prefix, |
| combinators, |
| deferredKeyword != null, |
| importKeyword.charOffset, |
| asKeyword?.next?.charOffset ?? -1); |
| } |
| checkEmpty(importKeyword.charOffset); |
| } |
| |
| @override |
| void handleRecoverExpression(Token token) { |
| debugEvent("RecoverExpression"); |
| push(NullValue.Expression); |
| } |
| |
| @override |
| void endPart(Token partKeyword, Token semicolon) { |
| debugEvent("Part"); |
| String uri = pop(); |
| List<MetadataBuilder> metadata = pop(); |
| if (uri != null) { |
| library.addPart(metadata, uri); |
| } |
| checkEmpty(partKeyword.charOffset); |
| } |
| |
| @override |
| void handleOperatorName(Token operatorKeyword, Token token) { |
| debugEvent("OperatorName"); |
| push(operatorFromString(token.stringValue)); |
| } |
| |
| @override |
| void endIdentifierList(int count) { |
| debugEvent("endIdentifierList"); |
| push(popList(count) ?? NullValue.IdentifierList); |
| } |
| |
| @override |
| void handleQualified(Token period) { |
| debugEvent("handleQualified"); |
| String name = pop(); |
| String receiver = pop(); |
| push("$receiver.$name"); |
| } |
| |
| @override |
| void endLibraryName(Token libraryKeyword, Token semicolon) { |
| debugEvent("endLibraryName"); |
| String name = pop(); |
| List<MetadataBuilder> metadata = pop(); |
| library.name = name; |
| library.metadata = metadata; |
| } |
| |
| @override |
| void beginClassDeclaration(Token begin, Token name) { |
| library.beginNestedDeclaration(name.lexeme); |
| } |
| |
| @override |
| void endClassDeclaration( |
| int interfacesCount, |
| Token beginToken, |
| Token classKeyword, |
| Token extendsKeyword, |
| Token implementsKeyword, |
| Token endToken) { |
| debugEvent("endClassDeclaration"); |
| List<TypeBuilder> interfaces = popList(interfacesCount); |
| TypeBuilder supertype = pop(); |
| List<TypeVariableBuilder> typeVariables = pop(); |
| String name = pop(); |
| if (typeVariables != null && supertype is MixinApplicationBuilder) { |
| supertype.typeVariables = typeVariables; |
| supertype.subclassName = name; |
| } |
| int modifiers = Modifier.validate(pop()); |
| List<MetadataBuilder> metadata = pop(); |
| library.addClass(metadata, modifiers, name, typeVariables, supertype, |
| interfaces, beginToken.charOffset); |
| checkEmpty(beginToken.charOffset); |
| } |
| |
| ProcedureKind computeProcedureKind(Token token) { |
| if (token == null) return ProcedureKind.Method; |
| if (optional("get", token)) return ProcedureKind.Getter; |
| if (optional("set", token)) return ProcedureKind.Setter; |
| return internalError("Unhandled: ${token.lexeme}"); |
| } |
| |
| @override |
| void beginTopLevelMethod(Token token, Token name) { |
| library.beginNestedDeclaration(name.lexeme, hasMembers: false); |
| } |
| |
| @override |
| void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { |
| debugEvent("endTopLevelMethod"); |
| MethodBody kind = pop(); |
| AsyncMarker asyncModifier = pop(); |
| List<FormalParameterBuilder> formals = pop(); |
| List<TypeVariableBuilder> typeVariables = pop(); |
| String name = pop(); |
| TypeBuilder returnType = pop(); |
| int modifiers = |
| Modifier.validate(pop(), isAbstract: kind == MethodBody.Abstract); |
| List<MetadataBuilder> metadata = pop(); |
| checkEmpty(beginToken.charOffset); |
| library.addProcedure( |
| metadata, |
| modifiers, |
| returnType, |
| name, |
| typeVariables, |
| formals, |
| asyncModifier, |
| computeProcedureKind(getOrSet), |
| beginToken.charOffset, |
| endToken.charOffset, |
| nativeMethodName, |
| isTopLevel: true); |
| nativeMethodName = null; |
| } |
| |
| @override |
| void handleNoFunctionBody(Token token) { |
| debugEvent("NoFunctionBody"); |
| push(MethodBody.Abstract); |
| } |
| |
| @override |
| void handleFunctionBodySkipped(Token token, bool isExpressionBody) { |
| debugEvent("handleFunctionBodySkipped"); |
| push(MethodBody.Regular); |
| } |
| |
| @override |
| void beginMethod(Token token, Token name) { |
| library.beginNestedDeclaration(name.lexeme, hasMembers: false); |
| } |
| |
| @override |
| void endMethod(Token getOrSet, Token beginToken, Token endToken) { |
| debugEvent("Method"); |
| MethodBody bodyKind = pop(); |
| if (bodyKind == MethodBody.RedirectingFactoryBody) { |
| // This will cause an error later. |
| pop(); |
| } |
| AsyncMarker asyncModifier = pop(); |
| List<FormalParameterBuilder> formals = pop(); |
| List<TypeVariableBuilder> typeVariables = pop(); |
| dynamic nameOrOperator = pop(); |
| if (Operator.subtract == nameOrOperator && formals == null) { |
| nameOrOperator = Operator.unaryMinus; |
| } |
| String name; |
| ProcedureKind kind; |
| if (nameOrOperator is Operator) { |
| name = operatorToString(nameOrOperator); |
| kind = ProcedureKind.Operator; |
| } else { |
| name = nameOrOperator; |
| kind = computeProcedureKind(getOrSet); |
| } |
| TypeBuilder returnType = pop(); |
| int modifiers = |
| Modifier.validate(pop(), isAbstract: bodyKind == MethodBody.Abstract); |
| List<MetadataBuilder> metadata = pop(); |
| library.addProcedure( |
| metadata, |
| modifiers, |
| returnType, |
| name, |
| typeVariables, |
| formals, |
| asyncModifier, |
| kind, |
| beginToken.charOffset, |
| endToken.charOffset, |
| nativeMethodName, |
| isTopLevel: false); |
| nativeMethodName = null; |
| } |
| |
| @override |
| void endMixinApplication(Token withKeyword) { |
| debugEvent("MixinApplication"); |
| List<TypeBuilder> mixins = pop(); |
| TypeBuilder supertype = pop(); |
| push(library.addMixinApplication(supertype, mixins, -1)); |
| } |
| |
| @override |
| void beginNamedMixinApplication(Token begin, Token name) { |
| library.beginNestedDeclaration(name.lexeme, hasMembers: false); |
| } |
| |
| @override |
| void endNamedMixinApplication(Token beginToken, Token classKeyword, |
| Token equals, Token implementsKeyword, Token endToken) { |
| debugEvent("endNamedMixinApplication"); |
| List<TypeBuilder> interfaces = popIfNotNull(implementsKeyword); |
| TypeBuilder mixinApplication = pop(); |
| List<TypeVariableBuilder> typeVariables = pop(); |
| String name = pop(); |
| if (typeVariables != null && mixinApplication is MixinApplicationBuilder) { |
| mixinApplication.typeVariables = typeVariables; |
| mixinApplication.subclassName = name; |
| } |
| int modifiers = Modifier.validate(pop()); |
| List<MetadataBuilder> metadata = pop(); |
| library.addNamedMixinApplication(metadata, name, typeVariables, modifiers, |
| mixinApplication, interfaces, beginToken.charOffset); |
| checkEmpty(beginToken.charOffset); |
| } |
| |
| @override |
| void endTypeArguments(int count, Token beginToken, Token endToken) { |
| debugEvent("TypeArguments"); |
| push(popList(count) ?? NullValue.TypeArguments); |
| } |
| |
| @override |
| void handleScript(Token token) { |
| debugEvent("Script"); |
| } |
| |
| @override |
| void handleType(Token beginToken, Token endToken) { |
| debugEvent("Type"); |
| List<TypeBuilder> arguments = pop(); |
| String name = pop(); |
| push(library.addNamedType(name, arguments, beginToken.charOffset)); |
| } |
| |
| @override |
| void endTypeList(int count) { |
| debugEvent("TypeList"); |
| push(popList(count) ?? NullValue.TypeList); |
| } |
| |
| @override |
| void endTypeVariables(int count, Token beginToken, Token endToken) { |
| debugEvent("TypeVariables"); |
| push(popList(count) ?? NullValue.TypeVariables); |
| } |
| |
| @override |
| void handleVoidKeyword(Token token) { |
| debugEvent("VoidKeyword"); |
| push(library.addVoidType(token.charOffset)); |
| } |
| |
| @override |
| void endFormalParameter( |
| Token covariantKeyword, Token thisKeyword, FormalParameterType kind) { |
| debugEvent("FormalParameter"); |
| String name = pop(); |
| TypeBuilder type = pop(); |
| int modifiers = Modifier.validate(pop()); |
| List<MetadataBuilder> metadata = pop(); |
| // TODO(ahe): Needs begin token. |
| push(library.addFormalParameter(metadata, modifiers, type, name, |
| thisKeyword != null, thisKeyword?.charOffset ?? -1)); |
| } |
| |
| @override |
| void handleValuedFormalParameter(Token equals, Token token) { |
| debugEvent("ValuedFormalParameter"); |
| // Ignored for now. |
| } |
| |
| @override |
| void handleFormalParameterWithoutValue(Token token) { |
| debugEvent("FormalParameterWithoutValue"); |
| // Ignored for now. |
| } |
| |
| @override |
| void endFunctionTypedFormalParameter( |
| Token covariantKeyword, Token thisKeyword, FormalParameterType kind) { |
| debugEvent("FunctionTypedFormalParameter"); |
| pop(); // Function type parameters. |
| pop(); // Type variables. |
| String name = pop(); |
| pop(); // Return type. |
| push(NullValue.Type); |
| push(name); |
| } |
| |
| @override |
| void endOptionalFormalParameters( |
| int count, Token beginToken, Token endToken) { |
| debugEvent("OptionalFormalParameters"); |
| FormalParameterType kind = optional("{", beginToken) |
| ? FormalParameterType.NAMED |
| : FormalParameterType.POSITIONAL; |
| // When recovering from an empty list of optional arguments, count may be |
| // 0. It might be simpler if the parser didn't call this method in that |
| // case, however, then [beginOptionalFormalParameters] wouldn't always be |
| // matched by this method. |
| List parameters = popList(count) ?? []; |
| for (FormalParameterBuilder parameter in parameters) { |
| parameter.kind = kind; |
| } |
| push(parameters); |
| } |
| |
| @override |
| void endFormalParameters(int count, Token beginToken, Token endToken) { |
| debugEvent("FormalParameters"); |
| List formals = popList(count); |
| if (formals != null && formals.isNotEmpty) { |
| var last = formals.last; |
| if (last is List) { |
| // TODO(sigmund): change `List newList` back to `var` (this is a |
| // workaround for issue #28651). Eventually, make optional |
| // formals a separate stack entry (#28673). |
| List newList = |
| new List<FormalParameterBuilder>(formals.length - 1 + last.length); |
| newList.setRange(0, formals.length - 1, formals); |
| newList.setRange(formals.length - 1, newList.length, last); |
| for (int i = 0; i < last.length; i++) { |
| newList[i + formals.length - 1] = last[i]; |
| } |
| formals = newList; |
| } |
| } |
| if (formals != null) { |
| for (var formal in formals) { |
| if (formal is! FormalParameterBuilder) { |
| internalError(formals); |
| } |
| } |
| formals = new List<FormalParameterBuilder>.from(formals); |
| } |
| push(formals ?? NullValue.FormalParameters); |
| } |
| |
| @override |
| void endEnum(Token enumKeyword, Token endBrace, int count) { |
| List<String> constants = popList(count); |
| String name = pop(); |
| List<MetadataBuilder> metadata = pop(); |
| library.addEnum( |
| metadata, name, constants, enumKeyword.charOffset, endBrace.charOffset); |
| checkEmpty(enumKeyword.charOffset); |
| } |
| |
| @override |
| void beginFunctionTypeAlias(Token token) { |
| library.beginNestedDeclaration(null, hasMembers: false); |
| } |
| |
| @override |
| void handleFunctionType(Token functionToken, Token endToken) { |
| debugEvent("FunctionType"); |
| List<FormalParameterBuilder> formals = pop(); |
| List<TypeVariableBuilder> typeVariables = pop(); |
| TypeBuilder returnType = pop(); |
| push(library.addFunctionType( |
| returnType, typeVariables, formals, functionToken.charOffset)); |
| } |
| |
| @override |
| void endFunctionTypeAlias( |
| Token typedefKeyword, Token equals, Token endToken) { |
| debugEvent("endFunctionTypeAlias"); |
| List<FormalParameterBuilder> formals; |
| List<TypeVariableBuilder> typeVariables; |
| String name; |
| TypeBuilder returnType; |
| if (equals == null) { |
| formals = pop(); |
| typeVariables = pop(); |
| name = pop(); |
| returnType = pop(); |
| } else { |
| var type = pop(); |
| typeVariables = pop(); |
| name = pop(); |
| if (type is FunctionTypeBuilder) { |
| // TODO(ahe): We need to start a nested declaration when parsing the |
| // formals and return type so we can correctly bind |
| // `type.typeVariables`. A typedef can have type variables, and a new |
| // function type can also have type variables (representing the type of |
| // a generic function). |
| formals = type.formals; |
| returnType = type.returnType; |
| } else { |
| // TODO(ahe): Improve this error message. |
| library.addCompileTimeError( |
| equals.charOffset, "Can't create typedef from non-function type."); |
| } |
| } |
| List<MetadataBuilder> metadata = pop(); |
| library.addFunctionTypeAlias(metadata, returnType, name, typeVariables, |
| formals, typedefKeyword.charOffset); |
| checkEmpty(typedefKeyword.charOffset); |
| } |
| |
| @override |
| void endTopLevelFields(int count, Token beginToken, Token endToken) { |
| debugEvent("endTopLevelFields"); |
| List<String> names = popList(count); |
| TypeBuilder type = pop(); |
| int modifiers = Modifier.validate(pop()); |
| List<MetadataBuilder> metadata = pop(); |
| library.addFields(metadata, modifiers, type, names); |
| checkEmpty(beginToken.charOffset); |
| } |
| |
| @override |
| void endFields( |
| int count, Token covariantToken, Token beginToken, Token endToken) { |
| debugEvent("Fields"); |
| List<String> names = popList(count); |
| TypeBuilder type = pop(); |
| int modifiers = Modifier.validate(pop()); |
| List<MetadataBuilder> metadata = pop(); |
| library.addFields(metadata, modifiers, type, names); |
| } |
| |
| @override |
| void endTypeVariable(Token token, Token extendsOrSuper) { |
| debugEvent("endTypeVariable"); |
| TypeBuilder bound = pop(); |
| String name = pop(); |
| // TODO(paulberry): type variable metadata should not be ignored. See |
| // dartbug.com/28981. |
| /* List<MetadataBuilder> metadata = */ pop(); |
| push(library.addTypeVariable(name, bound, token.charOffset)); |
| } |
| |
| @override |
| void endPartOf(Token partKeyword, Token semicolon, bool hasName) { |
| debugEvent("endPartOf"); |
| String containingLibrary = pop(); |
| List<MetadataBuilder> metadata = pop(); |
| if (hasName) { |
| library.addPartOf(metadata, containingLibrary, null); |
| } else { |
| library.addPartOf(metadata, null, containingLibrary); |
| } |
| } |
| |
| @override |
| void endConstructorReference( |
| Token start, Token periodBeforeName, Token endToken) { |
| debugEvent("ConstructorReference"); |
| String suffix = popIfNotNull(periodBeforeName); |
| List<TypeBuilder> typeArguments = pop(); |
| String name = pop(); |
| push(library.addConstructorReference( |
| name, typeArguments, suffix, start.charOffset)); |
| } |
| |
| @override |
| void beginFactoryMethod(Token token) { |
| library.beginNestedDeclaration(null, hasMembers: false); |
| } |
| |
| @override |
| void endFactoryMethod( |
| Token beginToken, Token factoryKeyword, Token endToken) { |
| debugEvent("FactoryMethod"); |
| MethodBody kind = pop(); |
| ConstructorReferenceBuilder redirectionTarget; |
| if (kind == MethodBody.RedirectingFactoryBody) { |
| redirectionTarget = pop(); |
| } |
| AsyncMarker asyncModifier = pop(); |
| List<FormalParameterBuilder> formals = pop(); |
| var name = pop(); |
| int modifiers = Modifier.validate(pop()); |
| List<MetadataBuilder> metadata = pop(); |
| library.addFactoryMethod( |
| metadata, |
| modifiers, |
| name, |
| formals, |
| asyncModifier, |
| redirectionTarget, |
| beginToken.charOffset, |
| endToken.charOffset, |
| nativeMethodName); |
| nativeMethodName = null; |
| } |
| |
| @override |
| void endRedirectingFactoryBody(Token beginToken, Token endToken) { |
| debugEvent("RedirectingFactoryBody"); |
| push(MethodBody.RedirectingFactoryBody); |
| } |
| |
| @override |
| void endFieldInitializer(Token assignmentOperator) { |
| debugEvent("FieldInitializer"); |
| // Ignoring field initializers for now. |
| } |
| |
| @override |
| void handleNoFieldInitializer(Token token) { |
| debugEvent("NoFieldInitializer"); |
| } |
| |
| @override |
| void endInitializers(int count, Token beginToken, Token endToken) { |
| debugEvent("Initializers"); |
| // Ignored for now. |
| } |
| |
| @override |
| void handleNoInitializers() { |
| debugEvent("NoInitializers"); |
| // This is a constructor initializer and it's ignored for now. |
| } |
| |
| @override |
| void endMember() { |
| debugEvent("Member"); |
| assert(nativeMethodName == null); |
| } |
| |
| @override |
| void endClassBody(int memberCount, Token beginToken, Token endToken) { |
| debugEvent("ClassBody"); |
| } |
| |
| @override |
| void handleAsyncModifier(Token asyncToken, Token starToken) { |
| debugEvent("AsyncModifier"); |
| push(asyncMarkerFromTokens(asyncToken, starToken)); |
| } |
| |
| @override |
| void handleModifier(Token token) { |
| debugEvent("Modifier"); |
| push(new Modifier.fromString(token.stringValue)); |
| } |
| |
| @override |
| void handleModifiers(int count) { |
| debugEvent("Modifiers"); |
| push(popList(count) ?? NullValue.Modifiers); |
| } |
| |
| @override |
| Token handleUnrecoverableError(Token token, ErrorKind kind, Map arguments) { |
| if (isDartLibrary && kind == ErrorKind.ExpectedBlockToSkip) { |
| Token recover = skipNativeClause(token); |
| if (recover != null) { |
| nativeMethodName = unescapeString(token.next.lexeme); |
| return recover; |
| } |
| } |
| return super.handleUnrecoverableError(token, kind, arguments); |
| } |
| |
| @override |
| Link<Token> handleMemberName(Link<Token> identifiers) { |
| if (!isDartLibrary || identifiers.isEmpty) return identifiers; |
| return removeNativeClause(identifiers); |
| } |
| |
| @override |
| void debugEvent(String name) { |
| // printEvent(name); |
| } |
| } |