blob: 5cf4a4c3d96c1feae1dbd1b17bdeedac6047d392 [file] [log] [blame]
//===- 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);
}
}