//===- 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 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 parseModuleDecl(bool TopLevel); std::optional parseExternModuleDecl(); std::optional parseConfigMacrosDecl(); std::optional parseConflictDecl(); std::optional parseExportDecl(); std::optional parseExportAsDecl(); std::optional parseUseDecl(); std::optional parseRequiresDecl(); std::optional parseHeaderDecl(MMToken::TokenKind LeadingToken, SourceLocation LeadingLoc); std::optional parseExcludeDecl(clang::SourceLocation LeadingLoc); std::optional parseUmbrellaDirDecl(SourceLocation UmbrellaLoc); std::optional 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 modulemap::parseModuleMap(FileID ID, clang::DirectoryEntryRef Dir, SourceManager &SM, DiagnosticsEngine &Diags, bool IsSystem, unsigned *Offset) { std::optional 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.ID = ID; Parser.MMF.Dir = Dir; Parser.MMF.Start = Start; Parser.MMF.IsSystem = IsSystem; 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 EMD = parseExternModuleDecl(); if (EMD) MMF.Decls.push_back(std::move(*EMD)); break; } case MMToken::ExplicitKeyword: case MMToken::ModuleKeyword: case MMToken::FrameworkKeyword: { std::optional 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 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 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 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 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 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 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 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 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 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 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(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 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 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 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(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(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 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); } }