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