From b9fa9250a6a7c0e52f7e317ffbe57cfd50bec5ed Mon Sep 17 00:00:00 2001 From: leftibot Date: Mon, 13 Apr 2026 21:01:25 -0600 Subject: [PATCH] Fix #28: Add std::regex support as a ChaiScript extras module Adds a new header-only regex module exposing std::regex, std::smatch, regex_search, regex_match, and regex_replace to ChaiScript. Includes support for regex flags (e.g. icase) and match result access via smatch indexing. Follows the existing module pattern used by math and string_methods. Co-Authored-By: Claude Opus 4.6 (1M context) --- include/chaiscript/extras/regex.hpp | 70 +++++++++++++++++++++++++++++ tests/CMakeLists.txt | 6 +++ tests/regex.cpp | 61 +++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 include/chaiscript/extras/regex.hpp create mode 100644 tests/regex.cpp diff --git a/include/chaiscript/extras/regex.hpp b/include/chaiscript/extras/regex.hpp new file mode 100644 index 0000000..34996da --- /dev/null +++ b/include/chaiscript/extras/regex.hpp @@ -0,0 +1,70 @@ +/** + * @file ChaiScript Regex Support + * + * Adds std::regex support to ChaiScript: + * + * regex(string pattern) + * regex(string pattern, regex_constants flag) + * regex_search(string subject, regex pattern) + * regex_search(string subject, regex pattern, smatch results) + * regex_match(string subject, regex pattern) + * regex_replace(string subject, regex pattern, string replacement) + * smatch::size() + * smatch[index] + * regex_icase() + */ + +#ifndef CHAISCRIPT_EXTRAS_REGEX_HPP_ +#define CHAISCRIPT_EXTRAS_REGEX_HPP_ + +#include +#include +#include +#include + +namespace chaiscript { + namespace extras { + namespace regex { + + ModulePtr bootstrap(ModulePtr m = std::make_shared()) + { + m->add(user_type(), "regex"); + m->add(user_type(), "smatch"); + + m->add(constructor(), "regex"); + m->add(constructor(), "regex"); + m->add(constructor(), "smatch"); + + m->add(fun([](){ return std::regex_constants::icase; }), "regex_icase"); + + m->add(fun([](const std::string &subject, const std::regex &pattern) { + return std::regex_search(subject, pattern); + }), "regex_search"); + + m->add(fun([](const std::string &subject, const std::regex &pattern, std::smatch &results) { + return std::regex_search(subject, results, pattern); + }), "regex_search"); + + m->add(fun([](const std::string &subject, const std::regex &pattern) { + return std::regex_match(subject, pattern); + }), "regex_match"); + + m->add(fun([](const std::string &subject, const std::regex &pattern, const std::string &replacement) { + return std::regex_replace(subject, pattern, replacement); + }), "regex_replace"); + + m->add(fun([](const std::smatch &results) { + return static_cast(results.size()); + }), "size"); + + m->add(fun([](const std::smatch &results, int index) { + return results[index].str(); + }), "[]"); + + return m; + } + } + } +} + +#endif /* CHAISCRIPT_EXTRAS_REGEX_HPP_ */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8416adb..2993c45 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) + +# Regex +add_executable(regex_test regex.cpp) +target_link_libraries(regex_test ${LIBS}) +target_include_directories(regex_test PUBLIC "${chaiscript_SOURCE_DIR}/include") +ADD_CATCH_TESTS(regex_test) diff --git a/tests/regex.cpp b/tests/regex.cpp new file mode 100644 index 0000000..88d1e32 --- /dev/null +++ b/tests/regex.cpp @@ -0,0 +1,61 @@ +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include +#include +#include "catch.hpp" + +#include +#include +#include "../include/chaiscript/extras/regex.hpp" + +TEST_CASE( "regex functions work", "[regex]" ) { + // Create the ChaiScript environment with stdlib available. + auto stdlib = chaiscript::Std_Lib::library(); + chaiscript::ChaiScript chai(stdlib); + + // Add the regex module. + auto regexlib = chaiscript::extras::regex::bootstrap(); + chai.add(chaiscript::bootstrap::standard_library::vector_type>("StringVector")); + chai.add(regexlib); + + // regex_search - match anywhere in string + CHECK(chai.eval("regex_search(\"Hello World 123\", regex(\"[0-9]+\"))") == true); + CHECK(chai.eval("regex_search(\"Hello World\", regex(\"[0-9]+\"))") == false); + + // regex_match - match entire string + CHECK(chai.eval("regex_match(\"123\", regex(\"[0-9]+\"))") == true); + CHECK(chai.eval("regex_match(\"Hello 123\", regex(\"[0-9]+\"))") == false); + + // regex_replace + CHECK(chai.eval("regex_replace(\"Hello World 123\", regex(\"[0-9]+\"), \"456\")") == "Hello World 456"); + CHECK(chai.eval("regex_replace(\"abc 123 def 456\", regex(\"[0-9]+\"), \"NUM\")") == "abc NUM def NUM"); + + // smatch - capture groups via regex_search + CHECK(chai.eval(R"chai( + var m = smatch() + regex_search("Hello 123 World", regex("([0-9]+)"), m) + m[0] + )chai") == "123"); + + CHECK(chai.eval(R"chai( + var m2 = smatch() + regex_search("2024-01-15", regex("([0-9]{4})-([0-9]{2})-([0-9]{2})"), m2) + m2[1] + )chai") == "2024"); + + CHECK(chai.eval(R"chai( + var m3 = smatch() + regex_search("2024-01-15", regex("([0-9]{4})-([0-9]{2})-([0-9]{2})"), m3) + m3[2] + )chai") == "01"); + + // smatch size + CHECK(chai.eval(R"chai( + var m4 = smatch() + regex_search("2024-01-15", regex("([0-9]{4})-([0-9]{2})-([0-9]{2})"), m4) + m4.size() + )chai") == 4); + + // regex with flags - icase + CHECK(chai.eval("regex_search(\"HELLO\", regex(\"hello\", regex_icase()))") == true); + CHECK(chai.eval("regex_search(\"HELLO\", regex(\"hello\"))") == false); +}