diff --git a/CMakeLists.txt b/CMakeLists.txt index 48249cb..b7a3205 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ add_library(UserScript STATIC src/generator.cpp src/interpreter.cpp src/lex.cpp - src/parse.cpp src/std/array.cpp src/std/crypto.cpp include/UserScriptRequire.h include/UserScriptWizardry.h) + src/parse.cpp src/std/array.cpp src/std/crypto.cpp include/UserScriptRequire.h include/UserScriptWizardry.h src/std/utils.cpp) target_include_directories(UserScript PUBLIC include) include_directories(priv_include) @@ -53,3 +53,4 @@ add_script_test("Scripting 004: While loops with bad terminator" tests/scrip 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) add_script_test("Scripting 007: Cryptography seems to work" tests/scripts/007.script tests/scripts/007.results) +add_script_test("Scripting 008: Utils seems to work" tests/scripts/008.script tests/scripts/008.results) diff --git a/include/UserScript.h b/include/UserScript.h index 4ef1f4d..4e5fe6c 100644 --- a/include/UserScript.h +++ b/include/UserScript.h @@ -51,6 +51,10 @@ namespace scripting { virtual void registerFunction(std::string name, function fn) = 0; virtual void clear_variables() = 0; virtual int32_t op_count() = 0; + virtual int32_t var_count() = 0; + virtual void var_clear() = 0; + virtual std::optional varname_by_idx(int32_t idx) = 0; + virtual std::optional var_by_idx(int32_t idx) = 0; virtual script_value resolve(const std::string& name) = 0; virtual std::variant> executeAtOnce(std::string code) = 0; virtual std::vector prepare(std::string code) = 0; @@ -58,7 +62,15 @@ namespace scripting { virtual ~UserScript() = default; }; + struct UserScriptLibraryParameters { + bool recursive_arrays = false; + int32_t array_size_limit = 1024; + int32_t string_size_limit = 1024; + int32_t variables_count = 1024; + }; + 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); - std::unique_ptr register_crypto_lib(std::unique_ptr target, int32_t array_size_limit = 1024, int32_t string_size_limit = 1024); + std::unique_ptr register_array_lib(std::unique_ptr target, const UserScriptLibraryParameters&); + std::unique_ptr register_crypto_lib(std::unique_ptr target, const UserScriptLibraryParameters&); + std::unique_ptr register_utils_lib(std::unique_ptr target, const UserScriptLibraryParameters&); } \ No newline at end of file diff --git a/priv_include/UserScript/interpreter.h b/priv_include/UserScript/interpreter.h index 971003a..c751855 100644 --- a/priv_include/UserScript/interpreter.h +++ b/priv_include/UserScript/interpreter.h @@ -121,6 +121,34 @@ namespace scripting { return bytecode.size(); } + int32_t var_count() final { + return variables.size(); + } + + void var_clear() final { + variables.clear(); + } + + std::optional var_by_idx(int32_t idx) final { + if(idx >= var_count() || idx < 0) return std::nullopt; + for(auto& val : variables) { + if(not idx--) { + return val.second; + } + } + return std::nullopt; + } + + std::optional varname_by_idx(int32_t idx) final { + if(idx >= var_count() || idx < 0) return std::nullopt; + for(auto& val : variables) { + if(not idx--) { + return val.first; + } + } + return std::nullopt; + } + std::variant> executeAtOnce(std::string code) final { std::vector errors; auto lexed = ast::lex(code, errors); diff --git a/script_exe/main.cpp b/script_exe/main.cpp index f8dd955..8ee560a 100644 --- a/script_exe/main.cpp +++ b/script_exe/main.cpp @@ -32,21 +32,6 @@ void print_value(std::ostream& stream, const scripting::script_value& res, bool } } -struct identity : public scripting::function_impl { - std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { - if(args.size() != 1) { - errors = scripting::script_error{.message = "identity expects a single argument"}; - } else { - if(std::holds_alternative(args.front())) { - return std::get(args.front()); - } else { - return self->resolve(std::get(args.front()).name); - } - } - return scripting::script_value({}); - } -}; - struct print : public scripting::function_impl { std::ostream& stream; @@ -67,39 +52,6 @@ struct print : public scripting::function_impl { } }; -struct set : public scripting::function_impl { - std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { - - if(args.size() != 2) { - errors = scripting::script_error{ - .message = "set expects 2 arguments" - }; - return scripting::script_value{}; - } - - auto& var = args.back(); - if(not holds_alternative(var)) { - errors = scripting::script_error{ - .message = "set expects the first argument to be a target variable" - }; - return scripting::script_value{}; - } - - auto& arg = args.front(); - - if(std::holds_alternative(arg)) { - self->setValue(get(var).name, std::get(arg)); - } else { - self->setValue(get(var).name, self->resolve(std::get(arg).name)); - } - if(auto v = self->getValue(get(var).name); v) { - return v.value(); - } else { - return scripting::script_value{}; - } - } -}; - struct terminate : public scripting::function_impl { std::optional apply(scripting::UserScript*,std::vector, std::optional&) final { std::exit(1); @@ -122,9 +74,14 @@ struct fn_exit : public scripting::function_impl { void process_bench(std::string target = "./tests/scripts/testfile.test") { auto engine = scripting::prepare_interpreter(std::string{}); - engine->registerFunction("identity", std::make_unique()); + + constexpr scripting::UserScriptLibraryParameters params{}; + + engine = scripting::register_array_lib(std::move(engine), params); + engine = scripting::register_crypto_lib(std::move(engine), params); + engine = scripting::register_utils_lib(std::move(engine), params); + engine->registerFunction("exit", std::make_unique()); - engine->registerFunction("set", std::make_unique()); /*** * This is a half assed benchmark, @@ -178,9 +135,14 @@ void process_bench(std::string target = "./tests/scripts/testfile.test") { void compile_bench(std::string target = "./tests/scripts/testfile.test") { auto engine = scripting::prepare_interpreter(std::string{}); - engine->registerFunction("identity", std::make_unique()); + + constexpr scripting::UserScriptLibraryParameters params{}; + + engine = scripting::register_array_lib(std::move(engine), params); + engine = scripting::register_crypto_lib(std::move(engine), params); + engine = scripting::register_utils_lib(std::move(engine), params); + engine->registerFunction("exit", std::make_unique()); - engine->registerFunction("set", std::make_unique()); /*** * Same as above but for compilation times @@ -220,15 +182,13 @@ void compile_bench(std::string target = "./tests/scripts/testfile.test") { void compare(std::string target, std::string expect) { auto engine = scripting::prepare_interpreter(std::string{}); - engine->registerFunction("identity", std::make_unique()); engine->registerFunction("exit", std::make_unique()); - engine->registerFunction("set", std::make_unique()); - constexpr size_t array_limit = 4096; - constexpr size_t string_limit = 4096; + constexpr scripting::UserScriptLibraryParameters params{}; - engine = scripting::register_array_lib(std::move(engine), true, array_limit); - engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit); + engine = scripting::register_array_lib(std::move(engine), params); + engine = scripting::register_crypto_lib(std::move(engine), params); + engine = scripting::register_utils_lib(std::move(engine), params); std::stringstream str; std::string_view filename_source = target; @@ -274,15 +234,13 @@ 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(should_exit)); - engine->registerFunction("set", std::make_unique()); + constexpr scripting::UserScriptLibraryParameters params{}; - constexpr size_t array_limit = 4096; - constexpr size_t string_limit = 4096; + engine = scripting::register_array_lib(std::move(engine), params); + engine = scripting::register_crypto_lib(std::move(engine), params); + engine = scripting::register_utils_lib(std::move(engine), params); - engine = scripting::register_array_lib(std::move(engine), true, array_limit); - engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit); + engine->registerFunction("exit", std::make_unique(should_exit)); engine->registerFunction("print", std::make_unique(std::cout)); while (not should_exit) { @@ -306,38 +264,54 @@ void immediate_interactive() { } void exec(std::span args) { - std::vector batch; + //std::vector batch; + + + std::ifstream src_str(std::string{args.front()}); + std::stringstream code; + code << src_str.rdbuf(); + std::string code_val = code.str(); auto engine = scripting::prepare_interpreter(std::string{}); + bool exit = false; - engine->registerFunction("identity", std::make_unique()); - engine->registerFunction("terminate", std::make_unique()); - engine->registerFunction("set", std::make_unique()); + constexpr scripting::UserScriptLibraryParameters params{}; - constexpr size_t array_limit = 4096; - constexpr size_t string_limit = 4096; + engine = scripting::register_array_lib(std::move(engine), params); + engine = scripting::register_crypto_lib(std::move(engine), params); + engine = scripting::register_utils_lib(std::move(engine), params); - engine = scripting::register_array_lib(std::move(engine), true, array_limit); - engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit); + engine->registerFunction("exit", std::make_unique(exit)); engine->registerFunction("print", std::make_unique(std::cout)); - bool exit = false; + + auto errors = engine->prepare(code_val); + + if(not errors.empty()) { + for (auto &line: errors) { + std::cout << line.message << "\n at line "; + if(line.location) { + std::cout << line.location->line_number << ":" + << line.location->column_number << "\n"; + std::cout << " " << *line.location->line_contents << "\n"; + std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n"; + } else std::cout << "UNKNOWN\n"; + } + return; + } + while (not exit) { - std::string code; - std::getline(std::cin, code); - auto res = engine->executeAtOnce(code); - if (std::holds_alternative(res)) { + auto res = engine->stepOnce(); + if (not res) { } else { - auto &errors = std::get>(res); - for (auto &line: errors) { - std::cout << line.message << "\n at line "; - if(line.location) { - std::cout << line.location->line_number << ":" - << line.location->column_number << "\n"; - std::cout << " " << *line.location->line_contents << "\n"; - std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n"; - } else std::cout << "UNKNOWN\n"; - } + auto line = res.value(); + std::cout << line.message << "\n at line "; + if(line.location) { + std::cout << line.location->line_number << ":" + << line.location->column_number << "\n"; + std::cout << " " << *line.location->line_contents << "\n"; + std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n"; + } else std::cout << "UNKNOWN\n"; } } } @@ -380,7 +354,7 @@ int cpp_main(std::span args) { if(args.empty()) compile_bench(); else compile_bench(std::string{args.front()}); } else if(args.front() == "exec") { - // exec(args.subspan(1)); + exec(args.subspan(1)); } else { std::cerr << "Unknown option" << std::endl; } diff --git a/src/std/array.cpp b/src/std/array.cpp index 1bf5b6a..43e804e 100644 --- a/src/std/array.cpp +++ b/src/std/array.cpp @@ -18,7 +18,7 @@ struct fn_array final : public scripting::function_impl { std::optional apply(UserScript* self, std::vector n, std::optional& error) final { array ary; - if(not can_contain_arrays && details::NoArrays<0>{}.verify(self, n)) { + if(not can_contain_arrays && not details::NoArrays<0>{}.verify(self, n)) { error = script_error{ .message = "/array: arrays cannot contain other arrays" }; @@ -402,16 +402,16 @@ struct fn_array_set final : public scripting::function_impl { }; 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)); + interpreter register_array_lib(interpreter target, const UserScriptLibraryParameters& params) { + target->registerFunction("array", std::make_unique(params.array_size_limit, params.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_append", std::make_unique("array_append", params.array_size_limit, params.recursive_arrays)); + target->registerFunction("array_push", std::make_unique("array_push", params.array_size_limit, params.recursive_arrays)); target->registerFunction("array_pop", std::make_unique("array_pop")); target->registerFunction("array_size", std::make_unique()); target->registerFunction("array_index", std::make_unique()); target->registerFunction("array_set", std::make_unique()); - target->registerFunction("queue_enqueue", std::make_unique("queue_enqueue", size_limit, recursive_arrays)); + target->registerFunction("queue_enqueue", std::make_unique("queue_enqueue", params.array_size_limit, params.recursive_arrays)); target->registerFunction("queue_dequeue", std::make_unique("queue_dequeue")); return std::move(target); } diff --git a/src/std/crypto.cpp b/src/std/crypto.cpp index 7e098c8..7899e66 100644 --- a/src/std/crypto.cpp +++ b/src/std/crypto.cpp @@ -375,11 +375,11 @@ namespace scripting { ~fn_decode_n() final = default; }; - interpreter register_crypto_lib(interpreter target, int32_t array_size_limit, int32_t string_size_limit) { - target->registerFunction("string_to_binary", std::make_unique(array_size_limit)); - target->registerFunction("binary_to_string", std::make_unique(string_size_limit)); - target->registerFunction("encode_n", std::make_unique(string_size_limit)); - target->registerFunction("decode_n", std::make_unique(string_size_limit)); + interpreter register_crypto_lib(interpreter target, const UserScriptLibraryParameters& params) { + target->registerFunction("string_to_binary", std::make_unique(params.array_size_limit)); + target->registerFunction("binary_to_string", std::make_unique(params.string_size_limit)); + target->registerFunction("encode_n", std::make_unique(params.string_size_limit)); + target->registerFunction("decode_n", std::make_unique(params.string_size_limit)); return std::move(target); } } diff --git a/src/std/utils.cpp b/src/std/utils.cpp new file mode 100644 index 0000000..cb6382d --- /dev/null +++ b/src/std/utils.cpp @@ -0,0 +1,227 @@ +#include "UserScript.h" +#include +#include +#include +#include "UserScriptWizardry.h" +#include "UserScriptRequire.h" + +using interpreter = decltype(scripting::prepare_interpreter({})); +using namespace scripting; + +namespace scripting { + + struct fn_identity : public scripting::function_impl { + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + // TODO: use the requirement sets here + if(args.size() != 1) { + errors = scripting::script_error{.message = "identity expects a single argument"}; + } else { + if(std::holds_alternative(args.front())) { + return std::get(args.front()); + } else { + return self->resolve(std::get(args.front()).name); + } + } + return scripting::script_value({}); + } + }; + + struct fn_var_clear : public scripting::function_impl { + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + using verifier = Verify< + details::SizeEquals<0> + >; + + if(not verifier{}.verify(self, args)) { + errors = scripting::script_error{.message = "var_clear argument error"}; + } else { + self->clear_variables(); + } + return scripting::script_value({}); + } + }; + + struct fn_var_name : public scripting::function_impl { + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + using verifier = Verify< + details::SizeEquals<1>, + details::TypeVerifier<0, int32_t> + >; + + if(not verifier{}.verify(self, args)) { + errors = scripting::script_error{.message = "var_name argument error"}; + } else { + int32_t idx = 0; + + if(std::holds_alternative(args.front())) { + idx = std::get(std::get(args.front())); + } else { + idx = std::get(self->resolve(std::get(args.front()).name)); + } + + if(auto val = self->varname_by_idx(idx); val) return val; + } + return scripting::script_value({}); + } + }; + + struct fn_var_count : public scripting::function_impl { + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + using verifier = Verify< + details::SizeEquals<0> + >; + + if(not verifier{}.verify(self, args)) { + errors = scripting::script_error{.message = "var_count argument error"}; + } else { + return self->var_count(); + } + return scripting::script_value({}); + } + }; + + struct fn_null : public scripting::function_impl { + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + using verifier = Verify< + details::SizeEquals<0> + >; + + if(not verifier{}.verify(self, args)) { + errors = scripting::script_error{.message = "null argument error"}; + } + return scripting::script_value({}); + } + }; + + struct fn_var_cap : public scripting::function_impl { + int32_t var_cap; + + explicit fn_var_cap(int32_t _var_cap) + : var_cap(_var_cap) {} + + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + using verifier = Verify< + details::SizeEquals<0> + >; + + if(not verifier{}.verify(self, args)) { + errors = scripting::script_error{.message = "var_cap argument error"}; + return scripting::script_value({}); + } + return var_cap; + } + }; + + struct fn_var_type : public scripting::function_impl { + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + using verifier = Verify< + details::SizeEquals<1> + >; + + if(not verifier{}.verify(self, args)) { + errors = scripting::script_error{.message = "var_type argument error"}; + } else { + script_value var; + + if(std::holds_alternative(args.front())) { + var = std::get(args.front()); + } else { + var = self->resolve(std::get(args.front()).name); + } + return std::visit( + wizardry::overloaded{ + [](int32_t&) {return "integer";}, + [](array&) {return "array";}, + [](null&) {return "null";}, + [](std::string&) {return "string";} + }, + var + ); + } + return scripting::script_value({}); + } + }; + + struct fn_var_dump : public scripting::function_impl { + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + using verifier = Verify< + details::SizeEquals<1>, + details::TypeVerifier<0, int32_t> + >; + + if(not verifier{}.verify(self, args)) { + errors = scripting::script_error{.message = "var_dump argument error"}; + } else { + int32_t idx = 0; + + if(std::holds_alternative(args.front())) { + idx = std::get(std::get(args.front())); + } else { + idx = std::get(self->resolve(std::get(args.front()).name)); + } + + if(auto val = self->var_by_idx(idx); val) { + return val.value(); + } + } + return scripting::script_value({}); + } + }; + + struct fn_set : public scripting::function_impl { + int32_t max_variable_count; + + explicit fn_set(int32_t _max_variable_count) + : max_variable_count(_max_variable_count) {} + + std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { + // TODO: use the requirement sets here + if(args.size() != 2) { + errors = scripting::script_error{ + .message = "set expects 2 arguments" + }; + return scripting::script_value{}; + } + + auto& var = args.back(); + if(not holds_alternative(var)) { + errors = scripting::script_error{ + .message = "set expects the first argument to be a target variable" + }; + return scripting::script_value{}; + } + + auto& arg = args.front(); + + if(std::holds_alternative(arg)) { + if(self->getValue(get(var).name) && self->var_count() >= max_variable_count) { + errors = scripting::script_error{ + .message = "set expects 2 arguments" + }; + return scripting::script_value{}; + } + self->setValue(get(var).name, std::get(arg)); + } else { + self->setValue(get(var).name, self->resolve(std::get(arg).name)); + } + if(auto v = self->getValue(get(var).name); v) { + return v.value(); + } else { + return scripting::script_value{}; + } + } + }; + + interpreter register_utils_lib(interpreter target, const UserScriptLibraryParameters& params) { + target->registerFunction("identity", std::make_unique()); + target->registerFunction("var_clear", std::make_unique()); + target->registerFunction("var_name", std::make_unique()); + target->registerFunction("var_count", std::make_unique()); + target->registerFunction("var_cap", std::make_unique(params.variables_count)); + target->registerFunction("var_dump", std::make_unique()); + target->registerFunction("var_type", std::make_unique()); + target->registerFunction("null", std::make_unique()); + target->registerFunction("set", std::make_unique(params.variables_count)); + return std::move(target); + } +} \ No newline at end of file diff --git a/tests/parser_test.cpp b/tests/parser_test.cpp index 110fb50..709a1b2 100644 --- a/tests/parser_test.cpp +++ b/tests/parser_test.cpp @@ -86,6 +86,7 @@ constexpr auto runner = [](){ "../tests/scripts/005.script", "../tests/scripts/006.script", "../tests/scripts/007.script", + "../tests/scripts/008.script", }; auto seed = seed_template == -1 ? std::random_device{}() : seed_template; @@ -124,7 +125,7 @@ constexpr auto runner = [](){ size_t count = 0; size_t error_cnt = 0; size_t success_cnt = 0; - constexpr size_t max_count = 7000000; + constexpr size_t max_count = 8000000; auto begin = std::chrono::high_resolution_clock::now(); while(count < max_count) { @@ -168,4 +169,5 @@ TEST_CASE("Try to crash the parser (known seeds)") { TEST_CASE("Try to crash the parser (new seeds)") { runner<>(); -} \ No newline at end of file +} + diff --git a/tests/scripts/008.results b/tests/scripts/008.results new file mode 100644 index 0000000..3d70d00 --- /dev/null +++ b/tests/scripts/008.results @@ -0,0 +1,5 @@ +4 variables detected, printing: +answer: 42 +ary: ["wizard", 7] +hello: "world" +it: 3 diff --git a/tests/scripts/008.script b/tests/scripts/008.script new file mode 100644 index 0000000..2c0ed01 --- /dev/null +++ b/tests/scripts/008.script @@ -0,0 +1,21 @@ +/set hello "world" +/set answer 42 +/set ary (/array "wizard" 7) + +if(it == (/null)) + /set it 0 +endif + +/print (/var_count) " variables detected, printing:\n" + +while(it < (/var_count)) + /print (/var_name it) ": " + if((/var_type (/var_dump it)) == "string") + /print "\"" (/var_dump it) "\"\n" + else + /print (/var_dump it) "\n" + endif + /set it it+1 +endwhile + +/exit \ No newline at end of file