diff options
author | Michael Spencer <bigcheesegs@gmail.com> | 2025-02-26 14:32:50 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-26 14:32:50 -0800 |
commit | 8fb88f568011fb916cda9d7927ac97c6751a8b89 (patch) | |
tree | 4fc0dcd235ecfc4b15dcae6d4f8ea752c7bdba74 /clang/lib/Lex/ModuleMapFile.cpp | |
parent | 39bab1de33333ee3c62b586c4e8d26f8c443bc60 (diff) | |
download | llvm-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.cpp | 1240 |
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); + } +} |