forked from OctaForge/libcubescript
use normal exception handling to handle errors C++-side, also guarantee valid pointer from alloc
parent
35ebb0e822
commit
4510e53916
|
@ -336,6 +336,36 @@ private:
|
||||||
bool p_gap;
|
bool p_gap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CsErrorException {
|
||||||
|
friend struct CsState;
|
||||||
|
|
||||||
|
CsErrorException() = delete;
|
||||||
|
CsErrorException(CsErrorException const &) = delete;
|
||||||
|
CsErrorException(CsErrorException &&v):
|
||||||
|
p_errmsg(v.p_errmsg), p_stack(ostd::move(v.p_stack))
|
||||||
|
{}
|
||||||
|
|
||||||
|
ostd::ConstCharRange what() const {
|
||||||
|
return p_errmsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
CsStackState &get_stack() {
|
||||||
|
return p_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
CsStackState const &get_stack() const {
|
||||||
|
return p_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CsErrorException(ostd::ConstCharRange v, CsStackState &&st):
|
||||||
|
p_errmsg(v), p_stack(ostd::move(st))
|
||||||
|
{}
|
||||||
|
|
||||||
|
ostd::ConstCharRange p_errmsg;
|
||||||
|
CsStackState p_stack;
|
||||||
|
};
|
||||||
|
|
||||||
using CsHookCb = ostd::Function<void(CsState &)>;
|
using CsHookCb = ostd::Function<void(CsState &)>;
|
||||||
using CsPanicCb =
|
using CsPanicCb =
|
||||||
ostd::Function<void(CsState &, ostd::ConstCharRange, CsStackState)>;
|
ostd::Function<void(CsState &, ostd::ConstCharRange, CsStackState)>;
|
||||||
|
@ -379,15 +409,10 @@ struct OSTD_EXPORT CsState {
|
||||||
CsIdentLink *p_callstack = nullptr;
|
CsIdentLink *p_callstack = nullptr;
|
||||||
|
|
||||||
int identflags = 0;
|
int identflags = 0;
|
||||||
int protect = 0;
|
|
||||||
|
|
||||||
CsState();
|
CsState();
|
||||||
virtual ~CsState();
|
virtual ~CsState();
|
||||||
|
|
||||||
bool is_alive() const {
|
|
||||||
return bool(p_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
CsStream const &get_out() const;
|
CsStream const &get_out() const;
|
||||||
CsStream &get_out();
|
CsStream &get_out();
|
||||||
void set_out(CsStream &s);
|
void set_out(CsStream &s);
|
||||||
|
@ -407,31 +432,32 @@ struct OSTD_EXPORT CsState {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void *alloc(void *ptr, ostd::Size olds, ostd::Size news);
|
void *alloc(void *ptr, ostd::Size olds, ostd::Size news);
|
||||||
|
|
||||||
|
template<typename T, typename ...A>
|
||||||
|
T *create(A &&...args) {
|
||||||
|
T *ret = static_cast<T *>(alloc(nullptr, 0, sizeof(T)));
|
||||||
|
new (ret) T(ostd::forward<A>(args)...);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T *create_array(ostd::Size len) {
|
||||||
|
T *ret = static_cast<T *>(alloc(nullptr, 0, len * sizeof(T)));
|
||||||
|
for (ostd::Size i = 0; i < len; ++i) {
|
||||||
|
new (&ret[i]) T();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void destroy(T *v) noexcept {
|
||||||
|
v->~T();
|
||||||
|
alloc(v, sizeof(T), 0);
|
||||||
|
}
|
||||||
|
|
||||||
void init_libs(int libs = CsLibAll);
|
void init_libs(int libs = CsLibAll);
|
||||||
|
|
||||||
CsPanicCb set_panic_func(CsPanicCb func);
|
|
||||||
CsPanicCb const &get_panic_func() const;
|
|
||||||
CsPanicCb &get_panic_func();
|
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
CsPanicCb set_panic_func(F &&f) {
|
|
||||||
return set_panic_func(CsPanicCb(
|
|
||||||
ostd::allocator_arg, CsAllocator<char>(*this), ostd::forward<F>(f)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
bool pcall(
|
|
||||||
F func, ostd::ConstCharRange *error = nullptr,
|
|
||||||
CsStackState *stack = nullptr
|
|
||||||
) {
|
|
||||||
return ipcall([](void *data) {
|
|
||||||
(*static_cast<F *>(data))();
|
|
||||||
}, error, stack, &func);
|
|
||||||
}
|
|
||||||
|
|
||||||
void error(ostd::ConstCharRange msg);
|
void error(ostd::ConstCharRange msg);
|
||||||
|
|
||||||
template<typename ...A>
|
template<typename ...A>
|
||||||
|
@ -584,14 +610,9 @@ struct OSTD_EXPORT CsState {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CsIdent *add_ident(CsIdent *id);
|
CsIdent *add_ident(CsIdent *id);
|
||||||
bool ipcall(
|
|
||||||
void (*f)(void *data), ostd::ConstCharRange *error,
|
|
||||||
CsStackState *stack, void *data
|
|
||||||
);
|
|
||||||
|
|
||||||
char p_errbuf[512];
|
char p_errbuf[512];
|
||||||
CsHookCb p_callhook;
|
CsHookCb p_callhook;
|
||||||
CsPanicCb p_panicfunc;
|
|
||||||
CsStream *p_out, *p_err;
|
CsStream *p_out, *p_err;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
66
src/cs_vm.hh
66
src/cs_vm.hh
|
@ -98,72 +98,6 @@ template<typename T>
|
||||||
constexpr ostd::Size CsTypeStorageSize =
|
constexpr ostd::Size CsTypeStorageSize =
|
||||||
(sizeof(T) - 1) / sizeof(ostd::Uint32) + 1;
|
(sizeof(T) - 1) / sizeof(ostd::Uint32) + 1;
|
||||||
|
|
||||||
struct CsErrorException {
|
|
||||||
ostd::ConstCharRange errmsg;
|
|
||||||
CsStackState stack;
|
|
||||||
CsErrorException() = delete;
|
|
||||||
CsErrorException(CsErrorException const &) = delete;
|
|
||||||
CsErrorException(CsErrorException &&v):
|
|
||||||
errmsg(v.errmsg), stack(ostd::move(v.stack))
|
|
||||||
{}
|
|
||||||
CsErrorException(ostd::ConstCharRange v, CsStackState &&st):
|
|
||||||
errmsg(v), stack(ostd::move(st))
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void *cs_alloc_throw(
|
|
||||||
CsState &cs, void *ptr, ostd::Size olds, ostd::Size news
|
|
||||||
) {
|
|
||||||
void *ret = cs.alloc(ptr, olds, news);
|
|
||||||
if (news && !ret) {
|
|
||||||
cs.error("could not allocate memory");
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename ...A>
|
|
||||||
inline T *cs_new(CsState &cs, A &&...args) {
|
|
||||||
void *ret = cs.alloc(nullptr, 0, sizeof(T));
|
|
||||||
if (!ret) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
new (ret) T(ostd::forward<A>(args)...);
|
|
||||||
return static_cast<T *>(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
inline T *cs_new_array(CsState &cs, ostd::Size len) {
|
|
||||||
T *ret = static_cast<T *>(cs.alloc(nullptr, 0, len * sizeof(T)));
|
|
||||||
if (ret) {
|
|
||||||
for (ostd::Size i = 0; i < len; ++i) {
|
|
||||||
new (&ret[i]) T();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename ...A>
|
|
||||||
inline T *cs_new_throw(CsState &cs, A &&...args) {
|
|
||||||
void *ret = cs_alloc_throw(cs, nullptr, 0, sizeof(T));
|
|
||||||
new (ret) T(ostd::forward<A>(args)...);
|
|
||||||
return static_cast<T *>(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
inline T *cs_new_array_throw(CsState &cs, ostd::Size len) {
|
|
||||||
T *ret = static_cast<T *>(cs_alloc_throw(cs, nullptr, 0, len * sizeof(T)));
|
|
||||||
for (ostd::Size i = 0; i < len; ++i) {
|
|
||||||
new (&ret[i]) T();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
inline void cs_delete(CsState &cs, T *v) noexcept {
|
|
||||||
v->~T();
|
|
||||||
cs.alloc(v, sizeof(T), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
CsStackState cs_save_stack(CsState &cs);
|
CsStackState cs_save_stack(CsState &cs);
|
||||||
|
|
||||||
template<typename ...A>
|
template<typename ...A>
|
||||||
|
|
|
@ -253,26 +253,9 @@ int CsCommand::get_num_args() const {
|
||||||
void cs_init_lib_base(CsState &cs);
|
void cs_init_lib_base(CsState &cs);
|
||||||
|
|
||||||
CsState::CsState():
|
CsState::CsState():
|
||||||
p_state(nullptr), p_callhook(), p_panicfunc(),
|
p_state(create<CsSharedState>()), p_callhook(),
|
||||||
p_out(&ostd::out), p_err(&ostd::err)
|
p_out(&ostd::out), p_err(&ostd::err)
|
||||||
{
|
{
|
||||||
CsSharedState *ps = static_cast<CsSharedState *>(
|
|
||||||
alloc(nullptr, 0, sizeof(CsSharedState))
|
|
||||||
);
|
|
||||||
if (!ps) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* TODO: protect with a Box */
|
|
||||||
new (ps) CsSharedState();
|
|
||||||
p_state = ps;
|
|
||||||
|
|
||||||
/* default panic func */
|
|
||||||
p_panicfunc = [](CsState &cs, ostd::ConstCharRange v, CsStackState) {
|
|
||||||
cs.get_err().writefln(
|
|
||||||
"PANIC: unprotected error in call to CubeScript (%s)", v
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < MaxArguments; ++i) {
|
for (int i = 0; i < MaxArguments; ++i) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
snprintf(buf, sizeof(buf), "arg%d", i + 1);
|
snprintf(buf, sizeof(buf), "arg%d", i + 1);
|
||||||
|
@ -363,10 +346,9 @@ CsState::~CsState() {
|
||||||
a->get_value().force_null();
|
a->get_value().force_null();
|
||||||
CsAliasInternal::clean_code(a);
|
CsAliasInternal::clean_code(a);
|
||||||
}
|
}
|
||||||
delete i;
|
destroy(i);
|
||||||
}
|
}
|
||||||
p_state->~CsSharedState();
|
destroy(p_state);
|
||||||
alloc(p_state, sizeof(CsSharedState), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CsStream const &CsState::get_out() const {
|
CsStream const &CsState::get_out() const {
|
||||||
|
@ -408,49 +390,10 @@ CsHookCb &CsState::get_call_hook() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void *CsState::alloc(void *ptr, ostd::Size, ostd::Size ns) {
|
void *CsState::alloc(void *ptr, ostd::Size, ostd::Size ns) {
|
||||||
delete[] static_cast<ostd::byte *>(ptr);
|
|
||||||
if (!ns) {
|
if (!ns) {
|
||||||
return nullptr;
|
delete static_cast<unsigned char *>(ptr);
|
||||||
}
|
}
|
||||||
return new ostd::byte[ns];
|
return new unsigned char[ns];
|
||||||
}
|
|
||||||
|
|
||||||
CsPanicCb CsState::set_panic_func(CsPanicCb func) {
|
|
||||||
auto hk = ostd::move(p_panicfunc);
|
|
||||||
p_panicfunc = ostd::move(func);
|
|
||||||
return hk;
|
|
||||||
}
|
|
||||||
|
|
||||||
CsPanicCb const &CsState::get_panic_func() const {
|
|
||||||
return p_panicfunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
CsPanicCb &CsState::get_panic_func() {
|
|
||||||
return p_panicfunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CsState::ipcall(
|
|
||||||
void (*f)(void *data), ostd::ConstCharRange *error,
|
|
||||||
CsStackState *stack, void *data
|
|
||||||
) {
|
|
||||||
++protect;
|
|
||||||
try {
|
|
||||||
f(data);
|
|
||||||
} catch (CsErrorException &v) {
|
|
||||||
--protect;
|
|
||||||
if (error) {
|
|
||||||
*error = v.errmsg;
|
|
||||||
}
|
|
||||||
if (stack) {
|
|
||||||
*stack = ostd::move(v.stack);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (...) {
|
|
||||||
--protect;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
--protect;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CsState::error(ostd::ConstCharRange msg) {
|
void CsState::error(ostd::ConstCharRange msg) {
|
||||||
|
@ -459,14 +402,7 @@ void CsState::error(ostd::ConstCharRange msg) {
|
||||||
}
|
}
|
||||||
memcpy(p_errbuf, msg.data(), msg.size());
|
memcpy(p_errbuf, msg.data(), msg.size());
|
||||||
auto err = ostd::ConstCharRange(p_errbuf, msg.size());
|
auto err = ostd::ConstCharRange(p_errbuf, msg.size());
|
||||||
if (protect) {
|
throw CsErrorException(err, cs_save_stack(*this));
|
||||||
throw CsErrorException(err, cs_save_stack(*this));
|
|
||||||
} else {
|
|
||||||
if (p_panicfunc) {
|
|
||||||
p_panicfunc(*this, err, cs_save_stack(*this));
|
|
||||||
}
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CsState::clear_override(CsIdent &id) {
|
void CsState::clear_override(CsIdent &id) {
|
||||||
|
@ -528,7 +464,7 @@ CsIdent *CsState::new_ident(ostd::ConstCharRange name, int flags) {
|
||||||
);
|
);
|
||||||
return p_state->identmap[DummyIdx];
|
return p_state->identmap[DummyIdx];
|
||||||
}
|
}
|
||||||
id = add_ident(new CsAlias(name, flags));
|
id = add_ident(create<CsAlias>(name, flags));
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -585,20 +521,24 @@ CsConstIdentRange CsState::get_idents() const {
|
||||||
CsIvar *CsState::new_ivar(
|
CsIvar *CsState::new_ivar(
|
||||||
ostd::ConstCharRange n, CsInt m, CsInt x, CsInt v, CsVarCb f, int flags
|
ostd::ConstCharRange n, CsInt m, CsInt x, CsInt v, CsVarCb f, int flags
|
||||||
) {
|
) {
|
||||||
return add_ident(new CsIvar(n, m, x, v, ostd::move(f), flags))->get_ivar();
|
return add_ident(
|
||||||
|
create<CsIvar>(n, m, x, v, ostd::move(f), flags)
|
||||||
|
)->get_ivar();
|
||||||
}
|
}
|
||||||
|
|
||||||
CsFvar *CsState::new_fvar(
|
CsFvar *CsState::new_fvar(
|
||||||
ostd::ConstCharRange n, CsFloat m, CsFloat x, CsFloat v, CsVarCb f, int flags
|
ostd::ConstCharRange n, CsFloat m, CsFloat x, CsFloat v, CsVarCb f, int flags
|
||||||
) {
|
) {
|
||||||
return add_ident(new CsFvar(n, m, x, v, ostd::move(f), flags))->get_fvar();
|
return add_ident(
|
||||||
|
create<CsFvar>(n, m, x, v, ostd::move(f), flags)
|
||||||
|
)->get_fvar();
|
||||||
}
|
}
|
||||||
|
|
||||||
CsSvar *CsState::new_svar(
|
CsSvar *CsState::new_svar(
|
||||||
ostd::ConstCharRange n, CsString v, CsVarCb f, int flags
|
ostd::ConstCharRange n, CsString v, CsVarCb f, int flags
|
||||||
) {
|
) {
|
||||||
return add_ident(
|
return add_ident(
|
||||||
new CsSvar(n, ostd::move(v), ostd::move(f), flags)
|
create<CsSvar>(n, ostd::move(v), ostd::move(f), flags)
|
||||||
)->get_svar();
|
)->get_svar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,7 +594,7 @@ void CsState::set_alias(ostd::ConstCharRange name, CsValue v) {
|
||||||
} else if (cs_check_num(name)) {
|
} else if (cs_check_num(name)) {
|
||||||
cs_debug_code(*this, "cannot alias number %s", name);
|
cs_debug_code(*this, "cannot alias number %s", name);
|
||||||
} else {
|
} else {
|
||||||
add_ident(new CsAlias(name, ostd::move(v), identflags));
|
add_ident(create<CsAlias>(name, ostd::move(v), identflags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1084,7 +1024,7 @@ CsCommand *CsState::new_command(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return static_cast<CsCommand *>(
|
return static_cast<CsCommand *>(
|
||||||
add_ident(new CsCommand(name, args, nargs, ostd::move(func)))
|
add_ident(create<CsCommand>(name, args, nargs, ostd::move(func)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,21 +1085,20 @@ void cs_init_lib_base(CsState &gcs) {
|
||||||
ret.set_int(0);
|
ret.set_int(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ostd::ConstCharRange errmsg;
|
|
||||||
CsValue result, tback;
|
CsValue result, tback;
|
||||||
CsStackState stack;
|
bool rc = true;
|
||||||
bool rc = cs.pcall([&cs, &args, &result]() {
|
try {
|
||||||
cs.run(args[0].get_code(), result);
|
cs.run(args[0].get_code(), result);
|
||||||
}, &errmsg, &stack);
|
} catch (CsErrorException const &e) {
|
||||||
ret.set_int(rc);
|
result.set_str(e.what());
|
||||||
if (!rc) {
|
if (e.get_stack().get()) {
|
||||||
result.set_str(errmsg);
|
|
||||||
if (stack.get()) {
|
|
||||||
auto app = ostd::appender<CsString>();
|
auto app = ostd::appender<CsString>();
|
||||||
cscript::util::print_stack(app, stack);
|
cscript::util::print_stack(app, e.get_stack());
|
||||||
tback.set_str(ostd::move(app.get()));
|
tback.set_str(ostd::move(app.get()));
|
||||||
}
|
}
|
||||||
|
rc = false;
|
||||||
}
|
}
|
||||||
|
ret.set_int(rc);
|
||||||
CsAliasInternal::set_alias(cret, cs, result);
|
CsAliasInternal::set_alias(cret, cs, result);
|
||||||
if (css->get_index() != DummyIdx) {
|
if (css->get_index() != DummyIdx) {
|
||||||
CsAliasInternal::set_alias(css, cs, tback);
|
CsAliasInternal::set_alias(css, cs, tback);
|
||||||
|
|
|
@ -194,9 +194,7 @@ static bool do_call(CsState &cs, ostd::ConstCharRange line, bool file = false) {
|
||||||
CsValue ret;
|
CsValue ret;
|
||||||
scs = &cs;
|
scs = &cs;
|
||||||
signal(SIGINT, do_sigint);
|
signal(SIGINT, do_sigint);
|
||||||
ostd::ConstCharRange err;
|
try {
|
||||||
cscript::CsStackState st;
|
|
||||||
auto tocall = [&]() {
|
|
||||||
if (file) {
|
if (file) {
|
||||||
if (!cs.run_file(line, ret)) {
|
if (!cs.run_file(line, ret)) {
|
||||||
ostd::err.writeln("cannot read file: ", line);
|
ostd::err.writeln("cannot read file: ", line);
|
||||||
|
@ -204,11 +202,10 @@ static bool do_call(CsState &cs, ostd::ConstCharRange line, bool file = false) {
|
||||||
} else {
|
} else {
|
||||||
cs.run(line, ret);
|
cs.run(line, ret);
|
||||||
}
|
}
|
||||||
};
|
} catch (cscript::CsErrorException const &e) {
|
||||||
if (!cs.pcall(ostd::move(tocall), &err, &st)) {
|
|
||||||
signal(SIGINT, SIG_DFL);
|
signal(SIGINT, SIG_DFL);
|
||||||
scs = nullptr;
|
scs = nullptr;
|
||||||
ostd::ConstCharRange terr = err;
|
ostd::ConstCharRange terr = e.what();
|
||||||
auto col = ostd::find(terr, ':');
|
auto col = ostd::find(terr, ':');
|
||||||
if (!col.empty()) {
|
if (!col.empty()) {
|
||||||
terr = col + 2;
|
terr = col + 2;
|
||||||
|
@ -216,9 +213,9 @@ static bool do_call(CsState &cs, ostd::ConstCharRange line, bool file = false) {
|
||||||
if (!file && ((terr == "missing \"]\"") || (terr == "missing \")\""))) {
|
if (!file && ((terr == "missing \"]\"") || (terr == "missing \")\""))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
cs.get_out().writeln(col.empty() ? "stdin: " : "stdin:", err);
|
cs.get_out().writeln(col.empty() ? "stdin: " : "stdin:", e.what());
|
||||||
if (st.get()) {
|
if (e.get_stack().get()) {
|
||||||
cscript::util::print_stack(cs.get_out().iter(), st);
|
cscript::util::print_stack(cs.get_out().iter(), e.get_stack());
|
||||||
cs.get_out().write('\n');
|
cs.get_out().write('\n');
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -273,9 +270,6 @@ static void do_tty(CsState &cs) {
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
CsState gcs;
|
CsState gcs;
|
||||||
if (!gcs.is_alive()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
gcs.init_libs();
|
gcs.init_libs();
|
||||||
|
|
||||||
gcs.new_command("exec", "sb", [](CsState &cs, auto args, auto &res) {
|
gcs.new_command("exec", "sb", [](CsState &cs, auto args, auto &res) {
|
||||||
|
|
Loading…
Reference in New Issue