blob: 516a6b6dceb7a2cd5b018e62f74c8893efecaea8 [file] [log] [blame]
// 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.diet_listener;
import 'package:kernel/ast.dart'
show
AsyncMarker,
Class,
Expression,
Field,
InterfaceType,
Library,
LibraryDependency,
LibraryPart,
Node,
Typedef;
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/core_types.dart' show CoreTypes;
import '../../scanner/token.dart' show Token;
import '../builder/builder.dart';
import '../constant_context.dart' show ConstantContext;
import '../deprecated_problems.dart'
show Crash, deprecated_InputError, deprecated_inputError;
import '../fasta_codes.dart'
show Message, messageExpectedBlockToSkip, templateInternalProblemNotFound;
import '../kernel/kernel_body_builder.dart' show KernelBodyBuilder;
import '../parser.dart' show Assert, MemberKind, Parser, optional;
import '../problems.dart' show internalProblem, unexpected;
import '../type_inference/type_inference_engine.dart' show TypeInferenceEngine;
import '../type_inference/type_inference_listener.dart'
show TypeInferenceListener;
import 'source_library_builder.dart' show SourceLibraryBuilder;
import 'stack_listener.dart' show NullValue, StackListener;
import '../quote.dart' show unescapeString;
class DietListener extends StackListener {
final SourceLibraryBuilder library;
final ClassHierarchy hierarchy;
final CoreTypes coreTypes;
final bool enableNative;
final bool stringExpectedAfterNative;
final TypeInferenceEngine typeInferenceEngine;
int importExportDirectiveIndex = 0;
int partDirectiveIndex = 0;
ClassBuilder currentClass;
/// For top-level declarations, this is the library scope. For class members,
/// this is the instance scope of [currentClass].
Scope memberScope;
@override
Uri uri;
DietListener(SourceLibraryBuilder library, this.hierarchy, this.coreTypes,
this.typeInferenceEngine)
: library = library,
uri = library.fileUri,
memberScope = library.scope,
enableNative =
library.loader.target.backendTarget.enableNative(library.uri),
stringExpectedAfterNative =
library.loader.target.backendTarget.nativeExtensionExpectsString;
void discard(int n) {
for (int i = 0; i < n; i++) {
pop();
}
}
@override
void endMetadataStar(int count) {
debugEvent("MetadataStar");
push(popList(count, new List<Token>.filled(count, null, growable: true))
?.first ??
NullValue.Metadata);
}
@override
void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
debugEvent("Metadata");
discard(periodBeforeName == null ? 1 : 2);
push(beginToken);
}
@override
void endPartOf(
Token partKeyword, Token ofKeyword, Token semicolon, bool hasName) {
debugEvent("PartOf");
if (hasName) discard(1);
discard(1); // Metadata.
}
@override
void handleInvalidTopLevelDeclaration(Token beginToken) {
debugEvent("InvalidTopLevelDeclaration");
pop(); // metadata star
}
@override
void handleNoArguments(Token token) {
debugEvent("NoArguments");
}
@override
void handleNoTypeArguments(Token token) {
debugEvent("NoTypeArguments");
}
@override
void handleNoConstructorReferenceContinuationAfterTypeArguments(Token token) {
debugEvent("NoConstructorReferenceContinuationAfterTypeArguments");
}
@override
void handleNoType(Token lastConsumed) {
debugEvent("NoType");
}
@override
void handleType(Token beginToken, Token endToken) {
debugEvent("Type");
discard(1);
}
@override
void endTypeList(int count) {
debugEvent("TypeList");
}
@override
void endMixinApplication(Token withKeyword) {
debugEvent("MixinApplication");
}
@override
void endTypeArguments(int count, Token beginToken, Token endToken) {
debugEvent("TypeArguments");
}
@override
void endFieldInitializer(Token assignmentOperator, Token token) {
debugEvent("FieldInitializer");
}
@override
void handleNoFieldInitializer(Token token) {
debugEvent("NoFieldInitializer");
}
@override
void handleNoTypeVariables(Token token) {
debugEvent("NoTypeVariables");
}
@override
void endFormalParameters(
int count, Token beginToken, Token endToken, MemberKind kind) {
debugEvent("FormalParameters");
assert(count == 0); // Count is always 0 as the diet parser skips formals.
if (kind != MemberKind.GeneralizedFunctionType &&
identical(peek(), "-") &&
identical(beginToken.next, endToken)) {
pop();
push("unary-");
}
push(beginToken);
}
@override
void handleNoFormalParameters(Token token, MemberKind kind) {
debugEvent("NoFormalParameters");
if (identical(peek(), "-")) {
pop();
push("unary-");
}
push(token);
}
@override
void endFunctionType(Token functionToken, Token endToken) {
debugEvent("FunctionType");
discard(1);
}
@override
void endFunctionTypeAlias(
Token typedefKeyword, Token equals, Token endToken) {
debugEvent("FunctionTypeAlias");
if (equals == null) pop(); // endToken
String name = pop();
Token metadata = pop();
Declaration typedefBuilder = lookupBuilder(typedefKeyword, null, name);
Typedef target = typedefBuilder.target;
var metadataConstants = parseMetadata(typedefBuilder, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
target.addAnnotation(metadataConstant);
}
}
checkEmpty(typedefKeyword.charOffset);
}
@override
void endFields(Token staticToken, Token covariantToken, Token varFinalOrConst,
int count, Token beginToken, Token endToken) {
debugEvent("Fields");
buildFields(count, beginToken, false);
}
@override
void handleAsyncModifier(Token asyncToken, Token startToken) {
debugEvent("AsyncModifier");
}
@override
void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
debugEvent("TopLevelMethod");
Token bodyToken = pop();
String name = pop();
Token metadata = pop();
checkEmpty(beginToken.charOffset);
buildFunctionBody(bodyToken, lookupBuilder(beginToken, getOrSet, name),
MemberKind.TopLevelMethod, metadata);
}
@override
void handleNoFunctionBody(Token token) {
debugEvent("NoFunctionBody");
}
@override
void endTopLevelFields(Token staticToken, Token covariantToken,
Token varFinalOrConst, int count, Token beginToken, Token endToken) {
debugEvent("TopLevelFields");
buildFields(count, beginToken, true);
}
@override
void handleVoidKeyword(Token token) {
debugEvent("VoidKeyword");
}
@override
void handleNoInitializers() {
debugEvent("NoInitializers");
}
@override
void endInitializers(int count, Token beginToken, Token endToken) {
debugEvent("Initializers");
}
@override
void handleQualified(Token period) {
debugEvent("handleQualified");
String suffix = pop();
var prefix = pop();
push(new QualifiedName(prefix, suffix, period.charOffset));
}
@override
void endLibraryName(Token libraryKeyword, Token semicolon) {
debugEvent("endLibraryName");
pop(); // name
Token metadata = pop();
Library target = library.target;
var metadataConstants = parseMetadata(library, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
target.addAnnotation(metadataConstant);
}
}
}
@override
void beginLiteralString(Token token) {
debugEvent("beginLiteralString");
}
@override
void handleStringPart(Token token) {
debugEvent("StringPart");
}
@override
void endLiteralString(int interpolationCount, Token endToken) {
debugEvent("endLiteralString");
}
@override
void handleNativeClause(Token nativeToken, bool hasName) {
debugEvent("NativeClause");
}
@override
void handleScript(Token token) {
debugEvent("Script");
}
@override
void handleStringJuxtaposition(int literalCount) {
debugEvent("StringJuxtaposition");
}
@override
void handleDottedName(int count, Token firstIdentifier) {
debugEvent("DottedName");
discard(count);
}
@override
void endConditionalUri(Token ifKeyword, Token leftParen, Token equalSign) {
debugEvent("ConditionalUri");
}
@override
void endConditionalUris(int count) {
debugEvent("ConditionalUris");
}
@override
void handleOperatorName(Token operatorKeyword, Token token) {
debugEvent("OperatorName");
push(token.stringValue);
}
@override
void handleInvalidOperatorName(Token operatorKeyword, Token token) {
debugEvent("InvalidOperatorName");
push('invalid');
}
@override
void handleIdentifierList(int count) {
debugEvent("IdentifierList");
discard(count);
}
@override
void endShow(Token showKeyword) {
debugEvent("Show");
}
@override
void endHide(Token hideKeyword) {
debugEvent("Hide");
}
@override
void endCombinators(int count) {
debugEvent("Combinators");
}
@override
void handleImportPrefix(Token deferredKeyword, Token asKeyword) {
debugEvent("ImportPrefix");
pushIfNull(asKeyword, NullValue.Prefix);
}
@override
void endImport(Token importKeyword, Token semicolon) {
debugEvent("Import");
pop(NullValue.Prefix);
Token metadata = pop();
// Native imports must be skipped because they aren't assigned corresponding
// LibraryDependency nodes.
Token importUriToken = importKeyword.next;
String importUri =
unescapeString(importUriToken.lexeme, importUriToken, this);
if (importUri.startsWith("dart-ext:")) return;
Library libraryNode = library.target;
LibraryDependency dependency =
libraryNode.dependencies[importExportDirectiveIndex++];
var metadataConstants = parseMetadata(library, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
dependency.addAnnotation(metadataConstant);
}
}
}
@override
void handleRecoverImport(Token semicolon) {
pop(NullValue.Prefix);
}
@override
void endExport(Token exportKeyword, Token semicolon) {
debugEvent("Export");
Token metadata = pop();
Library libraryNode = library.target;
LibraryDependency dependency =
libraryNode.dependencies[importExportDirectiveIndex++];
var metadataConstants = parseMetadata(library, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
dependency.addAnnotation(metadataConstant);
}
}
}
@override
void endPart(Token partKeyword, Token semicolon) {
debugEvent("Part");
Token metadata = pop();
Library libraryNode = library.target;
LibraryPart part = libraryNode.parts[partDirectiveIndex++];
var metadataConstants = parseMetadata(library, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
part.addAnnotation(metadataConstant);
}
}
}
@override
void endTypeVariable(Token token, Token extendsOrSuper) {
debugEvent("TypeVariable");
discard(1); // Metadata.
}
@override
void endTypeVariables(int count, Token beginToken, Token endToken) {
debugEvent("TypeVariables");
}
@override
void endConstructorReference(
Token start, Token periodBeforeName, Token endToken) {
debugEvent("ConstructorReference");
popIfNotNull(periodBeforeName);
}
@override
void endFactoryMethod(
Token beginToken, Token factoryKeyword, Token endToken) {
debugEvent("FactoryMethod");
Token bodyToken = pop();
Object name = pop();
Token metadata = pop();
checkEmpty(beginToken.charOffset);
if (bodyToken == null || optional("=", bodyToken.endGroup.next)) {
// TODO(ahe): Don't skip this. We need to compile metadata and
// redirecting factory bodies.
return;
}
buildFunctionBody(bodyToken, lookupConstructor(beginToken, name),
MemberKind.Factory, metadata);
}
@override
void endRedirectingFactoryBody(Token beginToken, Token endToken) {
debugEvent("RedirectingFactoryBody");
discard(1); // ConstructorReference.
}
@override
void handleNativeFunctionBody(Token nativeToken, Token semicolon) {
debugEvent("NativeFunctionBody");
}
@override
void handleNativeFunctionBodyIgnored(Token nativeToken, Token semicolon) {
debugEvent("NativeFunctionBodyIgnored");
}
@override
void handleNativeFunctionBodySkipped(Token nativeToken, Token semicolon) {
debugEvent("NativeFunctionBodySkipped");
if (!enableNative) {
super.handleUnrecoverableError(nativeToken, messageExpectedBlockToSkip);
}
}
@override
void endMethod(
Token getOrSet, Token beginToken, Token beginParam, Token endToken) {
debugEvent("Method");
// TODO(danrubel): Consider removing the beginParam parameter
// and using bodyToken, but pushing a NullValue on the stack
// in handleNoFormalParameters rather than the supplied token.
pop(); // bodyToken
Object name = pop();
Token metadata = pop();
checkEmpty(beginToken.charOffset);
ProcedureBuilder builder;
if (name is QualifiedName ||
(getOrSet == null && name == currentClass.name)) {
builder = lookupConstructor(beginToken, name);
} else {
builder = lookupBuilder(beginToken, getOrSet, name);
}
buildFunctionBody(
beginParam,
builder,
builder.isStatic ? MemberKind.StaticMethod : MemberKind.NonStaticMethod,
metadata);
}
StackListener createListener(
ModifierBuilder builder, Scope memberScope, bool isInstanceMember,
[Scope formalParameterScope,
TypeInferenceListener<int, int, Node, int> listener]) {
listener ??= new TypeInferenceListener<int, int, Node, int>();
// Note: we set thisType regardless of whether we are building a static
// member, since that provides better error recovery.
InterfaceType thisType = currentClass?.target?.thisType;
var typeInferrer = library.disableTypeInference
? typeInferenceEngine.createDisabledTypeInferrer()
: typeInferenceEngine.createLocalTypeInferrer(
uri, listener, thisType, library);
ConstantContext constantContext = builder.isConstructor && builder.isConst
? ConstantContext.inferred
: ConstantContext.none;
return new KernelBodyBuilder(
library,
builder,
memberScope,
formalParameterScope,
hierarchy,
coreTypes,
currentClass,
isInstanceMember,
uri,
typeInferrer)
..constantContext = constantContext;
}
void buildFunctionBody(
Token token, ProcedureBuilder builder, MemberKind kind, Token metadata) {
Scope typeParameterScope = builder.computeTypeParameterScope(memberScope);
Scope formalParameterScope =
builder.computeFormalParameterScope(typeParameterScope);
assert(typeParameterScope != null);
assert(formalParameterScope != null);
parseFunctionBody(
createListener(builder, typeParameterScope, builder.isInstanceMember,
formalParameterScope),
token,
metadata,
kind);
}
void buildFields(int count, Token token, bool isTopLevel) {
List<String> names =
popList(count, new List<String>.filled(count, null, growable: true));
Declaration declaration = lookupBuilder(token, null, names.first);
Token metadata = pop();
// TODO(paulberry): don't re-parse the field if we've already parsed it
// for type inference.
parseFields(
createListener(declaration, memberScope, declaration.isInstanceMember),
token,
metadata,
isTopLevel);
}
@override
void handleInvalidMember(Token endToken) {
debugEvent("InvalidMember");
pop(); // metadata star
}
@override
void endMember() {
debugEvent("Member");
checkEmpty(-1);
}
@override
void endAssert(Token assertKeyword, Assert kind, Token leftParenthesis,
Token commaToken, Token semicolonToken) {
debugEvent("Assert");
// Do nothing
}
@override
void beginClassBody(Token token) {
debugEvent("beginClassBody");
String name = pop();
Token metadata = pop();
assert(currentClass == null);
assert(memberScope == library.scope);
Declaration classBuilder = lookupBuilder(token, null, name);
Class target = classBuilder.target;
var metadataConstants = parseMetadata(classBuilder, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
target.addAnnotation(metadataConstant);
}
}
currentClass = classBuilder;
memberScope = currentClass.scope;
}
@override
void endClassBody(int memberCount, Token beginToken, Token endToken) {
debugEvent("ClassBody");
currentClass = null;
memberScope = library.scope;
}
@override
void endClassDeclaration(Token beginToken, Token endToken) {
debugEvent("ClassDeclaration");
checkEmpty(beginToken.charOffset);
}
@override
void endEnum(Token enumKeyword, Token leftBrace, int count) {
debugEvent("Enum");
List metadataAndValues = new List.filled(count * 2, null, growable: true);
popList(count * 2, metadataAndValues);
String name = pop();
Token metadata = pop();
ClassBuilder enumBuilder = lookupBuilder(enumKeyword, null, name);
Class target = enumBuilder.target;
var metadataConstants = parseMetadata(enumBuilder, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
target.addAnnotation(metadataConstant);
}
}
for (int i = 0; i < metadataAndValues.length; i += 2) {
Token metadata = metadataAndValues[i];
String valueName = metadataAndValues[i + 1];
Declaration declaration = enumBuilder.scope.local[valueName];
if (metadata != null) {
Field field = declaration.target;
for (var annotation in parseMetadata(declaration, metadata)) {
field.addAnnotation(annotation);
}
}
}
checkEmpty(enumKeyword.charOffset);
}
@override
void endNamedMixinApplication(Token beginToken, Token classKeyword,
Token equals, Token implementsKeyword, Token endToken) {
debugEvent("NamedMixinApplication");
String name = pop();
Token metadata = pop();
Declaration classBuilder = lookupBuilder(classKeyword, null, name);
Class target = classBuilder.target;
var metadataConstants = parseMetadata(classBuilder, metadata);
if (metadataConstants != null) {
for (var metadataConstant in metadataConstants) {
target.addAnnotation(metadataConstant);
}
}
checkEmpty(beginToken.charOffset);
}
AsyncMarker getAsyncMarker(StackListener listener) => listener.pop();
/// Invokes the listener's [finishFunction] method.
///
/// This is a separate method so that it may be overridden by a derived class
/// if more computation must be done before finishing the function.
void listenerFinishFunction(
StackListener listener,
Token token,
Token metadata,
MemberKind kind,
List metadataConstants,
dynamic formals,
AsyncMarker asyncModifier,
dynamic body) {
listener.finishFunction(metadataConstants, formals, asyncModifier, body);
}
/// Invokes the listener's [finishFields] method.
///
/// This is a separate method so that it may be overridden by a derived class
/// if more computation must be done before finishing the function.
void listenerFinishFields(StackListener listener, Token startToken,
Token metadata, bool isTopLevel) {
listener.finishFields();
}
void parseFunctionBody(StackListener listener, Token startToken,
Token metadata, MemberKind kind) {
Token token = startToken;
try {
Parser parser = new Parser(listener);
List metadataConstants;
if (metadata != null) {
parser.parseMetadataStar(parser.syntheticPreviousToken(metadata));
metadataConstants = listener.pop();
}
token = parser.parseFormalParametersOpt(
parser.syntheticPreviousToken(token), kind);
var formals = listener.pop();
listener.checkEmpty(token.next.charOffset);
token = parser.parseInitializersOpt(token);
token = parser.parseAsyncModifierOpt(token);
AsyncMarker asyncModifier = getAsyncMarker(listener) ?? AsyncMarker.Sync;
bool isExpression = false;
bool allowAbstract = asyncModifier == AsyncMarker.Sync;
parser.parseFunctionBody(token, isExpression, allowAbstract);
var body = listener.pop();
listener.checkEmpty(token.charOffset);
listenerFinishFunction(listener, startToken, metadata, kind,
metadataConstants, formals, asyncModifier, body);
} on deprecated_InputError {
rethrow;
} catch (e, s) {
throw new Crash(uri, token.charOffset, e, s);
}
}
void parseFields(StackListener listener, Token startToken, Token metadata,
bool isTopLevel) {
Token token = startToken;
Parser parser = new Parser(listener);
if (isTopLevel) {
token = parser.parseTopLevelMember(metadata ?? token);
} else {
token = parser.parseClassMember(metadata ?? token).next;
}
listenerFinishFields(listener, startToken, metadata, isTopLevel);
listener.checkEmpty(token.charOffset);
}
Declaration lookupBuilder(Token token, Token getOrSet, String name) {
// TODO(ahe): Can I move this to Scope or ScopeBuilder?
Declaration declaration;
if (currentClass != null) {
if (uri != currentClass.fileUri) {
unexpected("$uri", "${currentClass.fileUri}", currentClass.charOffset,
currentClass.fileUri);
}
if (getOrSet != null && optional("set", getOrSet)) {
declaration = currentClass.scope.setters[name];
} else {
declaration = currentClass.scope.local[name];
}
} else if (getOrSet != null && optional("set", getOrSet)) {
declaration = library.scope.setters[name];
} else {
declaration = library.scopeBuilder[name];
}
checkBuilder(token, declaration, name);
return declaration;
}
Declaration lookupConstructor(Token token, Object nameOrQualified) {
assert(currentClass != null);
Declaration declaration;
String name;
String suffix;
if (nameOrQualified is QualifiedName) {
name = nameOrQualified.prefix;
suffix = nameOrQualified.suffix;
} else {
name = nameOrQualified;
suffix = name == currentClass.name ? "" : name;
}
declaration = currentClass.constructors.local[suffix];
checkBuilder(token, declaration, nameOrQualified);
return declaration;
}
void checkBuilder(Token token, Declaration declaration, Object name) {
if (declaration == null) {
internalProblem(templateInternalProblemNotFound.withArguments("$name"),
token.charOffset, uri);
}
if (declaration.next != null) {
deprecated_inputError(uri, token.charOffset, "Duplicated name: $name");
}
if (uri != declaration.fileUri) {
unexpected("$uri", "${declaration.fileUri}", declaration.charOffset,
declaration.fileUri);
}
}
@override
void addCompileTimeError(Message message, int charOffset, int length) {
library.addCompileTimeError(message, charOffset, length, uri);
}
void addProblem(Message message, int charOffset, int length) {
library.addProblem(message, charOffset, length, uri);
}
@override
void debugEvent(String name) {
// printEvent('DietListener: $name');
}
/// If the [metadata] is not `null`, return the parsed metadata [Expression]s.
/// Otherwise, return `null`.
List<Expression> parseMetadata(ModifierBuilder builder, Token metadata) {
if (metadata != null) {
var listener = createListener(builder, memberScope, false);
var parser = new Parser(listener);
parser.parseMetadataStar(parser.syntheticPreviousToken(metadata));
return listener.finishMetadata();
}
return null;
}
}