mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-05-01 03:19:28 +08:00
Merge b3046bf9417339599138d5148d059b9db7c63126 into 56506bc111ec6aa8dd3aed9dced23ef6a56b6c49
This commit is contained in:
commit
c29df04b6c
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user