diff --git a/include/cubescript/cubescript/callable.hh b/include/cubescript/cubescript/callable.hh index 24a8dd7..0cb5f62 100644 --- a/include/cubescript/cubescript/callable.hh +++ b/include/cubescript/cubescript/callable.hh @@ -10,6 +10,7 @@ #ifndef LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH #define LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH +#include #include #include #include diff --git a/include/cubescript/cubescript/error.hh b/include/cubescript/cubescript/error.hh index da175ec..58fe24e 100644 --- a/include/cubescript/cubescript/error.hh +++ b/include/cubescript/cubescript/error.hh @@ -13,8 +13,6 @@ #include #include #include -#include -#include namespace cubescript { @@ -108,6 +106,9 @@ struct LIBCUBESCRIPT_EXPORT error { p_stack{std::move(v.p_stack)} {} + /** @brief Construct an error using a string. */ + error(state &cs, std::string_view msg); + /** @brief Get a view of the error message. */ std::string_view what() const { return std::string_view{p_errbeg, std::size_t(p_errend - p_errbeg)}; @@ -122,45 +123,10 @@ struct LIBCUBESCRIPT_EXPORT error { stack_state const &stack() const { return p_stack; } - - /** @brief Construct an error using an unformatted string. */ - error(state &cs, std::string_view msg): - p_errbeg{}, p_errend{}, p_stack{cs} - { - char *sp; - char *buf = request_buf(cs, msg.size(), sp); - std::memcpy(buf, msg.data(), msg.size()); - buf[msg.size()] = '\0'; - p_errbeg = sp; - p_errend = buf + msg.size(); - p_stack = save_stack(cs); - } - - /** @brief Construct an error using a `printf`-style format string. */ - template - error(state &cs, std::string_view msg, A const &...args): - p_errbeg{}, p_errend{}, p_stack{cs} - { - std::size_t sz = msg.size() + 64; - char *buf, *sp; - for (;;) { - buf = request_buf(cs, sz, sp); - int written = std::snprintf(buf, sz, msg.data(), args...); - if (written <= 0) { - throw error{cs, "malformed format string"}; - } else if (std::size_t(written) <= sz) { - break; - } - sz = std::size_t(written); - } - p_errbeg = sp; - p_errend = buf + sz; - p_stack = save_stack(cs); - } - private: - stack_state save_stack(state &cs); - char *request_buf(state &cs, std::size_t bufs, char *&sp); + friend struct error_p; + + error(state &cs, char const *errbeg, char const *errend); char const *p_errbeg, *p_errend; stack_state p_stack; diff --git a/src/cs_error.cc b/src/cs_error.cc index 2dd0a49..c0a9252 100644 --- a/src/cs_error.cc +++ b/src/cs_error.cc @@ -4,6 +4,7 @@ #include #include "cs_thread.hh" +#include "cs_error.hh" namespace cubescript { @@ -44,46 +45,7 @@ LIBCUBESCRIPT_EXPORT bool stack_state::gap() const { return p_gap; } -LIBCUBESCRIPT_EXPORT char *error::request_buf( - state &cs, std::size_t bufs, char *&sp -) { - auto &ts = state_p{cs}.ts(); - charbuf &cb = ts.errbuf; - cb.clear(); - std::size_t sz = 0; - if (ts.current_line) { - /* we can attach line number */ - sz = ts.source.size() + 32; - for (;;) { - /* we are using so the buffer tracks the elements and therefore - * does not wipe them when we attempt to reserve more capacity - */ - cb.resize(sz); - int nsz; - if (!ts.source.empty()) { - nsz = std::snprintf( - cb.data(), sz, "%.*s:%zu: ", - int(ts.source.size()), ts.source.data(), - *ts.current_line - ); - } else { - nsz = std::snprintf(cb.data(), sz, "%zu: ", *ts.current_line); - } - if (nsz <= 0) { - abort(); /* should be unreachable */ - } else if (std::size_t(nsz) < sz) { - sz = std::size_t(nsz); - break; - } - sz = std::size_t(nsz + 1); - } - } - cb.resize(sz + bufs + 1); - sp = cb.data(); - return &cb[sz]; -} - -LIBCUBESCRIPT_EXPORT stack_state error::save_stack(state &cs) { +static stack_state save_stack(state &cs) { auto &ts = state_p{cs}.ts(); builtin_var *dalias = ts.istate->ivar_dbgalias; auto dval = std::clamp( @@ -124,4 +86,22 @@ LIBCUBESCRIPT_EXPORT stack_state error::save_stack(state &cs) { return stack_state{cs, ret, total > dval}; } +LIBCUBESCRIPT_EXPORT error::error(state &cs, std::string_view msg): + p_errbeg{}, p_errend{}, p_stack{cs} +{ + char *sp; + char *buf = state_p{cs}.ts().request_errbuf(msg.size(), sp); + std::memcpy(buf, msg.data(), msg.size()); + buf[msg.size()] = '\0'; + p_errbeg = sp; + p_errend = buf + msg.size(); + p_stack = save_stack(cs); +} + +LIBCUBESCRIPT_EXPORT error::error( + state &cs, char const *errbeg, char const *errend +): p_errbeg{errbeg}, p_errend{errend}, p_stack{cs} { + p_stack = save_stack(cs); +} + } /* namespace cubescript */ diff --git a/src/cs_ident.cc b/src/cs_ident.cc index cb99445..8771870 100644 --- a/src/cs_ident.cc +++ b/src/cs_ident.cc @@ -3,6 +3,7 @@ #include "cs_bcode.hh" #include "cs_thread.hh" #include "cs_vm.hh" +#include "cs_error.hh" namespace cubescript { @@ -229,10 +230,10 @@ LIBCUBESCRIPT_EXPORT void builtin_var::save(state &cs) { auto &ts = state_p{cs}.ts(); if ((ts.ident_flags & IDENT_FLAG_OVERRIDDEN) || is_overridable()) { if (p_impl->p_flags & IDENT_FLAG_PERSIST) { - throw error{ + throw error_p::make( cs, "cannot override persistent variable '%s'", name().data() - }; + ); } if (!(p_impl->p_flags & IDENT_FLAG_OVERRIDDEN)) { auto *imp = static_cast(p_impl); @@ -293,9 +294,9 @@ LIBCUBESCRIPT_EXPORT void builtin_var::set_value( state &cs, any_value val, bool do_write, bool trigger ) { if (is_read_only()) { - throw error{ + throw error_p::make( cs, "variable '%s' is read only", name().data() - }; + ); } if (!do_write) { return; @@ -375,7 +376,7 @@ LIBCUBESCRIPT_EXPORT any_value command::call( LIBCUBESCRIPT_EXPORT alias_local::alias_local(state &cs, ident &a) { if (a.type() != ident_type::ALIAS) { - throw error{cs, "ident '%s' is not an alias", a.name().data()}; + throw error_p::make(cs, "ident '%s' is not an alias", a.name().data()); } auto &ts = state_p{cs}.ts(); p_alias = static_cast(&a); diff --git a/src/cs_parser.cc b/src/cs_parser.cc index 31fc5d9..9e370cb 100644 --- a/src/cs_parser.cc +++ b/src/cs_parser.cc @@ -6,6 +6,7 @@ #include #include "cs_parser.hh" +#include "cs_error.hh" namespace cubescript { @@ -56,9 +57,9 @@ LIBCUBESCRIPT_EXPORT char const *parse_string( end: nlines = nl; if ((beg == end) || (*beg != '\"')) { - throw error{ + throw error_p::make( cs, "unfinished string '%.*s'", int(beg - orig), orig - }; + ); } return ++beg; } @@ -1116,7 +1117,9 @@ static bool finish_statement(parser_state &ps, bool more, int term) { /* EOS */ case '\0': if (ps.current() != term) { - throw error{*ps.ts.pstate, "missing \"%c\"", char(term)}; + throw error_p::make( + *ps.ts.pstate, "missing \"%c\"", char(term) + ); } return false; /* terminating parens/brackets */ @@ -1127,7 +1130,9 @@ static bool finish_statement(parser_state &ps, bool more, int term) { ps.next_char(); return false; } - throw error{*ps.ts.pstate, "unexpected \"%c\"", ps.current()}; + throw error_p::make( + *ps.ts.pstate, "unexpected \"%c\"", ps.current() + ); /* potential comment */ case '/': ps.next_char(); diff --git a/src/cs_state.cc b/src/cs_state.cc index 7296297..7077d88 100644 --- a/src/cs_state.cc +++ b/src/cs_state.cc @@ -8,6 +8,7 @@ #include "cs_strman.hh" #include "cs_vm.hh" // break/continue, call_with_args #include "cs_parser.hh" +#include "cs_error.hh" namespace cubescript { @@ -58,9 +59,9 @@ ident &internal_state::new_ident(state &cs, std::string_view name, int flags) { ident *id = get_ident(name); if (!id) { if (!is_valid_name(name)) { - throw error{ + throw error_p::make( cs, "'%s' is not a valid identifier name", name.data() - }; + ); } auto *inst = create( cs, string_ref{cs, name}, flags @@ -392,14 +393,14 @@ static void var_name_check( state &cs, ident *id, std::string_view n ) { if (id) { - throw error{ + throw error_p::make( cs, "redefinition of ident '%.*s'", int(n.size()), n.data() - }; + ); } else if (!is_valid_name(n)) { - throw error{ + throw error_p::make( cs, "'%.*s' is not a valid variable name", int(n.size()), n.data() - }; + ); } } @@ -472,13 +473,15 @@ LIBCUBESCRIPT_EXPORT void state::assign_value( id->get().call(span_type{&v, 1}, *this); break; default: - throw error{ + throw error_p::make( *this, "cannot redefine builtin %s with an alias", id->get().name().data() - }; + ); } } else if (!is_valid_name(name)) { - throw error{*this, "cannot alias invalid name '%s'", name.data()}; + throw error_p::make( + *this, "cannot alias invalid name '%s'", name.data() + ); } else { auto *a = p_tstate->istate->create( *this, string_ref{*this, name}, std::move(v), @@ -531,17 +534,21 @@ LIBCUBESCRIPT_EXPORT any_value state::lookup_value(std::string_view name) { return any_value{}; } } - throw error{*this, "unknown alias lookup: %s", name.data()}; + throw error_p::make(*this, "unknown alias lookup: %s", name.data()); } LIBCUBESCRIPT_EXPORT void state::reset_value(std::string_view name) { auto id = get_ident(name); if (!id) { - throw error{*this, "variable '%s' does not exist", name.data()}; + throw error_p::make( + *this, "variable '%s' does not exist", name.data() + ); } if (id->get().type() == ident_type::VAR) { if (static_cast(id->get()).is_read_only()) { - throw error{*this, "variable '%s' is read only", name.data()}; + throw error_p::make( + *this, "variable '%s' is read only", name.data() + ); } } clear_override(id->get()); @@ -619,9 +626,9 @@ LIBCUBESCRIPT_EXPORT command &state::new_command( fmt += 2; break; default: - throw error{ + throw error_p::make( *this, "invalid argument type: %c", *fmt - }; + ); } } auto &is = *p_tstate->istate; @@ -653,18 +660,18 @@ LIBCUBESCRIPT_EXPORT command &state::new_command( } /* we haven't found one matching the list, so error */ is.destroy(cmd); - throw error{ + throw error_p::make( *this, "forbidden builtin command: %.*s", int(name.size()), name.data() - }; + ); } valid: if (is.get_ident(name)) { is.destroy(cmd); - throw error{ + throw error_p::make( *this, "redefinition of ident '%.*s'", int(name.size()), name.data() - }; + ); } do_add: is.add_ident(cmd, cmd); diff --git a/src/cs_thread.cc b/src/cs_thread.cc index 1eefca5..533be32 100644 --- a/src/cs_thread.cc +++ b/src/cs_thread.cc @@ -1,5 +1,7 @@ #include "cs_thread.hh" +#include + namespace cubescript { thread_state::thread_state(internal_state *cs): @@ -27,4 +29,41 @@ alias_stack &thread_state::get_astack(alias const *a) { return it.first->second; } +char *thread_state::request_errbuf(std::size_t bufs, char *&sp) { + errbuf.clear(); + std::size_t sz = 0; + if (current_line) { + /* we can attach line number */ + sz = source.size() + 32; + for (;;) { + /* we are using so the buffer tracks the elements and therefore + * does not wipe them when we attempt to reserve more capacity + */ + errbuf.resize(sz); + int nsz; + if (!source.empty()) { + nsz = std::snprintf( + errbuf.data(), sz, "%.*s:%zu: ", + int(source.size()), source.data(), + *current_line + ); + } else { + nsz = std::snprintf( + errbuf.data(), sz, "%zu: ", *current_line + ); + } + if (nsz <= 0) { + abort(); /* should be unreachable */ + } else if (std::size_t(nsz) < sz) { + sz = std::size_t(nsz); + break; + } + sz = std::size_t(nsz + 1); + } + } + errbuf.resize(sz + bufs + 1); + sp = errbuf.data(); + return &errbuf[sz]; +} + } /* namespace cubescript */ diff --git a/src/cs_thread.hh b/src/cs_thread.hh index e64605a..6899cdc 100644 --- a/src/cs_thread.hh +++ b/src/cs_thread.hh @@ -53,6 +53,8 @@ struct thread_state { hook_func const &get_hook() const { return call_hook; } alias_stack &get_astack(alias const *a); + + char *request_errbuf(std::size_t bufs, char *&sp); }; } /* namespace cubescript */ diff --git a/src/cs_vm.cc b/src/cs_vm.cc index 1051389..f25c7c8 100644 --- a/src/cs_vm.cc +++ b/src/cs_vm.cc @@ -2,6 +2,7 @@ #include "cs_vm.hh" #include "cs_std.hh" #include "cs_parser.hh" +#include "cs_error.hh" #include #include @@ -149,9 +150,9 @@ bool exec_alias( return false; } } else if (aast.flags & IDENT_FLAG_UNKNOWN) { - throw error { + throw error_p::make( *ts.pstate, "unknown command: %s", a->name().data() - }; + ); } /* excess arguments get ignored (make error maybe?) */ callargs = std::min(callargs, MAX_ARGUMENTS); @@ -237,9 +238,9 @@ static inline alias *get_lookup_id( } else { ast = &ts.get_astack(static_cast(id)); if (ast->flags & IDENT_FLAG_UNKNOWN) { - throw error{ + throw error_p::make( *ts.pstate, "unknown alias lookup: %s", id->name().data() - }; + ); } } return static_cast(id); @@ -799,9 +800,9 @@ noid: result.force_none(); force_arg(cs, result, op & BC_INST_RET_MASK); std::string_view ids{idn}; - throw error{ + throw error_p::make( cs, "unknown command: %s", ids.data() - }; + ); } result.force_none(); switch (ident_p{id->get()}.impl().p_type) { diff --git a/src/lib_base.cc b/src/lib_base.cc index 8d61949..4d3545c 100644 --- a/src/lib_base.cc +++ b/src/lib_base.cc @@ -5,6 +5,7 @@ #include "cs_std.hh" #include "cs_ident.hh" #include "cs_thread.hh" +#include "cs_error.hh" namespace cubescript { @@ -72,10 +73,10 @@ LIBCUBESCRIPT_EXPORT void std_init_base(state &gcs) { auto &cret = args[1].get_ident(cs); auto &css = args[2].get_ident(cs); if (cret.type() != ident_type::ALIAS) { - throw error{cs, "'%s' is not an alias", cret.name().data()}; + throw error_p::make(cs, "'%s' is not an alias", cret.name().data()); } if (css.type() != ident_type::ALIAS) { - throw error{cs, "'%s' is not an alias", css.name().data()}; + throw error_p::make(cs, "'%s' is not an alias", css.name().data()); } any_value result{}, tback{}; bool rc = true; @@ -98,6 +99,25 @@ LIBCUBESCRIPT_EXPORT void std_init_base(state &gcs) { ts.get_astack(ssa).set_alias(ssa, ts, tback); }); + new_cmd_quiet(gcs, "assert", "ss#", [](auto &s, auto args, auto &ret) { + auto val = args[0]; + val.force_code(s); + if (!val.get_code().call(s).get_bool()) { + if (args[2].get_integer() > 1) { + throw error_p::make( + s, "assertion failed: [%s] (%s)", + args[0].get_string(s).data(), args[1].get_string(s).data() + ); + } else { + throw error_p::make( + s, "assertion failed: [%s]", + args[0].get_string(s).data() + ); + } + } + ret = std::move(args[0]); + }); + new_cmd_quiet(gcs, "?", "aaa", [](auto &, auto args, auto &res) { if (args[0].get_bool()) { res = std::move(args[1]); @@ -343,7 +363,7 @@ end: new_cmd_quiet(gcs, "getalias", "s", [](auto &cs, auto args, auto &res) { auto &id = cs.new_ident(args[0].get_string(cs)); if (id.type() != ident_type::ALIAS) { - throw error{cs, "'%s' is not an alias", id.name().data()}; + throw error_p::make(cs, "'%s' is not an alias", id.name().data()); } if (ident_p{id}.impl().p_flags & IDENT_FLAG_UNKNOWN) { return; diff --git a/tests/runner.cc b/tests/runner.cc index b329592..02eb605 100644 --- a/tests/runner.cc +++ b/tests/runner.cc @@ -58,26 +58,6 @@ int main(int argc, char **argv) { throw skip_test{}; }); - /* takes a string so we can print it */ - gcs.new_command("assert", "ss#", [](auto &s, auto args, auto &ret) { - auto val = args[0]; - val.force_code(s); - if (!val.get_code().call(s).get_bool()) { - if (args[2].get_integer() > 1) { - throw cs::error{ - s, "assertion failed: [%s] (%s)", - args[0].get_string(s).data(), args[1].get_string(s).data() - }; - } else { - throw cs::error{ - s, "assertion failed: [%s]", - args[0].get_string(s).data() - }; - } - } - ret = std::move(args[0]); - }); - try { do_exec_file(gcs, argv[1]); } catch (skip_test) { diff --git a/tools/repl.cc b/tools/repl.cc index 5d0ad10..3331bfb 100644 --- a/tools/repl.cc +++ b/tools/repl.cc @@ -360,9 +360,11 @@ int main(int argc, char **argv) { cs::any_value val{}; bool ret = do_exec_file(css, file, val); if (!ret) { - throw cs::error( - css, "could not execute file \"%s\"", file.data() + char buf[4096]; + std::snprintf( + buf, sizeof(buf), "could not execute file \"%s\"", file.data() ); + throw cs::error(css, buf); } });