#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;
|
|
}
|
|
}
|
|
}
|
|
}
|