LSP: initial LSP implementation for IR files to assist with decompilation (#1647)

* lsp: json-rpc example is working, a decent place to start...

* lsp: vendor library

* lsp: cleanup and time to get started

* lsp: commit what i got so far

* lsp: example `initialize` payload

* lsp: switch to `stdio`

* stash

* modularize the lsp implementation

* lsp: implement first actual LSP feature - function names in outline

* lsp: produce document diagnostics

* lsp: remove unused third-party lib

* lsp: support hovering MIPS instructions in IR files

* lsp: basic go-to all-types definition

* stash

* lsp: cleanup code, just need to add it to the release artifacts

* fix some project configuration

* fix linux build

* lsp: add lsp to PR artifacts and release assets

* lsp: address feedback
This commit is contained in:
Tyler Wilding
2022-07-18 18:26:57 -04:00
committed by GitHub
parent 50af536e80
commit 01c70368e3
44 changed files with 2398 additions and 19 deletions
+1
View File
@@ -63,3 +63,4 @@ jobs:
./build/goalc/goalc
./build/decompiler/extractor
./build/game/gk
./build/lsp/lsp
+7 -3
View File
@@ -46,20 +46,24 @@ jobs:
- name: Display structure of downloaded files
run: ls -Rl ./ci-artifacts/
- name: Prepare Linux Release Asset
- name: Prepare Linux Release Assets
run: |
mkdir -p ./ci-artifacts/linux
./.github/scripts/releases/extract_build_linux.sh ./ci-artifacts/linux ./ci-artifacts/opengoal-linux-static ./
cd ci-artifacts/linux
pushd ci-artifacts/linux
TAG_VAL=$(echo ${{ github.REF }} | awk -F'refs/tags/' '{print $2}')
tar czf ../final/opengoal-linux-${TAG_VAL}.tar.gz .
popd
strip ./ci-artifacts/opengoal-linux-static/lsp/lsp
cp ./ci-artifacts/opengoal-linux-static/lsp/lsp ./ci-artifacts/final/opengoal-lsp-linux-${TAG_VAL}.bin
- name: Prepare Windows Build Asset
- name: Prepare Windows Build Assets
run: |
mkdir -p ./ci-artifacts/windows
./.github/scripts/releases/extract_build_windows.sh ./ci-artifacts/windows ./ci-artifacts/opengoal-windows-static ./
TAG_VAL=$(echo ${{ github.REF }} | awk -F'refs/tags/' '{print $2}')
7z a -tzip ./ci-artifacts/final/opengoal-windows-${TAG_VAL}.zip ./ci-artifacts/windows/*
cp ./ci-artifacts/opengoal-windows-static/lsp.exe ./ci-artifacts/final/opengoal-lsp-windows-${TAG_VAL}.exe
- name: Upload Assets
env:
+7
View File
@@ -170,6 +170,13 @@
"projectTarget": "extractor.exe (bin\\extractor.exe)",
"name": "Tools - Extractor - Full",
"args": ["\"E:\\ISOs\\Jak\\Jak 1.iso\""]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "lsp.exe (bin\\lsp.exe)",
"name" : "Run - LSP",
"args" : []
}
]
}
+3
View File
@@ -153,6 +153,9 @@ add_subdirectory(decompiler)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${THIRDPARTY_IGNORED_WARNINGS} ")
add_subdirectory(third-party/cubeb)
# build LSP
add_subdirectory(lsp)
# build glfw library
add_subdirectory(third-party/glfw)
add_subdirectory(third-party/zstd)
+24
View File
@@ -137,6 +137,19 @@ std::string TextDb::get_info_for(const Object& o, bool* terminate_compiler_error
}
}
std::optional<TextDb::ShortInfo> TextDb::get_short_info_for(const Object& o) const {
if (o.is_pair()) {
auto kv = m_map.find(o.heap_obj);
if (kv != m_map.end()) {
return get_short_info_for(kv->second.frag, kv->second.offset);
} else {
return {};
}
} else {
return {};
}
}
/*!
* Given a source text and an offset, print a description of where it is.
*/
@@ -151,6 +164,17 @@ std::string TextDb::get_info_for(const std::shared_ptr<SourceText>& frag, int of
return result + pointer;
}
std::optional<TextDb::ShortInfo> TextDb::get_short_info_for(const std::shared_ptr<SourceText>& frag,
int offset) const {
int line_idx = frag->get_line_idx(offset);
int offset_in_line = std::max(offset - frag->get_offset_of_line(line_idx), 1) - 1;
ShortInfo info_result;
info_result.filename = frag->get_description();
info_result.line_idx_to_display = line_idx;
info_result.pos_in_line = offset_in_line;
return std::make_optional(info_result);
}
std::optional<TextDb::ShortInfo> TextDb::try_get_short_info(const Object& o) const {
if (o.is_pair()) {
auto it = m_map.find(o.heap_obj);
+3
View File
@@ -107,7 +107,10 @@ class TextDb {
void insert(const std::shared_ptr<SourceText>& frag);
void link(const Object& o, std::shared_ptr<SourceText> frag, int offset);
std::string get_info_for(const Object& o, bool* terminate_compiler_error = nullptr) const;
std::optional<ShortInfo> get_short_info_for(const Object& o) const;
std::string get_info_for(const std::shared_ptr<SourceText>& frag, int offset) const;
std::optional<ShortInfo> get_short_info_for(const std::shared_ptr<SourceText>& frag,
int offset) const;
std::optional<ShortInfo> try_get_short_info(const Object& o) const;
bool has_info(const Object& o) const;
void inherit_info(const Object& parent, const Object& child);
+1 -1
View File
@@ -22,7 +22,7 @@ struct LogTime {
#endif
// Logging API
enum class level { trace = 0, debug = 1, info = 2, warn = 3, error = 4, die = 5 };
enum class level { trace = 0, debug = 1, info = 2, warn = 3, error = 4, die = 5, off = 6 };
namespace internal {
// log implementation stuff, not to be called by the user
+7
View File
@@ -123,6 +123,13 @@ class Type {
std::string m_runtime_name;
bool m_is_boxed = false; // does this have runtime type information?
int m_heap_base = 0;
// definition information
// TODO - LSP - .gc support
/*std::string m_defining_file;
int m_line_number;
int m_line_offset;
void update_definition_meta(const std::string& defining_file, int line_number, int line_offset);*/
};
/*!
+1 -1
View File
@@ -130,4 +130,4 @@ void TypeSpec::add_or_modify_tag(const std::string& tag_name, const std::string&
}
}
m_tags.push_back({tag_name, tag_value});
}
}
+5 -1
View File
@@ -12,4 +12,8 @@ GameVersion game_name_to_version(const std::string& name) {
} else {
ASSERT_MSG(false, fmt::format("invalid game name: {}", name));
}
}
}
bool valid_game_version(const std::string& name) {
return name == "jak1" || name == "jak2";
}
+2 -1
View File
@@ -52,4 +52,5 @@ struct PerGameVersion {
constexpr PerGameVersion<const char*> game_version_names = {"jak1", "jak2"};
GameVersion game_name_to_version(const std::string& name);
GameVersion game_name_to_version(const std::string& name);
bool valid_game_version(const std::string& name);
+2 -1
View File
@@ -476,7 +476,8 @@ void Function::find_global_function_defs(LinkedObjectFile& file, DecompilerTypeS
auto& func = file.get_function_at_label(label_id);
ASSERT(func.guessed_name.empty());
func.guessed_name.set_as_global(name);
dts.add_symbol(name, "function");
// TODO - get definition info?
dts.add_symbol(name, "function", {});
;
// todo - inform function.
}
@@ -125,7 +125,7 @@ static uint32_t c_symlink2(LinkedObjectFile& f,
word_kind = LinkedWord::EMPTY_PTR;
break;
case SymbolLinkKind::TYPE:
dts.add_symbol(name, "type");
dts.add_symbol(name, "type", {});
word_kind = LinkedWord::TYPE_PTR;
break;
default:
@@ -183,7 +183,7 @@ static uint32_t c_symlink3(LinkedObjectFile& f,
word_kind = LinkedWord::EMPTY_PTR;
break;
case SymbolLinkKind::TYPE:
dts.add_symbol(name, "type");
dts.add_symbol(name, "type", {});
word_kind = LinkedWord::TYPE_PTR;
break;
default:
+5 -2
View File
@@ -693,8 +693,6 @@ void ObjectFileDB::ir2_write_results(const fs::path& output_dir,
const std::vector<std::string>& imports,
ObjectFileData& obj) {
if (obj.linked_data.has_any_functions()) {
// todo
auto file_text = ir2_to_file(obj, config);
auto file_name = output_dir / (obj.to_unique_name() + "_ir2.asm");
file_util::write_text_file(file_name, file_text);
@@ -708,6 +706,11 @@ void ObjectFileDB::ir2_write_results(const fs::path& output_dir,
std::string ObjectFileDB::ir2_to_file(ObjectFileData& data, const Config& config) {
std::string result;
auto all_types_path = file_util::get_file_path({config.all_types_file});
auto game_version = game_version_names[config.game_version];
result += fmt::format("; ALL_TYPES={}={}\n\n", game_version, all_types_path);
const char* segment_names[] = {"main segment", "debug segment", "top-level segment"};
ASSERT(data.linked_data.segments <= 3);
for (int seg = data.linked_data.segments; seg-- > 0;) {
+13 -4
View File
@@ -61,16 +61,19 @@ void DecompilerTypeSystem::parse_type_defs(const std::vector<std::string>& file_
if (!cdr(*rest).is_empty_list()) {
throw std::runtime_error("malformed define-extern");
}
add_symbol(sym_name.as_symbol()->name, parse_typespec(&ts, sym_type));
auto info = m_reader.db.get_short_info_for(o);
add_symbol(sym_name.as_symbol()->name, parse_typespec(&ts, sym_type), info);
} else if (car(o).as_symbol()->name == "deftype") {
auto dtr = parse_deftype(cdr(o), &ts);
auto info = m_reader.db.get_short_info_for(o);
if (dtr.create_runtime_type) {
add_symbol(dtr.type.base_type(), "type");
add_symbol(dtr.type.base_type(), "type", info);
}
// declare the type's states globally
for (auto& state : dtr.type_info->get_states_declared_for_type()) {
add_symbol(state.first, state.second);
// TODO - get definition info for the state definitions specifically
add_symbol(state.first, state.second, info);
}
} else if (car(o).as_symbol()->name == "declare-type") {
@@ -158,11 +161,17 @@ bool DecompilerTypeSystem::lookup_flags(const std::string& type, u64* dest) cons
return false;
}
void DecompilerTypeSystem::add_symbol(const std::string& name, const TypeSpec& type_spec) {
void DecompilerTypeSystem::add_symbol(
const std::string& name,
const TypeSpec& type_spec,
const std::optional<goos::TextDb::ShortInfo>& definition_info) {
add_symbol(name);
auto skv = symbol_types.find(name);
if (skv == symbol_types.end() || skv->second == type_spec) {
symbol_types[name] = type_spec;
if (definition_info) {
symbol_definition_info[name] = definition_info.value();
}
} else {
if (ts.tc(type_spec, skv->second)) {
} else {
+9 -3
View File
@@ -2,6 +2,7 @@
#include "common/goos/Reader.h"
#include "common/type_system/TypeSystem.h"
#include <common/goos/TextDB.h>
#include "decompiler/Disasm/Register.h"
@@ -15,6 +16,7 @@ class DecompilerTypeSystem {
TypeSystem ts;
std::unordered_map<std::string, TypeSpec> symbol_types;
std::unordered_set<std::string> symbols;
std::unordered_map<std::string, goos::TextDb::ShortInfo> symbol_definition_info;
std::vector<std::string> symbol_add_order;
std::unordered_map<std::string, u64> type_flags;
std::unordered_map<std::string, std::string> type_parents;
@@ -30,11 +32,15 @@ class DecompilerTypeSystem {
}
}
void add_symbol(const std::string& name, const std::string& base_type) {
add_symbol(name, TypeSpec(base_type));
void add_symbol(const std::string& name,
const std::string& base_type,
const std::optional<goos::TextDb::ShortInfo>& definition_info) {
add_symbol(name, TypeSpec(base_type), definition_info);
}
void add_symbol(const std::string& name, const TypeSpec& type_spec);
void add_symbol(const std::string& name,
const TypeSpec& type_spec,
const std::optional<goos::TextDb::ShortInfo>& definition_info);
void parse_type_defs(const std::vector<std::string>& file_path);
TypeSpec parse_type_spec(const std::string& str) const;
void add_type_flags(const std::string& name, u64 flags);
+14
View File
@@ -0,0 +1,14 @@
add_executable(lsp
main.cpp
transport/stdio.cpp
state/workspace.cpp
handlers/lsp_router.cpp
protocol/common_types.cpp
protocol/document_symbols.cpp
protocol/document_synchronization.cpp
protocol/document_diagnostics.cpp
protocol/hover.cpp
state/data/mips_instruction.cpp)
target_link_libraries(lsp common decomp)
+12
View File
@@ -0,0 +1,12 @@
#include "common/log/log.h"
#include "lsp/protocol/initialize_result.h"
#include "third-party/json.hpp"
using json = nlohmann::json;
std::optional<json> initialize_handler(Workspace& workspace, int id, json params) {
InitializeResult result;
return result.to_json();
}
+138
View File
@@ -0,0 +1,138 @@
#include "lsp_router.h"
#include "lsp/handlers/initialize.h"
#include "common/log/log.h"
#include "lsp/protocol/error_codes.h"
#include "text_document/document_symbol.h"
#include "text_document/document_synchronization.h"
#include "text_document/go_to.h"
#include "text_document/hover.h"
#include "third-party/fmt/core.h"
LSPRoute::LSPRoute() : m_route_type(LSPRouteType::NOOP) {}
LSPRoute::LSPRoute(std::function<void(Workspace&, json)> notification_handler)
: m_route_type(LSPRouteType::NOTIFICATION), m_notification_handler(notification_handler) {}
LSPRoute::LSPRoute(std::function<void(Workspace&, json)> notification_handler,
std::function<std::optional<json>(Workspace&, json)> post_notification_publish)
: m_route_type(LSPRouteType::NOTIFICATION),
m_notification_handler(notification_handler),
m_post_notification_publish(post_notification_publish) {}
LSPRoute::LSPRoute(std::function<std::optional<json>(Workspace&, int, json)> request_handler)
: m_route_type(LSPRouteType::REQUEST_RESPONSE), m_request_handler(request_handler) {}
void LSPRouter::init_routes() {
m_routes["initialize"] = LSPRoute(initialize_handler);
m_routes["initialize"].m_generic_post_action = [](Workspace& workspace) {
workspace.set_initialized(true);
};
m_routes["initialized"] = LSPRoute();
m_routes["textDocument/documentSymbol"] = LSPRoute(document_symbols_handler);
m_routes["textDocument/didOpen"] = LSPRoute(did_open_handler, did_open_push_diagnostics);
m_routes["textDocument/didChange"] = LSPRoute(did_change_handler, did_change_push_diagnostics);
m_routes["textDocument/didClose"] = LSPRoute(did_close_handler);
m_routes["textDocument/hover"] = LSPRoute(hover_handler);
m_routes["textDocument/definition"] = LSPRoute(go_to_definition_handler);
}
json error_resp(ErrorCodes error_code, const std::string& error_message) {
json error{
{"code", static_cast<int>(error_code)},
{"message", error_message},
};
return json{{"error", error}};
}
std::string LSPRouter::make_response(const json& result) {
json content = result;
content["jsonrpc"] = "2.0";
std::string header;
header.append("Content-Length: " + std::to_string(content.dump().size()) + "\r\n");
header.append("Content-Type: application/vscode-jsonrpc;charset=utf-8\r\n");
header.append("\r\n");
return header + content.dump();
}
std::optional<std::vector<std::string>> LSPRouter::route_message(
const MessageBuffer& message_buffer,
AppState& appstate) {
const json& body = message_buffer.body();
auto method = body["method"];
lg::info(method);
// If the workspace has not yet been initialized but the client sends a
// message that doesn't have method "initialize" then we'll return an error
// as per LSP spec.
if (method != "initialize" && !appstate.workspace.is_initialized()) {
auto error = {
make_response(error_resp(ErrorCodes::ServerNotInitialized, "Server not yet initialized."))};
return std::make_optional(error);
}
// Exit early if we can't handle the route
if (m_routes.count(method) == 0) {
lg::warn("Method not supported '{}'", method);
auto error = {make_response(
error_resp(ErrorCodes::MethodNotFound, fmt::format("Method '{}' not supported", method)))};
return std::make_optional(error);
}
try {
auto route = m_routes[method];
std::vector<json> resp_bodies;
// Handle the request/notificiation
switch (route.m_route_type) {
case LSPRouteType::NOOP:
break;
case LSPRouteType::NOTIFICATION:
route.m_notification_handler(appstate.workspace, body["params"]);
break;
case LSPRouteType::REQUEST_RESPONSE:
auto resp_body = route.m_request_handler(appstate.workspace, body["id"], body["params"]);
if (resp_body) {
json resp;
// TODO - this should be the job of the handler, not here!
resp["id"] = body["id"];
resp["result"] = resp_body.value();
resp_bodies.push_back(resp);
}
break;
}
// Run any publish we need to do after the fact
if (route.m_post_notification_publish) {
auto resp = route.m_post_notification_publish.value()(appstate.workspace, body["params"]);
if (resp) {
lg::info("adding publish resp");
resp_bodies.push_back(resp.value());
}
}
// Run any generic post action
if (route.m_generic_post_action) {
route.m_generic_post_action.value()(appstate.workspace);
}
// Serialize all payloads with headers
std::vector<std::string> resps;
for (const auto& body : resp_bodies) {
resps.push_back(make_response(body));
}
// Return
if (resps.empty()) {
return {};
}
return resps;
} catch (std::exception& e) {
lg::error("Unexpected exception occurred - {} | {}", e.what(), body.dump());
// TODO - return an error with the message
return {};
}
}
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <optional>
#include <string>
#include "lsp/state/app.h"
#include "lsp/state/workspace.h"
#include "lsp/transport/stdio.h"
#include "third-party/json.hpp"
using json = nlohmann::json;
enum class LSPRouteType { NOOP = 0, NOTIFICATION = 1, REQUEST_RESPONSE = 2 };
class LSPRoute {
public:
LSPRoute();
LSPRoute(std::function<void(Workspace&, json)> notification_handler);
LSPRoute(std::function<void(Workspace&, json)> notification_handler,
std::function<std::optional<json>(Workspace&, json)> post_notification_publish);
LSPRoute(std::function<std::optional<json>(Workspace&, int, json)> request_handler);
LSPRouteType m_route_type;
/// @brief Handle a notification -- this requires no response to the client
std::function<void(Workspace&, json)> m_notification_handler;
/// @brief Handle a new request from the client that expects a response
std::function<std::optional<json>(Workspace&, int, json)> m_request_handler;
/// @brief Prepares a notification response body to be published _after_ the main handler is
/// processed
std::optional<std::function<std::optional<json>(Workspace&, json)>> m_post_notification_publish;
/// @brief Generic function to perform some action after processing
std::optional<std::function<void(Workspace&)>> m_generic_post_action;
};
class LSPRouter {
public:
void init_routes();
std::optional<std::vector<std::string>> route_message(const MessageBuffer& message_buffer,
AppState& appstate);
std::string make_response(const json& result);
private:
std::unordered_map<std::string, LSPRoute> m_routes;
;
};
@@ -0,0 +1,25 @@
#include <optional>
#include "lsp/protocol/common_types.h"
#include "lsp/state/workspace.h"
#include "third-party/json.hpp"
using json = nlohmann::json;
std::optional<json> document_symbols_handler(Workspace& workspace, int id, json params) {
auto converted_params = params.get<LSPSpec::DocumentSymbolParams>();
auto tracked_file = workspace.get_tracked_ir_file(converted_params.m_textDocument.m_uri);
if (!tracked_file) {
return {};
}
// TODO - convert to type!
json arr = json::array();
for (const auto& symbol : tracked_file.value().m_symbols) {
arr.push_back(symbol);
}
return arr;
}
@@ -0,0 +1,67 @@
#include <optional>
#include "lsp/protocol/document_diagnostics.h"
#include "lsp/protocol/document_synchronization.h"
#include "lsp/state/workspace.h"
#include "third-party/json.hpp"
using json = nlohmann::json;
void did_open_handler(Workspace& workspace, json raw_params) {
auto params = raw_params.get<LSPSpec::DidOpenTextDocumentParams>();
workspace.start_tracking_file(params.m_textDocument.m_uri, params.m_textDocument.m_languageId,
params.m_textDocument.m_text);
}
void did_change_handler(Workspace& workspace, json raw_params) {
auto params = raw_params.get<LSPSpec::DidChangeTextDocumentParams>();
for (const auto& change : params.m_contentChanges) {
workspace.update_tracked_file(params.m_textDocument.m_uri, change.m_text);
}
}
void did_close_handler(Workspace& workspace, json raw_params) {
auto params = raw_params.get<LSPSpec::DidCloseTextDocumentParams>();
workspace.stop_tracking_file(params.m_textDocument.m_uri);
}
std::optional<json> did_open_push_diagnostics(Workspace& workspace, json params) {
auto converted_params = params.get<LSPSpec::DidOpenTextDocumentParams>();
auto tracked_file = workspace.get_tracked_ir_file(converted_params.m_textDocument.m_uri);
if (!tracked_file) {
return {};
}
LSPSpec::PublishDiagnosticParams publish_params;
publish_params.m_uri = converted_params.m_textDocument.m_uri;
publish_params.m_diagnostics = tracked_file.value().m_diagnostics;
publish_params.m_version = converted_params.m_textDocument.m_version;
json response;
response["method"] = "textDocument/publishDiagnostics";
response["params"] = publish_params;
return response;
}
std::optional<json> did_change_push_diagnostics(Workspace& workspace, json raw_params) {
auto params = raw_params.get<LSPSpec::DidChangeTextDocumentParams>();
auto tracked_file = workspace.get_tracked_ir_file(params.m_textDocument.m_uri);
if (!tracked_file) {
return {};
}
LSPSpec::PublishDiagnosticParams publish_params;
publish_params.m_uri = params.m_textDocument.m_uri;
publish_params.m_diagnostics = tracked_file.value().m_diagnostics;
publish_params.m_version = params.m_textDocument.m_version;
json response;
response["method"] = "textDocument/publishDiagnostics";
response["params"] = publish_params;
return response;
}
+44
View File
@@ -0,0 +1,44 @@
#include <optional>
#include "lsp/protocol/common_types.h"
#include "lsp/protocol/hover.h"
#include "lsp/state/data/mips_instructions.h"
#include "lsp/state/workspace.h"
std::optional<json> go_to_definition_handler(Workspace& workspace, int id, json params) {
auto converted_params = params.get<LSPSpec::TextDocumentPositionParams>();
auto tracked_file = workspace.get_tracked_ir_file(converted_params.m_textDocument.m_uri);
if (!tracked_file) {
lg::debug("GOTODEF - no file");
return {};
}
json locations = json::array();
auto symbol_name = tracked_file->get_symbol_at_position(converted_params.m_position);
if (!symbol_name) {
lg::debug("GOTODEF - no symbol");
return locations;
}
lg::debug("GOTODEF - symbol - {}", symbol_name.value());
auto symbol_info =
workspace.get_symbol_info_from_all_types(symbol_name.value(), tracked_file->m_all_types_uri);
if (!symbol_info) {
lg::debug("GOTODEF - no symbol info");
return locations;
}
LSPSpec::Location location;
location.m_uri = tracked_file->m_all_types_uri;
location.m_range.m_start = {(uint32_t)symbol_info->line_idx_to_display,
(uint32_t)symbol_info->pos_in_line};
location.m_range.m_end = {(uint32_t)symbol_info->line_idx_to_display,
(uint32_t)symbol_info->pos_in_line};
locations.push_back(location);
return locations;
}
+44
View File
@@ -0,0 +1,44 @@
#include <optional>
#include "lsp/protocol/common_types.h"
#include "lsp/protocol/hover.h"
#include "lsp/state/data/mips_instructions.h"
#include "lsp/state/workspace.h"
std::optional<json> hover_handler(Workspace& workspace, int id, json params) {
auto converted_params = params.get<LSPSpec::TextDocumentPositionParams>();
auto tracked_file = workspace.get_tracked_ir_file(converted_params.m_textDocument.m_uri);
if (!tracked_file) {
return {};
}
auto token_at_pos = tracked_file->get_mips_instruction_at_position(converted_params.m_position);
LSPSpec::MarkupContent markup;
markup.m_kind = "markdown";
if (token_at_pos) {
auto token = token_at_pos.value();
std::transform(token.begin(), token.end(), token.begin(),
[](unsigned char c) { return std::tolower(c); });
// Find the instruction, there are some edge-cases here where they could be multiple
// TODO - handle those (print both is an easy fix?)
// TODO - havn't addressed `bc` and such instructions! Those need to be prefixed matched
for (const auto& instr : LSPData::MIPS_INSTRUCTION_LIST) {
auto mnemonic_lower = instr.mnemonic;
std::transform(mnemonic_lower.begin(), mnemonic_lower.end(), mnemonic_lower.begin(),
[](unsigned char c) { return std::tolower(c); });
if (mnemonic_lower == token) {
markup.m_value = fmt::format("### {}\n{}", instr.mnemonic, instr.description);
break;
}
}
} else {
return {};
}
LSPSpec::Hover hover_resp;
hover_resp.m_contents = markup;
// TODO - try specifying the range so it highlights everything, ie. `c.lt.s`
return hover_resp;
}
+108
View File
@@ -0,0 +1,108 @@
// clang-format off
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <optional>
#include <vector>
#include <regex>
#include "common/log/log.h"
#include "lsp/handlers/lsp_router.h"
#include "lsp/state/workspace.h"
#include "lsp/transport/stdio.h"
#include "lsp/state/app.h"
#include "third-party/CLI11.hpp"
// clang-format on
// NOTE - if we ever add HTTP support to the LSP
/*
What needs to be understood is that for connection timing issues the server is actually a client
and the client is the server in terms of opening the ports.
When you specify a socket transport the client is listening on that port for a connection. The
socket port number is passed as --socket=${port} to the server process started.
*/
void setup_logging(bool verbose, std::string log_file) {
lg::set_file(log_file);
if (verbose) {
lg::set_file_level(lg::level::debug);
lg::set_flush_level(lg::level::debug);
} else {
lg::set_file_level(lg::level::info);
lg::set_flush_level(lg::level::info);
}
// We use stdout to communicate with the client, so don't use it at all!
lg::set_stdout_level(lg::level::off);
lg::initialize();
}
int main(int argc, char** argv) {
fs::u8arguments u8guard(argc, argv);
if (!u8guard.valid()) {
exit(EXIT_FAILURE);
}
CLI::App app{"OpenGOAL Language Server"};
bool use_stdin = true;
bool verbose = false;
std::string logfile;
app.add_flag("--stdio", use_stdin,
"Don't launch an HTTP server and instead accept input on stdin");
app.add_flag("-v,--verbose", verbose, "Enable verbose logging");
app.add_option("-l,--log", logfile, "Log file path");
app.validate_positionals();
CLI11_PARSE(app, argc, argv);
AppState appstate;
LSPRouter lsp_router;
appstate.verbose = verbose;
if (!logfile.empty()) {
setup_logging(appstate.verbose, logfile);
}
lsp_router.init_routes();
lg::info("OpenGOAL LSP Initialized, ready for requests");
#ifdef _WIN32
_setmode(_fileno(stdout), _O_BINARY);
_setmode(_fileno(stdin), _O_BINARY);
#endif
char c;
MessageBuffer message_buffer;
while (std::cin.get(c)) {
message_buffer.handle_char(c);
if (message_buffer.message_completed()) {
json body = message_buffer.body();
auto method_name = body["method"].get<std::string>();
lg::info(">>> Received message of method '{}'", method_name);
auto responses = lsp_router.route_message(message_buffer, appstate);
if (responses) {
for (const auto& response : responses.value()) {
std::cout << response.c_str() << std::flush;
if (appstate.verbose) {
lg::debug("<<< Sending message: {}", response);
} else {
lg::info("<<< Sending message of method '{}'", method_name);
}
}
}
message_buffer.clear();
}
}
return 0;
}
+69
View File
@@ -0,0 +1,69 @@
#include "common_types.h"
void LSPSpec::to_json(json& j, const Position& obj) {
j = json{{"line", obj.m_line}, {"character", obj.m_character}};
}
void LSPSpec::from_json(const json& j, Position& obj) {
j.at("line").get_to(obj.m_line);
j.at("character").get_to(obj.m_character);
}
void LSPSpec::to_json(json& j, const Range& obj) {
// TODO - not sure if this works yet, but nice if it does!
j = json{{"start", obj.m_start}, {"end", obj.m_end}};
}
void LSPSpec::from_json(const json& j, Range& obj) {
obj.m_start = j.at("start").get<Position>();
obj.m_end = j.at("end").get<Position>();
}
void LSPSpec::to_json(json& j, const TextDocumentItem& obj) {
j = json{{"uri", obj.m_uri},
{"languageId", obj.m_languageId},
{"version", obj.m_version},
{"text", obj.m_text}};
}
void LSPSpec::from_json(const json& j, TextDocumentItem& obj) {
j.at("uri").get_to(obj.m_uri);
j.at("languageId").get_to(obj.m_languageId);
j.at("version").get_to(obj.m_version);
j.at("text").get_to(obj.m_text);
}
void LSPSpec::to_json(json& j, const TextDocumentIdentifier& obj) {
j = json{{"uri", obj.m_uri}};
}
void LSPSpec::from_json(const json& j, TextDocumentIdentifier& obj) {
j.at("uri").get_to(obj.m_uri);
}
void LSPSpec::to_json(json& j, const VersionedTextDocumentIdentifier& obj) {
j = json{{"uri", obj.m_uri}, {"version", obj.m_version}};
}
void LSPSpec::from_json(const json& j, VersionedTextDocumentIdentifier& obj) {
j.at("uri").get_to(obj.m_uri);
j.at("version").get_to(obj.m_version);
}
void LSPSpec::to_json(json& j, const Location& obj) {
j = json{{"uri", obj.m_uri}, {"range", obj.m_range}};
}
void LSPSpec::from_json(const json& j, Location& obj) {
j.at("uri").get_to(obj.m_uri);
j.at("range").get_to(obj.m_range);
}
void LSPSpec::to_json(json& j, const TextDocumentPositionParams& obj) {
j = json{{"textDocument", obj.m_textDocument}, {"position", obj.m_position}};
}
void LSPSpec::from_json(const json& j, TextDocumentPositionParams& obj) {
j.at("textDocument").get_to(obj.m_textDocument);
j.at("position").get_to(obj.m_position);
}
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include <optional>
#include <stdint.h>
#include <string>
#include <vector>
#include "third-party/json.hpp"
using json = nlohmann::json;
namespace LSPSpec {
// TODO - eventually parse this
typedef std::string URI;
typedef std::string DocumentUri;
struct Position {
/// @brief Line position in a document (zero-based).
uint32_t m_line;
/// @brief Character offset on a line in a document (zero-based). The meaning of this
/// offset is determined by the negotiated `PositionEncodingKind`.
///
/// If the character value is greater than the line length it defaults back
/// to the line length.
uint32_t m_character;
};
void to_json(json& j, const Position& obj);
void from_json(const json& j, Position& obj);
struct Range {
Position m_start;
Position m_end;
};
void to_json(json& j, const Range& obj);
void from_json(const json& j, Range& obj);
struct TextDocumentItem {
DocumentUri m_uri;
std::string m_languageId; // ie. opengoal-ir
int32_t m_version;
std::string m_text;
};
void to_json(json& j, const TextDocumentItem& obj);
void from_json(const json& j, TextDocumentItem& obj);
struct TextDocumentIdentifier {
DocumentUri m_uri;
};
void to_json(json& j, const TextDocumentIdentifier& obj);
void from_json(const json& j, TextDocumentIdentifier& obj);
struct VersionedTextDocumentIdentifier {
DocumentUri m_uri;
/// @brief The version number of this document.
/// The version number of a document will increase after each change, including undo/redo. The
/// number doesn't need to be consecutive.
int32_t m_version;
};
void to_json(json& j, const VersionedTextDocumentIdentifier& obj);
void from_json(const json& j, VersionedTextDocumentIdentifier& obj);
struct Location {
DocumentUri m_uri;
Range m_range;
};
void to_json(json& j, const Location& obj);
void from_json(const json& j, Location& obj);
struct TextDocumentPositionParams {
/// @brief The text document.
TextDocumentIdentifier m_textDocument;
/// @brief The position inside the text document.
Position m_position;
};
void to_json(json& j, const TextDocumentPositionParams& obj);
void from_json(const json& j, TextDocumentPositionParams& obj);
} // namespace LSPSpec
+74
View File
@@ -0,0 +1,74 @@
#include "document_diagnostics.h"
void LSPSpec::to_json(json& j, const CodeDescription& obj) {
j = json{{"href", obj.m_href}};
}
void LSPSpec::from_json(const json& j, CodeDescription& obj) {
j.at("href").get_to(obj.m_href);
}
void LSPSpec::to_json(json& j, const DiangosticRelatedInformation& obj) {
j = json{{"location", obj.m_location}, {"message", obj.m_message}};
}
void LSPSpec::from_json(const json& j, DiangosticRelatedInformation& obj) {
j.at("location").get_to(obj.m_location);
j.at("message").get_to(obj.m_message);
}
void LSPSpec::to_json(json& j, const Diagnostic& obj) {
j = json{{"range", obj.m_range}, {"severity", obj.m_severity}, {"message", obj.m_message}};
if (obj.m_code) {
j["code"] = obj.m_code.value();
}
if (obj.m_codeDescription) {
j["codeDescription"] = obj.m_codeDescription.value();
}
if (obj.m_source) {
j["source"] = obj.m_source.value();
}
if (obj.m_tags) {
j["tags"] = obj.m_tags.value();
}
if (obj.m_relatedInformation) {
j["relatedInformation"] = obj.m_relatedInformation.value();
}
}
void LSPSpec::from_json(const json& j, Diagnostic& obj) {
j.at("range").get_to(obj.m_range);
j.at("severity").get_to(obj.m_severity);
j.at("message").get_to(obj.m_message);
if (j.contains("code")) {
obj.m_code = std::make_optional(j.at("code").get<std::string>());
}
if (j.contains("codeDescription")) {
obj.m_codeDescription = std::make_optional(j.at("codeDescription").get<CodeDescription>());
}
if (j.contains("source")) {
obj.m_source = std::make_optional(j.at("source").get<std::string>());
}
if (j.contains("tags")) {
obj.m_tags = std::make_optional(j.at("tags").get<std::vector<DiagnosticTag>>());
}
if (j.contains("relatedInformation")) {
obj.m_relatedInformation = std::make_optional(
j.at("relatedInformation").get<std::vector<DiangosticRelatedInformation>>());
}
}
void LSPSpec::to_json(json& j, const PublishDiagnosticParams& obj) {
j = json{{"uri", obj.m_uri}, {"diagnostics", obj.m_diagnostics}};
if (obj.m_version) {
j["version"] = obj.m_version.value();
}
}
void LSPSpec::from_json(const json& j, PublishDiagnosticParams& obj) {
j.at("uri").get_to(obj.m_uri);
j.at("diagnostics").get_to(obj.m_diagnostics);
if (j.contains("version")) {
obj.m_version = std::make_optional(j.at("version").get<int32_t>());
}
}
+86
View File
@@ -0,0 +1,86 @@
#pragma once
#include "common_types.h"
namespace LSPSpec {
enum class DiagnosticSeverity {
/// Reports an error.
Error = 1,
/// Reports a warning.
Warning = 2,
/// Reports an information.
Information = 3,
/// Reports a hint.
Hint = 4,
};
/// @brief Structure to capture a description for an error code.
struct CodeDescription {
/// @brief An URI to open with more information about the diagnostic error.
URI m_href;
};
void to_json(json& j, const CodeDescription& obj);
void from_json(const json& j, CodeDescription& obj);
enum class DiagnosticTag {
/// @brief Unused or unnecessary code.
/// Clients are allowed to render diagnostics with this tag faded out instead of having an error
/// squiggle.
Unnecessary = 1,
/// @brief Deprecated or obsolete code.
/// Clients are allowed to rendered diagnostics with this tag strike through.
Deprecated = 2
};
struct DiangosticRelatedInformation {
/// @brief The location of this related diagnostic information.
Location m_location;
/// @brief The message of this related diagnostic information.
std::string m_message;
};
void to_json(json& j, const DiangosticRelatedInformation& obj);
void from_json(const json& j, DiangosticRelatedInformation& obj);
struct Diagnostic {
/// @brief The range at which the message applies.
Range m_range;
/// @brief The diagnostic's severity. Can be omitted. If omitted it is up to the client to
/// interpret diagnostics as error, warning, info or hint.
DiagnosticSeverity m_severity;
/// @brief The diagnostic's code, which might appear in the user interface.
std::optional<std::string> m_code;
/// @brief An optional property to describe the error code.
std::optional<CodeDescription> m_codeDescription;
/// @brief A human-readable string describing the source of this diagnostic, e.g. 'typescript' or
/// 'super lint'.
std::optional<std::string> m_source;
/// @brief The diagnostic's message.
std::string m_message;
/// @brief Additional metadata about the diagnostic.
std::optional<std::vector<DiagnosticTag>> m_tags;
/// @brief An array of related diagnostic information, e.g. when symbol-names within a scope
/// collide all definitions can be marked via this property.
std::optional<std::vector<DiangosticRelatedInformation>> m_relatedInformation;
// omitting `data` field
/// A data entry field that is preserved between a `textDocument/publishDiagnostics` notification
/// and `textDocument/codeAction` request
};
void to_json(json& j, const Diagnostic& obj);
void from_json(const json& j, Diagnostic& obj);
struct PublishDiagnosticParams {
/// @brief The URI for which diagnostic information is reported
DocumentUri m_uri;
/// @brief Optional the version number of the document the diagnostics are published for
std::optional<int32_t> m_version;
/// @brief An array of diagnostic information items
std::vector<Diagnostic> m_diagnostics;
};
void to_json(json& j, const PublishDiagnosticParams& obj);
void from_json(const json& j, PublishDiagnosticParams& obj);
} // namespace LSPSpec
+41
View File
@@ -0,0 +1,41 @@
#include "document_symbols.h"
void LSPSpec::to_json(json& j, const DocumentSymbol& obj) {
j = json{{"name", obj.m_name},
{"kind", obj.m_kind},
{"range", obj.m_range},
{"selectionRange", obj.m_selectionRange}};
if (obj.m_detail) {
j["detail"] = obj.m_detail.value();
}
if (obj.m_tags) {
j["tags"] = obj.m_tags.value();
}
if (obj.m_children) {
j["children"] = obj.m_children.value();
}
}
void LSPSpec::from_json(const json& j, DocumentSymbol& obj) {
j.at("name").get_to(obj.m_name);
j.at("kind").get_to(obj.m_kind);
j.at("range").get_to(obj.m_range);
j.at("selectionRange").get_to(obj.m_selectionRange);
if (j.contains("detail")) {
obj.m_detail = std::make_optional(j.at("detail").get<std::string>());
}
if (j.contains("tags")) {
obj.m_tags = std::make_optional(j.at("tags").get<std::vector<SymbolTag>>());
}
if (j.contains("children")) {
obj.m_children = std::make_optional(j.at("children").get<std::vector<DocumentSymbol>>());
}
}
void LSPSpec::to_json(json& j, const DocumentSymbolParams& obj) {
j = json{{"textDocument", obj.m_textDocument}};
}
void LSPSpec::from_json(const json& j, DocumentSymbolParams& obj) {
j.at("textDocument").get_to(obj.m_textDocument);
}
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include <optional>
#include "common_types.h"
#include "third-party/json.hpp"
namespace LSPSpec {
enum class SymbolKind {
File = 1,
Module = 2,
Namespace = 3,
Package = 4,
Class = 5,
Method = 6,
Property = 7,
Field = 8,
Constructor = 9,
Enum = 10,
Interface = 11,
Function = 12,
Variable = 13,
Constant = 14,
String = 15,
Number = 16,
Boolean = 17,
Array = 18,
Object = 19,
Key = 20,
Null = 21,
EnumMember = 22,
Struct = 23,
Event = 24,
Operator = 25,
TypeParameter = 26
};
/// @brief Symbol tags are extra annotations that tweak the rendering of a symbol.
/// @since 3.16
enum class SymbolTag {
/// @brief Render a symbol as obsolete, usually using a strike-out.
Deprecated = 1
};
struct DocumentSymbol {
std::string m_name;
std::optional<std::string> m_detail;
SymbolKind m_kind;
std::optional<std::vector<SymbolTag>> m_tags;
/// @brief The range enclosing this symbol not including leading/trailing whitespace
/// but everything else like comments. This information is typically used to determine
/// if the clients cursor is inside the symbol to reveal in the symbol in the UI.
Range m_range;
/// @brief The range that should be selected and revealed when this symbol is being
/// picked, e.g. the name of a function. Must be contained by the `range`.
Range m_selectionRange;
/// @brief Children of this symbol, e.g. properties of a class.
std::optional<std::vector<DocumentSymbol>> m_children;
};
void to_json(json& j, const DocumentSymbol& obj);
void from_json(const json& j, DocumentSymbol& obj);
struct DocumentSymbolParams {
TextDocumentIdentifier m_textDocument;
};
void to_json(json& j, const DocumentSymbolParams& obj);
void from_json(const json& j, DocumentSymbolParams& obj);
} // namespace LSPSpec
+33
View File
@@ -0,0 +1,33 @@
#include "document_synchronization.h"
void LSPSpec::to_json(json& j, const DidOpenTextDocumentParams& obj) {
j = json{{"textDocument", obj.m_textDocument}};
}
void LSPSpec::from_json(const json& j, DidOpenTextDocumentParams& obj) {
j.at("textDocument").get_to(obj.m_textDocument);
}
void LSPSpec::to_json(json& j, const TextDocumentContentChangeEvent& obj) {
j = json{{"text", obj.m_text}};
}
void LSPSpec::from_json(const json& j, TextDocumentContentChangeEvent& obj) {
j.at("text").get_to(obj.m_text);
}
void LSPSpec::to_json(json& j, const DidChangeTextDocumentParams& obj) {
j = json{{"textDocument", obj.m_textDocument}};
}
void LSPSpec::from_json(const json& j, DidChangeTextDocumentParams& obj) {
j.at("textDocument").get_to(obj.m_textDocument);
}
void LSPSpec::to_json(json& j, const DidCloseTextDocumentParams& obj) {
j = json{{"textDocument", obj.m_textDocument}};
}
void LSPSpec::from_json(const json& j, DidCloseTextDocumentParams& obj) {
j.at("textDocument").get_to(obj.m_textDocument);
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "common_types.h"
namespace LSPSpec {
struct DidOpenTextDocumentParams {
TextDocumentItem m_textDocument;
};
void to_json(json& j, const DidOpenTextDocumentParams& obj);
void from_json(const json& j, DidOpenTextDocumentParams& obj);
struct TextDocumentContentChangeEvent {
std::string m_text;
};
void to_json(json& j, const TextDocumentContentChangeEvent& obj);
void from_json(const json& j, TextDocumentContentChangeEvent& obj);
struct DidChangeTextDocumentParams {
VersionedTextDocumentIdentifier m_textDocument;
std::vector<TextDocumentContentChangeEvent> m_contentChanges;
};
void to_json(json& j, const DidChangeTextDocumentParams& obj);
void from_json(const json& j, DidChangeTextDocumentParams& obj);
struct DidCloseTextDocumentParams {
TextDocumentIdentifier m_textDocument;
};
void to_json(json& j, const DidCloseTextDocumentParams& obj);
void from_json(const json& j, DidCloseTextDocumentParams& obj);
} // namespace LSPSpec
+93
View File
@@ -0,0 +1,93 @@
#pragma once
enum class ErrorCodes {
// Defined by JSON-RPC
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
/**
* This is the start range of JSON-RPC reserved error codes.
* It doesn't denote a real error code. No LSP error codes should
* be defined between the start and end range. For backwards
* compatibility the `ServerNotInitialized` and the `UnknownErrorCode`
* are left in the range.
*
* @since 3.16.0
*/
jsonrpcReservedErrorRangeStart = -32099,
/** @deprecated use jsonrpcReservedErrorRangeStart */
serverErrorStart = jsonrpcReservedErrorRangeStart,
/**
* Error code indicating that a server received a notification or
* request before the server has received the `initialize` request.
*/
ServerNotInitialized = -32002,
UnknownErrorCode = -32001,
/**
* This is the end range of JSON-RPC reserved error codes.
* It doesn't denote a real error code.
*
* @since 3.16.0
*/
jsonrpcReservedErrorRangeEnd = -32000,
/** @deprecated use jsonrpcReservedErrorRangeEnd */
serverErrorEnd = jsonrpcReservedErrorRangeEnd,
/**
* This is the start range of LSP reserved error codes.
* It doesn't denote a real error code.
*
* @since 3.16.0
*/
lspReservedErrorRangeStart = -32899,
/**
* A request failed but it was syntactically correct, e.g the
* method name was known and the parameters were valid. The error
* message should contain human readable information about why
* the request failed.
*
* @since 3.17.0
*/
RequestFailed = -32803,
/**
* The server cancelled the request. This error code should
* only be used for requests that explicitly support being
* server cancellable.
*
* @since 3.17.0
*/
ServerCancelled = -32802,
/**
* The server detected that the content of a document got
* modified outside normal conditions. A server should
* NOT send this error code if it detects a content change
* in it unprocessed messages. The result even computed
* on an older state might still be useful for the client.
*
* If a client decides that a result is not of any use anymore
* the client should cancel the request.
*/
ContentModified = -32801,
/**
* The client has canceled a request and a server as detected
* the cancel.
*/
RequestCancelled = -32800,
/**
* This is the end range of LSP reserved error codes.
* It doesn't denote a real error code.
*
* @since 3.16.0
*/
lspReservedErrorRangeEnd = -32800,
};
+24
View File
@@ -0,0 +1,24 @@
#include "hover.h"
void LSPSpec::to_json(json& j, const MarkupContent& obj) {
j = json{{"kind", obj.m_kind}, {"value", obj.m_value}};
}
void LSPSpec::from_json(const json& j, MarkupContent& obj) {
j.at("kind").get_to(obj.m_kind);
j.at("value").get_to(obj.m_value);
}
void LSPSpec::to_json(json& j, const Hover& obj) {
j = json{{"contents", obj.m_contents}};
if (obj.m_range) {
j["range"] = obj.m_range.value();
}
}
void LSPSpec::from_json(const json& j, Hover& obj) {
j.at("contents").get_to(obj.m_contents);
if (j.contains("range")) {
obj.m_range = std::make_optional(j.at("range").get<Range>());
}
}
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "common_types.h"
namespace LSPSpec {
/**
* A `MarkupContent` literal represents a string value which content is
* interpreted base on its kind flag. Currently the protocol supports
* `plaintext` and `markdown` as markup kinds.
*
* If the kind is `markdown` then the value can contain fenced code blocks like
* in GitHub issues.
*
* Here is an example how such a string can be constructed using
* JavaScript / TypeScript:
* ```typescript
* let markdown: MarkdownContent = {
* kind: MarkupKind.Markdown,
* value: [
* '# Header',
* 'Some text',
* '```typescript',
* 'someCode();',
* '```'
* ].join('\n')
* };
* ```
*
* *Please Note* that clients might sanitize the return markdown. A client could
* decide to remove HTML from the markdown to avoid script execution.
*/
struct MarkupContent {
std::string m_kind; // Actually a MarkupKind which is either 'plaintext' or 'markdown'
std::string m_value;
};
void to_json(json& j, const MarkupContent& obj);
void from_json(const json& j, MarkupContent& obj);
struct Hover {
/// @brief The hover's content
MarkupContent m_contents;
/// @brief An optional range is a range inside a text document that is used to visualize a hover,
/// e.g. by changing the background color.
std::optional<Range> m_range;
};
void to_json(json& j, const Hover& obj);
void from_json(const json& j, Hover& obj);
} // namespace LSPSpec
+59
View File
@@ -0,0 +1,59 @@
// TODO - convert this a proper class
#include "third-party/json.hpp"
using json = nlohmann::json;
class InitializeResult {
public:
InitializeResult(){};
json to_json() { return result; }
private:
json text_document_sync{
{"openClose", true},
{"change", 1}, // Full sync
{"willSave", false},
{"willSaveWaitUntil", false},
{"save", {{"includeText", false}}},
};
json completion_provider{
{"resolveProvider", false},
{"triggerCharacters", {}},
};
json signature_help_provider{{"triggerCharacters", ""}};
json code_lens_provider{{"resolveProvider", false}};
json document_on_type_formatting_provider{
{"firstTriggerCharacter", ""},
{"moreTriggerCharacter", ""},
};
json document_link_provider{{"resolveProvider", false}};
json execute_command_provider{{"commands", {}}};
json document_symbol_provder{{"label", "OpenGOAL"}};
json result{{"capabilities",
{
{"textDocumentSync", text_document_sync},
{"hoverProvider", true},
{"completionProvider", completion_provider},
{"signatureHelpProvider", signature_help_provider},
{"definitionProvider", true},
{"referencesProvider", false},
{"documentHighlightProvider", false},
{"documentSymbolProvider",
document_symbol_provder}, // TODO - there is another selectionRangeProvider i
// think i need, or word boundaries need to change!
{"workspaceSymbolProvider", false},
{"codeActionProvider", false},
{"codeLensProvider", code_lens_provider},
{"documentFormattingProvider", false},
{"documentRangeFormattingProvider", false},
{"documentOnTypeFormattingProvider", document_on_type_formatting_provider},
{"renameProvider", false},
{"documentLinkProvider", document_link_provider},
{"executeCommandProvider", execute_command_provider},
{"experimental", {}},
}}};
};
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include "workspace.h"
struct AppState {
Workspace workspace;
bool verbose;
};
+531
View File
@@ -0,0 +1,531 @@
#include "mips_instructions.h"
namespace LSPData {
const std::vector<MIPSInstruction> MIPS_INSTRUCTION_LIST = {
{"ee", "ADD", "Add Word"},
{"ee", "ADDI", "Add Immediate Word"},
{"ee", "ADDIU", "Add Immediate Unsigned Word"},
{"ee", "ADDU", "Add Unsigned Word"},
{"ee", "AND", "And"},
{"ee", "ANDI", "Add Immediate"},
{"ee", "BEQ", "Branch on Equal"},
{"ee", "BEQL", "Branch on Equal Likely"},
{"ee", "BGEZ", "Branch on Greater Than or Equal to Zero"},
{"ee", "BGEZAL", "Branch on Greater Than or Equal to Zero and Link"},
{"ee", "BGEZALL", "Branch on Greater Than or Equal to Zero and Link Likely"},
{"ee", "BGEZL", "Branch on Greater Than or Equal to Zero Likely"},
{"ee", "BGTZ", "Branch on Greater Than Zero"},
{"ee", "BGTZL", "Branch on Greater Than Zero Likely"},
{"ee", "BLEZ", "Branch on Less Than or Equal to Zero"},
{"ee", "BLEZL", "Branch on Less Than or Equal to Zero Likely"},
{"ee", "BLTZ", "Branch on Less Than Zero"},
{"ee", "BLTZAL", "Branch on Less Than Zero and Link"},
{"ee", "BLTZALL", "Branch on Less Than Zero and Link Likely"},
{"ee", "BLTZL", "Branch on Less Than Zero Likely"},
{"ee", "BNE", "Branch on Not Equal"},
{"ee", "BNEL", "Branch on Not Equal Likely"},
{"ee", "BREAK", "Breakpoint"},
{"ee", "DADD", "Doubleword Add"},
{"ee", "DADDI", "Doubleword Add Immediate"},
{"ee", "DADDIU", "Doubleword Add Immediate Unsigned"},
{"ee", "DADDU", "Doubleword Add Unsigned"},
{"ee", "DIV", "Divide Word"},
{"ee", "DIVU", "Divide Unsigned Word"},
{"ee", "DSLL", "Doubleword Shift Left Logical"},
{"ee", "DSLL32", "Doubleword Shift Left Logical Plus 32"},
{"ee", "DSLLV", "Doubleword Shift Left Logical Variable"},
{"ee", "DSRA", "Doubleword Shift Right Arithmetic"},
{"ee", "DSRA32", "Doubleword Shift Right Arithmetic Plus 32"},
{"ee", "DSRAV", "Doubleword Shift Right Arithmetic Variable"},
{"ee", "DSRL", "Doubleword Shift Right Logical"},
{"ee", "DSRL32", "Doubleword Shift Right Logical Plus 32"},
{"ee", "DSRLV", "Doubleword Shift Right Logical Variable"},
{"ee", "DSUB", "Doubleword Subtract"},
{"ee", "DSUBU", "Doubleword Subtract Unsigned"},
{"ee", "J", "Jump"},
{"ee", "JAL", "Jump and Link"},
{"ee", "JALR", "Jump and Link Register"},
{"ee", "JR", "Jump Register"},
{"ee", "LB", "Load Byte"},
{"ee", "LBU", "Load Byte Unsigned"},
{"ee", "LD", "Load Doubleword"},
{"ee", "LDL", "Load Doubleword Left"},
{"ee", "LDR", "Load Doubleword Right"},
{"ee", "LH", "Load Halfword"},
{"ee", "LHU", "Load Halfword Unsigned"},
{"ee", "LUI", "Load Upper Immediate"},
{"ee", "LW", "Load Word"},
{"ee", "LWL", "Load Word Left"},
{"ee", "LWR", "Load Word Right"},
{"ee", "LWU", "Load Word Unsigned"},
{"ee", "MFHI", "Move from HI Register"},
{"ee", "MFLO", "Move from LO Register"},
{"ee", "MOVN", "Move Conditional on Not Zero"},
{"ee", "MOVZ", "Move Conditional on Zero"},
{"ee", "MTHI", "Move to HI Register"},
{"ee", "MTLO", "Move to LO Register"},
{"ee", "MULT", "Multiply Word"},
{"ee", "MULTU", "Multiply Unsigned Word"},
{"ee", "NOR", "Not Or"},
{"ee", "OR", "Or"},
{"ee", "ORI", "Or immediate"},
{"ee", "PREF", "Prefetch"},
{"ee", "SB", "Store Byte"},
{"ee", "SD", "Store Doubleword"},
{"ee", "SDL", "Store Doubleword Left"},
{"ee", "SDR", "Store Doubleword Right"},
{"ee", "SH", "Store Halfword"},
{"ee", "SLL", "Shift Word Left Logical"},
{"ee", "SLLV", "Shift Word Left Logical Variable"},
{"ee", "SLT", "Set on Less Than"},
{"ee", "SLTI", "Set on Less Than Immediate"},
{"ee", "SLTIU", "Set on Less Than Immediate Unsigned"},
{"ee", "SLTU", "Set on Less Than Unsigned"},
{"ee", "SRA", "Shift Word Right Arithmetic"},
{"ee", "SRAV", "Shift Word Right Arithmetic Variable"},
{"ee", "SRL", "Shift Word Right Logical"},
{"ee", "SRLV", "Shift Word Right Logical Variable"},
{"ee", "SUB", "Subtract Word"},
{"ee", "SUBU", "Subtract Unsigned Word"},
{"ee", "SW", "Store Word"},
{"ee", "SWL", "Store Word Left"},
{"ee", "SWR", "Store Word Right"},
{"ee", "SYNC", "Synchronize Shared Memory"},
{"ee", "SYNC.L", "Synchronize Shared Memory"},
{"ee", "SYNC.P", "Synchronize Shared Memory"},
{"ee", "SYSCALL", "System Call"},
{"ee", "TEQ", "Trap if Equal"},
{"ee", "TEQI", "Trap if Equal Immediate"},
{"ee", "TGE", "Trap if Greater or Equal"},
{"ee", "TGEI", "Trap if Greater or Equal Immediate"},
{"ee", "TGEIU", "Trap if Greater or Equal Immediate Unsigned"},
{"ee", "TGEU", "Trap if Greater or Equal Unsigned"},
{"ee", "TLT", "Trap if Less Than"},
{"ee", "TLTI", "Trap if Less Than Immediate"},
{"ee", "TLTIU", "Trap if Less Than Immediate Unsigned"},
{"ee", "TLTU", "Trap if Less Than Unsigned"},
{"ee", "TNE", "Trap if Not Equal"},
{"ee", "TNEI", "Trap if Not Equal Immediate"},
{"ee", "XOR", "Exclusive OR"},
{"ee", "XORI", "Exclusive OR Immediate"},
{"ee", "DIV1", "Divide Word Pipeline 1"},
{"ee", "DIVU1", "Divide Unsigned Word Pipeline 1"},
{"ee", "LQ", "Load Quadword"},
{"ee", "MADD", "Multiply-Add word"},
{"ee", "MADD1", "Multiply-Add word Pipeline"},
{"ee", "MADDU", "Multiply-Add Unsigned word"},
{"ee", "MADDU1", "Multiply-Add Unsigned word Pipeline"},
{"ee", "MFHI1", "Move From HI1 Register"},
{"ee", "MFLO1", "Move From LO1 Register"},
{"ee", "MFSA", "Move from Shift Amount Register"},
{"ee", "MTHI1", "Move To HI1 Register"},
{"ee", "MTLO1", "Move To LO1 Register"},
{"ee", "MTSA", "Move to Shift Amount Register"},
{"ee", "MTSAB", "Move Byte Count to Shift Amount Register"},
{"ee", "MTSAH", "Move Halfword Count to Shift Amount Register"},
{"ee", "MULT", "Multiply Word"},
{"ee", "MULT1", "Multiply Word Pipeline"},
{"ee", "MULTU", "Multiply Unsigned Word"},
{"ee", "MULTU1", "Multiply Unsigned Word Pipeline"},
{"ee", "PABSH", "Parallel Absolute Halfword"},
{"ee", "PABSW", "Parallel Absolute Word"},
{"ee", "PADDB", "Parallel Add Byte"},
{"ee", "PADDH", "Parallel Add Halfword"},
{"ee", "PADDSB", "Parallel Add with Signed Saturation Byte"},
{"ee", "PADDSH", "Parallel Add with Signed Saturation Halfword"},
{"ee", "PADDSW", "Parallel Add with Signed Saturation Word"},
{"ee", "PADDUB", "Parallel Add with Unsigned Saturation Byte"},
{"ee", "PADDUH", "Parallel Add with Unsigned Saturation Halfword"},
{"ee", "PADDUW", "Parallel Add with Unsigned Saturation Word"},
{"ee", "PADDW", "Parallel Add Word"},
{"ee", "PADSBH", "Parallel Add/Subtract Halfword"},
{"ee", "PAND", "Parallel And"},
{"ee", "PCEQB", "Parallel Compare for Equal Byte"},
{"ee", "PCEQH", "Parallel Compare for Equal Halfword"},
{"ee", "PCEQW", "Parallel Compare for Equal Word"},
{"ee", "PCGTB", "Parallel Compare for Greater Than Byte"},
{"ee", "PCGTH", "Parallel Compare for Greater Than Halfword"},
{"ee", "PCGTW", "Parallel Compare for Greater Than Word"},
{"ee", "PCPYH", "Parallel Copy Halfword"},
{"ee", "PCPYLD", "Parallel Copy Lower Doubleword"},
{"ee", "PCPYUD", "Parallel Copy Upper Doubleword"},
{"ee", "PDIVBW", "Parallel Divide Broadcast Word"},
{"ee", "PDIVUW", "Parallel Divide Unsigned Word"},
{"ee", "PDIVW", "Parallel Divide Word"},
{"ee", "PEXCH", "Parallel Exchange Center Halfword"},
{"ee", "PEXCW", "Parallel Exchange Center Word"},
{"ee", "PEXEH", "Parallel Exchange Even Halfword"},
{"ee", "PEXEW", "Parallel Exchange Even Word"},
{"ee", "PEXT5", "Parallel Extend from 5 bits"},
{"ee", "PEXTLB", "Parallel Extend Lower from Byte"},
{"ee", "PEXTLH", "Parallel Extend Lower from Halfword"},
{"ee", "PEXTLW", "Parallel Extend Lower from Word"},
{"ee", "PEXTUB", "Parallel Extend Upper from Byte"},
{"ee", "PEXTUH", "Parallel Extend Upper from Halfword"},
{"ee", "PEXTUW", "Parallel Extend Upper from Word"},
{"ee", "PHMADH", "Parallel Horizontal Multiply-Add Halfword"},
{"ee", "PHMSBH", "Parallel Horizontal Multiply-Subtract Halfword"},
{"ee", "PINTEH", "Parallel Interleave Even Halfword"},
{"ee", "PINTH", "Parallel Interleave Halfword"},
{"ee", "PLZCW", "Parallel Leading Zero or one Count Word"},
{"ee", "PMADDH", "Parallel Multiply-Add Halfword"},
{"ee", "PMADDUW", "Parallel Multiply-Add Unsigned Word"},
{"ee", "PMADDW", "Parallel Multiply-Add Word"},
{"ee", "PMAXH", "Parallel Maximize Halfword"},
{"ee", "PMAXW", "Parallel Maximize Word"},
{"ee", "PMFHI", "Parallel Move From HI Register"},
{"ee", "PMFHL", "Parallel Move From HI/LO Register"},
{"ee", "PMFHL", "Parallel Move From HI/LO Register"},
{"ee", "PMFHL", "Parallel Move From HI/LO Register"},
{"ee", "PMFHL", "Parallel Move From HI/LO Register"},
{"ee", "PMFHL", "Parallel Move From HI/LO Register"},
{"ee", "PMFLO", "Parallel Move From LO Register"},
{"ee", "PMINH", "Parallel Minimize Halfword"},
{"ee", "PMINW", "Parallel Minimize Word"},
{"ee", "PMSUBH", "Parallel Multiply-Subtract Halfword"},
{"ee", "PMSUBW", "Parallel Multiply-Subtract Word"},
{"ee", "PMTHI", "Parallel Move To HI Register"},
{"ee", "PMTHL", "Parallel Move To HI/LO Register"},
{"ee", "PMTLO", "Parallel Move To LO Register"},
{"ee", "PMULTH", "Parallel Multiply Halfword"},
{"ee", "PMULTUW", "Parallel Multiply Unsigned Word"},
{"ee", "PMULTW", "Parallel Multiply Word"},
{"ee", "PNOR", "Parallel Not Or"},
{"ee", "POR", "Parallel Or"},
{"ee", "PPAC5", "Parallel Pack to 5 bits"},
{"ee", "PPACB", "Parallel Pack to Byte"},
{"ee", "PPACH", "Parallel Pack to Halfword"},
{"ee", "PPACW", "Parallel Pack to Word"},
{"ee", "PREVH", "Parallel Reverse Halfword"},
{"ee", "PROT3W", "Parallel Rotate 3 Words Left"},
{"ee", "PSLLH", "Parallel Shift Left Logical Halfword"},
{"ee", "PSLLVW", "Parallel Shift Left Logical Variable Word"},
{"ee", "PSLLW", "Parallel Shift Left Logical Word"},
{"ee", "PSRAH", "Parallel Shift Right Arithmetic Halfword"},
{"ee", "PSRAVW", "Parallel Shift Right Arithmetic Variable Word"},
{"ee", "PSRAW", "Parallel Shift Right Arithmetic Word"},
{"ee", "PSRLH", "Parallel Shift Right Logical Halfword"},
{"ee", "PSRLVW", "Parallel Shift Right Logical Variable Word"},
{"ee", "PSRLW", "Parallel Shift Right Logical Word"},
{"ee", "PSUBB", "Parallel Subtract Byte"},
{"ee", "PSUBH", "Parallel Subtract Halfword"},
{"ee", "PSUBSB", "Parallel Subtract with Signed saturation Byte"},
{"ee", "PSUBSH", "Parallel Subtract with Signed Saturation Halfword"},
{"ee", "PSUBSW", "Parallel Subtract with Signed Saturation Word"},
{"ee", "PSUBUB", "Parallel Subtract with Unsigned Saturation Byte"},
{"ee", "PSUBUH", "Parallel Subtract with Unsigned Saturation Halfword"},
{"ee", "PSUBUW", "Parallel Subtract with Unsigned Saturation Word"},
{"ee", "PSUBW", "Parallel Subtract Word"},
{"ee", "PXOR", "Parallel Exclusive OR"},
{"ee", "QFSRV", "Quadword Funnel Shift Right Variable"},
{"ee", "SQ", "Store Quadword"},
{"ee", "BC0F", "Branch on Coprocessor 0 False"},
{"ee", "BC0FL", "Branch on Coprocessor 0 False Likely"},
{"ee", "BC0T", "Branch on Coprocessor 0 True"},
{"ee", "BC0TL", "Branch on Coprocessor 0 True Likely"},
{"ee", "cache bfh", "Cache Operation (BTAC Flush)"},
{"ee", "cache bhinbt", "Cache Operation (Hit Invalidate BTAC)"},
{"ee", "cache bxlbt", "Cache Operation (Index Load BTAC)"},
{"ee", "cache bxsbt", "Cache Operation (Index Store BTAC)"},
{"ee", "cache dhin", "Cache Operation (Hit Invalidate)"},
{"ee", "cache dhwbin", "Cache Operation (Hit Writeback Invalidate)"},
{"ee", "cache dhwoin", "Cache Operation (Hit Writeback Without Invalidate)"},
{"ee", "cache dxin", "Cache Operation (Index Invalidate)"},
{"ee", "cache dxldt", "Cache Operation (Index Load Data)"},
{"ee", "cache dxltg", "Cache Operation (Index Load Tag)"},
{"ee", "cache dxsdt", "Cache Operation (Index Store Data)"},
{"ee", "cache dxstg", "Cache Operation (Index Store Tag)"},
{"ee", "cache dxwbin", "Cache Operation (Index Writeback Invalidate)"},
{"ee", "cache ifl", "Cache Operation (Fill)"},
{"ee", "cache ihin", "Cache Operation (Hit Invalidate)"},
{"ee", "cache ixin", "Cache Operation (Index Invalidate)"},
{"ee", "cache ixldt", "Cache Operation (Index Load Data)"},
{"ee", "cache ixltg", "Cache Operation (Index Load Tag)"},
{"ee", "cache ixsdt", "Cache Operation (Index Store Data)"},
{"ee", "cache ixstg", "Cache Operation (Index Store Tag)"},
{"ee", "DI", "Disable Interrupt"},
{"ee", "EI", "Enable Interrupt"},
{"ee", "ERET", "Exception Return"},
{"ee", "MFBPC", "Move from Breakpoint Control Register"},
{"ee", "MFC0", "Move from System Control Coprocessor"},
{"ee", "MFDAB", "Move from Data Address Breakpoint Register"},
{"ee", "MFDABM", "Move from Data Address Breakpoint Mask Register"},
{"ee", "MFDVB", "Move from Data value Breakpoint Register"},
{"ee", "MFDVBM", "Move from Data Value Breakpoint Mask Register"},
{"ee", "MFIAB", "Move from Instruction Address Breakpoint Register"},
{"ee", "MFIABM", "Move from Instruction Address Breakpoint Mask Register"},
{"ee", "MFPC", "Move from Performance Counter"},
{"ee", "MFPS", "Move from Performance Event Specifier"},
{"ee", "MTBPC", "Move to Breakpoint Control Register"},
{"ee", "MTC0", "Move to System Control Coprocessor"},
{"ee", "MTDAB", "Move to Data Address Breakpoint Register"},
{"ee", "MTDABM", "Move to Data Address Breakpoint Mask Register"},
{"ee", "MTDVB", "Move to Data Value Breakpoint Register"},
{"ee", "MTDVBM", "Move to Data Value Breakpoint Mask Register"},
{"ee", "MTIAB", "Move to Instruction Address Breakpoint Register"},
{"ee", "MTIABM", "Move to Instruction Address Breakpoint Mask Register"},
{"ee", "MTPC", "Move to Performance Counter"},
{"ee", "MTPS", "Move to Performance Event Specifier"},
{"ee", "TLBP", "Probe TLB for Matching Entry"},
{"ee", "TLBR", "Read Indexed TLB Entry"},
{"ee", "TLBWI", "Write Index TLB Entry"},
{"ee", "TLBWR", "Write Random TLB Entry"},
{"ee", "ABS", "Floating Point Absolute Value"},
{"ee", "ADD", "Floating Point ADD"},
{"ee", "ADDA", "Floating Point Add to Accumulator"},
{"ee", "BC1F", "Branch on FP False"},
{"ee", "BC1FL", "Branch on FP False Likely"},
{"ee", "BC1T", "Branch on FP True"},
{"ee", "BC1TL", "Branch on FP True Likely"},
{"ee", "C.EQ.S", "Floating Point Compare"},
{"ee", "c.f.s", "Floating Point Compare"},
{"ee", "c.le.s", "Floating Point Compare"},
{"ee", "c.lt.s", "Floating Point Compare"},
{"ee", "CFC1", "Move Control Word from Floating Point"},
{"ee", "CTC1", "Move Control Word to Floating Point"},
{"ee", "CVT", "Fixed-point Convert to Single Floating Point"},
{"ee", "CVT", "Floating Point Convert to Word Fixed-point"},
{"ee", "DIV", "Floating Point Divide"},
{"ee", "LWC1", "Load Word to Floating Point"},
{"ee", "MADD", "Floating Point Multiply-ADD"},
{"ee", "MADDA", "Floating Point Multiply-Add"},
{"ee", "MAX", "Floating Point Maximum"},
{"ee", "MFC1", "Move Word from Floating Point"},
{"ee", "MIN", "Floating Point Minimum"},
{"ee", "MOV", "Floating Point Move"},
{"ee", "MSUB", "Floating Point Multiply and Subtract"},
{"ee", "MSUBA", "Floating Point Multiply and Subtract from Accumulator"},
{"ee", "MTC1", "Move Word to Floating Point"},
{"ee", "MUL", "Floating Point Multiply"},
{"ee", "MULA", "Floating Point Multiply to Accumulator"},
{"ee", "NEG", "Floating Point Negate"},
{"ee", "RSQRT", "Floating Point Reciprocal Square Root"},
{"ee", "SQRT", "Floating Point Square Root"},
{"ee", "SUB", "Floating Point Subtract"},
{"ee", "SUBA", "Floating Point Subtract to Accumulator"},
{"ee", "SWC1", "Store Word from Floating Point"},
{"vu", "ABS", "Absolute Value"},
{"vu", "ADD", "Add"},
{"vu", "ADDi", "Add to I Register"},
{"vu", "ADDq", "Add to Q Register"},
{"vu", "ADDbc", "Broadcast Add"},
{"vu", "ADDA", "Add to Accumulator"},
{"vu", "ADDAi", "Add I Register to Accumulator"},
{"vu", "ADDAq", "Add Q Register to Accumulator"},
{"vu", "ADDAbc", "Broadcast Add to Accumulator"},
{"vu", "CLIP", "Clipping Judgment"},
{"vu", "FTOI0", "Convert to Fixed Point"},
{"vu", "FTOI4", "Convert to Fixed Point"},
{"vu", "FTOI12", "Convert to Fixed Point"},
{"vu", "FTOI15", "Convert to Fixed Point"},
{"vu", "ITOF0", "Convert to Floating-Point Number"},
{"vu", "ITOF4", "Convert to Floating-Point Number"},
{"vu", "ITOF12", "Convert to Floating-Point Number"},
{"vu", "ITOF15", "Convert to Floating-Point Number"},
{"vu", "MADD", "Product Sum"},
{"vu", "MADDi", "Product Sum with I Register"},
{"vu", "MADDq", "Product Sum by Q Register"},
{"vu", "MADDbc", "Broadcast Product Sum"},
{"vu", "MADDA", "Product Sum to Accumulator"},
{"vu", "MADDAi", "Product Sum by I register, to Accumulator"},
{"vu", "MADDAq", "Product Sum by Q Register, to Accumulator"},
{"vu", "MADDAbc", "Broadcast Product Sum to Accumulator"},
{"vu", "MAX", "Maximum Value"},
{"vu", "MAXi", "Maximum Value"},
{"vu", "MAXbc", "Maximum Value"},
{"vu", "MINI", "Minimum Value"},
{"vu", "MINIi", "Minimum Value"},
{"vu", "MINIbc", "Minimum Value"},
{"vu", "MSUB", "Multiply and Subtract"},
{"vu", "MSUBi", "Multiply and Subtract with I Register"},
{"vu", "MSUBq", "Multiply and Subtract by Q Register"},
{"vu", "MSUBbc", "Broadcast Multiply and Subtract"},
{"vu", "MSUBA", "Multiply and Subtract to Accumulator"},
{"vu", "MSUBAi", "Multiply and Subtract with I Register, to Accumulator"},
{"vu", "MSUBAq", "Multiply and Subtract by Q Register, to Accumulator"},
{"vu", "MSUBAbc", "Broadcast Multiply and Subtract to Accumulator"},
{"vu", "MUL", "Multiply"},
{"vu", "MULi", "Multiply by I Register"},
{"vu", "MULq", "Multiply by Q Register"},
{"vu", "MULbc", "Multiply by Broadcast"},
{"vu", "MULA", "Multiply to Accumulator"},
{"vu", "MULAi", "Multiply by I Register, to Accumulator"},
{"vu", "MULAq", "Multiply by Q Register, to Accumulator"},
{"vu", "MULAbc", "Broadcast Multiply by broadcast, to Accumulator"},
{"vu", "NOP", "No Operation"},
{"vu", "OPMULA", "Vector Outer Product"},
{"vu", "OPMSUB", "Vector Outer Product"},
{"vu", "SUB", "Subtract"},
{"vu", "SUBi", "Subtract I Register"},
{"vu", "SUBq", "Subtract Q Register"},
{"vu", "SUBbc", "Broadcast Subtract"},
{"vu", "SUBA", "Substract to Accumulator"},
{"vu", "SUBAi", "Subtract I Register to Accumulator"},
{"vu", "SUBAq", "Subtract Q Register to Accumulator"},
{"vu", "SUBAbc", "Broadcast Subtract to Accumulator"},
{"vu", "B", "Unconditional Branch"},
{"vu", "BAL", "Unconditional Branch with Saving Address"},
{"vu", "DIV", "Divide"},
{"vu", "EATAN", "Arctangent"},
{"vu", "EATANxy", "Arctangent"},
{"vu", "EATANxz", "Arctangent"},
{"vu", "EEXP", "Exponent"},
{"vu", "ELENG", "Length"},
{"vu", "ERCPR", "Reciprocal Number"},
{"vu", "ERLENG", "Reciprocal Number of Length"},
{"vu", "ERSADD", "Reciprocal Number"},
{"vu", "ERSQRT", "Reciprocal Number of Square Root"},
{"vu", "ESADD", "Sum of Square Numbers"},
{"vu", "ESIN", "Sine"},
{"vu", "ESQRT", "Square Root"},
{"vu", "ESUM", "Sum of Each Field"},
{"vu", "FCAND", "Test Clipping Flag"},
{"vu", "FCEQ", "Test Clipping Flag"},
{"vu", "FCGET", "Get Clipping Flag"},
{"vu", "FCOR", "Test Clipping Flag"},
{"vu", "FCSET", "Setting Clipping Flag"},
{"vu", "FMAND", "Test MAC Flag Check"},
{"vu", "FMEQ", "Test MAC Flag Check"},
{"vu", "FMOR", "Test MAC Flag Check"},
{"vu", "FSAND", "Test Status Flag Check"},
{"vu", "FSEQ", "Test Status Flag Check"},
{"vu", "FSOR", "Test Status Flag"},
{"vu", "FSSET", "Set Sticky Flags"},
{"vu", "IADD", "ADD Integer"},
{"vu", "IADDI", "Add Immediate Value Integer"},
{"vu", "IADDIU", "Add Immediate Integer"},
{"vu", "IAND", "Logical Product"},
{"vu", "IBEQ", "Conditional Branch"},
{"vu", "IBGEZ", "Conditional Branch"},
{"vu", "IBGTZ", "Conditional Branch"},
{"vu", "IBLEZ", "Conditional Branch"},
{"vu", "IBLTZ", "Conditional Branch"},
{"vu", "IBNE", "Conditional Branch"},
{"vu", "ILW", "Integer Load with Offset Specification"},
{"vu", "ILWR", "Integer Load"},
{"vu", "IOR", "Logical Sum"},
{"vu", "ISUB", "Integer Subtract"},
{"vu", "ISUBIU", "Immediate Value Integer Subtract"},
{"vu", "ISW", "Integer Store with Offset"},
{"vu", "ISWR", "Integer Store"},
{"vu", "JALR", "Unconditional Jump with Address Saving"},
{"vu", "JR", "Unconditional Jump"},
{"vu", "LQ", "Load Qword"},
{"vu", "LQD", "Load Qword with Pre-Decrement"},
{"vu", "LQI", "Load with Post-Increment"},
{"vu", "MFIR", "Move from Integer Register to Floating-Point Register"},
{"vu", "MFP", "Move from P Register to Floating-Point Register"},
{"vu", "MOVE", "Transfer between Floating-Point Registers"},
{"vu", "MR32", "Move with Rotate"},
{"vu", "MTIR", "Move from Floating-Point Register to Integer Register"},
{"vu", "RGET", "Get Random Number"},
{"vu", "RINIT", "Random Number Intialize"},
{"vu", "RNEXT", "Next Random Number"},
{"vu", "RSQRT", "Square Root Division"},
{"vu", "RXOR", "Random Number Set"},
{"vu", "SQ", "Store Qword with Offset"},
{"vu", "SQD", "Store Qword with Pre-Decrement"},
{"vu", "SQI", "Store with Post-Increment"},
{"vu", "SQRT", "Square Root"},
{"vu", "WAITP", "P Register Syncronize"},
{"vu", "WAITQ", "Q Register Syncronize"},
{"vu", "XGKICK", "GIF Control"},
{"vu", "XITOP", "VIF Control"},
{"vu", "XTOP", "VIF Control"},
{"vu", "BC2F", "Branch on COP2 Conditional Signal"},
{"vu", "BC2FL", "Branch on COP2 Conditional Signal"},
{"vu", "BC2T", "Branch on COP2 Conditional Signal"},
{"vu", "BC2TL", "Branch on COP2 Conditional signal"},
{"vu", "CFC2", "Transfer Integer Data from VU to EE Core"},
{"vu", "CTC2", "Transfer Integer Data from EE Core to VU"},
{"vu", "LQC2", "Floating-Point Data Transfer from EE Core to VU"},
{"vu", "QMFC2", "Floating-Point Data Transfer from VU to EE Core"},
{"vu", "QMTC2", "Floating-Point Data Transfer from EE Core to VU"},
{"vu", "SQC2", "Floating-Point Data Transfer from VU to EE Core"},
{"vu", "VABS", "Absolute Value"},
{"vu", "VADD", "Add"},
{"vu", "VADDi", "Add to I Register"},
{"vu", "VADDq", "Add to Q Register"},
{"vu", "VADDbc", "Broadcast Add"},
{"vu", "VADDA", "Add to Accumulator"},
{"vu", "VADDAi", "Add I Register to Accumulator"},
{"vu", "VADDAq", "Add Q Register to Accumulator"},
{"vu", "VADDAbc", "Broadcast Add to Accumulator"},
{"vu", "VCALLMS", "Start Micro Sub-Routine"},
{"vu", "VCALLMSR", "Start Micro Sub-Routine by Register"},
{"vu", "VCLIP", "Clipping Judgment"},
{"vu", "VDIV", "Divide"},
{"vu", "VFTOI0", "Conversion to Fixed Point"},
{"vu", "VFTOI4", "Conversion to Fixed Point"},
{"vu", "VFTOI12", "Conversion to Fixed Point"},
{"vu", "VFTOI15", "Conversion to Fixed Point"},
{"vu", "VIADD", "Add Integer"},
{"vu", "VIADDI", "Add Immediate Value Integer"},
{"vu", "VIAND", "Logical Product"},
{"vu", "VILWR", "Integer Load"},
{"vu", "VIOR", "Logical Sum"},
{"vu", "VISUB", "Integer Subtract"},
{"vu", "VISWR", "Integer Store"},
{"vu", "VITOF0", "Conversion to Floating-Point Number"},
{"vu", "VITOF4", "Conversion to Floating-Point Number"},
{"vu", "VITOF12", "Conversion to Floating-Point Number"},
{"vu", "VITOF15", "Conversion to Floating-Point Number"},
{"vu", "VLQD", "Load with Pre-Decrement"},
{"vu", "VLQI", "Load with Post-Increment"},
{"vu", "VMADD", "Product Sum"},
{"vu", "VMADDi", "Product Sum with I Register"},
{"vu", "VMADDq", "Product Sum with Q Register"},
{"vu", "VMADDbc", "Broadcast Product Sum"},
{"vu", "VMADDA", "Product Sum to Accumulator"},
{"vu", "VMADDAi", "Product Sum with I Register, to Accumulator"},
{"vu", "VMADDAq", "Product Sum with Q Register, to Accumulator"},
{"vu", "VMADDAbc", "Broadcast Product Sum to Accumulator"},
{"vu", "VMAX", "Maximum Value"},
{"vu", "VMAXi", "Maximum Value"},
{"vu", "VMAXbc", "Maximum Value"},
{"vu", "VMFIR", "Transfer from Integer Register to Floating-Point Register"},
{"vu", "VMINI", "Minimum Value"},
{"vu", "VMINIi", "Minimum Value"},
{"vu", "VMINIbc", "Minimum Value"},
{"vu", "VMOVE", "Transfer between Floating-Point Registers"},
{"vu", "VMR32", "Vector Rotate"},
{"vu", "VMSUB", "Multiply and Subtract"},
{"vu", "VMSUBi", "Multiply and Subtract with I Register"},
{"vu", "VMSUBq", "Multiply and Subtract Q Register"},
{"vu", "VMSUBbc", "Broadcast Multiply and Subtract"},
{"vu", "VMSUBA", "Multiply and Subtract to Accumulator"},
{"vu", "VMSUBAi", "Multiply and Subtract with I Register, to Accumulator"},
{"vu", "VMSUBAq", "Multiply and Subtract with Q Register, to Accumulator"},
{"vu", "VMSUBAbc", "Broadcast Multiply and Subtract to Accumulator"},
{"vu", "VMTIR", "Transfer from Floating-Point Register to Integer Register"},
{"vu", "VMUL", "Multiply"},
{"vu", "VMULi", "Multiply by I Register"},
{"vu", "VMULq", "Multiply by Q Register"},
{"vu", "VMULbc", "Broadcast Multiply"},
{"vu", "VMULA", "Multiply to Accumulator"},
{"vu", "VMULAi", "Multiply by I Register to Accumulator"},
{"vu", "VMULAq", "Multiply by Q Register to Accumulator"},
{"vu", "VMULAbc", "Broadcast Multiply to Accumulator"},
{"vu", "VNOP", "No Operation"},
{"vu", "VOPMULA", "Vector Outer Product"},
{"vu", "VOPMSUB", "Vector Outer Product"},
{"vu", "VRGET", "Get Random Numbers"},
{"vu", "VRINIT", "Random Number Initial Set"},
{"vu", "VRNEXT", "New Random Numbers"},
{"vu", "VRSQRT", "Square Root Division"},
{"vu", "VRXOR", "Random Number Set"},
{"vu", "VSQD", "Store with Pre-Decrement"},
{"vu", "VSQI", "Store with Post-Increment"},
{"vu", "VSQRT", "Square Root"},
{"vu", "VSUB", "Subtract"},
{"vu", "VSUBi", "Subtract I Register"},
{"vu", "VSUBq", "Subtract Q Register"},
{"vu", "VSUBbc", "Broadcast Subtract"},
{"vu", "VSUBA", "Subtract to Accumulator"},
{"vu", "VSUBAi", "Subtract I Register to Accumulator"},
{"vu", "VSUBAq", "Subtract Q Register to Accumulator"},
{"vu", "VSUBAbc", "Broadcast Subtract to Accumulator"},
{"vu", "VWAITQ", "Q Register Synchronize"},
};
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <vector>
namespace LSPData {
struct MIPSInstruction {
std::string type;
std::string mnemonic;
std::string description;
};
extern const std::vector<MIPSInstruction> MIPS_INSTRUCTION_LIST;
} // namespace LSPData
+289
View File
@@ -0,0 +1,289 @@
#include "workspace.h"
#include <regex>
#include "common/log/log.h"
#include "lsp/protocol/common_types.h"
LSPSpec::DocumentUri uri_from_path(fs::path path) {
// Replace slash type on windows
std::string path_str = path.string();
#ifdef _WIN32
std::replace(path_str.begin(), path_str.end(), '\\', '/');
#endif
return fmt::format("file:///{}", path_str);
}
Workspace::Workspace(){};
Workspace::~Workspace(){};
bool Workspace::is_initialized() {
return m_initialized;
};
void Workspace::set_initialized(bool new_value) {
m_initialized = new_value;
}
std::optional<WorkspaceIRFile> Workspace::get_tracked_ir_file(const LSPSpec::URI& file_uri) {
if (m_tracked_ir_files.count(file_uri) == 0) {
return {};
}
return m_tracked_ir_files[file_uri];
}
std::optional<goos::TextDb::ShortInfo> Workspace::get_symbol_info_from_all_types(
const std::string& symbol_name,
const LSPSpec::DocumentUri& all_types_uri) {
if (m_tracked_all_types_files.count(all_types_uri) == 0) {
return {};
}
const auto& dts = m_tracked_all_types_files[all_types_uri].m_dts;
if (dts.symbol_definition_info.count(symbol_name) == 0) {
return {};
}
return dts.symbol_definition_info.at(symbol_name);
}
void Workspace::start_tracking_file(const LSPSpec::DocumentUri& file_uri,
const std::string& language_id,
const std::string& content) {
if (language_id == "opengoal-ir") {
lg::debug("new ir file");
WorkspaceIRFile file(content);
lg::debug("parsed!");
m_tracked_ir_files[file_uri] = file;
if (!file.m_all_types_uri.empty()) {
if (m_tracked_all_types_files.count(file.m_all_types_uri) == 0) {
lg::debug("new all-types file");
m_tracked_all_types_files[file.m_all_types_uri] = WorkspaceAllTypesFile(
file.m_all_types_uri, file.m_game_version, file.m_all_types_file_path);
m_tracked_all_types_files[file.m_all_types_uri].parse_type_system();
}
}
}
// TODO - only supporting IR files currently!
}
void Workspace::update_tracked_file(const LSPSpec::DocumentUri& file_uri,
const std::string& content) {
// Check if the file is already tracked or not, this is done because change events don't give
// language details it's assumed you are keeping track of that!
if (m_tracked_ir_files.count(file_uri) != 0) {
WorkspaceIRFile file(content);
m_tracked_ir_files[file_uri] = file;
// There is the potential for the all-types to have changed, albeit this is probably never going
// to happen
if (!file.m_all_types_uri.empty() &&
m_tracked_all_types_files.count(file.m_all_types_uri) == 0) {
auto& all_types_file = m_tracked_all_types_files[file.m_all_types_uri];
all_types_file.m_file_path = file.m_all_types_file_path;
all_types_file.m_uri = file.m_all_types_uri;
all_types_file.m_game_version = file.m_game_version;
all_types_file.update_type_system();
}
}
if (m_tracked_all_types_files.count(file_uri) != 0) {
// If the all-types file has changed, re-parse it
// NOTE - this assumes its still for the same game version!
m_tracked_all_types_files[file_uri].update_type_system();
}
};
void Workspace::stop_tracking_file(const LSPSpec::DocumentUri& file_uri) {
if (m_tracked_ir_files.count(file_uri) != 0) {
m_tracked_ir_files.erase(file_uri);
}
if (m_tracked_all_types_files.count(file_uri) != 0) {
m_tracked_all_types_files.erase(file_uri);
}
}
WorkspaceIRFile::WorkspaceIRFile(const std::string& content) {
// Get all lines of file
std::string::size_type pos = 0;
std::string::size_type prev = 0;
// TODO - i hate this assignment inside a conditional, get rid of it
while ((pos = content.find('\r\n', prev)) != std::string::npos) {
std::string line = content.substr(prev, pos - prev);
m_lines.push_back(line);
// Run any checks on that line
find_all_types_path(line);
find_function_symbol(m_lines.size() - 1, line);
identify_diagnostics(m_lines.size() - 1, line);
prev = pos + 1;
}
std::string line = content.substr(prev);
m_lines.push_back(line);
find_function_symbol(m_lines.size() - 1, line);
identify_diagnostics(m_lines.size() - 1, line);
lg::info("Added new file. {} lines with {} symbols and {} diagnostics", m_lines.size(),
m_symbols.size(), m_diagnostics.size());
}
// This is kind of a hack, but to ensure consistency. The file will reference the all-types.gc
// file it was generated with, this lets us accurately jump to the definition properly!
void WorkspaceIRFile::find_all_types_path(const std::string& line) {
std::regex regex("; ALL_TYPES=(.*)=(.*)");
std::smatch matches;
if (std::regex_search(line, matches, regex)) {
if (matches.size() == 3) {
auto game_version = matches[1];
auto all_types_path = matches[2];
lg::debug("Found DTS Path - {} : {}", game_version.str(), all_types_path.str());
auto all_types_uri = uri_from_path(fs::path(all_types_path.str()));
lg::debug("DTS URI - {}", all_types_uri);
if (valid_game_version(game_version.str())) {
lg::debug("a");
m_game_version = game_name_to_version(game_version.str());
lg::debug("b");
m_all_types_uri = all_types_uri;
m_all_types_file_path = fs::path(all_types_path.str());
} else {
lg::error("Invalid game version, ignoring - {}", game_version.str());
}
}
}
}
void WorkspaceIRFile::find_function_symbol(const uint32_t line_num_zero_based,
const std::string& line) {
std::regex regex("; \\.function (.*)");
std::smatch matches;
if (std::regex_search(line, matches, regex)) {
// NOTE - assumes we can only find 1 function per line
if (matches.size() == 2) {
auto match = matches[1];
lg::info("Adding Symbol - {}", match.str());
LSPSpec::DocumentSymbol new_symbol;
new_symbol.m_name = match.str();
// TODO - function doc-string
// new_symbol.m_detail = ...
new_symbol.m_kind = LSPSpec::SymbolKind::Function;
LSPSpec::Range symbol_range;
symbol_range.m_start = {line_num_zero_based, 0};
symbol_range.m_end = {line_num_zero_based, 0}; // NOTE - set on the next function
new_symbol.m_range = symbol_range;
LSPSpec::Range symbol_selection_range;
symbol_selection_range.m_start = {line_num_zero_based, 0};
symbol_selection_range.m_end = {line_num_zero_based, (uint32_t)line.length() - 1};
new_symbol.m_selectionRange = symbol_selection_range;
m_symbols.push_back(new_symbol);
}
}
std::regex end_function("^;; \\.endfunction\\s*$");
if (std::regex_match(line, end_function)) {
lg::info("Found end of previous function on line - {}", line);
// Set the previous symbols end-line
if (!m_symbols.empty()) {
m_symbols[m_symbols.size() - 1].m_range.m_end.m_line = line_num_zero_based - 1;
}
}
}
void WorkspaceIRFile::identify_diagnostics(const uint32_t line_num_zero_based,
const std::string& line) {
std::regex info_regex(";; INFO: (.*)");
std::regex warn_regex(";; WARN: (.*)");
std::smatch info_matches;
std::smatch warn_matches;
LSPSpec::Range diag_range;
diag_range.m_start = {line_num_zero_based, 0};
diag_range.m_end = {line_num_zero_based, (uint32_t)line.length() - 1};
// Check for an info-level warnings
if (std::regex_search(line, info_matches, info_regex)) {
// NOTE - assumes we can only find 1 function per line
if (info_matches.size() == 2) {
auto match = info_matches[1];
lg::debug("Found info-level diagnostic - {}", match.str());
LSPSpec::Diagnostic new_diag;
new_diag.m_severity = LSPSpec::DiagnosticSeverity::Information;
new_diag.m_message = match.str();
new_diag.m_range = diag_range;
new_diag.m_source = "OpenGOAL LSP";
m_diagnostics.push_back(new_diag);
return;
}
}
// Check for a warn level warnings
if (std::regex_search(line, warn_matches, warn_regex)) {
// NOTE - assumes we can only find 1 function per line
if (warn_matches.size() == 2) {
auto match = warn_matches[1];
lg::debug("Found warn-level diagnostic - {}", match.str());
LSPSpec::Diagnostic new_diag;
new_diag.m_severity = LSPSpec::DiagnosticSeverity::Error;
new_diag.m_message = match.str();
new_diag.m_range = diag_range;
new_diag.m_source = "OpenGOAL LSP";
m_diagnostics.push_back(new_diag);
return;
}
}
}
std::optional<std::string> WorkspaceIRFile::get_mips_instruction_at_position(
const LSPSpec::Position position) {
// Split the line on typical word boundaries
std::string line = m_lines.at(position.m_line);
std::smatch matches;
std::regex regex("[\\w\\.]+");
if (std::regex_search(line, matches, regex)) {
auto match = matches[0];
lg::info("hover first match - {}", match.str());
auto match_start = matches.position(0);
auto match_end = match_start + match.length();
if (position.m_character >= match_start && position.m_character <= match_end) {
return match;
}
}
return {};
}
std::optional<std::string> WorkspaceIRFile::get_symbol_at_position(
const LSPSpec::Position position) {
// Split the line on typical word boundaries
std::string line = m_lines.at(position.m_line);
lg::debug("symbol checking - '{}'", line);
std::smatch matches;
std::regex regex("[\\w\\.\\-_!<>*]+");
std::regex_token_iterator<std::string::iterator> rend;
std::regex_token_iterator<std::string::iterator> match(line.begin(), line.end(), regex);
while (match != rend) {
lg::debug("match - '{}'", match->str());
auto match_start = std::distance(line.begin(), match->first);
auto match_end = match_start + match->length();
if (position.m_character >= match_start && position.m_character <= match_end) {
lg::debug("returning");
return match->str();
}
match++;
}
return {};
}
void WorkspaceAllTypesFile::parse_type_system() {
lg::debug("DTS Loading - '{}'", m_file_path.string());
m_dts.parse_type_defs({m_file_path.string()});
lg::debug("DTS Loaded At - '{}'", m_file_path.string());
}
void WorkspaceAllTypesFile::update_type_system() {
m_dts = decompiler::DecompilerTypeSystem(m_game_version);
parse_type_system();
}
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <optional>
#include <string>
#include <unordered_map>
#include "common/util/FileUtil.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "lsp/protocol/common_types.h"
#include "lsp/protocol/document_diagnostics.h"
#include "lsp/protocol/document_symbols.h"
class WorkspaceIRFile {
public:
WorkspaceIRFile(){};
WorkspaceIRFile(const std::string& content);
// TODO - make private
int32_t version;
std::vector<std::string> m_lines;
std::vector<LSPSpec::DocumentSymbol> m_symbols;
std::vector<LSPSpec::Diagnostic> m_diagnostics;
GameVersion m_game_version;
LSPSpec::DocumentUri m_all_types_uri = "";
fs::path m_all_types_file_path;
std::optional<std::string> get_mips_instruction_at_position(const LSPSpec::Position position);
std::optional<std::string> get_symbol_at_position(const LSPSpec::Position position);
private:
void find_all_types_path(const std::string& line);
void find_function_symbol(const uint32_t line_num_zero_based, const std::string& line);
/// @brief Make any relevant diagnostics on the IR line.
/// It's assumed each line in an IR can have atmost one diagnostic, and they are contained to just
/// that line!
/// @param line_num_zero_based
/// @param line
void identify_diagnostics(const uint32_t line_num_zero_based, const std::string& line);
};
class WorkspaceAllTypesFile {
public:
WorkspaceAllTypesFile() : m_dts(GameVersion::Jak1){};
WorkspaceAllTypesFile(const LSPSpec::DocumentUri& uri,
const GameVersion version,
const fs::path file_path)
: m_uri(uri), m_game_version(version), m_dts(m_game_version), m_file_path(file_path){};
GameVersion m_game_version;
LSPSpec::DocumentUri m_uri;
decompiler::DecompilerTypeSystem m_dts;
fs::path m_file_path;
void parse_type_system();
void update_type_system();
};
class Workspace {
public:
Workspace();
virtual ~Workspace();
bool is_initialized();
void set_initialized(bool new_value);
void start_tracking_file(const LSPSpec::DocumentUri& file_uri,
const std::string& language_id,
const std::string& content);
void update_tracked_file(const LSPSpec::DocumentUri& file_uri, const std::string& content);
void stop_tracking_file(const LSPSpec::DocumentUri& file_uri);
std::optional<WorkspaceIRFile> get_tracked_ir_file(const LSPSpec::URI& file_uri);
std::optional<goos::TextDb::ShortInfo> get_symbol_info_from_all_types(
const std::string& symbol_name,
const std::string& all_types_uri);
private:
bool m_initialized = false;
std::unordered_map<LSPSpec::DocumentUri, WorkspaceIRFile> m_tracked_ir_files = {};
std::unordered_map<LSPSpec::DocumentUri, WorkspaceAllTypesFile> m_tracked_all_types_files = {};
};
+109
View File
@@ -0,0 +1,109 @@
/*
https://github.com/svenstaro/glsl-language-server/blob/master/LICENSE
MIT License
Copyright (c) 2017 Sven-Hendrik Haase
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "stdio.h"
#include "common/log/log.h"
#include "common/util/FileUtil.h"
MessageBuffer::MessageBuffer() {}
MessageBuffer::~MessageBuffer() {}
void MessageBuffer::handle_char(char c) {
m_raw_message += c;
if (!m_reading_content) {
auto [header_name, header_value] = try_parse_header(m_raw_message);
// Check whether we were actually able to parse a header.
// If so, add it to our known headers.
// We'll also reset our string then.
if (!header_name.empty()) {
lg::debug("found header!");
m_headers[header_name] = header_value;
m_raw_message.clear();
}
}
// A sole \r\n is the separator between the header block and the body block
// but we don't need it.
if (m_raw_message == "\r\n") {
m_raw_message.clear();
m_is_header_done = true;
m_reading_content = true;
lg::debug("Header complete, content length: {}", m_headers["Content-Length"]);
}
if (m_is_header_done) {
// Now that we know that we're in the body, we just have to count until
// we reach the length of the body as provided in the Content-Length
// header.
auto content_length = std::stoi(m_headers["Content-Length"]);
if (m_raw_message.length() == content_length) {
m_body = json::parse(m_raw_message);
m_reading_content = false;
}
}
}
const std::map<std::string, std::string>& MessageBuffer::headers() const {
return m_headers;
}
const json& MessageBuffer::body() const {
return m_body;
}
const std::string& MessageBuffer::raw() const {
return m_raw_message;
}
bool MessageBuffer::message_completed() {
if (m_is_header_done && !m_body.empty()) {
return true;
}
return false;
}
std::tuple<std::string, std::string> MessageBuffer::try_parse_header(std::string& message) const {
auto eol_pos = message.find("\r\n");
if (eol_pos != std::string::npos) {
std::string header_string = message.substr(0, eol_pos);
auto delim_pos = header_string.find(":");
if (delim_pos != std::string::npos) {
std::string header_name = header_string.substr(0, delim_pos);
std::string header_value = header_string.substr(delim_pos + 1);
return std::make_tuple(header_name, header_value);
}
}
return std::make_tuple(std::string{}, std::string{});
}
void MessageBuffer::clear() {
m_raw_message.clear();
m_headers.clear();
m_body.clear();
m_is_header_done = false;
}
+58
View File
@@ -0,0 +1,58 @@
/*
https://github.com/svenstaro/glsl-language-server/blob/master/LICENSE
MIT License
Copyright (c) 2017 Sven-Hendrik Haase
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <string>
#include <tuple>
#include "third-party/json.hpp"
using json = nlohmann::json;
class MessageBuffer {
public:
MessageBuffer();
virtual ~MessageBuffer();
void handle_char(char c);
const std::map<std::string, std::string>& headers() const;
const json& body() const;
const std::string& raw() const;
bool message_completed();
void clear();
private:
std::tuple<std::string, std::string> try_parse_header(std::string& message) const;
std::string m_raw_message;
std::map<std::string, std::string> m_headers;
json m_body;
// This is set once a sole \r\n is encountered because it denotes that the
// header is done.
bool m_is_header_done = false;
bool m_reading_content = false;
};