// All of these are necessary because of catch.hpp. It's OK, they'll be // caught in other cpp files if chaiscript causes them #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4062 4242 4566 4640 4702 6330 28251) #endif #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunknown-pragmas" #pragma GCC diagnostic ignored "-Wparentheses" // This one is necessary for the const return non-reference test #pragma GCC diagnostic ignored "-Wignored-qualifiers" #endif #include #include #include #include #include "../static_libs/chaiscript_parser.hpp" #include "../static_libs/chaiscript_stdlib.hpp" #define CATCH_CONFIG_MAIN #include #include "catch.hpp" // lambda_tests TEST_CASE("C++11 Lambdas Can Be Registered") { // We cannot deduce the type of a lambda expression, you must either wrap it // in an std::function or provide the signature chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun([]() -> std::string { return "hello"; }), "f1"); // wrap chai.add(chaiscript::fun(std::function([] { return "world"; })), "f2"); CHECK(chai.eval("f1()") == "hello"); CHECK(chai.eval("f2()") == "world"); } TEST_CASE("Lambdas can return boolean") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // check lambdas returning bool from chaiscript: std::function chai_function; CHECK_NOTHROW(chai_function = chai.eval>("fun() { 42 != 0 }")); bool result = false; CHECK_NOTHROW(result = chai_function()); CHECK(result == true); // check lambdas returning bool from C++: auto cpp_function = [](int x) { return x == 42; }; CHECK_NOTHROW(chai.add(chaiscript::fun(cpp_function), "cpp_function")); CHECK_NOTHROW(result = chai.eval("cpp_function(314)")); CHECK(result == false); } // dynamic_object tests TEST_CASE("Dynamic_Object attributes can be shared with C++") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai("attr bob::z; def bob::bob() { this.z = 10 }; auto x = bob()"); chaiscript::dispatch::Dynamic_Object &mydo = chai.eval("x"); CHECK(mydo.get_type_name() == "bob"); CHECK(chaiscript::boxed_cast(mydo.get_attr("z")) == 10); chai("x.z = 15"); CHECK(chaiscript::boxed_cast(mydo.get_attr("z")) == 15); int &z = chaiscript::boxed_cast(mydo.get_attr("z")); CHECK(z == 15); z = 20; CHECK(z == 20); CHECK(chaiscript::boxed_cast(chai("x.z")) == 20); } TEST_CASE("Function objects can be created from chaiscript functions") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.eval("def func() { print(\"Hello World\"); } "); std::function f = chai.eval>("func"); f(); CHECK(chai.eval>("to_string")(6) == "6"); CHECK(chai.eval>("to_string")(chaiscript::var(6)) == "6"); } TEST_CASE("ChaiScript can be created and destroyed on heap") { auto *chai = new chaiscript::ChaiScript_Basic(create_chaiscript_stdlib(), create_chaiscript_parser()); delete chai; } ///////// Arithmetic Conversions // Tests to make sure that type conversions happen only when they should void arithmetic_conversions_f1(int) {} void arithmetic_conversions_f4(std::string) {} void arithmetic_conversions_f2(int) {} void arithmetic_conversions_f3(double) {} void arithmetic_conversions_f_func_return(const std::function &f) { // test the ability to return an unsigned with auto conversion f(4); } TEST_CASE("Test automatic arithmetic conversions") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(&arithmetic_conversions_f1), "f1"); chai.add(chaiscript::fun(&arithmetic_conversions_f2), "f2"); chai.add(chaiscript::fun(&arithmetic_conversions_f3), "f2"); chai.add(chaiscript::fun(&arithmetic_conversions_f1), "f3"); chai.add(chaiscript::fun(&arithmetic_conversions_f4), "f3"); chai.add(chaiscript::fun(&arithmetic_conversions_f_func_return), "func_return"); // no overloads chai.eval("f1(0)"); chai.eval("f1(0l)"); chai.eval("f1(0ul)"); chai.eval("f1(0ll)"); chai.eval("f1(0ull)"); chai.eval("f1(0.0)"); chai.eval("f1(0.0f)"); chai.eval("f1(0.0l)"); // expected overloads chai.eval("f2(1)"); chai.eval("f2(1.0)"); // 1 non-arithmetic overload chai.eval("f2(1.0)"); // various options for returning with conversions from chaiscript chai.eval("func_return(fun(x) { return 5u; })"); chai.eval("func_return(fun(x) { return 5; })"); chai.eval("func_return(fun(x) { return 5.0f; })"); CHECK_THROWS(chai.eval("f2(1.0l)")); } /////// Exception handling TEST_CASE("Generic exception handling with C++") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); try { chai.eval("throw(runtime_error(\"error\"));"); REQUIRE(false); } catch (const chaiscript::Boxed_Value &bv) { const std::exception &e = chai.boxed_cast(bv); CHECK(e.what() == std::string("error")); } } TEST_CASE("Throw an int") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); try { chai.eval("throw(1)", chaiscript::exception_specification()); REQUIRE(false); } catch (int e) { CHECK(e == 1); } } TEST_CASE("Throw int or double") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); try { chai.eval("throw(1.0)", chaiscript::exception_specification()); REQUIRE(false); } catch (const double e) { CHECK(e == Approx(1.0)); } } TEST_CASE("Deduction of pointer return types") { int val = 5; int *val_ptr = &val; auto &val_ptr_ref = val_ptr; const auto &val_ptr_const_ref = val_ptr; auto get_val_ptr = [&]() -> int * { return val_ptr; }; auto get_val_const_ptr = [&]() -> int const * { return val_ptr; }; auto get_val_ptr_ref = [&]() -> int *& { return val_ptr_ref; }; auto get_val_ptr_const_ref = [&]() -> int *const & { return val_ptr_const_ref; }; // auto get_val_const_ptr_const_ref = [ref=std::cref(val_ptr)]() -> int const * const & { return ref.get(); }; chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(get_val_ptr), "get_val_ptr"); chai.add(chaiscript::fun(get_val_const_ptr), "get_val_const_ptr"); chai.add(chaiscript::fun(get_val_ptr_ref), "get_val_ptr_ref"); chai.add(chaiscript::fun(get_val_ptr_const_ref), "get_val_ptr_const_ref"); // chai.add(chaiscript::fun(get_val_const_ptr_const_ref), "get_val_const_ptr_const_ref"); CHECK(chai.eval("get_val_ptr()") == &val); CHECK(*chai.eval("get_val_ptr()") == val); CHECK(chai.eval("get_val_const_ptr()") == &val); CHECK(*chai.eval("get_val_const_ptr()") == val); // note that we cannot maintain the references here, // chaiscript internals cannot handle that, effectively pointer to pointer CHECK(chai.eval("get_val_ptr_ref()") == &val); CHECK(*chai.eval("get_val_ptr_ref()") == val); CHECK(chai.eval("get_val_ptr_const_ref()") == &val); CHECK(*chai.eval("get_val_ptr_const_ref()") == val); // CHECK(chai.eval("get_val_const_ptr_const_ref()") == &val); } TEST_CASE("Throw a runtime_error") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); try { chai.eval("throw(runtime_error(\"error\"))", chaiscript::exception_specification()); REQUIRE(false); } catch (const double) { REQUIRE(false); } catch (int) { REQUIRE(false); } catch (float) { REQUIRE(false); } catch (const std::string &) { REQUIRE(false); } catch (const std::exception &) { REQUIRE(true); } } TEST_CASE("Throw unhandled type") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); try { chai.eval("throw(\"error\")", chaiscript::exception_specification()); REQUIRE(false); } catch (double) { REQUIRE(false); } catch (int) { REQUIRE(false); } catch (float) { REQUIRE(false); } catch (const std::exception &) { REQUIRE(false); } catch (const chaiscript::Boxed_Value &) { REQUIRE(true); } } ///////////// Tests to make sure no arity, dispatch or guard errors leak up past eval int expected_eval_errors_test_one(const int &) { return 1; } TEST_CASE("No unexpected exceptions leak") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(&expected_eval_errors_test_one), "test_fun"); chai.eval("def guard_fun(i) : i.get_type_info().is_type_arithmetic() {} "); //// Dot notation // non-existent function CHECK_THROWS_AS(chai.eval("\"test\".test_one()"), chaiscript::exception::eval_error); // wrong parameter type CHECK_THROWS_AS(chai.eval("\"test\".test_fun()"), chaiscript::exception::eval_error); // wrong number of parameters CHECK_THROWS_AS(chai.eval("\"test\".test_fun(1)"), chaiscript::exception::eval_error); // guard failure CHECK_THROWS_AS(chai.eval("\"test\".guard_fun()"), chaiscript::exception::eval_error); // regular notation // non-existent function CHECK_THROWS_AS(chai.eval("test_one(\"test\")"), chaiscript::exception::eval_error); // wrong parameter type CHECK_THROWS_AS(chai.eval("test_fun(\"test\")"), chaiscript::exception::eval_error); // wrong number of parameters CHECK_THROWS_AS(chai.eval("test_fun(\"test\")"), chaiscript::exception::eval_error); // guard failure CHECK_THROWS_AS(chai.eval("guard_fun(\"test\")"), chaiscript::exception::eval_error); // index operator CHECK_THROWS_AS(chai.eval("var a = [1,2,3]; a[\"bob\"];"), chaiscript::exception::eval_error); // unary operator CHECK_THROWS_AS(chai.eval("++\"bob\""), chaiscript::exception::eval_error); // binary operator CHECK_THROWS_AS(chai.eval("\"bob\" + 1"), chaiscript::exception::eval_error); } //////// Tests to make sure that the order in which function dispatches occur is correct #include int function_ordering_test_one(const int &) { return 1; } int function_ordering_test_two(int &) { return 2; } TEST_CASE("Function ordering") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.eval("def test_fun(x) { return 3; }"); chai.eval("def test_fun(x) : x == \"hi\" { return 4; }"); // chai.eval("def test_fun(x) { return 5; }"); chai.add(chaiscript::fun(&function_ordering_test_one), "test_fun"); chai.add(chaiscript::fun(&function_ordering_test_two), "test_fun"); CHECK(chai.eval("test_fun(1)") == 1); CHECK(chai.eval("auto i = 1; test_fun(i)") == 2); CHECK(chai.eval("test_fun(\"bob\")") == 3); CHECK(chai.eval("test_fun(\"hi\")") == 4); } int functor_cast_test_call(const std::function &f, int val) { return f(val); } TEST_CASE("Functor cast") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(&functor_cast_test_call), "test_call"); chai.eval("def func(i) { return i * 6; };"); int d = chai.eval("test_call(func, 3)"); CHECK(d == 3 * 6); } TEST_CASE("Non-ASCII characters in the middle of string") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); CHECK_THROWS_AS(chai.eval("prin\xeft \"Hello World\""), chaiscript::exception::eval_error); } TEST_CASE("Non-ASCII characters in the beginning of string") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); CHECK_THROWS_AS(chai.eval("\xefprint \"Hello World\""), chaiscript::exception::eval_error); } TEST_CASE("Non-ASCII characters in the end of string") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); CHECK_THROWS_AS(chai.eval("print \"Hello World\"\xef"), chaiscript::exception::eval_error); } TEST_CASE("BOM in string") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); CHECK_THROWS_AS(chai.eval("\xef\xbb\xbfprint \"Hello World\""), chaiscript::exception::eval_error); } int set_state_test_myfun() { return 2; } TEST_CASE("Set and restore chai state") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // save the initial state of globals and locals auto firststate = chai.get_state(); std::map locals = chai.get_locals(); // add some new globals and locals chai.add(chaiscript::var(1), "i"); chai.add(chaiscript::fun(&set_state_test_myfun), "myfun"); CHECK(chai.eval("myfun()") == 2); CHECK(chai.eval("i") == 1); chai.set_state(firststate); // set state should have reverted the state of the functions and dropped // the 'myfun' CHECK_THROWS_AS(chai.eval("myfun()"), chaiscript::exception::eval_error); // set state should not affect the local variables CHECK(chai.eval("i") == 1); // After resetting the locals we expect the 'i' to be gone chai.set_locals(locals); CHECK_THROWS_AS(chai.eval("i"), chaiscript::exception::eval_error); } TEST_CASE("Get function objects from public API") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // Add a custom function chai.add(chaiscript::fun(&set_state_test_myfun), "myfun"); // get_function_objects should be accessible from the public API auto funcs = chai.get_function_objects(); // Our custom function should be in the map CHECK(funcs.count("myfun") == 1); // Built-in functions should also be present CHECK(funcs.count("to_string") == 1); // The function should be callable CHECK(chai.eval("myfun()") == 2); } TEST_CASE("Get scripting objects from public API") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // Define some variables in script chai.eval("var x = 42"); chai.eval("var name = \"hello\""); // get_scripting_objects should be accessible from the public API auto objects = chai.get_scripting_objects(); // Our scripting variables should be in the map CHECK(objects.count("x") == 1); CHECK(objects.count("name") == 1); // Verify the values are correct CHECK(chaiscript::boxed_cast(objects["x"]) == 42); CHECK(chaiscript::boxed_cast(objects["name"]) == "hello"); } //// Short comparisons class Short_Comparison_Test { public: Short_Comparison_Test() : value_(5) { } short get_value() const { return value_; } short value_; }; TEST_CASE("Short comparison with int") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::user_type(), "Test"); chai.add(chaiscript::constructor(), "Test"); chai.add(chaiscript::fun(&Short_Comparison_Test::get_value), "get_value"); chai.eval("auto &t = Test();"); CHECK(chai.eval("t.get_value() == 5")); } ///// Test lookup of type names class Type_Name_MyClass { }; TEST_CASE("Test lookup of type names") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); auto type = chaiscript::user_type(); chai.add(type, "MyClass"); CHECK(chai.get_type_name(type) == "MyClass"); CHECK(chai.get_type_name() == "MyClass"); } /////// make sure many chaiscript objects can exist simultaneously int simultaneous_chaiscript_do_something(int i) { return i + 2; } int simultaneous_chaiscript_do_something_else(int i) { return i * 2; } TEST_CASE("Simultaneous ChaiScript tests") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(&simultaneous_chaiscript_do_something), "do_something"); chai.add(chaiscript::var(1), "i"); for (int i = 0; i < 10; ++i) { chaiscript::ChaiScript_Basic chai2(create_chaiscript_stdlib(), create_chaiscript_parser()); chai2.add(chaiscript::fun(&simultaneous_chaiscript_do_something_else), "do_something_else"); CHECK(chai.eval("do_something(" + std::to_string(i) + ")") == i + 2); CHECK(chai2.eval("do_something_else(" + std::to_string(i) + ")") == i * 2); CHECK_THROWS_AS(chai2.eval("do_something(1)"), chaiscript::exception::eval_error); CHECK_THROWS_AS(chai2.eval("i"), chaiscript::exception::eval_error); CHECK_NOTHROW(chai2.eval("do_something_else(1)")); } } /////////////// test utility functions class Utility_Test { public: void function() {} std::string function2() { return "Function2"; } void function3() {} std::string functionOverload(double) { return "double"; } std::string functionOverload(int) { return "int"; } }; TEST_CASE("Utility_Test utility class wrapper") { chaiscript::ModulePtr m = chaiscript::ModulePtr(new chaiscript::Module()); using namespace chaiscript; /// \todo fix overload resolution for fun<> chaiscript::utility::add_class( *m, "Utility_Test", {constructor(), constructor()}, {{fun(&Utility_Test::function), "function"}, {fun(&Utility_Test::function2), "function2"}, {fun(&Utility_Test::function3), "function3"}, {fun(static_cast(&Utility_Test::functionOverload)), "functionOverload"}, {fun(static_cast(&Utility_Test::functionOverload)), "functionOverload"}, {fun(static_cast(&Utility_Test::operator=)), "="} }); chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(m); CHECK(chai.eval("auto t = Utility_Test(); t.function2(); ") == "Function2"); CHECK(chai.eval("auto t2 = Utility_Test(); t2.functionOverload(1); ") == "int"); CHECK(chai.eval("auto t3 = Utility_Test(); t3.functionOverload(1.1); ") == "double"); chai.eval("t = Utility_Test();"); } enum Utility_Test_Numbers { ONE, TWO, THREE }; void do_something_with_enum_vector(const std::vector &v) { CHECK(v.size() == 3); CHECK(v[0] == ONE); CHECK(v[1] == THREE); CHECK(v[2] == TWO); } TEST_CASE("Utility_Test utility class wrapper for enum") { chaiscript::ModulePtr m = chaiscript::ModulePtr(new chaiscript::Module()); using namespace chaiscript; chaiscript::utility::add_class(*m, "Utility_Test_Numbers", {{ONE, "ONE"}, {TWO, "TWO"}, {THREE, "THREE"} }); chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(m); CHECK(chai.eval("ONE ") == 0); CHECK(chai.eval("TWO ") == 1); CHECK(chai.eval("THREE ") == 2); CHECK(chai.eval("ONE == 0")); chai.add(chaiscript::fun(&do_something_with_enum_vector), "do_something_with_enum_vector"); chai.add(chaiscript::vector_conversion>()); CHECK_NOTHROW(chai.eval("var a = [ONE, TWO, THREE]")); CHECK_NOTHROW(chai.eval("do_something_with_enum_vector([ONE, THREE, TWO])")); CHECK_NOTHROW(chai.eval("[ONE]")); const auto v = chai.eval>("a"); CHECK(v.size() == 3); CHECK(v.at(1) == TWO); CHECK(chai.eval("ONE == ONE")); CHECK(chai.eval("ONE != TWO")); 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(chai, "Issue601_EnumClass", {{Issue601_EnumClass::Apple, "Apple"}, {Issue601_EnumClass::Banana, "Banana"}, {Issue601_EnumClass::Pear, "Pear"}}); CHECK(chai.eval("Apple == Apple")); CHECK(chai.eval("Apple != Banana")); CHECK_NOTHROW(chai.eval("var e = Apple; e = Pear")); CHECK(chai.eval("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(chai, "Issue601_PlainEnum", {{Issue601_Red, "Red"}, {Issue601_Green, "Green"}, {Issue601_Blue, "Blue"}}); CHECK(chai.eval("Red == Red")); CHECK(chai.eval("Red == 0")); CHECK(chai.eval("Red != Green")); CHECK_NOTHROW(chai.eval("var c = Red; c = Blue")); } ////// Object copy count test class Object_Copy_Count_Test { public: Object_Copy_Count_Test() { std::cout << "Object_Copy_Count_Test()\n"; ++constructcount(); } Object_Copy_Count_Test(const Object_Copy_Count_Test &) { std::cout << "Object_Copy_Count_Test(const Object_Copy_Count_Test &)\n"; ++copycount(); } Object_Copy_Count_Test(Object_Copy_Count_Test &&) { std::cout << "Object_Copy_Count_Test(Object_Copy_Count_Test &&)\n"; ++movecount(); } ~Object_Copy_Count_Test() { std::cout << "~Object_Copy_Count_Test()\n"; ++destructcount(); } static int &constructcount() { static int c = 0; return c; } static int ©count() { static int c = 0; return c; } static int &movecount() { static int c = 0; return c; } static int &destructcount() { static int c = 0; return c; } }; Object_Copy_Count_Test object_copy_count_create() { return Object_Copy_Count_Test(); } TEST_CASE("Object copy counts") { chaiscript::ModulePtr m = chaiscript::ModulePtr(new chaiscript::Module()); m->add(chaiscript::user_type(), "Object_Copy_Count_Test"); m->add(chaiscript::constructor(), "Object_Copy_Count_Test"); m->add(chaiscript::constructor(), "Object_Copy_Count_Test"); m->add(chaiscript::fun(&object_copy_count_create), "create"); chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(m); chai.eval(" { auto i = create(); } "); CHECK(Object_Copy_Count_Test::copycount() == 0); CHECK(Object_Copy_Count_Test::constructcount() == 1); CHECK(Object_Copy_Count_Test::destructcount() == 2); CHECK(Object_Copy_Count_Test::movecount() == 1); } ///////////////////// Object lifetime test 1 class Object_Lifetime_Test { public: Object_Lifetime_Test() { ++count(); } Object_Lifetime_Test(const Object_Lifetime_Test &) { ++count(); } ~Object_Lifetime_Test() { --count(); } static int &count() { static int c = 0; return c; } }; TEST_CASE("Object lifetime tests") { chaiscript::ModulePtr m = chaiscript::ModulePtr(new chaiscript::Module()); m->add(chaiscript::user_type(), "Object_Lifetime_Test"); m->add(chaiscript::constructor(), "Object_Lifetime_Test"); m->add(chaiscript::constructor(), "Object_Lifetime_Test"); m->add(chaiscript::fun(&Object_Lifetime_Test::count), "count"); chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(m); CHECK(chai.eval("count()") == 0); CHECK(chai.eval("auto i = 0; { auto t = Object_Lifetime_Test(); } return i;") == 0); CHECK(chai.eval("i = 0; { auto t = Object_Lifetime_Test(); i = count(); } return i;") == 1); CHECK(chai.eval("i = 0; { auto t = Object_Lifetime_Test(); { auto t2 = Object_Lifetime_Test(); i = count(); } } return i;") == 2); CHECK(chai.eval("i = 0; { auto t = Object_Lifetime_Test(); { auto t2 = Object_Lifetime_Test(); } i = count(); } return i;") == 1); CHECK(chai.eval("i = 0; { auto t = Object_Lifetime_Test(); { auto t2 = Object_Lifetime_Test(); } } i = count(); return i;") == 0); } //// Object lifetime tests 2 template struct Object_Lifetime_Vector2 { Object_Lifetime_Vector2() : x(0) , y(0) { } Object_Lifetime_Vector2(T px, T py) : x(px) , y(py) { } Object_Lifetime_Vector2(const Object_Lifetime_Vector2 &cp) noexcept : x(cp.x) , y(cp.y) { } Object_Lifetime_Vector2 &operator+=(const Object_Lifetime_Vector2 &vec_r) { x += vec_r.x; y += vec_r.y; return *this; } Object_Lifetime_Vector2 operator+(const Object_Lifetime_Vector2 &vec_r) { return Object_Lifetime_Vector2(*this += vec_r); } Object_Lifetime_Vector2 &operator=(const Object_Lifetime_Vector2 &ver_r) { x = ver_r.x; y = ver_r.y; return *this; } T x; T y; }; Object_Lifetime_Vector2 Object_Lifetime_Vector2_GetValue() { return Object_Lifetime_Vector2(10, 15); } TEST_CASE("Object lifetime test 2") { chaiscript::ChaiScript_Basic _script(create_chaiscript_stdlib(), create_chaiscript_parser()); // Registering stuff _script.add(chaiscript::user_type>(), "Object_Lifetime_Vector2f"); _script.add(chaiscript::constructor()>(), "Object_Lifetime_Vector2f"); _script.add(chaiscript::constructor(float, float)>(), "Object_Lifetime_Vector2f"); _script.add(chaiscript::constructor(const Object_Lifetime_Vector2 &)>(), "Object_Lifetime_Vector2f"); _script.add(chaiscript::fun(&Object_Lifetime_Vector2::x), "x"); _script.add(chaiscript::fun(&Object_Lifetime_Vector2::y), "y"); _script.add(chaiscript::fun(&Object_Lifetime_Vector2::operator+), "+"); _script.add(chaiscript::fun(&Object_Lifetime_Vector2::operator+=), "+="); _script.add(chaiscript::fun(&Object_Lifetime_Vector2::operator=), "="); _script.add(chaiscript::fun(&Object_Lifetime_Vector2_GetValue), "getValue"); _script.eval(R"( var test = 0.0 var test2 = Object_Lifetime_Vector2f(10,10) test = getValue().x print(test) print(test2.x) )"); CHECK(_script.eval("to_string(test)") == "10"); CHECK(_script.eval("to_string(test2.x)") == "10"); } ///// Non-polymorphic base class conversions class Non_Poly_Base { }; class Non_Poly_Derived : public Non_Poly_Base { }; int myfunction(Non_Poly_Base *) { return 2; } TEST_CASE("Test Derived->Base with non-polymorphic classes") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::base_class()); Non_Poly_Derived d; chai.add(chaiscript::var(&d), "d"); chai.add(chaiscript::fun(&myfunction), "myfunction"); CHECK(chai.eval("myfunction(d)") == 2); } struct TestCppVariableScope { void print() { std::cout << "Printed" << std::endl; } }; TEST_CASE("Variable Scope When Calling From C++") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::user_type(), "Test"); chai.add(chaiscript::constructor(), "Test"); chai.add(chaiscript::fun(&TestCppVariableScope::print), "print"); chai.eval(R"(var t := Test(); def func() { t.print(); } )"); CHECK_THROWS(chai.eval("func()")); chai.eval("dump_object(t)"); auto func = chai.eval>("func"); CHECK_THROWS(func()); } TEST_CASE("Variable Scope When Calling From C++ 2") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.eval("var obj = 2;"); auto func = chai.eval>("fun(){ return obj; }"); CHECK_THROWS(func()); } void ulonglong(unsigned long long i) { std::cout << i << '\n'; } void longlong(long long i) { std::cout << i << '\n'; } TEST_CASE("Test long long dispatch") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(&longlong), "longlong"); chai.add(chaiscript::fun(&ulonglong), "ulonglong"); chai.eval("longlong(15)"); chai.eval("ulonglong(15)"); } struct Returned_Converted_Config { int num_iterations; int something_else; std::string a_string; std::function a_function; }; TEST_CASE("Return of converted type from script") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::constructor(), "Returned_Converted_Config"); chai.add(chaiscript::fun(&Returned_Converted_Config::num_iterations), "num_iterations"); chai.add(chaiscript::fun(&Returned_Converted_Config::something_else), "something_else"); chai.add(chaiscript::fun(&Returned_Converted_Config::a_string), "a_string"); chai.add(chaiscript::fun(&Returned_Converted_Config::a_function), "a_function"); chai.add(chaiscript::vector_conversion>()); auto c = chai.eval>(R"( var c = Returned_Converted_Config(); c.num_iterations = 5; c.something_else = c.num_iterations * 2; c.a_string = "string"; c.a_function = fun(s) { s.size(); } print("making vector"); var v = []; print("adding config item"); v.push_back_ref(c); print("returning vector"); v; )"); std::cout << typeid(decltype(c)).name() << std::endl; std::cout << "Info: " << c.size() << " " << &c[0] << std::endl; std::cout << "num_iterations " << c[0].num_iterations << '\n' << "something_else " << c[0].something_else << '\n' << "a_string " << c[0].a_string << '\n' << "a_function " << c[0].a_function("bob") << '\n'; chai.add(chaiscript::user_type(), "Returned_Converted_Config"); } int get_value_a(const std::map &t_m) { return t_m.at("a"); } TEST_CASE("Map conversions") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::map_conversion>()); chai.add(chaiscript::fun(&get_value_a), "get_value_a"); const auto c = chai.eval(R"( var m = ["a": 42]; get_value_a(m); )"); CHECK(c == 42); } TEST_CASE("Pair conversions") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::pair_conversion()); chai.add(chaiscript::pair_conversion()); { const auto p = chai.eval>(R"cs( Pair("chai", "script"); )cs"); CHECK(p.first == std::string{"chai"}); CHECK(p.second == "script"); } { const auto p = chai.eval>(R"cs( Pair(5, 3.14); )cs"); CHECK(p.first == 5); CHECK(p.second == Approx(3.14)); } } TEST_CASE("Parse floats with non-posix locale") { #ifdef CHAISCRIPT_MSVC std::setlocale(LC_ALL, "en-ZA"); #else std::setlocale(LC_ALL, "en_ZA.utf8"); #endif chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); const double parsed = chai.eval("print(1.3); 1.3"); CHECK(parsed == Approx(1.3)); const std::string str = chai.eval("to_string(1.3)"); CHECK(str == "1.3"); } bool FindBitmap(int &ox, int &oy, long) { ox = 1; oy = 2; return true; } TEST_CASE("Mismatched numeric types only convert necessary params") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(&FindBitmap), "FindBitmap"); int x = 0; int y = 0; chai.add(chaiscript::var(&x), "x"); chai.add(chaiscript::var(&y), "y"); chai.eval("if ( FindBitmap ( x, y, 0) ) { print(\"found at \" + to_string(x) + \", \" + to_string(y))}"); CHECK(x == 1); CHECK(y == 2); } TEST_CASE("type_conversion to bool") { auto module = std::make_shared(); struct T { operator bool() const { return true; } }; module->add(chaiscript::type_conversion()); } TEST_CASE("Make sure ChaiScript object still compiles / executes") { chaiscript::ChaiScript chai; } struct Count_Tracer { int count = 0; template void trace(const chaiscript::detail::Dispatch_State &, const chaiscript::eval::AST_Node_Impl *) { ++count; } }; TEST_CASE("Test count tracer") { using Parser_Type = chaiscript::parser::ChaiScript_Parser, chaiscript::optimizer::Optimizer_Default>; chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library(), std::make_unique()); Parser_Type &parser = dynamic_cast(chai.get_parser()); const auto count = parser.get_tracer().count; chai.eval(""); CHECK(parser.get_tracer().count > count); } TEST_CASE("Test stdlib options") { const auto test_has_external_scripts = [](chaiscript::ChaiScript_Basic &chai) { CHECK_NOTHROW(chai.eval("`use`")); CHECK_NOTHROW(chai.eval("`eval_file`")); }; const auto test_no_external_scripts = [](chaiscript::ChaiScript_Basic &chai) { CHECK_THROWS(chai.eval("`use`")); CHECK_THROWS(chai.eval("`eval_file`")); }; const auto test_has_load_modules = [](chaiscript::ChaiScript_Basic &chai) { CHECK_NOTHROW(chai.eval("`load_module`")); }; const auto test_no_load_modules = [](chaiscript::ChaiScript_Basic &chai) { CHECK_THROWS(chai.eval("`load_module`")); }; SECTION("Defaults") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); test_has_external_scripts(chai); test_has_load_modules(chai); } SECTION("Load_Modules, External_Scripts") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser(), {}, {}, {chaiscript::Options::Load_Modules, chaiscript::Options::External_Scripts}); test_has_external_scripts(chai); test_has_load_modules(chai); } SECTION("No_Load_Modules, No_External_Scripts") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); test_no_external_scripts(chai); test_no_load_modules(chai); } SECTION("No_Load_Modules, Load_Modules") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::Load_Modules}); test_no_external_scripts(chai); test_no_load_modules(chai); } SECTION("No_External_Scripts, External_Scripts") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_External_Scripts, chaiscript::Options::External_Scripts}); test_no_external_scripts(chai); test_no_load_modules(chai); } SECTION("No_External_Scripts, Load_Modules") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_External_Scripts, chaiscript::Options::Load_Modules}); test_no_external_scripts(chai); test_has_load_modules(chai); } SECTION("External_Scripts, No_Load_Modules") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser(), {}, {}, {chaiscript::Options::External_Scripts, chaiscript::Options::No_Load_Modules}); test_has_external_scripts(chai); test_no_load_modules(chai); } } void uservalueref(int &&) {} void usemoveonlytype(std::unique_ptr &&) {} TEST_CASE("Pass r-value reference to func") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun(&uservalueref), "uservalueref"); chai.add(chaiscript::fun(&usemoveonlytype), "usemoveonlytype"); chai.add(chaiscript::var(std::make_unique(1)), "iptr"); chai.eval("usemoveonlytype(iptr)"); } TEST_CASE("Use unique_ptr") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::fun([](int &i) { ++i; }), "inci"); chai.add(chaiscript::fun([]([[maybe_unused]] int i) { ++i; }), "copyi"); chai.add(chaiscript::fun([](int *i) { ++(*i); }), "derefi"); chai.add(chaiscript::fun([](const std::unique_ptr &i) { ++(*i); }), "constrefuniqptri"); chai.add(chaiscript::fun([](std::unique_ptr &i) { ++(*i); }), "refuniqptri"); chai.add(chaiscript::fun([](std::unique_ptr &&i) { ++(*i); }), "rvaluniqptri"); chai.add(chaiscript::var(std::make_unique(1)), "iptr"); CHECK(chai.eval("iptr") == 1); chai.eval("inci(iptr)"); CHECK(chai.eval("iptr") == 2); chai.eval("copyi(iptr)"); CHECK(chai.eval("iptr") == 2); chai.eval("derefi(iptr)"); CHECK(chai.eval("iptr") == 3); chai.eval("constrefuniqptri(iptr)"); CHECK(chai.eval("iptr") == 4); chai.eval("refuniqptri(iptr)"); CHECK(chai.eval("iptr") == 5); chai.eval("rvaluniqptri(iptr)"); CHECK(chai.eval("iptr") == 6); } class Unique_Ptr_Test_Class { public: Unique_Ptr_Test_Class() = default; Unique_Ptr_Test_Class(const Unique_Ptr_Test_Class &) = default; Unique_Ptr_Test_Class(Unique_Ptr_Test_Class &&) = default; Unique_Ptr_Test_Class &operator=(const Unique_Ptr_Test_Class &) = default; Unique_Ptr_Test_Class &operator=(Unique_Ptr_Test_Class &&) = default; virtual ~Unique_Ptr_Test_Class() = default; int getI() const { return 5; } }; std::unique_ptr make_Unique_Ptr_Test_Class() { return std::make_unique(); } TEST_CASE("Call methods through unique_ptr") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::var(std::make_unique()), "uptr"); chai.add(chaiscript::fun(make_Unique_Ptr_Test_Class), "make_Unique_Ptr_Test_Class"); chai.add(chaiscript::fun(&Unique_Ptr_Test_Class::getI), "getI"); CHECK(chai.eval("uptr.getI()") == 5); CHECK(chai.eval("var uptr2 = make_Unique_Ptr_Test_Class(); uptr2.getI()") == 5); } class Unique_Ptr_Test_Base_Class { public: int getI() const { return 5; } }; class Unique_Ptr_Test_Derived_Class : public Unique_Ptr_Test_Base_Class { }; std::unique_ptr make_Unique_Ptr_Test_Derived_Class() { return std::make_unique(); } TEST_CASE("Call methods on base class through unique_ptr") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::var(std::make_unique()), "uptr"); chai.add(chaiscript::fun(make_Unique_Ptr_Test_Derived_Class), "make_Unique_Ptr_Test_Derived_Class"); chai.add(chaiscript::fun(&Unique_Ptr_Test_Base_Class::getI), "getI"); chai.add(chaiscript::base_class()); CHECK(chai.eval("uptr.getI()") == 5); CHECK(chai.eval("var uptr2 = make_Unique_Ptr_Test_Derived_Class(); uptr2.getI()") == 5); } class A { public: A() = default; A(const A &) = default; A(A &&) = default; A &operator=(const A &) = default; A &operator=(A &&) = default; virtual ~A() = default; }; class B : public A { public: B() = default; }; TEST_CASE("Test typed chaiscript functions to perform conversions") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); //------------------------------------------------------------------------- chai.add(chaiscript::user_type(), "A"); chai.add(chaiscript::user_type(), "B"); chai.add(chaiscript::base_class()); chai.add(chaiscript::fun([](const B &) {}), "CppFunctWithBArg"); chai.add(chaiscript::fun([]() -> std::shared_ptr { return (std::shared_ptr(new B())); }), "Create"); chai.eval(R"( var inst = Create() // A* // it prints "A" inst.type_name().print() // Ok it is casted using conversion CppFunctWithBArg(inst) // Define a function with B as argument def ChaiFuncWithBArg(B inst) { print("ok") } // don't work ChaiFuncWithBArg(inst) )"); } struct Reference_MyClass { Reference_MyClass(double &t_x) : x(t_x) { } double &x; }; TEST_CASE("Test reference member being registered") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // Note, C++ will not allow us to do this: // chai.add(chaiscript::fun(&Reference_MyClass::x) , "x"); chai.add(chaiscript::fun([](Reference_MyClass &r) -> decltype(auto) { return (r.x); }), "x"); chai.add(chaiscript::fun([](const Reference_MyClass &r) -> decltype(auto) { return (r.x); }), "x"); double d; chai.add(chaiscript::var(Reference_MyClass(d)), "ref"); chai.eval("ref.x = 2.3"); CHECK(d == Approx(2.3)); } // starting with C++20 u8"" strings cannot be compared with std::string // and the support for std::u8strings is still terrible. TEST_CASE("Test unicode matches C++") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); CHECK("\U000000AC" == chai.eval(R"("\U000000AC")")); CHECK("\xF0\x9F\x8D\x8C" == chai.eval(R"("\xF0\x9F\x8D\x8C")")); CHECK("\U0001F34C" == chai.eval(R"("\U0001F34C")")); CHECK("\u2022" == chai.eval(R"("\u2022")")); } const int add_3(const int &i) { return i + 3; } TEST_CASE("Test returning by const non-reference") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // Note, C++ will not allow us to do this: // chai.add(chaiscript::fun(&Reference_MyClass::x) , "x"); chai.add(chaiscript::fun(&add_3), "add_3"); auto v = chai.eval("add_3(12)"); CHECK(v == 15); } struct MyException : std::runtime_error { using std::runtime_error::runtime_error; int value = 5; }; void throws_a_thing() { throw MyException("Hello World"); } TEST_CASE("Test throwing and catching custom exception") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::user_type(), "MyException"); chai.add(chaiscript::base_class()); // be sure to register base class relationship chai.add(chaiscript::fun(&throws_a_thing), "throws_a_thing"); chai.add(chaiscript::fun(&MyException::value), "value"); const auto s = chai.eval("fun(){ try { throws_a_thing(); } catch (MyException ex) { return ex.what(); } }()"); CHECK(s == "Hello World"); // this has an explicit clone to prevent returning a pointer to the `value` from inside of MyException const auto i = chai.eval("fun(){ try { throws_a_thing(); } catch (MyException ex) { var v = clone(ex.value); print(v); return v; } }()"); CHECK(i == 5); } TEST_CASE("Test ability to get 'use' function from default construction") { chaiscript::ChaiScript chai; const auto use_function = chai.eval>("use"); } TEST_CASE("Throw an exception when trying to add same conversion twice") { struct my_int { int value; my_int(int val) : value(val) { } }; chaiscript::ChaiScript chai; chai.add(chaiscript::type_conversion([](int x) { std::cout << "My_int type conversion 1\n"; return my_int(x); })); CHECK_THROWS_AS(chai.add(chaiscript::type_conversion([](int x) { std::cout << "My_int type conversion 2\n"; return my_int(x); })), chaiscript::exception::conversion_error); } TEST_CASE("Test if non copyable/movable types can be registered") { struct Noncopyable { Noncopyable() { str = "test"; } Noncopyable(const Noncopyable &) = delete; Noncopyable &operator=(const Noncopyable &) = delete; std::string str; }; struct Nonmovable { Nonmovable() { str = "test"; } Nonmovable(Nonmovable &&) = delete; Nonmovable &operator=(Nonmovable &&) = delete; std::string str; }; struct Nothing { Nothing() { str = "test"; } Nothing(Nothing &&) = delete; Nothing &operator=(Nothing &&) = delete; Nothing(const Nothing &) = delete; Nothing &operator=(const Nothing &) = delete; std::string str; }; chaiscript::ChaiScript chai; chai.add(chaiscript::user_type(), "Noncopyable"); chai.add(chaiscript::constructor(), "Noncopyable"); chai.add(chaiscript::user_type(), "Nonmovable"); chai.add(chaiscript::constructor(), "Nonmovable"); chai.add(chaiscript::user_type(), "Nothing"); chai.add(chaiscript::constructor(), "Nothing"); } // Tests for issue #146: configuration to bypass registering built-in functions // Tests through ChaiScript_Basic (library options passed explicitly to Std_Lib::library) TEST_CASE("ChaiScript_Basic No_Stdlib option disables all standard library functions") { chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Stdlib}), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); CHECK_NOTHROW(chai.eval("var x = 5")); CHECK_THROWS(chai.eval("print(\"hello\")")); CHECK_THROWS(chai.eval("var v = Vector()")); CHECK_THROWS(chai.eval("\"hello\".trim()")); CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); } TEST_CASE("ChaiScript_Basic No_IO option uses null handler by default") { chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_IO}), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, true); // print_string and println_string should still be available via the handler mechanism // but the default handler is a no-op (no stdout output) CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); CHECK_NOTHROW(chai.eval("println_string(\"hello\")")); CHECK(chai.eval("5 + 3") == 8); CHECK_NOTHROW(chai.eval("var v = Vector()")); // Users can set their own print handler even with No_IO std::string captured; chai.set_print_handler([&captured](const std::string &s) { captured += s; }); chai.eval("print_string(\"redirected\")"); CHECK(captured == "redirected"); } TEST_CASE("ChaiScript_Basic No_Prelude option disables prelude functions") { chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Prelude}), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); CHECK_THROWS(chai.eval("print(\"hello\")")); CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })")); CHECK(chai.eval("5 + 3") == 8); } TEST_CASE("ChaiScript_Basic No_JSON option disables JSON support") { chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_JSON}), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); CHECK(chai.eval("5 + 3") == 8); CHECK_NOTHROW(chai.eval("print(\"hello\")")); } TEST_CASE("ChaiScript_Basic default library has all functions") { chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library(), create_chaiscript_parser(), {}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); CHECK_NOTHROW(chai.eval("print(\"hello\")")); CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); CHECK_NOTHROW(chai.eval("var v = Vector()")); CHECK(chai.eval("5 + 3") == 8); } // Tests through ChaiScript (library options passed as constructor parameter) TEST_CASE("ChaiScript No_Stdlib option via library options parameter") { chaiscript::ChaiScript chai({}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, {chaiscript::Library_Options::No_Stdlib}); CHECK_NOTHROW(chai.eval("var x = 5")); CHECK_THROWS(chai.eval("print(\"hello\")")); CHECK_THROWS(chai.eval("var v = Vector()")); CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); } TEST_CASE("ChaiScript No_IO option via library options parameter") { chaiscript::ChaiScript chai({}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, {chaiscript::Library_Options::No_IO}); // print_string and println_string remain available via the handler mechanism // but the default handler is a no-op (no stdout output) CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); CHECK_NOTHROW(chai.eval("println_string(\"hello\")")); CHECK(chai.eval("5 + 3") == 8); CHECK_NOTHROW(chai.eval("var v = Vector()")); // Users can override the null handler with their own std::string captured; chai.set_print_handler([&captured](const std::string &s) { captured += s; }); chai.eval("print_string(\"redirected\")"); CHECK(captured == "redirected"); } TEST_CASE("ChaiScript No_Prelude option via library options parameter") { chaiscript::ChaiScript chai({}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, {chaiscript::Library_Options::No_Prelude}); CHECK_THROWS(chai.eval("print(\"hello\")")); CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })")); CHECK(chai.eval("5 + 3") == 8); } TEST_CASE("ChaiScript No_JSON option via library options parameter") { chaiscript::ChaiScript chai({}, {}, {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, {chaiscript::Library_Options::No_JSON}); CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); CHECK(chai.eval("5 + 3") == 8); CHECK_NOTHROW(chai.eval("print(\"hello\")")); } TEST_CASE("ChaiScript default has all functions") { chaiscript::ChaiScript chai; CHECK_NOTHROW(chai.eval("print(\"hello\")")); CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); CHECK_NOTHROW(chai.eval("var v = Vector()")); CHECK(chai.eval("5 + 3") == 8); } // Issue #421: Class with type_conversion from int and "==" operator // causes switch statement to compare destroyed objects. // The switch case comparison must use Function_Push_Pop to properly // manage the lifetime of temporaries created by type conversions. TEST_CASE("Issue #421 - Switch with type_conversion does not compare destroyed objects") { struct MyType { int value; explicit MyType(int v) : value(v) {} }; chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.add(chaiscript::user_type(), "MyType"); chai.add(chaiscript::constructor(), "MyType"); chai.add(chaiscript::fun(&MyType::value), "value"); chai.add(chaiscript::fun([](const MyType &a, const MyType &b) { return a.value == b.value; }), "=="); chai.add(chaiscript::type_conversion([](const int &i) { return MyType(i); })); // Test switch with type conversion - the case integer literals must be // converted to MyType via the registered conversion before comparison. // Without Function_Push_Pop, the converted temporaries may be destroyed // before the == operator can compare them. CHECK(chai.eval(R"({ var result = 0 var obj = MyType(2) switch(obj) { case (1) { result = 1 break } case (2) { result = 2 break } case (3) { result = 3 break } } result })") == 2); // Test fall-through with type conversion CHECK(chai.eval(R"({ var total = 0 var obj = MyType(2) switch(obj) { case (1) { total += 1 } case (2) { total += 2 } case (3) { total += 4 } } total })") == 6); // Test matching the first case CHECK(chai.eval(R"({ var result = 0 var obj = MyType(1) switch(obj) { case (1) { result = 10 break } case (2) { result = 20 break } } result })") == 10); // Test no match CHECK(chai.eval(R"({ var result = 0 var obj = MyType(5) switch(obj) { case (1) { result = 1 break } case (2) { result = 2 break } } result })") == 0); } // Issue #524: A std::vector of std::unique_ptrs can't be added // vector_type should compile with non-copyable value types by // skipping copy-dependent operations via if constexpr. struct Issue524_Foo { int value = 42; }; TEST_CASE("Issue #524 - vector of unique_ptr can be registered") { using VecType = std::vector>; chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // This should compile and not throw - previously failed to compile // because vector_type tried to instantiate copy constructor, assignment, // push_back(const T&), insert_at(const T&), and resize(n, const T&) // for the non-copyable std::unique_ptr. chaiscript::ModulePtr m = std::make_shared(); chaiscript::bootstrap::standard_library::vector_type("UniqueVec", *m); CHECK_NOTHROW(chai.add(m)); // Verify basic operations still work CHECK(chai.eval("var v = UniqueVec(); v.size()") == 0); CHECK(chai.eval("var v2 = UniqueVec(); v2.empty()") == true); } // Issue #625: function_less_than comparator must satisfy strict-weak ordering. // Registering overloaded functions with different arities triggered a // std::stable_sort assertion on macOS 15.2 (hardened libc++) because the // comparator violated transitivity of equivalence. TEST_CASE("Issue 625: function_less_than strict-weak ordering with different arities") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // Register overloaded functions with varying arities under the same name. // If the comparator doesn't order by arity when overlapping params match, // std::stable_sort may exhibit undefined behavior. CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, const chaiscript::Boxed_Value &) { return x; }), "overloaded")); CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, double y) { return x + static_cast(y); }), "overloaded")); CHECK_NOTHROW(chai.add(chaiscript::fun([](int x) { return x; }), "overloaded")); // Verify dispatch still works correctly CHECK(chai.eval("overloaded(5)") == 5); CHECK(chai.eval("overloaded(3, 2.0)") == 5); } TEST_CASE("IO redirection with set_print_handler") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); std::string captured_output; // Set custom print handler — both print_string and println_string dispatch through it chai.set_print_handler([&captured_output](const std::string &s) { captured_output += s; }); // Test that puts() uses the custom handler captured_output.clear(); chai.eval("puts(\"hello\")"); CHECK(captured_output == "hello"); // Test that print() uses the custom handler (println_string appends newline before calling handler) captured_output.clear(); chai.eval("print(\"world\")"); CHECK(captured_output == "world\n"); // Test that print_string() directly uses the custom handler captured_output.clear(); chai.eval("print_string(\"direct\")"); CHECK(captured_output == "direct"); // Test that println_string() directly uses the custom handler with newline captured_output.clear(); chai.eval("println_string(\"direct_ln\")"); CHECK(captured_output == "direct_ln\n"); } TEST_CASE("IO redirection captures numeric output") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); std::string captured_output; chai.set_print_handler([&captured_output](const std::string &s) { captured_output += s; }); chai.eval("print(42)"); CHECK(captured_output == "42\n"); } TEST_CASE("IO redirection different instances are independent") { chaiscript::ChaiScript_Basic chai1(create_chaiscript_stdlib(), create_chaiscript_parser()); chaiscript::ChaiScript_Basic chai2(create_chaiscript_stdlib(), create_chaiscript_parser()); std::string output1; std::string output2; chai1.set_print_handler([&output1](const std::string &s) { output1 += s; }); chai2.set_print_handler([&output2](const std::string &s) { output2 += s; }); chai1.eval("print(\"from1\")"); chai2.eval("print(\"from2\")"); CHECK(output1 == "from1\n"); CHECK(output2 == "from2\n"); } TEST_CASE("set_print_handler accessible from ChaiScript") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); auto captured = std::make_shared(); chai.add(chaiscript::fun([captured](const std::string &s) { *captured += s; }), "test_output_sink"); // Set the print handler from within ChaiScript chai.eval("set_print_handler(fun(s) { test_output_sink(s) })"); chai.eval("print(\"from_script\")"); CHECK(*captured == "from_script\n"); captured->clear(); chai.eval("puts(\"no_newline\")"); CHECK(*captured == "no_newline"); } // Regression test: push_back() on script-created vector has no effect when // vector_conversion is in effect. The bug occurs because dispatch selects // the C++ push_back for the converted type over the built-in one, operating // on a temporary copy of the vector. TEST_CASE("push_back on script vector with vector_conversion") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); auto m = std::make_shared(); chaiscript::bootstrap::standard_library::vector_type>("VectorString", *m); m->add(chaiscript::vector_conversion>()); chai.add(m); // Register a C++ function that accepts the converted type, so we can // verify that vector_conversion actually works for passing vectors chai.add(chaiscript::fun([](const std::vector &v) -> std::string { std::string result; for (const auto &s : v) { if (!result.empty()) { result += ","; } result += s; } return result; }), "join_strings"); // push_back on an empty script-created vector must be visible CHECK(chai.eval( "auto x = [];" "x.push_back(\"Hello\");" "x.size() == 1")); // push_back on a vector with initial elements must grow correctly CHECK(chai.eval( "auto y = [\"a\", \"b\"];" "y.push_back(\"c\");" "y.push_back(\"d\");" "y.size() == 4")); // Verify the actual content is preserved after push_back CHECK(chai.eval( "auto z = [];" "z.push_back(\"World\");" "z[0]") == "World"); // Round-trip: build a vector in script, push_back elements, then pass it // to a C++ function via vector_conversion and verify the contents CHECK(chai.eval( "auto v = [\"one\", \"two\"];" "v.push_back(\"three\");" "join_strings(v)") == "one,two,three"); // Verify conversion works on a freshly created vector too CHECK(chai.eval( "auto w = [];" "w.push_back(\"hello\");" "w.push_back(\"world\");" "join_strings(w)") == "hello,world"); } // Regression test for issue #607: AST_Node_Trace must be a complete type // when used in eval_error's std::vector call_stack member. // This failed to compile with C++20 on clang/libc++ when AST_Node_Trace // was only forward-declared before eval_error's definition. TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); // Trigger an eval_error by calling a non-existent function try { chai.eval("nonexistent_function()"); REQUIRE(false); } catch (const chaiscript::exception::eval_error &e) { // Verify that eval_error's call_stack member (std::vector) // is usable - this would fail to compile if AST_Node_Trace were incomplete const auto &stack = e.call_stack; CHECK(e.pretty_print().size() > 0); (void)stack; } } TEST_CASE("Test set_file_reader from C++ land") { chaiscript::ChaiScript chai; chai.set_file_reader([](const std::string &) { return std::string("var file_reader_test_val = 42"); }); chai.eval_file("nonexistent_file.chai"); CHECK(chai.eval("file_reader_test_val") == 42); } TEST_CASE("Test set_file_reader from ChaiScript land") { chaiscript::ChaiScript chai; chai.set_file_reader([](const std::string &) { return std::string("var from_custom_reader = true"); }); chai.eval("set_file_reader(fun(filename) { return \"var from_chai_reader = true\"; })"); chai.eval_file("any_file.chai"); CHECK(chai.eval("from_chai_reader") == true); } TEST_CASE("Test set_file_reader receives correct filename") { chaiscript::ChaiScript chai; std::string captured_filename; chai.set_file_reader([&captured_filename](const std::string &t_filename) { captured_filename = t_filename; return std::string("var dummy = 1"); }); chai.eval_file("my_special_file.chai"); CHECK(captured_filename == "my_special_file.chai"); } TEST_CASE("Test use with set_file_reader") { chaiscript::ChaiScript chai; chai.set_file_reader([](const std::string &) { return std::string("var use_reader_val = 99"); }); chai.use("virtual_file.chai"); CHECK(chai.eval("use_reader_val") == 99); } TEST_CASE("Nested namespaces via register_namespace with :: separator") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.register_namespace( [](chaiscript::Namespace &si) { si["mu_B"] = chaiscript::const_var(9.274); }, "constants::si"); chai.register_namespace( [](chaiscript::Namespace &mm) { mm["mu_B"] = chaiscript::const_var(0.05788); }, "constants::mm"); chai.import("constants"); CHECK(chai.eval("constants.si.mu_B") == Approx(9.274)); CHECK(chai.eval("constants.mm.mu_B") == Approx(0.05788)); // Scope resolution via :: works the same as . for access CHECK(chai.eval("constants::si::mu_B") == Approx(9.274)); CHECK(chai.eval("constants::mm::mu_B") == Approx(0.05788)); } TEST_CASE("Deeply nested namespaces via register_namespace") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.register_namespace( [](chaiscript::Namespace &leaf) { leaf["val"] = chaiscript::const_var(42); }, "a::b::c"); chai.import("a"); CHECK(chai.eval("a.b.c.val") == 42); CHECK(chai.eval("a::b::c::val") == 42); } TEST_CASE("Block namespace declaration with ::") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.eval(R"( namespace math { def square(x) { x * x } } )"); CHECK(chai.eval("math::square(5)") == 25); CHECK(chai.eval("math.square(5)") == 25); } TEST_CASE("Nested block namespace declaration") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.eval(R"( namespace physics::constants { def speed_of_light() { return 299792458 } } )"); CHECK(chai.eval("physics::constants::speed_of_light()") == 299792458); } TEST_CASE("Namespace block reopening") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.eval(R"( namespace ns { def foo() { return 1 } } namespace ns { def bar() { return 2 } } )"); CHECK(chai.eval("ns::foo()") == 1); CHECK(chai.eval("ns::bar()") == 2); } TEST_CASE("Namespace block with var declarations") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); chai.eval(R"( namespace config { var pi = 3.14 var name = "hello" } )"); CHECK(chai.eval("config::pi") == Approx(3.14)); CHECK(chai.eval("config::name") == "hello"); } TEST_CASE("Namespace block rejects non-declaration statements") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); CHECK_THROWS_AS(chai.eval(R"( namespace bad { 1 + 2 } )"), chaiscript::exception::eval_error); CHECK_THROWS_AS(chai.eval(R"( namespace bad { print("hello") } )"), chaiscript::exception::eval_error); CHECK_THROWS_AS(chai.eval(R"( var x = 5 namespace bad { x = 10 } )"), chaiscript::exception::eval_error); } 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(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(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(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(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(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("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(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("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"); chai.add(chaiscript::constructor(), "ThrowingType"); chai.add(chaiscript::fun([](const ThrowingType &, const ThrowingType &) -> ThrowingType { throw std::runtime_error("cpp operator+ threw"); }), "+"); CHECK(chai.eval(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"); chai.add(chaiscript::constructor(), "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("var obj = IndexableType(0); obj[5]") == 5); CHECK(chai.eval(R"( var caught = false try { var x = obj[-1] } catch(e) { caught = true } caught )") == true); }