From 5cebd2cd2213f0a45dd212bd608affa4a6c4b306 Mon Sep 17 00:00:00 2001 From: leftibot Date: Wed, 15 Apr 2026 15:14:32 -0600 Subject: [PATCH] Address review: add call stack and filename to wrapped script exceptions - Catch Boxed_Value in AST_Node_Impl::eval() and wrap in eval_error so call stack accumulates as the exception propagates through the AST chain - Add eval_error constructor accepting filename + Boxed_Value - Populate filename on wrapped eval_error in chaiscript_engine::eval() - Extract original boxed value in Try_AST_Node for script try/catch semantics - Unwrap boxed value in internal_eval/internal_eval_file to preserve script-level exception identity across nested eval() calls - Add tests for call stack presence, filename population, and exception_specification backward compatibility Requested by @lefticus in PR #682 review. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../chaiscript/language/chaiscript_common.hpp | 9 ++++- .../chaiscript/language/chaiscript_engine.hpp | 18 ++++++++- .../chaiscript/language/chaiscript_eval.hpp | 10 ++++- unittests/compiled_tests.cpp | 37 +++++++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index 387b12d3..3da0ef8d 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -341,11 +341,18 @@ namespace chaiscript { } eval_error(const std::string &t_why, Boxed_Value t_bv) noexcept - : std::runtime_error("Error: \"" + t_why + "\" ") + : 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(); } diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 6d82f206..3eb2a569 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -100,6 +100,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); } } @@ -113,6 +116,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); } } @@ -691,11 +697,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 exception::eval_error("Exception thrown during evaluation", bv); + throw exception::eval_error("Exception thrown during evaluation", t_filename, bv); } } diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index 4ff0d6d5..7fd5dca0 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -142,6 +142,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; } } @@ -1358,7 +1362,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) { diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index 4851b4b8..c0a78c46 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -320,6 +320,43 @@ TEST_CASE("eval_error without boxed_value has_boxed_value returns false") { 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()); + REQUIRE(false); + } catch (const int e) { + CHECK(e == 1); + } +} + TEST_CASE("Deduction of pointer return types") { int val = 5; int *val_ptr = &val;