|
|
@ -0,0 +1,335 @@ |
|
|
|
#include "UserScript.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <span>
|
|
|
|
#include <utility>
|
|
|
|
#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) { |
|
|
|
if(std::any_of(n.begin(), n.end(), [&](argument& arg) { |
|
|
|
if(std::holds_alternative<scripting::script_value>(arg)) { |
|
|
|
return true; |
|
|
|
} else { |
|
|
|
const auto& val = self->resolve(std::get<scripting::script_variable>(arg).name); |
|
|
|
if(not can_contain_arrays && std::holds_alternative<array>(val)) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
})) { |
|
|
|
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 cannot contain other arrays" |
|
|
|
}; |
|
|
|
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 { |
|
|
|
if(n.size() != 1) { |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
if(not std::holds_alternative<array>(target)) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_reverse takes exactly 1 argument of type array" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
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 { |
|
|
|
if(n.size() < 1) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_append: must provide an array variable as first argument" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
if(std::holds_alternative<script_value>(n.front())) { |
|
|
|
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(); |
|
|
|
|
|
|
|
if(not target) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_append: provided variable name is undefined" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
if(not std::holds_alternative<array>(target.value().get())) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_append: provided variable is not an array" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
auto& ary = std::get<array>(target.value().get()).value; |
|
|
|
|
|
|
|
if(not can_contain_arrays) { |
|
|
|
if(std::any_of(n.begin(), n.end(), [&](argument& arg) { |
|
|
|
if(std::holds_alternative<scripting::script_value>(arg)) { |
|
|
|
return true; |
|
|
|
} else { |
|
|
|
const auto& val = self->resolve(std::get<scripting::script_variable>(arg).name); |
|
|
|
if(not can_contain_arrays && std::holds_alternative<array>(val)) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
})) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_append: arrays cannot contain other arrays" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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_pop final : public scripting::function_impl { |
|
|
|
fn_array_pop() = default; |
|
|
|
|
|
|
|
std::optional<script_value> apply(UserScript* self, std::vector<argument> n, std::optional<script_error>& error) final { |
|
|
|
if(n.size() != 1) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_pop 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); |
|
|
|
} |
|
|
|
|
|
|
|
if(not std::holds_alternative<array>(target)) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_pop takes exactly 1 argument of type array" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
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 { |
|
|
|
if(n.size() != 1) { |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
if(not std::holds_alternative<array>(target)) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_size takes exactly 1 argument of type array" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
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_index takes exactly 1 argument of type array followed by an integer, provided a different amount" |
|
|
|
}; |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
if(not std::holds_alternative<array>(target)) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_index takes exactly 1 argument of type array followed by an integer, argument 1 is not an array" |
|
|
|
}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
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(not std::holds_alternative<int32_t>(idx)) { |
|
|
|
error = script_error{ |
|
|
|
.message = "/array_index 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>(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; |
|
|
|
}; |
|
|
|
|
|
|
|
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>()); |
|
|
|
target->registerFunction("array_size", std::make_unique<fn_array_size>()); |
|
|
|
target->registerFunction("array_index", std::make_unique<fn_array_index>()); |
|
|
|
return std::move(target); |
|
|
|
} |
|
|
|
|
|
|
|
} |