#include "UserScript.h"
|
|
#include "UserScriptRequire.h"
|
|
#include <algorithm>
|
|
#include <span>
|
|
#include <utility>
|
|
|
|
using interpreter = decltype(scripting::prepare_interpreter({}));
|
|
using namespace scripting;
|
|
|
|
struct fn_array final : public scripting::function_impl {
|
|
const int32_t size_limit;
|
|
const bool can_contain_arrays;
|
|
|
|
fn_array(int32_t _size_limit, bool _can_contain_arrays)
|
|
: size_limit(_size_limit)
|
|
, can_contain_arrays(_can_contain_arrays)
|
|
{}
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {
|
|
array ary;
|
|
if(not can_contain_arrays && details::NoArrays<0>{}.verify(self, n)) {
|
|
error = script_error{
|
|
.message = "/array: arrays cannot contain other arrays"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
if(n.size() > std::max<int32_t>(0, size_limit)) {
|
|
error = script_error{
|
|
.message = "/array: arrays exceeds max size"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::transform(n.rbegin(), n.rend(), std::back_inserter(ary.value), [&](argument& arg){
|
|
if(std::holds_alternative<scripting::script_value>(arg)) {
|
|
return std::get<scripting::script_value>(arg);
|
|
} else {
|
|
return self->resolve(std::get<scripting::script_variable>(arg).name);
|
|
}
|
|
});
|
|
|
|
return ary;
|
|
}
|
|
|
|
~fn_array() final = default;
|
|
};
|
|
|
|
struct fn_array_reverse final : public scripting::function_impl {
|
|
fn_array_reverse() = default;
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {
|
|
using verifier = Verify<details::TypeVerifier<0, array>>;
|
|
|
|
if(verifier{}.verify(self, n)) {
|
|
error = script_error{
|
|
.message = "/array_reverse takes exactly 1 argument of type array"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& arg = n.front();
|
|
script_value target;
|
|
|
|
if(std::holds_alternative<scripting::script_value>(arg)) {
|
|
target = std::get<scripting::script_value>(arg);
|
|
} else {
|
|
target = self->resolve(std::get<scripting::script_variable>(arg).name);
|
|
}
|
|
|
|
auto& ary = std::get<array>(target);
|
|
|
|
std::reverse(ary.value.begin(), ary.value.end());
|
|
|
|
return target;
|
|
}
|
|
|
|
~fn_array_reverse() final = default;
|
|
};
|
|
|
|
struct fn_array_append final : public scripting::function_impl {
|
|
const std::string name;
|
|
const int32_t size_limit;
|
|
const bool can_contain_arrays;
|
|
|
|
fn_array_append(std::string _name, int32_t _size_limit, bool _can_contain_arrays)
|
|
: name(std::move(_name))
|
|
, size_limit(_size_limit)
|
|
, can_contain_arrays(_can_contain_arrays)
|
|
{}
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {
|
|
using verifier = Verify<
|
|
details::TypeVerifier<0, array>,
|
|
details::VariableVerifier<0>,
|
|
details::SizeAtLeast<1>
|
|
>;
|
|
|
|
if(verifier{}.verify(self, n) && can_contain_arrays || details::NoArrays<1>{}.verify(self, n)) {
|
|
error = script_error{
|
|
.message = "/array_append: must provide an array variable as first argument"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto target = self->getValue(std::get<script_variable>(n.back()).name);
|
|
n.pop_back();
|
|
|
|
auto& ary = std::get<array>(target.value().get()).value;
|
|
|
|
if(n.size() + ary.size() > std::max<int32_t>(0, size_limit)) {
|
|
error = script_error{
|
|
.message = "/array_append: array would exceed size limit"
|
|
};
|
|
return script_value{0};
|
|
}
|
|
|
|
std::transform(n.rbegin(), n.rend(), std::back_inserter(ary), [&](argument& arg){
|
|
if(std::holds_alternative<scripting::script_value>(arg)) {
|
|
return std::get<scripting::script_value>(arg);
|
|
} else {
|
|
return self->resolve(std::get<scripting::script_variable>(arg).name);
|
|
}
|
|
});
|
|
|
|
return script_value{1};
|
|
}
|
|
|
|
~fn_array_append() final = default;
|
|
};
|
|
|
|
struct fn_array_prepend final : public scripting::function_impl {
|
|
const std::string name;
|
|
const int32_t size_limit;
|
|
const bool can_contain_arrays;
|
|
|
|
fn_array_prepend(std::string _name, int32_t _size_limit, bool _can_contain_arrays)
|
|
: name(std::move(_name))
|
|
, size_limit(_size_limit)
|
|
, can_contain_arrays(_can_contain_arrays)
|
|
{}
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {
|
|
using verifier = Verify<
|
|
details::TypeVerifier<0, array>,
|
|
details::VariableVerifier<0>,
|
|
details::SizeEquals<2>
|
|
>;
|
|
|
|
if((verifier{}.verify(self, n)) && (can_contain_arrays || details::NoArrays<1>{}.verify(self, n))) {
|
|
error = script_error{
|
|
.message = "/array_append: must provide an array variable as first argument"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto target = self->getValue(std::get<script_variable>(n.back()).name);
|
|
n.pop_back();
|
|
|
|
auto& ary = std::get<array>(target.value().get()).value;
|
|
|
|
if(n.size() + ary.size() > std::max<int32_t>(0, size_limit)) {
|
|
error = script_error{
|
|
.message = name + ": array would exceed size limit"
|
|
};
|
|
return script_value{0};
|
|
}
|
|
|
|
if(std::holds_alternative<scripting::script_value>(n.front())) {
|
|
ary.push_front(std::get<scripting::script_value>(n.front()));
|
|
} else {
|
|
ary.push_front(self->resolve(std::get<scripting::script_variable>(n.front()).name));
|
|
}
|
|
|
|
return script_value{1};
|
|
}
|
|
|
|
~fn_array_prepend() final = default;
|
|
};
|
|
|
|
struct fn_array_pop final : public scripting::function_impl {
|
|
std::string name;
|
|
|
|
fn_array_pop(std::string _name) : name(std::move(_name)) {}
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {
|
|
using verifier = Verify<
|
|
details::TypeVerifier<0, array>,
|
|
details::VariableVerifier<0>,
|
|
details::SizeEquals<1>
|
|
>;
|
|
|
|
if(verifier{}.verify(self, n)) {
|
|
error = script_error{
|
|
.message = name + " takes exactly 1 argument of type array"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& arg = n.front();
|
|
script_value target;
|
|
|
|
if(std::holds_alternative<scripting::script_value>(arg)) {
|
|
target = std::get<scripting::script_value>(arg);
|
|
} else {
|
|
target = self->resolve(std::get<scripting::script_variable>(arg).name);
|
|
}
|
|
|
|
auto& ary = std::get<array>(target);
|
|
|
|
if(ary.value.empty()) {
|
|
return {null{}};
|
|
}
|
|
|
|
auto value = ary.value.back();
|
|
|
|
ary.value.pop_back();
|
|
|
|
return value;
|
|
}
|
|
|
|
~fn_array_pop() final = default;
|
|
};
|
|
|
|
struct fn_array_size final : public scripting::function_impl {
|
|
fn_array_size() = default;
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {using verifier = Verify<
|
|
details::TypeVerifier<0, array>,
|
|
details::VariableVerifier<0>,
|
|
details::SizeEquals<1>
|
|
>;
|
|
|
|
if(verifier{}.verify(self, n)) {
|
|
error = script_error{
|
|
.message = "/array_size takes exactly 1 argument of type array"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& arg = n.front();
|
|
script_value target;
|
|
|
|
if(std::holds_alternative<scripting::script_value>(arg)) {
|
|
target = std::get<scripting::script_value>(arg);
|
|
} else {
|
|
target = self->resolve(std::get<scripting::script_variable>(arg).name);
|
|
}
|
|
|
|
return static_cast<int32_t>(std::get<array>(target).value.size());
|
|
}
|
|
|
|
~fn_array_size() final = default;
|
|
};
|
|
|
|
struct fn_array_index final : public scripting::function_impl {
|
|
fn_array_index() = default;
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {
|
|
if(n.size() != 2) {
|
|
error = script_error{
|
|
.message = "/array_size takes exactly 2 argument of type (array, integer)"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
using verifier = Verify<
|
|
details::TypeVerifier<0, array>,
|
|
details::VariableVerifier<0>,
|
|
details::TypeVerifier<1, int32_t>
|
|
>;
|
|
|
|
if(verifier{}.verify(self, n)) {
|
|
error = script_error{
|
|
.message = "/array_index takes exactly 2 argument of type (array, integer)"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& arg = n.back();
|
|
script_value target;
|
|
|
|
if(std::holds_alternative<scripting::script_value>(arg)) {
|
|
target = std::get<scripting::script_value>(arg);
|
|
} else {
|
|
target = self->resolve(std::get<scripting::script_variable>(arg).name);
|
|
}
|
|
|
|
auto& idx_arg = n.front();
|
|
script_value idx;
|
|
|
|
if(std::holds_alternative<scripting::script_value>(idx_arg)) {
|
|
idx = std::get<scripting::script_value>(idx_arg);
|
|
} else {
|
|
idx = self->resolve(std::get<scripting::script_variable>(idx_arg).name);
|
|
}
|
|
|
|
if(static_cast<int32_t>(std::get<array>(target).value.size()) <= std::get<int32_t>(idx)) {
|
|
error = script_error{
|
|
.message = "/array_index index must be smaller that the array size, the first element of the array has index 0"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
if(std::get<int32_t>(idx) < 0) {
|
|
error = script_error{
|
|
.message = "/array_index index must be 0 or more"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
return std::get<array>(target).value.at(static_cast<size_t>(std::get<int32_t>(idx)));
|
|
}
|
|
|
|
~fn_array_index() final = default;
|
|
};
|
|
|
|
struct fn_array_set final : public scripting::function_impl {
|
|
fn_array_set() = default;
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final {
|
|
if(n.size() != 3) {
|
|
error = script_error{
|
|
.message = "/array_set takes exactly 3 argument of type (array, integer, value), provided a different amount"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& arg = n.back();
|
|
|
|
if(std::holds_alternative<scripting::script_value>(arg)) {
|
|
error = script_error{
|
|
.message = "/array_set takes exactly 3 argument of type (array, integer, value), the array needs to be a variable"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto target = self->getValue(std::get<scripting::script_variable>(arg).name);
|
|
|
|
if(not target) {
|
|
error = script_error{
|
|
.message = "/array_set takes exactly 3 argument of type (array, integer, value), provided array variable is undefined"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
if(not std::holds_alternative<array>(target.value().get())) {
|
|
error = script_error{
|
|
.message = "/array_set takes exactly 1 argument of type array followed by an integer, argument 1 is not an array"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
|
|
auto& concrete_target = std::get<array>(target.value().get());
|
|
|
|
auto& idx_arg = n[1];
|
|
script_value idx;
|
|
|
|
if(std::holds_alternative<scripting::script_value>(idx_arg)) {
|
|
idx = std::get<scripting::script_value>(idx_arg);
|
|
} else {
|
|
idx = self->resolve(std::get<scripting::script_variable>(idx_arg).name);
|
|
}
|
|
|
|
if(not std::holds_alternative<int32_t>(idx)) {
|
|
error = script_error{
|
|
.message = "/array_set takes exactly 1 argument of type array followed by an integer, argument 2 is not an integer"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
if(static_cast<int32_t>(concrete_target.value.size()) <= std::get<int32_t>(idx)) {
|
|
error = script_error{
|
|
.message = "/array_set index must be smaller that the array size, the first element of the array has index 0"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
if(std::get<int32_t>(idx) < 0) {
|
|
error = script_error{
|
|
.message = "/array_set index must be 0 or more"
|
|
};
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& value_arg = n.front();
|
|
script_value value;
|
|
|
|
if(std::holds_alternative<scripting::script_value>(value_arg)) {
|
|
value = std::get<scripting::script_value>(value_arg);
|
|
} else {
|
|
value = self->resolve(std::get<scripting::script_variable>(value_arg).name);
|
|
}
|
|
|
|
concrete_target.value.at(std::get<int32_t>(idx)) = value;
|
|
|
|
return concrete_target.value[std::get<int32_t>(idx)];
|
|
}
|
|
|
|
~fn_array_set() final = default;
|
|
};
|
|
|
|
namespace scripting {
|
|
interpreter register_array_lib(interpreter target, bool recursive_arrays, int32_t size_limit) {
|
|
target->registerFunction("array", std::make_unique<fn_array>(size_limit, recursive_arrays));
|
|
target->registerFunction("array_reverse", std::make_unique<fn_array_reverse>());
|
|
target->registerFunction("array_append", std::make_unique<fn_array_append>("array_append", size_limit, recursive_arrays));
|
|
target->registerFunction("array_push", std::make_unique<fn_array_append>("array_push", size_limit, recursive_arrays));
|
|
target->registerFunction("array_pop", std::make_unique<fn_array_pop>("array_pop"));
|
|
target->registerFunction("array_size", std::make_unique<fn_array_size>());
|
|
target->registerFunction("array_index", std::make_unique<fn_array_index>());
|
|
target->registerFunction("array_set", std::make_unique<fn_array_set>());
|
|
target->registerFunction("queue_enqueue", std::make_unique<fn_array_prepend>("queue_enqueue", size_limit, recursive_arrays));
|
|
target->registerFunction("queue_dequeue", std::make_unique<fn_array_pop>("queue_dequeue"));
|
|
return std::move(target);
|
|
}
|
|
}
|