[clang] [llvm] [clang][modules] Separate parsing of modulemaps (PR #119740)

Michael Spencer via llvm-commits llvm-commits at lists.llvm.org
Thu Dec 12 10:36:18 PST 2024


https://github.com/Bigcheese created https://github.com/llvm/llvm-project/pull/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.

>From 12f19b16945b66e75a32cade1f7cb7aac8424b12 Mon Sep 17 00:00:00 2001
From: Michael Spencer <bigcheesegs at gmail.com>
Date: Thu, 5 Dec 2024 14:53:50 -0800
Subject: [PATCH] [clang][modules] Separate parsing of modulemaps

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.
---
 .../include/clang/Basic/DiagnosticLexKinds.td |    2 +
 clang/include/clang/Basic/Module.h            |   24 +
 clang/include/clang/Lex/ModuleMap.h           |   24 +-
 clang/include/clang/Lex/ModuleMapFile.h       |  142 ++
 clang/lib/Lex/CMakeLists.txt                  |    1 +
 clang/lib/Lex/ModuleMap.cpp                   | 1400 +++--------------
 clang/lib/Lex/ModuleMapFile.cpp               | 1248 +++++++++++++++
 .../Modules/Inputs/export_as_test.modulemap   |    4 +
 clang/test/Modules/diagnostics.modulemap      |   21 +-
 clang/test/Modules/export_as_test.c           |    5 +-
 llvm/include/llvm/ADT/STLExtras.h             |    9 +
 11 files changed, 1634 insertions(+), 1246 deletions(-)
 create mode 100644 clang/include/clang/Lex/ModuleMapFile.h
 create mode 100644 clang/lib/Lex/ModuleMapFile.cpp

diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 959376b0847216..e5869619e69d35 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -914,6 +914,8 @@ def warn_mmap_redundant_export_as : Warning<
   InGroup<PrivateModule>;
 def err_mmap_submodule_export_as : Error<
   "only top-level modules can be re-exported as public">;
+def err_mmap_qualified_export_as : Error<
+  "a module can only be re-exported as another top-level module">;
 
 def warn_quoted_include_in_framework_header : Warning<
   "double-quoted include \"%0\" in framework header, "
diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h
index dd384c1d76c5fd..ef6e2ac95d0819 100644
--- a/clang/include/clang/Basic/Module.h
+++ b/clang/include/clang/Basic/Module.h
@@ -100,6 +100,30 @@ struct ASTFileSignature : std::array<uint8_t, 20> {
   }
 };
 
+/// The set of attributes that can be attached to a module.
+struct ModuleAttributes {
+  /// Whether this is a system module.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned IsSystem : 1;
+
+  /// Whether this is an extern "C" module.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned IsExternC : 1;
+
+  /// Whether this is an exhaustive set of configuration macros.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned IsExhaustive : 1;
+
+  /// Whether files in this module can only include non-modular headers
+  /// and headers from used modules.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned NoUndeclaredIncludes : 1;
+
+  ModuleAttributes()
+      : IsSystem(false), IsExternC(false), IsExhaustive(false),
+        NoUndeclaredIncludes(false) {}
+};
+
 /// Required to construct a Module.
 ///
 /// This tag type is only constructible by ModuleMap, guaranteeing it ownership
diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h
index 53e9e0ec83ddb1..9de1b3b546c115 100644
--- a/clang/include/clang/Lex/ModuleMap.h
+++ b/clang/include/clang/Lex/ModuleMap.h
@@ -232,29 +232,7 @@ class ModuleMap {
 
   llvm::DenseMap<Module *, unsigned> ModuleScopeIDs;
 
-  /// The set of attributes that can be attached to a module.
-  struct Attributes {
-    /// Whether this is a system module.
-    LLVM_PREFERRED_TYPE(bool)
-    unsigned IsSystem : 1;
-
-    /// Whether this is an extern "C" module.
-    LLVM_PREFERRED_TYPE(bool)
-    unsigned IsExternC : 1;
-
-    /// Whether this is an exhaustive set of configuration macros.
-    LLVM_PREFERRED_TYPE(bool)
-    unsigned IsExhaustive : 1;
-
-    /// Whether files in this module can only include non-modular headers
-    /// and headers from used modules.
-    LLVM_PREFERRED_TYPE(bool)
-    unsigned NoUndeclaredIncludes : 1;
-
-    Attributes()
-        : IsSystem(false), IsExternC(false), IsExhaustive(false),
-          NoUndeclaredIncludes(false) {}
-  };
+  using Attributes = ModuleAttributes;
 
   /// A directory for which framework modules can be inferred.
   struct InferredDirectory {
diff --git a/clang/include/clang/Lex/ModuleMapFile.h b/clang/include/clang/Lex/ModuleMapFile.h
new file mode 100644
index 00000000000000..8b79897876ad61
--- /dev/null
+++ b/clang/include/clang/Lex/ModuleMapFile.h
@@ -0,0 +1,142 @@
+//===- ModuleMapFile.h - Parsing and representation -------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LEX_MODULEMAPFILE_H
+#define LLVM_CLANG_LEX_MODULEMAPFILE_H
+
+#include "clang/Basic/LLVM.h"
+// TODO: Consider moving ModuleId to another header, parsing a modulemap file is
+//   intended to not depend on anything about the clang::Module class.
+#include "clang/Basic/Module.h"
+#include "clang/Basic/SourceLocation.h"
+#include "llvm/ADT/StringRef.h"
+
+#include <optional>
+#include <variant>
+
+namespace clang {
+
+class DiagnosticsEngine;
+class SourceManager;
+
+namespace modulemap {
+
+using Decl =
+    std::variant<struct RequiresDecl, struct HeaderDecl, struct UmbrellaDirDecl,
+                 struct ModuleDecl, struct ExcludeDecl, struct ExportDecl,
+                 struct ExportAsDecl, struct ExternModuleDecl, struct UseDecl,
+                 struct LinkDecl, struct ConfigMacrosDecl, struct ConflictDecl>;
+
+struct RequiresFeature {
+  SourceLocation Location;
+  StringRef Feature;
+  bool RequiredState = true;
+};
+
+struct RequiresDecl {
+  SourceLocation Location;
+  std::vector<RequiresFeature> Features;
+};
+
+struct HeaderDecl {
+  SourceLocation Location;
+  StringRef Path;
+  SourceLocation PathLoc;
+  std::optional<int64_t> Size;
+  std::optional<int64_t> MTime;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Private : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Textual : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Umbrella : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Excluded : 1;
+};
+
+struct UmbrellaDirDecl {
+  SourceLocation Location;
+  StringRef Path;
+};
+
+struct ModuleDecl {
+  SourceLocation Location; /// Points to the first keyword in the decl.
+  ModuleId Id;
+  ModuleAttributes Attrs;
+  std::vector<Decl> Decls;
+
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Explicit : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Framework : 1;
+};
+
+struct ExcludeDecl {
+  SourceLocation Location;
+  StringRef Module;
+};
+
+struct ExportDecl {
+  SourceLocation Location;
+  ModuleId Id;
+  bool Wildcard;
+};
+
+struct ExportAsDecl {
+  SourceLocation Location;
+  ModuleId Id;
+};
+
+struct ExternModuleDecl {
+  SourceLocation Location;
+  ModuleId Id;
+  StringRef Path;
+};
+
+struct UseDecl {
+  SourceLocation Location;
+  ModuleId Id;
+};
+
+struct LinkDecl {
+  SourceLocation Location;
+  StringRef Library;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Framework : 1;
+};
+
+struct ConfigMacrosDecl {
+  SourceLocation Location;
+  std::vector<StringRef> Macros;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Exhaustive : 1;
+};
+
+struct ConflictDecl {
+  SourceLocation Location;
+  ModuleId Id;
+  StringRef Message;
+};
+
+using TopLevelDecl =
+    std::variant<ModuleDecl, ExternModuleDecl>;
+
+struct ModuleMapFile {
+  std::vector<TopLevelDecl> Decls;
+};
+
+std::optional<ModuleMapFile> parseModuleMap(FileEntryRef File,
+                                            SourceManager &SM,
+                                            DiagnosticsEngine &Diags,
+                                            bool IsSystem, unsigned *Offset);
+void dumpModuleMapFile(ModuleMapFile &MMF, llvm::raw_ostream &out);
+
+} // namespace modulemap
+} // namespace clang
+
+#endif
diff --git a/clang/lib/Lex/CMakeLists.txt b/clang/lib/Lex/CMakeLists.txt
index 766336b89a2382..5a049a1d84fd0d 100644
--- a/clang/lib/Lex/CMakeLists.txt
+++ b/clang/lib/Lex/CMakeLists.txt
@@ -15,6 +15,7 @@ add_clang_library(clangLex
   MacroArgs.cpp
   MacroInfo.cpp
   ModuleMap.cpp
+  ModuleMapFile.cpp
   PPCaching.cpp
   PPCallbacks.cpp
   PPConditionalDirectiveRecord.cpp
diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp
index ccf94f6345ff28..11b38db9eb6526 100644
--- a/clang/lib/Lex/ModuleMap.cpp
+++ b/clang/lib/Lex/ModuleMap.cpp
@@ -24,9 +24,7 @@
 #include "clang/Lex/HeaderSearch.h"
 #include "clang/Lex/HeaderSearchOptions.h"
 #include "clang/Lex/LexDiagnostic.h"
-#include "clang/Lex/Lexer.h"
-#include "clang/Lex/LiteralSupport.h"
-#include "clang/Lex/Token.h"
+#include "clang/Lex/ModuleMapFile.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallPtrSet.h"
@@ -34,7 +32,6 @@
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/StringSwitch.h"
-#include "llvm/Support/Allocator.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/Path.h"
@@ -1461,81 +1458,10 @@ bool ModuleMap::resolveConflicts(Module *Mod, bool Complain) {
 //----------------------------------------------------------------------------//
 
 namespace clang {
-
-  /// A token in a module map file.
-  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);
-    }
-  };
-
   class ModuleMapParser {
-    Lexer &L;
+    modulemap::ModuleMapFile &MMF;
     SourceManager &SourceMgr;
 
-    /// Default target information, used only for string literal
-    /// parsing.
-    const TargetInfo *Target;
-
     DiagnosticsEngine &Diags;
     ModuleMap ⤅
 
@@ -1555,13 +1481,6 @@ namespace clang {
     /// Whether an error occurred.
     bool HadError = false;
 
-    /// Stores string data for the various string literals referenced
-    /// during parsing.
-    llvm::BumpPtrAllocator StringData;
-
-    /// The current token.
-    MMToken Tok;
-
     /// The active module.
     Module *ActiveModule = nullptr;
 
@@ -1575,305 +1494,45 @@ namespace clang {
     /// 'textual' to match the original intent.
     llvm::SmallPtrSet<Module *, 2> UsesRequiresExcludedHack;
 
-    /// Consume the current token and return its location.
-    SourceLocation consumeToken();
-
-    /// Skip tokens until we reach the a token with the given kind
-    /// (or the end of the file).
-    void skipUntil(MMToken::TokenKind K);
-
-    bool parseModuleId(ModuleId &Id);
-    void parseModuleDecl();
-    void parseExternModuleDecl();
-    void parseRequiresDecl();
-    void parseHeaderDecl(MMToken::TokenKind, SourceLocation LeadingLoc);
-    void parseUmbrellaDirDecl(SourceLocation UmbrellaLoc);
-    void parseExportDecl();
-    void parseExportAsDecl();
-    void parseUseDecl();
-    void parseLinkDecl();
-    void parseConfigMacros();
-    void parseConflict();
-    void parseInferredModuleDecl(bool Framework, bool Explicit);
+    void handleModuleDecl(const modulemap::ModuleDecl &MD);
+    void handleExternModuleDecl(const modulemap::ExternModuleDecl &EMD);
+    void handleRequiresDecl(const modulemap::RequiresDecl &RD);
+    void handleHeaderDecl(const modulemap::HeaderDecl &HD);
+    void handleUmbrellaDirDecl(const modulemap::UmbrellaDirDecl &UDD);
+    void handleExportDecl(const modulemap::ExportDecl &ED);
+    void handleExportAsDecl(const modulemap::ExportAsDecl &EAD);
+    void handleUseDecl(const modulemap::UseDecl &UD);
+    void handleLinkDecl(const modulemap::LinkDecl &LD);
+    void handleConfigMacros(const modulemap::ConfigMacrosDecl &CMD);
+    void handleConflict(const modulemap::ConflictDecl &CD);
+    void handleInferredModuleDecl(const modulemap::ModuleDecl &MD);
 
     /// Private modules are canonicalized as Foo_Private. Clang provides extra
     /// module map search logic to find the appropriate private module when PCH
     /// is used with implicit module maps. Warn when private modules are written
     /// in other ways (FooPrivate and Foo.Private), providing notes and fixits.
-    void diagnosePrivateModules(SourceLocation ExplicitLoc,
-                                SourceLocation FrameworkLoc);
+    void diagnosePrivateModules(SourceLocation StartLoc);
 
     using Attributes = ModuleMap::Attributes;
 
-    bool parseOptionalAttributes(Attributes &Attrs);
-
   public:
-    ModuleMapParser(Lexer &L, SourceManager &SourceMgr,
-                    const TargetInfo *Target, DiagnosticsEngine &Diags,
+    ModuleMapParser(modulemap::ModuleMapFile &MMF,
+                    SourceManager &SourceMgr, DiagnosticsEngine &Diags,
                     ModuleMap &Map, FileID ModuleMapFID,
                     DirectoryEntryRef Directory, bool IsSystem)
-        : L(L), SourceMgr(SourceMgr), Target(Target), Diags(Diags), Map(Map),
-          ModuleMapFID(ModuleMapFID), Directory(Directory), IsSystem(IsSystem) {
-      Tok.clear();
-      consumeToken();
-    }
+        : MMF(MMF), SourceMgr(SourceMgr), Diags(Diags), Map(Map),
+          ModuleMapFID(ModuleMapFID), Directory(Directory), IsSystem(IsSystem) {}
 
     bool parseModuleMapFile();
-
-    bool terminatedByDirective() { return false; }
-    SourceLocation getLocation() { return Tok.getLocation(); }
   };
 
 } // namespace clang
 
-SourceLocation ModuleMapParser::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;
-    }
-
-    // Parse the string literal.
-    LangOptions LangOpts;
-    StringLiteralParser StringLiteral(LToken, SourceMgr, LangOpts, *Target);
-    if (StringLiteral.hadError)
-      goto retry;
-
-    // Copy the string literal into our string data allocator.
-    unsigned Length = StringLiteral.GetStringLength();
-    char *Saved = StringData.Allocate<char>(Length + 1);
-    memcpy(Saved, StringLiteral.GetString().data(), Length);
-    Saved[Length] = 0;
-
-    // Form the token.
-    Tok.Kind = MMToken::StringLiteral;
-    Tok.StringData = Saved;
-    Tok.StringLength = Length;
-    break;
-  }
-
-  case tok::numeric_constant: {
-    // We don't support any suffixes or other complications.
-    SmallString<32> SpellingBuffer;
-    SpellingBuffer.resize(LToken.getLength() + 1);
-    const char *Start = SpellingBuffer.data();
-    unsigned Length =
-        Lexer::getSpelling(LToken, Start, SourceMgr, Map.LangOpts);
-    uint64_t Value;
-    if (StringRef(Start, Length).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 ModuleMapParser::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 ModuleMapParser::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;
-}
-
-namespace {
-
-  /// 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
-  };
-
-} // namespace
-
 /// Private modules are canonicalized as Foo_Private. Clang provides extra
 /// module map search logic to find the appropriate private module when PCH
 /// is used with implicit module maps. Warn when private modules are written
 /// in other ways (FooPrivate and Foo.Private), providing notes and fixits.
-void ModuleMapParser::diagnosePrivateModules(SourceLocation ExplicitLoc,
-                                             SourceLocation FrameworkLoc) {
+void ModuleMapParser::diagnosePrivateModules(SourceLocation StartLoc) {
   auto GenNoteAndFixIt = [&](StringRef BadName, StringRef Canonical,
                              const Module *M, SourceRange ReplLoc) {
     auto D = Diags.Report(ActiveModule->DefinitionLoc,
@@ -1902,12 +1561,10 @@ void ModuleMapParser::diagnosePrivateModules(SourceLocation ExplicitLoc,
           << FullName;
 
       SourceLocation FixItInitBegin = CurrModuleDeclLoc;
-      if (FrameworkLoc.isValid())
-        FixItInitBegin = FrameworkLoc;
-      if (ExplicitLoc.isValid())
-        FixItInitBegin = ExplicitLoc;
+      if (StartLoc.isValid())
+        FixItInitBegin = StartLoc;
 
-      if (FrameworkLoc.isValid() || ActiveModule->Parent->IsFramework)
+      if (ActiveModule->Parent->IsFramework)
         FixedPrivModDecl.append("framework ");
       FixedPrivModDecl.append("module ");
       FixedPrivModDecl.append(Canonical);
@@ -1929,103 +1586,28 @@ void ModuleMapParser::diagnosePrivateModules(SourceLocation ExplicitLoc,
   }
 }
 
-/// 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
-void ModuleMapParser::parseModuleDecl() {
-  assert(Tok.is(MMToken::ExplicitKeyword) || Tok.is(MMToken::ModuleKeyword) ||
-         Tok.is(MMToken::FrameworkKeyword) || Tok.is(MMToken::ExternKeyword));
-  if (Tok.is(MMToken::ExternKeyword)) {
-    parseExternModuleDecl();
-    return;
-  }
-
-  // Parse 'explicit' or 'framework' keyword, if present.
-  SourceLocation ExplicitLoc;
-  SourceLocation FrameworkLoc;
-  bool Explicit = false;
-  bool Framework = false;
-
-  // Parse 'explicit' keyword, if present.
-  if (Tok.is(MMToken::ExplicitKeyword)) {
-    ExplicitLoc = consumeToken();
-    Explicit = true;
-  }
-
-  // Parse 'framework' keyword, if present.
-  if (Tok.is(MMToken::FrameworkKeyword)) {
-    FrameworkLoc = consumeToken();
-    Framework = true;
-  }
-
-  // Parse 'module' keyword.
-  if (!Tok.is(MMToken::ModuleKeyword)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
-    consumeToken();
-    HadError = true;
-    return;
-  }
-  CurrModuleDeclLoc = consumeToken(); // 'module' keyword
-
-  // If we have a wildcard for the module name, this is an inferred submodule.
-  // Parse it.
-  if (Tok.is(MMToken::Star))
-    return parseInferredModuleDecl(Framework, Explicit);
+void ModuleMapParser::handleModuleDecl(const modulemap::ModuleDecl &MD) {
+  if (MD.Id.front().first == "*")
+    return handleInferredModuleDecl(MD);
 
-  // Parse the module name.
-  ModuleId Id;
-  if (parseModuleId(Id)) {
-    HadError = true;
-    return;
-  }
-
-  if (ActiveModule) {
-    if (Id.size() > 1) {
-      Diags.Report(Id.front().second, diag::err_mmap_nested_submodule_id)
-        << SourceRange(Id.front().second, Id.back().second);
-
-      HadError = true;
-      return;
-    }
-  } else if (Id.size() == 1 && Explicit) {
-    // Top-level modules can't be explicit.
-    Diags.Report(ExplicitLoc, diag::err_mmap_explicit_top_level);
-    Explicit = false;
-    ExplicitLoc = SourceLocation();
-    HadError = true;
-  }
+  CurrModuleDeclLoc = MD.Location;
 
   Module *PreviousActiveModule = ActiveModule;
-  if (Id.size() > 1) {
+  if (MD.Id.size() > 1) {
     // This module map defines a submodule. Go find the module of which it
     // is a submodule.
     ActiveModule = nullptr;
     const Module *TopLevelModule = nullptr;
-    for (unsigned I = 0, N = Id.size() - 1; I != N; ++I) {
-      if (Module *Next = Map.lookupModuleQualified(Id[I].first, ActiveModule)) {
+    for (unsigned I = 0, N = MD.Id.size() - 1; I != N; ++I) {
+      if (Module *Next = Map.lookupModuleQualified(MD.Id[I].first, ActiveModule)) {
         if (I == 0)
           TopLevelModule = Next;
         ActiveModule = Next;
         continue;
       }
 
-      Diags.Report(Id[I].second, diag::err_mmap_missing_parent_module)
-          << Id[I].first << (ActiveModule != nullptr)
+      Diags.Report(MD.Id[I].second, diag::err_mmap_missing_parent_module)
+          << MD.Id[I].first << (ActiveModule != nullptr)
           << (ActiveModule
                   ? ActiveModule->getTopLevelModule()->getFullModuleName()
                   : "");
@@ -2043,22 +1625,8 @@ void ModuleMapParser::parseModuleDecl() {
     }
   }
 
-  StringRef ModuleName = Id.back().first;
-  SourceLocation ModuleNameLoc = Id.back().second;
-
-  // Parse the optional attribute list.
-  Attributes Attrs;
-  if (parseOptionalAttributes(Attrs))
-    return;
-
-  // Parse the opening brace.
-  if (!Tok.is(MMToken::LBrace)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_lbrace)
-      << ModuleName;
-    HadError = true;
-    return;
-  }
-  SourceLocation LBraceLoc = consumeToken();
+  StringRef ModuleName = MD.Id.back().first;
+  SourceLocation ModuleNameLoc = MD.Id.back().second;
 
   // Determine whether this (sub)module has already been defined.
   Module *ShadowingModule = nullptr;
@@ -2085,7 +1653,7 @@ void ModuleMapParser::parseModuleDecl() {
     //        that \c Existing is part of a framework iff the redefinition of FW
     //        we have just skipped had it too. Once we do that, stop checking
     //        the local framework qualifier and only rely on \c Existing.
-    bool PartOfFramework = Framework || Existing->isPartOfFramework();
+    bool PartOfFramework = MD.Framework || Existing->isPartOfFramework();
     //  - If we're building a (preprocessed) module and we've just loaded the
     //    module map file from which it was created.
     bool ParsedAsMainInput =
@@ -2096,14 +1664,6 @@ void ModuleMapParser::parseModuleDecl() {
     if (LoadedFromASTFile || Inferred || PartOfFramework || ParsedAsMainInput) {
       ActiveModule = PreviousActiveModule;
       // Skip the module definition.
-      skipUntil(MMToken::RBrace);
-      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;
     }
 
@@ -2114,12 +1674,6 @@ void ModuleMapParser::parseModuleDecl() {
       Diags.Report(ModuleNameLoc, diag::err_mmap_module_redefinition)
           << ModuleName;
       Diags.Report(Existing->DefinitionLoc, diag::note_mmap_prev_definition);
-
-      // Skip the module definition.
-      skipUntil(MMToken::RBrace);
-      if (Tok.is(MMToken::RBrace))
-        consumeToken();
-
       HadError = true;
       return;
     }
@@ -2128,18 +1682,18 @@ void ModuleMapParser::parseModuleDecl() {
   // Start defining this module.
   if (ShadowingModule) {
     ActiveModule =
-        Map.createShadowedModule(ModuleName, Framework, ShadowingModule);
+        Map.createShadowedModule(ModuleName, MD.Framework, ShadowingModule);
   } else {
     ActiveModule = Map.findOrCreateModuleFirst(ModuleName, ActiveModule,
-                                               Framework, Explicit);
+                                               MD.Framework, MD.Explicit);
   }
 
   ActiveModule->DefinitionLoc = ModuleNameLoc;
-  if (Attrs.IsSystem || IsSystem)
+  if (MD.Attrs.IsSystem || IsSystem)
     ActiveModule->IsSystem = true;
-  if (Attrs.IsExternC)
+  if (MD.Attrs.IsExternC)
     ActiveModule->IsExternC = true;
-  if (Attrs.NoUndeclaredIncludes)
+  if (MD.Attrs.NoUndeclaredIncludes)
     ActiveModule->NoUndeclaredIncludes = true;
   ActiveModule->Directory = Directory;
 
@@ -2161,89 +1715,34 @@ void ModuleMapParser::parseModuleDecl() {
       !Diags.isIgnored(diag::warn_mmap_mismatched_private_module_name,
                        StartLoc) &&
       ActiveModule->ModuleMapIsPrivate)
-    diagnosePrivateModules(ExplicitLoc, FrameworkLoc);
-
-  bool Done = false;
-  do {
-    switch (Tok.Kind) {
-    case MMToken::EndOfFile:
-    case MMToken::RBrace:
-      Done = true;
-      break;
-
-    case MMToken::ConfigMacros:
-      parseConfigMacros();
-      break;
-
-    case MMToken::Conflict:
-      parseConflict();
-      break;
-
-    case MMToken::ExplicitKeyword:
-    case MMToken::ExternKeyword:
-    case MMToken::FrameworkKeyword:
-    case MMToken::ModuleKeyword:
-      parseModuleDecl();
-      break;
-
-    case MMToken::ExportKeyword:
-      parseExportDecl();
-      break;
-
-    case MMToken::ExportAsKeyword:
-      parseExportAsDecl();
-      break;
-
-    case MMToken::UseKeyword:
-      parseUseDecl();
-      break;
-
-    case MMToken::RequiresKeyword:
-      parseRequiresDecl();
-      break;
-
-    case MMToken::TextualKeyword:
-      parseHeaderDecl(MMToken::TextualKeyword, consumeToken());
-      break;
-
-    case MMToken::UmbrellaKeyword: {
-      SourceLocation UmbrellaLoc = consumeToken();
-      if (Tok.is(MMToken::HeaderKeyword))
-        parseHeaderDecl(MMToken::UmbrellaKeyword, UmbrellaLoc);
-      else
-        parseUmbrellaDirDecl(UmbrellaLoc);
-      break;
-    }
-
-    case MMToken::ExcludeKeyword:
-      parseHeaderDecl(MMToken::ExcludeKeyword, consumeToken());
-      break;
-
-    case MMToken::PrivateKeyword:
-      parseHeaderDecl(MMToken::PrivateKeyword, consumeToken());
-      break;
-
-    case MMToken::HeaderKeyword:
-      parseHeaderDecl(MMToken::HeaderKeyword, consumeToken());
-      break;
-
-    case MMToken::LinkKeyword:
-      parseLinkDecl();
-      break;
-
-    default:
-      Diags.Report(Tok.getLocation(), diag::err_mmap_expected_member);
-      consumeToken();
-      break;
-    }
-  } 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;
+    diagnosePrivateModules(MD.Location);
+
+  for (const modulemap::Decl &Decl : MD.Decls) {
+    std::visit(
+        llvm::overloaded{
+            [&](const modulemap::RequiresDecl &RD) { handleRequiresDecl(RD); },
+            [&](const modulemap::HeaderDecl &HD) { handleHeaderDecl(HD); },
+            [&](const modulemap::UmbrellaDirDecl &UDD) {
+              handleUmbrellaDirDecl(UDD);
+            },
+            [&](const modulemap::ModuleDecl &MD) { handleModuleDecl(MD); },
+            [&](const modulemap::ExportDecl &ED) { handleExportDecl(ED); },
+            [&](const modulemap::ExportAsDecl &EAD) {
+              handleExportAsDecl(EAD);
+            },
+            [&](const modulemap::ExternModuleDecl &EMD) {
+              handleExternModuleDecl(EMD);
+            },
+            [&](const modulemap::UseDecl &UD) { handleUseDecl(UD); },
+            [&](const modulemap::LinkDecl &LD) { handleLinkDecl(LD); },
+            [&](const modulemap::ConfigMacrosDecl &CMD) {
+              handleConfigMacros(CMD);
+            },
+            [&](const modulemap::ConflictDecl &CD) { handleConflict(CD); },
+            [&](const modulemap::ExcludeDecl &ED) {
+              Diags.Report(ED.Location, diag::err_mmap_expected_member);
+            }},
+        Decl);
   }
 
   // If the active module is a top-level framework, and there are no link
@@ -2265,44 +1764,13 @@ void ModuleMapParser::parseModuleDecl() {
   ActiveModule = PreviousActiveModule;
 }
 
-/// Parse an extern module declaration.
-///
-///   extern module-declaration:
-///     'extern' 'module' module-id string-literal
-void ModuleMapParser::parseExternModuleDecl() {
-  assert(Tok.is(MMToken::ExternKeyword));
-  SourceLocation ExternLoc = consumeToken(); // 'extern' keyword
-
-  // Parse 'module' keyword.
-  if (!Tok.is(MMToken::ModuleKeyword)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
-    consumeToken();
-    HadError = true;
-    return;
-  }
-  consumeToken(); // 'module' keyword
-
-  // Parse the module name.
-  ModuleId Id;
-  if (parseModuleId(Id)) {
-    HadError = true;
-    return;
-  }
-
-  // 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::string FileName = std::string(Tok.getString());
-  consumeToken(); // filename
-
-  StringRef FileNameRef = FileName;
+void ModuleMapParser::handleExternModuleDecl(
+    const modulemap::ExternModuleDecl &EMD) {
+  StringRef FileNameRef = EMD.Path;
   SmallString<128> ModuleMapFileName;
   if (llvm::sys::path::is_relative(FileNameRef)) {
     ModuleMapFileName += Directory.getName();
-    llvm::sys::path::append(ModuleMapFileName, FileName);
+    llvm::sys::path::append(ModuleMapFileName, EMD.Path);
     FileNameRef = ModuleMapFileName;
   }
   if (auto File = SourceMgr.getFileManager().getOptionalFileRef(FileNameRef))
@@ -2311,7 +1779,7 @@ void ModuleMapParser::parseExternModuleDecl() {
         Map.HeaderInfo.getHeaderSearchOpts().ModuleMapFileHomeIsCwd
             ? Directory
             : File->getDir(),
-        FileID(), nullptr, ExternLoc);
+        FileID(), nullptr, EMD.Location);
 }
 
 /// Whether to add the requirement \p Feature to the module \p M.
@@ -2343,88 +1811,36 @@ static bool shouldAddRequirement(Module *M, StringRef Feature,
   return true;
 }
 
-/// Parse a requires declaration.
-///
-///   requires-declaration:
-///     'requires' feature-list
-///
-///   feature-list:
-///     feature ',' feature-list
-///     feature
-///
-///   feature:
-///     '!'[opt] identifier
-void ModuleMapParser::parseRequiresDecl() {
-  assert(Tok.is(MMToken::RequiresKeyword));
-
-  // Parse 'requires' keyword.
-  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;
-    }
-
-    // Consume the feature name.
-    std::string Feature = std::string(Tok.getString());
-    consumeToken();
+void ModuleMapParser::handleRequiresDecl(const modulemap::RequiresDecl &RD) {
 
+  for (const modulemap::RequiresFeature &RF : RD.Features) {
     bool IsRequiresExcludedHack = false;
     bool ShouldAddRequirement =
-        shouldAddRequirement(ActiveModule, Feature, IsRequiresExcludedHack);
+        shouldAddRequirement(ActiveModule, RF.Feature, IsRequiresExcludedHack);
 
     if (IsRequiresExcludedHack)
       UsesRequiresExcludedHack.insert(ActiveModule);
 
     if (ShouldAddRequirement) {
       // Add this feature.
-      ActiveModule->addRequirement(Feature, RequiredState, Map.LangOpts,
+      ActiveModule->addRequirement(RF.Feature, RF.RequiredState, Map.LangOpts,
                                    *Map.Target);
     }
-
-    if (!Tok.is(MMToken::Comma))
-      break;
-
-    // Consume the comma.
-    consumeToken();
-  } while (true);
+  }
 }
 
-/// 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
-///
-/// FIXME: Support 'private textual header'.
-void ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken,
-                                      SourceLocation LeadingLoc) {
+void ModuleMapParser::handleHeaderDecl(const modulemap::HeaderDecl &HD) {
   // We've already consumed the first token.
   ModuleMap::ModuleHeaderRole Role = ModuleMap::NormalHeader;
 
-  if (LeadingToken == MMToken::PrivateKeyword) {
+  if (HD.Private) {
     Role = ModuleMap::PrivateHeader;
-    // 'private' may optionally be followed by 'textual'.
-    if (Tok.is(MMToken::TextualKeyword)) {
-      LeadingToken = Tok.Kind;
-      consumeToken();
-    }
-  } else if (LeadingToken == MMToken::ExcludeKeyword) {
+  } else if (HD.Excluded) {
     Role = ModuleMap::ExcludedHeader;
   }
 
-  if (LeadingToken == MMToken::TextualKeyword)
+  if (HD.Textual)
     Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader);
 
   if (UsesRequiresExcludedHack.count(ActiveModule)) {
@@ -2433,28 +1849,10 @@ void ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken,
     Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader);
   }
 
-  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;
-    }
-    consumeToken();
-  }
-
-  // Parse the header name.
-  if (!Tok.is(MMToken::StringLiteral)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header)
-      << "header";
-    HadError = true;
-    return;
-  }
   Module::UnresolvedHeaderDirective Header;
-  Header.FileName = std::string(Tok.getString());
-  Header.FileNameLoc = consumeToken();
-  Header.IsUmbrella = LeadingToken == MMToken::UmbrellaKeyword;
+  Header.FileName = HD.Path;
+  Header.FileNameLoc = HD.PathLoc;
+  Header.IsUmbrella = HD.Umbrella;
   Header.Kind = Map.headerRoleToKind(Role);
 
   // Check whether we already have an umbrella.
@@ -2466,60 +1864,10 @@ void ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken,
     return;
   }
 
-  // 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 (Header.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;
-        }
-        Header.Size = Tok.getInteger();
-        consumeToken();
-        break;
-
-      case ModTime:
-        if (Header.ModTime)
-          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;
-        }
-        Header.ModTime = 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;
-    }
-  }
+  if (HD.Size)
+    Header.Size = HD.Size;
+  if (HD.MTime)
+    Header.ModTime = HD.MTime;
 
   bool NeedsFramework = false;
   // Don't add headers to the builtin modules if the builtin headers belong to
@@ -2541,26 +1889,13 @@ static bool compareModuleHeaders(const Module::Header &A,
   return A.NameAsWritten < B.NameAsWritten;
 }
 
-/// Parse an umbrella directory declaration.
-///
-///   umbrella-dir-declaration:
-///     umbrella string-literal
-void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) {
-  // Parse the directory name.
-  if (!Tok.is(MMToken::StringLiteral)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header)
-      << "umbrella";
-    HadError = true;
-    return;
-  }
-
-  std::string DirName = std::string(Tok.getString());
+void ModuleMapParser::handleUmbrellaDirDecl(const modulemap::UmbrellaDirDecl &UDD) {
+  std::string DirName = std::string(UDD.Path);
   std::string DirNameAsWritten = DirName;
-  SourceLocation DirNameLoc = consumeToken();
 
   // Check whether we already have an umbrella.
   if (!std::holds_alternative<std::monostate>(ActiveModule->Umbrella)) {
-    Diags.Report(DirNameLoc, diag::err_mmap_umbrella_clash)
+    Diags.Report(UDD.Location, diag::err_mmap_umbrella_clash)
       << ActiveModule->getFullModuleName();
     HadError = true;
     return;
@@ -2578,7 +1913,7 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) {
   }
 
   if (!Dir) {
-    Diags.Report(DirNameLoc, diag::warn_mmap_umbrella_dir_not_found)
+    Diags.Report(UDD.Location, diag::warn_mmap_umbrella_dir_not_found)
       << DirName;
     return;
   }
@@ -2609,7 +1944,7 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) {
   }
 
   if (Module *OwningModule = Map.UmbrellaDirs[*Dir]) {
-    Diags.Report(UmbrellaLoc, diag::err_mmap_umbrella_clash)
+    Diags.Report(UDD.Location, diag::err_mmap_umbrella_clash)
       << OwningModule->getFullModuleName();
     HadError = true;
     return;
@@ -2619,520 +1954,170 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) {
   Map.setUmbrellaDirAsWritten(ActiveModule, *Dir, DirNameAsWritten, DirName);
 }
 
-/// Parse a module export declaration.
-///
-///   export-declaration:
-///     'export' wildcard-module-id
-///
-///   wildcard-module-id:
-///     identifier
-///     '*'
-///     identifier '.' wildcard-module-id
-void ModuleMapParser::parseExportDecl() {
-  assert(Tok.is(MMToken::ExportKeyword));
-  SourceLocation ExportLoc = consumeToken();
-
-  // Parse the module-id with an optional wildcard at the end.
-  ModuleId ParsedModuleId;
-  bool Wildcard = false;
-  do {
-    // FIXME: Support string-literal module names here.
-    if (Tok.is(MMToken::Identifier)) {
-      ParsedModuleId.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)) {
-      Wildcard = true;
-      consumeToken();
-      break;
-    }
-
-    Diags.Report(Tok.getLocation(), diag::err_mmap_module_id);
-    HadError = true;
-    return;
-  } while (true);
-
+void ModuleMapParser::handleExportDecl(const modulemap::ExportDecl &ED) {
   Module::UnresolvedExportDecl Unresolved = {
-    ExportLoc, ParsedModuleId, Wildcard
+    ED.Location, ED.Id, ED.Wildcard
   };
   ActiveModule->UnresolvedExports.push_back(Unresolved);
 }
 
-/// Parse a module export_as declaration.
-///
-///   export-as-declaration:
-///     'export_as' identifier
-void ModuleMapParser::parseExportAsDecl() {
-  assert(Tok.is(MMToken::ExportAsKeyword));
-  consumeToken();
-
-  if (!Tok.is(MMToken::Identifier)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_module_id);
-    HadError = true;
-    return;
-  }
-
-  if (ActiveModule->Parent) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_submodule_export_as);
-    consumeToken();
-    return;
-  }
+void ModuleMapParser::handleExportAsDecl(const modulemap::ExportAsDecl &EAD) {
+  auto ModName = EAD.Id.front();
 
   if (!ActiveModule->ExportAsModule.empty()) {
-    if (ActiveModule->ExportAsModule == Tok.getString()) {
-      Diags.Report(Tok.getLocation(), diag::warn_mmap_redundant_export_as)
-        << ActiveModule->Name << Tok.getString();
+    if (ActiveModule->ExportAsModule == ModName.first) {
+      Diags.Report(ModName.second, diag::warn_mmap_redundant_export_as)
+        << ActiveModule->Name << ModName.first;
     } else {
-      Diags.Report(Tok.getLocation(), diag::err_mmap_conflicting_export_as)
+      Diags.Report(ModName.second, diag::err_mmap_conflicting_export_as)
         << ActiveModule->Name << ActiveModule->ExportAsModule
-        << Tok.getString();
+        << ModName.first;
     }
   }
 
-  ActiveModule->ExportAsModule = std::string(Tok.getString());
+  ActiveModule->ExportAsModule = ModName.first;
   Map.addLinkAsDependency(ActiveModule);
-
-  consumeToken();
 }
 
-/// Parse a module use declaration.
-///
-///   use-declaration:
-///     'use' wildcard-module-id
-void ModuleMapParser::parseUseDecl() {
-  assert(Tok.is(MMToken::UseKeyword));
-  auto KWLoc = consumeToken();
-  // Parse the module-id.
-  ModuleId ParsedModuleId;
-  parseModuleId(ParsedModuleId);
-
+void ModuleMapParser::handleUseDecl(const modulemap::UseDecl &UD) {
   if (ActiveModule->Parent)
-    Diags.Report(KWLoc, diag::err_mmap_use_decl_submodule);
+    Diags.Report(UD.Location, diag::err_mmap_use_decl_submodule);
   else
-    ActiveModule->UnresolvedDirectUses.push_back(ParsedModuleId);
+    ActiveModule->UnresolvedDirectUses.push_back(UD.Id);
 }
 
-/// Parse a link declaration.
-///
-///   module-declaration:
-///     'link' 'framework'[opt] string-literal
-void ModuleMapParser::parseLinkDecl() {
-  assert(Tok.is(MMToken::LinkKeyword));
-  SourceLocation LinkLoc = consumeToken();
-
-  // Parse the optional 'framework' keyword.
-  bool IsFramework = false;
-  if (Tok.is(MMToken::FrameworkKeyword)) {
-    consumeToken();
-    IsFramework = true;
-  }
-
-  // Parse the library name
-  if (!Tok.is(MMToken::StringLiteral)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_library_name)
-      << IsFramework << SourceRange(LinkLoc);
-    HadError = true;
-    return;
-  }
-
-  std::string LibraryName = std::string(Tok.getString());
-  consumeToken();
-  ActiveModule->LinkLibraries.push_back(Module::LinkLibrary(LibraryName,
-                                                            IsFramework));
+void ModuleMapParser::handleLinkDecl(const modulemap::LinkDecl &LD) {
+  ActiveModule->LinkLibraries.push_back(
+      Module::LinkLibrary(std::string{LD.Library}, LD.Framework));
 }
 
-/// Parse a configuration macro declaration.
-///
-///   module-declaration:
-///     'config_macros' attributes[opt] config-macro-list?
-///
-///   config-macro-list:
-///     identifier (',' identifier)?
-void ModuleMapParser::parseConfigMacros() {
-  assert(Tok.is(MMToken::ConfigMacros));
-  SourceLocation ConfigMacrosLoc = consumeToken();
-
-  // Only top-level modules can have configuration macros.
+void ModuleMapParser::handleConfigMacros(
+    const modulemap::ConfigMacrosDecl &CMD) {
   if (ActiveModule->Parent) {
-    Diags.Report(ConfigMacrosLoc, diag::err_mmap_config_macro_submodule);
-  }
-
-  // Parse the optional attributes.
-  Attributes Attrs;
-  if (parseOptionalAttributes(Attrs))
-    return;
-
-  if (Attrs.IsExhaustive && !ActiveModule->Parent) {
-    ActiveModule->ConfigMacrosExhaustive = true;
-  }
-
-  // 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))
+    Diags.Report(CMD.Location, diag::err_mmap_config_macro_submodule);
     return;
-
-  // Consume the first identifier.
-  if (!ActiveModule->Parent) {
-    ActiveModule->ConfigMacros.push_back(Tok.getString().str());
   }
-  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);
-      break;
-    }
-
-    // Consume the macro name.
-    if (!ActiveModule->Parent) {
-      ActiveModule->ConfigMacros.push_back(Tok.getString().str());
-    }
-    consumeToken();
-  } while (true);
-}
-
-/// Format a module-id into a string.
-static 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;
-    }
+  // TODO: Is this really the behavior we want for multiple config_macros
+  //       declarations? If any of them are exhaustive then all of them are.
+  if (CMD.Exhaustive) {
+    ActiveModule->ConfigMacrosExhaustive = true;
   }
-
-  return result;
+  ActiveModule->ConfigMacros.insert(ActiveModule->ConfigMacros.end(),
+                                    CMD.Macros.begin(), CMD.Macros.end());
 }
 
-/// Parse a conflict declaration.
-///
-///   module-declaration:
-///     'conflict' module-id ',' string-literal
-void ModuleMapParser::parseConflict() {
-  assert(Tok.is(MMToken::Conflict));
-  SourceLocation ConflictLoc = consumeToken();
+void ModuleMapParser::handleConflict(const modulemap::ConflictDecl &CD) {
   Module::UnresolvedConflict Conflict;
 
-  // Parse the module-id.
-  if (parseModuleId(Conflict.Id))
-    return;
-
-  // Parse the ','.
-  if (!Tok.is(MMToken::Comma)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_comma)
-      << SourceRange(ConflictLoc);
-    return;
-  }
-  consumeToken();
-
-  // Parse the message.
-  if (!Tok.is(MMToken::StringLiteral)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_message)
-      << formatModuleId(Conflict.Id);
-    return;
-  }
-  Conflict.Message = Tok.getString().str();
-  consumeToken();
+  Conflict.Id = CD.Id;
+  Conflict.Message = CD.Message;
 
-  // Add this unresolved conflict.
   ActiveModule->UnresolvedConflicts.push_back(Conflict);
 }
 
-/// Parse an inferred module declaration (wildcard modules).
-///
-///   module-declaration:
-///     'explicit'[opt] 'framework'[opt] 'module' * attributes[opt]
-///       { inferred-module-member* }
-///
-///   inferred-module-member:
-///     'export' '*'
-///     'exclude' identifier
-void ModuleMapParser::parseInferredModuleDecl(bool Framework, bool Explicit) {
-  assert(Tok.is(MMToken::Star));
-  SourceLocation StarLoc = consumeToken();
-  bool Failed = false;
+void ModuleMapParser::handleInferredModuleDecl(const modulemap::ModuleDecl &MD) {
+  SourceLocation StarLoc = MD.Id.front().second;
 
   // Inferred modules must be submodules.
-  if (!ActiveModule && !Framework) {
+  if (!ActiveModule && !MD.Framework) {
     Diags.Report(StarLoc, diag::err_mmap_top_level_inferred_submodule);
-    Failed = true;
+    return;
   }
 
   if (ActiveModule) {
     // Inferred modules must have umbrella directories.
-    if (!Failed && ActiveModule->IsAvailable &&
+    if (ActiveModule->IsAvailable &&
         !ActiveModule->getEffectiveUmbrellaDir()) {
       Diags.Report(StarLoc, diag::err_mmap_inferred_no_umbrella);
-      Failed = true;
+      return;
     }
 
     // Check for redefinition of an inferred module.
-    if (!Failed && ActiveModule->InferSubmodules) {
+    if (ActiveModule->InferSubmodules) {
       Diags.Report(StarLoc, diag::err_mmap_inferred_redef);
       if (ActiveModule->InferredSubmoduleLoc.isValid())
         Diags.Report(ActiveModule->InferredSubmoduleLoc,
                      diag::note_mmap_prev_definition);
-      Failed = true;
+      return;
     }
 
     // Check for the 'framework' keyword, which is not permitted here.
-    if (Framework) {
+    if (MD.Framework) {
       Diags.Report(StarLoc, diag::err_mmap_inferred_framework_submodule);
-      Framework = false;
+      return;
     }
-  } else if (Explicit) {
+  } else if (MD.Explicit) {
     Diags.Report(StarLoc, diag::err_mmap_explicit_inferred_framework);
-    Explicit = false;
-  }
-
-  // If there were any problems with this inferred submodule, skip its body.
-  if (Failed) {
-    if (Tok.is(MMToken::LBrace)) {
-      consumeToken();
-      skipUntil(MMToken::RBrace);
-      if (Tok.is(MMToken::RBrace))
-        consumeToken();
-    }
-    HadError = true;
     return;
   }
 
-  // Parse optional attributes.
-  Attributes Attrs;
-  if (parseOptionalAttributes(Attrs))
-    return;
-
   if (ActiveModule) {
     // Note that we have an inferred submodule.
     ActiveModule->InferSubmodules = true;
     ActiveModule->InferredSubmoduleLoc = StarLoc;
-    ActiveModule->InferExplicitSubmodules = Explicit;
+    ActiveModule->InferExplicitSubmodules = MD.Explicit;
   } else {
     // We'll be inferring framework modules for this directory.
     Map.InferredDirectories[Directory].InferModules = true;
-    Map.InferredDirectories[Directory].Attrs = Attrs;
+    Map.InferredDirectories[Directory].Attrs = MD.Attrs;
     Map.InferredDirectories[Directory].ModuleMapFID = ModuleMapFID;
     // FIXME: Handle the 'framework' keyword.
   }
 
-  // Parse the opening brace.
-  if (!Tok.is(MMToken::LBrace)) {
-    Diags.Report(Tok.getLocation(), diag::err_mmap_expected_lbrace_wildcard);
-    HadError = true;
-    return;
-  }
-  SourceLocation LBraceLoc = consumeToken();
-
-  // Parse the body of the inferred submodule.
-  bool Done = false;
-  do {
-    switch (Tok.Kind) {
-    case MMToken::EndOfFile:
-    case MMToken::RBrace:
-      Done = true;
-      break;
-
-    case MMToken::ExcludeKeyword:
-      if (ActiveModule) {
-        Diags.Report(Tok.getLocation(), diag::err_mmap_expected_inferred_member)
-          << (ActiveModule != nullptr);
-        consumeToken();
-        break;
-      }
-
-      consumeToken();
-      // FIXME: Support string-literal module names here.
-      if (!Tok.is(MMToken::Identifier)) {
-        Diags.Report(Tok.getLocation(), diag::err_mmap_missing_exclude_name);
-        break;
-      }
-
-      Map.InferredDirectories[Directory].ExcludedModules.push_back(
-          std::string(Tok.getString()));
-      consumeToken();
-      break;
-
-    case MMToken::ExportKeyword:
-      if (!ActiveModule) {
-        Diags.Report(Tok.getLocation(), diag::err_mmap_expected_inferred_member)
-          << (ActiveModule != nullptr);
-        consumeToken();
-        break;
-      }
-
-      consumeToken();
-      if (Tok.is(MMToken::Star))
-        ActiveModule->InferExportWildcard = true;
-      else
-        Diags.Report(Tok.getLocation(),
-                     diag::err_mmap_expected_export_wildcard);
-      consumeToken();
-      break;
-
-    case MMToken::ExplicitKeyword:
-    case MMToken::ModuleKeyword:
-    case MMToken::HeaderKeyword:
-    case MMToken::PrivateKeyword:
-    case MMToken::UmbrellaKeyword:
-    default:
-      Diags.Report(Tok.getLocation(), diag::err_mmap_expected_inferred_member)
-          << (ActiveModule != nullptr);
-      consumeToken();
-      break;
-    }
-  } 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;
+  for (const modulemap::Decl &Decl : MD.Decls) {
+    std::visit(
+        llvm::overloaded{
+            [&](const auto &Other) {
+              Diags.Report(Other.Location,
+                           diag::err_mmap_expected_inferred_member)
+                  << (ActiveModule != nullptr);
+            },
+            [&](const modulemap::ExcludeDecl &ED) {
+              // Only inferred frameworks can have exclude decls
+              if (ActiveModule) {
+                Diags.Report(ED.Location,
+                             diag::err_mmap_expected_inferred_member)
+                    << (ActiveModule != nullptr);
+                HadError = true;
+                return;
+              }
+              Map.InferredDirectories[Directory].ExcludedModules.emplace_back(
+                  ED.Module);
+            },
+            [&](const modulemap::ExportDecl &ED) {
+              // Only inferred submodules can have export decls
+              if (!ActiveModule) {
+                Diags.Report(ED.Location,
+                             diag::err_mmap_expected_inferred_member)
+                    << (ActiveModule != nullptr);
+                HadError = true;
+                return;
+              }
+
+              if (ED.Wildcard && ED.Id.size() == 0)
+                ActiveModule->InferExportWildcard = true;
+              else
+                Diags.Report(ED.Id.front().second,
+                             diag::err_mmap_expected_export_wildcard);
+            }},
+        Decl);
   }
 }
 
-/// 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 ModuleMapParser::parseOptionalAttributes(Attributes &Attrs) {
-  bool HadError = 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();
-      HadError = true;
-    }
-
-    // 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);
-      HadError = true;
-    }
-
-    if (Tok.is(MMToken::RSquare))
-      consumeToken();
+bool ModuleMapParser::parseModuleMapFile() {
+  for (const auto &Decl : MMF.Decls) {
+    std::visit(llvm::overloaded{[&](const modulemap::ModuleDecl &MD) {
+                                  handleModuleDecl(MD);
+                                },
+                                [&](const modulemap::ExternModuleDecl &EMD) {
+                                  handleExternModuleDecl(EMD);
+                                }},
+               Decl);
   }
-
   return HadError;
 }
 
-/// Parse a module map file.
-///
-///   module-map-file:
-///     module-declaration*
-bool ModuleMapParser::parseModuleMapFile() {
-  do {
-    switch (Tok.Kind) {
-    case MMToken::EndOfFile:
-      return HadError;
-
-    case MMToken::ExplicitKeyword:
-    case MMToken::ExternKeyword:
-    case MMToken::ModuleKeyword:
-    case MMToken::FrameworkKeyword:
-      parseModuleDecl();
-      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);
-}
-
 bool ModuleMap::parseModuleMapFile(FileEntryRef File, bool IsSystem,
                                    DirectoryEntryRef Dir, FileID ID,
                                    unsigned *Offset,
@@ -3157,25 +2142,18 @@ bool ModuleMap::parseModuleMapFile(FileEntryRef File, bool IsSystem,
   assert((!Offset || *Offset <= Buffer->getBufferSize()) &&
          "invalid buffer offset");
 
-  // Parse this module map file.
-  Lexer L(SourceMgr.getLocForStartOfFile(ID), MMapLangOpts,
-          Buffer->getBufferStart(),
-          Buffer->getBufferStart() + (Offset ? *Offset : 0),
-          Buffer->getBufferEnd());
-  SourceLocation Start = L.getSourceLocation();
-  ModuleMapParser Parser(L, SourceMgr, Target, Diags, *this, ID, Dir, IsSystem);
-  bool Result = Parser.parseModuleMapFile();
-  ParsedModuleMap[File] = Result;
-
-  if (Offset) {
-    auto Loc = SourceMgr.getDecomposedLoc(Parser.getLocation());
-    assert(Loc.first == ID && "stopped in a different file?");
-    *Offset = Loc.second;
+  std::optional<modulemap::ModuleMapFile> MMF =
+      modulemap::parseModuleMap(File, SourceMgr, Diags, IsSystem, Offset);
+  bool Result = false;
+  if (MMF) {
+    ModuleMapParser Parser(*MMF, SourceMgr, Diags, *this, ID, Dir, IsSystem);
+    Result = Parser.parseModuleMapFile();
   }
+  ParsedModuleMap[File] = Result;
 
   // Notify callbacks that we parsed it.
   for (const auto &Cb : Callbacks)
-    Cb->moduleMapFileRead(Start, File, IsSystem);
+    Cb->moduleMapFileRead(SourceLocation(), File, IsSystem);
 
   return Result;
 }
diff --git a/clang/lib/Lex/ModuleMapFile.cpp b/clang/lib/Lex/ModuleMapFile.cpp
new file mode 100644
index 00000000000000..23e6dd80e28973
--- /dev/null
+++ b/clang/lib/Lex/ModuleMapFile.cpp
@@ -0,0 +1,1248 @@
+//===- 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 ModuleMapParser {
+  // External context
+  Lexer &L;
+  DiagnosticsEngine &Diags;
+
+  /// The current module map file.
+  FileEntryRef MMFile;
+
+  /// Parsed representation of the module map file
+  ModuleMapFile MMF{};
+
+  /// Temporarily store string data during parsing
+  llvm::BumpPtrAllocator StringData{};
+
+  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(FileEntryRef File,
+                                                       SourceManager &SM,
+                                                       DiagnosticsEngine &Diags,
+                                                       bool IsSystem,
+                                                       unsigned *Offset) {
+  auto FileCharacter =
+      IsSystem ? SrcMgr::C_System_ModuleMap : SrcMgr::C_User_ModuleMap;
+  FileID ID = SM.getOrCreateFileID(File, FileCharacter);
+  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());
+
+  ModuleMapParser Parser{L, Diags, File};
+  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;
+  return std::move(Parser.MMF);
+}
+
+bool ModuleMapParser::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> ModuleMapParser::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> ModuleMapParser::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> ModuleMapParser::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> ModuleMapParser::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> ModuleMapParser::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> ModuleMapParser::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> ModuleMapParser::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> ModuleMapParser::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>
+ModuleMapParser::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>
+ModuleMapParser::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>
+ModuleMapParser::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> ModuleMapParser::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 ModuleMapParser::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 ModuleMapParser::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 ModuleMapParser::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 ModuleMapParser::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::overloaded{
+                   [&](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 modulemap::dumpModuleMapFile(ModuleMapFile &MMF, llvm::raw_ostream &out) {
+  for (const auto &Decl : MMF.Decls) {
+    std::visit(
+        llvm::overloaded{[&](const ModuleDecl &MD) { dumpModule(MD, out, 0); },
+                         [&](const ExternModuleDecl &EMD) {
+                           dumpExternModule(EMD, out, 0);
+                         }},
+        Decl);
+  }
+}
diff --git a/clang/test/Modules/Inputs/export_as_test.modulemap b/clang/test/Modules/Inputs/export_as_test.modulemap
index 4aaec4194ef5af..bbe60924046e98 100644
--- a/clang/test/Modules/Inputs/export_as_test.modulemap
+++ b/clang/test/Modules/Inputs/export_as_test.modulemap
@@ -7,3 +7,7 @@ module PrivateFoo {
     export_as Wibble
   }
 }
+
+module B {
+  export_as C.B
+}
diff --git a/clang/test/Modules/diagnostics.modulemap b/clang/test/Modules/diagnostics.modulemap
index c12fef50c38ed7..4f893ac897034e 100644
--- a/clang/test/Modules/diagnostics.modulemap
+++ b/clang/test/Modules/diagnostics.modulemap
@@ -1,36 +1,39 @@
 // RUN: rm -rf %t
-// RUN: not %clang_cc1 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/diagnostics-aux.modulemap -fmodule-map-file=%s -fsyntax-only -x c++ /dev/null 2>&1 | FileCheck %s --implicit-check-not error:
+// RUN: not %clang_cc1 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/diagnostics-aux.modulemap -fmodule-map-file=%s -fsyntax-only -x c++ /dev/null 2>&1 | FileCheck %s
 
 // CHECK: In file included from {{.*}}diagnostics-aux.modulemap:3:
 // CHECK: diagnostics-aux-2.modulemap:2:3: error: expected
 
 // PR22299: Ensure we can produce diagnostics for duplicate modules from -fmodule-map-file=.
 //
-// CHECK: diagnostics.modulemap:[[@LINE+2]]:8: error: redefinition of module 'foo'
-// CHECK: diagnostics-aux.modulemap:1:8: note: previously defined here
+// CHECK-DAG: diagnostics.modulemap:[[@LINE+2]]:8: error: redefinition of module 'foo'
+// CHECK-DAG: diagnostics-aux.modulemap:1:8: note: previously defined here
 module foo {}
 
 //* Check that we accept BCPL comments properly, not just as an extension. */
 
 module bad_use {
-  // CHECK: diagnostics.modulemap:[[@LINE+1]]:22: error: use declarations are only allowed in top-level modules
+  // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:22: error: use declarations are only allowed in top-level modules
   module submodule { use foo }
 }
 
 module header_attr {
-  // CHECK: diagnostics.modulemap:[[@LINE+1]]:20: error: expected a header attribute name
+  // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:20: error: expected a header attribute name
   header "foo.h" { x }
-  // CHECK: diagnostics.modulemap:[[@LINE+1]]:27: error: header attribute 'size' specified multiple times
+  // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:27: error: header attribute 'size' specified multiple times
   header "bar.h" { size 1 size 2 }
-  // CHECK: diagnostics.modulemap:[[@LINE+1]]:25: error: expected integer literal as value for header attribute 'size'
+  // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:25: error: expected integer literal as value for header attribute 'size'
   header "baz.h" { size "30 kilobytes" }
 
   header "quux.h" { size 1 mtime 2 }
   header "no_attrs.h" {}
 }
 
-// CHECK: diagnostics.modulemap:[[@LINE+1]]:8: error: no module named 'unknown' found, parent module must be defined before the submodule
+// CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:8: error: no module named 'unknown' found, parent module must be defined before the submodule
 module unknown.submodule {}
 module known_top_level {}
-// CHECK: diagnostics.modulemap:[[@LINE+1]]:24: error: no module named 'unknown' in 'known_top_level', parent module must be defined before the submodule
+// CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:24: error: no module named 'unknown' in 'known_top_level', parent module must be defined before the submodule
 module known_top_level.unknown.submodule {}
+
+// Check that there were no other errors emitted.
+// CHECK: 8 errors generated
diff --git a/clang/test/Modules/export_as_test.c b/clang/test/Modules/export_as_test.c
index a73d6bfc460d6e..a98dcafd4e6692 100644
--- a/clang/test/Modules/export_as_test.c
+++ b/clang/test/Modules/export_as_test.c
@@ -2,8 +2,7 @@
 // RUN: not %clang_cc1 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/export_as_test.modulemap %s 2> %t.err
 // RUN: FileCheck %s < %t.err
 
+// CHECK: export_as_test.modulemap:7:5: error: only top-level modules can be re-exported as public
+// CHECK: export_as_test.modulemap:12:15: error: a module can only be re-exported as another top-level module
 // CHECK: export_as_test.modulemap:3:13: error: conflicting re-export of module 'PrivateFoo' as 'Foo' or 'Bar'
 // CHECK: export_as_test.modulemap:4:13: warning: module 'PrivateFoo' already re-exported as 'Bar'
-// CHECK: export_as_test.modulemap:7:15: error: only top-level modules can be re-exported as public
-
-
diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h
index ace5f60b572d75..1e9765058fea7d 100644
--- a/llvm/include/llvm/ADT/STLExtras.h
+++ b/llvm/include/llvm/ADT/STLExtras.h
@@ -2596,6 +2596,15 @@ template <typename T> using has_sizeof = decltype(sizeof(T));
 template <typename T>
 constexpr bool is_incomplete_v = !is_detected<detail::has_sizeof, T>::value;
 
+//===----------------------------------------------------------------------===//
+//     Extra additions to <variant>
+//===----------------------------------------------------------------------===//
+
+template <class... Ts> struct overloaded : Ts... {
+  using Ts::operator()...;
+};
+template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
+
 } // end namespace llvm
 
 namespace std {



More information about the llvm-commits mailing list