// 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/base/value_store.h" #include "toolchain/lex/token_kind.h" #include "toolchain/lex/tokenized_buffer.h" #include "toolchain/parse/context.h" #include "toolchain/parse/handle.h" #include "toolchain/parse/node_ids.h" #include "toolchain/parse/node_kind.h" namespace Carbon::Parse { // Provides common error exiting logic that skips to the semi, if present. static auto OnParseError(Context& context, Context::StateStackEntry state, NodeKind declaration) -> void { return context.AddNode(declaration, context.SkipPastLikelyEnd(state.token), state.subtree_start, /*has_error=*/true); } // Determines whether the specified modifier appears within the introducer of // the given declaration. // TODO: Restructure how we handle packaging declarations to avoid the need to // do this. static auto HasModifier(Context& context, Context::StateStackEntry state, Lex::TokenKind modifier) -> bool { for (auto it = Lex::TokenIterator(state.token); it != context.position(); ++it) { if (context.tokens().GetKind(*it) == modifier) { return true; } } return false; } // Handles parsing of the library name. Returns the name's ID on success, which // may be invalid for `default`. static auto HandleLibraryName(Context& context, bool accept_default) -> std::optional { if (auto library_name_token = context.ConsumeIf(Lex::TokenKind::StringLiteral)) { context.AddLeafNode(NodeKind::LibraryName, *library_name_token); return context.tokens().GetStringLiteralValue(*library_name_token); } if (accept_default) { if (auto default_token = context.ConsumeIf(Lex::TokenKind::Default)) { context.AddLeafNode(NodeKind::DefaultLibrary, *default_token); return StringLiteralValueId::Invalid; } } CARBON_DIAGNOSTIC( ExpectedLibraryNameOrDefault, Error, "Expected `default` or a string literal to specify the library name."); CARBON_DIAGNOSTIC(ExpectedLibraryName, Error, "Expected a string literal to specify the library name."); context.emitter().Emit(*context.position(), accept_default ? ExpectedLibraryNameOrDefault : ExpectedLibraryName); return std::nullopt; } // Handles everything after the declaration's introducer. static auto HandleDeclContent(Context& context, Context::StateStackEntry state, NodeKind declaration, bool is_export, bool is_impl, llvm::function_ref on_parse_error) -> void { Tree::PackagingNames names{ .node_id = ImportDeclId(NodeId(state.subtree_start)), .is_export = is_export}; if (declaration != NodeKind::LibraryDecl) { if (auto package_name_token = context.ConsumeIf(Lex::TokenKind::Identifier)) { if (names.is_export) { names.is_export = false; state.has_error = true; CARBON_DIAGNOSTIC(ExportImportPackage, Error, "`export` cannot be used when importing a package."); context.emitter().Emit(*package_name_token, ExportImportPackage); } names.package_id = context.tokens().GetIdentifier(*package_name_token); context.AddLeafNode(NodeKind::PackageName, *package_name_token); } else if (declaration == NodeKind::PackageDecl || !context.PositionIs(Lex::TokenKind::Library)) { CARBON_DIAGNOSTIC(ExpectedIdentifierAfterPackage, Error, "Expected identifier after `package`."); CARBON_DIAGNOSTIC(ExpectedIdentifierAfterImport, Error, "Expected identifier or `library` after `import`."); context.emitter().Emit(*context.position(), declaration == NodeKind::PackageDecl ? ExpectedIdentifierAfterPackage : ExpectedIdentifierAfterImport); on_parse_error(); return; } } // Parse the optional library keyword. bool accept_default = !names.package_id.is_valid(); if (declaration == NodeKind::LibraryDecl) { auto library_id = HandleLibraryName(context, accept_default); if (!library_id) { on_parse_error(); return; } names.library_id = *library_id; } else { auto next_kind = context.PositionKind(); if (next_kind == Lex::TokenKind::Library) { auto library_token = context.ConsumeChecked(Lex::TokenKind::Library); auto library_subtree_start = context.tree().size(); auto library_id = HandleLibraryName(context, accept_default); if (!library_id) { on_parse_error(); return; } names.library_id = *library_id; context.AddNode(NodeKind::LibrarySpecifier, library_token, library_subtree_start, /*has_error=*/false); } else if (next_kind == Lex::TokenKind::StringLiteral || (accept_default && next_kind == Lex::TokenKind::Default)) { // If we come across a string literal and we didn't parse `library // "..."` yet, then most probably the user forgot to add `library` // before the library name. CARBON_DIAGNOSTIC(MissingLibraryKeyword, Error, "Missing `library` keyword."); context.emitter().Emit(*context.position(), MissingLibraryKeyword); on_parse_error(); return; } } if (auto semi = context.ConsumeIf(Lex::TokenKind::Semi)) { if (declaration == NodeKind::ImportDecl) { context.AddImport(names); } else { context.set_packaging_decl(names, is_impl); } context.AddNode(declaration, *semi, state.subtree_start, state.has_error); } else { context.DiagnoseExpectedDeclSemi(context.tokens().GetKind(state.token)); on_parse_error(); } } // Returns true if currently in a valid state for imports, false otherwise. May // update the packaging state respectively. static auto VerifyInImports(Context& context, Lex::TokenIndex intro_token) -> bool { switch (context.packaging_state()) { case Context::PackagingState::FileStart: // `package` is no longer allowed, but `import` may repeat. context.set_packaging_state(Context::PackagingState::InImports); return true; case Context::PackagingState::InImports: return true; case Context::PackagingState::AfterNonPackagingDecl: { context.set_packaging_state( Context::PackagingState::InImportsAfterNonPackagingDecl); CARBON_DIAGNOSTIC(ImportTooLate, Error, "`import` declarations must come after the `package` " "declaration (if present) and before any other " "entities in the file."); CARBON_DIAGNOSTIC(FirstDecl, Note, "First declaration is here."); context.emitter() .Build(intro_token, ImportTooLate) .Note(context.first_non_packaging_token(), FirstDecl) .Emit(); return false; } case Context::PackagingState::InImportsAfterNonPackagingDecl: // There is a sequential block of misplaced `import` statements, which can // occur if a declaration is added above `import`s. Avoid duplicate // warnings. return false; } } // Diagnoses if `export` is used in an `impl` file. static auto RestrictExportToApi(Context& context, Context::StateStackEntry& state) -> void { // Error for both Main//default and every implementation file. auto packaging = context.tree().packaging_decl(); if (!packaging || packaging->is_impl) { CARBON_DIAGNOSTIC(ExportFromImpl, Error, "`export` is only allowed in API files."); context.emitter().Emit(state.token, ExportFromImpl); state.has_error = true; } } auto HandleImport(Context& context) -> void { auto state = context.PopState(); auto declaration = NodeKind::ImportDecl; auto on_parse_error = [&] { OnParseError(context, state, declaration); }; if (VerifyInImports(context, state.token)) { // Scan the modifiers to see if this import declaration is exported. bool is_export = HasModifier(context, state, Lex::TokenKind::Export); if (is_export) { RestrictExportToApi(context, state); } HandleDeclContent(context, state, declaration, is_export, /*is_impl=*/false, on_parse_error); } else { on_parse_error(); } } auto HandleExportName(Context& context) -> void { auto state = context.PopState(); RestrictExportToApi(context, state); context.PushState(state, State::ExportNameFinish); context.PushState(State::DeclNameAndParams, state.token); } auto HandleExportNameFinish(Context& context) -> void { auto state = context.PopState(); context.AddNodeExpectingDeclSemi(state, NodeKind::ExportDecl, Lex::TokenKind::Export, /*is_def_allowed=*/false); } // Handles common logic for `package` and `library`. static auto HandlePackageAndLibraryDecls(Context& context, Lex::TokenKind intro_token_kind, NodeKind declaration) -> void { auto state = context.PopState(); bool is_impl = HasModifier(context, state, Lex::TokenKind::Impl); auto on_parse_error = [&] { OnParseError(context, state, declaration); }; if (state.token != Lex::TokenIndex::FirstNonCommentToken) { CARBON_DIAGNOSTIC( PackageTooLate, Error, "The `{0}` declaration must be the first non-comment line.", Lex::TokenKind); CARBON_DIAGNOSTIC(FirstNonCommentLine, Note, "First non-comment line is here."); context.emitter() .Build(state.token, PackageTooLate, intro_token_kind) .Note(Lex::TokenIndex::FirstNonCommentToken, FirstNonCommentLine) .Emit(); on_parse_error(); return; } // `package`/`library` is no longer allowed, but `import` may repeat. context.set_packaging_state(Context::PackagingState::InImports); HandleDeclContent(context, state, declaration, /*is_export=*/false, is_impl, on_parse_error); } auto HandlePackage(Context& context) -> void { HandlePackageAndLibraryDecls(context, Lex::TokenKind::Package, NodeKind::PackageDecl); } auto HandleLibrary(Context& context) -> void { HandlePackageAndLibraryDecls(context, Lex::TokenKind::Library, NodeKind::LibraryDecl); } } // namespace Carbon::Parse