From a9c3a5ecc0c635af652c075bcf8f2afe19c9ffbd Mon Sep 17 00:00:00 2001 From: Ludovic 'Archivist' Lagouardette Date: Fri, 14 Jul 2023 19:39:21 +0200 Subject: [PATCH] Added array function and fixed a stack purging error --- CMakeLists.txt | 7 +- include/UserScript.h | 1 + script_exe/main.cpp | 22 ++- src/interpreter.cpp | 3 + src/std/array.cpp | 335 ++++++++++++++++++++++++++++++++++++++ tests/scripts/006.results | 1 + tests/scripts/006.script | 2 + 7 files changed, 367 insertions(+), 4 deletions(-) create mode 100644 src/std/array.cpp create mode 100644 tests/scripts/006.results create mode 100644 tests/scripts/006.script diff --git a/CMakeLists.txt b/CMakeLists.txt index 19c3faa..6be54fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,12 @@ include(CTest) include(Catch) add_library(UserScript STATIC + include/UserScript.h + priv_include/UserScript/interpreter.h + src/generator.cpp src/interpreter.cpp - src/lex.cpp src/parse.cpp priv_include/UserScript/interpreter.h src/generator.cpp) + src/lex.cpp + src/parse.cpp src/std/array.cpp) target_include_directories(UserScript PUBLIC include) include_directories(priv_include) @@ -47,3 +51,4 @@ add_script_test("Scripting 002: Statements and Conditionals" tests/scrip add_script_test("Scripting 003: While loops" tests/scripts/003.script tests/scripts/003.results) add_script_test("Scripting 004: While loops with bad terminator" tests/scripts/004.script tests/scripts/004.results) add_script_test("Scripting 005: If statements with bad terminator" tests/scripts/005.script tests/scripts/005.results) +add_script_test("Scripting 006: The stack is properly purged" tests/scripts/006.script tests/scripts/006.results) diff --git a/include/UserScript.h b/include/UserScript.h index b6bdb7d..e9060ad 100644 --- a/include/UserScript.h +++ b/include/UserScript.h @@ -56,4 +56,5 @@ namespace scripting { }; std::unique_ptr prepare_interpreter(const std::string& code); + std::unique_ptr register_array_lib(std::unique_ptr target, bool recursive_arrays = false, int32_t size_limit = 1024); } \ No newline at end of file diff --git a/script_exe/main.cpp b/script_exe/main.cpp index e4bf977..1d553d2 100644 --- a/script_exe/main.cpp +++ b/script_exe/main.cpp @@ -104,6 +104,17 @@ struct terminate : public scripting::function_impl { } }; +struct fn_exit : public scripting::function_impl { + bool& exit_val; + + fn_exit(bool& _exit_val) : exit_val(_exit_val) {} + + std::optional apply(scripting::UserScript*,std::vector, std::optional&) final { + exit_val = true; + return scripting::script_value({}); + } +}; + void process_bench(std::string target = "./tests/scripts/testfile.test") { auto engine = scripting::prepare_interpreter(std::string{}); @@ -191,6 +202,7 @@ void compare(std::string target, std::string expect) { engine->registerFunction("exit", std::make_unique()); engine->registerFunction("set", std::make_unique()); + engine = scripting::register_array_lib(std::move(engine), true, 4096); std::stringstream str; std::string_view filename_source = target; @@ -233,15 +245,17 @@ void compare(std::string target, std::string expect) { } void immediate_interactive() { + bool should_exit = false; auto engine = scripting::prepare_interpreter(std::string{}); engine->registerFunction("identity", std::make_unique()); - engine->registerFunction("exit", std::make_unique()); + engine->registerFunction("exit", std::make_unique(should_exit)); engine->registerFunction("set", std::make_unique()); + engine = scripting::register_array_lib(std::move(engine), true, 4096); + engine->registerFunction("print", std::make_unique(std::cout)); - bool exit = false; - while (not exit) { + while (not should_exit) { std::string code; std::getline(std::cin, code); auto res = engine->executeAtOnce(code); @@ -270,6 +284,8 @@ void exec(std::span args) { engine->registerFunction("terminate", std::make_unique()); engine->registerFunction("set", std::make_unique()); + engine = scripting::register_array_lib(std::move(engine), true, 4096); + engine->registerFunction("print", std::make_unique(std::cout)); bool exit = false; while (not exit) { diff --git a/src/interpreter.cpp b/src/interpreter.cpp index b31d26a..d692382 100644 --- a/src/interpreter.cpp +++ b/src/interpreter.cpp @@ -77,6 +77,9 @@ namespace scripting { auto args_in_situ = std::span{execution_stack}.subspan(execution_stack.size() - v.arity); std::vector arguments{args_in_situ.begin(), args_in_situ.end()}; auto item = (*it).second->apply(this, arguments, error); + for(auto arity = v.arity; arity != 0; arity--) { + execution_stack.pop_back(); + } if(item) { execution_stack.emplace_back(item.value()); } diff --git a/src/std/array.cpp b/src/std/array.cpp new file mode 100644 index 0000000..2c34273 --- /dev/null +++ b/src/std/array.cpp @@ -0,0 +1,335 @@ +#include "UserScript.h" +#include +#include +#include +#include + +using interpreter = decltype(scripting::prepare_interpreter({})); +using namespace scripting; + +struct fn_array final : public scripting::function_impl { + const int32_t size_limit; + const bool can_contain_arrays; + + fn_array(int32_t _size_limit, bool _can_contain_arrays) + : size_limit(_size_limit) + , can_contain_arrays(_can_contain_arrays) + {} + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + array ary; + if(not can_contain_arrays) { + if(std::any_of(n.begin(), n.end(), [&](argument& arg) { + if(std::holds_alternative(arg)) { + return true; + } else { + const auto& val = self->resolve(std::get(arg).name); + if(not can_contain_arrays && std::holds_alternative(val)) { + return true; + } + } + return false; + })) { + error = script_error{ + .message = "/array: arrays cannot contain other arrays" + }; + return std::nullopt; + } + } + + if(n.size() > std::max(0, size_limit)) { + error = script_error{ + .message = "/array: arrays cannot contain other arrays" + }; + return std::nullopt; + } + + std::transform(n.rbegin(), n.rend(), std::back_inserter(ary.value), [&](argument& arg){ + if(std::holds_alternative(arg)) { + return std::get(arg); + } else { + return self->resolve(std::get(arg).name); + } + }); + + return ary; + } + + ~fn_array() final = default; +}; + +struct fn_array_reverse final : public scripting::function_impl { + fn_array_reverse() = default; + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + if(n.size() != 1) { + error = script_error{ + .message = "/array_reverse takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + auto& arg = n.front(); + script_value target; + + if(std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + if(not std::holds_alternative(target)) { + error = script_error{ + .message = "/array_reverse takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + auto& ary = std::get(target); + + std::reverse(ary.value.begin(), ary.value.end()); + + return target; + } + + ~fn_array_reverse() final = default; +}; + +struct fn_array_append final : public scripting::function_impl { + const std::string name; + const int32_t size_limit; + const bool can_contain_arrays; + + fn_array_append(std::string _name, int32_t _size_limit, bool _can_contain_arrays) + : name(std::move(_name)) + , size_limit(_size_limit) + , can_contain_arrays(_can_contain_arrays) + {} + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + if(n.size() < 1) { + error = script_error{ + .message = "/array_append: must provide an array variable as first argument" + }; + return std::nullopt; + } + + if(std::holds_alternative(n.front())) { + error = script_error{ + .message = "/array_append: must provide an array variable as first argument" + }; + return std::nullopt; + } + + auto target = self->getValue(std::get(n.back()).name); + n.pop_back(); + + if(not target) { + error = script_error{ + .message = "/array_append: provided variable name is undefined" + }; + return std::nullopt; + } + + if(not std::holds_alternative(target.value().get())) { + error = script_error{ + .message = "/array_append: provided variable is not an array" + }; + return std::nullopt; + } + + auto& ary = std::get(target.value().get()).value; + + if(not can_contain_arrays) { + if(std::any_of(n.begin(), n.end(), [&](argument& arg) { + if(std::holds_alternative(arg)) { + return true; + } else { + const auto& val = self->resolve(std::get(arg).name); + if(not can_contain_arrays && std::holds_alternative(val)) { + return true; + } + } + return false; + })) { + error = script_error{ + .message = "/array_append: arrays cannot contain other arrays" + }; + return std::nullopt; + } + } + + if(n.size() + ary.size() > std::max(0, size_limit)) { + error = script_error{ + .message = "/array_append: array would exceed size limit" + }; + return script_value{0}; + } + + std::transform(n.rbegin(), n.rend(), std::back_inserter(ary), [&](argument& arg){ + if(std::holds_alternative(arg)) { + return std::get(arg); + } else { + return self->resolve(std::get(arg).name); + } + }); + + return script_value{1}; + } + + ~fn_array_append() final = default; +}; + +struct fn_array_pop final : public scripting::function_impl { + fn_array_pop() = default; + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + if(n.size() != 1) { + error = script_error{ + .message = "/array_pop takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + auto& arg = n.front(); + script_value target; + + if(std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + if(not std::holds_alternative(target)) { + error = script_error{ + .message = "/array_pop takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + auto& ary = std::get(target); + + if(ary.value.empty()) { + return {null{}}; + } + + auto value = ary.value.back(); + + ary.value.pop_back(); + + return value; + } + + ~fn_array_pop() final = default; +}; + +struct fn_array_size final : public scripting::function_impl { + fn_array_size() = default; + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + if(n.size() != 1) { + error = script_error{ + .message = "/array_size takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + auto& arg = n.front(); + script_value target; + + if(std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + if(not std::holds_alternative(target)) { + error = script_error{ + .message = "/array_size takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + return static_cast(std::get(target).value.size()); + } + + ~fn_array_size() final = default; +}; + +struct fn_array_index final : public scripting::function_impl { + fn_array_index() = default; + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + if(n.size() != 2) { + error = script_error{ + .message = "/array_index takes exactly 1 argument of type array followed by an integer, provided a different amount" + }; + return std::nullopt; + } + + auto& arg = n.back(); + script_value target; + + if(std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + if(not std::holds_alternative(target)) { + error = script_error{ + .message = "/array_index takes exactly 1 argument of type array followed by an integer, argument 1 is not an array" + }; + return std::nullopt; + } + + auto& idx_arg = n.front(); + script_value idx; + + if(std::holds_alternative(idx_arg)) { + idx = std::get(idx_arg); + } else { + idx = self->resolve(std::get(idx_arg).name); + } + + if(not std::holds_alternative(idx)) { + error = script_error{ + .message = "/array_index takes exactly 1 argument of type array followed by an integer, argument 2 is not an integer" + }; + return std::nullopt; + } + + if(static_cast(std::get(target).value.size()) <= std::get(idx)) { + error = script_error{ + .message = "/array_index index must be smaller that the array size, the first element of the array has index 0" + }; + return std::nullopt; + } + + if(std::get(idx) < 0) { + error = script_error{ + .message = "/array_index index must be 0 or more" + }; + return std::nullopt; + } + + return std::get(target).value.at(static_cast(std::get(idx))); + } + + ~fn_array_index() final = default; +}; + +namespace scripting { + + interpreter register_array_lib(interpreter target, bool recursive_arrays, int32_t size_limit) { + target->registerFunction("array", std::make_unique(size_limit, recursive_arrays)); + target->registerFunction("array_reverse", std::make_unique()); + target->registerFunction("array_append", std::make_unique("array_append", size_limit, recursive_arrays)); + target->registerFunction("array_push", std::make_unique("array_push", size_limit, recursive_arrays)); + target->registerFunction("array_pop", std::make_unique()); + target->registerFunction("array_size", std::make_unique()); + target->registerFunction("array_index", std::make_unique()); + return std::move(target); + } + +} \ No newline at end of file diff --git a/tests/scripts/006.results b/tests/scripts/006.results new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/tests/scripts/006.results @@ -0,0 +1 @@ +3 diff --git a/tests/scripts/006.script b/tests/scripts/006.script new file mode 100644 index 0000000..38408b6 --- /dev/null +++ b/tests/scripts/006.script @@ -0,0 +1,2 @@ +/set ary (/array 0 1 2 3 4 5 6) +/print (/array_index ary 3) "\n"