| // Copyright (c) 2015, 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 dart2js.parser.element_listener; |
| |
| import 'package:front_end/src/fasta/fasta_codes.dart' show Message; |
| |
| import 'package:front_end/src/fasta/fasta_codes.dart' as codes; |
| |
| import '../common.dart'; |
| import '../diagnostics/messages.dart' show MessageTemplate; |
| import '../elements/elements.dart' |
| show Element, LibraryElement, MetadataAnnotation; |
| import '../elements/modelx.dart' |
| show |
| CompilationUnitElementX, |
| DeclarationSite, |
| ElementX, |
| EnumClassElementX, |
| FieldElementX, |
| LibraryElementX, |
| MetadataAnnotationX, |
| NamedMixinApplicationElementX, |
| VariableList; |
| import '../id_generator.dart'; |
| import '../native/native.dart' as native; |
| import '../string_validator.dart' show StringValidator; |
| import 'package:front_end/src/fasta/scanner.dart' |
| show ErrorToken, StringToken, Token; |
| import 'package:front_end/src/fasta/scanner.dart' as Tokens show EOF_TOKEN; |
| import '../tree/tree.dart'; |
| import '../util/util.dart' show Link, LinkBuilder; |
| import 'package:front_end/src/fasta/parser.dart' |
| show Listener, ParserError, optional; |
| import 'package:front_end/src/fasta/parser/identifier_context.dart' |
| show IdentifierContext; |
| import 'package:front_end/src/scanner/token.dart' show KeywordToken, TokenType; |
| import 'partial_elements.dart' |
| show |
| PartialClassElement, |
| PartialElement, |
| PartialFieldList, |
| PartialFunctionElement, |
| PartialMetadataAnnotation, |
| PartialTypedefElement; |
| |
| const bool VERBOSE = false; |
| |
| /// Options used for scanning. |
| /// |
| /// Use this to conditionally support special tokens. |
| /// |
| /// TODO(johnniwinther): This class should be renamed, it is not about options |
| /// in the same sense as `CompilerOptions` or `DiagnosticOptions`. |
| class ScannerOptions { |
| /// If `true` the pseudo keyword `native` is supported. |
| final bool canUseNative; |
| |
| const ScannerOptions({this.canUseNative: false}); |
| } |
| |
| /** |
| * A parser event listener designed to work with [PartialParser]. It |
| * builds elements representing the top-level declarations found in |
| * the parsed compilation unit and records them in |
| * [compilationUnitElement]. |
| */ |
| class ElementListener extends Listener { |
| final IdGenerator idGenerator; |
| final DiagnosticReporter reporter; |
| final ScannerOptions scannerOptions; |
| final CompilationUnitElementX compilationUnitElement; |
| final StringValidator stringValidator; |
| Link<StringQuoting> interpolationScope; |
| |
| Link<Node> nodes = const Link<Node>(); |
| |
| LinkBuilder<MetadataAnnotation> metadata = |
| new LinkBuilder<MetadataAnnotation>(); |
| |
| /// Indicates whether the parser is currently accepting a type variable. |
| bool inTypeVariable = false; |
| |
| /// Records a stack of booleans for each member parsed (a stack is used to |
| /// support nested members which isn't currently possible, but it also serves |
| /// as a simple way to tell we're currently parsing a member). In this case, |
| /// member refers to members of a library or a class (but currently, classes |
| /// themselves are not considered members). If the top of the stack |
| /// (memberErrors.head) is true, the current member has already reported at |
| /// least one parse error. |
| Link<bool> memberErrors = const Link<bool>(); |
| |
| bool suppressParseErrors = false; |
| |
| /// Set to true each time we parse a native function body. It is reset in |
| /// [handleInvalidFunctionBody] which is called immediately after. |
| bool lastErrorWasNativeFunctionBody = false; |
| |
| LiteralString nativeName; |
| |
| ElementListener(this.scannerOptions, DiagnosticReporter reporter, |
| this.compilationUnitElement, this.idGenerator) |
| : this.reporter = reporter, |
| stringValidator = new StringValidator(reporter), |
| interpolationScope = const Link<StringQuoting>(); |
| |
| @override |
| Uri get uri => compilationUnitElement?.script?.resourceUri; |
| |
| bool get currentMemberHasParseError { |
| return !memberErrors.isEmpty && memberErrors.head; |
| } |
| |
| void pushQuoting(StringQuoting quoting) { |
| interpolationScope = interpolationScope.prepend(quoting); |
| } |
| |
| StringQuoting popQuoting() { |
| StringQuoting result = interpolationScope.head; |
| interpolationScope = interpolationScope.tail; |
| return result; |
| } |
| |
| StringNode popLiteralString() { |
| StringNode node = popNode(); |
| // TODO(lrn): Handle interpolations in script tags. |
| if (node.isInterpolation) { |
| reporter.internalError( |
| node, "String interpolation not supported in library tags."); |
| return null; |
| } |
| return node; |
| } |
| |
| bool allowLibraryTags() { |
| // Library tags are only allowed in the library file itself, not |
| // in sourced files. |
| LibraryElement library = compilationUnitElement.implementationLibrary; |
| return !compilationUnitElement.hasMembers && |
| library.entryCompilationUnit == compilationUnitElement; |
| } |
| |
| @override |
| void endLibraryName(Token libraryKeyword, Token semicolon) { |
| Expression name = popNode(); |
| addLibraryTag(new LibraryName( |
| libraryKeyword, name, popMetadata(compilationUnitElement))); |
| } |
| |
| @override |
| void handleImportPrefix(Token deferredKeyword, Token asKeyword) { |
| if (asKeyword == null) { |
| // If asKeyword is null, then no prefix has been pushed on the stack. |
| // Push a placeholder indicating that there is no prefix. |
| pushNode(null); |
| } |
| pushNode(deferredKeyword != null ? Flag.TRUE : Flag.FALSE); |
| } |
| |
| @override |
| void endImport(Token importKeyword, Token semicolon) { |
| NodeList combinators = popNode(); |
| Flag flag = popNode(); |
| bool isDeferred = flag == Flag.TRUE; |
| Identifier prefix = popNode(); |
| NodeList conditionalUris = popNode(); |
| StringNode uri = popLiteralString(); |
| addLibraryTag(new Import(importKeyword, uri, conditionalUris, prefix, |
| combinators, popMetadata(compilationUnitElement), |
| isDeferred: isDeferred)); |
| } |
| |
| @override |
| void handleRecoverImport(Token semicolon) { |
| popNode(); // combinators |
| popNode(); // isDeferred |
| popNode(); // prefix |
| popNode(); // conditionalUris |
| // TODO(danrubel): recover |
| } |
| |
| @override |
| void handleDottedName(int count, Token token) { |
| NodeList identifiers = makeNodeList(count, null, null, '.'); |
| pushNode(new DottedName(token, identifiers)); |
| } |
| |
| @override |
| void endConditionalUris(int count) { |
| if (count == 0) { |
| pushNode(null); |
| } else { |
| pushNode(makeNodeList(count, null, null, " ")); |
| } |
| } |
| |
| @override |
| void endConditionalUri(Token ifToken, Token leftParen, Token equalSign) { |
| StringNode uri = popNode(); |
| LiteralString conditionValue = (equalSign != null) ? popNode() : null; |
| DottedName identifier = popNode(); |
| pushNode(new ConditionalUri(ifToken, identifier, conditionValue, uri)); |
| } |
| |
| @override |
| void endEnum(Token enumKeyword, Token leftBrace, int count) { |
| NodeList names = makeNodeList(count, leftBrace, leftBrace?.endGroup, ","); |
| Identifier name = popNode(); |
| |
| int id = idGenerator.getNextFreeId(); |
| Element enclosing = compilationUnitElement; |
| pushElement(new EnumClassElementX( |
| name.source, enclosing, id, new Enum(enumKeyword, name, names))); |
| rejectBuiltInIdentifier(name); |
| } |
| |
| @override |
| void endExport(Token exportKeyword, Token semicolon) { |
| NodeList combinators = popNode(); |
| NodeList conditionalUris = popNode(); |
| StringNode uri = popNode(); |
| addLibraryTag(new Export(exportKeyword, uri, conditionalUris, combinators, |
| popMetadata(compilationUnitElement))); |
| } |
| |
| @override |
| void endCombinators(int count) { |
| if (0 == count) { |
| pushNode(null); |
| } else { |
| pushNode(makeNodeList(count, null, null, " ")); |
| } |
| } |
| |
| @override |
| void endHide(Token hideKeyword) => pushCombinator(hideKeyword); |
| |
| @override |
| void endShow(Token showKeyword) => pushCombinator(showKeyword); |
| |
| void pushCombinator(Token keywordToken) { |
| NodeList identifiers = popNode(); |
| pushNode(new Combinator(identifiers, keywordToken)); |
| } |
| |
| @override |
| void handleIdentifierList(int count) { |
| pushNode(makeNodeList(count, null, null, ",")); |
| } |
| |
| @override |
| void endTypeList(int count) { |
| pushNode(makeNodeList(count, null, null, ",")); |
| } |
| |
| @override |
| void endPart(Token partKeyword, Token semicolon) { |
| StringNode uri = popLiteralString(); |
| addLibraryTag( |
| new Part(partKeyword, uri, popMetadata(compilationUnitElement))); |
| } |
| |
| @override |
| void endPartOf( |
| Token partKeyword, Token ofKeyword, Token semicolon, bool hasName) { |
| Expression name = popNode(); |
| addPartOfTag( |
| new PartOf(partKeyword, name, popMetadata(compilationUnitElement))); |
| } |
| |
| void addPartOfTag(PartOf tag) { |
| compilationUnitElement.setPartOf(tag, reporter); |
| } |
| |
| @override |
| void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { |
| if (periodBeforeName != null) { |
| popNode(); // Discard name. |
| } |
| popNode(); // Discard type parameters |
| popNode(); // Discard identifier |
| // TODO(paulberry,ahe): type variable metadata should not be ignored. See |
| // dartbug.com/5841. |
| if (!inTypeVariable) { |
| pushMetadata(new PartialMetadataAnnotation(beginToken, endToken)); |
| } |
| } |
| |
| @override |
| void endTopLevelDeclaration(Token token) { |
| if (!metadata.isEmpty) { |
| MetadataAnnotationX first = metadata.first; |
| recoverableError(reporter.spanFromToken(first.beginToken), |
| 'Metadata not supported here.'); |
| metadata.clear(); |
| } |
| } |
| |
| @override |
| void handleNativeFunctionBody(Token nativeToken, Token semicolon) {} |
| |
| @override |
| void handleClassImplements(Token implementsKeyword, int interfacesCount) { |
| makeNodeList(interfacesCount, implementsKeyword, null, ","); // interfaces |
| } |
| |
| @override |
| void handleRecoverClassHeader() { |
| popNode(); // superType |
| } |
| |
| @override |
| void endClassDeclaration(Token beginToken, Token endToken) { |
| popNode(); // superType |
| popNode(); // typeParameters |
| Identifier name = popNode(); |
| int id = idGenerator.getNextFreeId(); |
| PartialClassElement element = new PartialClassElement( |
| name.source, beginToken, endToken, compilationUnitElement, id); |
| pushElement(element); |
| rejectBuiltInIdentifier(name); |
| } |
| |
| void rejectBuiltInIdentifier(Identifier name) { |
| if (name.token is KeywordToken) { |
| TokenType type = name.token.type; |
| if (!type.isPseudo) { |
| recoverableError(name, "Illegal name '${type.lexeme}'."); |
| } |
| } |
| } |
| |
| @override |
| void endFunctionTypeAlias( |
| Token typedefKeyword, Token equals, Token endToken) { |
| Identifier name; |
| if (equals == null) { |
| popNode(); // TODO(karlklose): do not throw away typeVariables. |
| name = popNode(); |
| popNode(); // returnType |
| } else { |
| popNode(); // Function type. |
| popNode(); // TODO(karlklose): do not throw away typeVariables. |
| name = popNode(); |
| } |
| pushElement(new PartialTypedefElement( |
| name.source, compilationUnitElement, typedefKeyword, endToken)); |
| rejectBuiltInIdentifier(name); |
| } |
| |
| @override |
| void endNamedMixinApplication(Token beginToken, Token classKeyword, |
| Token equals, Token implementsKeyword, Token endToken) { |
| NodeList interfaces = (implementsKeyword != null) ? popNode() : null; |
| MixinApplication mixinApplication = popNode(); |
| NodeList typeParameters = popNode(); |
| Identifier name = popNode(); |
| Modifiers modifiers = popNode(); |
| NamedMixinApplication namedMixinApplication = new NamedMixinApplication( |
| name, |
| typeParameters, |
| modifiers, |
| mixinApplication, |
| interfaces, |
| beginToken, |
| endToken); |
| |
| int id = idGenerator.getNextFreeId(); |
| Element enclosing = compilationUnitElement; |
| pushElement(new NamedMixinApplicationElementX( |
| name.source, enclosing, id, namedMixinApplication)); |
| rejectBuiltInIdentifier(name); |
| } |
| |
| @override |
| void endMixinApplication(Token withKeyword) { |
| NodeList mixins = popNode(); |
| NominalTypeAnnotation superclass = popNode(); |
| pushNode(new MixinApplication(superclass, mixins)); |
| } |
| |
| @override |
| void handleVoidKeyword(Token token) { |
| pushNode(new NominalTypeAnnotation(new Identifier(token), null)); |
| } |
| |
| @override |
| void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { |
| bool hasParseError = currentMemberHasParseError; |
| memberErrors = memberErrors.tail; |
| popNode(); // typeVariables |
| Identifier name = popNode(); |
| popNode(); // type |
| Modifiers modifiers = popNode(); |
| PartialFunctionElement element = new PartialFunctionElement(name.source, |
| beginToken, getOrSet, endToken, modifiers, compilationUnitElement); |
| element.hasParseError = hasParseError; |
| pushElement(element); |
| } |
| |
| @override |
| void endTopLevelFields(int count, Token beginToken, Token endToken) { |
| bool hasParseError = currentMemberHasParseError; |
| memberErrors = memberErrors.tail; |
| void buildFieldElement(Identifier name, VariableList fields) { |
| pushElement(new FieldElementX(name, compilationUnitElement, fields)); |
| } |
| |
| NodeList variables = makeNodeList(count, null, null, ","); |
| popNode(); // type |
| Modifiers modifiers = popNode(); |
| buildFieldElements(modifiers, variables, compilationUnitElement, |
| buildFieldElement, beginToken, endToken, hasParseError); |
| } |
| |
| @override |
| void handleInvalidTopLevelDeclaration(Token endToken) { |
| memberErrors = memberErrors.tail; |
| } |
| |
| void buildFieldElements( |
| Modifiers modifiers, |
| NodeList variables, |
| Element enclosingElement, |
| void buildFieldElement(Identifier name, VariableList fields), |
| Token beginToken, |
| Token endToken, |
| bool hasParseError) { |
| VariableList fields = |
| new PartialFieldList(beginToken, endToken, modifiers, hasParseError); |
| for (Link<Node> variableNodes = variables.nodes; |
| !variableNodes.isEmpty; |
| variableNodes = variableNodes.tail) { |
| Expression initializedIdentifier = variableNodes.head; |
| Identifier identifier = initializedIdentifier.asIdentifier(); |
| if (identifier == null) { |
| identifier = initializedIdentifier.asSendSet().selector.asIdentifier(); |
| } |
| buildFieldElement(identifier, fields); |
| } |
| } |
| |
| @override |
| void handleIdentifier(Token token, IdentifierContext context) { |
| if (context == IdentifierContext.enumValueDeclaration) { |
| metadata.clear(); |
| } |
| pushNode(new Identifier(token)); |
| } |
| |
| @override |
| void handleQualified(Token period) { |
| Identifier last = popNode(); |
| Expression first = popNode(); |
| pushNode(new Send(first, last)); |
| } |
| |
| @override |
| void handleNoConstructorReferenceContinuationAfterTypeArguments( |
| Token token) {} |
| |
| @override |
| void handleNoType(Token lastConsumed) { |
| pushNode(null); |
| } |
| |
| @override |
| void beginTypeVariable(Token token) { |
| inTypeVariable = true; |
| } |
| |
| @override |
| void endTypeVariable(Token token, Token extendsOrSuper) { |
| inTypeVariable = false; |
| NominalTypeAnnotation bound = popNode(); |
| Identifier name = popNode(); |
| pushNode(new TypeVariable(name, extendsOrSuper, bound)); |
| rejectBuiltInIdentifier(name); |
| } |
| |
| @override |
| void endTypeVariables(int count, Token beginToken, Token endToken) { |
| pushNode(makeNodeList(count, beginToken, endToken, ',')); |
| } |
| |
| @override |
| void handleNoTypeVariables(Token token) { |
| pushNode(null); |
| } |
| |
| @override |
| void endTypeArguments(int count, Token beginToken, Token endToken) { |
| pushNode(makeNodeList(count, beginToken, endToken, ',')); |
| } |
| |
| @override |
| void handleNoTypeArguments(Token token) { |
| pushNode(null); |
| } |
| |
| @override |
| void handleType(Token beginToken, Token endToken) { |
| NodeList typeArguments = popNode(); |
| Expression typeName = popNode(); |
| pushNode(new NominalTypeAnnotation(typeName, typeArguments)); |
| } |
| |
| void handleNoName(Token token) { |
| pushNode(null); |
| } |
| |
| @override |
| void endFunctionType(Token functionToken, Token endToken) { |
| popNode(); // Return type. |
| popNode(); // Type parameters. |
| pushNode(null); |
| } |
| |
| @override |
| void handleParenthesizedExpression(Token token) { |
| Expression expression = popNode(); |
| pushNode(new ParenthesizedExpression(expression, token)); |
| } |
| |
| @override |
| void handleModifier(Token token) { |
| pushNode(new Identifier(token)); |
| } |
| |
| @override |
| void handleModifiers(int count) { |
| if (count == 0) { |
| pushNode(Modifiers.EMPTY); |
| } else { |
| NodeList modifierNodes = makeNodeList(count, null, null, ' '); |
| pushNode(new Modifiers(modifierNodes)); |
| } |
| } |
| |
| @override |
| Token handleUnrecoverableError(Token token, Message message) { |
| Token next = handleError(token, message); |
| if (next == null && |
| message.code != codes.codeUnterminatedComment && |
| message.code != codes.codeUnterminatedString) { |
| throw new ParserError.fromTokens(token, token, message); |
| } else { |
| return next; |
| } |
| } |
| |
| @override |
| void handleRecoverableError( |
| Message message, Token startToken, Token endToken) { |
| if (message == codes.messageNativeClauseShouldBeAnnotation) { |
| native.checkAllowedLibrary(this, startToken); |
| return; |
| } |
| handleError(startToken, message); |
| } |
| |
| @override |
| void handleInvalidExpression(Token token) { |
| pushNode(new ErrorExpression(token)); |
| } |
| |
| @override |
| void handleInvalidFunctionBody(Token token) { |
| lastErrorWasNativeFunctionBody = false; |
| } |
| |
| @override |
| void handleInvalidTypeReference(Token token) { |
| pushNode(null); |
| } |
| |
| Token handleError(Token token, Message message) { |
| MessageKind errorCode; |
| Map<String, dynamic> arguments = message.arguments; |
| |
| switch (message.code.dart2jsCode) { |
| case "MISSING_TOKEN_BEFORE_THIS": |
| String expected = arguments["string"]; |
| // TODO(danrubel): This functionality is being replaced by |
| // the parser's ensureSemicolon method. |
| if (identical(";", expected)) { |
| // When a semicolon is missing, it often leads to an error on the |
| // following line. So we try to find the token preceding the semicolon |
| // and report that something is missing *after* it. |
| Token preceding = findPrecedingToken(token); |
| if (preceding == token) { |
| reportErrorFromToken(token, MessageKind.MISSING_TOKEN_BEFORE_THIS, |
| {'token': expected}); |
| } else { |
| reportErrorFromToken(preceding, |
| MessageKind.MISSING_TOKEN_AFTER_THIS, {'token': expected}); |
| } |
| return preceding; |
| } else { |
| reportFatalError( |
| reporter.spanFromToken(token), |
| MessageTemplate.TEMPLATES[MessageKind.MISSING_TOKEN_BEFORE_THIS] |
| .message({'token': expected}, true).toString()); |
| return null; |
| } |
| break; |
| |
| case "EXPECTED_IDENTIFIER": |
| if (token is KeywordToken) { |
| reportErrorFromToken( |
| token, |
| MessageKind.EXPECTED_IDENTIFIER_NOT_RESERVED_WORD, |
| {'keyword': token.lexeme}); |
| } else if (token is ErrorToken) { |
| // TODO(ahe): This is dead code. |
| return newSyntheticToken(synthesizeIdentifier(token)); |
| } else { |
| reportFatalError(reporter.spanFromToken(token), |
| "Expected identifier, but got '${token.lexeme}'."); |
| } |
| return newSyntheticToken(token); |
| |
| case "NATIVE_OR_BODY_EXPECTED": |
| if (optional("native", token)) { |
| return newSyntheticToken(native.handleNativeBlockToSkip(this, token)); |
| } else { |
| errorCode = MessageKind.BODY_EXPECTED; |
| } |
| break; |
| |
| case "NATIVE_OR_FATAL": |
| if (optional("native", token)) { |
| lastErrorWasNativeFunctionBody = true; |
| return newSyntheticToken( |
| native.handleNativeFunctionBody(this, token)); |
| } else { |
| reportFatalError(reporter.spanFromToken(token), message.message); |
| } |
| return null; |
| |
| case "UNMATCHED_TOKEN": |
| reportErrorFromToken(token, MessageKind.UNMATCHED_TOKEN, |
| {"end": arguments["string"], "begin": arguments["token"]}); |
| Token next = token; |
| while (next.next is ErrorToken) { |
| next = next.next; |
| } |
| return next; |
| |
| case "EMPTY_NAMED_PARAMETER_LIST": |
| errorCode = MessageKind.EMPTY_NAMED_PARAMETER_LIST; |
| break; |
| |
| case "EMPTY_OPTIONAL_PARAMETER_LIST": |
| errorCode = MessageKind.EMPTY_OPTIONAL_PARAMETER_LIST; |
| break; |
| |
| case "BODY_EXPECTED": |
| errorCode = MessageKind.BODY_EXPECTED; |
| break; |
| |
| case "HEX_DIGIT_EXPECTED": |
| errorCode = MessageKind.HEX_DIGIT_EXPECTED; |
| break; |
| |
| case "GENERIC": |
| errorCode = MessageKind.GENERIC; |
| arguments = {"text": message.message}; |
| break; |
| |
| case "EXTRANEOUS_MODIFIER": |
| errorCode = MessageKind.EXTRANEOUS_MODIFIER; |
| arguments = {"modifier": arguments["token"]}; |
| break; |
| |
| case "EXTRANEOUS_MODIFIER_REPLACE": |
| errorCode = MessageKind.EXTRANEOUS_MODIFIER_REPLACE; |
| arguments = {"modifier": arguments["token"]}; |
| break; |
| |
| case "INVALID_AWAIT_FOR": |
| errorCode = MessageKind.INVALID_AWAIT_FOR; |
| break; |
| |
| case "BAD_INPUT_CHARACTER": |
| errorCode = MessageKind.BAD_INPUT_CHARACTER; |
| int codePoint = arguments["codePoint"]; |
| String hex = codePoint.toRadixString(16); |
| String padding = "0000".substring(hex.length); |
| arguments = {'characterHex': padding}; |
| break; |
| |
| case "INVALID_INLINE_FUNCTION_TYPE": |
| errorCode = MessageKind.INVALID_INLINE_FUNCTION_TYPE; |
| break; |
| |
| case "INVALID_SYNC_MODIFIER": |
| errorCode = MessageKind.INVALID_SYNC_MODIFIER; |
| break; |
| |
| case "VOID_NOT_ALLOWED": |
| errorCode = MessageKind.VOID_NOT_ALLOWED; |
| break; |
| |
| case "MALFORMED_STRING_LITERAL": |
| errorCode = MessageKind.MALFORMED_STRING_LITERAL; |
| break; |
| |
| case "EXPONENT_MISSING": |
| errorCode = MessageKind.EXPONENT_MISSING; |
| break; |
| |
| case "POSITIONAL_PARAMETER_WITH_EQUALS": |
| errorCode = MessageKind.POSITIONAL_PARAMETER_WITH_EQUALS; |
| break; |
| |
| case "REQUIRED_PARAMETER_WITH_DEFAULT": |
| errorCode = MessageKind.REQUIRED_PARAMETER_WITH_DEFAULT; |
| break; |
| |
| case "UNMATCHED_TOKEN": |
| errorCode = MessageKind.UNMATCHED_TOKEN; |
| break; |
| |
| case "UNSUPPORTED_PREFIX_PLUS": |
| errorCode = MessageKind.UNSUPPORTED_PREFIX_PLUS; |
| break; |
| |
| case "UNTERMINATED_COMMENT": |
| errorCode = MessageKind.UNTERMINATED_COMMENT; |
| break; |
| |
| case "UNTERMINATED_STRING": |
| errorCode = MessageKind.UNTERMINATED_STRING; |
| arguments = {"quote": arguments["string"]}; |
| break; |
| |
| case "UNTERMINATED_TOKEN": |
| errorCode = MessageKind.UNTERMINATED_TOKEN; |
| break; |
| |
| case "*fatal*": |
| // This is an error that Fasta can recover from, but dart2js can't. |
| reportFatalError(reporter.spanFromToken(token), message.message); |
| return null; |
| |
| case "*ignored*": |
| // This is an error that Fasta reports as a recoverable error during |
| // parsing. For historical reasons, dart2js implements this in a later |
| // phase already, so we just ignore it. Another possibilty is that we |
| // wan't to avoid introducing a breaking change to dart2js. |
| return null; |
| |
| default: |
| throw "Unexpected message code: ${message.code}"; |
| } |
| SourceSpan span = reporter.spanFromToken(token); |
| reportError(span, errorCode, arguments); |
| return null; |
| } |
| |
| /// Finds the preceding token via the begin token of the last AST node pushed |
| /// on the [nodes] stack. |
| Token findPrecedingToken(Token token) { |
| Token result; |
| Link<Node> nodes = this.nodes; |
| while (!nodes.isEmpty) { |
| result = findPrecedingTokenFromNode(nodes.head, token); |
| if (result != null) { |
| return result; |
| } |
| nodes = nodes.tail; |
| } |
| if (compilationUnitElement != null) { |
| if (compilationUnitElement is CompilationUnitElementX) { |
| CompilationUnitElementX unit = compilationUnitElement; |
| Link<Element> members = unit.localMembers; |
| while (!members.isEmpty) { |
| ElementX member = members.head; |
| DeclarationSite site = member.declarationSite; |
| if (site is PartialElement) { |
| result = findPrecedingTokenFromToken(site.endToken, token); |
| if (result != null) { |
| return result; |
| } |
| } |
| members = members.tail; |
| } |
| result = |
| findPrecedingTokenFromNode(compilationUnitElement.partTag, token); |
| if (result != null) { |
| return result; |
| } |
| } |
| } |
| return token; |
| } |
| |
| Token findPrecedingTokenFromNode(Node node, Token token) { |
| if (node != null) { |
| return findPrecedingTokenFromToken(node.getBeginToken(), token); |
| } |
| return null; |
| } |
| |
| Token findPrecedingTokenFromToken(Token start, Token token) { |
| if (start != null) { |
| Token current = start; |
| while (current.kind != Tokens.EOF_TOKEN && current.next != token) { |
| current = current.next; |
| } |
| if (current.kind != Tokens.EOF_TOKEN) { |
| return current; |
| } |
| } |
| return null; |
| } |
| |
| /// Finds the preceding token via the begin token of the last AST node pushed |
| /// on the [nodes] stack. |
| Token synthesizeIdentifier(Token token) { |
| Token synthesizedToken = |
| new StringToken.fromString(TokenType.IDENTIFIER, '?', token.charOffset); |
| synthesizedToken.next = token.next; |
| return synthesizedToken; |
| } |
| |
| void recoverableError(Spannable node, String message) { |
| // TODO(johnniwinther): Make recoverable errors non-fatal. |
| reportFatalError(node, message); |
| } |
| |
| void pushElement(ElementX element) { |
| assert(element.declarationSite != null, |
| failedAt(element, 'Missing declaration site for $element.')); |
| popMetadata(element); |
| compilationUnitElement.addMember(element, reporter); |
| } |
| |
| List<MetadataAnnotation> popMetadata(ElementX element) { |
| List<MetadataAnnotation> result = metadata.toList(); |
| element.metadata = result; |
| metadata.clear(); |
| return result; |
| } |
| |
| void pushMetadata(MetadataAnnotation annotation) { |
| metadata.addLast(annotation); |
| } |
| |
| void addLibraryTag(LibraryTag tag) { |
| if (!allowLibraryTags()) { |
| recoverableError(tag, 'Library tags not allowed here.'); |
| } |
| LibraryElementX implementationLibrary = |
| compilationUnitElement.implementationLibrary; |
| implementationLibrary.addTag(tag, reporter); |
| } |
| |
| void pushNode(Node node) { |
| nodes = nodes.prepend(node); |
| if (VERBOSE) log("push $nodes"); |
| } |
| |
| Node popNode() { |
| assert(!nodes.isEmpty); |
| Node node = nodes.head; |
| nodes = nodes.tail; |
| if (VERBOSE) log("pop $nodes"); |
| return node; |
| } |
| |
| void log(message) { |
| print(message); |
| } |
| |
| NodeList makeNodeList( |
| int count, Token beginToken, Token endToken, String delimiter) { |
| Link<Node> poppedNodes = const Link<Node>(); |
| for (; count > 0; --count) { |
| // This effectively reverses the order of nodes so they end up |
| // in correct (source) order. |
| poppedNodes = poppedNodes.prepend(popNode()); |
| } |
| return new NodeList(beginToken, poppedNodes, endToken, delimiter); |
| } |
| |
| @override |
| void beginLiteralString(Token token) { |
| String source = token.lexeme; |
| StringQuoting quoting = StringValidator.quotingFromString(source); |
| pushQuoting(quoting); |
| // Just wrap the token for now. At the end of the interpolation, |
| // when we know how many there are, go back and validate the tokens. |
| pushNode(new LiteralString(token, null)); |
| } |
| |
| @override |
| void handleStringPart(Token token) { |
| // Just push an unvalidated token now, and replace it when we know the |
| // end of the interpolation. |
| pushNode(new LiteralString(token, null)); |
| } |
| |
| @override |
| void endLiteralString(int interpolationCount, Token endToken) { |
| StringQuoting quoting = popQuoting(); |
| |
| Link<StringInterpolationPart> parts = const Link<StringInterpolationPart>(); |
| // Parts of the string interpolation are popped in reverse order, |
| // starting with the last literal string part. |
| bool isLast = true; |
| for (int i = 0; i < interpolationCount; i++) { |
| LiteralString string = popNode(); |
| DartString validation = stringValidator.validateInterpolationPart( |
| string.token, quoting, |
| isFirst: false, isLast: isLast); |
| // Replace the unvalidated LiteralString with a new LiteralString |
| // object that has the validation result included. |
| string = new LiteralString(string.token, validation); |
| Expression expression = popNode(); |
| parts = parts.prepend(new StringInterpolationPart(expression, string)); |
| isLast = false; |
| } |
| |
| LiteralString string = popNode(); |
| DartString validation = stringValidator.validateInterpolationPart( |
| string.token, quoting, |
| isFirst: true, isLast: isLast); |
| string = new LiteralString(string.token, validation); |
| if (isLast) { |
| pushNode(string); |
| } else { |
| NodeList partNodes = new NodeList(null, parts, null, ""); |
| pushNode(new StringInterpolation(string, partNodes)); |
| } |
| } |
| |
| @override |
| void handleNativeClause(Token nativeToken, bool hasName) { |
| if (hasName) { |
| nativeName = popNode(); // LiteralString |
| } else { |
| nativeName = null; |
| } |
| } |
| |
| @override |
| void handleStringJuxtaposition(int stringCount) { |
| assert(stringCount != 0); |
| Expression accumulator = popNode(); |
| stringCount--; |
| while (stringCount > 0) { |
| Expression expression = popNode(); |
| accumulator = new StringJuxtaposition(expression, accumulator); |
| stringCount--; |
| } |
| pushNode(accumulator); |
| } |
| |
| @override |
| void beginMember() { |
| memberErrors = memberErrors.prepend(false); |
| } |
| |
| @override |
| void beginTopLevelMember(Token token) { |
| beginMember(); |
| } |
| |
| @override |
| void endMember() { |
| memberErrors = memberErrors.tail; |
| } |
| |
| /// Don't call this method. Should only be used as a last resort when there |
| /// is no feasible way to recover from a parser error. |
| void reportFatalError(Spannable spannable, String message) { |
| reportError(spannable, MessageKind.GENERIC, {'text': message}); |
| // Some parse errors are infeasible to recover from, so we throw an error. |
| SourceSpan span = reporter.spanFromSpannable(spannable); |
| throw new ParserError( |
| span.begin, span.end, codes.templateUnspecified.withArguments(message)); |
| } |
| |
| void reportError(Spannable spannable, MessageKind errorCode, |
| [Map arguments = const {}]) { |
| if (currentMemberHasParseError) return; // Error already reported. |
| if (suppressParseErrors) return; |
| if (!memberErrors.isEmpty) { |
| memberErrors = memberErrors.tail.prepend(true); |
| } |
| reporter.reportErrorMessage(spannable, errorCode, arguments); |
| } |
| |
| void reportErrorFromToken(Token token, MessageKind errorCode, |
| [Map arguments = const {}]) { |
| reportError(reporter.spanFromToken(token), errorCode, arguments); |
| } |
| } |