mirror of
https://github.com/open-goal/jak-project
synced 2026-08-12 03:49:08 -04:00
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:
@@ -63,3 +63,4 @@ jobs:
|
||||
./build/goalc/goalc
|
||||
./build/decompiler/extractor
|
||||
./build/game/gk
|
||||
./build/lsp/lsp
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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" : []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);*/
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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>());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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", {}},
|
||||
}}};
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "workspace.h"
|
||||
|
||||
struct AppState {
|
||||
Workspace workspace;
|
||||
bool verbose;
|
||||
};
|
||||
@@ -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"},
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 = {};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user