ChaiScript/unittests/compiled_tests.cpp

2236 lines
75 KiB
C++

// 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 <chaiscript/chaiscript.hpp>
#include <chaiscript/chaiscript_basic.hpp>
#include <chaiscript/dispatchkit/bootstrap_stl.hpp>
#include <chaiscript/utility/utility.hpp>
#include "../static_libs/chaiscript_parser.hpp"
#include "../static_libs/chaiscript_stdlib.hpp"
#define CATCH_CONFIG_MAIN
#include <clocale>
#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<std::string()>([] { return "world"; })), "f2");
CHECK(chai.eval<std::string>("f1()") == "hello");
CHECK(chai.eval<std::string>("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<bool()> chai_function;
CHECK_NOTHROW(chai_function = chai.eval<std::function<bool()>>("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<bool>("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<chaiscript::dispatch::Dynamic_Object &>("x");
CHECK(mydo.get_type_name() == "bob");
CHECK(chaiscript::boxed_cast<int>(mydo.get_attr("z")) == 10);
chai("x.z = 15");
CHECK(chaiscript::boxed_cast<int>(mydo.get_attr("z")) == 15);
int &z = chaiscript::boxed_cast<int &>(mydo.get_attr("z"));
CHECK(z == 15);
z = 20;
CHECK(z == 20);
CHECK(chaiscript::boxed_cast<int>(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<void()> f = chai.eval<std::function<void()>>("func");
f();
CHECK(chai.eval<std::function<std::string(int)>>("to_string")(6) == "6");
CHECK(chai.eval<std::function<std::string(const chaiscript::Boxed_Value &)>>("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<unsigned int(unsigned long)> &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<const std::exception &>(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<int>());
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<int, double>());
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<int *>("get_val_ptr()") == &val);
CHECK(*chai.eval<int *>("get_val_ptr()") == val);
CHECK(chai.eval<int const *>("get_val_const_ptr()") == &val);
CHECK(*chai.eval<int const *>("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<int *>("get_val_ptr_ref()") == &val);
CHECK(*chai.eval<int *>("get_val_ptr_ref()") == val);
CHECK(chai.eval<int *>("get_val_ptr_const_ref()") == &val);
CHECK(*chai.eval<int *>("get_val_ptr_const_ref()") == val);
// CHECK(chai.eval<int const *>("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<int, double, float, const std::string &, const std::exception &>());
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<int, double, float, const std::exception &>());
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 <chaiscript/utility/utility.hpp>
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<int>("test_fun(1)") == 1);
CHECK(chai.eval<int>("auto i = 1; test_fun(i)") == 2);
CHECK(chai.eval<int>("test_fun(\"bob\")") == 3);
CHECK(chai.eval<int>("test_fun(\"hi\")") == 4);
}
int functor_cast_test_call(const std::function<int(int)> &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<int>("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<std::string>("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<std::string>("\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<std::string>("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<std::string>("\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<std::string, chaiscript::Boxed_Value> 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<int>("myfun()") == 2);
CHECK(chai.eval<int>("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<int>("myfun()"), chaiscript::exception::eval_error);
// set state should not affect the local variables
CHECK(chai.eval<int>("i") == 1);
// After resetting the locals we expect the 'i' to be gone
chai.set_locals(locals);
CHECK_THROWS_AS(chai.eval<int>("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<int>("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<int>(objects["x"]) == 42);
CHECK(chaiscript::boxed_cast<std::string>(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<Short_Comparison_Test>(), "Test");
chai.add(chaiscript::constructor<Short_Comparison_Test()>(), "Test");
chai.add(chaiscript::fun(&Short_Comparison_Test::get_value), "get_value");
chai.eval("auto &t = Test();");
CHECK(chai.eval<bool>("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<Type_Name_MyClass>();
chai.add(type, "MyClass");
CHECK(chai.get_type_name(type) == "MyClass");
CHECK(chai.get_type_name<Type_Name_MyClass>() == "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<int>("do_something(" + std::to_string(i) + ")") == i + 2);
CHECK(chai2.eval<int>("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<Utility_Test>(
*m,
"Utility_Test",
{constructor<Utility_Test()>(), constructor<Utility_Test(const Utility_Test &)>()},
{{fun(&Utility_Test::function), "function"},
{fun(&Utility_Test::function2), "function2"},
{fun(&Utility_Test::function3), "function3"},
{fun(static_cast<std::string (Utility_Test::*)(double)>(&Utility_Test::functionOverload)), "functionOverload"},
{fun(static_cast<std::string (Utility_Test::*)(int)>(&Utility_Test::functionOverload)), "functionOverload"},
{fun(static_cast<Utility_Test &(Utility_Test::*)(const Utility_Test &)>(&Utility_Test::operator=)), "="}
});
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(m);
CHECK(chai.eval<std::string>("auto t = Utility_Test(); t.function2(); ") == "Function2");
CHECK(chai.eval<std::string>("auto t2 = Utility_Test(); t2.functionOverload(1); ") == "int");
CHECK(chai.eval<std::string>("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<Utility_Test_Numbers> &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<Utility_Test_Numbers>(*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<Utility_Test_Numbers>("ONE ") == 0);
CHECK(chai.eval<Utility_Test_Numbers>("TWO ") == 1);
CHECK(chai.eval<Utility_Test_Numbers>("THREE ") == 2);
CHECK(chai.eval<bool>("ONE == 0"));
chai.add(chaiscript::fun(&do_something_with_enum_vector), "do_something_with_enum_vector");
chai.add(chaiscript::vector_conversion<std::vector<Utility_Test_Numbers>>());
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<std::vector<Utility_Test_Numbers>>("a");
CHECK(v.size() == 3);
CHECK(v.at(1) == TWO);
CHECK(chai.eval<bool>("ONE == ONE"));
CHECK(chai.eval<bool>("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<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
struct Object_Copy_Count_Test_State {
int ctor;
int copy_ctor;
int move_ctor;
int copy;
int move;
int dtor;
};
bool operator==(const Object_Copy_Count_Test_State &s1, const Object_Copy_Count_Test_State &s2) {
return std::make_tuple(s1.ctor, s1.copy_ctor, s1.move_ctor, s1.copy, s1.move, s1.dtor) == std::make_tuple(s2.ctor, s2.copy_ctor, s2.move_ctor, s2.copy, s2.move, s2.dtor);
}
std::ostream &operator<<(std::ostream &o, const Object_Copy_Count_Test_State &state) {
o << "ctor: " << state.ctor << " copy_ctor: " << state.copy_ctor << " move_ctor: " << state.move_ctor << " copy: " << state.copy << " move: " << state.move << " dtor: " << state.dtor << "\n";
return o;
}
struct Object_Copy_Count_Test {
Object_Copy_Count_Test() { state.ctor++; }
~Object_Copy_Count_Test() {
state.dtor++;
}
Object_Copy_Count_Test(Object_Copy_Count_Test &&other) {
std::swap(state, other.state);
state.move_ctor++;
}
Object_Copy_Count_Test &operator=(Object_Copy_Count_Test &&other) {
std::swap(state, other.state);
state.move++;
return *this;
}
Object_Copy_Count_Test(const Object_Copy_Count_Test &other) {
state = other.state;
state.copy_ctor++;
}
Object_Copy_Count_Test &operator=(const Object_Copy_Count_Test &other) {
state = other.state;
state.copy++;
return *this;
}
static Object_Copy_Count_Test_State state;
};
Object_Copy_Count_Test_State Object_Copy_Count_Test::state = {};
TEST_CASE("Test if objects are only copied if necessary") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chaiscript::utility::add_class<Object_Copy_Count_Test>(chai,
"Object_Copy_Count_Test",
{chaiscript::constructor<Object_Copy_Count_Test()>()},
{});
Object_Copy_Count_Test::state = {};
{
Object_Copy_Count_Test_State state{};
chai.eval("{auto obj = Object_Copy_Count_Test();}");
state.ctor = 1;
state.dtor = 1;
CHECK(state == Object_Copy_Count_Test::state);
}
}
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>(), "Object_Copy_Count_Test");
m->add(chaiscript::constructor<Object_Copy_Count_Test()>(), "Object_Copy_Count_Test");
m->add(chaiscript::constructor<Object_Copy_Count_Test(const Object_Copy_Count_Test &)>(), "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);
Object_Copy_Count_Test::state = {};
chai.eval(" { auto i = create(); } ");
{
Object_Copy_Count_Test_State state{};
state.ctor = 1;
state.move_ctor = 1;
state.dtor = 2;
CHECK(state == Object_Copy_Count_Test::state);
}
}
///////////////////// 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>(), "Object_Lifetime_Test");
m->add(chaiscript::constructor<Object_Lifetime_Test()>(), "Object_Lifetime_Test");
m->add(chaiscript::constructor<Object_Lifetime_Test(const Object_Lifetime_Test &)>(), "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<int>("count()") == 0);
CHECK(chai.eval<int>("auto i = 0; { auto t = Object_Lifetime_Test(); } return i;") == 0);
CHECK(chai.eval<int>("i = 0; { auto t = Object_Lifetime_Test(); i = count(); } return i;") == 1);
CHECK(chai.eval<int>("i = 0; { auto t = Object_Lifetime_Test(); { auto t2 = Object_Lifetime_Test(); i = count(); } } return i;") == 2);
CHECK(chai.eval<int>("i = 0; { auto t = Object_Lifetime_Test(); { auto t2 = Object_Lifetime_Test(); } i = count(); } return i;") == 1);
CHECK(chai.eval<int>("i = 0; { auto t = Object_Lifetime_Test(); { auto t2 = Object_Lifetime_Test(); } } i = count(); return i;") == 0);
}
//// Object lifetime tests 2
template<typename T>
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<float> Object_Lifetime_Vector2_GetValue() {
return Object_Lifetime_Vector2<float>(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_Vector2<float>>(), "Object_Lifetime_Vector2f");
_script.add(chaiscript::constructor<Object_Lifetime_Vector2<float>()>(), "Object_Lifetime_Vector2f");
_script.add(chaiscript::constructor<Object_Lifetime_Vector2<float>(float, float)>(), "Object_Lifetime_Vector2f");
_script.add(chaiscript::constructor<Object_Lifetime_Vector2<float>(const Object_Lifetime_Vector2<float> &)>(), "Object_Lifetime_Vector2f");
_script.add(chaiscript::fun(&Object_Lifetime_Vector2<float>::x), "x");
_script.add(chaiscript::fun(&Object_Lifetime_Vector2<float>::y), "y");
_script.add(chaiscript::fun(&Object_Lifetime_Vector2<float>::operator+), "+");
_script.add(chaiscript::fun(&Object_Lifetime_Vector2<float>::operator+=), "+=");
_script.add(chaiscript::fun(&Object_Lifetime_Vector2<float>::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<std::string>("to_string(test)") == "10");
CHECK(_script.eval<std::string>("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_Base, Non_Poly_Derived>());
Non_Poly_Derived d;
chai.add(chaiscript::var(&d), "d");
chai.add(chaiscript::fun(&myfunction), "myfunction");
CHECK(chai.eval<int>("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<TestCppVariableScope>(), "Test");
chai.add(chaiscript::constructor<TestCppVariableScope()>(), "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<std::function<void()>>("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<std::function<void()>>("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<int(const std::string &)> 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()>(), "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<std::vector<Returned_Converted_Config>>());
auto c = chai.eval<std::vector<Returned_Converted_Config>>(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>(), "Returned_Converted_Config");
}
int get_value_a(const std::map<std::string, int> &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<std::map<std::string, int>>());
chai.add(chaiscript::fun(&get_value_a), "get_value_a");
const auto c = chai.eval<int>(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<std::string, std::string>());
chai.add(chaiscript::pair_conversion<int, double>());
{
const auto p = chai.eval<std::pair<std::string, std::string>>(R"cs(
Pair("chai", "script");
)cs");
CHECK(p.first == std::string{"chai"});
CHECK(p.second == "script");
}
{
const auto p = chai.eval<std::pair<int, double>>(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<double>("print(1.3); 1.3");
CHECK(parsed == Approx(1.3));
const std::string str = chai.eval<std::string>("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<chaiscript::Module>();
struct T {
operator bool() const { return true; }
};
module->add(chaiscript::type_conversion<T, bool>());
}
TEST_CASE("Make sure ChaiScript object still compiles / executes") {
chaiscript::ChaiScript chai;
}
struct Count_Tracer {
int count = 0;
template<typename T>
void trace(const chaiscript::detail::Dispatch_State &, const chaiscript::eval::AST_Node_Impl<T> *) {
++count;
}
};
TEST_CASE("Test count tracer") {
using Parser_Type = chaiscript::parser::ChaiScript_Parser<chaiscript::eval::Tracer<Count_Tracer>, chaiscript::optimizer::Optimizer_Default>;
chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library(), std::make_unique<Parser_Type>());
Parser_Type &parser = dynamic_cast<Parser_Type &>(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<int> &&) {}
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<int>(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<int> &i) { ++(*i); }), "constrefuniqptri");
chai.add(chaiscript::fun([](std::unique_ptr<int> &i) { ++(*i); }), "refuniqptri");
chai.add(chaiscript::fun([](std::unique_ptr<int> &&i) { ++(*i); }), "rvaluniqptri");
chai.add(chaiscript::var(std::make_unique<int>(1)), "iptr");
CHECK(chai.eval<int>("iptr") == 1);
chai.eval("inci(iptr)");
CHECK(chai.eval<int>("iptr") == 2);
chai.eval("copyi(iptr)");
CHECK(chai.eval<int>("iptr") == 2);
chai.eval("derefi(iptr)");
CHECK(chai.eval<int>("iptr") == 3);
chai.eval("constrefuniqptri(iptr)");
CHECK(chai.eval<int>("iptr") == 4);
chai.eval("refuniqptri(iptr)");
CHECK(chai.eval<int>("iptr") == 5);
chai.eval("rvaluniqptri(iptr)");
CHECK(chai.eval<int>("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<Unique_Ptr_Test_Class> make_Unique_Ptr_Test_Class() {
return std::make_unique<Unique_Ptr_Test_Class>();
}
TEST_CASE("Call methods through unique_ptr") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::var(std::make_unique<Unique_Ptr_Test_Class>()), "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<int>("uptr.getI()") == 5);
CHECK(chai.eval<int>("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<Unique_Ptr_Test_Derived_Class> make_Unique_Ptr_Test_Derived_Class() {
return std::make_unique<Unique_Ptr_Test_Derived_Class>();
}
TEST_CASE("Call methods on base class through unique_ptr<derived>") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::var(std::make_unique<Unique_Ptr_Test_Derived_Class>()), "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<Unique_Ptr_Test_Base_Class, Unique_Ptr_Test_Derived_Class>());
CHECK(chai.eval<int>("uptr.getI()") == 5);
CHECK(chai.eval<int>("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>(), "A");
chai.add(chaiscript::user_type<B>(), "B");
chai.add(chaiscript::base_class<A, B>());
chai.add(chaiscript::fun([](const B &) {}), "CppFunctWithBArg");
chai.add(chaiscript::fun([]() -> std::shared_ptr<A> { return (std::shared_ptr<A>(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<std::string>(R"("\U000000AC")"));
CHECK("\xF0\x9F\x8D\x8C" == chai.eval<std::string>(R"("\xF0\x9F\x8D\x8C")"));
CHECK("\U0001F34C" == chai.eval<std::string>(R"("\U0001F34C")"));
CHECK("\u2022" == chai.eval<std::string>(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<int>("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>(), "MyException");
chai.add(chaiscript::base_class<std::runtime_error, MyException>()); // 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<std::string>("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<int>("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<std::function<chaiscript::Boxed_Value(const std::string &)>>("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, my_int>([](int x) {
std::cout << "My_int type conversion 1\n";
return my_int(x);
}));
CHECK_THROWS_AS(chai.add(chaiscript::type_conversion<int, my_int>([](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>(), "Noncopyable");
chai.add(chaiscript::constructor<Noncopyable()>(), "Noncopyable");
chai.add(chaiscript::user_type<Nonmovable>(), "Nonmovable");
chai.add(chaiscript::constructor<Nonmovable()>(), "Nonmovable");
chai.add(chaiscript::user_type<Nothing>(), "Nothing");
chai.add(chaiscript::constructor<Nothing()>(), "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<int>("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<int>("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<int>("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<int>("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<int>("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<int>("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<int>("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<int>("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>(), "MyType");
chai.add(chaiscript::constructor<MyType(int)>(), "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<int, MyType>([](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<int>(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<int>(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<int>(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<int>(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<std::unique_ptr<Issue524_Foo>>;
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<Issue524_Foo>.
chaiscript::ModulePtr m = std::make_shared<chaiscript::Module>();
chaiscript::bootstrap::standard_library::vector_type<VecType>("UniqueVec", *m);
CHECK_NOTHROW(chai.add(m));
// Verify basic operations still work
CHECK(chai.eval<size_t>("var v = UniqueVec(); v.size()") == 0);
CHECK(chai.eval<bool>("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<int>(y); }), "overloaded"));
CHECK_NOTHROW(chai.add(chaiscript::fun([](int x) { return x; }), "overloaded"));
// Verify dispatch still works correctly
CHECK(chai.eval<int>("overloaded(5)") == 5);
CHECK(chai.eval<int>("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<std::string>();
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::Module>();
chaiscript::bootstrap::standard_library::vector_type<std::vector<std::string>>("VectorString", *m);
m->add(chaiscript::vector_conversion<std::vector<std::string>>());
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<std::string> &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<bool>(
"auto x = [];"
"x.push_back(\"Hello\");"
"x.size() == 1"));
// push_back on a vector with initial elements must grow correctly
CHECK(chai.eval<bool>(
"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<std::string>(
"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<std::string>(
"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<std::string>(
"auto w = [];"
"w.push_back(\"hello\");"
"w.push_back(\"world\");"
"join_strings(w)")
== "hello,world");
}
TEST_CASE("vector of vectors conversion (issue #374)") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
auto m = std::make_shared<chaiscript::Module>();
chaiscript::bootstrap::standard_library::vector_type<std::vector<double>>("VectorDouble", *m);
chaiscript::bootstrap::standard_library::vector_type<std::vector<std::vector<double>>>("VectorVectorDouble", *m);
m->add(chaiscript::vector_conversion<std::vector<double>>());
m->add(chaiscript::vector_conversion<std::vector<std::vector<double>>>());
chai.add(m);
chai.add(chaiscript::fun([](const std::vector<std::vector<double>> &v) -> double {
double sum = 0;
for (const auto &inner : v) {
for (const auto d : inner) {
sum += d;
}
}
return sum;
}),
"sum_nested");
CHECK(chai.eval<double>("sum_nested([[1.0, 2.0], [3.0, 4.0]])") == Approx(10.0));
CHECK(chai.eval<bool>(
"auto v = VectorVectorDouble();"
"v = [[1.0, 2.0], [3.0, 4.0]];"
"v.size() == 2"));
}
// Regression test for issue #607: AST_Node_Trace must be a complete type
// when used in eval_error's std::vector<AST_Node_Trace> 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<AST_Node_Trace>)
// 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<int>("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<bool>("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<int>("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<double>("constants.si.mu_B") == Approx(9.274));
CHECK(chai.eval<double>("constants.mm.mu_B") == Approx(0.05788));
// Scope resolution via :: works the same as . for access
CHECK(chai.eval<double>("constants::si::mu_B") == Approx(9.274));
CHECK(chai.eval<double>("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<int>("a.b.c.val") == 42);
CHECK(chai.eval<int>("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<int>("math::square(5)") == 25);
CHECK(chai.eval<int>("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<int>("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<int>("ns::foo()") == 1);
CHECK(chai.eval<int>("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<double>("config::pi") == Approx(3.14));
CHECK(chai.eval<std::string>("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<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);
}