// 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/shared_value_stores.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 { context.AddNode(declaration, context.SkipPastLikelyEnd(state.token), /*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 (Lex::TokenIterator it(state.token); it != context.position(); ++it) { if (context.tokens().GetKind(*it) == modifier) { return true; } } return false; } // 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.has_value(); if (declaration == NodeKind::LibraryDecl) { auto library_id = context.ParseLibraryName(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) { if (auto library_id = context.ParseLibrarySpecifier(accept_default)) { names.library_id = *library_id; } else { on_parse_error(); return; } } 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.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