2 コミット

作成者 SHA1 メッセージ 日付
  Ludovic 'Archivist' Lagouardette 5156035f84 small edit to test 008 9ヶ月前
  Ludovic 'Archivist' Lagouardette 8f97e3f52f Added some utils, more tests, more bug fixes 10ヶ月前
10個のファイルの変更374行の追加105行の削除
分割表示
  1. +2
    -1
      CMakeLists.txt
  2. +14
    -2
      include/UserScript.h
  3. +28
    -0
      priv_include/UserScript/interpreter.h
  4. +63
    -89
      script_exe/main.cpp
  5. +6
    -6
      src/std/array.cpp
  6. +5
    -5
      src/std/crypto.cpp
  7. +227
    -0
      src/std/utils.cpp
  8. +4
    -2
      tests/parser_test.cpp
  9. +5
    -0
      tests/scripts/008.results
  10. +20
    -0
      tests/scripts/008.script

+ 2
- 1
CMakeLists.txt ファイルの表示

@ -25,7 +25,7 @@ add_library(UserScript STATIC
src/generator.cpp
src/interpreter.cpp
src/lex.cpp
src/parse.cpp src/std/array.cpp src/std/crypto.cpp include/UserScriptRequire.h include/UserScriptWizardry.h)
src/parse.cpp src/std/array.cpp src/std/crypto.cpp include/UserScriptRequire.h include/UserScriptWizardry.h src/std/utils.cpp)
target_include_directories(UserScript PUBLIC include)
include_directories(priv_include)
@ -53,3 +53,4 @@ add_script_test("Scripting 004: While loops with bad terminator" tests/scrip
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)
add_script_test("Scripting 007: Cryptography seems to work" tests/scripts/007.script tests/scripts/007.results)
add_script_test("Scripting 008: Utils seems to work" tests/scripts/008.script tests/scripts/008.results)

+ 14
- 2
include/UserScript.h ファイルの表示

@ -51,6 +51,10 @@ namespace scripting {
virtual void registerFunction(std::string name, function fn) = 0;
virtual void clear_variables() = 0;
virtual int32_t op_count() = 0;
virtual int32_t var_count() = 0;
virtual void var_clear() = 0;
virtual std::optional<script_value> varname_by_idx(int32_t idx) = 0;
virtual std::optional<script_value> var_by_idx(int32_t idx) = 0;
virtual script_value resolve(const std::string& name) = 0;
virtual std::variant<script_value, std::vector<script_error>> executeAtOnce(std::string code) = 0;
virtual std::vector<script_error> prepare(std::string code) = 0;
@ -58,7 +62,15 @@ namespace scripting {
virtual ~UserScript() = default;
};
struct UserScriptLibraryParameters {
bool recursive_arrays = false;
int32_t array_size_limit = 1024;
int32_t string_size_limit = 1024;
int32_t variables_count = 1024;
};
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);
std::unique_ptr<UserScript> register_crypto_lib(std::unique_ptr<UserScript> target, int32_t array_size_limit = 1024, int32_t string_size_limit = 1024);
std::unique_ptr<UserScript> register_array_lib(std::unique_ptr<UserScript> target, const UserScriptLibraryParameters&);
std::unique_ptr<UserScript> register_crypto_lib(std::unique_ptr<UserScript> target, const UserScriptLibraryParameters&);
std::unique_ptr<UserScript> register_utils_lib(std::unique_ptr<UserScript> target, const UserScriptLibraryParameters&);
}

+ 28
- 0
priv_include/UserScript/interpreter.h ファイルの表示

@ -121,6 +121,34 @@ namespace scripting {
return bytecode.size();
}
int32_t var_count() final {
return variables.size();
}
void var_clear() final {
variables.clear();
}
std::optional<script_value> var_by_idx(int32_t idx) final {
if(idx >= var_count() || idx < 0) return std::nullopt;
for(auto& val : variables) {
if(not idx--) {
return val.second;
}
}
return std::nullopt;
}
std::optional<script_value> varname_by_idx(int32_t idx) final {
if(idx >= var_count() || idx < 0) return std::nullopt;
for(auto& val : variables) {
if(not idx--) {
return val.first;
}
}
return std::nullopt;
}
std::variant<script_value, std::vector<script_error>> executeAtOnce(std::string code) final {
std::vector<script_error> errors;
auto lexed = ast::lex(code, errors);

+ 63
- 89
script_exe/main.cpp ファイルの表示

@ -32,21 +32,6 @@ void print_value(std::ostream& stream, const scripting::script_value& res, bool
}
}
struct identity : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
if(args.size() != 1) {
errors = scripting::script_error{.message = "identity expects a single argument"};
} else {
if(std::holds_alternative<scripting::script_value>(args.front())) {
return std::get<scripting::script_value>(args.front());
} else {
return self->resolve(std::get<scripting::script_variable>(args.front()).name);
}
}
return scripting::script_value({});
}
};
struct print : public scripting::function_impl {
std::ostream& stream;
@ -67,39 +52,6 @@ struct print : public scripting::function_impl {
}
};
struct set : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
if(args.size() != 2) {
errors = scripting::script_error{
.message = "set expects 2 arguments"
};
return scripting::script_value{};
}
auto& var = args.back();
if(not holds_alternative<scripting::script_variable>(var)) {
errors = scripting::script_error{
.message = "set expects the first argument to be a target variable"
};
return scripting::script_value{};
}
auto& arg = args.front();
if(std::holds_alternative<scripting::script_value>(arg)) {
self->setValue(get<scripting::script_variable>(var).name, std::get<scripting::script_value>(arg));
} else {
self->setValue(get<scripting::script_variable>(var).name, self->resolve(std::get<scripting::script_variable>(arg).name));
}
if(auto v = self->getValue(get<scripting::script_variable>(var).name); v) {
return v.value();
} else {
return scripting::script_value{};
}
}
};
struct terminate : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript*,std::vector<scripting::argument>, std::optional<scripting::script_error>&) final {
std::exit(1);
@ -122,9 +74,14 @@ struct fn_exit : public scripting::function_impl {
void process_bench(std::string target = "./tests/scripts/testfile.test") {
auto engine = scripting::prepare_interpreter(std::string{});
engine->registerFunction("identity", std::make_unique<identity>());
constexpr scripting::UserScriptLibraryParameters params{};
engine = scripting::register_array_lib(std::move(engine), params);
engine = scripting::register_crypto_lib(std::move(engine), params);
engine = scripting::register_utils_lib(std::move(engine), params);
engine->registerFunction("exit", std::make_unique<terminate>());
engine->registerFunction("set", std::make_unique<set>());
/***
* This is a half assed benchmark,
@ -178,9 +135,14 @@ void process_bench(std::string target = "./tests/scripts/testfile.test") {
void compile_bench(std::string target = "./tests/scripts/testfile.test") {
auto engine = scripting::prepare_interpreter(std::string{});
engine->registerFunction("identity", std::make_unique<identity>());
constexpr scripting::UserScriptLibraryParameters params{};
engine = scripting::register_array_lib(std::move(engine), params);
engine = scripting::register_crypto_lib(std::move(engine), params);
engine = scripting::register_utils_lib(std::move(engine), params);
engine->registerFunction("exit", std::make_unique<terminate>());
engine->registerFunction("set", std::make_unique<set>());
/***
* Same as above but for compilation times
@ -220,15 +182,13 @@ void compile_bench(std::string target = "./tests/scripts/testfile.test") {
void compare(std::string target, std::string expect) {
auto engine = scripting::prepare_interpreter(std::string{});
engine->registerFunction("identity", std::make_unique<identity>());
engine->registerFunction("exit", std::make_unique<terminate>());
engine->registerFunction("set", std::make_unique<set>());
constexpr size_t array_limit = 4096;
constexpr size_t string_limit = 4096;
constexpr scripting::UserScriptLibraryParameters params{};
engine = scripting::register_array_lib(std::move(engine), true, array_limit);
engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit);
engine = scripting::register_array_lib(std::move(engine), params);
engine = scripting::register_crypto_lib(std::move(engine), params);
engine = scripting::register_utils_lib(std::move(engine), params);
std::stringstream str;
std::string_view filename_source = target;
@ -274,15 +234,13 @@ 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<fn_exit>(should_exit));
engine->registerFunction("set", std::make_unique<set>());
constexpr scripting::UserScriptLibraryParameters params{};
constexpr size_t array_limit = 4096;
constexpr size_t string_limit = 4096;
engine = scripting::register_array_lib(std::move(engine), params);
engine = scripting::register_crypto_lib(std::move(engine), params);
engine = scripting::register_utils_lib(std::move(engine), params);
engine = scripting::register_array_lib(std::move(engine), true, array_limit);
engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit);
engine->registerFunction("exit", std::make_unique<fn_exit>(should_exit));
engine->registerFunction("print", std::make_unique<print>(std::cout));
while (not should_exit) {
@ -306,38 +264,54 @@ void immediate_interactive() {
}
void exec(std::span<std::string_view> args) {
std::vector<decltype(scripting::prepare_interpreter(std::string{}))> batch;
//std::vector<decltype(scripting::prepare_interpreter(std::string{}))> batch;
std::ifstream src_str(std::string{args.front()});
std::stringstream code;
code << src_str.rdbuf();
std::string code_val = code.str();
auto engine = scripting::prepare_interpreter(std::string{});
bool exit = false;
engine->registerFunction("identity", std::make_unique<identity>());
engine->registerFunction("terminate", std::make_unique<terminate>());
engine->registerFunction("set", std::make_unique<set>());
constexpr scripting::UserScriptLibraryParameters params{};
constexpr size_t array_limit = 4096;
constexpr size_t string_limit = 4096;
engine = scripting::register_array_lib(std::move(engine), params);
engine = scripting::register_crypto_lib(std::move(engine), params);
engine = scripting::register_utils_lib(std::move(engine), params);
engine = scripting::register_array_lib(std::move(engine), true, array_limit);
engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit);
engine->registerFunction("exit", std::make_unique<fn_exit>(exit));
engine->registerFunction("print", std::make_unique<print>(std::cout));
bool exit = false;
auto errors = engine->prepare(code_val);
if(not errors.empty()) {
for (auto &line: errors) {
std::cout << line.message << "\n at line ";
if(line.location) {
std::cout << line.location->line_number << ":"
<< line.location->column_number << "\n";
std::cout << " " << *line.location->line_contents << "\n";
std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
} else std::cout << "UNKNOWN\n";
}
return;
}
while (not exit) {
std::string code;
std::getline(std::cin, code);
auto res = engine->executeAtOnce(code);
if (std::holds_alternative<scripting::script_value>(res)) {
auto res = engine->stepOnce();
if (not res) {
} else {
auto &errors = std::get<std::vector<scripting::script_error>>(res);
for (auto &line: errors) {
std::cout << line.message << "\n at line ";
if(line.location) {
std::cout << line.location->line_number << ":"
<< line.location->column_number << "\n";
std::cout << " " << *line.location->line_contents << "\n";
std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
} else std::cout << "UNKNOWN\n";
}
auto line = res.value();
std::cout << line.message << "\n at line ";
if(line.location) {
std::cout << line.location->line_number << ":"
<< line.location->column_number << "\n";
std::cout << " " << *line.location->line_contents << "\n";
std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
} else std::cout << "UNKNOWN\n";
}
}
}
@ -380,7 +354,7 @@ int cpp_main(std::span args) {
if(args.empty()) compile_bench();
else compile_bench(std::string{args.front()});
} else if(args.front() == "exec") {
c1">// exec(args.subspan(1));
n">exec(args.subspan(1));
} else {
std::cerr << "Unknown option" << std::endl;
}

+ 6
- 6
src/std/array.cpp ファイルの表示

@ -18,7 +18,7 @@ struct fn_array final : public scripting::function_impl {
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)) {
if(not can_contain_arrays && not details::NoArrays<0>{}.verify(self, n)) {
error = script_error{
.message = "/array: arrays cannot contain other arrays"
};
@ -402,16 +402,16 @@ struct fn_array_set final : public scripting::function_impl {
};
namespace scripting {
interpreter register_array_lib(interpreter target, t">bool recursive_arrays, int32_t size_limit) {
target->registerFunction("array", std::make_unique<fn_array>(size_limit, recursive_arrays));
interpreter register_array_lib(interpreter target, ">const UserScriptLibraryParameters& params) {
target->registerFunction("array", std::make_unique<fn_array>(params.array_size_limit, params.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_append", std::make_unique<fn_array_append>("array_append", params.array_size_limit, params.recursive_arrays));
target->registerFunction("array_push", std::make_unique<fn_array_append>("array_push", params.array_size_limit, params.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_enqueue", std::make_unique<fn_array_prepend>("queue_enqueue", params.array_size_limit, params.recursive_arrays));
target->registerFunction("queue_dequeue", std::make_unique<fn_array_pop>("queue_dequeue"));
return std::move(target);
}

+ 5
- 5
src/std/crypto.cpp ファイルの表示

@ -375,11 +375,11 @@ namespace scripting {
~fn_decode_n() final = default;
};
interpreter register_crypto_lib(interpreter target, t">int32_t array_size_limit, int32_t string_size_limit) {
target->registerFunction("string_to_binary", std::make_unique<fn_string_to_binary>(array_size_limit));
target->registerFunction("binary_to_string", std::make_unique<fn_binary_to_string>(string_size_limit));
target->registerFunction("encode_n", std::make_unique<fn_encode_n>(string_size_limit));
target->registerFunction("decode_n", std::make_unique<fn_decode_n>(string_size_limit));
interpreter register_crypto_lib(interpreter target, ">const UserScriptLibraryParameters& params) {
target->registerFunction("string_to_binary", std::make_unique<fn_string_to_binary>(params.array_size_limit));
target->registerFunction("binary_to_string", std::make_unique<fn_binary_to_string>(params.string_size_limit));
target->registerFunction("encode_n", std::make_unique<fn_encode_n>(params.string_size_limit));
target->registerFunction("decode_n", std::make_unique<fn_decode_n>(params.string_size_limit));
return std::move(target);
}
}

+ 227
- 0
src/std/utils.cpp ファイルの表示

@ -0,0 +1,227 @@
#include "UserScript.h"
#include <algorithm>
#include <span>
#include <utility>
#include "UserScriptWizardry.h"
#include "UserScriptRequire.h"
using interpreter = decltype(scripting::prepare_interpreter({}));
using namespace scripting;
namespace scripting {
struct fn_identity : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
// TODO: use the requirement sets here
if(args.size() != 1) {
errors = scripting::script_error{.message = "identity expects a single argument"};
} else {
if(std::holds_alternative<scripting::script_value>(args.front())) {
return std::get<scripting::script_value>(args.front());
} else {
return self->resolve(std::get<scripting::script_variable>(args.front()).name);
}
}
return scripting::script_value({});
}
};
struct fn_var_clear : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
using verifier = Verify<
details::SizeEquals<0>
>;
if(not verifier{}.verify(self, args)) {
errors = scripting::script_error{.message = "var_clear argument error"};
} else {
self->clear_variables();
}
return scripting::script_value({});
}
};
struct fn_var_name : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
using verifier = Verify<
details::SizeEquals<1>,
details::TypeVerifier<0, int32_t>
>;
if(not verifier{}.verify(self, args)) {
errors = scripting::script_error{.message = "var_name argument error"};
} else {
int32_t idx = 0;
if(std::holds_alternative<scripting::script_value>(args.front())) {
idx = std::get<int32_t>(std::get<scripting::script_value>(args.front()));
} else {
idx = std::get<int32_t>(self->resolve(std::get<scripting::script_variable>(args.front()).name));
}
if(auto val = self->varname_by_idx(idx); val) return val;
}
return scripting::script_value({});
}
};
struct fn_var_count : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
using verifier = Verify<
details::SizeEquals<0>
>;
if(not verifier{}.verify(self, args)) {
errors = scripting::script_error{.message = "var_count argument error"};
} else {
return self->var_count();
}
return scripting::script_value({});
}
};
struct fn_null : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
using verifier = Verify<
details::SizeEquals<0>
>;
if(not verifier{}.verify(self, args)) {
errors = scripting::script_error{.message = "null argument error"};
}
return scripting::script_value({});
}
};
struct fn_var_cap : public scripting::function_impl {
int32_t var_cap;
explicit fn_var_cap(int32_t _var_cap)
: var_cap(_var_cap) {}
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
using verifier = Verify<
details::SizeEquals<0>
>;
if(not verifier{}.verify(self, args)) {
errors = scripting::script_error{.message = "var_cap argument error"};
return scripting::script_value({});
}
return var_cap;
}
};
struct fn_var_type : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
using verifier = Verify<
details::SizeEquals<1>
>;
if(not verifier{}.verify(self, args)) {
errors = scripting::script_error{.message = "var_type argument error"};
} else {
script_value var;
if(std::holds_alternative<scripting::script_value>(args.front())) {
var = std::get<scripting::script_value>(args.front());
} else {
var = self->resolve(std::get<scripting::script_variable>(args.front()).name);
}
return std::visit(
wizardry::overloaded{
[](int32_t&) {return "integer";},
[](array&) {return "array";},
[](null&) {return "null";},
[](std::string&) {return "string";}
},
var
);
}
return scripting::script_value({});
}
};
struct fn_var_dump : public scripting::function_impl {
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
using verifier = Verify<
details::SizeEquals<1>,
details::TypeVerifier<0, int32_t>
>;
if(not verifier{}.verify(self, args)) {
errors = scripting::script_error{.message = "var_dump argument error"};
} else {
int32_t idx = 0;
if(std::holds_alternative<scripting::script_value>(args.front())) {
idx = std::get<int32_t>(std::get<scripting::script_value>(args.front()));
} else {
idx = std::get<int32_t>(self->resolve(std::get<scripting::script_variable>(args.front()).name));
}
if(auto val = self->var_by_idx(idx); val) {
return val.value();
}
}
return scripting::script_value({});
}
};
struct fn_set : public scripting::function_impl {
int32_t max_variable_count;
explicit fn_set(int32_t _max_variable_count)
: max_variable_count(_max_variable_count) {}
std::optional<scripting::script_value> apply(scripting::UserScript* self,std::vector<scripting::argument> args, std::optional<scripting::script_error>& errors) final {
// TODO: use the requirement sets here
if(args.size() != 2) {
errors = scripting::script_error{
.message = "set expects 2 arguments"
};
return scripting::script_value{};
}
auto& var = args.back();
if(not holds_alternative<scripting::script_variable>(var)) {
errors = scripting::script_error{
.message = "set expects the first argument to be a target variable"
};
return scripting::script_value{};
}
auto& arg = args.front();
if(std::holds_alternative<scripting::script_value>(arg)) {
if(self->getValue(get<scripting::script_variable>(var).name) && self->var_count() >= max_variable_count) {
errors = scripting::script_error{
.message = "set expects 2 arguments"
};
return scripting::script_value{};
}
self->setValue(get<scripting::script_variable>(var).name, std::get<scripting::script_value>(arg));
} else {
self->setValue(get<scripting::script_variable>(var).name, self->resolve(std::get<scripting::script_variable>(arg).name));
}
if(auto v = self->getValue(get<scripting::script_variable>(var).name); v) {
return v.value();
} else {
return scripting::script_value{};
}
}
};
interpreter register_utils_lib(interpreter target, const UserScriptLibraryParameters& params) {
target->registerFunction("identity", std::make_unique<fn_identity>());
target->registerFunction("var_clear", std::make_unique<fn_var_clear>());
target->registerFunction("var_name", std::make_unique<fn_var_name>());
target->registerFunction("var_count", std::make_unique<fn_var_count>());
target->registerFunction("var_cap", std::make_unique<fn_var_cap>(params.variables_count));
target->registerFunction("var_dump", std::make_unique<fn_var_dump>());
target->registerFunction("var_type", std::make_unique<fn_var_type>());
target->registerFunction("null", std::make_unique<fn_null>());
target->registerFunction("set", std::make_unique<fn_set>(params.variables_count));
return std::move(target);
}
}

+ 4
- 2
tests/parser_test.cpp ファイルの表示

@ -86,6 +86,7 @@ constexpr auto runner = [](){
"../tests/scripts/005.script",
"../tests/scripts/006.script",
"../tests/scripts/007.script",
"../tests/scripts/008.script",
};
auto seed = seed_template == -1 ? std::random_device{}() : seed_template;
@ -124,7 +125,7 @@ constexpr auto runner = [](){
size_t count = 0;
size_t error_cnt = 0;
size_t success_cnt = 0;
constexpr size_t max_count = 7000000;
constexpr size_t max_count = 8000000;
auto begin = std::chrono::high_resolution_clock::now();
while(count < max_count) {
@ -168,4 +169,5 @@ TEST_CASE("Try to crash the parser (known seeds)") {
TEST_CASE("Try to crash the parser (new seeds)") {
runner<>();
}
}

+ 5
- 0
tests/scripts/008.results ファイルの表示

@ -0,0 +1,5 @@
4 variables detected, printing:
answer: 42
ary: ["wizard", 7]
hello: "world"
it: 3

+ 20
- 0
tests/scripts/008.script ファイルの表示

@ -0,0 +1,20 @@
/set hello "world"
/set answer 42
/set ary (/array "wizard" 7)
if(it == (/null))
/set it 0
endif
/print (/var_count) " variables detected, printing:\n"
while(it < (/var_count))
/print (/var_name it) ": "
if((/var_type (/var_dump it)) == "string")
/print "\"" (/var_dump it) "\"\n"
else
/print (/var_dump it) "\n"
endif
/set it it+1
endwhile
/exit

読み込み中…
キャンセル
保存