#include #include #include #include #include #include #include #include #include #include "UserScript.h" void print_value(std::ostream& stream, const scripting::script_value& res, bool array_print = false) { if(std::holds_alternative(res)) { stream << "["; auto max = std::get(res).value.size(); auto no_comma = max - 1; for(size_t idx = 0; idx < max; ++idx) { print_value(stream, std::get(res).value[idx], true); stream << (idx != no_comma ? ", " : ""); } stream << "]"; } else if(std::holds_alternative(res)) { if(array_print) { stream << std::quoted(std::get(res)); } else { stream << std::get(res); } } else if(std::holds_alternative(res)) { stream << "null"; } else { stream << std::get(res); } } struct print : public scripting::function_impl { std::ostream& stream; print(std::ostream& _stream) : stream(_stream) {} std::optional apply(scripting::UserScript* self,std::vector args, std::optional& errors) final { while(not args.empty()) { auto& arg = args.back(); if(std::holds_alternative(arg)) { print_value(stream, std::get(arg)); } else { print_value(stream, self->resolve(std::get(arg).name)); } args.pop_back(); } return scripting::script_value({}); } }; struct terminate : public scripting::function_impl { std::optional apply(scripting::UserScript*,std::vector, std::optional&) final { std::exit(1); // PLEASE DO NOT ACTUALLY EXIT YOU FUCKING IDIOT return scripting::script_value({}); } }; 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{}); 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()); /*** * This is a half assed benchmark, * Document results here to keep the thingy in check performance wise (release mode only) * * 2023-07-04 Archivist -> 2618ns - 308ns - 49ns (clang+libstdc++) * 2023-07-07 Archivist -> 2481ns - 291ns - 46ns (clang+libc++) * 2023-07-07 Archivist -> 106ns - 12ns - 2ns (clang+march=native+libc++) * ?????????? Archivist: These results above are weird, the benchmark was updated in case something strange went on * 2023-07-18 Archivist -> 3446ns - 405ns - 65ns (gcc+libstdc++) * ?????????? Archivist: Corrected some extra weirdness * 2023-07-18 Archivist -> 9952µs - 234ns - 30ns (gcc+libstdc++) * */ engine->registerFunction("print", std::make_unique(std::cout)); std::ifstream src_str(target); std::stringstream code; code << src_str.rdbuf(); int steps = 0; decltype(std::chrono::high_resolution_clock::now()-std::chrono::high_resolution_clock::now()) per_exec{}, per_step{}, per_op{}; constexpr size_t runs_count = 500; for(int runs = 0; runs < runs_count; runs++) { auto res = engine->prepare(code.str()); engine->clear_variables(); auto begin = std::chrono::high_resolution_clock::now(); while (not engine->getValue("exit_ctr").has_value()) { engine->stepOnce(); steps++; } auto end = std::chrono::high_resolution_clock::now(); per_exec += (end - begin); per_step += (end - begin); per_op += (end - begin); } auto executed_ops = (runs_count * engine->op_count() * 5000 /* The code loops 5000 times */); per_exec /= runs_count; per_step /= steps; per_op = per_op / executed_ops; std::cout << "time per exec (" << runs_count << ") = " << std::chrono::duration_cast(per_exec).count() << "µs\n"; std::cout << "time per step (" << steps << ", around " << (double)executed_ops / steps << "op/s) = " << std::chrono::duration_cast(per_step).count() << "ns\n"; std::cout << "time per avg op (" << executed_ops << " by groups of " << engine->op_count() << ") = " << std::chrono::duration_cast(per_op).count() << "ns\n"; } void compile_bench(std::string target = "./tests/scripts/testfile.test") { auto engine = scripting::prepare_interpreter(std::string{}); 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()); /*** * Same as above but for compilation times * * 2023-07-04 Archivist -> 386µs * 2023-07-18 Archivist -> 166µs * 2023-07-18 Archivist -> 156µs */ engine->registerFunction("print", std::make_unique(std::cout)); std::ifstream src_str("./tests/scripts/testfile.test"); std::stringstream code; code << src_str.rdbuf(); constexpr size_t daruns_count = 100; auto begin = std::chrono::high_resolution_clock::now(); for(size_t i = 0; i < daruns_count; ++i) { [&]() __attribute__((optimize("O0"))) { auto res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); res = engine->prepare(code.str()); }(); } auto end = std::chrono::high_resolution_clock::now(); auto per_exec = (end - begin) / 10 / daruns_count; std::cout << "time per exec = " << std::chrono::duration_cast(per_exec).count() << "µs\n"; } void compare(std::string target, std::string expect) { auto engine = scripting::prepare_interpreter(std::string{}); engine->registerFunction("exit", 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); std::stringstream str; std::string_view filename_source = target; std::string_view filename_output = expect; engine->registerFunction("print", std::make_unique(str)); std::ifstream src_str(std::string{filename_source}); std::stringstream code; code << src_str.rdbuf(); std::ifstream out_str(std::string{filename_output}); std::stringstream output; output << out_str.rdbuf(); auto res = engine->executeAtOnce(code.str()); if (std::holds_alternative(res)) { } else { auto &errors = std::get>(res); for (auto &line: errors) { str << line.message << "\n at line " << line.location->line_number << ":" << line.location->column_number << "\n"; str << " " << *line.location->line_contents << "\n"; str << " " << std::string(line.location->column_number - 1, ' ') << "^\n"; } } int status = 0; while(not output.eof()) { std::string expected, found; std::getline(output, expected); std::getline(str, found); bool ok = (expected != found); status+= ok ; (ok ? std::cerr : std::cout) << (not ok ? "\033[21;32m" : "\033[1;31m") << expected << std::string(std::max(0, 40 - expected.size()), ' ')<< "| " << found << std::endl; } if(status) std::exit(status); } void immediate_interactive() { bool should_exit = false; auto engine = scripting::prepare_interpreter(std::string{}); 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(should_exit)); engine->registerFunction("print", std::make_unique(std::cout)); while (not should_exit) { std::string code; std::getline(std::cin, code); auto res = engine->executeAtOnce(code); if (std::holds_alternative(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"; } } } } void exec(std::span args) { //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; 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(exit)); engine->registerFunction("print", std::make_unique(std::cout)); 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) { auto res = engine->stepOnce(); if (not res) { } else { 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"; } } } #if defined(__linux__) or defined(WIN32) constexpr bool trim_first_argument = true; #else constexpr bool trim_first_argument = false; static_assert(false, "Undefined status of the first argument"); #endif int cpp_main(std::span args) { if constexpr (trim_first_argument) { args = args.subspan(1); } if(args.empty() || args.front() == "immediate") { immediate_interactive(); std::exit(0); } else if(args.front() == "compare") { args = args.subspan(1); if(args.size() != 2) { std::cerr << "compare expects 2 files as arguments" << std::endl; std::terminate(); } } else if(args.front() == "bench_exec") { args = args.subspan(1); if(args.size() > 1) { std::cerr << "bench_exec expects 0 or 1 file as arguments" << std::endl; std::terminate(); } if(args.empty()) process_bench(); else process_bench(std::string{args.front()}); } else if(args.front() == "bench_compile") { args = args.subspan(1); if(args.size() > 1) { std::cerr << "bench_compile expects 0 or 1 file as arguments" << std::endl; std::terminate(); } if(args.empty()) compile_bench(); else compile_bench(std::string{args.front()}); } else if(args.front() == "exec") { exec(args.subspan(1)); } else { std::cerr << "Unknown option" << std::endl; } return 0; } int main(int argc, char** argv) { std::vector args; for(auto& arg : std::span(argv, argv+argc)) { args.emplace_back(arg, arg+strlen(arg)); } return cpp_main(args); }