You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

370 rivejä
12 KiB

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <sstream>
#include <cmath>
#include <chrono>
#include <fstream>
#include <span>
#include <cstring>
#include "UserScript.h"
void print_value(std::ostream& stream, const scripting::script_value& res, bool array_print = false) {
if(std::holds_alternative<scripting::array>(res)) {
stream << "[";
auto max = std::get<scripting::array>(res).value.size();
auto no_comma = max - 1;
for(size_t idx = 0; idx < max; ++idx) {
print_value(stream, std::get<scripting::array>(res).value[idx], true);
stream << (idx != no_comma ? ", " : "");
}
stream << "]";
} else if(std::holds_alternative<std::string>(res)) {
if(array_print) {
stream << std::quoted(std::get<std::string>(res));
} else {
stream << std::get<std::string>(res);
}
} else if(std::holds_alternative<scripting::null>(res)) {
stream << "null";
} else {
stream << std::get<int32_t>(res);
}
}
struct print : public scripting::function_impl {
std::ostream& stream;
print(std::ostream& _stream) : stream(_stream) {}
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
while(not args.empty()) {
auto& arg = args.back();
if(std::holds_alternative<scripting::script_value>(arg)) {
print_value(stream, std::get<scripting::script_value>(arg));
} else {
print_value(stream, self->resolve(std::get<scripting::script_variable>(arg).name));
}
args.pop_back();
}
return scripting::script_value({});
}
};
struct terminate : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript*,std::vector<scripting::argument>, std::optional<scripting::script_error>&) 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<scripting::script_value> apply(scripting::UserScript*,std::vector<scripting::argument>, std::optional<scripting::script_error>&) 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<terminate>());
/***
* 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<print>(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<std::chrono::microseconds>(per_exec).count() << "µs\n";
std::cout << "time per step (" << steps << ", around " << (double)executed_ops / steps << "op/s) = " << std::chrono::duration_cast<std::chrono::nanoseconds>(per_step).count() << "ns\n";
std::cout << "time per avg op (" << executed_ops << " by groups of " << engine->op_count() << ") = " << std::chrono::duration_cast<std::chrono::nanoseconds>(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<terminate>());
/***
* 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<print>(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<std::chrono::microseconds>(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<terminate>());
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<print>(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<scripting::script_value>(res)) {
} else {
auto &errors = std::get<std::vector<scripting::script_error>>(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<size_t>(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<fn_exit>(should_exit));
engine->registerFunction("print", std::make_unique<print>(std::cout));
while (not should_exit) {
std::string code;
std::getline(std::cin, code);
auto res = engine->executeAtOnce(code);
if (std::holds_alternative<scripting::script_value>(res)) {
} else {
auto &errors = std::get<std::vector<scripting::script_error>>(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<std::string_view> args) {
//std::vector<decltype(scripting::prepare_interpreter(std::string{}))> 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<fn_exit>(exit));
engine->registerFunction("print", std::make_unique<print>(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<std::string_view> 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<std::string_view> args;
for(auto& arg : std::span(argv, argv+argc)) {
args.emplace_back(arg, arg+strlen(arg));
}
return cpp_main(args);
}