diff --git a/mlir/docs/Tools/MLIRLSP.md b/mlir/docs/Tools/MLIRLSP.md --- a/mlir/docs/Tools/MLIRLSP.md +++ b/mlir/docs/Tools/MLIRLSP.md @@ -176,6 +176,8 @@ * Copy `mlir/utils/textmate/mlir.json` to the extension directory and rename to `grammar.json`. +* Copy `llvm/utils/textmate/tablegen.json` to the extension directory and rename + to `tablegen-grammar.json`. * Copy `https://mlir.llvm.org//LogoAssets/logo/PNG/full_color/mlir-identity-03.png` to the extension directory and rename to `icon.png`. diff --git a/mlir/include/mlir/Tools/tblgen-lsp-server/TableGenLspServerMain.h b/mlir/include/mlir/Tools/tblgen-lsp-server/TableGenLspServerMain.h new file mode 100644 --- /dev/null +++ b/mlir/include/mlir/Tools/tblgen-lsp-server/TableGenLspServerMain.h @@ -0,0 +1,24 @@ +//===- TableGenLSPServerMain.h - TableGen Language Server main --*- 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 +// +//===----------------------------------------------------------------------===// +// +// Main entry function for tblgen-lsp-server when built as standalone binary. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_TOOLS_TBLGENLSPSERVER_TABLEGENLSPSERVERMAIN_H +#define MLIR_TOOLS_TBLGENLSPSERVER_TABLEGENLSPSERVERMAIN_H + +namespace mlir { +struct LogicalResult; + +/// Implementation for tools like `tblgen-lsp-server`. +LogicalResult TableGenLspServerMain(int argc, char **argv); + +} // namespace mlir + +#endif // MLIR_TOOLS_TBLGENLSPSERVER_TABLEGENLSPSERVERMAIN_H diff --git a/mlir/lib/Tools/CMakeLists.txt b/mlir/lib/Tools/CMakeLists.txt --- a/mlir/lib/Tools/CMakeLists.txt +++ b/mlir/lib/Tools/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory(mlir-reduce) add_subdirectory(mlir-translate) add_subdirectory(PDLL) +add_subdirectory(tblgen-lsp-server) diff --git a/mlir/lib/Tools/lsp-server-support/CMakeLists.txt b/mlir/lib/Tools/lsp-server-support/CMakeLists.txt --- a/mlir/lib/Tools/lsp-server-support/CMakeLists.txt +++ b/mlir/lib/Tools/lsp-server-support/CMakeLists.txt @@ -1,6 +1,7 @@ add_mlir_library(MLIRLspServerSupportLib Logging.cpp Protocol.cpp + SourceMgrUtils.cpp Transport.cpp ADDITIONAL_HEADER_DIRS diff --git a/mlir/lib/Tools/lsp-server-support/SourceMgrUtils.h b/mlir/lib/Tools/lsp-server-support/SourceMgrUtils.h new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/lsp-server-support/SourceMgrUtils.h @@ -0,0 +1,31 @@ +//===--- SourceMgrUtils.h - SourceMgr LSP Utils -----------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file contains an array of generally useful SourceMgr utilities for +// interacting with LSP components. +// +//===----------------------------------------------------------------------===// + +#ifndef LIB_MLIR_TOOLS_LSPSERVERSUPPORT_TRANSPORT_H_ +#define LIB_MLIR_TOOLS_LSPSERVERSUPPORT_TRANSPORT_H_ + +#include "Protocol.h" +#include "llvm/Support/SourceMgr.h" + +namespace mlir { +namespace lsp { + +/// Returns the range of a lexical token given a SMLoc corresponding to the +/// start of an token location. The range is computed heuristically, and +/// supports identifier-like tokens, strings, etc. +SMRange convertTokenLocToRange(SMLoc loc); + +} // namespace lsp +} // namespace mlir + +#endif diff --git a/mlir/lib/Tools/lsp-server-support/SourceMgrUtils.cpp b/mlir/lib/Tools/lsp-server-support/SourceMgrUtils.cpp new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/lsp-server-support/SourceMgrUtils.cpp @@ -0,0 +1,61 @@ +//===--- SourceMgrUtils.cpp - SourceMgr LSP Utils -------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "SourceMgrUtils.h" + +using namespace mlir; +using namespace mlir::lsp; + +/// Find the end of a string whose contents start at the given `curPtr`. Returns +/// the position at the end of the string, after a terminal or invalid character +/// (e.g. `"` or `\0`). +static const char *lexLocStringTok(const char *curPtr) { + while (char c = *curPtr++) { + // Check for various terminal characters. + if (StringRef("\"\n\v\f").contains(c)) + return curPtr; + + // Check for escape sequences. + if (c == '\\') { + // Check a few known escapes and \xx hex digits. + if (*curPtr == '"' || *curPtr == '\\' || *curPtr == 'n' || *curPtr == 't') + ++curPtr; + else if (llvm::isHexDigit(*curPtr) && llvm::isHexDigit(curPtr[1])) + curPtr += 2; + else + return curPtr; + } + } + + // If we hit this point, we've reached the end of the buffer. Update the end + // pointer to not point past the buffer. + return curPtr - 1; +} + +SMRange lsp::convertTokenLocToRange(SMLoc loc) { + if (!loc.isValid()) + return SMRange(); + const char *curPtr = loc.getPointer(); + + // Check if this is a string token. + if (*curPtr == '"') { + curPtr = lexLocStringTok(curPtr + 1); + + // Otherwise, default to handling an identifier. + } else { + // Return if the given character is a valid identifier character. + auto isIdentifierChar = [](char c) { + return isalnum(c) || c == '$' || c == '.' || c == '_' || c == '-'; + }; + + while (*curPtr && isIdentifierChar(*(++curPtr))) + continue; + } + + return SMRange(loc, SMLoc::getFromPointer(curPtr)); +} diff --git a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp --- a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp +++ b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp @@ -9,6 +9,7 @@ #include "MLIRServer.h" #include "../lsp-server-support/Logging.h" #include "../lsp-server-support/Protocol.h" +#include "../lsp-server-support/SourceMgrUtils.h" #include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/Operation.h" #include "mlir/Parser/AsmParserState.h" @@ -55,7 +56,7 @@ // Use range of potential identifier starting at location, else length 1 // range. location->range.end.character += 1; - if (Optional range = AsmParserState::convertIdLocToRange(loc)) { + if (Optional range = lsp::convertTokenLocToRange(loc)) { auto lineCol = sourceMgr.getLineAndColumn(range->End); location->range.end.character = std::max(fileLoc.getColumn() + 1, lineCol.second - 1); diff --git a/mlir/lib/Tools/tblgen-lsp-server/CMakeLists.txt b/mlir/lib/Tools/tblgen-lsp-server/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/tblgen-lsp-server/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS + Demangle + Support + TableGen +) + +llvm_add_library(TableGenLspServerLib + LSPServer.cpp + TableGenServer.cpp + TableGenLspServerMain.cpp + + ADDITIONAL_HEADER_DIRS + ${MLIR_MAIN_INCLUDE_DIR}/mlir/Tools/tblgen-lsp-server + + LINK_LIBS PUBLIC + MLIRLspServerSupportLib + MLIRSupport + ) diff --git a/mlir/lib/Tools/tblgen-lsp-server/LSPServer.h b/mlir/lib/Tools/tblgen-lsp-server/LSPServer.h new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/tblgen-lsp-server/LSPServer.h @@ -0,0 +1,29 @@ +//===- LSPServer.h - TableGen LSP Server ------------------------*- 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 LIB_MLIR_TOOLS_TBLGENLSPSERVER_LSPSERVER_H +#define LIB_MLIR_TOOLS_TBLGENLSPSERVER_LSPSERVER_H + +#include + +namespace mlir { +struct LogicalResult; + +namespace lsp { +class JSONTransport; +class TableGenServer; + +/// Run the main loop of the LSP server using the given TableGen server and +/// transport. +LogicalResult runTableGenLSPServer(TableGenServer &server, + JSONTransport &transport); + +} // namespace lsp +} // namespace mlir + +#endif // LIB_MLIR_TOOLS_TBLGENLSPSERVER_LSPSERVER_H diff --git a/mlir/lib/Tools/tblgen-lsp-server/LSPServer.cpp b/mlir/lib/Tools/tblgen-lsp-server/LSPServer.cpp new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/tblgen-lsp-server/LSPServer.cpp @@ -0,0 +1,163 @@ +//===- LSPServer.cpp - TableGen Language Server ---------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "LSPServer.h" + +#include "../lsp-server-support/Logging.h" +#include "../lsp-server-support/Protocol.h" +#include "../lsp-server-support/Transport.h" +#include "TableGenServer.h" +#include "llvm/ADT/FunctionExtras.h" +#include "llvm/ADT/StringMap.h" + +using namespace mlir; +using namespace mlir::lsp; + +//===----------------------------------------------------------------------===// +// LSPServer +//===----------------------------------------------------------------------===// + +namespace { +struct LSPServer { + LSPServer(TableGenServer &server, JSONTransport &transport) + : server(server), transport(transport) {} + + //===--------------------------------------------------------------------===// + // Initialization + + void onInitialize(const InitializeParams ¶ms, + Callback reply); + void onInitialized(const InitializedParams ¶ms); + void onShutdown(const NoParams ¶ms, Callback reply); + + //===--------------------------------------------------------------------===// + // Document Change + + void onDocumentDidOpen(const DidOpenTextDocumentParams ¶ms); + void onDocumentDidClose(const DidCloseTextDocumentParams ¶ms); + void onDocumentDidChange(const DidChangeTextDocumentParams ¶ms); + + //===--------------------------------------------------------------------===// + // Fields + //===--------------------------------------------------------------------===// + + TableGenServer &server; + JSONTransport &transport; + + /// An outgoing notification used to send diagnostics to the client when they + /// are ready to be processed. + OutgoingNotification publishDiagnostics; + + /// Used to indicate that the 'shutdown' request was received from the + /// Language Server client. + bool shutdownRequestReceived = false; +}; +} // namespace + +//===----------------------------------------------------------------------===// +// Initialization + +void LSPServer::onInitialize(const InitializeParams ¶ms, + Callback reply) { + // Send a response with the capabilities of this server. + llvm::json::Object serverCaps{ + {"textDocumentSync", + llvm::json::Object{ + {"openClose", true}, + {"change", (int)TextDocumentSyncKind::Full}, + {"save", true}, + }}, + }; + + llvm::json::Object result{ + {{"serverInfo", llvm::json::Object{{"name", "tblgen-lsp-server"}, + {"version", "0.0.1"}}}, + {"capabilities", std::move(serverCaps)}}}; + reply(std::move(result)); +} +void LSPServer::onInitialized(const InitializedParams &) {} +void LSPServer::onShutdown(const NoParams &, Callback reply) { + shutdownRequestReceived = true; + reply(nullptr); +} + +//===----------------------------------------------------------------------===// +// Document Change + +void LSPServer::onDocumentDidOpen(const DidOpenTextDocumentParams ¶ms) { + PublishDiagnosticsParams diagParams(params.textDocument.uri, + params.textDocument.version); + server.addOrUpdateDocument(params.textDocument.uri, params.textDocument.text, + params.textDocument.version, + diagParams.diagnostics); + + // Publish any recorded diagnostics. + publishDiagnostics(diagParams); +} +void LSPServer::onDocumentDidClose(const DidCloseTextDocumentParams ¶ms) { + Optional version = server.removeDocument(params.textDocument.uri); + if (!version) + return; + + // Empty out the diagnostics shown for this document. This will clear out + // anything currently displayed by the client for this document (e.g. in the + // "Problems" pane of VSCode). + publishDiagnostics( + PublishDiagnosticsParams(params.textDocument.uri, *version)); +} +void LSPServer::onDocumentDidChange(const DidChangeTextDocumentParams ¶ms) { + // TODO: We currently only support full document updates, we should refactor + // to avoid this. + if (params.contentChanges.size() != 1) + return; + PublishDiagnosticsParams diagParams(params.textDocument.uri, + params.textDocument.version); + server.addOrUpdateDocument( + params.textDocument.uri, params.contentChanges.front().text, + params.textDocument.version, diagParams.diagnostics); + + // Publish any recorded diagnostics. + publishDiagnostics(diagParams); +} + +//===----------------------------------------------------------------------===// +// Entry Point +//===----------------------------------------------------------------------===// + +LogicalResult mlir::lsp::runTableGenLSPServer(TableGenServer &server, + JSONTransport &transport) { + LSPServer lspServer(server, transport); + MessageHandler messageHandler(transport); + + // Initialization + messageHandler.method("initialize", &lspServer, &LSPServer::onInitialize); + messageHandler.notification("initialized", &lspServer, + &LSPServer::onInitialized); + messageHandler.method("shutdown", &lspServer, &LSPServer::onShutdown); + + // Document Changes + messageHandler.notification("textDocument/didOpen", &lspServer, + &LSPServer::onDocumentDidOpen); + messageHandler.notification("textDocument/didClose", &lspServer, + &LSPServer::onDocumentDidClose); + messageHandler.notification("textDocument/didChange", &lspServer, + &LSPServer::onDocumentDidChange); + + // Diagnostics + lspServer.publishDiagnostics = + messageHandler.outgoingNotification( + "textDocument/publishDiagnostics"); + + // Run the main loop of the transport. + if (llvm::Error error = transport.run(messageHandler)) { + Logger::error("Transport error: {0}", error); + llvm::consumeError(std::move(error)); + return failure(); + } + return success(lspServer.shutdownRequestReceived); +} diff --git a/mlir/lib/Tools/tblgen-lsp-server/TableGenLspServerMain.cpp b/mlir/lib/Tools/tblgen-lsp-server/TableGenLspServerMain.cpp new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/tblgen-lsp-server/TableGenLspServerMain.cpp @@ -0,0 +1,73 @@ +//===- TableGenLspServerMain.cpp - TableGen Language Server main ----------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "mlir/Tools/tblgen-lsp-server/TableGenLspServerMain.h" +#include "../lsp-server-support/Logging.h" +#include "../lsp-server-support/Transport.h" +#include "LSPServer.h" +#include "TableGenServer.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Program.h" + +using namespace mlir; +using namespace mlir::lsp; + +LogicalResult mlir::TableGenLspServerMain(int argc, char **argv) { + llvm::cl::opt inputStyle{ + "input-style", + llvm::cl::desc("Input JSON stream encoding"), + llvm::cl::values(clEnumValN(JSONStreamStyle::Standard, "standard", + "usual LSP protocol"), + clEnumValN(JSONStreamStyle::Delimited, "delimited", + "messages delimited by `// -----` lines, " + "with // comment support")), + llvm::cl::init(JSONStreamStyle::Standard), + llvm::cl::Hidden, + }; + llvm::cl::opt litTest{ + "lit-test", + llvm::cl::desc( + "Abbreviation for -input-style=delimited -pretty -log=verbose. " + "Intended to simplify lit tests"), + llvm::cl::init(false), + }; + llvm::cl::opt logLevel{ + "log", + llvm::cl::desc("Verbosity of log messages written to stderr"), + llvm::cl::values( + clEnumValN(Logger::Level::Error, "error", "Error messages only"), + clEnumValN(Logger::Level::Info, "info", + "High level execution tracing"), + clEnumValN(Logger::Level::Debug, "verbose", "Low level details")), + llvm::cl::init(Logger::Level::Info), + }; + llvm::cl::opt prettyPrint{ + "pretty", + llvm::cl::desc("Pretty-print JSON output"), + llvm::cl::init(false), + }; + + llvm::cl::ParseCommandLineOptions(argc, argv, "TableGen LSP Language Server"); + + if (litTest) { + inputStyle = JSONStreamStyle::Delimited; + logLevel = Logger::Level::Debug; + prettyPrint = true; + } + + // Configure the logger. + Logger::setLogLevel(logLevel); + + // Configure the transport used for communication. + llvm::sys::ChangeStdinToBinary(); + JSONTransport transport(stdin, llvm::outs(), inputStyle, prettyPrint); + + // Configure the servers and start the main language server. + TableGenServer server; + return runTableGenLSPServer(server, transport); +} diff --git a/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.h b/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.h new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.h @@ -0,0 +1,50 @@ +//===- TableGenServer.h - TableGen Language Server --------------*- 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 LIB_MLIR_TOOLS_TBLGENLSPSERVER_TABLEGENSERVER_H_ +#define LIB_MLIR_TOOLS_TBLGENLSPSERVER_TABLEGENSERVER_H_ + +#include "mlir/Support/LLVM.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace mlir { +namespace lsp { +struct Diagnostic; +class URIForFile; + +/// This class implements all of the TableGen related functionality necessary +/// for a language server. This class allows for keeping the TableGen specific +/// logic separate from the logic that involves LSP server/client communication. +class TableGenServer { +public: + TableGenServer(); + ~TableGenServer(); + + /// Add or update the document, with the provided `version`, at the given URI. + /// Any diagnostics emitted for this document should be added to + /// `diagnostics`. + void addOrUpdateDocument(const URIForFile &uri, StringRef contents, + int64_t version, + std::vector &diagnostics); + + /// Remove the document with the given uri. Returns the version of the removed + /// document, or None if the uri did not have a corresponding document within + /// the server. + Optional removeDocument(const URIForFile &uri); + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace lsp +} // namespace mlir + +#endif // LIB_MLIR_TOOLS_TBLGENLSPSERVER_TABLEGENSERVER_H_ diff --git a/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp b/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp new file mode 100644 --- /dev/null +++ b/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp @@ -0,0 +1,190 @@ +//===- TableGenServer.cpp - TableGen Language Server ----------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "TableGenServer.h" + +#include "../lsp-server-support/Logging.h" +#include "../lsp-server-support/Protocol.h" +#include "../lsp-server-support/SourceMgrUtils.h" +#include "llvm/ADT/IntervalMap.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/TableGen/Parser.h" +#include "llvm/TableGen/Record.h" + +using namespace mlir; + +/// Returns a language server uri for the given source location. `mainFileURI` +/// corresponds to the uri for the main file of the source manager. +static lsp::URIForFile getURIFromLoc(const llvm::SourceMgr &mgr, SMLoc loc, + const lsp::URIForFile &mainFileURI) { + int bufferId = mgr.FindBufferContainingLoc(loc); + if (bufferId == 0 || bufferId == static_cast(mgr.getMainFileID())) + return mainFileURI; + llvm::Expected fileForLoc = lsp::URIForFile::fromFile( + mgr.getBufferInfo(bufferId).Buffer->getBufferIdentifier()); + if (fileForLoc) + return *fileForLoc; + lsp::Logger::error("Failed to create URI for include file: {0}", + llvm::toString(fileForLoc.takeError())); + return mainFileURI; +} + +/// Returns a language server location from the given source range. +static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, SMLoc loc, + const lsp::URIForFile &uri) { + return lsp::Location(getURIFromLoc(mgr, loc, uri), + lsp::Range(mgr, lsp::convertTokenLocToRange(loc))); +} + +/// Convert the given TableGen diagnostic to the LSP form. +static Optional +getLspDiagnoticFromDiag(const llvm::SMDiagnostic &diag, + const lsp::URIForFile &uri) { + auto *sourceMgr = const_cast(diag.getSourceMgr()); + if (!sourceMgr || !diag.getLoc().isValid()) + return llvm::None; + + lsp::Diagnostic lspDiag; + lspDiag.source = "tablegen"; + lspDiag.category = "Parse Error"; + + // Try to grab a file location for this diagnostic. + lsp::Location loc = getLocationFromLoc(*sourceMgr, diag.getLoc(), uri); + lspDiag.range = loc.range; + + // Skip diagnostics that weren't emitted within the main file. + if (loc.uri != uri) + return llvm::None; + + // Convert the severity for the diagnostic. + switch (diag.getKind()) { + case llvm::SourceMgr::DK_Warning: + lspDiag.severity = lsp::DiagnosticSeverity::Warning; + break; + case llvm::SourceMgr::DK_Error: + lspDiag.severity = lsp::DiagnosticSeverity::Error; + break; + case llvm::SourceMgr::DK_Note: + // Notes are emitted separately from the main diagnostic, so we just treat + // them as remarks given that we can't determine the diagnostic to relate + // them to. + case llvm::SourceMgr::DK_Remark: + lspDiag.severity = lsp::DiagnosticSeverity::Information; + break; + } + lspDiag.message = diag.getMessage().str(); + + return lspDiag; +} + +//===----------------------------------------------------------------------===// +// TableGenTextFile +//===----------------------------------------------------------------------===// + +namespace { +/// This class represents a text file containing one or more TableGen documents. +class TableGenTextFile { +public: + TableGenTextFile(const lsp::URIForFile &uri, StringRef fileContents, + int64_t version, std::vector &diagnostics); + + /// Return the current version of this text file. + int64_t getVersion() const { return version; } + +private: + /// The full string contents of the file. + std::string contents; + + /// The version of this file. + int64_t version; + + /// The include directories for this file. + std::vector includeDirs; + + /// The source manager containing the contents of the input file. + llvm::SourceMgr sourceMgr; + + /// The record keeper containing the parsed tablegen constructs. + llvm::RecordKeeper recordKeeper; +}; +} // namespace + +TableGenTextFile::TableGenTextFile(const lsp::URIForFile &uri, + StringRef fileContents, int64_t version, + std::vector &diagnostics) + : contents(fileContents.str()), version(version) { + auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file()); + if (!memBuffer) { + lsp::Logger::error("Failed to create memory buffer for file", uri.file()); + return; + } + + // Build the set of include directories for this file. + // TODO: Setup external include directories. + llvm::SmallString<32> uriDirectory(uri.file()); + llvm::sys::path::remove_filename(uriDirectory); + includeDirs.push_back(uriDirectory.str().str()); + + sourceMgr.setIncludeDirs(includeDirs); + sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); + + // This class provides a context argument for the llvm::SourceMgr diagnostic + // handler. + struct DiagHandlerContext { + std::vector &diagnostics; + const lsp::URIForFile &uri; + } handlerContext{diagnostics, uri}; + + // Set the diagnostic handler for the tablegen source manager. + sourceMgr.setDiagHandler( + [](const llvm::SMDiagnostic &diag, void *rawHandlerContext) { + auto *ctx = reinterpret_cast(rawHandlerContext); + if (auto lspDiag = getLspDiagnoticFromDiag(diag, ctx->uri)) + ctx->diagnostics.push_back(*lspDiag); + }, + &handlerContext); + if (llvm::TableGenParseFile(sourceMgr, recordKeeper)) + return; +} + +//===----------------------------------------------------------------------===// +// TableGenServer::Impl +//===----------------------------------------------------------------------===// + +struct lsp::TableGenServer::Impl { + /// The files held by the server, mapped by their URI file name. + llvm::StringMap> files; +}; + +//===----------------------------------------------------------------------===// +// TableGenServer +//===----------------------------------------------------------------------===// + +lsp::TableGenServer::TableGenServer() : impl(std::make_unique()) {} +lsp::TableGenServer::~TableGenServer() = default; + +void lsp::TableGenServer::addOrUpdateDocument( + const URIForFile &uri, StringRef contents, int64_t version, + std::vector &diagnostics) { + impl->files[uri.file()] = + std::make_unique(uri, contents, version, diagnostics); +} + +Optional lsp::TableGenServer::removeDocument(const URIForFile &uri) { + auto it = impl->files.find(uri.file()); + if (it == impl->files.end()) + return llvm::None; + + int64_t version = it->second->getVersion(); + impl->files.erase(it); + return version; +} diff --git a/mlir/test/CMakeLists.txt b/mlir/test/CMakeLists.txt --- a/mlir/test/CMakeLists.txt +++ b/mlir/test/CMakeLists.txt @@ -97,6 +97,7 @@ mlir-reduce mlir-tblgen mlir-translate + tblgen-lsp-server ) # The native target may not be enabled, in this case we won't diff --git a/mlir/test/tblgen-lsp-server/diagnostics.test b/mlir/test/tblgen-lsp-server/diagnostics.test new file mode 100644 --- /dev/null +++ b/mlir/test/tblgen-lsp-server/diagnostics.test @@ -0,0 +1,36 @@ +// RUN: tblgen-lsp-server -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"tablegen","capabilities":{},"trace":"off"}} +// ----- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{ + "uri":"test:///foo.td", + "languageId":"tablegen", + "version":1, + "text":"class Foo<>;" +}}} +// CHECK: "method": "textDocument/publishDiagnostics", +// CHECK-NEXT: "params": { +// CHECK-NEXT: "diagnostics": [ +// CHECK-NEXT: { +// CHECK-NEXT: "category": "Parse Error", +// CHECK-NEXT: "message": "Unknown token when expecting a type", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "end": { +// CHECK-NEXT: "character": 11, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: }, +// CHECK-NEXT: "start": { +// CHECK-NEXT: "character": 10, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "severity": 1, +// CHECK-NEXT: "source": "tablegen" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "uri": "test:///foo.td", +// CHECK-NEXT: "version": 1 +// CHECK-NEXT: } +// ----- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +// ----- +{"jsonrpc":"2.0","method":"exit"} diff --git a/mlir/test/tblgen-lsp-server/exit-eof.test b/mlir/test/tblgen-lsp-server/exit-eof.test new file mode 100644 --- /dev/null +++ b/mlir/test/tblgen-lsp-server/exit-eof.test @@ -0,0 +1,7 @@ +// RUN: not tblgen-lsp-server < %s 2> %t.err +// RUN: FileCheck %s < %t.err +// +// No LSP messages here, just let tblgen-lsp-server see the end-of-file +// CHECK: Transport error: +// (Typically "Transport error: Input/output error" but platform-dependent). + diff --git a/mlir/test/tblgen-lsp-server/exit-with-shutdown.test b/mlir/test/tblgen-lsp-server/exit-with-shutdown.test new file mode 100644 --- /dev/null +++ b/mlir/test/tblgen-lsp-server/exit-with-shutdown.test @@ -0,0 +1,6 @@ +// RUN: tblgen-lsp-server -lit-test < %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"tablegen","capabilities":{},"trace":"off"}} +// ----- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +// ----- +{"jsonrpc":"2.0","method":"exit"} diff --git a/mlir/test/tblgen-lsp-server/exit-without-shutdown.test b/mlir/test/tblgen-lsp-server/exit-without-shutdown.test new file mode 100644 --- /dev/null +++ b/mlir/test/tblgen-lsp-server/exit-without-shutdown.test @@ -0,0 +1,4 @@ +// RUN: not tblgen-lsp-server -lit-test < %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"tablegen","capabilities":{},"trace":"off"}} +// ----- +{"jsonrpc":"2.0","method":"exit"} diff --git a/mlir/test/tblgen-lsp-server/initialize-params-invalid.test b/mlir/test/tblgen-lsp-server/initialize-params-invalid.test new file mode 100644 --- /dev/null +++ b/mlir/test/tblgen-lsp-server/initialize-params-invalid.test @@ -0,0 +1,12 @@ +// RUN: tblgen-lsp-server -lit-test < %s | FileCheck %s +// Test with invalid initialize request parameters +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","rootUri":"test:///workspace","capabilities":{},"trace":"verbose"}} +// CHECK: "id": 0, +// CHECK-NEXT: "jsonrpc": "2.0", +// CHECK-NEXT: "result": { +// CHECK-NEXT: "capabilities": { +// ... +// ----- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +// ----- +{"jsonrpc":"2.0","method":"exit"} diff --git a/mlir/test/tblgen-lsp-server/initialize-params.test b/mlir/test/tblgen-lsp-server/initialize-params.test new file mode 100644 --- /dev/null +++ b/mlir/test/tblgen-lsp-server/initialize-params.test @@ -0,0 +1,25 @@ +// RUN: tblgen-lsp-server -lit-test < %s | FileCheck %s +// Test initialize request parameters with rootUri +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"test:///workspace","capabilities":{},"trace":"off"}} +// CHECK: "id": 0, +// CHECK-NEXT: "jsonrpc": "2.0", +// CHECK-NEXT: "result": { +// CHECK-NEXT: "capabilities": { +// CHECK-NEXT: "textDocumentSync": { +// CHECK-NEXT: "change": 1, +// CHECK-NEXT: "openClose": true, +// CHECK-NEXT: "save": true +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "serverInfo": { +// CHECK-NEXT: "name": "tblgen-lsp-server", +// CHECK-NEXT: "version": "{{.*}}" +// CHECK-NEXT: } +// CHECK-NEXT: } +// ----- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +// CHECK: "id": 3, +// CHECK-NEXT: "jsonrpc": "2.0", +// CHECK-NEXT: "result": null +// ----- +{"jsonrpc":"2.0","method":"exit"} diff --git a/mlir/tools/CMakeLists.txt b/mlir/tools/CMakeLists.txt --- a/mlir/tools/CMakeLists.txt +++ b/mlir/tools/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(mlir-spirv-cpu-runner) add_subdirectory(mlir-translate) add_subdirectory(mlir-vulkan-runner) +add_subdirectory(tblgen-lsp-server) # mlir-cpu-runner requires ExecutionEngine which is only built # when the native target is configured in. diff --git a/mlir/tools/tblgen-lsp-server/CMakeLists.txt b/mlir/tools/tblgen-lsp-server/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/mlir/tools/tblgen-lsp-server/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LIBS + TableGenLspServerLib + ) + +add_llvm_tool(tblgen-lsp-server + tblgen-lsp-server.cpp + + DEPENDS + ${LIBS} + ) + +target_link_libraries(tblgen-lsp-server PRIVATE ${LIBS}) +llvm_update_compile_flags(tblgen-lsp-server) + +mlir_check_all_link_libraries(tblgen-lsp-server) diff --git a/mlir/tools/tblgen-lsp-server/tblgen-lsp-server.cpp b/mlir/tools/tblgen-lsp-server/tblgen-lsp-server.cpp new file mode 100644 --- /dev/null +++ b/mlir/tools/tblgen-lsp-server/tblgen-lsp-server.cpp @@ -0,0 +1,16 @@ +//===- tblgen-lsp-server.cpp - TableGen Language Server main --------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "mlir/Support/LogicalResult.h" +#include "mlir/Tools/tblgen-lsp-server/TableGenLspServerMain.h" + +using namespace mlir; + +int main(int argc, char **argv) { + return failed(TableGenLspServerMain(argc, argv)); +} diff --git a/mlir/utils/vscode/.gitignore b/mlir/utils/vscode/.gitignore --- a/mlir/utils/vscode/.gitignore +++ b/mlir/utils/vscode/.gitignore @@ -3,5 +3,6 @@ .vscode-test *.vsix grammar.json +tablegen-grammar.json !.vscode \ No newline at end of file diff --git a/mlir/utils/vscode/package.json b/mlir/utils/vscode/package.json --- a/mlir/utils/vscode/package.json +++ b/mlir/utils/vscode/package.json @@ -15,11 +15,15 @@ "keywords": [ "LLVM", "MLIR", - "PDLL" + "PDLL", + "TableGen", + "tblgen", + "tablegen" ], "activationEvents": [ "onLanguage:mlir", - "onLanguage:pdll" + "onLanguage:pdll", + "onLanguage:tablegen" ], "main": "./out/extension", "scripts": { @@ -75,6 +79,17 @@ ".pdll" ], "configuration": "./pdll-language-configuration.json" + }, + { + "id": "tablegen", + "aliases": [ + "TableGen", + "tblgen" + ], + "extensions": [ + ".td" + ], + "configuration": "./tablegen-language-configuration.json" } ], "grammars": [ @@ -108,6 +123,11 @@ "language": "pdll", "scopeName": "source.pdll", "path": "./pdll-grammar.json" + }, + { + "language": "tablegen", + "scopeName": "source.tablegen", + "path": "./tablegen-grammar.json" } ], "configuration": { @@ -129,6 +149,11 @@ "type": "array", "description": "A list of `pdll_compile_commands.yml` database files containing information about .pdll files processed by the server." }, + "mlir.tablegen_server_path": { + "scope": "resource", + "type": "string", + "description": "The file path of the tblgen-lsp-server executable." + }, "mlir.onSettingsChanged": { "type": "string", "default": "prompt", diff --git a/mlir/utils/vscode/src/mlirContext.ts b/mlir/utils/vscode/src/mlirContext.ts --- a/mlir/utils/vscode/src/mlirContext.ts +++ b/mlir/utils/vscode/src/mlirContext.ts @@ -42,6 +42,8 @@ serverSettingName = 'server_path'; } else if (document.languageId === 'pdll') { serverSettingName = 'pdll_server_path'; + } else if (document.languageId === 'tablegen') { + serverSettingName = 'tablegen_server_path'; } else { return; } @@ -268,6 +270,9 @@ if (serverSettingName === 'server_path') { return 'mlir-lsp-server'; } + if (serverSettingName === 'tablegen_server_path') { + return 'tblgen-lsp-server'; + } return ''; } diff --git a/mlir/utils/vscode/tablegen-language-configuration.json b/mlir/utils/vscode/tablegen-language-configuration.json new file mode 100644 --- /dev/null +++ b/mlir/utils/vscode/tablegen-language-configuration.json @@ -0,0 +1,71 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": [ + "/*", + "*/" + ] + }, + "brackets": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "<", + ">" + ] + ], + "autoClosingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "<", + ">" + ], + [ + "\"", + "\"" + ] + ], + "surroundingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "<", + ">" + ], + [ + "\"", + "\"" + ] + ] +} \ No newline at end of file