Add more package and import support. (#3402)

This should cover:

```
library "lib" api;
import Foo library default;
import library default;
import library "lib";
```

This splits out `PackageName` and `LibraryName` to their own parse nodes
so that checking can ignore them and still get a balanced parse tree
(otherwise, we essentially need to implement handling of the parse nodes
only to remove the identifiers/string literals -- the optional names
mean we can't blindly do that as before). For reference, these nodes
don't need to be handled because CheckParseTree will need to directly
funnel import information along with checked IRs.
This commit is contained in:
Jon Ross-Perkins
2023-11-16 17:09:36 +00:00
committed by GitHub
parent 49d46bd8a4
commit 204c04dbb9
42 changed files with 762 additions and 298 deletions
+161 -92
View File
@@ -5,113 +5,165 @@
#include "toolchain/base/value_store.h"
#include "toolchain/lex/tokenized_buffer.h"
#include "toolchain/parse/context.h"
#include "toolchain/parse/node_kind.h"
namespace Carbon::Parse {
// Provides error exiting logic for `import`/`package`, skipping to the semi.
static auto ExitOnParseError(Context& context, Context::StateStackEntry state,
NodeKind directive) {
// Provides common error exiting logic that skips to the semi, if present.
static auto OnParseError(Context& context, Context::StateStackEntry state,
NodeKind directive) -> void {
auto semi_token = context.SkipPastLikelyEnd(state.token);
return context.AddNode(directive, semi_token ? *semi_token : state.token,
state.subtree_start,
/*has_error=*/true);
}
// Handles the main parsing of `import`/`package`. It's expected that the
// introducer is already added.
static auto HandleImportAndPackage(Context& context,
// 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<StringLiteralId> {
if (auto library_name_token =
context.ConsumeIf(Lex::TokenKind::StringLiteral)) {
context.AddLeafNode(NodeKind::LibraryName, *library_name_token);
return context.tokens().GetStringLiteral(*library_name_token);
}
if (accept_default) {
if (auto default_token = context.ConsumeIf(Lex::TokenKind::Default)) {
context.AddLeafNode(NodeKind::DefaultLibrary, *default_token);
return StringLiteralId::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;
}
// Returns whether `api` or `impl` is provided, or prints an error and returns
// nullopt.
static auto HandleApiOrImpl(Context& context)
-> std::optional<Tree::ApiOrImpl> {
switch (context.PositionKind()) {
case Lex::TokenKind::Api: {
context.AddLeafNode(NodeKind::PackageApi,
context.ConsumeChecked(Lex::TokenKind::Api));
return Tree::ApiOrImpl::Api;
break;
}
case Lex::TokenKind::Impl: {
context.AddLeafNode(NodeKind::PackageImpl,
context.ConsumeChecked(Lex::TokenKind::Impl));
return Tree::ApiOrImpl::Impl;
break;
}
default: {
CARBON_DIAGNOSTIC(ExpectedApiOrImpl, Error, "Expected `api` or `impl`.");
context.emitter().Emit(*context.position(), ExpectedApiOrImpl);
return std::nullopt;
}
}
}
// Handles everything after the directive's introducer.
static auto HandleDirectiveContent(Context& context,
Context::StateStackEntry state,
NodeKind directive, bool is_package)
NodeKind directive,
llvm::function_ref<void()> on_parse_error)
-> void {
Tree::PackagingNames names{.node = Node(state.subtree_start)};
if (auto package_name_token = context.ConsumeIf(Lex::TokenKind::Identifier)) {
names.package_id = context.tokens().GetIdentifier(*package_name_token);
context.AddLeafNode(NodeKind::Name, *package_name_token);
} else {
CARBON_DIAGNOSTIC(ExpectedIdentifierAfterKeyword, Error,
"Expected identifier after `{0}`.", Lex::TokenKind);
context.emitter().Emit(*context.position(), ExpectedIdentifierAfterKeyword,
context.tokens().GetKind(state.token));
ExitOnParseError(context, state, directive);
return;
}
if (auto library_token = context.ConsumeIf(Lex::TokenKind::Library)) {
auto library_start = context.tree().size();
if (auto library_name_token =
context.ConsumeIf(Lex::TokenKind::StringLiteral)) {
names.library_id = context.tokens().GetStringLiteral(*library_name_token);
context.AddLeafNode(NodeKind::Literal, *library_name_token);
} else {
CARBON_DIAGNOSTIC(
ExpectedLibraryName, Error,
"Expected a string literal to specify the library name.");
context.emitter().Emit(*context.position(), ExpectedLibraryName);
ExitOnParseError(context, state, directive);
if (directive != NodeKind::LibraryDirective) {
if (auto package_name_token =
context.ConsumeIf(Lex::TokenKind::Identifier)) {
names.package_id = context.tokens().GetIdentifier(*package_name_token);
context.AddLeafNode(NodeKind::PackageName, *package_name_token);
} else if (directive == NodeKind::PackageDirective ||
!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(),
directive == NodeKind::PackageDirective
? ExpectedIdentifierAfterPackage
: ExpectedIdentifierAfterImport);
on_parse_error();
return;
}
context.AddNode(NodeKind::Library, *library_token, library_start,
/*has_error=*/false);
}
auto next_kind = context.PositionKind();
if (!names.library_id.is_valid() &&
next_kind == Lex::TokenKind::StringLiteral) {
// If we come acroess 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);
ExitOnParseError(context, state, directive);
return;
}
Tree::ApiOrImpl api_or_impl;
if (is_package) {
switch (next_kind) {
case Lex::TokenKind::Api: {
context.AddLeafNode(NodeKind::PackageApi, context.Consume());
api_or_impl = Tree::ApiOrImpl::Api;
break;
}
case Lex::TokenKind::Impl: {
context.AddLeafNode(NodeKind::PackageImpl, context.Consume());
api_or_impl = Tree::ApiOrImpl::Impl;
break;
}
default: {
CARBON_DIAGNOSTIC(ExpectedApiOrImpl, Error,
"Expected `api` or `impl`.");
context.emitter().Emit(*context.position(), ExpectedApiOrImpl);
ExitOnParseError(context, state, directive);
// Parse the optional library keyword.
bool accept_default = !names.package_id.is_valid();
if (directive == NodeKind::LibraryDirective) {
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 (!context.PositionIs(Lex::TokenKind::Semi)) {
context.EmitExpectedDeclSemi(context.tokens().GetKind(state.token));
ExitOnParseError(context, state, directive);
return;
std::optional<Tree::ApiOrImpl> api_or_impl;
if (directive != NodeKind::ImportDirective) {
api_or_impl = HandleApiOrImpl(context);
if (!api_or_impl) {
on_parse_error();
return;
}
}
if (is_package) {
context.set_packaging_directive(names, api_or_impl);
if (auto semi = context.ConsumeIf(Lex::TokenKind::Semi)) {
if (directive == NodeKind::ImportDirective) {
context.AddImport(names);
} else {
context.set_packaging_directive(names, *api_or_impl);
}
context.AddNode(directive, *semi, state.subtree_start, state.has_error);
} else {
context.AddImport(names);
context.EmitExpectedDeclSemi(context.tokens().GetKind(state.token));
on_parse_error();
}
context.AddNode(directive, context.Consume(), state.subtree_start,
state.has_error);
}
auto HandleImport(Context& context) -> void {
auto state = context.PopState();
auto intro_token = context.Consume();
auto directive = NodeKind::ImportDirective;
auto on_parse_error = [&] { OnParseError(context, state, directive); };
auto intro_token = context.ConsumeChecked(Lex::TokenKind::Import);
context.AddLeafNode(NodeKind::ImportIntroducer, intro_token);
switch (context.packaging_state()) {
@@ -121,8 +173,7 @@ auto HandleImport(Context& context) -> void {
[[clang::fallthrough]];
case Context::PackagingState::InImports:
HandleImportAndPackage(context, state, NodeKind::ImportDirective,
/*is_package=*/false);
HandleDirectiveContent(context, state, directive, on_parse_error);
break;
case Context::PackagingState::AfterNonPackagingDecl: {
@@ -137,42 +188,60 @@ auto HandleImport(Context& context) -> void {
.Build(intro_token, ImportTooLate)
.Note(context.first_non_packaging_token(), FirstDecl)
.Emit();
ExitOnParseError(context, state, NodeKind::ImportDirective);
on_parse_error();
break;
}
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.
ExitOnParseError(context, state, NodeKind::ImportDirective);
on_parse_error();
break;
}
}
auto HandlePackage(Context& context) -> void {
// Handles common logic for `package` and `library`.
static auto HandlePackageAndLibraryDirectives(Context& context,
Lex::TokenKind intro_token_kind,
NodeKind intro,
NodeKind directive) -> void {
auto state = context.PopState();
auto intro_token = context.Consume();
context.AddLeafNode(NodeKind::PackageIntroducer, intro_token);
auto on_parse_error = [&] { OnParseError(context, state, directive); };
auto intro_token = context.ConsumeChecked(intro_token_kind);
context.AddLeafNode(intro, intro_token);
if (intro_token != Lex::Token::FirstNonCommentToken) {
CARBON_DIAGNOSTIC(
PackageTooLate, Error,
"The `package` directive must be the first non-comment line.");
CARBON_DIAGNOSTIC(PackageTooLate, Error,
"The `{0}` directive must be the first non-comment line.",
Lex::TokenKind);
CARBON_DIAGNOSTIC(FirstNonCommentLine, Note,
"First non-comment line is here.");
context.emitter()
.Build(intro_token, PackageTooLate)
.Build(intro_token, PackageTooLate, intro_token_kind)
.Note(Lex::Token::FirstNonCommentToken, FirstNonCommentLine)
.Emit();
ExitOnParseError(context, state, NodeKind::PackageDirective);
on_parse_error();
return;
}
// `package` is no longer allowed, but `import` may repeat.
// `package`/`library` is no longer allowed, but `import` may repeat.
context.set_packaging_state(Context::PackagingState::InImports);
HandleImportAndPackage(context, state, NodeKind::PackageDirective,
/*is_package=*/true);
HandleDirectiveContent(context, state, directive, on_parse_error);
}
auto HandlePackage(Context& context) -> void {
HandlePackageAndLibraryDirectives(context, Lex::TokenKind::Package,
NodeKind::PackageIntroducer,
NodeKind::PackageDirective);
}
auto HandleLibrary(Context& context) -> void {
HandlePackageAndLibraryDirectives(context, Lex::TokenKind::Library,
NodeKind::LibraryIntroducer,
NodeKind::LibraryDirective);
}
} // namespace Carbon::Parse