Merge remote-tracking branch 'origin/develop' into fix/issue-677-add-strong-typedefs

This commit is contained in:
leftibot 2026-04-14 12:30:42 -06:00
commit b02380a1b3
4 changed files with 1175 additions and 80 deletions

View File

@ -15,168 +15,168 @@
#include "register_function.hpp"
namespace chaiscript::bootstrap::operators {
template<typename T>
void assign(Module &m) {
template<typename T, typename ModuleType>
void assign(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs = rhs; }), "=");
}
template<typename T>
void assign_bitwise_and(Module &m) {
template<typename T, typename ModuleType>
void assign_bitwise_and(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs &= rhs; }), "&=");
}
template<typename T>
void assign_xor(Module &m) {
template<typename T, typename ModuleType>
void assign_xor(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs ^= rhs; }), "^=");
}
template<typename T>
void assign_bitwise_or(Module &m) {
template<typename T, typename ModuleType>
void assign_bitwise_or(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs |= rhs; }), "|=");
}
template<typename T>
void assign_difference(Module &m) {
template<typename T, typename ModuleType>
void assign_difference(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs -= rhs; }), "-=");
}
template<typename T>
void assign_left_shift(Module &m) {
template<typename T, typename ModuleType>
void assign_left_shift(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "<<=");
}
template<typename T>
void assign_product(Module &m) {
template<typename T, typename ModuleType>
void assign_product(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs <<= rhs; }), "*=");
}
template<typename T>
void assign_quotient(Module &m) {
template<typename T, typename ModuleType>
void assign_quotient(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs /= rhs; }), "/=");
}
template<typename T>
void assign_remainder(Module &m) {
template<typename T, typename ModuleType>
void assign_remainder(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs %= rhs; }), "%=");
}
template<typename T>
void assign_right_shift(Module &m) {
template<typename T, typename ModuleType>
void assign_right_shift(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs >>= rhs; }), ">>=");
}
template<typename T>
void assign_sum(Module &m) {
template<typename T, typename ModuleType>
void assign_sum(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs, const T &rhs) -> T & { return lhs += rhs; }), "+=");
}
template<typename T>
void prefix_decrement(Module &m) {
template<typename T, typename ModuleType>
void prefix_decrement(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs) -> T & { return --lhs; }), "--");
}
template<typename T>
void prefix_increment(Module &m) {
template<typename T, typename ModuleType>
void prefix_increment(ModuleType &m) {
m.add(chaiscript::fun([](T &lhs) -> T & { return ++lhs; }), "++");
}
template<typename T>
void equal(Module &m) {
template<typename T, typename ModuleType>
void equal(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs == rhs; }), "==");
}
template<typename T>
void greater_than(Module &m) {
template<typename T, typename ModuleType>
void greater_than(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs > rhs; }), ">");
}
template<typename T>
void greater_than_equal(Module &m) {
template<typename T, typename ModuleType>
void greater_than_equal(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >= rhs; }), ">=");
}
template<typename T>
void less_than(Module &m) {
template<typename T, typename ModuleType>
void less_than(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs < rhs; }), "<");
}
template<typename T>
void less_than_equal(Module &m) {
template<typename T, typename ModuleType>
void less_than_equal(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs <= rhs; }), "<=");
}
template<typename T>
void logical_compliment(Module &m) {
template<typename T, typename ModuleType>
void logical_compliment(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs) { return !lhs; }), "!");
}
template<typename T>
void not_equal(Module &m) {
template<typename T, typename ModuleType>
void not_equal(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs != rhs; }), "!=");
}
template<typename T>
void addition(Module &m) {
template<typename T, typename ModuleType>
void addition(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs + rhs; }), "+");
}
template<typename T>
void unary_plus(Module &m) {
template<typename T, typename ModuleType>
void unary_plus(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs) { return +lhs; }), "+");
}
template<typename T>
void subtraction(Module &m) {
template<typename T, typename ModuleType>
void subtraction(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs - rhs; }), "-");
}
template<typename T>
void unary_minus(Module &m) {
template<typename T, typename ModuleType>
void unary_minus(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs) { return -lhs; }), "-");
}
template<typename T>
void bitwise_and(Module &m) {
template<typename T, typename ModuleType>
void bitwise_and(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs & rhs; }), "&");
}
template<typename T>
void bitwise_compliment(Module &m) {
template<typename T, typename ModuleType>
void bitwise_compliment(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs) { return ~lhs; }), "~");
}
template<typename T>
void bitwise_xor(Module &m) {
template<typename T, typename ModuleType>
void bitwise_xor(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs ^ rhs; }), "^");
}
template<typename T>
void bitwise_or(Module &m) {
template<typename T, typename ModuleType>
void bitwise_or(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs | rhs; }), "|");
}
template<typename T>
void division(Module &m) {
template<typename T, typename ModuleType>
void division(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs / rhs; }), "/");
}
template<typename T>
void left_shift(Module &m) {
template<typename T, typename ModuleType>
void left_shift(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs << rhs; }), "<<");
}
template<typename T>
void multiplication(Module &m) {
template<typename T, typename ModuleType>
void multiplication(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs * rhs; }), "*");
}
template<typename T>
void remainder(Module &m) {
template<typename T, typename ModuleType>
void remainder(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs % rhs; }), "%");
}
template<typename T>
void right_shift(Module &m) {
template<typename T, typename ModuleType>
void right_shift(ModuleType &m) {
m.add(chaiscript::fun([](const T &lhs, const T &rhs) { return lhs >> rhs; }), ">>");
}
} // namespace chaiscript::bootstrap::operators

View File

@ -1471,6 +1471,7 @@ namespace chaiscript {
Boxed_Value handle_exception(const chaiscript::detail::Dispatch_State &t_ss, const Boxed_Value &t_except) const {
Boxed_Value retval;
bool handled = false;
size_t end_point = this->children.size();
if (this->children.back()->identifier == AST_Node_Type::Finally) {
@ -1484,6 +1485,7 @@ namespace chaiscript {
if (catch_block.children.size() == 1) {
// No variable capture
retval = catch_block.children[0]->eval(t_ss);
handled = true;
break;
} else if (catch_block.children.size() == 2 || catch_block.children.size() == 3) {
const auto name = Arg_List_AST_Node<T>::get_arg_name(*catch_block.children[0]);
@ -1497,17 +1499,19 @@ namespace chaiscript {
if (catch_block.children.size() == 2) {
// Variable capture
retval = catch_block.children[1]->eval(t_ss);
handled = true;
break;
}
}
} else {
if (this->children.back()->identifier == AST_Node_Type::Finally) {
this->children.back()->children[0]->eval(t_ss);
}
throw exception::eval_error("Internal error: catch block size unrecognized");
}
}
if (!handled) {
throw;
}
return retval;
}
@ -1517,17 +1521,19 @@ namespace chaiscript {
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
try {
retval = this->children[0]->eval(t_ss);
} catch (const exception::eval_error &e) {
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) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
} catch (const std::exception &e) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
} catch (Boxed_Value &e) {
retval = handle_exception(t_ss, e);
try {
retval = this->children[0]->eval(t_ss);
} catch (const exception::eval_error &e) {
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) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
} catch (const std::exception &e) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
} catch (Boxed_Value &e) {
retval = handle_exception(t_ss, e);
}
} catch (...) {
if (this->children.back()->identifier == AST_Node_Type::Finally) {
this->children.back()->children[0]->eval(t_ss);

View File

@ -604,6 +604,45 @@ TEST_CASE("Utility_Test utility class wrapper for enum") {
CHECK_NOTHROW(chai.eval("var o = ONE; o = TWO"));
}
// Issue #601: add_class for enums should work directly with ChaiScript reference
enum class Issue601_EnumClass { Apple, Banana, Pear };
TEST_CASE("Issue 601: add_class enum with ChaiScript reference directly") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
// This should compile and work — previously it failed because the operator
// functions in chaiscript::bootstrap::operators hardcoded Module& as their
// first parameter instead of using a template parameter.
chaiscript::utility::add_class<Issue601_EnumClass>(chai,
"Issue601_EnumClass",
{{Issue601_EnumClass::Apple, "Apple"},
{Issue601_EnumClass::Banana, "Banana"},
{Issue601_EnumClass::Pear, "Pear"}});
CHECK(chai.eval<bool>("Apple == Apple"));
CHECK(chai.eval<bool>("Apple != Banana"));
CHECK_NOTHROW(chai.eval("var e = Apple; e = Pear"));
CHECK(chai.eval<Issue601_EnumClass>("Banana") == Issue601_EnumClass::Banana);
}
// Also test non-scoped enum directly with ChaiScript reference
enum Issue601_PlainEnum { Issue601_Red = 0, Issue601_Green = 1, Issue601_Blue = 2 };
TEST_CASE("Issue 601: add_class plain enum with ChaiScript reference directly") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chaiscript::utility::add_class<Issue601_PlainEnum>(chai,
"Issue601_PlainEnum",
{{Issue601_Red, "Red"},
{Issue601_Green, "Green"},
{Issue601_Blue, "Blue"}});
CHECK(chai.eval<bool>("Red == Red"));
CHECK(chai.eval<bool>("Red == 0"));
CHECK(chai.eval<bool>("Red != Green"));
CHECK_NOTHROW(chai.eval("var c = Red; c = Blue"));
}
////// Object copy count test
class Object_Copy_Count_Test {
@ -1782,3 +1821,191 @@ TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") {
(void)stack;
}
}
TEST_CASE("C++ runtime_error thrown from registered function is catchable in ChaiScript") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::fun([]() -> int { throw std::runtime_error("cpp_runtime_error"); }), "cpp_throw_runtime");
CHECK(chai.eval<bool>(R"(
var caught = false
try {
cpp_throw_runtime()
}
catch(e) {
caught = true
}
caught
)") == true);
}
TEST_CASE("C++ out_of_range thrown from registered function is catchable in ChaiScript") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::fun([]() -> int { throw std::out_of_range("cpp_out_of_range"); }), "cpp_throw_oor");
CHECK(chai.eval<bool>(R"(
var caught = false
try {
cpp_throw_oor()
}
catch(e) {
caught = true
}
caught
)") == true);
}
TEST_CASE("C++ logic_error thrown from registered function is catchable in ChaiScript") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::fun([]() -> int { throw std::logic_error("cpp_logic_error"); }), "cpp_throw_logic");
CHECK(chai.eval<bool>(R"(
var caught = false
try {
cpp_throw_logic()
}
catch(e) {
caught = true
}
caught
)") == true);
}
TEST_CASE("ChaiScript throw(int) propagates as Boxed_Value to C++") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(42)");
REQUIRE(false);
} catch (chaiscript::Boxed_Value &bv) {
CHECK(chaiscript::boxed_cast<int>(bv) == 42);
}
}
TEST_CASE("ChaiScript throw(string) propagates as Boxed_Value to C++") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval(R"(throw("error msg"))");
REQUIRE(false);
} catch (chaiscript::Boxed_Value &bv) {
CHECK(chaiscript::boxed_cast<std::string>(bv) == "error msg");
}
}
TEST_CASE("Typed catch with no match propagates exception") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
CHECK_THROWS_AS(chai.eval(R"(
try {
throw(42)
}
catch(string e) {
// wrong type, should not match
}
)"), chaiscript::Boxed_Value);
}
TEST_CASE("Typed catch with no match still runs finally block") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
CHECK_THROWS_AS(chai.eval(R"(
var finally_ran = false
try {
throw(42)
}
catch(string e) {
// wrong type
}
finally {
finally_ran = true
}
)"), chaiscript::Boxed_Value);
CHECK(chai.eval<bool>("finally_ran") == true);
}
TEST_CASE("Multiple C++ exception types from registered functions") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::fun([](int which) -> int {
switch (which) {
case 0: throw std::runtime_error("runtime");
case 1: throw std::out_of_range("range");
case 2: throw std::logic_error("logic");
default: return which;
}
}), "cpp_multi_throw");
CHECK(chai.eval<int>(R"(
var catch_count = 0
for (var i = 0; i < 3; ++i) {
try {
cpp_multi_throw(i)
}
catch(e) {
catch_count = catch_count + 1
}
}
catch_count
)") == 3);
CHECK(chai.eval<int>("cpp_multi_throw(5)") == 5);
}
TEST_CASE("Exception from C++ binary operator is catchable in ChaiScript") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
struct ThrowingType {
int value;
};
chai.add(chaiscript::user_type<ThrowingType>(), "ThrowingType");
chai.add(chaiscript::constructor<ThrowingType(int)>(), "ThrowingType");
chai.add(chaiscript::fun([](const ThrowingType &, const ThrowingType &) -> ThrowingType {
throw std::runtime_error("cpp operator+ threw");
}), "+");
CHECK(chai.eval<bool>(R"(
var caught = false
try {
var a = ThrowingType(1)
var b = ThrowingType(2)
var c = a + b
}
catch(e) {
caught = true
}
caught
)") == true);
}
TEST_CASE("Exception from C++ [] operator is catchable in ChaiScript") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
struct IndexableType {
int value;
};
chai.add(chaiscript::user_type<IndexableType>(), "IndexableType");
chai.add(chaiscript::constructor<IndexableType(int)>(), "IndexableType");
chai.add(chaiscript::fun([](const IndexableType &, int idx) -> int {
if (idx < 0) { throw std::out_of_range("negative index"); }
return idx;
}), "[]");
CHECK(chai.eval<int>("var obj = IndexableType(0); obj[5]") == 5);
CHECK(chai.eval<bool>(R"(
var caught = false
try {
var x = obj[-1]
}
catch(e) {
caught = true
}
caught
)") == true);
}

View File

@ -0,0 +1,862 @@
// Comprehensive exception throwing and catching tests
// Tests throw/catch from various contexts: operators, functions, lambdas,
// methods, [] operator, nested scopes, and with various value types.
// ============================================================
// Section 1: Throwing and catching different value types
// ============================================================
// Throw int
try {
throw(42)
}
catch(e) {
assert_equal(42, e)
}
// Throw string
try {
throw("error message")
}
catch(e) {
assert_equal("error message", e)
}
// Throw double
try {
throw(3.14)
}
catch(e) {
assert_equal(3.14, e)
}
// Throw bool
try {
throw(true)
}
catch(e) {
assert_equal(true, e)
}
// Throw a Vector (via named variable to preserve mutability)
auto thrown_vec = [1, 2, 3]
try {
throw(thrown_vec)
}
catch(e) {
assert_equal(3, e.size())
assert_equal(1, e[0])
assert_equal(2, e[1])
assert_equal(3, e[2])
}
// Throw a Map (via named variable to preserve mutability)
auto thrown_map = ["key": 42]
try {
throw(thrown_map)
}
catch(e) {
assert_equal(42, e["key"])
}
// ============================================================
// Section 2: Typed catch blocks
// ============================================================
// Typed catch matching int
auto typed_result = 0
try {
throw(10)
}
catch(int e) {
typed_result = e + 1
}
assert_equal(11, typed_result)
// Typed catch matching string
typed_result = 0
try {
throw("hello")
}
catch(string e) {
typed_result = 1
}
assert_equal(1, typed_result)
// Typed catch mismatch falls through to untyped
typed_result = 0
try {
throw(42)
}
catch(string e) {
typed_result = 1
}
catch(e) {
typed_result = 2
}
assert_equal(2, typed_result)
// Multiple typed catches - first match wins
typed_result = 0
try {
throw("test")
}
catch(int e) {
typed_result = 1
}
catch(string e) {
typed_result = 2
}
catch(e) {
typed_result = 3
}
assert_equal(2, typed_result)
// Typed catch with int, multiple typed blocks, none match except untyped
typed_result = 0
try {
throw(3.14)
}
catch(int e) {
typed_result = 1
}
catch(string e) {
typed_result = 2
}
catch(e) {
typed_result = 3
}
assert_equal(3, typed_result)
// ============================================================
// Section 3: Catch-all (no variable) catch block
// ============================================================
auto catch_all_reached = false
try {
throw(99)
}
catch {
catch_all_reached = true
}
assert_true(catch_all_reached)
// ============================================================
// Section 4: Finally block semantics
// ============================================================
// Finally runs after exception
auto finally_ran = false
try {
throw(1)
}
catch(e) {
// caught
}
finally {
finally_ran = true
}
assert_true(finally_ran)
// Finally runs without exception
finally_ran = false
try {
auto x = 1
}
catch(e) {
// not reached
}
finally {
finally_ran = true
}
assert_true(finally_ran)
// Finally runs even with typed catch that matches
finally_ran = false
try {
throw(42)
}
catch(int e) {
assert_equal(42, e)
}
finally {
finally_ran = true
}
assert_true(finally_ran)
// Finally runs when typed catch does NOT match (exception not caught)
finally_ran = false
auto outer_caught = false
try {
try {
throw(42)
}
catch(string e) {
// wrong type, won't match
}
finally {
finally_ran = true
}
}
catch(e) {
outer_caught = true
}
assert_true(finally_ran)
assert_true(outer_caught)
// ============================================================
// Section 5: Throwing from functions
// ============================================================
def throwing_function() {
throw("from function")
}
try {
throwing_function()
}
catch(e) {
assert_equal("from function", e)
}
// Throwing from nested function calls
def inner_throw() {
throw("inner")
}
def outer_call() {
inner_throw()
}
try {
outer_call()
}
catch(e) {
assert_equal("inner", e)
}
// Function that throws conditionally
def conditional_throw(should_throw) {
if (should_throw) {
throw("conditional")
}
return "no throw"
}
assert_equal("no throw", conditional_throw(false))
try {
conditional_throw(true)
}
catch(e) {
assert_equal("conditional", e)
}
// ============================================================
// Section 6: Throwing from lambdas
// ============================================================
auto throwing_lambda = fun() { throw("from lambda") }
try {
throwing_lambda()
}
catch(e) {
assert_equal("from lambda", e)
}
// Lambda that captures and throws
auto captured_val = "captured"
auto capture_lambda = fun[captured_val]() { throw(captured_val) }
try {
capture_lambda()
}
catch(e) {
assert_equal("captured", e)
}
// ============================================================
// Section 7: Throwing from binary operators
// ============================================================
// Define a type and an operator that throws
attr ThrowOnAdd::val
def ThrowOnAdd::ThrowOnAdd(v) { this.val = v }
def `+`(ThrowOnAdd x, ThrowOnAdd y) {
throw("add not supported")
}
try {
auto a = ThrowOnAdd(1)
auto b = ThrowOnAdd(2)
auto c = a + b
assert_true(false) // should not reach here
}
catch(e) {
assert_equal("add not supported", e)
}
// Operator that throws for specific values
def `-`(ThrowOnAdd x, ThrowOnAdd y) {
if (x.val == y.val) {
throw("cannot subtract equal values")
}
return ThrowOnAdd(x.val - y.val)
}
try {
auto a = ThrowOnAdd(5)
auto b = ThrowOnAdd(5)
auto c = a - b
assert_true(false)
}
catch(e) {
assert_equal("cannot subtract equal values", e)
}
// Multiplication operator that throws (not pre-defined for Dynamic_Object)
def `*`(ThrowOnAdd x, ThrowOnAdd y) {
throw("multiply not supported")
}
try {
auto a = ThrowOnAdd(1)
auto b = ThrowOnAdd(2)
auto c = a * b
assert_true(false)
}
catch(e) {
assert_equal("multiply not supported", e)
}
// Subtraction works for non-equal values
auto sub_result = ThrowOnAdd(10) - ThrowOnAdd(3)
assert_equal(7, sub_result.val)
// ============================================================
// Section 8: Throwing from unary/prefix operators
// ============================================================
def `++`(ThrowOnAdd x) {
throw("increment not supported")
}
try {
auto a = ThrowOnAdd(1)
++a
assert_true(false)
}
catch(e) {
assert_equal("increment not supported", e)
}
// ============================================================
// Section 9: Throwing from [] operator
// ============================================================
attr ThrowOnIndex::data
def ThrowOnIndex::ThrowOnIndex() { this.data = [1, 2, 3] }
def `[]`(ThrowOnIndex obj, int idx) {
if (idx < 0) {
throw("negative index not allowed")
}
return obj.data[idx]
}
auto toi = ThrowOnIndex()
assert_equal(1, toi[0])
assert_equal(2, toi[1])
try {
auto val = toi[-1]
assert_true(false)
}
catch(e) {
assert_equal("negative index not allowed", e)
}
// ============================================================
// Section 10: Throwing from member functions
// ============================================================
attr Validatable::value
def Validatable::Validatable(v) { this.value = v }
def Validatable::validate() {
if (this.value < 0) {
throw("validation failed: negative value")
}
return true
}
auto valid_obj = Validatable(10)
assert_true(valid_obj.validate())
auto invalid_obj = Validatable(-1)
try {
invalid_obj.validate()
assert_true(false)
}
catch(e) {
assert_equal("validation failed: negative value", e)
}
// ============================================================
// Section 11: Nested try/catch
// ============================================================
auto inner_caught = false
auto outer_caught_val = 0
try {
try {
throw(1)
}
catch(e) {
inner_caught = true
throw(e + 10)
}
}
catch(e) {
outer_caught_val = e
}
assert_true(inner_caught)
assert_equal(11, outer_caught_val)
// Deeply nested try/catch
auto depth = 0
try {
try {
try {
throw("deep")
}
catch(e) {
depth = 1
throw(e + "er")
}
}
catch(e) {
depth = 2
throw(e + "est")
}
}
catch(e) {
depth = 3
assert_equal("deeperest", e)
}
assert_equal(3, depth)
// ============================================================
// Section 12: Rethrow from catch block
// ============================================================
auto rethrow_caught = false
try {
try {
throw("rethrown")
}
catch(e) {
throw(e)
}
}
catch(e) {
rethrow_caught = true
assert_equal("rethrown", e)
}
assert_true(rethrow_caught)
// ============================================================
// Section 13: Exception in for loop
// ============================================================
auto loop_exception_val = 0
try {
for (auto i = 0; i < 10; ++i) {
if (i == 5) {
throw(i)
}
}
}
catch(e) {
loop_exception_val = e
}
assert_equal(5, loop_exception_val)
// ============================================================
// Section 14: Exception in while loop
// ============================================================
auto while_exc_val = 0
auto counter = 0
try {
while (true) {
++counter
if (counter == 3) {
throw(counter)
}
}
}
catch(e) {
while_exc_val = e
}
assert_equal(3, while_exc_val)
// ============================================================
// Section 15: Exception preserves value through nested calls
// ============================================================
def deep_throw(val) {
throw(val)
}
def middle_call(val) {
deep_throw(val)
}
def top_call(val) {
middle_call(val)
}
auto nested_map = ["key": "value"]
try {
top_call(nested_map)
}
catch(e) {
assert_equal("value", e["key"])
}
auto nested_vec = [10, 20, 30]
try {
top_call(nested_vec)
}
catch(e) {
assert_equal(3, e.size())
assert_equal(20, e[1])
}
// ============================================================
// Section 16: Code after throw is not executed
// ============================================================
auto after_throw = false
try {
throw(1)
after_throw = true
}
catch(e) {
// caught
}
assert_false(after_throw)
// ============================================================
// Section 17: Exception value is usable in catch block arithmetic
// ============================================================
auto catch_computed = 0
try {
throw(1)
}
catch(e) {
catch_computed = e + 100
}
assert_equal(101, catch_computed)
// ============================================================
// Section 18: No exception means catch is skipped
// ============================================================
auto catch_skipped = true
try {
auto x = 42
}
catch(e) {
catch_skipped = false
}
assert_true(catch_skipped)
// ============================================================
// Section 19: Exception from dynamic object method chaining
// ============================================================
attr Chain::val
def Chain::Chain(v) { this.val = v }
def Chain::add(n) {
if (this.val + n > 100) {
throw("overflow: " + to_string(this.val + n))
}
this.val = this.val + n
return this
}
auto chain = Chain(50)
try {
chain.add(30).add(30)
assert_true(false)
}
catch(e) {
assert_equal("overflow: 110", e)
}
assert_equal(80, chain.val)
// ============================================================
// Section 20: Exception thrown during map construction
// ============================================================
def exploding_value() {
throw("boom during construction")
}
try {
auto m = ["ok": 1, "bad": exploding_value()]
assert_true(false)
}
catch(e) {
assert_equal("boom during construction", e)
}
// ============================================================
// Section 21: Exception thrown during vector construction
// ============================================================
try {
auto v = [1, 2, exploding_value(), 4]
assert_true(false)
}
catch(e) {
assert_equal("boom during construction", e)
}
// ============================================================
// Section 22: Exception in if-condition
// ============================================================
def exploding_condition() {
throw("condition exploded")
}
try {
if (exploding_condition()) {
assert_true(false)
}
}
catch(e) {
assert_equal("condition exploded", e)
}
// ============================================================
// Section 23: Multiple catch blocks - only first matching runs
// ============================================================
auto catch_count = 0
try {
throw(42)
}
catch(int e) {
++catch_count
}
catch(e) {
++catch_count
}
assert_equal(1, catch_count)
// ============================================================
// Section 24: Throwing from within catch, with finally
// ============================================================
auto s24_finally = false
auto s24_outer = false
try {
try {
throw("original")
}
catch(e) {
throw("replaced: " + e)
}
finally {
s24_finally = true
}
}
catch(e) {
s24_outer = true
assert_equal("replaced: original", e)
}
assert_true(s24_finally)
assert_true(s24_outer)
// ============================================================
// Section 25: Unhandled typed catch propagates exception
// ============================================================
auto s25_caught = false
try {
try {
throw(3.14)
}
catch(int e) {
assert_true(false) // should not match double
}
catch(string e) {
assert_true(false) // should not match double
}
}
catch(e) {
s25_caught = true
assert_equal(3.14, e)
}
assert_true(s25_caught)
// ============================================================
// Section 26: Throw from range-based for
// ============================================================
auto s26_val = 0
try {
for (x : [10, 20, 30, 40]) {
if (x == 30) {
throw(x)
}
}
}
catch(e) {
s26_val = e
}
assert_equal(30, s26_val)
// ============================================================
// Section 27: Throw from eval
// ============================================================
try {
eval("throw(\"from eval\")")
}
catch(e) {
assert_equal("from eval", e)
}
// ============================================================
// Section 28: Exception from built-in operations (out of range)
// ============================================================
auto s28_caught = false
try {
auto v = [1, 2, 3]
auto x = v[10]
}
catch(e) {
s28_caught = true
}
assert_true(s28_caught)
// ============================================================
// Section 29: Throw zero and empty string (falsy values)
// ============================================================
try {
throw(0)
}
catch(e) {
assert_equal(0, e)
}
try {
throw("")
}
catch(e) {
assert_equal("", e)
}
// ============================================================
// Section 30: Throw from ternary-style inline_if
// ============================================================
def maybe_throw(do_it) {
if (do_it) { throw("inline threw") } else { "ok" }
}
try {
maybe_throw(true)
}
catch(e) {
assert_equal("inline threw", e)
}
assert_equal("ok", maybe_throw(false))
// ============================================================
// Section 31: Verify catch variable scope isolation
// ============================================================
auto outer_e = "untouched"
try {
throw("caught_value")
}
catch(e) {
assert_equal("caught_value", e)
}
assert_equal("untouched", outer_e)
// ============================================================
// Section 32: Exception from recursive function
// ============================================================
def recursive_throw(n) {
if (n == 0) {
throw("bottom")
}
recursive_throw(n - 1)
}
try {
recursive_throw(5)
}
catch(e) {
assert_equal("bottom", e)
}
// ============================================================
// Section 33: Try/catch in a function body
// ============================================================
def safe_divide(a, b) {
try {
if (b == 0) {
throw("division by zero")
}
return a / b
}
catch(e) {
return e
}
}
assert_equal(5, safe_divide(10, 2))
assert_equal("division by zero", safe_divide(10, 0))
// ============================================================
// Section 34: Throw from [] on a Map with missing key
// ============================================================
auto s34_caught = false
try {
auto m = ["a": 1]
auto x = m["nonexistent"]
}
catch(e) {
s34_caught = true
}
assert_true(s34_caught)
// ============================================================
// Section 35: Arithmetic exception (divide by zero)
// ============================================================
auto s35_caught = false
try {
auto x = 1 / 0
}
catch(e) {
s35_caught = true
}
assert_true(s35_caught)