blob: 97423bd49f05c7480b8b9861d2464831083eed26 [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.analyzer.ast_builder;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/dart/ast/ast_factory.dart' show AstFactory;
import 'package:analyzer/dart/ast/standard_ast_factory.dart' as standard;
import 'package:analyzer/dart/ast/token.dart' show Token, TokenType;
import 'package:front_end/src/fasta/parser.dart'
show
Assert,
FormalParameterKind,
IdentifierContext,
MemberKind,
Parser,
closeBraceTokenFor;
import 'package:front_end/src/fasta/scanner/string_scanner.dart';
import 'package:front_end/src/fasta/scanner/token.dart' show CommentToken;
import 'package:front_end/src/scanner/token.dart' as analyzer;
import 'package:front_end/src/fasta/problems.dart' show unhandled;
import 'package:front_end/src/fasta/messages.dart'
show
Code,
Message,
codeExpectedExpression,
codeExpectedFunctionBody,
messageNativeClauseShouldBeAnnotation;
import 'package:front_end/src/fasta/kernel/kernel_builder.dart'
show Builder, KernelLibraryBuilder, Scope;
import 'package:front_end/src/fasta/quote.dart';
import 'package:front_end/src/fasta/source/scope_listener.dart'
show JumpTargetKind, NullValue, ScopeListener;
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
class AstBuilder extends ScopeListener {
final AstFactory ast = standard.astFactory;
final ErrorReporter errorReporter;
final KernelLibraryBuilder library;
final Builder member;
ScriptTag scriptTag;
final List<Directive> directives = <Directive>[];
final List<CompilationUnitMember> declarations = <CompilationUnitMember>[];
@override
final Uri uri;
/**
* The [Parser] that uses this listener, used to parse optional parts, e.g.
* `native` support.
*/
Parser parser;
bool parseGenericMethodComments = false;
/// The name of the class currently being parsed, or `null` if no class is
/// being parsed.
String className;
/// If true, this is building a full AST. Otherwise, only create method
/// bodies.
final bool isFullAst;
/// `true` if the `native` clause is allowed
/// in class, method, and function declarations.
///
/// This is being replaced by the @native(...) annotation.
//
// TODO(danrubel) Move this flag to a better location
// and should only be true if either:
// * The current library is a platform library
// * The current library has an import that uses the scheme "dart-ext".
bool allowNativeClause = false;
StringLiteral nativeName;
AstBuilder(this.errorReporter, this.library, this.member, Scope scope,
this.isFullAst,
[Uri uri])
: uri = uri ?? library.fileUri,
super(scope);
createJumpTarget(JumpTargetKind kind, int charOffset) {
// TODO(ahe): Implement jump targets.
return null;
}
void beginLiteralString(Token token) {
debugEvent("beginLiteralString");
push(token);
}
void handleNamedArgument(Token colon) {
debugEvent("NamedArgument");
Expression expression = pop();
SimpleIdentifier name = pop();
push(ast.namedExpression(ast.label(name, colon), expression));
}
@override
void handleNoConstructorReferenceContinuationAfterTypeArguments(Token token) {
debugEvent("NoConstructorReferenceContinuationAfterTypeArguments");
push(NullValue.ConstructorReferenceContinuationAfterTypeArguments);
}
@override
void endConstructorReference(
Token start, Token periodBeforeName, Token endToken) {
debugEvent("ConstructorReference");
SimpleIdentifier constructorName = pop();
TypeArgumentList typeArguments = pop();
Identifier typeNameIdentifier = pop();
push(ast.constructorName(ast.typeName(typeNameIdentifier, typeArguments),
periodBeforeName, constructorName));
}
@override
void endConstExpression(Token token) {
debugEvent("ConstExpression");
_handleInstanceCreation(token);
}
@override
void endConstLiteral(Token token) {
debugEvent("endConstLiteral");
}
void _handleInstanceCreation(Token token) {
MethodInvocation arguments = pop();
ConstructorName constructorName = pop();
push(ast.instanceCreationExpression(
token, constructorName, arguments.argumentList));
}
@override
void endNewExpression(Token token) {
debugEvent("NewExpression");
_handleInstanceCreation(token);
}
@override
void handleParenthesizedExpression(Token token) {
debugEvent("ParenthesizedExpression");
Expression expression = pop();
push(ast.parenthesizedExpression(
token, expression, closeBraceTokenFor(token)));
}
void handleStringPart(Token token) {
debugEvent("StringPart");
push(token);
}
void doStringPart(Token token) {
push(ast.simpleStringLiteral(token, token.lexeme));
}
void endLiteralString(int interpolationCount, Token endToken) {
debugEvent("endLiteralString");
if (interpolationCount == 0) {
Token token = pop();
String value = unescapeString(token.lexeme);
push(ast.simpleStringLiteral(token, value));
} else {
List parts = popList(1 + interpolationCount * 2);
Token first = parts.first;
Token last = parts.last;
Quote quote = analyzeQuote(first.lexeme);
List<InterpolationElement> elements = <InterpolationElement>[];
elements.add(ast.interpolationString(
first, unescapeFirstStringPart(first.lexeme, quote)));
for (int i = 1; i < parts.length - 1; i++) {
var part = parts[i];
if (part is Token) {
elements.add(ast.interpolationString(part, part.lexeme));
} else if (part is Expression) {
elements.add(ast.interpolationExpression(null, part, null));
} else {
unhandled("${part.runtimeType}", "string interpolation",
first.charOffset, uri);
}
}
elements.add(ast.interpolationString(
last, unescapeLastStringPart(last.lexeme, quote)));
push(ast.stringInterpolation(elements));
}
}
@override
void handleNativeClause(Token nativeToken, bool hasName) {
debugEvent("NativeClause");
if (hasName) {
nativeName = pop(); // StringLiteral
} else {
nativeName = null;
}
}
void handleScript(Token token) {
debugEvent("Script");
scriptTag = ast.scriptTag(token);
}
void handleStringJuxtaposition(int literalCount) {
debugEvent("StringJuxtaposition");
push(ast.adjacentStrings(popList(literalCount)));
}
void endArguments(int count, Token beginToken, Token endToken) {
debugEvent("Arguments");
List expressions = popList(count);
ArgumentList arguments =
ast.argumentList(beginToken, expressions, endToken);
push(ast.methodInvocation(null, null, null, null, arguments));
}
void handleIdentifier(Token token, IdentifierContext context) {
debugEvent("handleIdentifier");
Token analyzerToken = token;
if (context.inSymbol) {
push(analyzerToken);
return;
}
SimpleIdentifier identifier = ast.simpleIdentifier(analyzerToken,
isDeclaration: context.inDeclaration);
if (context.inLibraryOrPartOfDeclaration) {
if (!context.isContinuation) {
push([identifier]);
} else {
push(identifier);
}
} else if (context == IdentifierContext.enumValueDeclaration) {
// TODO(paulberry): analyzer's ASTs allow for enumerated values to have
// metadata, but the spec doesn't permit it.
List<Annotation> metadata;
Comment comment = _toAnalyzerComment(token.precedingComments);
push(ast.enumConstantDeclaration(comment, metadata, identifier));
} else {
push(identifier);
}
}
void handleSend(Token beginToken, Token endToken) {
debugEvent("Send");
MethodInvocation arguments = pop();
TypeArgumentList typeArguments = pop();
if (arguments != null) {
doInvocation(endToken, typeArguments, arguments);
} else {
doPropertyGet(endToken);
}
}
void doInvocation(
Token token, TypeArgumentList typeArguments, MethodInvocation arguments) {
Expression receiver = pop();
if (receiver is SimpleIdentifier) {
arguments.methodName = receiver;
if (typeArguments != null) {
arguments.typeArguments = typeArguments;
}
push(arguments);
} else {
push(ast.functionExpressionInvocation(
receiver, typeArguments, arguments.argumentList));
}
}
void doPropertyGet(Token token) {}
void endExpressionStatement(Token token) {
debugEvent("ExpressionStatement");
push(ast.expressionStatement(pop(), token));
}
@override
void handleNativeFunctionBody(Token nativeToken, Token semicolon) {
debugEvent("NativeFunctionBody");
// TODO(danrubel) Change the parser to not produce these modifiers.
pop(); // star
pop(); // async
push(ast.nativeFunctionBody(nativeToken, nativeName, semicolon));
}
@override
void handleEmptyFunctionBody(Token semicolon) {
debugEvent("EmptyFunctionBody");
// TODO(scheglov) Change the parser to not produce these modifiers.
pop(); // star
pop(); // async
push(ast.emptyFunctionBody(semicolon));
}
@override
void handleEmptyStatement(Token token) {
debugEvent("EmptyStatement");
push(ast.emptyStatement(token));
}
void endBlockFunctionBody(int count, Token beginToken, Token endToken) {
debugEvent("BlockFunctionBody");
List statements = popList(count);
if (beginToken != null) {
exitLocalScope();
}
Block block = ast.block(beginToken, statements, endToken);
Token star = pop();
Token asyncKeyword = pop();
push(ast.blockFunctionBody(asyncKeyword, star, block));
}
void finishFunction(annotations, formals, asyncModifier, FunctionBody body) {
debugEvent("finishFunction");
Statement bodyStatement;
if (body is EmptyFunctionBody) {
bodyStatement = ast.emptyStatement(body.semicolon);
} else if (body is NativeFunctionBody) {
// TODO(danrubel): what do we need to do with NativeFunctionBody?
} else if (body is ExpressionFunctionBody) {
bodyStatement = ast.returnStatement(null, body.expression, null);
} else {
bodyStatement = (body as BlockFunctionBody).block;
}
// TODO(paulberry): what do we need to do with bodyStatement at this point?
bodyStatement; // Suppress "unused local variable" hint
}
void beginCascade(Token token) {
debugEvent("beginCascade");
Expression expression = pop();
push(token);
if (expression is CascadeExpression) {
push(expression);
} else {
push(ast.cascadeExpression(expression, <Expression>[]));
}
push(NullValue.CascadeReceiver);
}
void endCascade() {
debugEvent("Cascade");
Expression expression = pop();
CascadeExpression receiver = pop();
pop(); // Token.
receiver.cascadeSections.add(expression);
push(receiver);
}
void handleOperator(Token token) {
debugEvent("Operator");
push(token);
}
void handleSymbolVoid(Token token) {
debugEvent("SymbolVoid");
push(token);
}
@override
void endBinaryExpression(Token token) {
debugEvent("BinaryExpression");
if (identical(".", token.stringValue) ||
identical("?.", token.stringValue) ||
identical("..", token.stringValue)) {
doDotExpression(token);
} else {
Expression right = pop();
Expression left = pop();
push(ast.binaryExpression(left, token, right));
}
}
void doDotExpression(Token token) {
Expression identifierOrInvoke = pop();
Expression receiver = pop();
if (identifierOrInvoke is SimpleIdentifier) {
if (receiver is SimpleIdentifier && identical('.', token.stringValue)) {
push(ast.prefixedIdentifier(receiver, token, identifierOrInvoke));
} else {
push(ast.propertyAccess(receiver, token, identifierOrInvoke));
}
} else if (identifierOrInvoke is MethodInvocation) {
assert(identifierOrInvoke.target == null);
identifierOrInvoke
..target = receiver
..operator = token;
push(identifierOrInvoke);
} else {
unhandled("${identifierOrInvoke.runtimeType}", "property access",
token.charOffset, uri);
}
}
void handleLiteralInt(Token token) {
debugEvent("LiteralInt");
push(ast.integerLiteral(token, int.parse(token.lexeme)));
}
void handleExpressionFunctionBody(Token arrowToken, Token endToken) {
debugEvent("ExpressionFunctionBody");
Expression expression = pop();
Token star = pop();
Token asyncKeyword = pop();
assert(star == null);
push(ast.expressionFunctionBody(
asyncKeyword, arrowToken, expression, endToken));
}
void endReturnStatement(
bool hasExpression, Token beginToken, Token endToken) {
debugEvent("ReturnStatement");
Expression expression = hasExpression ? pop() : null;
push(ast.returnStatement(beginToken, expression, endToken));
}
void endIfStatement(Token ifToken, Token elseToken) {
Statement elsePart = popIfNotNull(elseToken);
Statement thenPart = pop();
ParenthesizedExpression condition = pop();
push(ast.ifStatement(ifToken, condition.leftParenthesis, condition,
condition.rightParenthesis, thenPart, elseToken, elsePart));
}
void handleNoInitializers() {
debugEvent("NoInitializers");
if (!isFullAst) return;
push(NullValue.ConstructorInitializerSeparator);
push(NullValue.ConstructorInitializers);
}
void endInitializers(int count, Token beginToken, Token endToken) {
debugEvent("Initializers");
List<Object> initializerObjects = popList(count) ?? const [];
if (!isFullAst) return;
push(beginToken);
var initializers = <ConstructorInitializer>[];
for (Object initializerObject in initializerObjects) {
if (initializerObject is FunctionExpressionInvocation) {
Expression function = initializerObject.function;
if (function is SuperExpression) {
initializers.add(ast.superConstructorInvocation(function.superKeyword,
null, null, initializerObject.argumentList));
} else {
initializers.add(ast.redirectingConstructorInvocation(
(function as ThisExpression).thisKeyword,
null,
null,
initializerObject.argumentList));
}
} else if (initializerObject is MethodInvocation) {
Expression target = initializerObject.target;
if (target is SuperExpression) {
initializers.add(ast.superConstructorInvocation(
target.superKeyword,
initializerObject.operator,
initializerObject.methodName,
initializerObject.argumentList));
} else {
initializers.add(ast.redirectingConstructorInvocation(
(target as ThisExpression).thisKeyword,
initializerObject.operator,
initializerObject.methodName,
initializerObject.argumentList));
}
} else if (initializerObject is AssignmentExpression) {
Token thisKeyword;
Token period;
SimpleIdentifier fieldName;
Expression left = initializerObject.leftHandSide;
if (left is PropertyAccess) {
var thisExpression = left.target as ThisExpression;
thisKeyword = thisExpression.thisKeyword;
period = left.operator;
fieldName = left.propertyName;
} else {
fieldName = left as SimpleIdentifier;
}
initializers.add(ast.constructorFieldInitializer(
thisKeyword,
period,
fieldName,
initializerObject.operator,
initializerObject.rightHandSide));
} else if (initializerObject is AssertInitializer) {
initializers.add(initializerObject);
}
}
push(initializers);
}
void endVariableInitializer(Token assignmentOperator) {
debugEvent("VariableInitializer");
assert(assignmentOperator.stringValue == "=");
Expression initializer = pop();
Identifier identifier = pop();
// TODO(ahe): Don't push initializers, instead install them.
push(ast.variableDeclaration(identifier, assignmentOperator, initializer));
}
@override
void endWhileStatement(Token whileKeyword, Token endToken) {
debugEvent("WhileStatement");
Statement body = pop();
ParenthesizedExpression condition = pop();
exitContinueTarget();
exitBreakTarget();
push(ast.whileStatement(whileKeyword, condition.leftParenthesis,
condition.expression, condition.rightParenthesis, body));
}
@override
void endYieldStatement(Token yieldToken, Token starToken, Token endToken) {
debugEvent("YieldStatement");
assert(endToken.lexeme == ';');
Expression expression = pop();
push(ast.yieldStatement(yieldToken, starToken, expression, endToken));
}
@override
void handleNoVariableInitializer(Token token) {
debugEvent("NoVariableInitializer");
}
void endInitializedIdentifier(Token nameToken) {
debugEvent("InitializedIdentifier");
AstNode node = pop();
VariableDeclaration variable;
// TODO(paulberry): This seems kludgy. It would be preferable if we
// could respond to a "handleNoVariableInitializer" event by converting a
// SimpleIdentifier into a VariableDeclaration, and then when this code was
// reached, node would always be a VariableDeclaration.
if (node is VariableDeclaration) {
variable = node;
} else if (node is SimpleIdentifier) {
variable = ast.variableDeclaration(node, null, null);
} else {
unhandled("${node.runtimeType}", "identifier", nameToken.charOffset, uri);
}
push(variable);
}
void endVariablesDeclaration(int count, Token endToken) {
debugEvent("VariablesDeclaration");
List<VariableDeclaration> variables = popList(count);
TypeAnnotation type = pop();
_Modifiers modifiers = pop();
Token keyword = modifiers?.finalConstOrVarKeyword;
List<Annotation> metadata = pop();
Comment comment = pop();
push(ast.variableDeclarationStatement(
ast.variableDeclarationList(
comment, metadata, keyword, type, variables),
endToken));
}
void handleAssignmentExpression(Token token) {
debugEvent("AssignmentExpression");
Expression rhs = pop();
Expression lhs = pop();
push(ast.assignmentExpression(lhs, token, rhs));
}
void endBlock(int count, Token beginToken, Token endToken) {
debugEvent("Block");
List<Statement> statements = popList(count) ?? <Statement>[];
exitLocalScope();
push(ast.block(beginToken, statements, endToken));
}
void endForStatement(Token forKeyword, Token leftSeparator,
int updateExpressionCount, Token endToken) {
debugEvent("ForStatement");
Statement body = pop();
List<Expression> updates = popList(updateExpressionCount);
Statement conditionStatement = pop();
Object initializerPart = pop();
exitLocalScope();
exitContinueTarget();
exitBreakTarget();
analyzer.BeginToken leftParenthesis =
unsafeToken(forKeyword.next, TokenType.OPEN_PAREN);
VariableDeclarationList variableList;
Expression initializer;
if (initializerPart is VariableDeclarationStatement) {
variableList = initializerPart.variables;
} else {
initializer = initializerPart as Expression;
}
Expression condition;
Token rightSeparator;
if (conditionStatement is ExpressionStatement) {
condition = conditionStatement.expression;
rightSeparator = conditionStatement.semicolon;
} else {
rightSeparator = (conditionStatement as EmptyStatement).semicolon;
}
push(ast.forStatement(
forKeyword,
leftParenthesis,
variableList,
initializer,
leftSeparator,
condition,
rightSeparator,
updates,
leftParenthesis?.endGroup,
body));
}
void handleLiteralList(
int count, Token beginToken, Token constKeyword, Token endToken) {
debugEvent("LiteralList");
List<Expression> expressions = popList(count);
TypeArgumentList typeArguments = pop();
push(ast.listLiteral(
constKeyword, typeArguments, beginToken, expressions, endToken));
}
void handleAsyncModifier(Token asyncToken, Token starToken) {
debugEvent("AsyncModifier");
push(asyncToken ?? NullValue.FunctionBodyAsyncToken);
push(starToken ?? NullValue.FunctionBodyStarToken);
}
void endAwaitExpression(Token beginToken, Token endToken) {
debugEvent("AwaitExpression");
push(ast.awaitExpression(beginToken, pop()));
}
void handleLiteralBool(Token token) {
debugEvent("LiteralBool");
bool value = identical(token.stringValue, "true");
assert(value || identical(token.stringValue, "false"));
push(ast.booleanLiteral(token, value));
}
void handleLiteralDouble(Token token) {
debugEvent("LiteralDouble");
push(ast.doubleLiteral(token, double.parse(token.lexeme)));
}
void handleLiteralNull(Token token) {
debugEvent("LiteralNull");
push(ast.nullLiteral(token));
}
void handleLiteralMap(
int count, Token beginToken, Token constKeyword, Token endToken) {
debugEvent("LiteralMap");
List<MapLiteralEntry> entries = popList(count) ?? <MapLiteralEntry>[];
TypeArgumentList typeArguments = pop();
push(ast.mapLiteral(
constKeyword, typeArguments, beginToken, entries, endToken));
}
void endLiteralMapEntry(Token colon, Token endToken) {
debugEvent("LiteralMapEntry");
Expression value = pop();
Expression key = pop();
push(ast.mapLiteralEntry(key, colon, value));
}
void endLiteralSymbol(Token hashToken, int tokenCount) {
debugEvent("LiteralSymbol");
List<Token> components = popList(tokenCount);
push(ast.symbolLiteral(hashToken, components));
}
@override
void handleSuperExpression(Token token, IdentifierContext context) {
debugEvent("SuperExpression");
push(ast.superExpression(token));
}
@override
void handleThisExpression(Token token, IdentifierContext context) {
debugEvent("ThisExpression");
push(ast.thisExpression(token));
}
void handleType(Token beginToken, Token endToken) {
debugEvent("Type");
TypeArgumentList arguments = pop();
Identifier name = pop();
push(ast.typeName(name, arguments));
}
@override
void endAssert(Token assertKeyword, Assert kind, Token leftParenthesis,
Token comma, Token rightParenthesis, Token semicolon) {
debugEvent("Assert");
Expression message = popIfNotNull(comma);
Expression condition = pop();
switch (kind) {
case Assert.Expression:
throw new UnimplementedError(
'assert expressions are not yet supported');
break;
case Assert.Initializer:
push(ast.assertInitializer(assertKeyword, leftParenthesis, condition,
comma, message, rightParenthesis));
break;
case Assert.Statement:
push(ast.assertStatement(assertKeyword, leftParenthesis, condition,
comma, message, rightParenthesis, semicolon));
break;
}
}
void handleAsOperator(Token operator, Token endToken) {
debugEvent("AsOperator");
TypeAnnotation type = pop();
Expression expression = pop();
push(ast.asExpression(expression, operator, type));
}
@override
void handleBreakStatement(
bool hasTarget, Token breakKeyword, Token semicolon) {
debugEvent("BreakStatement");
SimpleIdentifier label = hasTarget ? pop() : null;
push(ast.breakStatement(breakKeyword, label, semicolon));
}
@override
void handleContinueStatement(
bool hasTarget, Token continueKeyword, Token semicolon) {
debugEvent("ContinueStatement");
SimpleIdentifier label = hasTarget ? pop() : null;
push(ast.continueStatement(continueKeyword, label, semicolon));
}
void handleIsOperator(Token operator, Token not, Token endToken) {
debugEvent("IsOperator");
TypeAnnotation type = pop();
Expression expression = pop();
push(ast.isExpression(expression, operator, not, type));
}
void handleConditionalExpression(Token question, Token colon) {
debugEvent("ConditionalExpression");
Expression elseExpression = pop();
Expression thenExpression = pop();
Expression condition = pop();
push(ast.conditionalExpression(
condition, question, thenExpression, colon, elseExpression));
}
@override
void endRedirectingFactoryBody(Token equalToken, Token endToken) {
debugEvent("RedirectingFactoryBody");
ConstructorName constructorName = pop();
Token starToken = pop();
Token asyncToken = pop();
push(new _RedirectingFactoryBody(
asyncToken, starToken, equalToken, constructorName));
}
@override
void endRethrowStatement(Token rethrowToken, Token endToken) {
debugEvent("RethrowStatement");
RethrowExpression expression = ast.rethrowExpression(rethrowToken);
// TODO(scheglov) According to the specification, 'rethrow' is a statement.
push(ast.expressionStatement(expression, endToken));
}
void handleThrowExpression(Token throwToken, Token endToken) {
debugEvent("ThrowExpression");
push(ast.throwExpression(throwToken, pop()));
}
@override
void endOptionalFormalParameters(
int count, Token beginToken, Token endToken) {
debugEvent("OptionalFormalParameters");
push(new _OptionalFormalParameters(popList(count), beginToken, endToken));
}
void handleValuedFormalParameter(Token equals, Token token) {
debugEvent("ValuedFormalParameter");
Expression value = pop();
push(new _ParameterDefaultValue(equals, value));
}
@override
void endFunctionType(Token functionToken, Token semicolon) {
debugEvent("FunctionType");
FormalParameterList parameters = pop();
TypeAnnotation returnType = pop();
TypeParameterList typeParameters = pop();
push(ast.genericFunctionType(
returnType, functionToken, typeParameters, parameters));
}
void handleFormalParameterWithoutValue(Token token) {
debugEvent("FormalParameterWithoutValue");
push(NullValue.ParameterDefaultValue);
}
@override
void endForInExpression(Token token) {
debugEvent("ForInExpression");
}
@override
void endForIn(Token awaitToken, Token forToken, Token leftParenthesis,
Token inKeyword, Token rightParenthesis, Token endToken) {
debugEvent("ForInExpression");
Statement body = pop();
Expression iterator = pop();
Object variableOrDeclaration = pop();
exitLocalScope();
exitContinueTarget();
exitBreakTarget();
if (variableOrDeclaration is SimpleIdentifier) {
push(ast.forEachStatementWithReference(
awaitToken,
forToken,
leftParenthesis,
variableOrDeclaration,
inKeyword,
iterator,
rightParenthesis,
body));
} else {
var statement = variableOrDeclaration as VariableDeclarationStatement;
VariableDeclarationList variableList = statement.variables;
push(ast.forEachStatementWithDeclaration(
awaitToken,
forToken,
leftParenthesis,
ast.declaredIdentifier(
variableList.documentationComment,
variableList.metadata,
variableList.keyword,
variableList.type,
variableList.variables.single.name),
inKeyword,
iterator,
rightParenthesis,
body));
}
}
void endFormalParameter(Token thisKeyword, Token nameToken,
FormalParameterKind kind, MemberKind memberKind) {
debugEvent("FormalParameter");
_ParameterDefaultValue defaultValue = pop();
SimpleIdentifier name = pop();
AstNode typeOrFunctionTypedParameter = pop();
_Modifiers modifiers = pop();
Token keyword = modifiers?.finalConstOrVarKeyword;
Token covariantKeyword = modifiers?.covariantKeyword;
pop(); // TODO(paulberry): Metadata.
Comment comment = pop();
FormalParameter node;
if (typeOrFunctionTypedParameter is FunctionTypedFormalParameter) {
// This is a temporary AST node that was constructed in
// [endFunctionTypedFormalParameter]. We now deconstruct it and create
// the final AST node.
if (thisKeyword == null) {
node = ast.functionTypedFormalParameter2(
identifier: name,
comment: comment,
covariantKeyword: covariantKeyword,
returnType: typeOrFunctionTypedParameter.returnType,
typeParameters: typeOrFunctionTypedParameter.typeParameters,
parameters: typeOrFunctionTypedParameter.parameters);
} else {
node = ast.fieldFormalParameter2(
identifier: name,
comment: comment,
covariantKeyword: covariantKeyword,
type: typeOrFunctionTypedParameter.returnType,
thisKeyword: thisKeyword,
period: unsafeToken(thisKeyword.next, TokenType.PERIOD),
typeParameters: typeOrFunctionTypedParameter.typeParameters,
parameters: typeOrFunctionTypedParameter.parameters);
}
} else {
TypeAnnotation type = typeOrFunctionTypedParameter;
if (thisKeyword == null) {
node = ast.simpleFormalParameter2(
comment: comment,
covariantKeyword: covariantKeyword,
keyword: keyword,
type: type,
identifier: name);
} else {
node = ast.fieldFormalParameter2(
comment: comment,
covariantKeyword: covariantKeyword,
keyword: keyword,
type: type,
thisKeyword: thisKeyword,
period: thisKeyword.next,
identifier: name);
}
}
ParameterKind analyzerKind = _toAnalyzerParameterKind(kind);
if (analyzerKind != ParameterKind.REQUIRED) {
node = ast.defaultFormalParameter(
node, analyzerKind, defaultValue?.separator, defaultValue?.value);
}
push(node);
}
@override
void endFunctionTypedFormalParameter() {
debugEvent("FunctionTypedFormalParameter");
FormalParameterList formalParameters = pop();
TypeAnnotation returnType = pop();
TypeParameterList typeParameters = pop();
// Create a temporary formal parameter that will be dissected later in
// [endFormalParameter].
push(ast.functionTypedFormalParameter2(
identifier: null,
returnType: returnType,
typeParameters: typeParameters,
parameters: formalParameters));
}
void endFormalParameters(
int count, Token beginToken, Token endToken, MemberKind kind) {
debugEvent("FormalParameters");
List rawParameters = popList(count) ?? const <Object>[];
List<FormalParameter> parameters = <FormalParameter>[];
Token leftDelimiter;
Token rightDelimiter;
for (Object raw in rawParameters) {
if (raw is _OptionalFormalParameters) {
parameters.addAll(raw.parameters);
leftDelimiter = raw.leftDelimiter;
rightDelimiter = raw.rightDelimiter;
} else {
parameters.add(raw as FormalParameter);
}
}
push(ast.formalParameterList(
beginToken, parameters, leftDelimiter, rightDelimiter, endToken));
}
@override
void endSwitchBlock(int caseCount, Token leftBracket, Token rightBracket) {
debugEvent("SwitchBlock");
List<List<SwitchMember>> membersList = popList(caseCount);
exitBreakTarget();
exitLocalScope();
List<SwitchMember> members =
membersList?.expand((members) => members)?.toList() ?? <SwitchMember>[];
push(leftBracket);
push(members);
push(rightBracket);
}
@override
void endSwitchCase(int labelCount, int expressionCount, Token defaultKeyword,
int statementCount, Token firstToken, Token endToken) {
debugEvent("SwitchCase");
List<Statement> statements = popList(statementCount);
List<SwitchMember> members = popList(expressionCount) ?? [];
List<Label> labels = popList(labelCount);
if (defaultKeyword != null) {
members.add(ast.switchDefault(
<Label>[], defaultKeyword, defaultKeyword.next, <Statement>[]));
}
members.last.statements.addAll(statements);
members.first.labels.addAll(labels);
push(members);
}
@override
void handleCaseMatch(Token caseKeyword, Token colon) {
debugEvent("CaseMatch");
Expression expression = pop();
push(ast.switchCase(
<Label>[], caseKeyword, expression, colon, <Statement>[]));
}
@override
void endSwitchStatement(Token switchKeyword, Token endToken) {
debugEvent("SwitchStatement");
Token rightBracket = pop();
List<SwitchMember> members = pop();
Token leftBracket = pop();
ParenthesizedExpression expression = pop();
push(ast.switchStatement(
switchKeyword,
expression.leftParenthesis,
expression.expression,
expression.rightParenthesis,
leftBracket,
members,
rightBracket));
}
void handleCatchBlock(Token onKeyword, Token catchKeyword, Token comma) {
debugEvent("CatchBlock");
Block body = pop();
FormalParameterList catchParameterList = popIfNotNull(catchKeyword);
TypeAnnotation type = popIfNotNull(onKeyword);
SimpleIdentifier exception;
SimpleIdentifier stackTrace;
if (catchParameterList != null) {
List<FormalParameter> catchParameters = catchParameterList.parameters;
if (catchParameters.length > 0) {
exception = catchParameters[0].identifier;
}
if (catchParameters.length > 1) {
stackTrace = catchParameters[1].identifier;
}
}
// TODO(brianwilkerson) The parser needs to pass in the comma token.
push(ast.catchClause(
onKeyword,
type,
catchKeyword,
catchParameterList?.leftParenthesis,
exception,
comma,
stackTrace,
catchParameterList?.rightParenthesis,
body));
}
@override
void handleFinallyBlock(Token finallyKeyword) {
debugEvent("FinallyBlock");
// The finally block is popped in "endTryStatement".
}
void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) {
Block finallyBlock = popIfNotNull(finallyKeyword);
List<CatchClause> catchClauses = popList(catchCount);
Block body = pop();
push(ast.tryStatement(
tryKeyword, body, catchClauses, finallyKeyword, finallyBlock));
}
@override
void handleLabel(Token colon) {
debugEvent("Label");
SimpleIdentifier name = pop();
push(ast.label(name, colon));
}
void handleNoExpression(Token token) {
debugEvent("NoExpression");
push(NullValue.Expression);
}
void handleIndexedExpression(
Token openSquareBracket, Token closeSquareBracket) {
debugEvent("IndexedExpression");
Expression index = pop();
Expression target = pop();
if (target == null) {
CascadeExpression receiver = pop();
Token token = peek();
push(receiver);
IndexExpression expression = ast.indexExpressionForCascade(
token, openSquareBracket, index, closeSquareBracket);
assert(expression.isCascaded);
push(expression);
} else {
push(ast.indexExpressionForTarget(
target, openSquareBracket, index, closeSquareBracket));
}
}
@override
void handleInvalidExpression(Token token) {
debugEvent("InvalidExpression");
}
@override
void handleInvalidFunctionBody(Token token) {
debugEvent("InvalidFunctionBody");
}
@override
Token handleUnrecoverableError(Token token, Message message) {
if (message.code == codeExpectedFunctionBody) {
if (identical('native', token.stringValue) && parser != null) {
Token nativeKeyword = token;
Token semicolon = parser.parseLiteralString(token.next);
// TODO(brianwilkerson) Should this be using ensureSemicolon?
token = parser.expectSemicolon(semicolon);
StringLiteral name = pop();
pop(); // star
pop(); // async
push(ast.nativeFunctionBody(nativeKeyword, name, semicolon));
return token;
}
} else if (message.code == codeExpectedExpression) {
String lexeme = token.lexeme;
if (identical('async', lexeme) || identical('yield', lexeme)) {
errorReporter?.reportErrorForOffset(
ParserErrorCode.ASYNC_KEYWORD_USED_AS_IDENTIFIER,
token.charOffset,
token.charCount);
push(ast.simpleIdentifier(token));
return token;
}
}
return super.handleUnrecoverableError(token, message);
}
void handleUnaryPrefixExpression(Token token) {
debugEvent("UnaryPrefixExpression");
push(ast.prefixExpression(token, pop()));
}
void handleUnaryPrefixAssignmentExpression(Token token) {
debugEvent("UnaryPrefixAssignmentExpression");
push(ast.prefixExpression(token, pop()));
}
void handleUnaryPostfixAssignmentExpression(Token token) {
debugEvent("UnaryPostfixAssignmentExpression");
push(ast.postfixExpression(pop(), token));
}
void handleModifier(Token token) {
debugEvent("Modifier");
push(token);
}
void handleModifiers(int count) {
debugEvent("Modifiers");
if (count == 0) {
push(NullValue.Modifiers);
} else {
push(new _Modifiers(popList(count)));
}
}
void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
// TODO(paulberry): set up scopes properly to resolve parameters and type
// variables.
debugEvent("TopLevelMethod");
FunctionBody body = pop();
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
Token propertyKeyword = getOrSet;
TypeAnnotation returnType = pop();
_Modifiers modifiers = pop();
Token externalKeyword = modifiers?.externalKeyword;
List<Annotation> metadata = pop();
Comment comment = pop();
declarations.add(ast.functionDeclaration(
comment,
metadata,
externalKeyword,
returnType,
propertyKeyword,
name,
ast.functionExpression(typeParameters, parameters, body)));
}
@override
void endTopLevelDeclaration(Token token) {
debugEvent("TopLevelDeclaration");
}
@override
void handleInvalidTopLevelDeclaration(Token endToken) {
debugEvent("InvalidTopLevelDeclaration");
pop(); // metadata star
// TODO(danrubel): consider creating a AST node
// representing the invalid declaration to better support code completion,
// quick fixes, etc, rather than discarding the metadata and token
}
@override
void beginCompilationUnit(Token token) {
push(token);
}
@override
void endCompilationUnit(int count, Token endToken) {
debugEvent("CompilationUnit");
Token beginToken = pop();
checkEmpty(endToken.charOffset);
push(ast.compilationUnit(
beginToken, scriptTag, directives, declarations, endToken));
}
void endImport(Token importKeyword, Token deferredKeyword, Token asKeyword,
Token semicolon) {
debugEvent("Import");
List<Combinator> combinators = pop();
SimpleIdentifier prefix = popIfNotNull(asKeyword);
List<Configuration> configurations = pop();
StringLiteral uri = pop();
List<Annotation> metadata = pop();
assert(metadata == null); // TODO(paulberry): fix.
Comment comment = pop();
directives.add(ast.importDirective(
comment,
metadata,
importKeyword,
uri,
configurations,
deferredKeyword,
asKeyword,
prefix,
combinators,
semicolon));
}
void endExport(Token exportKeyword, Token semicolon) {
debugEvent("Export");
List<Combinator> combinators = pop();
List<Configuration> configurations = pop();
StringLiteral uri = pop();
List<Annotation> metadata = pop();
assert(metadata == null);
Comment comment = pop();
directives.add(ast.exportDirective(comment, metadata, exportKeyword, uri,
configurations, combinators, semicolon));
}
@override
void endDottedName(int count, Token firstIdentifier) {
debugEvent("DottedName");
List<SimpleIdentifier> components = popList(count);
push(ast.dottedName(components));
}
@override
void endDoWhileStatement(
Token doKeyword, Token whileKeyword, Token semicolon) {
debugEvent("DoWhileStatement");
ParenthesizedExpression condition = pop();
Statement body = pop();
exitContinueTarget();
exitBreakTarget();
push(ast.doStatement(
doKeyword,
body,
whileKeyword,
condition.leftParenthesis,
condition.expression,
condition.rightParenthesis,
semicolon));
}
void endConditionalUri(
Token ifKeyword, Token leftParen, Token equalSign, Token rightParen) {
debugEvent("ConditionalUri");
StringLiteral libraryUri = pop();
StringLiteral value = popIfNotNull(equalSign);
DottedName name = pop();
push(ast.configuration(
ifKeyword, leftParen, name, equalSign, value, rightParen, libraryUri));
}
@override
void endConditionalUris(int count) {
debugEvent("ConditionalUris");
push(popList(count) ?? NullValue.ConditionalUris);
}
@override
void endIdentifierList(int count) {
debugEvent("IdentifierList");
push(popList(count) ?? NullValue.IdentifierList);
}
@override
void endShow(Token showKeyword) {
debugEvent("Show");
List<SimpleIdentifier> shownNames = pop();
push(ast.showCombinator(showKeyword, shownNames));
}
@override
void endHide(Token hideKeyword) {
debugEvent("Hide");
List<SimpleIdentifier> hiddenNames = pop();
push(ast.hideCombinator(hideKeyword, hiddenNames));
}
@override
void endTypeList(int count) {
debugEvent("TypeList");
push(popList(count) ?? NullValue.TypeList);
}
@override
void endClassBody(int memberCount, Token beginToken, Token endToken) {
debugEvent("ClassBody");
push(new _ClassBody(
beginToken, popList(memberCount) ?? <ClassMember>[], endToken));
}
@override
void beginClassDeclaration(Token beginToken, Token name) {
assert(className == null);
className = name.lexeme;
}
@override
void endClassDeclaration(
int interfacesCount,
Token beginToken,
Token classKeyword,
Token extendsKeyword,
Token implementsKeyword,
Token nativeToken,
Token endToken) {
debugEvent("ClassDeclaration");
_ClassBody body = pop();
NativeClause nativeClause;
if (nativeToken != null) {
nativeClause = ast.nativeClause(nativeToken, nativeName);
}
ImplementsClause implementsClause;
if (implementsKeyword != null) {
List<TypeName> interfaces = popList(interfacesCount);
implementsClause = ast.implementsClause(implementsKeyword, interfaces);
}
ExtendsClause extendsClause;
WithClause withClause;
var supertype = pop();
if (supertype == null) {
// No extends clause
} else if (supertype is TypeName) {
extendsClause = ast.extendsClause(extendsKeyword, supertype);
} else if (supertype is _MixinApplication) {
extendsClause = ast.extendsClause(extendsKeyword, supertype.supertype);
withClause = ast.withClause(supertype.withKeyword, supertype.mixinTypes);
} else {
unhandled("${supertype.runtimeType}", "supertype",
extendsKeyword.charOffset, uri);
}
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
assert(className == name.name);
className = null;
_Modifiers modifiers = pop();
Token abstractKeyword = modifiers?.abstractKeyword;
List<Annotation> metadata = pop();
Comment comment = pop();
ClassDeclaration classDeclaration = ast.classDeclaration(
comment,
metadata,
abstractKeyword,
classKeyword,
name,
typeParameters,
extendsClause,
withClause,
implementsClause,
body.beginToken,
body.members,
body.endToken);
classDeclaration.nativeClause = nativeClause;
declarations.add(classDeclaration);
}
@override
void endMixinApplication(Token withKeyword) {
debugEvent("MixinApplication");
List<TypeName> mixinTypes = pop();
TypeName supertype = pop();
push(new _MixinApplication(supertype, withKeyword, mixinTypes));
}
@override
void endNamedMixinApplication(Token beginToken, Token classKeyword,
Token equalsToken, Token implementsKeyword, Token endToken) {
debugEvent("NamedMixinApplication");
ImplementsClause implementsClause;
if (implementsKeyword != null) {
List<TypeName> interfaces = pop();
implementsClause = ast.implementsClause(implementsKeyword, interfaces);
}
_MixinApplication mixinApplication = pop();
var superclass = mixinApplication.supertype;
var withClause = ast.withClause(
mixinApplication.withKeyword, mixinApplication.mixinTypes);
Token equals = equalsToken;
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
_Modifiers modifiers = pop();
Token abstractKeyword = modifiers?.abstractKeyword;
List<Annotation> metadata = pop();
Comment comment = pop();
declarations.add(ast.classTypeAlias(
comment,
metadata,
classKeyword,
name,
typeParameters,
equals,
abstractKeyword,
superclass,
withClause,
implementsClause,
endToken));
}
@override
void endLabeledStatement(int labelCount) {
debugEvent("LabeledStatement");
Statement statement = pop();
List<Label> labels = popList(labelCount);
push(ast.labeledStatement(labels, statement));
}
@override
void endLibraryName(Token libraryKeyword, Token semicolon) {
debugEvent("LibraryName");
List<SimpleIdentifier> libraryName = pop();
var name = ast.libraryIdentifier(libraryName);
List<Annotation> metadata = pop();
Comment comment = pop();
directives.add(ast.libraryDirective(
comment, metadata, libraryKeyword, name, semicolon));
}
@override
void handleRecoverableError(Token token, Message message) {
/// TODO(danrubel): Ignore this error until we deprecate `native` support.
if (message == messageNativeClauseShouldBeAnnotation && allowNativeClause) {
return;
}
debugEvent("Error: ${message.message}");
addCompileTimeError(message, token.offset);
}
@override
void handleQualified(Token period) {
SimpleIdentifier identifier = pop();
var prefix = pop();
if (prefix is List) {
// We're just accumulating components into a list.
prefix.add(identifier);
push(prefix);
} else if (prefix is SimpleIdentifier) {
// TODO(paulberry): resolve [identifier]. Note that BodyBuilder handles
// this situation using SendAccessor.
push(ast.prefixedIdentifier(prefix, period, identifier));
} else {
// TODO(paulberry): implement.
logEvent('Qualified with >1 dot');
}
}
@override
void endPart(Token partKeyword, Token semicolon) {
debugEvent("Part");
StringLiteral uri = pop();
List<Annotation> metadata = pop();
Comment comment = pop();
directives
.add(ast.partDirective(comment, metadata, partKeyword, uri, semicolon));
}
@override
void endPartOf(
Token partKeyword, Token ofKeyword, Token semicolon, bool hasName) {
debugEvent("PartOf");
var libraryNameOrUri = pop();
LibraryIdentifier name;
StringLiteral uri;
if (libraryNameOrUri is StringLiteral) {
uri = libraryNameOrUri;
} else {
name = ast.libraryIdentifier(libraryNameOrUri);
}
List<Annotation> metadata = pop();
Comment comment = pop();
directives.add(ast.partOfDirective(
comment, metadata, partKeyword, ofKeyword, uri, name, semicolon));
}
@override
void endFunctionExpression(Token beginToken, Token token) {
// TODO(paulberry): set up scopes properly to resolve parameters and type
// variables. Note that this is tricky due to the handling of initializers
// in constructors, so the logic should be shared with BodyBuilder as much
// as possible.
debugEvent("FunctionExpression");
FunctionBody body = pop();
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
push(ast.functionExpression(typeParameters, parameters, body));
}
@override
void handleNoFieldInitializer(Token token) {
debugEvent("NoFieldInitializer");
SimpleIdentifier name = pop();
push(ast.variableDeclaration(name, null, null));
}
@override
void endFactoryMethod(
Token beginToken, Token factoryKeyword, Token semicolon) {
debugEvent("FactoryMethod");
FunctionBody body;
Token separator;
ConstructorName redirectedConstructor;
Object bodyObject = pop();
if (bodyObject is FunctionBody) {
body = bodyObject;
} else if (bodyObject is _RedirectingFactoryBody) {
separator = bodyObject.equalToken;
redirectedConstructor = bodyObject.constructorName;
body = ast.emptyFunctionBody(semicolon);
} else {
unhandled("${bodyObject.runtimeType}", "bodyObject",
beginToken.charOffset, uri);
}
FormalParameterList parameters = pop();
ConstructorName constructorName = pop();
_Modifiers modifiers = pop();
List<Annotation> metadata = pop();
Comment comment = pop();
// Decompose the preliminary ConstructorName into the type name and
// the actual constructor name.
SimpleIdentifier returnType;
Token period;
SimpleIdentifier name;
Identifier typeName = constructorName.type.name;
if (typeName is SimpleIdentifier) {
returnType = typeName;
} else if (typeName is PrefixedIdentifier) {
returnType = typeName.prefix;
period = typeName.period;
name =
ast.simpleIdentifier(typeName.identifier.token, isDeclaration: true);
}
push(ast.constructorDeclaration(
comment,
metadata,
modifiers?.externalKeyword,
modifiers?.finalConstOrVarKeyword,
factoryKeyword,
ast.simpleIdentifier(returnType.token),
period,
name,
parameters,
separator,
null,
redirectedConstructor,
body));
}
void endFieldInitializer(Token assignment, Token token) {
debugEvent("FieldInitializer");
Expression initializer = pop();
SimpleIdentifier name = pop();
push(ast.variableDeclaration(name, assignment, initializer));
}
@override
void endNamedFunctionExpression(Token endToken) {
// TODO(scheglov): The logEvent() invocation is commented because it
// spams to the console. We already know that these test fail, uncomment
// when you are working on fixing them.
// logEvent("NamedFunctionExpression");
unhandled("NamedFunctionExpression", "$runtimeType", -1, uri);
}
@override
void endLocalFunctionDeclaration(Token token) {
debugEvent("LocalFunctionDeclaration");
FunctionBody body = pop();
if (isFullAst) {
pop(); // constructor initializers
pop(); // separator before constructor initializers
}
FormalParameterList parameters = pop();
SimpleIdentifier name = pop();
TypeAnnotation returnType = pop();
pop(); // modifiers
TypeParameterList typeParameters = pop();
FunctionExpression functionExpression =
ast.functionExpression(typeParameters, parameters, body);
push(ast.functionDeclarationStatement(ast.functionDeclaration(
null, null, null, returnType, null, name, functionExpression)));
}
@override
void endFunctionName(Token beginToken, Token token) {
debugEvent("FunctionName");
}
void endTopLevelFields(int count, Token beginToken, Token endToken) {
debugEvent("TopLevelFields");
List<VariableDeclaration> variables = popList(count);
TypeAnnotation type = pop();
_Modifiers modifiers = pop();
Token keyword = modifiers?.finalConstOrVarKeyword;
var variableList =
ast.variableDeclarationList(null, null, keyword, type, variables);
List<Annotation> metadata = pop();
Comment comment = pop();
declarations.add(ast.topLevelVariableDeclaration(
comment, metadata, variableList, endToken));
}
@override
void endTypeVariable(Token token, Token extendsOrSuper) {
// TODO(paulberry): set up scopes properly to resolve parameters and type
// variables. Note that this is tricky due to the handling of initializers
// in constructors, so the logic should be shared with BodyBuilder as much
// as possible.
debugEvent("TypeVariable");
TypeAnnotation bound = pop();
SimpleIdentifier name = pop();
List<Annotation> metadata = pop();
Comment comment = pop();
push(ast.typeParameter(comment, metadata, name, extendsOrSuper, bound));
}
@override
void endTypeVariables(int count, Token beginToken, Token endToken) {
debugEvent("TypeVariables");
List<TypeParameter> typeParameters = popList(count);
push(ast.typeParameterList(beginToken, typeParameters, endToken));
}
@override
void endMethod(Token getOrSet, Token beginToken, Token endToken) {
debugEvent("Method");
FunctionBody body = pop();
ConstructorName redirectedConstructor = null; // TODO(paulberry)
List<ConstructorInitializer> initializers = pop() ?? const [];
Token separator = pop();
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
var name = pop();
TypeAnnotation returnType = pop();
_Modifiers modifiers = pop();
List<Annotation> metadata = pop();
Comment comment = pop();
void constructor(
SimpleIdentifier returnType, Token period, SimpleIdentifier name) {
push(ast.constructorDeclaration(
comment,
metadata,
modifiers?.externalKeyword,
modifiers?.finalConstOrVarKeyword,
null, // TODO(paulberry): factoryKeyword
ast.simpleIdentifier(returnType.token),
period,
name,
parameters,
separator,
initializers,
redirectedConstructor,
body));
}
void method(Token operatorKeyword, SimpleIdentifier name) {
push(ast.methodDeclaration(
comment,
metadata,
modifiers?.externalKeyword,
modifiers?.abstractKeyword ?? modifiers?.staticKeyword,
returnType,
getOrSet,
operatorKeyword,
name,
typeParameters,
parameters,
body));
}
if (name is SimpleIdentifier) {
if (name.name == className) {
constructor(name, null, null);
} else {
method(null, name);
}
} else if (name is _OperatorName) {
method(name.operatorKeyword, name.name);
} else if (name is PrefixedIdentifier) {
constructor(name.prefix, name.period, name.identifier);
} else {
throw new UnimplementedError();
}
}
@override
void endMember() {
debugEvent("Member");
}
@override
void handleVoidKeyword(Token token) {
debugEvent("VoidKeyword");
// TODO(paulberry): is this sufficient, or do we need to hook the "void"
// keyword up to an element?
handleIdentifier(token, IdentifierContext.typeReference);
handleNoTypeArguments(token);
handleType(token, token);
}
@override
void endFunctionTypeAlias(
Token typedefKeyword, Token equals, Token endToken) {
debugEvent("FunctionTypeAlias");
if (equals == null) {
FormalParameterList parameters = pop();
TypeParameterList typeParameters = pop();
SimpleIdentifier name = pop();
TypeAnnotation returnType = pop();
List<Annotation> metadata = pop();
Comment comment = pop();
declarations.add(ast.functionTypeAlias(comment, metadata, typedefKeyword,
returnType, name, typeParameters, parameters, endToken));
} else {
TypeAnnotation type = pop();
TypeParameterList templateParameters = pop();
SimpleIdentifier name = pop();
List<Annotation> metadata = pop();
Comment comment = pop();
if (type is! GenericFunctionType) {
// TODO(paulberry) Generate an error and recover (better than
// this).
type = null;
}
declarations.add(ast.genericTypeAlias(comment, metadata, typedefKeyword,
name, templateParameters, equals, type, endToken));
}
}
@override
void endEnum(Token enumKeyword, Token endBrace, int count) {
debugEvent("Enum");
List<EnumConstantDeclaration> constants = popList(count);
// TODO(paulberry,ahe): the parser should pass in the openBrace token.
var openBrace =
unsafeToken(enumKeyword.next.next, TokenType.OPEN_CURLY_BRACKET)
as analyzer.BeginToken;
// TODO(paulberry): what if the '}' is missing and the parser has performed
// error recovery?
Token closeBrace = openBrace?.endGroup;
SimpleIdentifier name = pop();
List<Annotation> metadata = pop();
Comment comment = pop();
declarations.add(ast.enumDeclaration(comment, metadata, enumKeyword, name,
openBrace, constants, closeBrace));
}
@override
void endTypeArguments(int count, Token beginToken, Token endToken) {
debugEvent("TypeArguments");
List<TypeAnnotation> arguments = popList(count);
push(ast.typeArgumentList(beginToken, arguments, endToken));
}
@override
void endFields(int count, Token beginToken, Token endToken) {
debugEvent("Fields");
List<VariableDeclaration> variables = popList(count);
TypeAnnotation type = pop();
_Modifiers modifiers = pop();
var variableList = ast.variableDeclarationList(
null, null, modifiers?.finalConstOrVarKeyword, type, variables);
Token covariantKeyword = modifiers?.covariantKeyword;
List<Annotation> metadata = pop();
Comment comment = pop();
push(ast.fieldDeclaration2(
comment: comment,
metadata: metadata,
covariantKeyword: covariantKeyword,
staticKeyword: modifiers?.staticKeyword,
fieldList: variableList,
semicolon: endToken));
}
@override
AstNode finishFields() {
debugEvent("finishFields");
return declarations.removeLast();
}
@override
void handleOperatorName(Token operatorKeyword, Token token) {
debugEvent("OperatorName");
push(new _OperatorName(
operatorKeyword, ast.simpleIdentifier(token, isDeclaration: true)));
}
@override
void beginMetadataStar(Token token) {
debugEvent("beginMetadataStar");
if (token.precedingComments != null) {
push(_toAnalyzerComment(token.precedingComments));
} else {
push(NullValue.Comments);
}
}
@override
void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
debugEvent("Metadata");
MethodInvocation invocation = pop();
SimpleIdentifier constructorName = periodBeforeName != null ? pop() : null;
pop(); // Type arguments, not allowed.
Identifier name = pop();
push(ast.annotation(beginToken, name, periodBeforeName, constructorName,
invocation?.argumentList));
}
@override
void endMetadataStar(int count) {
debugEvent("MetadataStar");
push(popList(count) ?? NullValue.Metadata);
}
ParameterKind _toAnalyzerParameterKind(FormalParameterKind type) {
if (type == FormalParameterKind.optionalPositional) {
return ParameterKind.POSITIONAL;
} else if (type == FormalParameterKind.optionalNamed) {
return ParameterKind.NAMED;
} else {
return ParameterKind.REQUIRED;
}
}
Comment _toAnalyzerComment(Token comments) {
if (comments == null) return null;
// This is temporary placeholder code to get tests to pass.
// TODO(paulberry): after analyzer and fasta token representations are
// unified, refactor the code in analyzer's parser that handles
// documentation comments so that it is reusable, and reuse it here.
// See Parser.parseCommentAndMetadata
var tokens = <Token>[comments];
var references = <CommentReference>[];
return ast.documentationComment(tokens, references);
}
@override
void debugEvent(String name) {
// printEvent('AstBuilder: $name');
}
@override
Token injectGenericCommentTypeAssign(Token token) {
// TODO(paulberry,scheglov,ahe): figure out how to share these generic
// comment methods with BodyBuilder.
return _injectGenericComment(
token, TokenType.GENERIC_METHOD_TYPE_ASSIGN, 3);
}
@override
Token injectGenericCommentTypeList(Token token) {
return _injectGenericComment(token, TokenType.GENERIC_METHOD_TYPE_LIST, 2);
}
@override
Token replaceTokenWithGenericCommentTypeAssign(
Token tokenToStartReplacing, Token tokenWithComment) {
Token injected = injectGenericCommentTypeAssign(tokenWithComment);
if (!identical(injected, tokenWithComment)) {
Token prev = tokenToStartReplacing.previous;
prev.setNextWithoutSettingPrevious(injected);
tokenToStartReplacing = injected;
tokenToStartReplacing.previous = prev;
}
return tokenToStartReplacing;
}
@override
void discardTypeReplacedWithCommentTypeAssign() {
pop();
}
/// Check if the given [token] has a comment token with the given [type],
/// which should be either [TokenType.GENERIC_METHOD_TYPE_ASSIGN] or
/// [TokenType.GENERIC_METHOD_TYPE_LIST]. If found, parse the comment
/// into tokens and inject into the token stream before the [token].
Token _injectGenericComment(Token token, TokenType type, int prefixLen) {
if (parseGenericMethodComments) {
CommentToken t = token.precedingComments;
for (; t != null; t = t.next) {
if (t.type == type) {
String code = t.lexeme.substring(prefixLen, t.lexeme.length - 2);
Token tokens = _scanGenericMethodComment(code, t.offset + prefixLen);
if (tokens != null) {
// Remove the token from the comment stream.
t.remove();
// Insert the tokens into the stream.
_injectTokenList(token, tokens);
return tokens;
}
}
}
}
return token;
}
void _injectTokenList(Token beforeToken, Token firstToken) {
// Scanner creates a cyclic EOF token.
Token lastToken = firstToken;
while (lastToken.next.type != TokenType.EOF) {
lastToken = lastToken.next;
}
// Inject these new tokens into the stream.
Token previous = beforeToken.previous;
lastToken.setNext(beforeToken);
previous.setNext(firstToken);
beforeToken = firstToken;
}
/// Scans the given [code], and returns the tokens, otherwise returns `null`.
Token _scanGenericMethodComment(String code, int offset) {
var scanner = new SubStringScanner(offset, code);
Token firstToken = scanner.tokenize();
if (scanner.hasErrors) {
return null;
}
return firstToken;
}
@override
void addCompileTimeError(Message message, int charOffset) {
Code code = message.code;
Map<String, dynamic> arguments = message.arguments;
String stringOrTokenLexeme() {
var text = arguments['string'];
if (text == null) {
Token token = arguments['token'];
if (token != null) {
text = token.lexeme;
}
}
return text;
}
switch (code.analyzerCode) {
case "ABSTRACT_CLASS_MEMBER":
errorReporter?.reportErrorForOffset(
ParserErrorCode.ABSTRACT_CLASS_MEMBER, charOffset, 1);
return;
case "CONST_CLASS":
errorReporter?.reportErrorForOffset(
ParserErrorCode.CONST_CLASS, charOffset, 1);
return;
case "EXPECTED_STRING_LITERAL":
errorReporter?.reportErrorForOffset(
ParserErrorCode.EXPECTED_STRING_LITERAL, charOffset, 1);
return;
case "EXPECTED_TYPE_NAME":
errorReporter?.reportErrorForOffset(
ParserErrorCode.EXPECTED_TYPE_NAME, charOffset, 1);
return;
case "EXTERNAL_METHOD_WITH_BODY":
errorReporter?.reportErrorForOffset(
ParserErrorCode.EXTERNAL_METHOD_WITH_BODY, charOffset, 1);
return;
case "EXTRANEOUS_MODIFIER":
String text = stringOrTokenLexeme();
errorReporter?.reportErrorForOffset(ParserErrorCode.EXTRANEOUS_MODIFIER,
charOffset, text.length, [text]);
return;
case "NATIVE_CLAUSE_SHOULD_BE_ANNOTATION":
errorReporter?.reportErrorForOffset(
ParserErrorCode.NATIVE_CLAUSE_SHOULD_BE_ANNOTATION, charOffset, 1);
return;
case "UNEXPECTED_TOKEN":
String text = stringOrTokenLexeme();
if (text == ';') {
errorReporter?.reportErrorForOffset(
ParserErrorCode.EXPECTED_TOKEN, charOffset, text.length, [text]);
} else {
errorReporter?.reportErrorForOffset(ParserErrorCode.UNEXPECTED_TOKEN,
charOffset, text.length, [text]);
}
return;
default:
// fall through
}
}
/// A marker method used to mark locations where a token is being located in
/// an unsafe way. In all such cases the parser needs to be fixed to pass in
/// the token.
Token unsafeToken(Token token, TokenType tokenType) {
// TODO(brianwilkerson) Eliminate the need for this method.
return token.type == tokenType ? token : null;
}
}
/// Data structure placed on the stack to represent a class body.
///
/// This is needed because analyzer has no separate AST representation of a
/// class body; it simply stores all of the relevant data in the
/// [ClassDeclaration] object.
class _ClassBody {
final Token beginToken;
final List<ClassMember> members;
final Token endToken;
_ClassBody(this.beginToken, this.members, this.endToken);
}
/// Data structure placed on the stack to represent a mixin application (a
/// structure of the form "A with B, C").
///
/// This is needed because analyzer has no separate AST representation of a
/// mixin application; it simply stores all of the relevant data in the
/// [ClassDeclaration] or [ClassTypeAlias] object.
class _MixinApplication {
final TypeName supertype;
final Token withKeyword;
final List<TypeName> mixinTypes;
_MixinApplication(this.supertype, this.withKeyword, this.mixinTypes);
}
/// Data structure placed on the stack to represent the default parameter
/// value with the separator token.
class _ParameterDefaultValue {
final Token separator;
final Expression value;
_ParameterDefaultValue(this.separator, this.value);
}
/// Data structure placed on stack to represent the redirected constructor.
class _RedirectingFactoryBody {
final Token asyncKeyword;
final Token starKeyword;
final Token equalToken;
final ConstructorName constructorName;
_RedirectingFactoryBody(this.asyncKeyword, this.starKeyword, this.equalToken,
this.constructorName);
}
/// Data structure placed on the stack as a container for optional parameters.
class _OptionalFormalParameters {
final List<FormalParameter> parameters;
final Token leftDelimiter;
final Token rightDelimiter;
_OptionalFormalParameters(
this.parameters, this.leftDelimiter, this.rightDelimiter);
}
/// Data structure placed on the stack to represent the keyword "operator"
/// followed by a token.
class _OperatorName {
final Token operatorKeyword;
final SimpleIdentifier name;
_OperatorName(this.operatorKeyword, this.name);
}
/// Data structure placed on the stack to represent a non-empty sequence
/// of modifiers.
class _Modifiers {
Token abstractKeyword;
Token externalKeyword;
Token finalConstOrVarKeyword;
Token staticKeyword;
Token covariantKeyword;
_Modifiers(List<Token> modifierTokens) {
// No need to check the order and uniqueness of the modifiers, or that
// disallowed modifiers are not used; the parser should do that.
// TODO(paulberry,ahe): implement the necessary logic in the parser.
for (var token in modifierTokens) {
var s = token.lexeme;
if (identical('abstract', s)) {
abstractKeyword = token;
} else if (identical('const', s)) {
finalConstOrVarKeyword = token;
} else if (identical('external', s)) {
externalKeyword = token;
} else if (identical('final', s)) {
finalConstOrVarKeyword = token;
} else if (identical('static', s)) {
staticKeyword = token;
} else if (identical('var', s)) {
finalConstOrVarKeyword = token;
} else if (identical('covariant', s)) {
covariantKeyword = token;
} else {
unhandled("$s", "modifier", token.charOffset, null);
}
}
}
}