Address review: add rethrow_typed and boxed_value accessors to eval_error

eval_error now stores the original Boxed_Value from script-thrown exceptions,
accessible via has_boxed_value() and boxed_value(). The new rethrow_typed<Types...>(engine)
method provides auto-unboxing without requiring exception_specification to be passed to eval().
This delivers the API simplification envisioned in issue #63: users can catch eval_error,
inspect the call stack, and rethrow as typed exceptions in a single catch block.

Requested by @lefticus in PR #682 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-14 15:42:29 -06:00
parent 2c80c2a55a
commit 97e8b0a251
3 changed files with 91 additions and 1 deletions

View File

@ -340,8 +340,24 @@ namespace chaiscript {
, reason(t_why) {
}
eval_error(const std::string &t_why, Boxed_Value t_bv) noexcept
: std::runtime_error("Error: \"" + t_why + "\" ")
, reason(t_why)
, m_boxed_value(std::move(t_bv)) {
}
eval_error(const eval_error &) = default;
bool has_boxed_value() const noexcept { return !m_boxed_value.is_undef(); }
const Boxed_Value &boxed_value() const noexcept { return m_boxed_value; }
template<typename... Types, typename Engine>
void rethrow_typed(const Engine &t_engine) const {
if (has_boxed_value()) {
(try_rethrow<Types>(t_engine), ...);
}
}
std::string pretty_print() const {
std::ostringstream ss;
@ -365,6 +381,16 @@ namespace chaiscript {
~eval_error() noexcept override = default;
private:
Boxed_Value m_boxed_value;
template<typename T, typename Engine>
void try_rethrow(const Engine &t_engine) const {
try {
throw t_engine.template boxed_cast<T>(m_boxed_value);
} catch (const chaiscript::exception::bad_boxed_cast &) {
}
}
template<typename T>
static AST_Node_Type id(const T &t) noexcept {
return t.identifier;

View File

@ -695,7 +695,7 @@ namespace chaiscript {
if (t_handler) {
t_handler->handle(bv, m_engine);
}
throw exception::eval_error("Exception thrown during evaluation");
throw exception::eval_error("Exception thrown during evaluation", bv);
}
}

View File

@ -256,6 +256,70 @@ TEST_CASE("eval_error includes nested exception for script-thrown exceptions") {
}
}
TEST_CASE("eval_error stores boxed_value for script-thrown exceptions") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(42);");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
CHECK(chai.boxed_cast<int>(ee.boxed_value()) == 42);
}
}
TEST_CASE("eval_error rethrow_typed auto-unboxes runtime_error") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(runtime_error(\"typed error\"));");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
try {
ee.rethrow_typed<int, double, const std::runtime_error &>(chai);
REQUIRE(false);
} catch (const std::runtime_error &e) {
CHECK(e.what() == std::string("typed error"));
}
}
}
TEST_CASE("eval_error rethrow_typed auto-unboxes int") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(42);");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
try {
ee.rethrow_typed<int, double>(chai);
REQUIRE(false);
} catch (const int e) {
CHECK(e == 42);
}
}
}
TEST_CASE("eval_error rethrow_typed with no match does not throw") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(\"a string\");");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
ee.rethrow_typed<int, double>(chai);
CHECK(true);
}
}
TEST_CASE("eval_error without boxed_value has_boxed_value returns false") {
const chaiscript::exception::eval_error ee("plain error");
CHECK_FALSE(ee.has_boxed_value());
}
TEST_CASE("Deduction of pointer return types") {
int val = 5;
int *val_ptr = &val;