// Part of the Carbon Language project, under the Apache License v2.0 with LLVM // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/lex/token_kind.h" #include "toolchain/parse/context.h" #include "toolchain/parse/handle.h" #include "toolchain/parse/node_kind.h" namespace Carbon::Parse { // Finishes an invalid declaration, skipping past its end. static auto FinishAndSkipInvalidDecl(Context& context, int32_t subtree_start) -> void { auto cursor = *context.position(); // Output an invalid parse subtree including everything up to the next `;` // or end of line. context.ReplacePlaceholderNode(subtree_start, NodeKind::InvalidParseStart, cursor, /*has_error=*/true); context.AddNode(NodeKind::InvalidParseSubtree, context.SkipPastLikelyEnd(cursor), /*has_error=*/true); } // Prints a diagnostic and calls FinishAndSkipInvalidDecl. static auto HandleUnrecognizedDecl(Context& context, int32_t subtree_start) -> void { CARBON_DIAGNOSTIC(UnrecognizedDecl, Error, "unrecognized declaration introducer"); context.emitter().Emit(*context.position(), UnrecognizedDecl); FinishAndSkipInvalidDecl(context, subtree_start); } // Replaces the introducer placeholder node, and pushes the introducer state for // processing. static auto ApplyIntroducer(Context& context, Context::StateStackEntry state, NodeKind introducer_kind, State next_state) -> void { context.ReplacePlaceholderNode(state.subtree_start, introducer_kind, context.Consume()); // Reuse state here to retain its `subtree_start`. context.PushState(state, next_state); } namespace { // The kind of declaration introduced by an introducer keyword. enum class DeclIntroducerKind : int8_t { Unrecognized, PackagingDecl, NonPackagingDecl, }; // Information about a keyword that might be an introducer keyword. struct DeclIntroducerInfo { DeclIntroducerKind introducer_kind; NodeKind node_kind; State state; }; } // namespace static constexpr auto DeclIntroducers = [] { DeclIntroducerInfo introducers[] = { #define CARBON_TOKEN(Name) \ {.introducer_kind = DeclIntroducerKind::Unrecognized, \ .node_kind = NodeKind::InvalidParse, \ .state = State::Invalid}, #include "toolchain/lex/token_kind.def" }; auto set = [&](Lex::TokenKind token_kind, NodeKind node_kind, State state) { introducers[token_kind.AsInt()] = { .introducer_kind = DeclIntroducerKind::NonPackagingDecl, .node_kind = node_kind, .state = state}; }; auto set_packaging = [&](Lex::TokenKind token_kind, NodeKind node_kind, State state) { introducers[token_kind.AsInt()] = { .introducer_kind = DeclIntroducerKind::PackagingDecl, .node_kind = node_kind, .state = state}; }; set(Lex::TokenKind::Adapt, NodeKind::AdaptIntroducer, State::AdaptAfterIntroducer); set(Lex::TokenKind::Alias, NodeKind::AliasIntroducer, State::Alias); set(Lex::TokenKind::Base, NodeKind::BaseIntroducer, State::BaseAfterIntroducer); set(Lex::TokenKind::Choice, NodeKind::ChoiceIntroducer, State::ChoiceIntroducer); set(Lex::TokenKind::Class, NodeKind::ClassIntroducer, State::TypeAfterIntroducerAsClass); set(Lex::TokenKind::Constraint, NodeKind::NamedConstraintIntroducer, State::TypeAfterIntroducerAsNamedConstraint); set(Lex::TokenKind::Export, NodeKind::ExportIntroducer, State::ExportName); // TODO: Treat `extend` as a declaration introducer. set(Lex::TokenKind::Fn, NodeKind::FunctionIntroducer, State::FunctionIntroducer); set(Lex::TokenKind::Impl, NodeKind::ImplIntroducer, State::ImplAfterIntroducer); set(Lex::TokenKind::Interface, NodeKind::InterfaceIntroducer, State::TypeAfterIntroducerAsInterface); set(Lex::TokenKind::Namespace, NodeKind::NamespaceStart, State::Namespace); set(Lex::TokenKind::Let, NodeKind::LetIntroducer, State::Let); set(Lex::TokenKind::Var, NodeKind::VariableIntroducer, State::VarAsDecl); set_packaging(Lex::TokenKind::Package, NodeKind::PackageIntroducer, State::Package); set_packaging(Lex::TokenKind::Library, NodeKind::LibraryIntroducer, State::Library); set_packaging(Lex::TokenKind::Import, NodeKind::ImportIntroducer, State::Import); return std::to_array(introducers); }(); // Attempts to handle the current token as a declaration introducer. // Returns true if the current position is a declaration. If we see a // declaration introducer keyword token, replace the placeholder node and switch // to a state to parse the rest of the declaration. static auto TryHandleAsDecl(Context& context, Context::StateStackEntry state, bool saw_modifier) -> bool { const auto& info = DeclIntroducers[context.PositionKind().AsInt()]; switch (info.introducer_kind) { case DeclIntroducerKind::Unrecognized: { // A `;` with no modifiers is an empty declaration. if (!saw_modifier) { if (auto loc = context.ConsumeIf(Lex::TokenKind::Semi)) { context.ReplacePlaceholderNode(state.subtree_start, NodeKind::EmptyDecl, *loc); return true; } } return false; } case DeclIntroducerKind::PackagingDecl: { // Packaging declarations update the packaging state themselves as needed. break; } case DeclIntroducerKind::NonPackagingDecl: { // Because a non-packaging keyword was encountered, packaging is complete. // Misplaced packaging keywords may lead to this being re-triggered. if (context.packaging_state() != Context::PackagingState::AfterNonPackagingDecl) { if (!context.first_non_packaging_token().has_value()) { context.set_first_non_packaging_token(*context.position()); } context.set_packaging_state( Context::PackagingState::AfterNonPackagingDecl); } break; } } ApplyIntroducer(context, state, info.node_kind, info.state); return true; } // Returns true if position_kind could be either an introducer or modifier, and // should be treated as an introducer. static auto ResolveAmbiguousTokenAsDeclaration(Context& context, Lex::TokenKind position_kind) -> bool { switch (position_kind) { case Lex::TokenKind::Base: case Lex::TokenKind::Export: case Lex::TokenKind::Extend: case Lex::TokenKind::Impl: // This is an ambiguous token, so now we check what the next token is. // We use the macro for modifiers, including introducers which are // also modifiers (such as `base`). Other introducer tokens need to be // added by hand. switch (context.PositionKind(Lookahead::NextToken)) { case Lex::TokenKind::Adapt: case Lex::TokenKind::Alias: case Lex::TokenKind::Class: case Lex::TokenKind::Constraint: case Lex::TokenKind::Extern: case Lex::TokenKind::Fn: case Lex::TokenKind::Import: case Lex::TokenKind::Interface: case Lex::TokenKind::Let: case Lex::TokenKind::Library: case Lex::TokenKind::Namespace: case Lex::TokenKind::Var: #define CARBON_PARSE_NODE_KIND(...) #define CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Name, ...) \ case Lex::TokenKind::Name: #include "toolchain/parse/node_kind.def" return false; case Lex::TokenKind::Package: // `package.foo` is an expression; any other token after `package` is // a `package` introducer. return context.PositionKind(static_cast(2)) == Lex::TokenKind::Period; default: return true; } break; default: return false; } } // Returns true if the current position is a modifier, handling it if so. static auto TryHandleAsModifier(Context& context) -> bool { auto position_kind = context.PositionKind(); if (ResolveAmbiguousTokenAsDeclaration(context, position_kind)) { return false; } switch (position_kind) { #define CARBON_PARSE_NODE_KIND(...) #define CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Name, ...) \ case Lex::TokenKind::Name: \ context.AddLeafNode(NodeKind::Name##Modifier, context.Consume()); \ return true; #include "toolchain/parse/node_kind.def" case Lex::TokenKind::Extern: { auto extern_token = context.Consume(); if (context.PositionIs(Lex::TokenKind::Library)) { // `extern library ` syntax. context.ParseLibrarySpecifier(/*accept_default=*/true); // TODO: Consider error recovery when a non-declaration token is next, // like a typo of the library name. context.AddNode(NodeKind::ExternModifierWithLibrary, extern_token, /*has_error=*/false); } else { // `extern` syntax without a library. context.AddLeafNode(NodeKind::ExternModifier, extern_token); } return true; } default: return false; } } auto HandleDecl(Context& context) -> void { auto state = context.PopState(); // Add a placeholder node, to be replaced by the declaration introducer once // it is found. context.AddLeafNode(NodeKind::Placeholder, *context.position()); bool saw_modifier = false; while (TryHandleAsModifier(context)) { saw_modifier = true; } if (!TryHandleAsDecl(context, state, saw_modifier)) { HandleUnrecognizedDecl(context, state.subtree_start); } } auto HandleDeclScopeLoop(Context& context) -> void { // This maintains the current state unless we're at the end of the scope. if (context.PositionIs(Lex::TokenKind::CloseCurlyBrace) || context.PositionIs(Lex::TokenKind::FileEnd)) { // This is the end of the scope, so the loop state ends. context.PopAndDiscardState(); return; } context.PushState(State::Decl); } } // namespace Carbon::Parse