#include "UserScript/interpreter.h" namespace scripting { void to_null(script_value& value, auto on_value, auto on_error) { if(std::holds_alternative(value)) { on_value(std::get(value)); } else { on_error(value); } } void to_int(script_value& value, auto on_value, auto on_error) { if(std::holds_alternative(value)) { on_value(std::get(value)); } else { on_error(value); } } void to_string(script_value& value, auto on_value, auto on_error) { if(std::holds_alternative(value)) { on_value(std::get(value)); } else { on_error(value); } } void to_array(script_value& value, auto on_value, auto on_error) { if(std::holds_alternative(value)) { on_value(std::get(value)); } else { on_error(value); } } namespace wizardry { // taken from cppreference: https://en.cppreference.com/w/cpp/utility/variant/visit template struct overloaded : Ts ... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C++20) template overloaded(Ts...) -> overloaded; } bool ByteCodeInterpreter::step(std::optional& error) { if(instruction_ptr >= bytecode.size()) return true; auto& curr_op = bytecode[instruction_ptr]; auto& instr = curr_op.element; bool ret = std::visit(wizardry::overloaded{ [&](script_value& v){ execution_stack.push_back(v); return false; }, [&](variable_tag& v){ execution_stack.push_back(script_variable{.name = v.name}); return false; }, [&](ByteCodeInterpreter::operator_t& v){ big_f_ing_switch(curr_op, error); return v == ByteCodeInterpreter::operator_t::INTERNAL_jump or v == ByteCodeInterpreter::operator_t::INTERNAL_jump_if; }, [&](ByteCodeInterpreter::function_tag& v){ if(v.arity > execution_stack.size()) { error = script_error{.location = bytecode[instruction_ptr].location, .message = "INTERNAL ERROR: invalid amount of argument found in stack, please warn the devs, this is bad and should never happen"}; return true; } auto it = functions.find(v.name); if(it == functions.end()) { for(auto arity = v.arity; arity != 0; arity--) { execution_stack.pop_back(); } execution_stack.push_back(argument{script_value{}}); // Invalid function is not an error return true; } auto args_in_situ = std::span{execution_stack}.subspan(execution_stack.size() - v.arity); std::vector arguments{args_in_situ.begin(), args_in_situ.end()}; auto item = (*it).second->apply(this, arguments, error); if(item) { execution_stack.emplace_back(item.value()); } return true; }, }, instr); instruction_ptr = instruction_ptr + 1; return instruction_ptr >= bytecode.size() || error || ret; } std::unique_ptr prepare_interpreter(const std::string& code) { auto script = std::make_unique(); script->prepare(code); return script; } /// BIG FUCKING SWITCH void ByteCodeInterpreter::big_f_ing_switch(operand& op, std::optional& error) { switch (get(op.element)) { case operator_t::logical_not: { auto v = resolve_and_pop(); // TODO: strings and arrays to booleans? to_int( v, [&](int32_t &value) { execution_stack.push_back(script_value{int32_t(!value)}); }, [&](auto &other) { to_null( other, [&](null &) { execution_stack.push_back(script_value{int32_t(1)}); }, [&](auto &) { error = script_error{ op.location, "! operator requires an integer or null" }; execution_stack.push_back(script_value{}); } ); } ); break; } case operator_t::binary_not: { auto v = resolve_and_pop(); to_int( v, [&](int32_t &value) { execution_stack.push_back(script_value{int32_t(~value)}); }, [&](auto &other) { error = script_error{ op.location, "~ operator requires an integer" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::unary_plus: { auto v = resolve_and_pop(); error = script_error{ op.location, "unary + operator is unimplemented" }; execution_stack.push_back(script_value{}); break; } case operator_t::unary_minus: { auto rhs = resolve_and_pop(); error = script_error{ op.location, "unary - operator is unimplemented" }; execution_stack.push_back(script_value{}); break; } case operator_t::divide: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { if (value_rhs == 0) { error = script_error{ op.location, "Division by zero: / operator requires an non-zero integer as right hand side" }; execution_stack.push_back(script_value{}); return; }// TODO: this should be `value_` versions of the variables execution_stack.push_back(script_value{value_lhs / value_rhs}); }, [&](auto &other) { error = script_error { op.location, "/ operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "/ operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::modulo: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { if (value_rhs == 0) { error = script_error{ op.location, "Division by zero: % operator requires an non-zero integer as right hand side" }; execution_stack.push_back(script_value{}); return; } execution_stack.push_back(script_value{value_lhs % value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "% operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "% operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::multiply: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs * value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "* operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "* operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::subtract: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs - value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "- operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "- operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::add: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays concats? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs + value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "+ operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "+ operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::bitshift_left: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays shifts and rotates? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { if (value_rhs < 0 or value_rhs > 32) { error = script_error{ op.location, "bad shift: shift must be between 0 and 32 bits" }; execution_stack.push_back(script_value{}); return; } uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(true_lhs << true_rhs)}); }, [&](auto &other) { error = script_error{ op.location, "<< operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "<< operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::bitshift_right: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays shifts and rotates? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { if (value_rhs < 0 or value_rhs > 32) { error = script_error{ op.location, "bad shift: shift must be between 0 and 32 bits" }; execution_stack.push_back(script_value{}); return; } uint32_t true_lhs = *reinterpret_cast(&lhs), true_rhs = *reinterpret_cast(&rhs); execution_stack.push_back(script_value{static_cast(true_lhs >> true_rhs)}); }, [&](auto &other) { error = script_error{ op.location, ">> operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, ">> operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::rotate_left: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays shifts and rotates? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { if (value_rhs < 0 or value_rhs > 32) { error = script_error{ op.location, "bad rotate: rotate must be between 0 and 32 bits" }; execution_stack.push_back(script_value{}); return; } uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(std::rotl(true_lhs, true_rhs))}); }, [&](auto &other) { error = script_error{ op.location, "<<< operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "<<< operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::rotate_right: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays shifts and rotates? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { if (value_rhs < 0 or value_rhs > 32) { error = script_error{ op.location, "bad rotate: rotate must be between 0 and 32 bits" }; execution_stack.push_back(script_value{}); return; } uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(std::rotr(true_lhs, true_rhs))}); }, [&](auto &other) { error = script_error{ op.location, ">>> operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, ">>> operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::less_than: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: lexicographical strings and arrays shifts comparisons to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs < value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "< operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "< operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::greater_than: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: lexicographical strings and arrays shifts comparisons to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs > value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "> operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "> operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::less_or_equal_than: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: lexicographical strings and arrays shifts comparisons to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs <= value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "<= operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "<= operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::greater_or_equal_than: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: lexicographical strings and arrays shifts comparisons to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs >= value_rhs}); }, [&](auto &other) { error = script_error{ op.location, ">= operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, ">= operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::equals: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: error reporting (got degraded) // TODO: compare arrays if(rhs.index() != lhs.index()) { execution_stack.push_back(script_value{0}); } else if(holds_alternative(rhs)) { execution_stack.push_back(script_value{1}); } else if(holds_alternative(rhs)) { execution_stack.push_back(script_value{get(rhs) == get(lhs)}); } else if(holds_alternative(rhs)) { execution_stack.push_back(script_value{get(rhs) == get(lhs)}); } else if(holds_alternative(rhs)) { execution_stack.push_back(script_value{0}); //execution_stack.push_back(script_value{get(rhs).value == get(lhs).value}); } break; } case operator_t::different: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays different to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { execution_stack.push_back(script_value{value_lhs != value_rhs}); }, [&](auto &other) { error = script_error{ op.location, "!= operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "!= operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::binary_and: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(true_lhs & true_rhs)}); }, [&](auto &other) { error = script_error{ op.location, "& operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "& operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::binary_or: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(true_lhs | true_rhs)}); }, [&](auto &other) { error = script_error{ op.location, "| operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "| operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::binary_xor: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: XORing strings maybe? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(true_lhs ^ true_rhs)}); }, [&](auto &other) { error = script_error{ op.location, "^ operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "^ operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::logical_and: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays to booleans? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(true_lhs && true_rhs)}); }, [&](auto &other) { error = script_error{ op.location, "&& operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "&& operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::logical_or: { auto rhs = resolve_and_pop(); auto lhs = resolve_and_pop(); // TODO: strings and arrays to booleans? to_int( rhs, [&](int32_t &value_rhs) { to_int( lhs, [&](int32_t &value_lhs) { uint32_t true_lhs = *reinterpret_cast(&value_lhs), true_rhs = *reinterpret_cast(&value_rhs); execution_stack.push_back(script_value{static_cast(true_lhs || true_rhs)}); }, [&](auto &other) { error = script_error{ op.location, "|| operator requires an integer as left hand side" }; execution_stack.push_back(script_value{}); } ); }, [&](auto &other) { error = script_error{ op.location, "|| operator requires an integer as right hand side" }; execution_stack.push_back(script_value{}); } ); break; } case operator_t::INTERNAL_jump: { auto location = resolve_and_pop(); to_int( location, [&](int32_t &instruction_target) { instruction_ptr = instruction_target; }, [&](auto &instruction_target) { error = script_error{ op.location, "Jump to invalid location" }; } ); break; } case operator_t::INTERNAL_jump_if: { auto location = resolve_and_pop(); auto condition = resolve_and_pop(); // TODO: handle null as the condition to_int( condition, [&](int32_t &condition_value) { // CAUTION: the condition is inverted, this should really be called jump_if_not // TODO: rename to jump_if_not appropriately if (not condition_value) { to_int( location, [&](int32_t &instruction_target) { instruction_ptr = instruction_target; }, [&](auto &instruction_target) { error = script_error{ op.location, "JumpIf to invalid location " }; } ); } }, [&](auto &instruction_target) { error = script_error{ op.location, "Condition is not an integer" }; } ); break; } case operator_t::INTERNAL_stack_cls: { execution_stack.clear(); break; } } } }