#include #include #include #include #include #include #include #include #include #include "UserScript.h" void print_value(std::ostream& stream, const scripting::script_value& res) { 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]); stream << (idx != no_comma ? ", " : ""); } stream << "]"; } else if(std::holds_alternative(res)) { stream << std::get(res); } else if(std::holds_alternative(res)) { stream << "null"; } else { stream << std::get(res); } } 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; 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 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); // PLEASE DO NOT ACTUALLY EXIT YOU FUCKING IDIOT return scripting::script_value({}); } }; void process_bench(std::string target = "./tests/scripts/testfile.test") { 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()); /*** * 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++) */ 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{}; for(int runs = 0; runs < 5000; runs++) { auto res = engine->prepare(code.str()); 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); } per_exec /= 5000; per_step /= steps; per_op = per_op / 5000 / 53; std::cout << "time per exec = " << std::chrono::duration_cast(per_exec).count() << "ns\n"; std::cout << "time per step = " << std::chrono::duration_cast(per_step).count() << "ns\n"; std::cout << "time per avg op = " << 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{}); engine->registerFunction("identity", std::make_unique()); engine->registerFunction("exit", std::make_unique()); engine->registerFunction("set", std::make_unique()); /*** * Same as above but for compilation times * * 2023-07-04 Archivist -> 386µs */ engine->registerFunction("print", std::make_unique(std::cout)); std::ifstream src_str("./tests/scripts/testfile.test"); std::stringstream code; code << src_str.rdbuf(); auto begin = std::chrono::high_resolution_clock::now(); [&]() __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()); }(); auto end = std::chrono::high_resolution_clock::now(); auto per_exec = (end - begin)/5; 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("identity", std::make_unique()); engine->registerFunction("exit", std::make_unique()); engine->registerFunction("set", std::make_unique()); 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() { 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()); engine->registerFunction("print", std::make_unique(std::cout)); bool exit = false; while (not 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; auto engine = scripting::prepare_interpreter(std::string{}); engine->registerFunction("identity", std::make_unique()); engine->registerFunction("terminate", std::make_unique()); engine->registerFunction("set", std::make_unique()); engine->registerFunction("print", std::make_unique(std::cout)); bool exit = false; while (not 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"; } } } } #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); }