From 86b50f0ae4ad3a60b6375c25362e13ef43f407bc Mon Sep 17 00:00:00 2001 From: leftibot Date: Mon, 13 Apr 2026 20:56:34 -0600 Subject: [PATCH 1/3] Fix #3: Add random number generator module Add chaiscript::extras::random providing rand(), rand(max), rand(min, max), and srand(seed) functions using C++11's facilities (std::mt19937 with std::uniform_int_distribution). The engine state is shared via std::shared_ptr so srand() produces deterministic sequences. Also add CATCH_CONFIG_NO_POSIX_SIGNALS to test files to fix SIGSTKSZ build errors on newer glibc. Co-Authored-By: Claude Opus 4.6 (1M context) --- include/chaiscript/extras/random.hpp | 41 ++++++++++++++++++++++++ tests/CMakeLists.txt | 6 ++++ tests/math.cpp | 1 + tests/random.cpp | 48 ++++++++++++++++++++++++++++ tests/string_methods.cpp | 1 + 5 files changed, 97 insertions(+) create mode 100644 include/chaiscript/extras/random.hpp create mode 100644 tests/random.cpp diff --git a/include/chaiscript/extras/random.hpp b/include/chaiscript/extras/random.hpp new file mode 100644 index 0000000..d5c4638 --- /dev/null +++ b/include/chaiscript/extras/random.hpp @@ -0,0 +1,41 @@ +#ifndef CHAISCRIPT_EXTRAS_RANDOM_HPP_ +#define CHAISCRIPT_EXTRAS_RANDOM_HPP_ + +#include +#include + +#include + +namespace chaiscript { + namespace extras { + namespace random { + ModulePtr bootstrap(ModulePtr m = std::make_shared()) + { + auto engine = std::make_shared(std::random_device{}()); + + m->add(chaiscript::fun([engine]() -> int { + std::uniform_int_distribution dist(0, RAND_MAX); + return dist(*engine); + }), "rand"); + + m->add(chaiscript::fun([engine](int max) -> int { + std::uniform_int_distribution dist(0, max); + return dist(*engine); + }), "rand"); + + m->add(chaiscript::fun([engine](int min, int max) -> int { + std::uniform_int_distribution dist(min, max); + return dist(*engine); + }), "rand"); + + m->add(chaiscript::fun([engine](unsigned int seed) { + engine->seed(seed); + }), "srand"); + + return m; + } + } + } +} + +#endif /* CHAISCRIPT_EXTRAS_RANDOM_HPP_ */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8416adb..a86db28 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -50,3 +50,9 @@ add_executable(string_methods_test string_methods.cpp) target_link_libraries(string_methods_test ${LIBS}) target_include_directories(string_methods_test PUBLIC "${chaiscript_SOURCE_DIR}/include") ADD_CATCH_TESTS(string_methods_test) + +# Random +add_executable(random_test random.cpp) +target_link_libraries(random_test ${LIBS}) +target_include_directories(random_test PUBLIC "${chaiscript_SOURCE_DIR}/include") +ADD_CATCH_TESTS(random_test) diff --git a/tests/math.cpp b/tests/math.cpp index 63eafcd..7efebf0 100644 --- a/tests/math.cpp +++ b/tests/math.cpp @@ -1,4 +1,5 @@ #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#define CATCH_CONFIG_NO_POSIX_SIGNALS //#include #include "catch.hpp" diff --git a/tests/random.cpp b/tests/random.cpp new file mode 100644 index 0000000..e26d1ba --- /dev/null +++ b/tests/random.cpp @@ -0,0 +1,48 @@ +#define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_NO_POSIX_SIGNALS +#include "catch.hpp" + +#include +#include +#include "../include/chaiscript/extras/random.hpp" + +TEST_CASE( "Random functions work", "[random]" ) { + auto randomlib = chaiscript::extras::random::bootstrap(); + + auto stdlib = chaiscript::Std_Lib::library(); + chaiscript::ChaiScript chai(stdlib); + chai.add(randomlib); + + SECTION("rand() returns a non-negative integer") { + const auto result = chai.eval("rand()"); + CHECK(result >= 0); + } + + SECTION("rand(max) returns a value in [0, max]") { + const auto result = chai.eval("rand(10)"); + CHECK(result >= 0); + CHECK(result <= 10); + } + + SECTION("rand(min, max) returns a value in [min, max]") { + const auto result = chai.eval("rand(5, 10)"); + CHECK(result >= 5); + CHECK(result <= 10); + } + + SECTION("srand(seed) produces deterministic results") { + chai.eval("srand(42)"); + const auto first = chai.eval("rand(0, 1000)"); + chai.eval("srand(42)"); + const auto second = chai.eval("rand(0, 1000)"); + CHECK(first == second); + } + + SECTION("rand() with same seed is deterministic") { + chai.eval("srand(123)"); + const auto a = chai.eval("rand()"); + chai.eval("srand(123)"); + const auto b = chai.eval("rand()"); + CHECK(a == b); + } +} diff --git a/tests/string_methods.cpp b/tests/string_methods.cpp index 07efc3f..57ee709 100644 --- a/tests/string_methods.cpp +++ b/tests/string_methods.cpp @@ -1,4 +1,5 @@ #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#define CATCH_CONFIG_NO_POSIX_SIGNALS #include #include "catch.hpp" From 97843bed5d3a785b36f4a7f0e15d1187d92f4fef Mon Sep 17 00:00:00 2001 From: leftibot Date: Mon, 13 Apr 2026 23:20:30 -0600 Subject: [PATCH 2/3] Address review: expose Random_Engine type mirroring C++ interface Replace free functions (rand/srand) with a Random_Engine class that wraps std::mt19937. Users construct engine objects, copy them, seed them, and generate values via random_int(min, max) and random_float(min, max). Requested by @lefticus in PR #31 review. Co-Authored-By: Claude Opus 4.6 (1M context) --- include/chaiscript/extras/random.hpp | 66 ++++++++++++++++++--------- tests/random.cpp | 67 +++++++++++++++++++--------- 2 files changed, 93 insertions(+), 40 deletions(-) diff --git a/include/chaiscript/extras/random.hpp b/include/chaiscript/extras/random.hpp index d5c4638..0f8d9e6 100644 --- a/include/chaiscript/extras/random.hpp +++ b/include/chaiscript/extras/random.hpp @@ -9,28 +9,54 @@ namespace chaiscript { namespace extras { namespace random { + class Random_Engine { + public: + Random_Engine() + : m_engine(std::random_device{}()) + { + } + + explicit Random_Engine(unsigned int t_seed) + : m_engine(t_seed) + { + } + + Random_Engine(const Random_Engine &) = default; + Random_Engine &operator=(const Random_Engine &) = default; + + void seed(unsigned int t_seed) { + m_engine.seed(t_seed); + } + + int random_int(int t_min, int t_max) { + std::uniform_int_distribution dist(t_min, t_max); + return dist(m_engine); + } + + double random_float(double t_min, double t_max) { + std::uniform_real_distribution dist(t_min, t_max); + return dist(m_engine); + } + + private: + std::mt19937 m_engine; + }; + ModulePtr bootstrap(ModulePtr m = std::make_shared()) { - auto engine = std::make_shared(std::random_device{}()); - - m->add(chaiscript::fun([engine]() -> int { - std::uniform_int_distribution dist(0, RAND_MAX); - return dist(*engine); - }), "rand"); - - m->add(chaiscript::fun([engine](int max) -> int { - std::uniform_int_distribution dist(0, max); - return dist(*engine); - }), "rand"); - - m->add(chaiscript::fun([engine](int min, int max) -> int { - std::uniform_int_distribution dist(min, max); - return dist(*engine); - }), "rand"); - - m->add(chaiscript::fun([engine](unsigned int seed) { - engine->seed(seed); - }), "srand"); + m->add(user_type(), "Random_Engine"); + m->add(constructor(), "Random_Engine"); + m->add(constructor(), "Random_Engine"); + m->add(constructor(), "Random_Engine"); + + m->add(fun(&Random_Engine::seed), "seed"); + m->add(fun(&Random_Engine::random_int), "random_int"); + m->add(fun(&Random_Engine::random_float), "random_float"); + + m->add(fun([](Random_Engine &t_lhs, const Random_Engine &t_rhs) -> Random_Engine & { + t_lhs = t_rhs; + return t_lhs; + }), "="); return m; } diff --git a/tests/random.cpp b/tests/random.cpp index e26d1ba..7f04dea 100644 --- a/tests/random.cpp +++ b/tests/random.cpp @@ -6,43 +6,70 @@ #include #include "../include/chaiscript/extras/random.hpp" -TEST_CASE( "Random functions work", "[random]" ) { +TEST_CASE( "Random_Engine type is usable in ChaiScript", "[random]" ) { auto randomlib = chaiscript::extras::random::bootstrap(); auto stdlib = chaiscript::Std_Lib::library(); chaiscript::ChaiScript chai(stdlib); chai.add(randomlib); - SECTION("rand() returns a non-negative integer") { - const auto result = chai.eval("rand()"); + SECTION("default constructor creates a valid engine") { + const auto result = chai.eval("var rng = Random_Engine(); rng.random_int(0, 100)"); CHECK(result >= 0); + CHECK(result <= 100); } - SECTION("rand(max) returns a value in [0, max]") { - const auto result = chai.eval("rand(10)"); + SECTION("seeded constructor creates a valid engine") { + const auto result = chai.eval("var rng = Random_Engine(42u); rng.random_int(0, 100)"); CHECK(result >= 0); - CHECK(result <= 10); + CHECK(result <= 100); + } + + SECTION("seed produces deterministic sequences") { + chai.eval("var rng = Random_Engine()"); + chai.eval("rng.seed(42u)"); + const auto first = chai.eval("rng.random_int(0, 1000)"); + chai.eval("rng.seed(42u)"); + const auto second = chai.eval("rng.random_int(0, 1000)"); + CHECK(first == second); + } + + SECTION("copy constructor copies engine state") { + chai.eval("var rng = Random_Engine(99u)"); + chai.eval("var rng2 = Random_Engine(rng)"); + const auto a = chai.eval("rng.random_int(0, 1000)"); + const auto b = chai.eval("rng2.random_int(0, 1000)"); + CHECK(a == b); + } + + SECTION("assignment copies engine state") { + chai.eval("var rng = Random_Engine(77u)"); + chai.eval("var rng2 = Random_Engine()"); + chai.eval("rng2 = rng"); + const auto a = chai.eval("rng.random_int(0, 1000)"); + const auto b = chai.eval("rng2.random_int(0, 1000)"); + CHECK(a == b); } - SECTION("rand(min, max) returns a value in [min, max]") { - const auto result = chai.eval("rand(5, 10)"); + SECTION("random_int returns values in range") { + chai.eval("var rng = Random_Engine(42u)"); + const auto result = chai.eval("rng.random_int(5, 10)"); CHECK(result >= 5); CHECK(result <= 10); } - SECTION("srand(seed) produces deterministic results") { - chai.eval("srand(42)"); - const auto first = chai.eval("rand(0, 1000)"); - chai.eval("srand(42)"); - const auto second = chai.eval("rand(0, 1000)"); - CHECK(first == second); + SECTION("random_float returns values in range") { + chai.eval("var rng = Random_Engine(42u)"); + const auto result = chai.eval("rng.random_float(0.0, 1.0)"); + CHECK(result >= 0.0); + CHECK(result < 1.0); } - SECTION("rand() with same seed is deterministic") { - chai.eval("srand(123)"); - const auto a = chai.eval("rand()"); - chai.eval("srand(123)"); - const auto b = chai.eval("rand()"); - CHECK(a == b); + SECTION("different seeds produce different sequences") { + chai.eval("var rng1 = Random_Engine(1u)"); + chai.eval("var rng2 = Random_Engine(2u)"); + const auto a = chai.eval("rng1.random_int(0, 1000000)"); + const auto b = chai.eval("rng2.random_int(0, 1000000)"); + CHECK(a != b); } } From 955b4ba9b8709c00180350c3b96de055e0ddfc95 Mon Sep 17 00:00:00 2001 From: leftibot Date: Tue, 14 Apr 2026 15:50:15 -0600 Subject: [PATCH 3/3] Address review: rename Random_Engine to MT19937_Engine Make the underlying random device (std::mt19937) explicit in the type name, so users know which engine they are working with. Requested by @lefticus in PR #31 review. Co-Authored-By: Claude Opus 4.6 (1M context) --- include/chaiscript/extras/random.hpp | 26 +++++++++++++------------- tests/random.cpp | 24 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/chaiscript/extras/random.hpp b/include/chaiscript/extras/random.hpp index 0f8d9e6..5c52ea8 100644 --- a/include/chaiscript/extras/random.hpp +++ b/include/chaiscript/extras/random.hpp @@ -9,20 +9,20 @@ namespace chaiscript { namespace extras { namespace random { - class Random_Engine { + class MT19937_Engine { public: - Random_Engine() + MT19937_Engine() : m_engine(std::random_device{}()) { } - explicit Random_Engine(unsigned int t_seed) + explicit MT19937_Engine(unsigned int t_seed) : m_engine(t_seed) { } - Random_Engine(const Random_Engine &) = default; - Random_Engine &operator=(const Random_Engine &) = default; + MT19937_Engine(const MT19937_Engine &) = default; + MT19937_Engine &operator=(const MT19937_Engine &) = default; void seed(unsigned int t_seed) { m_engine.seed(t_seed); @@ -44,16 +44,16 @@ namespace chaiscript { ModulePtr bootstrap(ModulePtr m = std::make_shared()) { - m->add(user_type(), "Random_Engine"); - m->add(constructor(), "Random_Engine"); - m->add(constructor(), "Random_Engine"); - m->add(constructor(), "Random_Engine"); + m->add(user_type(), "MT19937_Engine"); + m->add(constructor(), "MT19937_Engine"); + m->add(constructor(), "MT19937_Engine"); + m->add(constructor(), "MT19937_Engine"); - m->add(fun(&Random_Engine::seed), "seed"); - m->add(fun(&Random_Engine::random_int), "random_int"); - m->add(fun(&Random_Engine::random_float), "random_float"); + m->add(fun(&MT19937_Engine::seed), "seed"); + m->add(fun(&MT19937_Engine::random_int), "random_int"); + m->add(fun(&MT19937_Engine::random_float), "random_float"); - m->add(fun([](Random_Engine &t_lhs, const Random_Engine &t_rhs) -> Random_Engine & { + m->add(fun([](MT19937_Engine &t_lhs, const MT19937_Engine &t_rhs) -> MT19937_Engine & { t_lhs = t_rhs; return t_lhs; }), "="); diff --git a/tests/random.cpp b/tests/random.cpp index 7f04dea..dfb355c 100644 --- a/tests/random.cpp +++ b/tests/random.cpp @@ -6,7 +6,7 @@ #include #include "../include/chaiscript/extras/random.hpp" -TEST_CASE( "Random_Engine type is usable in ChaiScript", "[random]" ) { +TEST_CASE( "MT19937_Engine type is usable in ChaiScript", "[random]" ) { auto randomlib = chaiscript::extras::random::bootstrap(); auto stdlib = chaiscript::Std_Lib::library(); @@ -14,19 +14,19 @@ TEST_CASE( "Random_Engine type is usable in ChaiScript", "[random]" ) { chai.add(randomlib); SECTION("default constructor creates a valid engine") { - const auto result = chai.eval("var rng = Random_Engine(); rng.random_int(0, 100)"); + const auto result = chai.eval("var rng = MT19937_Engine(); rng.random_int(0, 100)"); CHECK(result >= 0); CHECK(result <= 100); } SECTION("seeded constructor creates a valid engine") { - const auto result = chai.eval("var rng = Random_Engine(42u); rng.random_int(0, 100)"); + const auto result = chai.eval("var rng = MT19937_Engine(42u); rng.random_int(0, 100)"); CHECK(result >= 0); CHECK(result <= 100); } SECTION("seed produces deterministic sequences") { - chai.eval("var rng = Random_Engine()"); + chai.eval("var rng = MT19937_Engine()"); chai.eval("rng.seed(42u)"); const auto first = chai.eval("rng.random_int(0, 1000)"); chai.eval("rng.seed(42u)"); @@ -35,16 +35,16 @@ TEST_CASE( "Random_Engine type is usable in ChaiScript", "[random]" ) { } SECTION("copy constructor copies engine state") { - chai.eval("var rng = Random_Engine(99u)"); - chai.eval("var rng2 = Random_Engine(rng)"); + chai.eval("var rng = MT19937_Engine(99u)"); + chai.eval("var rng2 = MT19937_Engine(rng)"); const auto a = chai.eval("rng.random_int(0, 1000)"); const auto b = chai.eval("rng2.random_int(0, 1000)"); CHECK(a == b); } SECTION("assignment copies engine state") { - chai.eval("var rng = Random_Engine(77u)"); - chai.eval("var rng2 = Random_Engine()"); + chai.eval("var rng = MT19937_Engine(77u)"); + chai.eval("var rng2 = MT19937_Engine()"); chai.eval("rng2 = rng"); const auto a = chai.eval("rng.random_int(0, 1000)"); const auto b = chai.eval("rng2.random_int(0, 1000)"); @@ -52,22 +52,22 @@ TEST_CASE( "Random_Engine type is usable in ChaiScript", "[random]" ) { } SECTION("random_int returns values in range") { - chai.eval("var rng = Random_Engine(42u)"); + chai.eval("var rng = MT19937_Engine(42u)"); const auto result = chai.eval("rng.random_int(5, 10)"); CHECK(result >= 5); CHECK(result <= 10); } SECTION("random_float returns values in range") { - chai.eval("var rng = Random_Engine(42u)"); + chai.eval("var rng = MT19937_Engine(42u)"); const auto result = chai.eval("rng.random_float(0.0, 1.0)"); CHECK(result >= 0.0); CHECK(result < 1.0); } SECTION("different seeds produce different sequences") { - chai.eval("var rng1 = Random_Engine(1u)"); - chai.eval("var rng2 = Random_Engine(2u)"); + chai.eval("var rng1 = MT19937_Engine(1u)"); + chai.eval("var rng2 = MT19937_Engine(2u)"); const auto a = chai.eval("rng1.random_int(0, 1000000)"); const auto b = chai.eval("rng2.random_int(0, 1000000)"); CHECK(a != b);