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.

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