From 9d09c800055fc020997bd39686f56bdc22a19841 Mon Sep 17 00:00:00 2001 From: Gary Lafortune Date: Fri, 10 Sep 2021 18:20:09 -0500 Subject: [PATCH 1/4] First attempt at Joao hook-up The Joao/main repository is now a Git submodule within /joao_repo, especially prepared to work as this hunk of code compiled along with the rest of the extools DLL. --- .gitmodules | 3 + byond-extools/CMakeLists.txt | 5 +- byond-extools/src/joao/joao.cpp | 146 ++++++++++++++++++++++++ byond-extools/src/joao/joao.h | 5 + byond-extools/src/joao/joao_exports.cpp | 11 ++ byond-extools/src/joao/joao_repo | 1 + 6 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 100644 byond-extools/src/joao/joao.cpp create mode 100644 byond-extools/src/joao/joao.h create mode 100644 byond-extools/src/joao/joao_exports.cpp create mode 160000 byond-extools/src/joao/joao_repo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4a2c2c43 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "byond-extools/src/joao/joao_repo"] + path = byond-extools/src/joao/joao_repo + url = https://github.com/Altoids1/Joao.git diff --git a/byond-extools/CMakeLists.txt b/byond-extools/CMakeLists.txt index cb42f2a1..61dfa77d 100644 --- a/byond-extools/CMakeLists.txt +++ b/byond-extools/CMakeLists.txt @@ -15,19 +15,20 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) if (WIN32) - file(GLOB_RECURSE SRC_FILES + file(GLOB_RECURSE SRC_FILES LIST_DIRECTORIES true "${SRC_DIR}/*.cpp" "${SRC_DIR}/*.h" "${SRC_DIR}/*.hpp" "${SRC_DIR}/*.s" ) else() - file(GLOB_RECURSE SRC_FILES + file(GLOB_RECURSE SRC_FILES LIST_DIRECTORIES true "${SRC_DIR}/*.cpp" "${SRC_DIR}/*.h" "${SRC_DIR}/*.hpp" ) endif() +list(FILTER SRC_FILES EXCLUDE REGEX ".*joao_repo.*") ## For Joćo, so we can do some manual inclusion in joao.cpp set(SRC_FILES ${SRC_FILES} "${SRC_DIR}/third_party/subhook/subhook.c") add_library(byond-extools SHARED ${SRC_FILES}) diff --git a/byond-extools/src/joao/joao.cpp b/byond-extools/src/joao/joao.cpp new file mode 100644 index 00000000..947f7e49 --- /dev/null +++ b/byond-extools/src/joao/joao.cpp @@ -0,0 +1,146 @@ +#include "joao.h" +#include "../core/core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace // To make absolutely sure no other part of the code tries to link to all this shit +{ + namespace joao + { +#define JOAO_NO_INCLUDE_STD // A kludge necessary to avoid namespace problems associated with wrapping all of Joćo in this funky namespace block +#define JOAO_SAFE //not-as-much-of-a kludge used to enable throttling and disable some dangerous libraries such as the /file Object type +#include "./joao_repo/AST.cpp" +#include "./joao_repo/Directory.cpp" +#include "./joao_repo/Interpreter.cpp" + +#include "./joao_repo/nativefuncs/error.cpp" +#include "./joao_repo/nativefuncs/math.cpp" +#include "./joao_repo/nativefuncs/string.cpp" +#include "./joao_repo/nativefuncs/tablelib.cpp" + +#include "./joao_repo/Object.cpp" +#include "./joao_repo/Parser.cpp" +#include "./joao_repo/Program.cpp" +#include "./joao_repo/Scanner.cpp" +#include "./joao_repo/Table.cpp" +#undef JOAO_NO_INCLUDE_STD +#undef JOAO_SAFE + } +} + +//for the sake of being explicit, since there is a minor name collision going on here +using ByondValue = Value; +using JoaoValue = joao::Value; + +namespace +{ + namespace ID + { + int data; + int content; + int message; + int frequency; + int name; + } +} + + +const char* enable_joao() +{ + // get the var IDs for SANIC SPEED + ID::data = Core::GetStringId("data", true); + ID::content = Core::GetStringId("content", true); + ID::message = Core::GetStringId("message", true); + ID::frequency = Core::GetStringId("frequency", true); + ID::name = Core::GetStringId("name", true); + + //Set up hooks + Core::get_proc("/proc/run_script").hook(run_script); + return "ok"; +} + +trvh run_script(unsigned int argn, ByondValue* args, ByondValue src) +{ + using namespace joao; + if(argn < 2) // If we weren't given a script or signal + return ByondValue::False(); + + ByondValue& script = args[0]; + if(script.type != DataType::STRING) // If what we were given can't be a script + return ByondValue::False(); + + ByondValue& signal = args[1]; + if(signal.type != DataType::DATUM) // If what we were given isn't even a datum (signals are, this comment upholding, /datum/signal/subspace/vocal) + return ByondValue::False(); + + std::istringstream stream = std::istringstream(std::string(script)); + + Scanner scanner; + scanner.scan(stream); + Parser pears(scanner); + + //Tries to follow the structure of the script_signal var from NTSL + //The default values are not based on the actual incoming signal, since they are used whenever *any* /signal Object is instantiated within Joao. + ObjectType signal_type("signal"); + JoaoValue empty_string = JoaoValue(std::string("")); + signal_type.set_typeproperty_raw("content", empty_string); + signal_type.set_typeproperty_raw("freq", 0); + signal_type.set_typeproperty_raw("source", empty_string); + + + pears.IncludeAlienType(&signal_type); + Program parsed = pears.parse(); + + Interpreter interpreter(parsed, false); + + //Constructing the Joćo Object to be passed as the argument + Object* signal_obj = signal_type.makeObject(interpreter, {}); + signal_obj->set_property_raw("content", JoaoValue(Core::GetStringFromId(signal.get_by_id(ID::data).get_by_id(ID::message).value))); + signal_obj->set_property_raw("freq", JoaoValue(signal.get_by_id(ID::frequency).valuef)); // I think? + signal_obj->set_property_raw("source", JoaoValue(Core::GetStringFromId(signal.get_by_id(ID::name).value))); + + JoaoValue jargs = interpreter.makeBaseTable({ JoaoValue(signal_obj) }, {}, nullptr); + + //Execute! + JoaoValue ret = interpreter.execute(parsed, jargs); + if (ret.t_vType != JoaoValue::vType::Object || ret.t_value.as_object_ptr->object_type != "/signal") // If the program returned something that isn't a signal object + { + return ByondValue::False(); + } + Object*& retptr = ret.t_value.as_object_ptr; // $#(@$*@!!!! + + //Content typecheck & set + JoaoValue& ret_content = retptr->get_property_raw("content"); + if (ret_content.t_vType != JoaoValue::vType::String) + { + return ByondValue::False(); + } + signal.get_by_id(ID::data).set_by_id(ID::message, *ret_content.t_value.as_string_ptr); + + //Frequency typecheck & set + JoaoValue& ret_freq = retptr->get_property_raw("freq"); + if (ret_freq.t_vType != JoaoValue::vType::Integer) + { + return ByondValue::False(); + } + signal.get_by_id(ID::data).set_by_id(ID::frequency, ret_freq.t_value.as_int); + + //Source typecheck & set + JoaoValue& ret_source = retptr->get_property_raw("source"); + if (ret_source.t_vType != JoaoValue::vType::String) + { + return ByondValue::False(); + } + signal.get_by_id(ID::data).set_by_id(ID::name, *ret_source.t_value.as_string_ptr); + + return ByondValue::True(); +} + + diff --git a/byond-extools/src/joao/joao.h b/byond-extools/src/joao/joao.h new file mode 100644 index 00000000..eb745bca --- /dev/null +++ b/byond-extools/src/joao/joao.h @@ -0,0 +1,5 @@ +#pragma once +#include "../core/byond_structures.h" +trvh run_script(unsigned int, Value*, Value); // Joćo also has a Value class, so... + +const char* enable_joao(); \ No newline at end of file diff --git a/byond-extools/src/joao/joao_exports.cpp b/byond-extools/src/joao/joao_exports.cpp new file mode 100644 index 00000000..1534a89e --- /dev/null +++ b/byond-extools/src/joao/joao_exports.cpp @@ -0,0 +1,11 @@ +#include "../core/core.h" +#include "joao.h" + +extern "C" EXPORT const char* init_joao(int n_args, const char** args) +{ + if (!Core::initialize()) + { + return "Extools Init Failed"; + } + return enable_joao(); +} \ No newline at end of file diff --git a/byond-extools/src/joao/joao_repo b/byond-extools/src/joao/joao_repo new file mode 160000 index 00000000..8a0c36e3 --- /dev/null +++ b/byond-extools/src/joao/joao_repo @@ -0,0 +1 @@ +Subproject commit 8a0c36e376fe380b9f6f29b6a5c95019690ca994 From d70985155a9f3b47bea64e93748afaa025909239 Mon Sep 17 00:00:00 2001 From: Gary Lafortune Date: Fri, 10 Sep 2021 18:20:35 -0500 Subject: [PATCH 2/4] Adds Value::set_by_id() Seemed weird to not have this when get_by_id() is available. API improvements, woo! --- byond-extools/src/core/byond_structures.cpp | 5 +++++ byond-extools/src/core/byond_structures.h | 1 + 2 files changed, 6 insertions(+) diff --git a/byond-extools/src/core/byond_structures.cpp b/byond-extools/src/core/byond_structures.cpp index 56e49ae2..83c45cff 100644 --- a/byond-extools/src/core/byond_structures.cpp +++ b/byond-extools/src/core/byond_structures.cpp @@ -116,6 +116,11 @@ void Value::set(std::string name, Value newvalue) SetVariable(type, value, Core::GetStringId(name), newvalue); } +void Value::set_by_id(int id, Value newvalue) +{ + SetVariable(type, value, id, newvalue); +} + ManagedValue Value::invoke(std::string name, std::vector args, Value usr) { std::replace(name.begin(), name.end(), '_', ' '); diff --git a/byond-extools/src/core/byond_structures.h b/byond-extools/src/core/byond_structures.h index 42273c0b..e4956e2e 100644 --- a/byond-extools/src/core/byond_structures.h +++ b/byond-extools/src/core/byond_structures.h @@ -134,6 +134,7 @@ struct Value std::unordered_map get_all_vars(); bool has_var(std::string name); void set(std::string name, Value value); + void set_by_id(int id, Value newvalue); }; struct ManagedValue : Value From 97510dc37c32a2b80a8ee30dcf4736f2a8d87a3c Mon Sep 17 00:00:00 2001 From: Gary Lafortune Date: Fri, 24 Sep 2021 17:06:00 -0500 Subject: [PATCH 3/4] =?UTF-8?q?Gets=20Jo=C3=A3o=20compiling=20and=20workin?= =?UTF-8?q?g=20as=20an=20extools=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's still some iffy decisions here, especially that it currently totally crashes when a parser or scanner error takes place, but it does indeed work! UPDATES THE JOAO SUBMODULE TO THIS COMMIT: https://github.com/Altoids1/Joao/commit/4c2267c9e1da3d33b6adda0cc55681b57e7fbf19 --- byond-extools/src/joao/joao.cpp | 80 ++++++++++++++++++++++++++------ byond-extools/src/joao/joao_repo | 2 +- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/byond-extools/src/joao/joao.cpp b/byond-extools/src/joao/joao.cpp index 947f7e49..8d51c227 100644 --- a/byond-extools/src/joao/joao.cpp +++ b/byond-extools/src/joao/joao.cpp @@ -69,15 +69,20 @@ const char* enable_joao() trvh run_script(unsigned int argn, ByondValue* args, ByondValue src) { using namespace joao; - if(argn < 2) // If we weren't given a script or signal + if (argn < 2) // If we weren't given a script or signal return ByondValue::False(); ByondValue& script = args[0]; - if(script.type != DataType::STRING) // If what we were given can't be a script + if (script.type != DataType::STRING) // If what we were given can't be a script + { return ByondValue::False(); + } - ByondValue& signal = args[1]; - if(signal.type != DataType::DATUM) // If what we were given isn't even a datum (signals are, this comment upholding, /datum/signal/subspace/vocal) + ByondValue& packet = args[1]; + if (packet.type != DataType::DATUM) + return ByondValue::False(); + ByondValue& signal = args[1].get("signal"); + if (signal.type != DataType::DATUM) // If what we were given isn't even a datum (signals are, this comment upholding, /datum/signal/subspace/vocal) return ByondValue::False(); std::istringstream stream = std::istringstream(std::string(script)); @@ -86,15 +91,15 @@ trvh run_script(unsigned int argn, ByondValue* args, ByondValue src) scanner.scan(stream); Parser pears(scanner); + //Tries to follow the structure of the script_signal var from NTSL //The default values are not based on the actual incoming signal, since they are used whenever *any* /signal Object is instantiated within Joao. ObjectType signal_type("signal"); JoaoValue empty_string = JoaoValue(std::string("")); signal_type.set_typeproperty_raw("content", empty_string); - signal_type.set_typeproperty_raw("freq", 0); + signal_type.set_typeproperty_raw("freq", JoaoValue(0)); signal_type.set_typeproperty_raw("source", empty_string); - pears.IncludeAlienType(&signal_type); Program parsed = pears.parse(); @@ -102,43 +107,92 @@ trvh run_script(unsigned int argn, ByondValue* args, ByondValue src) //Constructing the Joćo Object to be passed as the argument Object* signal_obj = signal_type.makeObject(interpreter, {}); + signal_obj->set_property_raw("content", JoaoValue(Core::GetStringFromId(signal.get_by_id(ID::data).get_by_id(ID::message).value))); - signal_obj->set_property_raw("freq", JoaoValue(signal.get_by_id(ID::frequency).valuef)); // I think? - signal_obj->set_property_raw("source", JoaoValue(Core::GetStringFromId(signal.get_by_id(ID::name).value))); + signal_obj->set_property_raw("freq", JoaoValue(JoaoValue::JoaoInt(signal.get_by_id(ID::frequency).valuef))); // I think? + signal_obj->set_property_raw("source", JoaoValue(Core::GetStringFromId(signal.get_by_id(ID::data).get_by_id(ID::name).value))); JoaoValue jargs = interpreter.makeBaseTable({ JoaoValue(signal_obj) }, {}, nullptr); //Execute! - JoaoValue ret = interpreter.execute(parsed, jargs); - if (ret.t_vType != JoaoValue::vType::Object || ret.t_value.as_object_ptr->object_type != "/signal") // If the program returned something that isn't a signal object + JoaoValue ret; + try + { + ret = interpreter.execute(parsed, jargs); + } + catch (const JoaoValue& err) + { + ByondValue& error = packet.get("err"); + error.set("what", ByondValue(*(err.t_value.as_object_ptr->get_property_raw("what").t_value.as_string_ptr))); + error.set("code", ByondValue(err.t_value.as_object_ptr->get_property_raw("code").t_value.as_int)); + return ByondValue::False(); + } + catch (std::exception exp) { + ByondValue& error = packet.get("err"); + error.set("what", ByondValue(std::string(exp.what()))); + error.set("code", -1); + return ByondValue::False(); + } + catch(...) // A real fucking scary thing to put here but I dunno how else to resolve this + { + ByondValue& error = packet.get("err"); + error.set("what", "An unknown error occurred!"); + error.set("code", -2); + return ByondValue::False(); + } + if (ret.t_vType != JoaoValue::vType::Object || ret.t_value.as_object_ptr->object_type != "signal") // If the program returned something that isn't a signal object + { + ByondValue& error = packet.get("err"); + error.set("what", "Returned value was not a /signal!"); + error.set("code", -3); return ByondValue::False(); } Object*& retptr = ret.t_value.as_object_ptr; // $#(@$*@!!!! + //preparing for the agony of setting the elements of a /list + auto datas = signal.get_by_id(ID::data); // "datas" is the plural of "data," which is in turn the plural of "datum." + //Content typecheck & set JoaoValue& ret_content = retptr->get_property_raw("content"); if (ret_content.t_vType != JoaoValue::vType::String) { + ByondValue& error = packet.get("err"); + error.set("what", "/signal.content was not of type String!"); + error.set("code", -4); return ByondValue::False(); } - signal.get_by_id(ID::data).set_by_id(ID::message, *ret_content.t_value.as_string_ptr); + + ManagedValue msg = ManagedValue(*ret_content.t_value.as_string_ptr); + SetAssocElement(datas.type, datas.value, DataType::STRING, ID::message, msg.type, msg.value); // Have to use this raw shit since extools lacks a good API for it //Frequency typecheck & set JoaoValue& ret_freq = retptr->get_property_raw("freq"); if (ret_freq.t_vType != JoaoValue::vType::Integer) { + ByondValue& error = packet.get("err"); + error.set("what", "/signal.freq was not of type Integer!"); + error.set("code", -4); return ByondValue::False(); } - signal.get_by_id(ID::data).set_by_id(ID::frequency, ret_freq.t_value.as_int); + //signal.get_by_id(ID::data).set_by_id(ID::frequency, ret_freq.t_value.as_int); + ManagedValue freq = ManagedValue(ret_freq.t_value.as_int); + SetAssocElement(datas.type, datas.value, DataType::STRING, ID::frequency, freq.type, freq.value); // Have to use this raw shit since extools lacks a good API for it + //Source typecheck & set JoaoValue& ret_source = retptr->get_property_raw("source"); if (ret_source.t_vType != JoaoValue::vType::String) { + ByondValue& error = packet.get("err"); + error.set("what", "/signal.source was not of type String!"); + error.set("code", -4); return ByondValue::False(); } - signal.get_by_id(ID::data).set_by_id(ID::name, *ret_source.t_value.as_string_ptr); + //signal.get_by_id(ID::data).set_by_id(ID::name, *ret_source.t_value.as_string_ptr); + + ManagedValue source = ManagedValue(*ret_source.t_value.as_string_ptr); + SetAssocElement(datas.type, datas.value, DataType::STRING, ID::name, source.type, source.value); // Have to use this raw shit since extools lacks a good API for it return ByondValue::True(); } diff --git a/byond-extools/src/joao/joao_repo b/byond-extools/src/joao/joao_repo index 8a0c36e3..4c2267c9 160000 --- a/byond-extools/src/joao/joao_repo +++ b/byond-extools/src/joao/joao_repo @@ -1 +1 @@ -Subproject commit 8a0c36e376fe380b9f6f29b6a5c95019690ca994 +Subproject commit 4c2267c9e1da3d33b6adda0cc55681b57e7fbf19 From de675c8744607ce913d73c35f34541a8c4f8941e Mon Sep 17 00:00:00 2001 From: Gary Lafortune Date: Thu, 7 Oct 2021 21:22:47 -0500 Subject: [PATCH 4/4] Adds error-catching for Scanner & Parser errors Writing bad code no longer crashes the server. Woo! --- byond-extools/src/joao/joao.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/byond-extools/src/joao/joao.cpp b/byond-extools/src/joao/joao.cpp index 8d51c227..5a5bd520 100644 --- a/byond-extools/src/joao/joao.cpp +++ b/byond-extools/src/joao/joao.cpp @@ -81,14 +81,31 @@ trvh run_script(unsigned int argn, ByondValue* args, ByondValue src) ByondValue& packet = args[1]; if (packet.type != DataType::DATUM) return ByondValue::False(); + if (!(packet.has_var("signal"))) // Lazy type-checking of $packet + { + return ByondValue::False(); + } + ByondValue& signal = args[1].get("signal"); if (signal.type != DataType::DATUM) // If what we were given isn't even a datum (signals are, this comment upholding, /datum/signal/subspace/vocal) return ByondValue::False(); + std::istringstream stream = std::istringstream(std::string(script)); + Scanner scanner; - scanner.scan(stream); + try + { + scanner.scan(stream); + } + catch (joao::error::scanner s_err) + { + ByondValue& error = packet.get("err"); + error.set("what", s_err.what()); + error.set("code", -3); + return ByondValue::False(); + } Parser pears(scanner); @@ -101,7 +118,18 @@ trvh run_script(unsigned int argn, ByondValue* args, ByondValue src) signal_type.set_typeproperty_raw("source", empty_string); pears.IncludeAlienType(&signal_type); - Program parsed = pears.parse(); + Program parsed; + try + { + parsed = pears.parse(); + } + catch (joao::error::parser p_err) + { + ByondValue& error = packet.get("err"); + error.set("what", p_err.what()); + error.set("code", -3); + return ByondValue::False(); + } Interpreter interpreter(parsed, false);