Browse Source

Added array function and fixed a stack purging error

crazy_things
Ludovic 'Archivist' Lagouardette 9 months ago
parent
commit
a9c3a5ecc0
7 changed files with 367 additions and 4 deletions
  1. +6
    -1
      CMakeLists.txt
  2. +1
    -0
      include/UserScript.h
  3. +19
    -3
      script_exe/main.cpp
  4. +3
    -0
      src/interpreter.cpp
  5. +335
    -0
      src/std/array.cpp
  6. +1
    -0
      tests/scripts/006.results
  7. +2
    -0
      tests/scripts/006.script

+ 6
- 1
CMakeLists.txt View File

@ -20,8 +20,12 @@ include(CTest)
include(Catch)
add_library(UserScript STATIC
include/UserScript.h
priv_include/UserScript/interpreter.h
src/generator.cpp
src/interpreter.cpp
src/lex.cpp src/parse.cpp priv_include/UserScript/interpreter.h src/generator.cpp)
src/lex.cpp
src/parse.cpp src/std/array.cpp)
target_include_directories(UserScript PUBLIC include)
include_directories(priv_include)
@ -47,3 +51,4 @@ add_script_test("Scripting 002: Statements and Conditionals" tests/scrip
add_script_test("Scripting 003: While loops" tests/scripts/003.script tests/scripts/003.results)
add_script_test("Scripting 004: While loops with bad terminator" tests/scripts/004.script tests/scripts/004.results)
add_script_test("Scripting 005: If statements with bad terminator" tests/scripts/005.script tests/scripts/005.results)
add_script_test("Scripting 006: The stack is properly purged" tests/scripts/006.script tests/scripts/006.results)

+ 1
- 0
include/UserScript.h View File

@ -56,4 +56,5 @@ namespace scripting {
};
std::unique_ptr<UserScript> prepare_interpreter(const std::string& code);
std::unique_ptr<UserScript> register_array_lib(std::unique_ptr<UserScript> target, bool recursive_arrays = false, int32_t size_limit = 1024);
}

+ 19
- 3
script_exe/main.cpp View File

@ -104,6 +104,17 @@ struct terminate : public scripting::function_impl {
}
};
struct fn_exit : public scripting::function_impl {
bool& exit_val;
fn_exit(bool& _exit_val) : exit_val(_exit_val) {}
std::optional<scripting::script_value> apply(scripting::UserScript*,std::vector<scripting::argument>, std::optional<scripting::script_error>&) final {
exit_val = true;
return scripting::script_value({});
}
};
void process_bench(std::string target = "./tests/scripts/testfile.test") {
auto engine = scripting::prepare_interpreter(std::string{});
@ -191,6 +202,7 @@ void compare(std::string target, std::string expect) {
engine->registerFunction("exit", std::make_unique<terminate>());
engine->registerFunction("set", std::make_unique<set>());
engine = scripting::register_array_lib(std::move(engine), true, 4096);
std::stringstream str;
std::string_view filename_source = target;
@ -233,15 +245,17 @@ void compare(std::string target, std::string expect) {
}
void immediate_interactive() {
bool should_exit = false;
auto engine = scripting::prepare_interpreter(std::string{});
engine->registerFunction("identity", std::make_unique<identity>());
engine->registerFunction("exit", std::make_unique<terminate>());
engine->registerFunction("exit", std::make_unique<fn_exit>(should_exit));
engine->registerFunction("set", std::make_unique<set>());
engine = scripting::register_array_lib(std::move(engine), true, 4096);
engine->registerFunction("print", std::make_unique<print>(std::cout));
bool exit = false;
while (not exit) {
while (not should_exit) {
std::string code;
std::getline(std::cin, code);
auto res = engine->executeAtOnce(code);
@ -270,6 +284,8 @@ void exec(std::span args) {
engine->registerFunction("terminate", std::make_unique<terminate>());
engine->registerFunction("set", std::make_unique<set>());
engine = scripting::register_array_lib(std::move(engine), true, 4096);
engine->registerFunction("print", std::make_unique<print>(std::cout));
bool exit = false;
while (not exit) {

+ 3
- 0
src/interpreter.cpp View File

@ -77,6 +77,9 @@ namespace scripting {
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);
for(auto arity = v.arity; arity != 0; arity--) {
execution_stack.pop_back();
}
if(item) {
execution_stack.emplace_back(item.value());
}

+ 335
- 0
src/std/array.cpp View File

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

+ 1
- 0
tests/scripts/006.results View File

@ -0,0 +1 @@
3

+ 2
- 0
tests/scripts/006.script View File

@ -0,0 +1,2 @@
/set ary (/array 0 1 2 3 4 5 6)
/print (/array_index ary 3) "\n"

Loading…
Cancel
Save