1 //===- TableGenServer.cpp - TableGen Language Server ----------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "TableGenServer.h" 10 11 #include "../lsp-server-support/Logging.h" 12 #include "../lsp-server-support/Protocol.h" 13 #include "../lsp-server-support/SourceMgrUtils.h" 14 #include "llvm/ADT/IntervalMap.h" 15 #include "llvm/ADT/StringMap.h" 16 #include "llvm/ADT/StringSet.h" 17 #include "llvm/ADT/TypeSwitch.h" 18 #include "llvm/Support/FileSystem.h" 19 #include "llvm/Support/Path.h" 20 #include "llvm/TableGen/Parser.h" 21 #include "llvm/TableGen/Record.h" 22 23 using namespace mlir; 24 25 /// Returns a language server uri for the given source location. `mainFileURI` 26 /// corresponds to the uri for the main file of the source manager. 27 static lsp::URIForFile getURIFromLoc(const llvm::SourceMgr &mgr, SMLoc loc, 28 const lsp::URIForFile &mainFileURI) { 29 int bufferId = mgr.FindBufferContainingLoc(loc); 30 if (bufferId == 0 || bufferId == static_cast<int>(mgr.getMainFileID())) 31 return mainFileURI; 32 llvm::Expected<lsp::URIForFile> fileForLoc = lsp::URIForFile::fromFile( 33 mgr.getBufferInfo(bufferId).Buffer->getBufferIdentifier()); 34 if (fileForLoc) 35 return *fileForLoc; 36 lsp::Logger::error("Failed to create URI for include file: {0}", 37 llvm::toString(fileForLoc.takeError())); 38 return mainFileURI; 39 } 40 41 /// Returns a language server location from the given source range. 42 static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, SMLoc loc, 43 const lsp::URIForFile &uri) { 44 return lsp::Location(getURIFromLoc(mgr, loc, uri), 45 lsp::Range(mgr, lsp::convertTokenLocToRange(loc))); 46 } 47 48 /// Convert the given TableGen diagnostic to the LSP form. 49 static Optional<lsp::Diagnostic> 50 getLspDiagnoticFromDiag(const llvm::SMDiagnostic &diag, 51 const lsp::URIForFile &uri) { 52 auto *sourceMgr = const_cast<llvm::SourceMgr *>(diag.getSourceMgr()); 53 if (!sourceMgr || !diag.getLoc().isValid()) 54 return llvm::None; 55 56 lsp::Diagnostic lspDiag; 57 lspDiag.source = "tablegen"; 58 lspDiag.category = "Parse Error"; 59 60 // Try to grab a file location for this diagnostic. 61 lsp::Location loc = getLocationFromLoc(*sourceMgr, diag.getLoc(), uri); 62 lspDiag.range = loc.range; 63 64 // Skip diagnostics that weren't emitted within the main file. 65 if (loc.uri != uri) 66 return llvm::None; 67 68 // Convert the severity for the diagnostic. 69 switch (diag.getKind()) { 70 case llvm::SourceMgr::DK_Warning: 71 lspDiag.severity = lsp::DiagnosticSeverity::Warning; 72 break; 73 case llvm::SourceMgr::DK_Error: 74 lspDiag.severity = lsp::DiagnosticSeverity::Error; 75 break; 76 case llvm::SourceMgr::DK_Note: 77 // Notes are emitted separately from the main diagnostic, so we just treat 78 // them as remarks given that we can't determine the diagnostic to relate 79 // them to. 80 case llvm::SourceMgr::DK_Remark: 81 lspDiag.severity = lsp::DiagnosticSeverity::Information; 82 break; 83 } 84 lspDiag.message = diag.getMessage().str(); 85 86 return lspDiag; 87 } 88 89 //===----------------------------------------------------------------------===// 90 // TableGenTextFile 91 //===----------------------------------------------------------------------===// 92 93 namespace { 94 /// This class represents a text file containing one or more TableGen documents. 95 class TableGenTextFile { 96 public: 97 TableGenTextFile(const lsp::URIForFile &uri, StringRef fileContents, 98 int64_t version, std::vector<lsp::Diagnostic> &diagnostics); 99 100 /// Return the current version of this text file. 101 int64_t getVersion() const { return version; } 102 103 private: 104 /// The full string contents of the file. 105 std::string contents; 106 107 /// The version of this file. 108 int64_t version; 109 110 /// The include directories for this file. 111 std::vector<std::string> includeDirs; 112 113 /// The source manager containing the contents of the input file. 114 llvm::SourceMgr sourceMgr; 115 116 /// The record keeper containing the parsed tablegen constructs. 117 llvm::RecordKeeper recordKeeper; 118 }; 119 } // namespace 120 121 TableGenTextFile::TableGenTextFile(const lsp::URIForFile &uri, 122 StringRef fileContents, int64_t version, 123 std::vector<lsp::Diagnostic> &diagnostics) 124 : contents(fileContents.str()), version(version) { 125 auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file()); 126 if (!memBuffer) { 127 lsp::Logger::error("Failed to create memory buffer for file", uri.file()); 128 return; 129 } 130 131 // Build the set of include directories for this file. 132 // TODO: Setup external include directories. 133 llvm::SmallString<32> uriDirectory(uri.file()); 134 llvm::sys::path::remove_filename(uriDirectory); 135 includeDirs.push_back(uriDirectory.str().str()); 136 137 sourceMgr.setIncludeDirs(includeDirs); 138 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); 139 140 // This class provides a context argument for the llvm::SourceMgr diagnostic 141 // handler. 142 struct DiagHandlerContext { 143 std::vector<lsp::Diagnostic> &diagnostics; 144 const lsp::URIForFile &uri; 145 } handlerContext{diagnostics, uri}; 146 147 // Set the diagnostic handler for the tablegen source manager. 148 sourceMgr.setDiagHandler( 149 [](const llvm::SMDiagnostic &diag, void *rawHandlerContext) { 150 auto *ctx = reinterpret_cast<DiagHandlerContext *>(rawHandlerContext); 151 if (auto lspDiag = getLspDiagnoticFromDiag(diag, ctx->uri)) 152 ctx->diagnostics.push_back(*lspDiag); 153 }, 154 &handlerContext); 155 if (llvm::TableGenParseFile(sourceMgr, recordKeeper)) 156 return; 157 } 158 159 //===----------------------------------------------------------------------===// 160 // TableGenServer::Impl 161 //===----------------------------------------------------------------------===// 162 163 struct lsp::TableGenServer::Impl { 164 /// The files held by the server, mapped by their URI file name. 165 llvm::StringMap<std::unique_ptr<TableGenTextFile>> files; 166 }; 167 168 //===----------------------------------------------------------------------===// 169 // TableGenServer 170 //===----------------------------------------------------------------------===// 171 172 lsp::TableGenServer::TableGenServer() : impl(std::make_unique<Impl>()) {} 173 lsp::TableGenServer::~TableGenServer() = default; 174 175 void lsp::TableGenServer::addOrUpdateDocument( 176 const URIForFile &uri, StringRef contents, int64_t version, 177 std::vector<Diagnostic> &diagnostics) { 178 impl->files[uri.file()] = 179 std::make_unique<TableGenTextFile>(uri, contents, version, diagnostics); 180 } 181 182 Optional<int64_t> lsp::TableGenServer::removeDocument(const URIForFile &uri) { 183 auto it = impl->files.find(uri.file()); 184 if (it == impl->files.end()) 185 return llvm::None; 186 187 int64_t version = it->second->getVersion(); 188 impl->files.erase(it); 189 return version; 190 } 191