Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

1186 рядки
38 KiB

#include <stack>
#include <map>
#include "UserScript.h"
#include "UserScript/parser.h"
namespace scripting {
void to_null(script_value& value, auto on_value, auto on_error) {
if(std::holds_alternative<null>(value)) {
on_value(std::get<null>(value));
} else {
on_error(value);
}
}
void to_int(script_value& value, auto on_value, auto on_error) {
if(std::holds_alternative<int32_t>(value)) {
on_value(std::get<int32_t>(value));
} else {
on_error(value);
}
}
void to_string(script_value& value, auto on_value, auto on_error) {
if(std::holds_alternative<std::string>(value)) {
on_value(std::get<std::string>(value));
} else {
on_error(value);
}
}
void to_array(script_value& value, auto on_value, auto on_error) {
if(std::holds_alternative<array>(value)) {
on_value(std::get<array>(value));
} else {
on_error(value);
}
}
class ByteCodeInterpreter final : public UserScript {
std::map<std::string, script_value> variables;
std::map<std::string, function> functions;
std::vector<argument> execution_stack;
public:
struct function_tag {
std::string name;
size_t arity;
std::shared_ptr<const code_location> location;
};
struct variable_tag {
std::string name;
std::shared_ptr<const code_location> location;
};
enum class operator_t : uint8_t {
logical_not,
binary_not,
unary_plus,
unary_minus,
divide,
modulo,
multiply,
subtract,
add,
bitshift_left,
bitshift_right,
rotate_left,
rotate_right,
less_than,
greater_than,
less_or_equal_than,
greater_or_equal_than,
equals,
different,
binary_and,
binary_or,
binary_xor,
logical_and,
logical_or,
INTERNAL_jump,
INTERNAL_jump_if,
INTERNAL_stack_cls,
};
struct operand {
std::variant<script_value, function_tag, variable_tag, operator_t> element;
std::shared_ptr<const code_location> location;
};
std::optional<std::reference_wrapper<script_value>> getValue(const std::string& name) {
if(auto var = variables.find(name); var != variables.end()) {
return var->second;
} else {
return std::nullopt;
}
}
bool setValue(const std::string& name, script_value value) {
if(auto var = variables.find(name); var != variables.end()) {
var->second = value;
return true;
} else {
variables.emplace(std::make_pair(name, value));
return false;
}
}
std::vector<operand> bytecode;
size_t instruction_ptr;
script_value resolve(const std::string& name) final {
auto it = variables.find(name);
if(it == variables.end()) {
return script_value{};
}
return (*it).second;
}
script_value resolve_and_pop() {
if(execution_stack.empty()) return script_value{};
auto value = std::move(execution_stack.back());
auto resolved = std::visit([&](auto v) -> script_value {
if constexpr (std::is_same_v<script_variable, decltype(v)>) {
auto it = variables.find(v.name);
if(it == variables.end()) {
return script_value{};
}
return (*it).second;
} else {
return v;
}
}, value);
execution_stack.pop_back();
return resolved;
}
void big_f_ing_switch(operand& op, std::optional<script_error>& error);
std::vector<ByteCodeInterpreter::operand> generate(std::vector<script_error>& errors, ast::block &tree, bool loop = true);
void registerFunction(std::string name, function fn) final {
functions.insert_or_assign(name, std::move(fn));
}
std::variant<script_value, std::vector<script_error>> executeAtOnce(std::string code) final {
std::vector<script_error> errors;
auto lexed = ast::lex(code, errors);
auto parsed = ast::parse(lexed, errors);
if(not errors.empty()) return errors;
bytecode = generate(errors, parsed, false);
if(not errors.empty()) return errors;
std::optional<script_error> maybe_error;
instruction_ptr = 0;
while(instruction_ptr < bytecode.size()) {
step(maybe_error);
if(maybe_error) return std::vector<script_error>({maybe_error.value()});
}
auto v = resolve_and_pop();
execution_stack.clear();
return v;
}
std::vector<script_error> prepare(std::string code) final {
std::vector<script_error> errors;
auto lexed = ast::lex(code, errors);
auto parsed = ast::parse(lexed, errors);
if(errors.empty()) {
bytecode = generate(errors, parsed, true);
}
return errors;
}
std::optional<script_error> stepOnce() final {
std::optional<script_error> error;
while(not step(error));
return error;
}
bool step(std::optional<script_error>& error);
~ByteCodeInterpreter() final {}
};
namespace wizardry {
// taken from cppreference: https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts>
struct overloaded : Ts ... {
using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
}
bool ByteCodeInterpreter::step(std::optional<script_error>& 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<argument> 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;
}
// Replace with constexpr vector & find ?
static const std::map<ast::operator_t, ByteCodeInterpreter::operator_t> mappings = {
{ast::operator_t::logical_not, ByteCodeInterpreter::operator_t::logical_not},
{ast::operator_t::binary_not, ByteCodeInterpreter::operator_t::binary_not},
{ast::operator_t::divide, ByteCodeInterpreter::operator_t::divide},
{ast::operator_t::modulo, ByteCodeInterpreter::operator_t::modulo},
{ast::operator_t::multiply, ByteCodeInterpreter::operator_t::multiply},
{ast::operator_t::subtract, ByteCodeInterpreter::operator_t::subtract},
{ast::operator_t::add, ByteCodeInterpreter::operator_t::add},
{ast::operator_t::bitshift_left, ByteCodeInterpreter::operator_t::bitshift_left},
{ast::operator_t::bitshift_right, ByteCodeInterpreter::operator_t::bitshift_right},
{ast::operator_t::rotate_left, ByteCodeInterpreter::operator_t::rotate_left},
{ast::operator_t::rotate_right, ByteCodeInterpreter::operator_t::rotate_right},
{ast::operator_t::less_than, ByteCodeInterpreter::operator_t::less_than},
{ast::operator_t::greater_than, ByteCodeInterpreter::operator_t::greater_than},
{ast::operator_t::less_or_equal_than, ByteCodeInterpreter::operator_t::less_or_equal_than},
{ast::operator_t::greater_or_equal_than, ByteCodeInterpreter::operator_t::greater_or_equal_than},
{ast::operator_t::equals, ByteCodeInterpreter::operator_t::equals},
{ast::operator_t::different, ByteCodeInterpreter::operator_t::different},
{ast::operator_t::binary_and, ByteCodeInterpreter::operator_t::binary_and},
{ast::operator_t::binary_or, ByteCodeInterpreter::operator_t::binary_or},
{ast::operator_t::binary_xor, ByteCodeInterpreter::operator_t::binary_xor},
{ast::operator_t::logical_and, ByteCodeInterpreter::operator_t::logical_and},
{ast::operator_t::logical_or, ByteCodeInterpreter::operator_t::logical_or},
};
/// GENERATION HANDLERS DECLARATIONS
template<typename T>
void handle(std::vector<ByteCodeInterpreter::operand>&, std::vector<script_error>&, T&);
template<>
void handle<ast::block>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::block& block);
template<>
void handle<ast::command_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::command_expression& cmd);
template<>
void handle<ast::binary_algebraic_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::binary_algebraic_expression& cmd);
template<>
void handle<ast::unary_algebraic_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::unary_algebraic_expression& cmd);
template<>
void handle<ast::paren_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::paren_expression& cmd);
template<>
void handle<ast::conditional>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::conditional& cmd);
template<>
void handle<ast::while_loop>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::while_loop& cmd);
template<>
void handle<ast::expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::expression& cmd);
template<>
void handle<ast::variable_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::variable_expression& cmd);
template<>
void handle<ast::literal_int_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::literal_int_expression& cmd);
template<>
void handle<ast::literal_string_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::literal_string_expression& cmd);
/// GENERATION HANDLERS DEFINITIONS
template<>
void handle<ast::block>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::block& block) {
for(auto& elem : block.contents) {
std::visit([&](auto& v) {handle(ctx, errors, *v);}, elem.contents);
ctx.push_back(ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::operator_t::INTERNAL_stack_cls});
}
}
template<>
void handle<ast::command_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::command_expression& cmd) {
for(auto it = cmd.arguments.rbegin(); it != cmd.arguments.rend(); ++it) {
std::visit([&](auto& v) {handle(ctx, errors, *v);}, (*it)->contents);
}
ctx.push_back(ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::function_tag{.name = cmd.name.value, .arity = cmd.arguments.size()}, .location = cmd.location});
}
template<>
void handle<ast::paren_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::paren_expression& expr) {
std::visit([&](auto& v) {handle(ctx, errors, *v);}, expr.content);
}
template<>
void handle<ast::expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::expression& expr) {
std::visit([&](auto& v) {handle(ctx, errors, *v);}, expr.contents);
}
template<>
void handle<ast::binary_algebraic_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::binary_algebraic_expression& expr) {
handle(ctx, errors, *expr.lhs);
handle(ctx, errors, *expr.rhs);
ctx.push_back(ByteCodeInterpreter::operand{.element = mappings.at(expr.op), .location = expr.location});
}
template<>
void handle<ast::unary_algebraic_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::unary_algebraic_expression& expr) {
handle(ctx, errors, *expr.content);
ctx.push_back(ByteCodeInterpreter::operand{.element = mappings.at(expr.op), .location = expr.location});
}
template<>
void handle<ast::variable_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::variable_expression& expr) {
ctx.push_back(ByteCodeInterpreter::operand{ByteCodeInterpreter::variable_tag{.name = expr.name.value, .location = expr.location}});
}
template<>
void handle<ast::literal_int_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::literal_int_expression& expr) {
ctx.push_back(ByteCodeInterpreter::operand{script_value{expr.value}});
}
template<>
void handle<ast::literal_string_expression>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::literal_string_expression& expr) {
ctx.push_back(ByteCodeInterpreter::operand{script_value{expr.value}});
}
template<>
void handle<ast::conditional>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::conditional& cond) {
/// some basic documentation (from before the reference stability bug but things are the same):
/// https://app.excalidraw.com/s/hxPegpAmTX/2c8KKzinqeg
std::visit([&](auto& v) {handle(ctx, errors, *v);}, cond.condition->contents);
ctx.push_back(ByteCodeInterpreter::operand{.element = script_value{}, .location = cond.location});
/// As you can see, being smart is dumb, be a fucking monkey that comes from the 70s and use 70s technology:tm: to your advantage
/// More seriously, WTF (?) we do this because we used to have a bug with unreliable references to these locations, which makes sense since we
/// don't have reference stability
auto else_side_idx = ctx.size()-1;
ctx.push_back(ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::operator_t::INTERNAL_jump_if, .location = cond.location});
handle(ctx, errors, *cond.on_condition);
if(cond.otherwise) {
ctx.push_back(ByteCodeInterpreter::operand{.element = script_value{}, .location = cond.location});
auto end_side_idx = ctx.size()-1;
ctx.push_back(ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::operator_t::INTERNAL_jump, .location = cond.location});
ctx[else_side_idx].element = static_cast<int32_t>(ctx.size())-1;
ctx[else_side_idx].location = cond.location;
handle(ctx, errors, *cond.otherwise);
ctx[end_side_idx].element = static_cast<int32_t>(ctx.size())-1;
ctx[end_side_idx].location = cond.location;
} else {
ctx[else_side_idx].element = static_cast<int32_t>(ctx.size())-1;
ctx[else_side_idx].location = cond.location;
}
}
template<>
void handle<ast::while_loop>(std::vector<ByteCodeInterpreter::operand>& ctx, std::vector<script_error>& errors, ast::while_loop& cond) {
auto beforewhile_side_idx = static_cast<int32_t>(ctx.size())-1;
std::visit([&](auto& v) {handle(ctx, errors, *v);}, cond.condition->contents);
ctx.push_back(ByteCodeInterpreter::operand{.element = script_value{}, .location = cond.location});
auto endwhile_side_idx = ctx.size()-1;
ctx.push_back(ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::operator_t::INTERNAL_jump_if, .location = cond.location});
handle(ctx, errors, *cond.on_condition);
ctx.push_back(ByteCodeInterpreter::operand{.element = script_value{beforewhile_side_idx}, .location = cond.location});
ctx.push_back(ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::operator_t::INTERNAL_jump, .location = cond.location});
ctx[endwhile_side_idx].element = static_cast<int32_t>(ctx.size())-1;
ctx[endwhile_side_idx].location = cond.location;
}
std::vector<ByteCodeInterpreter::operand> ByteCodeInterpreter::generate(std::vector<script_error>& errors, ast::block &tree, bool loop) {
std::vector<operand> code;
handle(code, errors, tree);
if(loop) {
// Here we have to deal with the quirks of jumping before the increments happens again
code.push_back(ByteCodeInterpreter::operand{.element = script_value{-1}, .location = tree.location});
code.push_back(ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::operator_t::INTERNAL_jump, .location = tree.location});
}
return code;
}
std::unique_ptr<UserScript> prepare_interpreter(const std::string& code) {
auto script = std::make_unique<ByteCodeInterpreter>();
script->prepare(code);
return script;
}
/// BIG FUCKING SWITCH
void ByteCodeInterpreter::big_f_ing_switch(operand& op, std::optional<script_error>& error) {
switch (get<ByteCodeInterpreter::operator_t>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<uint32_t *>(&lhs), true_rhs = *reinterpret_cast<uint32_t *>(&rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<null>(rhs)) {
execution_stack.push_back(script_value{1});
} else if(holds_alternative<int32_t>(rhs)) {
execution_stack.push_back(script_value{get<int32_t>(rhs) == get<int32_t>(lhs)});
} else if(holds_alternative<std::string>(rhs)) {
execution_stack.push_back(script_value{get<std::string>(rhs) == get<std::string>(lhs)});
} else if(holds_alternative<array>(rhs)) {
execution_stack.push_back(script_value{0});
//execution_stack.push_back(script_value{get<array>(rhs).value == get<array>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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<uint32_t *>(&value_lhs), true_rhs = *reinterpret_cast<uint32_t *>(&value_rhs);
execution_stack.push_back(script_value{static_cast<int32_t>(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 "// + std::to_string(holds_alternative<scripting::null>(instruction_target))
};
}
);
}
},
[&](auto &instruction_target) {
error = script_error{
op.location,
"Condition is not an integer"
};
}
);
break;
}
case operator_t::INTERNAL_stack_cls: {
execution_stack.clear();
break;
}
}
}
}