aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/Lex/ModuleMapFile.cpp
diff options
context:
space:
mode:
authorMichael Spencer <bigcheesegs@gmail.com>2025-02-26 14:32:50 -0800
committerGitHub <noreply@github.com>2025-02-26 14:32:50 -0800
commit8fb88f568011fb916cda9d7927ac97c6751a8b89 (patch)
tree4fc0dcd235ecfc4b15dcae6d4f8ea752c7bdba74 /clang/lib/Lex/ModuleMapFile.cpp
parent39bab1de33333ee3c62b586c4e8d26f8c443bc60 (diff)
downloadllvm-8fb88f568011fb916cda9d7927ac97c6751a8b89.zip
llvm-8fb88f568011fb916cda9d7927ac97c6751a8b89.tar.gz
llvm-8fb88f568011fb916cda9d7927ac97c6751a8b89.tar.bz2
[clang][modules] Separate parsing of modulemaps (#119740)
This separates out parsing of modulemaps from updating the `clang::ModuleMap` information. Currently this has no effect other than slightly changing diagnostics. Upcoming changes will use this to allow searching for modules without fully processing modulemaps. This creates a new `modulemap` namespace because there are too many things called ModuleMap* right now that mean different things. I'd like to clean this up, but I'm not sure yet what I want to call everything. This also drops the `SourceLocation` from `moduleMapFileRead`. This is never used in tree, and in future patches I plan to make the modulemap parser use a different `SourceManager` so that we can share modulemap parsing between `CompilerInstance`s. This will make the `SourceLocation` meaningless.
Diffstat (limited to 'clang/lib/Lex/ModuleMapFile.cpp')
-rw-r--r--clang/lib/Lex/ModuleMapFile.cpp1240
1 files changed, 1240 insertions, 0 deletions
diff --git a/clang/lib/Lex/ModuleMapFile.cpp b/clang/lib/Lex/ModuleMapFile.cpp
new file mode 100644
index 0000000..5cf4a4c
--- /dev/null
+++ b/clang/lib/Lex/ModuleMapFile.cpp
@@ -0,0 +1,1240 @@
+//===- ModuleMapFile.cpp - ------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file handles parsing of modulemap files into a simple AST.
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/Lex/ModuleMapFile.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/Module.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/LexDiagnostic.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Lex/ModuleMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/Format.h"
+#include <optional>
+
+using namespace clang;
+using namespace modulemap;
+
+namespace {
+struct MMToken {
+ enum TokenKind {
+ Comma,
+ ConfigMacros,
+ Conflict,
+ EndOfFile,
+ HeaderKeyword,
+ Identifier,
+ Exclaim,
+ ExcludeKeyword,
+ ExplicitKeyword,
+ ExportKeyword,
+ ExportAsKeyword,
+ ExternKeyword,
+ FrameworkKeyword,
+ LinkKeyword,
+ ModuleKeyword,
+ Period,
+ PrivateKeyword,
+ UmbrellaKeyword,
+ UseKeyword,
+ RequiresKeyword,
+ Star,
+ StringLiteral,
+ IntegerLiteral,
+ TextualKeyword,
+ LBrace,
+ RBrace,
+ LSquare,
+ RSquare
+ } Kind;
+
+ SourceLocation::UIntTy Location;
+ unsigned StringLength;
+ union {
+ // If Kind != IntegerLiteral.
+ const char *StringData;
+
+ // If Kind == IntegerLiteral.
+ uint64_t IntegerValue;
+ };
+
+ void clear() {
+ Kind = EndOfFile;
+ Location = 0;
+ StringLength = 0;
+ StringData = nullptr;
+ }
+
+ bool is(TokenKind K) const { return Kind == K; }
+
+ SourceLocation getLocation() const {
+ return SourceLocation::getFromRawEncoding(Location);
+ }
+
+ uint64_t getInteger() const {
+ return Kind == IntegerLiteral ? IntegerValue : 0;
+ }
+
+ StringRef getString() const {
+ return Kind == IntegerLiteral ? StringRef()
+ : StringRef(StringData, StringLength);
+ }
+};
+
+struct ModuleMapFileParser {
+ // External context
+ Lexer &L;
+ DiagnosticsEngine &Diags;
+
+ /// Parsed representation of the module map file
+ ModuleMapFile MMF{};
+
+ bool HadError = false;
+
+ /// The current token.
+ MMToken Tok{};
+
+ bool parseTopLevelDecls();
+ std::optional<ModuleDecl> parseModuleDecl(bool TopLevel);
+ std::optional<ExternModuleDecl> parseExternModuleDecl();
+ std::optional<ConfigMacrosDecl> parseConfigMacrosDecl();
+ std::optional<ConflictDecl> parseConflictDecl();
+ std::optional<ExportDecl> parseExportDecl();
+ std::optional<ExportAsDecl> parseExportAsDecl();
+ std::optional<UseDecl> parseUseDecl();
+ std::optional<RequiresDecl> parseRequiresDecl();
+ std::optional<HeaderDecl> parseHeaderDecl(MMToken::TokenKind LeadingToken,
+ SourceLocation LeadingLoc);
+ std::optional<ExcludeDecl> parseExcludeDecl(clang::SourceLocation LeadingLoc);
+ std::optional<UmbrellaDirDecl>
+ parseUmbrellaDirDecl(SourceLocation UmbrellaLoc);
+ std::optional<LinkDecl> parseLinkDecl();
+
+ SourceLocation consumeToken();
+ void skipUntil(MMToken::TokenKind K);
+ bool parseModuleId(ModuleId &Id);
+ bool parseOptionalAttributes(ModuleAttributes &Attrs);
+
+ SourceLocation getLocation() const { return Tok.getLocation(); };
+};
+
+std::string formatModuleId(const ModuleId &Id) {
+ std::string result;
+ {
+ llvm::raw_string_ostream OS(result);
+
+ for (unsigned I = 0, N = Id.size(); I != N; ++I) {
+ if (I)
+ OS << ".";
+ OS << Id[I].first;
+ }
+ }
+
+ return result;
+}
+} // end anonymous namespace
+
+std::optional<ModuleMapFile>
+modulemap::parseModuleMap(FileID ID, clang::DirectoryEntryRef Dir,
+ SourceManager &SM, DiagnosticsEngine &Diags,
+ bool IsSystem, unsigned *Offset) {
+ std::optional<llvm::MemoryBufferRef> Buffer = SM.getBufferOrNone(ID);
+ LangOptions LOpts;
+ LOpts.LangStd = clang::LangStandard::lang_c99;
+ Lexer L(SM.getLocForStartOfFile(ID), LOpts, Buffer->getBufferStart(),
+ Buffer->getBufferStart() + (Offset ? *Offset : 0),
+ Buffer->getBufferEnd());
+ SourceLocation Start = L.getSourceLocation();
+
+ ModuleMapFileParser Parser{L, Diags};
+ bool Failed = Parser.parseTopLevelDecls();
+
+ if (Offset) {
+ auto Loc = SM.getDecomposedLoc(Parser.getLocation());
+ assert(Loc.first == ID && "stopped in a different file?");
+ *Offset = Loc.second;
+ }
+
+ if (Failed)
+ return std::nullopt;
+ Parser.MMF.Start = Start;
+ return std::move(Parser.MMF);
+}
+
+bool ModuleMapFileParser::parseTopLevelDecls() {
+ Tok.clear();
+ consumeToken();
+ do {
+ switch (Tok.Kind) {
+ case MMToken::EndOfFile:
+ return HadError;
+ case MMToken::ExternKeyword: {
+ std::optional<ExternModuleDecl> EMD = parseExternModuleDecl();
+ if (EMD)
+ MMF.Decls.push_back(std::move(*EMD));
+ break;
+ }
+ case MMToken::ExplicitKeyword:
+ case MMToken::ModuleKeyword:
+ case MMToken::FrameworkKeyword: {
+ std::optional<ModuleDecl> MD = parseModuleDecl(true);
+ if (MD)
+ MMF.Decls.push_back(std::move(*MD));
+ break;
+ }
+ case MMToken::Comma:
+ case MMToken::ConfigMacros:
+ case MMToken::Conflict:
+ case MMToken::Exclaim:
+ case MMToken::ExcludeKeyword:
+ case MMToken::ExportKeyword:
+ case MMToken::ExportAsKeyword:
+ case MMToken::HeaderKeyword:
+ case MMToken::Identifier:
+ case MMToken::LBrace:
+ case MMToken::LinkKeyword:
+ case MMToken::LSquare:
+ case MMToken::Period:
+ case MMToken::PrivateKeyword:
+ case MMToken::RBrace:
+ case MMToken::RSquare:
+ case MMToken::RequiresKeyword:
+ case MMToken::Star:
+ case MMToken::StringLiteral:
+ case MMToken::IntegerLiteral:
+ case MMToken::TextualKeyword:
+ case MMToken::UmbrellaKeyword:
+ case MMToken::UseKeyword:
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
+ HadError = true;
+ consumeToken();
+ break;
+ }
+ } while (true);
+}
+
+/// Parse a module declaration.
+///
+/// module-declaration:
+/// 'extern' 'module' module-id string-literal
+/// 'explicit'[opt] 'framework'[opt] 'module' module-id attributes[opt]
+/// { module-member* }
+///
+/// module-member:
+/// requires-declaration
+/// header-declaration
+/// submodule-declaration
+/// export-declaration
+/// export-as-declaration
+/// link-declaration
+///
+/// submodule-declaration:
+/// module-declaration
+/// inferred-submodule-declaration
+std::optional<ModuleDecl> ModuleMapFileParser::parseModuleDecl(bool TopLevel) {
+ assert(Tok.is(MMToken::ExplicitKeyword) || Tok.is(MMToken::ModuleKeyword) ||
+ Tok.is(MMToken::FrameworkKeyword));
+
+ ModuleDecl MDecl;
+
+ SourceLocation ExplicitLoc;
+ MDecl.Explicit = false;
+ MDecl.Framework = false;
+
+ // Parse 'explicit' keyword, if present.
+ if (Tok.is(MMToken::ExplicitKeyword)) {
+ MDecl.Location = ExplicitLoc = consumeToken();
+ MDecl.Explicit = true;
+ }
+
+ // Parse 'framework' keyword, if present.
+ if (Tok.is(MMToken::FrameworkKeyword)) {
+ SourceLocation FrameworkLoc = consumeToken();
+ if (!MDecl.Location.isValid())
+ MDecl.Location = FrameworkLoc;
+ MDecl.Framework = true;
+ }
+
+ // Parse 'module' keyword.
+ if (!Tok.is(MMToken::ModuleKeyword)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
+ consumeToken();
+ HadError = true;
+ return std::nullopt;
+ }
+ SourceLocation ModuleLoc = consumeToken();
+ if (!MDecl.Location.isValid())
+ MDecl.Location = ModuleLoc; // 'module' keyword
+
+ // If we have a wildcard for the module name, this is an inferred submodule.
+ // We treat it as a normal module at this point.
+ if (Tok.is(MMToken::Star)) {
+ SourceLocation StarLoc = consumeToken();
+ MDecl.Id.push_back({"*", StarLoc});
+ if (TopLevel && !MDecl.Framework) {
+ Diags.Report(StarLoc, diag::err_mmap_top_level_inferred_submodule);
+ HadError = true;
+ return std::nullopt;
+ }
+ } else {
+ // Parse the module name.
+ if (parseModuleId(MDecl.Id)) {
+ HadError = true;
+ return std::nullopt;
+ }
+ if (!TopLevel) {
+ if (MDecl.Id.size() > 1) {
+ Diags.Report(MDecl.Id.front().second,
+ diag::err_mmap_nested_submodule_id)
+ << SourceRange(MDecl.Id.front().second, MDecl.Id.back().second);
+
+ HadError = true;
+ }
+ } else if (MDecl.Id.size() == 1 && MDecl.Explicit) {
+ // Top-level modules can't be explicit.
+ Diags.Report(ExplicitLoc, diag::err_mmap_explicit_top_level);
+ MDecl.Explicit = false;
+ HadError = true;
+ }
+ }
+
+ // Parse the optional attribute list.
+ if (parseOptionalAttributes(MDecl.Attrs))
+ return std::nullopt;
+
+ // Parse the opening brace.
+ if (!Tok.is(MMToken::LBrace)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_lbrace)
+ << MDecl.Id.back().first;
+ HadError = true;
+ return std::nullopt;
+ }
+ SourceLocation LBraceLoc = consumeToken();
+
+ bool Done = false;
+ do {
+ std::optional<Decl> SubDecl;
+ switch (Tok.Kind) {
+ case MMToken::EndOfFile:
+ case MMToken::RBrace:
+ Done = true;
+ break;
+
+ case MMToken::ConfigMacros:
+ // Only top-level modules can have configuration macros.
+ if (!TopLevel)
+ Diags.Report(Tok.getLocation(), diag::err_mmap_config_macro_submodule);
+ SubDecl = parseConfigMacrosDecl();
+ break;
+
+ case MMToken::Conflict:
+ SubDecl = parseConflictDecl();
+ break;
+
+ case MMToken::ExternKeyword:
+ SubDecl = parseExternModuleDecl();
+ break;
+
+ case MMToken::ExplicitKeyword:
+ case MMToken::FrameworkKeyword:
+ case MMToken::ModuleKeyword:
+ SubDecl = parseModuleDecl(false);
+ break;
+
+ case MMToken::ExportKeyword:
+ SubDecl = parseExportDecl();
+ break;
+
+ case MMToken::ExportAsKeyword:
+ if (!TopLevel) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_submodule_export_as);
+ parseExportAsDecl();
+ } else
+ SubDecl = parseExportAsDecl();
+ break;
+
+ case MMToken::UseKeyword:
+ SubDecl = parseUseDecl();
+ break;
+
+ case MMToken::RequiresKeyword:
+ SubDecl = parseRequiresDecl();
+ break;
+
+ case MMToken::TextualKeyword:
+ SubDecl = parseHeaderDecl(MMToken::TextualKeyword, consumeToken());
+ break;
+
+ case MMToken::UmbrellaKeyword: {
+ SourceLocation UmbrellaLoc = consumeToken();
+ if (Tok.is(MMToken::HeaderKeyword))
+ SubDecl = parseHeaderDecl(MMToken::UmbrellaKeyword, UmbrellaLoc);
+ else
+ SubDecl = parseUmbrellaDirDecl(UmbrellaLoc);
+ break;
+ }
+
+ case MMToken::ExcludeKeyword: {
+ SourceLocation ExcludeLoc = consumeToken();
+ if (Tok.is(MMToken::HeaderKeyword))
+ SubDecl = parseHeaderDecl(MMToken::ExcludeKeyword, ExcludeLoc);
+ else
+ SubDecl = parseExcludeDecl(ExcludeLoc);
+ break;
+ }
+
+ case MMToken::PrivateKeyword:
+ SubDecl = parseHeaderDecl(MMToken::PrivateKeyword, consumeToken());
+ break;
+
+ case MMToken::HeaderKeyword:
+ SubDecl = parseHeaderDecl(MMToken::HeaderKeyword, consumeToken());
+ break;
+
+ case MMToken::LinkKeyword:
+ SubDecl = parseLinkDecl();
+ break;
+
+ default:
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_member);
+ consumeToken();
+ break;
+ }
+ if (SubDecl)
+ MDecl.Decls.push_back(std::move(*SubDecl));
+ } while (!Done);
+
+ if (Tok.is(MMToken::RBrace))
+ consumeToken();
+ else {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace);
+ Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match);
+ HadError = true;
+ }
+ return std::move(MDecl);
+}
+
+std::optional<ExternModuleDecl> ModuleMapFileParser::parseExternModuleDecl() {
+ assert(Tok.is(MMToken::ExternKeyword));
+ ExternModuleDecl EMD;
+ EMD.Location = consumeToken(); // 'extern' keyword
+
+ // Parse 'module' keyword.
+ if (!Tok.is(MMToken::ModuleKeyword)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
+ consumeToken();
+ HadError = true;
+ return std::nullopt;
+ }
+ consumeToken(); // 'module' keyword
+
+ // Parse the module name.
+ if (parseModuleId(EMD.Id)) {
+ HadError = true;
+ return std::nullopt;
+ }
+
+ // Parse the referenced module map file name.
+ if (!Tok.is(MMToken::StringLiteral)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_mmap_file);
+ HadError = true;
+ return std::nullopt;
+ }
+ EMD.Path = Tok.getString();
+ consumeToken(); // filename
+
+ return std::move(EMD);
+}
+
+/// Parse a configuration macro declaration.
+///
+/// module-declaration:
+/// 'config_macros' attributes[opt] config-macro-list?
+///
+/// config-macro-list:
+/// identifier (',' identifier)?
+std::optional<ConfigMacrosDecl> ModuleMapFileParser::parseConfigMacrosDecl() {
+ assert(Tok.is(MMToken::ConfigMacros));
+ ConfigMacrosDecl CMDecl;
+ CMDecl.Location = consumeToken();
+
+ // Parse the optional attributes.
+ ModuleAttributes Attrs;
+ if (parseOptionalAttributes(Attrs))
+ return std::nullopt;
+
+ CMDecl.Exhaustive = Attrs.IsExhaustive;
+
+ // If we don't have an identifier, we're done.
+ // FIXME: Support macros with the same name as a keyword here.
+ if (!Tok.is(MMToken::Identifier))
+ return std::nullopt;
+
+ // Consume the first identifier.
+ CMDecl.Macros.push_back(Tok.getString());
+ consumeToken();
+
+ do {
+ // If there's a comma, consume it.
+ if (!Tok.is(MMToken::Comma))
+ break;
+ consumeToken();
+
+ // We expect to see a macro name here.
+ // FIXME: Support macros with the same name as a keyword here.
+ if (!Tok.is(MMToken::Identifier)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_config_macro);
+ return std::nullopt;
+ }
+
+ // Consume the macro name.
+ CMDecl.Macros.push_back(Tok.getString());
+ consumeToken();
+ } while (true);
+ return std::move(CMDecl);
+}
+
+/// Parse a conflict declaration.
+///
+/// module-declaration:
+/// 'conflict' module-id ',' string-literal
+std::optional<ConflictDecl> ModuleMapFileParser::parseConflictDecl() {
+ assert(Tok.is(MMToken::Conflict));
+ ConflictDecl CD;
+ CD.Location = consumeToken();
+
+ // Parse the module-id.
+ if (parseModuleId(CD.Id))
+ return std::nullopt;
+
+ // Parse the ','.
+ if (!Tok.is(MMToken::Comma)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_comma)
+ << SourceRange(CD.Location);
+ return std::nullopt;
+ }
+ consumeToken();
+
+ // Parse the message.
+ if (!Tok.is(MMToken::StringLiteral)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_message)
+ << formatModuleId(CD.Id);
+ return std::nullopt;
+ }
+ CD.Message = Tok.getString();
+ consumeToken();
+ return std::move(CD);
+}
+
+/// Parse a module export declaration.
+///
+/// export-declaration:
+/// 'export' wildcard-module-id
+///
+/// wildcard-module-id:
+/// identifier
+/// '*'
+/// identifier '.' wildcard-module-id
+std::optional<ExportDecl> ModuleMapFileParser::parseExportDecl() {
+ assert(Tok.is(MMToken::ExportKeyword));
+ ExportDecl ED;
+ ED.Location = consumeToken();
+
+ // Parse the module-id with an optional wildcard at the end.
+ ED.Wildcard = false;
+ do {
+ // FIXME: Support string-literal module names here.
+ if (Tok.is(MMToken::Identifier)) {
+ ED.Id.push_back(
+ std::make_pair(std::string(Tok.getString()), Tok.getLocation()));
+ consumeToken();
+
+ if (Tok.is(MMToken::Period)) {
+ consumeToken();
+ continue;
+ }
+
+ break;
+ }
+
+ if (Tok.is(MMToken::Star)) {
+ ED.Wildcard = true;
+ consumeToken();
+ break;
+ }
+
+ Diags.Report(Tok.getLocation(), diag::err_mmap_module_id);
+ HadError = true;
+ return std::nullopt;
+ } while (true);
+
+ return std::move(ED);
+}
+
+/// Parse a module export_as declaration.
+///
+/// export-as-declaration:
+/// 'export_as' identifier
+std::optional<ExportAsDecl> ModuleMapFileParser::parseExportAsDecl() {
+ assert(Tok.is(MMToken::ExportAsKeyword));
+ ExportAsDecl EAD;
+ EAD.Location = consumeToken();
+
+ if (!Tok.is(MMToken::Identifier)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_module_id);
+ HadError = true;
+ return std::nullopt;
+ }
+
+ if (parseModuleId(EAD.Id))
+ return std::nullopt;
+ if (EAD.Id.size() > 1)
+ Diags.Report(EAD.Id[1].second, diag::err_mmap_qualified_export_as);
+ return std::move(EAD);
+}
+
+/// Parse a module use declaration.
+///
+/// use-declaration:
+/// 'use' wildcard-module-id
+std::optional<UseDecl> ModuleMapFileParser::parseUseDecl() {
+ assert(Tok.is(MMToken::UseKeyword));
+ UseDecl UD;
+ UD.Location = consumeToken();
+ if (parseModuleId(UD.Id))
+ return std::nullopt;
+ return std::move(UD);
+}
+
+/// Parse a requires declaration.
+///
+/// requires-declaration:
+/// 'requires' feature-list
+///
+/// feature-list:
+/// feature ',' feature-list
+/// feature
+///
+/// feature:
+/// '!'[opt] identifier
+std::optional<RequiresDecl> ModuleMapFileParser::parseRequiresDecl() {
+ assert(Tok.is(MMToken::RequiresKeyword));
+ RequiresDecl RD;
+ RD.Location = consumeToken();
+
+ // Parse the feature-list.
+ do {
+ bool RequiredState = true;
+ if (Tok.is(MMToken::Exclaim)) {
+ RequiredState = false;
+ consumeToken();
+ }
+
+ if (!Tok.is(MMToken::Identifier)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_feature);
+ HadError = true;
+ return std::nullopt;
+ }
+
+ // Consume the feature name.
+ RequiresFeature RF;
+ RF.Feature = Tok.getString();
+ RF.Location = consumeToken();
+ RF.RequiredState = RequiredState;
+
+ RD.Features.push_back(std::move(RF));
+
+ if (!Tok.is(MMToken::Comma))
+ break;
+
+ // Consume the comma.
+ consumeToken();
+ } while (true);
+ return std::move(RD);
+}
+
+/// Parse a header declaration.
+///
+/// header-declaration:
+/// 'textual'[opt] 'header' string-literal
+/// 'private' 'textual'[opt] 'header' string-literal
+/// 'exclude' 'header' string-literal
+/// 'umbrella' 'header' string-literal
+std::optional<HeaderDecl>
+ModuleMapFileParser::parseHeaderDecl(MMToken::TokenKind LeadingToken,
+ clang::SourceLocation LeadingLoc) {
+ HeaderDecl HD;
+ HD.Private = false;
+ HD.Excluded = false;
+ HD.Textual = false;
+ // We've already consumed the first token.
+ HD.Location = LeadingLoc;
+
+ if (LeadingToken == MMToken::PrivateKeyword) {
+ HD.Private = true;
+ // 'private' may optionally be followed by 'textual'.
+ if (Tok.is(MMToken::TextualKeyword)) {
+ HD.Textual = true;
+ LeadingToken = Tok.Kind;
+ consumeToken();
+ }
+ } else if (LeadingToken == MMToken::ExcludeKeyword)
+ HD.Excluded = true;
+ else if (LeadingToken == MMToken::TextualKeyword)
+ HD.Textual = true;
+
+ if (LeadingToken != MMToken::HeaderKeyword) {
+ if (!Tok.is(MMToken::HeaderKeyword)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header)
+ << (LeadingToken == MMToken::PrivateKeyword ? "private"
+ : LeadingToken == MMToken::ExcludeKeyword ? "exclude"
+ : LeadingToken == MMToken::TextualKeyword ? "textual"
+ : "umbrella");
+ return std::nullopt;
+ }
+ consumeToken();
+ }
+
+ // Parse the header name.
+ if (!Tok.is(MMToken::StringLiteral)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) << "header";
+ HadError = true;
+ return std::nullopt;
+ }
+ HD.Path = Tok.getString();
+ HD.PathLoc = consumeToken();
+ HD.Umbrella = LeadingToken == MMToken::UmbrellaKeyword;
+
+ // If we were given stat information, parse it so we can skip looking for
+ // the file.
+ if (Tok.is(MMToken::LBrace)) {
+ SourceLocation LBraceLoc = consumeToken();
+
+ while (!Tok.is(MMToken::RBrace) && !Tok.is(MMToken::EndOfFile)) {
+ enum Attribute { Size, ModTime, Unknown };
+ StringRef Str = Tok.getString();
+ SourceLocation Loc = consumeToken();
+ switch (llvm::StringSwitch<Attribute>(Str)
+ .Case("size", Size)
+ .Case("mtime", ModTime)
+ .Default(Unknown)) {
+ case Size:
+ if (HD.Size)
+ Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str;
+ if (!Tok.is(MMToken::IntegerLiteral)) {
+ Diags.Report(Tok.getLocation(),
+ diag::err_mmap_invalid_header_attribute_value)
+ << Str;
+ skipUntil(MMToken::RBrace);
+ break;
+ }
+ HD.Size = Tok.getInteger();
+ consumeToken();
+ break;
+
+ case ModTime:
+ if (HD.MTime)
+ Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str;
+ if (!Tok.is(MMToken::IntegerLiteral)) {
+ Diags.Report(Tok.getLocation(),
+ diag::err_mmap_invalid_header_attribute_value)
+ << Str;
+ skipUntil(MMToken::RBrace);
+ break;
+ }
+ HD.MTime = Tok.getInteger();
+ consumeToken();
+ break;
+
+ case Unknown:
+ Diags.Report(Loc, diag::err_mmap_expected_header_attribute);
+ skipUntil(MMToken::RBrace);
+ break;
+ }
+ }
+
+ if (Tok.is(MMToken::RBrace))
+ consumeToken();
+ else {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace);
+ Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match);
+ HadError = true;
+ }
+ }
+ return std::move(HD);
+}
+
+/// Parse an exclude declaration.
+///
+/// exclude-declaration:
+/// 'exclude' identifier
+std::optional<ExcludeDecl>
+ModuleMapFileParser::parseExcludeDecl(clang::SourceLocation LeadingLoc) {
+ // FIXME: Support string-literal module names here.
+ if (!Tok.is(MMToken::Identifier)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_missing_exclude_name);
+ HadError = true;
+ return std::nullopt;
+ }
+
+ ExcludeDecl ED;
+ ED.Location = LeadingLoc;
+ ED.Module = Tok.getString();
+ consumeToken();
+ return std::move(ED);
+}
+
+/// Parse an umbrella directory declaration.
+///
+/// umbrella-dir-declaration:
+/// umbrella string-literal
+std::optional<UmbrellaDirDecl>
+ModuleMapFileParser::parseUmbrellaDirDecl(clang::SourceLocation UmbrellaLoc) {
+ UmbrellaDirDecl UDD;
+ UDD.Location = UmbrellaLoc;
+ // Parse the directory name.
+ if (!Tok.is(MMToken::StringLiteral)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header)
+ << "umbrella";
+ HadError = true;
+ return std::nullopt;
+ }
+
+ UDD.Path = Tok.getString();
+ consumeToken();
+ return std::move(UDD);
+}
+
+/// Parse a link declaration.
+///
+/// module-declaration:
+/// 'link' 'framework'[opt] string-literal
+std::optional<LinkDecl> ModuleMapFileParser::parseLinkDecl() {
+ assert(Tok.is(MMToken::LinkKeyword));
+ LinkDecl LD;
+ LD.Location = consumeToken();
+
+ // Parse the optional 'framework' keyword.
+ LD.Framework = false;
+ if (Tok.is(MMToken::FrameworkKeyword)) {
+ consumeToken();
+ LD.Framework = true;
+ }
+
+ // Parse the library name
+ if (!Tok.is(MMToken::StringLiteral)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_library_name)
+ << LD.Framework << SourceRange(LD.Location);
+ HadError = true;
+ return std::nullopt;
+ }
+
+ LD.Library = Tok.getString();
+ consumeToken();
+ return std::move(LD);
+}
+
+SourceLocation ModuleMapFileParser::consumeToken() {
+ SourceLocation Result = Tok.getLocation();
+
+retry:
+ Tok.clear();
+ Token LToken;
+ L.LexFromRawLexer(LToken);
+ Tok.Location = LToken.getLocation().getRawEncoding();
+ switch (LToken.getKind()) {
+ case tok::raw_identifier: {
+ StringRef RI = LToken.getRawIdentifier();
+ Tok.StringData = RI.data();
+ Tok.StringLength = RI.size();
+ Tok.Kind = llvm::StringSwitch<MMToken::TokenKind>(RI)
+ .Case("config_macros", MMToken::ConfigMacros)
+ .Case("conflict", MMToken::Conflict)
+ .Case("exclude", MMToken::ExcludeKeyword)
+ .Case("explicit", MMToken::ExplicitKeyword)
+ .Case("export", MMToken::ExportKeyword)
+ .Case("export_as", MMToken::ExportAsKeyword)
+ .Case("extern", MMToken::ExternKeyword)
+ .Case("framework", MMToken::FrameworkKeyword)
+ .Case("header", MMToken::HeaderKeyword)
+ .Case("link", MMToken::LinkKeyword)
+ .Case("module", MMToken::ModuleKeyword)
+ .Case("private", MMToken::PrivateKeyword)
+ .Case("requires", MMToken::RequiresKeyword)
+ .Case("textual", MMToken::TextualKeyword)
+ .Case("umbrella", MMToken::UmbrellaKeyword)
+ .Case("use", MMToken::UseKeyword)
+ .Default(MMToken::Identifier);
+ break;
+ }
+
+ case tok::comma:
+ Tok.Kind = MMToken::Comma;
+ break;
+
+ case tok::eof:
+ Tok.Kind = MMToken::EndOfFile;
+ break;
+
+ case tok::l_brace:
+ Tok.Kind = MMToken::LBrace;
+ break;
+
+ case tok::l_square:
+ Tok.Kind = MMToken::LSquare;
+ break;
+
+ case tok::period:
+ Tok.Kind = MMToken::Period;
+ break;
+
+ case tok::r_brace:
+ Tok.Kind = MMToken::RBrace;
+ break;
+
+ case tok::r_square:
+ Tok.Kind = MMToken::RSquare;
+ break;
+
+ case tok::star:
+ Tok.Kind = MMToken::Star;
+ break;
+
+ case tok::exclaim:
+ Tok.Kind = MMToken::Exclaim;
+ break;
+
+ case tok::string_literal: {
+ if (LToken.hasUDSuffix()) {
+ Diags.Report(LToken.getLocation(), diag::err_invalid_string_udl);
+ HadError = true;
+ goto retry;
+ }
+
+ // Form the token.
+ Tok.Kind = MMToken::StringLiteral;
+ Tok.StringData = LToken.getLiteralData() + 1;
+ Tok.StringLength = LToken.getLength() - 2;
+ break;
+ }
+
+ case tok::numeric_constant: {
+ // We don't support any suffixes or other complications.
+ uint64_t Value;
+ if (StringRef(LToken.getLiteralData(), LToken.getLength())
+ .getAsInteger(0, Value)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token);
+ HadError = true;
+ goto retry;
+ }
+
+ Tok.Kind = MMToken::IntegerLiteral;
+ Tok.IntegerValue = Value;
+ break;
+ }
+
+ case tok::comment:
+ goto retry;
+
+ case tok::hash:
+ // A module map can be terminated prematurely by
+ // #pragma clang module contents
+ // When building the module, we'll treat the rest of the file as the
+ // contents of the module.
+ {
+ auto NextIsIdent = [&](StringRef Str) -> bool {
+ L.LexFromRawLexer(LToken);
+ return !LToken.isAtStartOfLine() && LToken.is(tok::raw_identifier) &&
+ LToken.getRawIdentifier() == Str;
+ };
+ if (NextIsIdent("pragma") && NextIsIdent("clang") &&
+ NextIsIdent("module") && NextIsIdent("contents")) {
+ Tok.Kind = MMToken::EndOfFile;
+ break;
+ }
+ }
+ [[fallthrough]];
+
+ default:
+ Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token);
+ HadError = true;
+ goto retry;
+ }
+
+ return Result;
+}
+
+void ModuleMapFileParser::skipUntil(MMToken::TokenKind K) {
+ unsigned braceDepth = 0;
+ unsigned squareDepth = 0;
+ do {
+ switch (Tok.Kind) {
+ case MMToken::EndOfFile:
+ return;
+
+ case MMToken::LBrace:
+ if (Tok.is(K) && braceDepth == 0 && squareDepth == 0)
+ return;
+
+ ++braceDepth;
+ break;
+
+ case MMToken::LSquare:
+ if (Tok.is(K) && braceDepth == 0 && squareDepth == 0)
+ return;
+
+ ++squareDepth;
+ break;
+
+ case MMToken::RBrace:
+ if (braceDepth > 0)
+ --braceDepth;
+ else if (Tok.is(K))
+ return;
+ break;
+
+ case MMToken::RSquare:
+ if (squareDepth > 0)
+ --squareDepth;
+ else if (Tok.is(K))
+ return;
+ break;
+
+ default:
+ if (braceDepth == 0 && squareDepth == 0 && Tok.is(K))
+ return;
+ break;
+ }
+
+ consumeToken();
+ } while (true);
+}
+
+/// Parse a module-id.
+///
+/// module-id:
+/// identifier
+/// identifier '.' module-id
+///
+/// \returns true if an error occurred, false otherwise.
+bool ModuleMapFileParser::parseModuleId(ModuleId &Id) {
+ Id.clear();
+ do {
+ if (Tok.is(MMToken::Identifier) || Tok.is(MMToken::StringLiteral)) {
+ Id.push_back(
+ std::make_pair(std::string(Tok.getString()), Tok.getLocation()));
+ consumeToken();
+ } else {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module_name);
+ return true;
+ }
+
+ if (!Tok.is(MMToken::Period))
+ break;
+
+ consumeToken();
+ } while (true);
+
+ return false;
+}
+
+/// Parse optional attributes.
+///
+/// attributes:
+/// attribute attributes
+/// attribute
+///
+/// attribute:
+/// [ identifier ]
+///
+/// \param Attrs Will be filled in with the parsed attributes.
+///
+/// \returns true if an error occurred, false otherwise.
+bool ModuleMapFileParser::parseOptionalAttributes(ModuleAttributes &Attrs) {
+ bool Error = false;
+
+ while (Tok.is(MMToken::LSquare)) {
+ // Consume the '['.
+ SourceLocation LSquareLoc = consumeToken();
+
+ // Check whether we have an attribute name here.
+ if (!Tok.is(MMToken::Identifier)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_attribute);
+ skipUntil(MMToken::RSquare);
+ if (Tok.is(MMToken::RSquare))
+ consumeToken();
+ Error = true;
+ }
+
+ /// Enumerates the known attributes.
+ enum AttributeKind {
+ /// An unknown attribute.
+ AT_unknown,
+
+ /// The 'system' attribute.
+ AT_system,
+
+ /// The 'extern_c' attribute.
+ AT_extern_c,
+
+ /// The 'exhaustive' attribute.
+ AT_exhaustive,
+
+ /// The 'no_undeclared_includes' attribute.
+ AT_no_undeclared_includes
+ };
+
+ // Decode the attribute name.
+ AttributeKind Attribute =
+ llvm::StringSwitch<AttributeKind>(Tok.getString())
+ .Case("exhaustive", AT_exhaustive)
+ .Case("extern_c", AT_extern_c)
+ .Case("no_undeclared_includes", AT_no_undeclared_includes)
+ .Case("system", AT_system)
+ .Default(AT_unknown);
+ switch (Attribute) {
+ case AT_unknown:
+ Diags.Report(Tok.getLocation(), diag::warn_mmap_unknown_attribute)
+ << Tok.getString();
+ break;
+
+ case AT_system:
+ Attrs.IsSystem = true;
+ break;
+
+ case AT_extern_c:
+ Attrs.IsExternC = true;
+ break;
+
+ case AT_exhaustive:
+ Attrs.IsExhaustive = true;
+ break;
+
+ case AT_no_undeclared_includes:
+ Attrs.NoUndeclaredIncludes = true;
+ break;
+ }
+ consumeToken();
+
+ // Consume the ']'.
+ if (!Tok.is(MMToken::RSquare)) {
+ Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rsquare);
+ Diags.Report(LSquareLoc, diag::note_mmap_lsquare_match);
+ skipUntil(MMToken::RSquare);
+ Error = true;
+ }
+
+ if (Tok.is(MMToken::RSquare))
+ consumeToken();
+ }
+
+ if (Error)
+ HadError = true;
+
+ return Error;
+}
+
+static void dumpModule(const ModuleDecl &MD, llvm::raw_ostream &out, int depth);
+
+static void dumpExternModule(const ExternModuleDecl &EMD,
+ llvm::raw_ostream &out, int depth) {
+ out.indent(depth * 2);
+ out << "extern module " << formatModuleId(EMD.Id) << " \"" << EMD.Path
+ << "\"\n";
+}
+
+static void dumpDecls(ArrayRef<Decl> Decls, llvm::raw_ostream &out, int depth) {
+ for (const auto &Decl : Decls) {
+ std::visit(llvm::makeVisitor(
+ [&](const RequiresDecl &RD) {
+ out.indent(depth * 2);
+ out << "requires\n";
+ },
+ [&](const HeaderDecl &HD) {
+ out.indent(depth * 2);
+ if (HD.Private)
+ out << "private ";
+ if (HD.Textual)
+ out << "textual ";
+ if (HD.Excluded)
+ out << "excluded ";
+ if (HD.Umbrella)
+ out << "umbrella ";
+ out << "header \"" << HD.Path << "\"\n";
+ },
+ [&](const UmbrellaDirDecl &UDD) {
+ out.indent(depth * 2);
+ out << "umbrella\n";
+ },
+ [&](const ModuleDecl &MD) { dumpModule(MD, out, depth); },
+ [&](const ExcludeDecl &ED) {
+ out.indent(depth * 2);
+ out << "exclude " << ED.Module << "\n";
+ },
+ [&](const ExportDecl &ED) {
+ out.indent(depth * 2);
+ out << "export "
+ << (ED.Wildcard ? "*" : formatModuleId(ED.Id)) << "\n";
+ },
+ [&](const ExportAsDecl &EAD) {
+ out.indent(depth * 2);
+ out << "export as\n";
+ },
+ [&](const ExternModuleDecl &EMD) {
+ dumpExternModule(EMD, out, depth);
+ },
+ [&](const UseDecl &UD) {
+ out.indent(depth * 2);
+ out << "use\n";
+ },
+ [&](const LinkDecl &LD) {
+ out.indent(depth * 2);
+ out << "link\n";
+ },
+ [&](const ConfigMacrosDecl &CMD) {
+ out.indent(depth * 2);
+ out << "config_macros ";
+ if (CMD.Exhaustive)
+ out << "[exhaustive] ";
+ for (auto Macro : CMD.Macros) {
+ out << Macro << " ";
+ }
+ out << "\n";
+ },
+ [&](const ConflictDecl &CD) {
+ out.indent(depth * 2);
+ out << "conflicts\n";
+ }),
+ Decl);
+ }
+}
+
+static void dumpModule(const ModuleDecl &MD, llvm::raw_ostream &out,
+ int depth) {
+ out.indent(depth * 2);
+ out << "module " << formatModuleId(MD.Id) << "\n";
+ dumpDecls(MD.Decls, out, depth + 1);
+}
+
+void ModuleMapFile::dump(llvm::raw_ostream &out) const {
+ for (const auto &Decl : Decls) {
+ std::visit(
+ llvm::makeVisitor([&](const ModuleDecl &MD) { dumpModule(MD, out, 0); },
+ [&](const ExternModuleDecl &EMD) {
+ dumpExternModule(EMD, out, 0);
+ }),
+ Decl);
+ }
+}