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.

373 lines
13 KiB

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