Merge b3046bf9417339599138d5148d059b9db7c63126 into 56506bc111ec6aa8dd3aed9dced23ef6a56b6c49

This commit is contained in:
leftibot 2026-04-29 02:19:36 +00:00 committed by GitHub
commit c29df04b6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 239 additions and 18 deletions

View File

@ -11,6 +11,7 @@
#define CHAISCRIPT_COMMON_HPP_
#include <algorithm>
#include <exception>
#include <memory>
#include <sstream>
#include <stdexcept>
@ -299,7 +300,7 @@ namespace chaiscript {
};
/// Errors generated during parsing or evaluation
struct eval_error : std::runtime_error {
struct eval_error : std::runtime_error, std::nested_exception {
std::string reason;
File_Position start_position;
std::string filename;
@ -342,8 +343,31 @@ namespace chaiscript {
, reason(t_why) {
}
eval_error(const std::string &t_why, Boxed_Value t_bv) noexcept
: std::runtime_error(format_why(t_why) + " " + format_filename("__EVAL__"))
, reason(t_why)
, m_boxed_value(std::move(t_bv)) {
}
eval_error(const std::string &t_why, const std::string &t_fname, Boxed_Value t_bv) noexcept
: std::runtime_error(format_why(t_why) + " " + format_filename(t_fname))
, reason(t_why)
, filename(t_fname)
, 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;
@ -367,6 +391,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

@ -101,6 +101,9 @@ namespace chaiscript {
} catch (const exception::file_not_found_error &) {
// failed to load, try the next path
} catch (const exception::eval_error &t_ee) {
if (t_ee.has_boxed_value()) {
throw Boxed_Value(t_ee.boxed_value());
}
throw Boxed_Value(t_ee);
}
}
@ -114,6 +117,9 @@ namespace chaiscript {
try {
return do_eval(t_e, "__EVAL__", true);
} catch (const exception::eval_error &t_ee) {
if (t_ee.has_boxed_value()) {
throw Boxed_Value(t_ee.boxed_value());
}
throw Boxed_Value(t_ee);
}
}
@ -718,11 +724,21 @@ namespace chaiscript {
eval(const std::string &t_input, const Exception_Handler &t_handler = Exception_Handler(), const std::string &t_filename = "__EVAL__") {
try {
return do_eval(t_input, t_filename);
} catch (exception::eval_error &ee) {
if (ee.has_boxed_value()) {
if (ee.filename.empty()) {
ee.filename = t_filename;
}
if (t_handler) {
t_handler->handle(ee.boxed_value(), m_engine);
}
}
throw;
} catch (Boxed_Value &bv) {
if (t_handler) {
t_handler->handle(bv, m_engine);
}
throw;
throw exception::eval_error("Exception thrown during evaluation", t_filename, bv);
}
}

View File

@ -306,6 +306,10 @@ namespace chaiscript {
} catch (exception::eval_error &ee) {
ee.call_stack.push_back(*this);
throw;
} catch (const Boxed_Value &bv) {
exception::eval_error ee("Exception thrown during evaluation", bv);
ee.call_stack.push_back(*this);
throw ee;
}
}
@ -1787,7 +1791,11 @@ namespace chaiscript {
try {
retval = this->children[0]->eval(t_ss);
} catch (const exception::eval_error &e) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
if (e.has_boxed_value()) {
retval = handle_exception(t_ss, e.boxed_value());
} else {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
}
} catch (const std::runtime_error &e) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
} catch (const std::out_of_range &e) {

View File

@ -165,9 +165,15 @@ TEST_CASE("Generic exception handling with C++") {
try {
chai.eval("throw(runtime_error(\"error\"));");
REQUIRE(false);
} catch (const chaiscript::Boxed_Value &bv) {
const std::exception &e = chai.boxed_cast<const std::exception &>(bv);
CHECK(e.what() == std::string("error"));
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.nested_ptr() != nullptr);
try {
ee.rethrow_nested();
REQUIRE(false);
} catch (const chaiscript::Boxed_Value &bv) {
const std::exception &e = chai.boxed_cast<const std::exception &>(bv);
CHECK(e.what() == std::string("error"));
}
}
}
@ -193,6 +199,163 @@ TEST_CASE("Throw int or double") {
}
}
TEST_CASE("eval_error derives from std::nested_exception") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
static_assert(std::is_base_of_v<std::nested_exception, chaiscript::exception::eval_error>,
"eval_error must derive from std::nested_exception");
try {
chai.eval("throw(runtime_error(\"inner error\"));");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
CHECK(ee.nested_ptr() != nullptr);
try {
ee.rethrow_nested();
REQUIRE(false);
} catch (const chaiscript::Boxed_Value &bv) {
const std::exception &nested = chai.boxed_cast<const std::exception &>(bv);
CHECK(nested.what() == std::string("inner error"));
}
}
}
TEST_CASE("eval_error wraps non-eval exceptions with nested exception") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(42);");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
CHECK(ee.nested_ptr() != nullptr);
try {
ee.rethrow_nested();
REQUIRE(false);
} catch (const chaiscript::Boxed_Value &) {
CHECK(true);
}
}
}
TEST_CASE("eval_error includes nested exception for script-thrown exceptions") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("def foo() { throw(runtime_error(\"from foo\")); } \n foo();");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
CHECK(ee.nested_ptr() != nullptr);
try {
ee.rethrow_nested();
REQUIRE(false);
} catch (const chaiscript::Boxed_Value &bv) {
const std::exception &nested = chai.boxed_cast<const std::exception &>(bv);
CHECK(nested.what() == std::string("from foo"));
}
}
}
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("eval_error wrapping script throw includes call stack") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("def foo() { throw(42); } \n foo();");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
CHECK_FALSE(ee.call_stack.empty());
const auto pretty = ee.pretty_print();
CHECK(pretty.find("foo") != std::string::npos);
}
}
TEST_CASE("eval_error wrapping script throw includes filename") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(99);", chaiscript::Exception_Handler(), "test_script.chai");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
CHECK(ee.filename == "test_script.chai");
}
}
TEST_CASE("exception_specification still auto-unboxes for backward compatibility") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(1)", chaiscript::exception_specification<int>());
REQUIRE(false);
} catch (const int e) {
CHECK(e == 1);
}
}
TEST_CASE("Deduction of pointer return types") {
int val = 5;
int *val_ptr = &val;
@ -258,10 +421,8 @@ TEST_CASE("Throw unhandled type") {
REQUIRE(false);
} catch (float) {
REQUIRE(false);
} catch (const std::exception &) {
REQUIRE(false);
} catch (const chaiscript::Boxed_Value &) {
REQUIRE(true);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.nested_ptr() != nullptr);
}
}
@ -2070,8 +2231,9 @@ TEST_CASE("ChaiScript throw(int) propagates as Boxed_Value to C++") {
try {
chai.eval("throw(42)");
REQUIRE(false);
} catch (chaiscript::Boxed_Value &bv) {
CHECK(chaiscript::boxed_cast<int>(bv) == 42);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
CHECK(chai.boxed_cast<int>(ee.boxed_value()) == 42);
}
}
@ -2081,8 +2243,9 @@ TEST_CASE("ChaiScript throw(string) propagates as Boxed_Value to C++") {
try {
chai.eval(R"(throw("error msg"))");
REQUIRE(false);
} catch (chaiscript::Boxed_Value &bv) {
CHECK(chaiscript::boxed_cast<std::string>(bv) == "error msg");
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
CHECK(chai.boxed_cast<std::string>(ee.boxed_value()) == "error msg");
}
}
@ -2094,10 +2257,10 @@ TEST_CASE("Typed catch with no match propagates exception") {
throw(42)
}
catch(string e) {
// wrong type, should not match
// wrong type, should not match — exception propagates
}
)"),
chaiscript::Boxed_Value);
chaiscript::exception::eval_error);
}
TEST_CASE("Typed catch with no match still runs finally block") {
@ -2109,13 +2272,13 @@ TEST_CASE("Typed catch with no match still runs finally block") {
throw(42)
}
catch(string e) {
// wrong type
// wrong type, should not match — exception propagates
}
finally {
finally_ran = true
}
)"),
chaiscript::Boxed_Value);
chaiscript::exception::eval_error);
CHECK(chai.eval<bool>("finally_ran") == true);
}