You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

418 lines
12 KiB

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