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.

367 lines
12 KiB

  1. #include <iostream>
  2. #include <iomanip>
  3. #include <algorithm>
  4. #include <chrono>
  5. #include <fstream>
  6. #include <span>
  7. #include <cstring>
  8. #include "UserScript.h"
  9. void print_value(std::ostream& stream, const scripting::script_value& res, bool array_print = false) {
  10. if(std::holds_alternative<scripting::array>(res)) {
  11. stream << "[";
  12. auto max = std::get<scripting::array>(res).value.size();
  13. auto no_comma = max - 1;
  14. for(size_t idx = 0; idx < max; ++idx) {
  15. print_value(stream, std::get<scripting::array>(res).value[idx], true);
  16. stream << (idx != no_comma ? ", " : "");
  17. }
  18. stream << "]";
  19. } else if(std::holds_alternative<std::string>(res)) {
  20. if(array_print) {
  21. stream << std::quoted(std::get<std::string>(res));
  22. } else {
  23. stream << std::get<std::string>(res);
  24. }
  25. } else if(std::holds_alternative<scripting::null>(res)) {
  26. stream << "null";
  27. } else {
  28. stream << std::get<int32_t>(res);
  29. }
  30. }
  31. struct print : public scripting::function_impl {
  32. std::ostream& stream;
  33. print(std::ostream& _stream) : stream(_stream) {}
  34. std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
  35. while(not args.empty()) {
  36. auto& arg = args.back();
  37. if(std::holds_alternative<scripting::script_value>(arg)) {
  38. print_value(stream, std::get<scripting::script_value>(arg));
  39. } else {
  40. print_value(stream, self->resolve(std::get<scripting::script_variable>(arg).name));
  41. }
  42. args.pop_back();
  43. }
  44. return scripting::script_value({});
  45. }
  46. };
  47. struct terminate : public scripting::function_impl {
  48. std::optional<scripting::script_value> apply(scripting::UserScript*,std::vector<scripting::argument>, std::optional<scripting::script_error>&) final {
  49. std::exit(1);
  50. // PLEASE DO NOT ACTUALLY EXIT YOU FUCKING IDIOT
  51. return scripting::script_value({});
  52. }
  53. };
  54. struct fn_exit : public scripting::function_impl {
  55. bool& exit_val;
  56. fn_exit(bool& _exit_val) : exit_val(_exit_val) {}
  57. std::optional<scripting::script_value> apply(scripting::UserScript*,std::vector<scripting::argument>, std::optional<scripting::script_error>&) final {
  58. exit_val = true;
  59. return scripting::script_value({});
  60. }
  61. };
  62. void process_bench(std::string target = "./tests/scripts/testfile.test") {
  63. auto engine = scripting::prepare_interpreter(std::string{});
  64. constexpr scripting::UserScriptLibraryParameters params{};
  65. engine = scripting::register_array_lib(std::move(engine), params);
  66. engine = scripting::register_crypto_lib(std::move(engine), params);
  67. engine = scripting::register_utils_lib(std::move(engine), params);
  68. engine->registerFunction("exit", std::make_unique<terminate>());
  69. /***
  70. * This is a half assed benchmark,
  71. * Document results here to keep the thingy in check performance wise (release mode only)
  72. *
  73. * 2023-07-04 Archivist -> 2618ns - 308ns - 49ns (clang+libstdc++)
  74. * 2023-07-07 Archivist -> 2481ns - 291ns - 46ns (clang+libc++)
  75. * 2023-07-07 Archivist -> 106ns - 12ns - 2ns (clang+march=native+libc++)
  76. * ?????????? Archivist: These results above are weird, the benchmark was updated in case something strange went on
  77. * 2023-07-18 Archivist -> 3446ns - 405ns - 65ns (gcc+libstdc++)
  78. * ?????????? Archivist: Corrected some extra weirdness
  79. * 2023-07-18 Archivist -> 9952µs - 234ns - 30ns (gcc+libstdc++)
  80. *
  81. */
  82. engine->registerFunction("print", std::make_unique<print>(std::cout));
  83. std::ifstream src_str(target);
  84. std::stringstream code;
  85. code << src_str.rdbuf();
  86. int steps = 0;
  87. decltype(std::chrono::high_resolution_clock::now()-std::chrono::high_resolution_clock::now()) per_exec{}, per_step{}, per_op{};
  88. constexpr size_t runs_count = 500;
  89. for(int runs = 0; runs < runs_count; runs++) {
  90. auto res = engine->prepare(code.str());
  91. engine->clear_variables();
  92. auto begin = std::chrono::high_resolution_clock::now();
  93. while (not engine->getValue("exit_ctr").has_value()) {
  94. engine->stepOnce();
  95. steps++;
  96. }
  97. auto end = std::chrono::high_resolution_clock::now();
  98. per_exec += (end - begin);
  99. per_step += (end - begin);
  100. per_op += (end - begin);
  101. }
  102. auto executed_ops = (runs_count * engine->op_count() * 5000 /* The code loops 5000 times */);
  103. per_exec /= runs_count;
  104. per_step /= steps;
  105. per_op = per_op / executed_ops;
  106. std::cout << "time per exec (" << runs_count << ") = " << std::chrono::duration_cast<std::chrono::microseconds>(per_exec).count() << "µs\n";
  107. 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";
  108. 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";
  109. }
  110. void compile_bench(std::string target = "./tests/scripts/testfile.test") {
  111. auto engine = scripting::prepare_interpreter(std::string{});
  112. constexpr scripting::UserScriptLibraryParameters params{};
  113. engine = scripting::register_array_lib(std::move(engine), params);
  114. engine = scripting::register_crypto_lib(std::move(engine), params);
  115. engine = scripting::register_utils_lib(std::move(engine), params);
  116. engine->registerFunction("exit", std::make_unique<terminate>());
  117. /***
  118. * Same as above but for compilation times
  119. *
  120. * 2023-07-04 Archivist -> 386µs
  121. * 2023-07-18 Archivist -> 166µs
  122. * 2023-07-18 Archivist -> 156µs
  123. */
  124. engine->registerFunction("print", std::make_unique<print>(std::cout));
  125. std::ifstream src_str("./tests/scripts/testfile.test");
  126. std::stringstream code;
  127. code << src_str.rdbuf();
  128. constexpr size_t daruns_count = 100;
  129. auto begin = std::chrono::high_resolution_clock::now();
  130. for(size_t i = 0; i < daruns_count; ++i) {
  131. [&]() __attribute__((optimize("O0"))) {
  132. auto res = engine->prepare(code.str());
  133. res = engine->prepare(code.str());
  134. res = engine->prepare(code.str());
  135. res = engine->prepare(code.str());
  136. res = engine->prepare(code.str());
  137. res = engine->prepare(code.str());
  138. res = engine->prepare(code.str());
  139. res = engine->prepare(code.str());
  140. res = engine->prepare(code.str());
  141. res = engine->prepare(code.str());
  142. }();
  143. }
  144. auto end = std::chrono::high_resolution_clock::now();
  145. auto per_exec = (end - begin) / 10 / daruns_count;
  146. std::cout << "time per exec = " << std::chrono::duration_cast<std::chrono::microseconds>(per_exec).count() << "µs\n";
  147. }
  148. void compare(std::string target, std::string expect) {
  149. auto engine = scripting::prepare_interpreter(std::string{});
  150. engine->registerFunction("exit", std::make_unique<terminate>());
  151. constexpr scripting::UserScriptLibraryParameters params{};
  152. engine = scripting::register_array_lib(std::move(engine), params);
  153. engine = scripting::register_crypto_lib(std::move(engine), params);
  154. engine = scripting::register_utils_lib(std::move(engine), params);
  155. std::stringstream str;
  156. std::string_view filename_source = target;
  157. std::string_view filename_output = expect;
  158. engine->registerFunction("print", std::make_unique<print>(str));
  159. std::ifstream src_str(std::string{filename_source});
  160. std::stringstream code;
  161. code << src_str.rdbuf();
  162. std::ifstream out_str(std::string{filename_output});
  163. std::stringstream output;
  164. output << out_str.rdbuf();
  165. auto res = engine->executeAtOnce(code.str());
  166. if (std::holds_alternative<scripting::script_value>(res)) {
  167. } else {
  168. auto &errors = std::get<std::vector<scripting::script_error>>(res);
  169. for (auto &line: errors) {
  170. str << line.message << "\n at line " << line.location->line_number << ":"
  171. << line.location->column_number << "\n";
  172. str << " " << *line.location->line_contents << "\n";
  173. str << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
  174. }
  175. }
  176. int status = 0;
  177. while(not output.eof()) {
  178. std::string expected, found;
  179. std::getline(output, expected);
  180. std::getline(str, found);
  181. bool ok = (expected != found);
  182. status+= ok ;
  183. (ok ? std::cerr : std::cout)
  184. << (not ok ? "\033[21;32m" : "\033[1;31m") << expected
  185. << std::string(std::max<size_t>(0, 40 - expected.size()), ' ')<< "| " << found << std::endl;
  186. }
  187. if(status) std::exit(status);
  188. }
  189. void immediate_interactive() {
  190. bool should_exit = false;
  191. auto engine = scripting::prepare_interpreter(std::string{});
  192. constexpr scripting::UserScriptLibraryParameters params{};
  193. engine = scripting::register_array_lib(std::move(engine), params);
  194. engine = scripting::register_crypto_lib(std::move(engine), params);
  195. engine = scripting::register_utils_lib(std::move(engine), params);
  196. engine->registerFunction("exit", std::make_unique<fn_exit>(should_exit));
  197. engine->registerFunction("print", std::make_unique<print>(std::cout));
  198. while (not should_exit) {
  199. std::string code;
  200. std::getline(std::cin, code);
  201. auto res = engine->executeAtOnce(code);
  202. if (std::holds_alternative<scripting::script_value>(res)) {
  203. } else {
  204. auto &errors = std::get<std::vector<scripting::script_error>>(res);
  205. for (auto &line: errors) {
  206. std::cout << line.message << "\n at line ";
  207. if(line.location) {
  208. std::cout << line.location->line_number << ":"
  209. << line.location->column_number << "\n";
  210. std::cout << " " << *line.location->line_contents << "\n";
  211. std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
  212. } else std::cout << "UNKNOWN\n";
  213. }
  214. }
  215. }
  216. }
  217. void exec(std::span<std::string_view> args) {
  218. //std::vector<decltype(scripting::prepare_interpreter(std::string{}))> batch;
  219. std::ifstream src_str(std::string{args.front()});
  220. std::stringstream code;
  221. code << src_str.rdbuf();
  222. std::string code_val = code.str();
  223. auto engine = scripting::prepare_interpreter(std::string{});
  224. bool exit = false;
  225. constexpr scripting::UserScriptLibraryParameters params{};
  226. engine = scripting::register_array_lib(std::move(engine), params);
  227. engine = scripting::register_crypto_lib(std::move(engine), params);
  228. engine = scripting::register_utils_lib(std::move(engine), params);
  229. engine->registerFunction("exit", std::make_unique<fn_exit>(exit));
  230. engine->registerFunction("print", std::make_unique<print>(std::cout));
  231. auto errors = engine->prepare(code_val);
  232. if(not errors.empty()) {
  233. for (auto &line: errors) {
  234. std::cout << line.message << "\n at line ";
  235. if(line.location) {
  236. std::cout << line.location->line_number << ":"
  237. << line.location->column_number << "\n";
  238. std::cout << " " << *line.location->line_contents << "\n";
  239. std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
  240. } else std::cout << "UNKNOWN\n";
  241. }
  242. return;
  243. }
  244. while (not exit) {
  245. auto res = engine->stepOnce();
  246. if (not res) {
  247. } else {
  248. auto line = res.value();
  249. std::cout << line.message << "\n at line ";
  250. if(line.location) {
  251. std::cout << line.location->line_number << ":"
  252. << line.location->column_number << "\n";
  253. std::cout << " " << *line.location->line_contents << "\n";
  254. std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
  255. } else std::cout << "UNKNOWN\n";
  256. }
  257. }
  258. }
  259. #if defined(__linux__) or defined(WIN32)
  260. constexpr bool trim_first_argument = true;
  261. #else
  262. constexpr bool trim_first_argument = false;
  263. static_assert(false, "Undefined status of the first argument");
  264. #endif
  265. int cpp_main(std::span<std::string_view> args) {
  266. if constexpr (trim_first_argument) {
  267. args = args.subspan(1);
  268. }
  269. if(args.empty() || args.front() == "immediate") {
  270. immediate_interactive();
  271. std::exit(0);
  272. } else if(args.front() == "compare") {
  273. args = args.subspan(1);
  274. if(args.size() != 2) {
  275. std::cerr << "compare expects 2 files as arguments" << std::endl;
  276. std::terminate();
  277. }
  278. } else if(args.front() == "bench_exec") {
  279. args = args.subspan(1);
  280. if(args.size() > 1) {
  281. std::cerr << "bench_exec expects 0 or 1 file as arguments" << std::endl;
  282. std::terminate();
  283. }
  284. if(args.empty()) process_bench();
  285. else process_bench(std::string{args.front()});
  286. } else if(args.front() == "bench_compile") {
  287. args = args.subspan(1);
  288. if(args.size() > 1) {
  289. std::cerr << "bench_compile expects 0 or 1 file as arguments" << std::endl;
  290. std::terminate();
  291. }
  292. if(args.empty()) compile_bench();
  293. else compile_bench(std::string{args.front()});
  294. } else if(args.front() == "exec") {
  295. exec(args.subspan(1));
  296. } else {
  297. std::cerr << "Unknown option" << std::endl;
  298. }
  299. return 0;
  300. }
  301. int main(int argc, char** argv) {
  302. std::vector<std::string_view> args;
  303. for(auto& arg : std::span(argv, argv+argc)) {
  304. args.emplace_back(arg, arg+strlen(arg));
  305. }
  306. return cpp_main(args);
  307. }