From 49269bc5e01935976f011fbb019f6688a207dca6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 07:04:45 +0000 Subject: [PATCH 01/23] feat: add google/re2 and abseil-cpp dependencies Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/0e3cf009-a2d6-49bc-bf3e-dd63ff740acd Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- CMakeLists.txt | 3 +++ external/google/CMakeLists.txt | 2 ++ external/google/abseil/CMakeLists.txt | 11 +++++++++++ external/google/re2/CMakeLists.txt | 10 ++++++++++ 4 files changed, 26 insertions(+) create mode 100644 external/google/abseil/CMakeLists.txt create mode 100644 external/google/re2/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 29c0d02c..4ac5c822 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,9 @@ else() if (CCR_BUILD_TESTS) find_package(yaml-cpp REQUIRED) endif() + + find_package(absl) + find_package(re2) endif() add_subdirectory(cucumber_cpp) diff --git a/external/google/CMakeLists.txt b/external/google/CMakeLists.txt index 73deebc3..090972dd 100644 --- a/external/google/CMakeLists.txt +++ b/external/google/CMakeLists.txt @@ -1 +1,3 @@ +add_subdirectory(abseil) # before re2 add_subdirectory(googletest) +add_subdirectory(re2) diff --git a/external/google/abseil/CMakeLists.txt b/external/google/abseil/CMakeLists.txt new file mode 100644 index 00000000..fd40a6d0 --- /dev/null +++ b/external/google/abseil/CMakeLists.txt @@ -0,0 +1,11 @@ +FetchContent_Declare( + absl + GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git + GIT_TAG 0cf0a5c9d12cc3783363ab20f11613e69fd04c9a # 20250814.2 + OVERRIDE_FIND_PACKAGE +) + +set(ABSL_PROPAGATE_CXX_STD On CACHE BOOL "" FORCE) +set(ABSL_ENABLE_INSTALL Off CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(absl) diff --git a/external/google/re2/CMakeLists.txt b/external/google/re2/CMakeLists.txt new file mode 100644 index 00000000..0cd9b7e0 --- /dev/null +++ b/external/google/re2/CMakeLists.txt @@ -0,0 +1,10 @@ +FetchContent_Declare( + re2 + GIT_REPOSITORY https://github.com/google/re2.git + GIT_TAG 0f6c07eae69151e606acb3d9232750c3442dff23 # 2025-08-12 + OVERRIDE_FIND_PACKAGE +) + +set(RE2_BUILD_TESTING Off CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(re2) From d625ab22b12e38585fda785a350f7467d9fefb4e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 07:19:01 +0000 Subject: [PATCH 02/23] fix: enable ABSL_ENABLE_INSTALL to resolve re2 export set error Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/9f30db49-b4ba-4e81-8e79-9526f60101d5 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- external/google/abseil/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/google/abseil/CMakeLists.txt b/external/google/abseil/CMakeLists.txt index fd40a6d0..1713d7d4 100644 --- a/external/google/abseil/CMakeLists.txt +++ b/external/google/abseil/CMakeLists.txt @@ -6,6 +6,6 @@ FetchContent_Declare( ) set(ABSL_PROPAGATE_CXX_STD On CACHE BOOL "" FORCE) -set(ABSL_ENABLE_INSTALL Off CACHE BOOL "" FORCE) +set(ABSL_ENABLE_INSTALL On CACHE BOOL "" FORCE) FetchContent_MakeAvailable(absl) From eee719136ec11adcb1e6e073e1f52b8e8ffb9d70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 07:34:45 +0000 Subject: [PATCH 03/23] build: pin abseil to stable 20240722.0 Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/40e20c8b-1421-4d1f-867d-fb4e29d1b994 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- external/google/abseil/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/google/abseil/CMakeLists.txt b/external/google/abseil/CMakeLists.txt index 1713d7d4..5ff56455 100644 --- a/external/google/abseil/CMakeLists.txt +++ b/external/google/abseil/CMakeLists.txt @@ -1,7 +1,7 @@ FetchContent_Declare( absl GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git - GIT_TAG 0cf0a5c9d12cc3783363ab20f11613e69fd04c9a # 20250814.2 + GIT_TAG 20240722.0 OVERRIDE_FIND_PACKAGE ) From 1dd92ddbdc51a7eedda9d5ba1bed19035e8a131b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 07:45:08 +0000 Subject: [PATCH 04/23] build: avoid sanitizer breakage with gcc 14 in standalone builds Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/40e20c8b-1421-4d1f-867d-fb4e29d1b994 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- CMakeLists.txt | 8 ++++++-- external/google/abseil/CMakeLists.txt | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ac5c822..ee09482c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,8 +27,12 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) if (CCR_STANDALONE AND NOT CCR_ENABLE_COVERAGE) if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) - add_compile_options(-fsanitize=address -fsanitize=undefined) - add_link_options(-fsanitize=address -fsanitize=undefined) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14) + message(STATUS "Skipping sanitizers for GCC >= 14 due incompatibility in fetched Abseil build") + else() + add_compile_options(-fsanitize=address -fsanitize=undefined) + add_link_options(-fsanitize=address -fsanitize=undefined) + endif() endif() endif() diff --git a/external/google/abseil/CMakeLists.txt b/external/google/abseil/CMakeLists.txt index 5ff56455..1713d7d4 100644 --- a/external/google/abseil/CMakeLists.txt +++ b/external/google/abseil/CMakeLists.txt @@ -1,7 +1,7 @@ FetchContent_Declare( absl GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git - GIT_TAG 20240722.0 + GIT_TAG 0cf0a5c9d12cc3783363ab20f11613e69fd04c9a # 20250814.2 OVERRIDE_FIND_PACKAGE ) From 63e588ce1f2cd1080f7d025882c742f25f457dc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 07:52:24 +0000 Subject: [PATCH 05/23] chore: fix sanitizer skip status message wording Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/40e20c8b-1421-4d1f-867d-fb4e29d1b994 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee09482c..0b0c19b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) if (CCR_STANDALONE AND NOT CCR_ENABLE_COVERAGE) if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14) - message(STATUS "Skipping sanitizers for GCC >= 14 due incompatibility in fetched Abseil build") + message(STATUS "Skipping sanitizers for GCC >= 14 due to incompatibility in fetched Abseil build") else() add_compile_options(-fsanitize=address -fsanitize=undefined) add_link_options(-fsanitize=address -fsanitize=undefined) From 907298c0c10f9974e18d6b3c89432b03a40f2dbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 08:28:55 +0000 Subject: [PATCH 06/23] fix: move sanitizer flags after external deps to avoid Abseil GCC+ASan conflict Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/7bb1f5cc-0fbc-4d37-b19a-d7d70116d3cd Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- CMakeLists.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b0c19b2..2f3e17dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,17 +25,6 @@ option(CCR_ENABLE_TIME_PROFILE "Enable compiler flags for time profiling measure set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if (CCR_STANDALONE AND NOT CCR_ENABLE_COVERAGE) - if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14) - message(STATUS "Skipping sanitizers for GCC >= 14 due to incompatibility in fetched Abseil build") - else() - add_compile_options(-fsanitize=address -fsanitize=undefined) - add_link_options(-fsanitize=address -fsanitize=undefined) - endif() - endif() -endif() - if (CCR_BUILD_TESTS) ccr_enable_testing() endif() @@ -90,6 +79,17 @@ else() find_package(re2) endif() +# Sanitizer flags are set after external dependencies so that fetched third-party +# libraries (abseil, re2) are not compiled with sanitizer instrumentation. +# Abseil's constexpr function-pointer comparisons are not compatible with GCC's +# ASan/UBSan instrumentation, which causes build failures. +if (CCR_STANDALONE AND NOT CCR_ENABLE_COVERAGE) + if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) + add_compile_options(-fsanitize=address -fsanitize=undefined) + add_link_options(-fsanitize=address -fsanitize=undefined) + endif() +endif() + add_subdirectory(cucumber_cpp) if (CCR_STANDALONE) From e9d7ecc9a2849a2c37a44b1b0e8f259df22ee8ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 10:17:40 +0000 Subject: [PATCH 07/23] feat: add RegexStrategy with StdRegexStrategy and Re2RegexStrategy implementations Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/e62df561-2d9b-48d9-9306-405d1d941bb1 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- .../cucumber_expression/CMakeLists.txt | 3 + .../cucumber_expression/RegexStrategy.cpp | 56 +++++++++ .../cucumber_expression/RegexStrategy.hpp | 54 ++++++++ .../cucumber_expression/test/CMakeLists.txt | 1 + .../test/TestRegexStrategy.cpp | 115 ++++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp create mode 100644 cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp create mode 100644 cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index ddb05d8a..0db5e845 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -21,6 +21,8 @@ target_sources(cucumber_cpp.library.cucumber_expression PRIVATE ParameterRegistry.hpp RegularExpression.cpp RegularExpression.hpp + RegexStrategy.cpp + RegexStrategy.hpp TreeRegexp.cpp TreeRegexp.hpp ) @@ -31,6 +33,7 @@ target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC fmt::fmt + re2::re2 ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp new file mode 100644 index 00000000..d517a399 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp @@ -0,0 +1,56 @@ +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + StdRegexStrategy::StdRegexStrategy(std::string_view pattern) + : pattern{ pattern } + , regex{ std::string(pattern) } + { + } + + std::optional> StdRegexStrategy::Match(std::string_view text) const + { + std::smatch match; + const std::string textStr(text); + if (!std::regex_search(textStr, match, regex)) + return std::nullopt; + + std::vector result; + result.reserve(match.size()); + for (const auto& m : match) + result.emplace_back(m.str()); + + return result; + } + + Re2RegexStrategy::Re2RegexStrategy(std::string_view pattern) + : pattern{ pattern } + , re2{ std::make_unique(pattern) } + { + } + + Re2RegexStrategy::~Re2RegexStrategy() = default; + + std::optional> Re2RegexStrategy::Match(std::string_view text) const + { + const int nCaptures = re2->NumberOfCapturingGroups(); + const int nSubmatch = nCaptures + 1; + std::vector submatch(static_cast(nSubmatch)); + + if (!re2->Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) + return std::nullopt; + + std::vector result; + result.reserve(static_cast(nSubmatch)); + for (const auto& piece : submatch) + result.emplace_back(piece.data(), piece.size()); + + return result; + } +} diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp new file mode 100644 index 00000000..533b8d4c --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp @@ -0,0 +1,54 @@ +#ifndef CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP +#define CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP + +#include +#include +#include +#include +#include +#include + +namespace re2 +{ + class RE2; +} + +namespace cucumber_cpp::library::cucumber_expression +{ + struct RegexStrategy + { + RegexStrategy() = default; + RegexStrategy(const RegexStrategy&) = delete; + RegexStrategy& operator=(const RegexStrategy&) = delete; + RegexStrategy(RegexStrategy&&) = delete; + RegexStrategy& operator=(RegexStrategy&&) = delete; + virtual ~RegexStrategy() = default; + + [[nodiscard]] virtual std::optional> Match(std::string_view text) const = 0; + }; + + struct StdRegexStrategy : RegexStrategy + { + explicit StdRegexStrategy(std::string_view pattern); + + [[nodiscard]] std::optional> Match(std::string_view text) const override; + + private: + std::string pattern; + std::regex regex; + }; + + struct Re2RegexStrategy : RegexStrategy + { + explicit Re2RegexStrategy(std::string_view pattern); + ~Re2RegexStrategy() override; + + [[nodiscard]] std::optional> Match(std::string_view text) const override; + + private: + std::string pattern; + std::unique_ptr re2; + }; +} + +#endif diff --git a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt index 5c4e3254..10b1a96e 100644 --- a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources(cucumber_cpp.library.cucumber_expression.test PRIVATE TestExpression.cpp TestExpressionParser.cpp TestExpressionTokenizer.cpp + TestRegexStrategy.cpp TestRegularExpression.cpp TestTransformation.cpp TestTreeRegexp.cpp diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp new file mode 100644 index 00000000..02c606cc --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp @@ -0,0 +1,115 @@ +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + // StdRegexStrategy + + TEST(StdRegexStrategy, ReturnsNulloptWhenNoMatch) + { + StdRegexStrategy strategy{ R"__(hello)__" }; + + EXPECT_THAT(strategy.Match("world"), testing::IsFalse()); + } + + TEST(StdRegexStrategy, ReturnsVectorWhenPatternMatches) + { + StdRegexStrategy strategy{ R"__((\d+))__" }; + + const auto result = strategy.Match("abc 42 def"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(0), testing::StrEq("42")); // whole match + EXPECT_THAT(result->at(1), testing::StrEq("42")); // first capture group + } + + TEST(StdRegexStrategy, WholeMatchIsAtIndexZero) + { + StdRegexStrategy strategy{ R"__((\w+)\s+(\w+))__" }; + + const auto result = strategy.Match("hello world"); + + ASSERT_THAT(result, testing::IsTrue()); + ASSERT_THAT(result->size(), testing::Eq(3)); + EXPECT_THAT(result->at(0), testing::StrEq("hello world")); // whole match + EXPECT_THAT(result->at(1), testing::StrEq("hello")); // first capture group + EXPECT_THAT(result->at(2), testing::StrEq("world")); // second capture group + } + + TEST(StdRegexStrategy, MatchesNegativeInteger) + { + StdRegexStrategy strategy{ R"__((-?\d+))__" }; + + const auto result = strategy.Match("-22"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(1), testing::StrEq("-22")); + } + + TEST(StdRegexStrategy, MatchesEmptyCapture) + { + StdRegexStrategy strategy{ R"__(^The value equals "([^"]*)"$)__" }; + + const auto result = strategy.Match(R"__(The value equals "")__"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(1), testing::StrEq("")); + } + + // Re2RegexStrategy + + TEST(Re2RegexStrategy, ReturnsNulloptWhenNoMatch) + { + Re2RegexStrategy strategy{ R"__(hello)__" }; + + EXPECT_THAT(strategy.Match("world"), testing::IsFalse()); + } + + TEST(Re2RegexStrategy, ReturnsVectorWhenPatternMatches) + { + Re2RegexStrategy strategy{ R"__((\d+))__" }; + + const auto result = strategy.Match("abc 42 def"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(0), testing::StrEq("42")); // whole match + EXPECT_THAT(result->at(1), testing::StrEq("42")); // first capture group + } + + TEST(Re2RegexStrategy, WholeMatchIsAtIndexZero) + { + Re2RegexStrategy strategy{ R"__((\w+)\s+(\w+))__" }; + + const auto result = strategy.Match("hello world"); + + ASSERT_THAT(result, testing::IsTrue()); + ASSERT_THAT(result->size(), testing::Eq(3)); + EXPECT_THAT(result->at(0), testing::StrEq("hello world")); // whole match + EXPECT_THAT(result->at(1), testing::StrEq("hello")); // first capture group + EXPECT_THAT(result->at(2), testing::StrEq("world")); // second capture group + } + + TEST(Re2RegexStrategy, MatchesNegativeInteger) + { + Re2RegexStrategy strategy{ R"__((-?\d+))__" }; + + const auto result = strategy.Match("-22"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(1), testing::StrEq("-22")); + } + + TEST(Re2RegexStrategy, MatchesEmptyCapture) + { + Re2RegexStrategy strategy{ R"__(^The value equals "([^"]*)"$)__" }; + + const auto result = strategy.Match(R"__(The value equals "")__"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(1), testing::StrEq("")); + } +} From 5ce20307e039d00c32ac1a9a26daf53a310e901c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 10:23:20 +0000 Subject: [PATCH 08/23] refactor: remove unused pattern members, add RE2 error handling and invalid-pattern test Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/e62df561-2d9b-48d9-9306-405d1d941bb1 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- .../library/cucumber_expression/RegexStrategy.cpp | 9 +++++---- .../library/cucumber_expression/RegexStrategy.hpp | 2 -- .../cucumber_expression/test/TestRegexStrategy.cpp | 6 ++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp index d517a399..68b19765 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -9,8 +10,7 @@ namespace cucumber_cpp::library::cucumber_expression { StdRegexStrategy::StdRegexStrategy(std::string_view pattern) - : pattern{ pattern } - , regex{ std::string(pattern) } + : regex{ std::string(pattern) } { } @@ -30,9 +30,10 @@ namespace cucumber_cpp::library::cucumber_expression } Re2RegexStrategy::Re2RegexStrategy(std::string_view pattern) - : pattern{ pattern } - , re2{ std::make_unique(pattern) } + : re2{ std::make_unique(pattern) } { + if (!re2->ok()) + throw std::invalid_argument(re2->error()); } Re2RegexStrategy::~Re2RegexStrategy() = default; diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp index 533b8d4c..9a71b761 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp @@ -34,7 +34,6 @@ namespace cucumber_cpp::library::cucumber_expression [[nodiscard]] std::optional> Match(std::string_view text) const override; private: - std::string pattern; std::regex regex; }; @@ -46,7 +45,6 @@ namespace cucumber_cpp::library::cucumber_expression [[nodiscard]] std::optional> Match(std::string_view text) const override; private: - std::string pattern; std::unique_ptr re2; }; } diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp index 02c606cc..20c394a0 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -112,4 +113,9 @@ namespace cucumber_cpp::library::cucumber_expression ASSERT_THAT(result, testing::IsTrue()); EXPECT_THAT(result->at(1), testing::StrEq("")); } + + TEST(Re2RegexStrategy, ThrowsOnInvalidPattern) + { + EXPECT_THROW(Re2RegexStrategy{ R"__([invalid)__" }, std::invalid_argument); + } } From a29ca14d40f12342be6d7c18ca04814d704b273e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 13:37:58 +0000 Subject: [PATCH 09/23] refactor: split RegexStrategy into separate files per class, use typed tests Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/9823874c-5558-4d0a-bb90-309e7c61d636 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- .../cucumber_expression/CMakeLists.txt | 7 +- .../cucumber_expression/Re2RegexStrategy.cpp | 36 ++++++++ .../cucumber_expression/Re2RegexStrategy.hpp | 30 +++++++ .../cucumber_expression/RegexStrategy.cpp | 57 +------------ .../cucumber_expression/RegexStrategy.hpp | 28 ------- .../cucumber_expression/StdRegexStrategy.cpp | 29 +++++++ .../cucumber_expression/StdRegexStrategy.hpp | 24 ++++++ .../test/TestRegexStrategy.cpp | 82 ++++--------------- 8 files changed, 143 insertions(+), 150 deletions(-) create mode 100644 cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp create mode 100644 cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp create mode 100644 cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp create mode 100644 cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index 0db5e845..219aa1ec 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -19,10 +19,13 @@ target_sources(cucumber_cpp.library.cucumber_expression PRIVATE MatchRange.hpp ParameterRegistry.cpp ParameterRegistry.hpp + Re2RegexStrategy.cpp + Re2RegexStrategy.hpp + RegexStrategy.hpp RegularExpression.cpp RegularExpression.hpp - RegexStrategy.cpp - RegexStrategy.hpp + StdRegexStrategy.cpp + StdRegexStrategy.hpp TreeRegexp.cpp TreeRegexp.hpp ) diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp new file mode 100644 index 00000000..04a7a707 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp @@ -0,0 +1,36 @@ +#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + Re2RegexStrategy::Re2RegexStrategy(std::string_view pattern) + : re2{ std::make_unique(pattern) } + { + if (!re2->ok()) + throw std::invalid_argument(re2->error()); + } + + Re2RegexStrategy::~Re2RegexStrategy() = default; + + std::optional> Re2RegexStrategy::Match(std::string_view text) const + { + const int nCaptures = re2->NumberOfCapturingGroups(); + const int nSubmatch = nCaptures + 1; + std::vector submatch(static_cast(nSubmatch)); + + if (!re2->Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) + return std::nullopt; + + std::vector result; + result.reserve(static_cast(nSubmatch)); + for (const auto& piece : submatch) + result.emplace_back(piece.data(), piece.size()); + + return result; + } +} diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp new file mode 100644 index 00000000..bfece97f --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp @@ -0,0 +1,30 @@ +#ifndef CUCUMBER_EXPRESSION_RE2REGEXSTRATEGY_HPP +#define CUCUMBER_EXPRESSION_RE2REGEXSTRATEGY_HPP + +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" +#include +#include +#include +#include +#include + +namespace re2 +{ + class RE2; +} + +namespace cucumber_cpp::library::cucumber_expression +{ + struct Re2RegexStrategy : RegexStrategy + { + explicit Re2RegexStrategy(std::string_view pattern); + ~Re2RegexStrategy() override; + + [[nodiscard]] std::optional> Match(std::string_view text) const override; + + private: + std::unique_ptr re2; + }; +} + +#endif diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp index 68b19765..e81e1917 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp @@ -1,57 +1,2 @@ -#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" -#include -#include -#include -#include -#include -#include -#include +// This file has been superseded by StdRegexStrategy.cpp and Re2RegexStrategy.cpp. -namespace cucumber_cpp::library::cucumber_expression -{ - StdRegexStrategy::StdRegexStrategy(std::string_view pattern) - : regex{ std::string(pattern) } - { - } - - std::optional> StdRegexStrategy::Match(std::string_view text) const - { - std::smatch match; - const std::string textStr(text); - if (!std::regex_search(textStr, match, regex)) - return std::nullopt; - - std::vector result; - result.reserve(match.size()); - for (const auto& m : match) - result.emplace_back(m.str()); - - return result; - } - - Re2RegexStrategy::Re2RegexStrategy(std::string_view pattern) - : re2{ std::make_unique(pattern) } - { - if (!re2->ok()) - throw std::invalid_argument(re2->error()); - } - - Re2RegexStrategy::~Re2RegexStrategy() = default; - - std::optional> Re2RegexStrategy::Match(std::string_view text) const - { - const int nCaptures = re2->NumberOfCapturingGroups(); - const int nSubmatch = nCaptures + 1; - std::vector submatch(static_cast(nSubmatch)); - - if (!re2->Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) - return std::nullopt; - - std::vector result; - result.reserve(static_cast(nSubmatch)); - for (const auto& piece : submatch) - result.emplace_back(piece.data(), piece.size()); - - return result; - } -} diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp index 9a71b761..2cf0908b 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp @@ -1,18 +1,11 @@ #ifndef CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP #define CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP -#include #include -#include #include #include #include -namespace re2 -{ - class RE2; -} - namespace cucumber_cpp::library::cucumber_expression { struct RegexStrategy @@ -26,27 +19,6 @@ namespace cucumber_cpp::library::cucumber_expression [[nodiscard]] virtual std::optional> Match(std::string_view text) const = 0; }; - - struct StdRegexStrategy : RegexStrategy - { - explicit StdRegexStrategy(std::string_view pattern); - - [[nodiscard]] std::optional> Match(std::string_view text) const override; - - private: - std::regex regex; - }; - - struct Re2RegexStrategy : RegexStrategy - { - explicit Re2RegexStrategy(std::string_view pattern); - ~Re2RegexStrategy() override; - - [[nodiscard]] std::optional> Match(std::string_view text) const override; - - private: - std::unique_ptr re2; - }; } #endif diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp new file mode 100644 index 00000000..d13b440b --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp @@ -0,0 +1,29 @@ +#include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + StdRegexStrategy::StdRegexStrategy(std::string_view pattern) + : regex{ std::string(pattern) } + { + } + + std::optional> StdRegexStrategy::Match(std::string_view text) const + { + std::smatch match; + const std::string textStr(text); + if (!std::regex_search(textStr, match, regex)) + return std::nullopt; + + std::vector result; + result.reserve(match.size()); + for (const auto& m : match) + result.emplace_back(m.str()); + + return result; + } +} diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp new file mode 100644 index 00000000..0b598910 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp @@ -0,0 +1,24 @@ +#ifndef CUCUMBER_EXPRESSION_STDREGEXSTRATEGY_HPP +#define CUCUMBER_EXPRESSION_STDREGEXSTRATEGY_HPP + +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + struct StdRegexStrategy : RegexStrategy + { + explicit StdRegexStrategy(std::string_view pattern); + + [[nodiscard]] std::optional> Match(std::string_view text) const override; + + private: + std::regex regex; + }; +} + +#endif diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp index 20c394a0..68d3cd4c 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp @@ -1,4 +1,5 @@ -#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" +#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" +#include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" #include #include #include @@ -8,71 +9,23 @@ namespace cucumber_cpp::library::cucumber_expression { - // StdRegexStrategy + template + struct TestRegexStrategy : testing::Test + {}; - TEST(StdRegexStrategy, ReturnsNulloptWhenNoMatch) - { - StdRegexStrategy strategy{ R"__(hello)__" }; - - EXPECT_THAT(strategy.Match("world"), testing::IsFalse()); - } - - TEST(StdRegexStrategy, ReturnsVectorWhenPatternMatches) - { - StdRegexStrategy strategy{ R"__((\d+))__" }; - - const auto result = strategy.Match("abc 42 def"); - - ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(0), testing::StrEq("42")); // whole match - EXPECT_THAT(result->at(1), testing::StrEq("42")); // first capture group - } - - TEST(StdRegexStrategy, WholeMatchIsAtIndexZero) - { - StdRegexStrategy strategy{ R"__((\w+)\s+(\w+))__" }; - - const auto result = strategy.Match("hello world"); - - ASSERT_THAT(result, testing::IsTrue()); - ASSERT_THAT(result->size(), testing::Eq(3)); - EXPECT_THAT(result->at(0), testing::StrEq("hello world")); // whole match - EXPECT_THAT(result->at(1), testing::StrEq("hello")); // first capture group - EXPECT_THAT(result->at(2), testing::StrEq("world")); // second capture group - } - - TEST(StdRegexStrategy, MatchesNegativeInteger) - { - StdRegexStrategy strategy{ R"__((-?\d+))__" }; - - const auto result = strategy.Match("-22"); - - ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1), testing::StrEq("-22")); - } + using RegexStrategyTypes = testing::Types; + TYPED_TEST_SUITE(TestRegexStrategy, RegexStrategyTypes); - TEST(StdRegexStrategy, MatchesEmptyCapture) + TYPED_TEST(TestRegexStrategy, ReturnsNulloptWhenNoMatch) { - StdRegexStrategy strategy{ R"__(^The value equals "([^"]*)"$)__" }; - - const auto result = strategy.Match(R"__(The value equals "")__"); - - ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1), testing::StrEq("")); - } - - // Re2RegexStrategy - - TEST(Re2RegexStrategy, ReturnsNulloptWhenNoMatch) - { - Re2RegexStrategy strategy{ R"__(hello)__" }; + TypeParam strategy{ R"__(hello)__" }; EXPECT_THAT(strategy.Match("world"), testing::IsFalse()); } - TEST(Re2RegexStrategy, ReturnsVectorWhenPatternMatches) + TYPED_TEST(TestRegexStrategy, ReturnsVectorWhenPatternMatches) { - Re2RegexStrategy strategy{ R"__((\d+))__" }; + TypeParam strategy{ R"__((\d+))__" }; const auto result = strategy.Match("abc 42 def"); @@ -81,9 +34,9 @@ namespace cucumber_cpp::library::cucumber_expression EXPECT_THAT(result->at(1), testing::StrEq("42")); // first capture group } - TEST(Re2RegexStrategy, WholeMatchIsAtIndexZero) + TYPED_TEST(TestRegexStrategy, WholeMatchIsAtIndexZero) { - Re2RegexStrategy strategy{ R"__((\w+)\s+(\w+))__" }; + TypeParam strategy{ R"__((\w+)\s+(\w+))__" }; const auto result = strategy.Match("hello world"); @@ -94,9 +47,9 @@ namespace cucumber_cpp::library::cucumber_expression EXPECT_THAT(result->at(2), testing::StrEq("world")); // second capture group } - TEST(Re2RegexStrategy, MatchesNegativeInteger) + TYPED_TEST(TestRegexStrategy, MatchesNegativeInteger) { - Re2RegexStrategy strategy{ R"__((-?\d+))__" }; + TypeParam strategy{ R"__((-?\d+))__" }; const auto result = strategy.Match("-22"); @@ -104,9 +57,9 @@ namespace cucumber_cpp::library::cucumber_expression EXPECT_THAT(result->at(1), testing::StrEq("-22")); } - TEST(Re2RegexStrategy, MatchesEmptyCapture) + TYPED_TEST(TestRegexStrategy, MatchesEmptyCapture) { - Re2RegexStrategy strategy{ R"__(^The value equals "([^"]*)"$)__" }; + TypeParam strategy{ R"__(^The value equals "([^"]*)"$)__" }; const auto result = strategy.Match(R"__(The value equals "")__"); @@ -119,3 +72,4 @@ namespace cucumber_cpp::library::cucumber_expression EXPECT_THROW(Re2RegexStrategy{ R"__([invalid)__" }, std::invalid_argument); } } + From 89aae220cd2e3cd314f78743817ebe2a1a39182c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 22:37:08 +0000 Subject: [PATCH 10/23] feat: make RE2 optional via CCR_USE_RE2, add RegexStrategyFactory Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/1d5dafd2-6ab6-4e4e-9b8d-4c8f6f07386c Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- CMakeLists.txt | 7 +++ .../cucumber_expression/CMakeLists.txt | 20 +++++++-- .../RegexStrategyFactory.cpp | 19 ++++++++ .../RegexStrategyFactory.hpp | 13 ++++++ .../cucumber_expression/test/CMakeLists.txt | 1 + .../test/TestRegexStrategy.cpp | 10 ++++- .../test/TestRegexStrategyFactory.cpp | 43 +++++++++++++++++++ external/google/CMakeLists.txt | 6 ++- 8 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp create mode 100644 cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp create mode 100644 cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f3e17dc..8c22ae88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ endif() option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." ${CCR_DEFAULTOPT} ) option(CCR_BUILD_TESTS "Enable build of the tests" ${CCR_DEFAULTOPT}) +option(CCR_USE_RE2 "Use the RE2 regex engine (fetched when CCR_FETCH_DEPS is set, otherwise auto-detected)" ${CCR_DEFAULTOPT}) option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements" Off) option(CCR_ENABLE_TIME_PROFILE "Enable compiler flags for time profiling measurements" Off) @@ -79,6 +80,12 @@ else() find_package(re2) endif() +if (TARGET re2::re2) + set(CCR_HAS_RE2 On) +else() + set(CCR_HAS_RE2 Off) +endif() + # Sanitizer flags are set after external dependencies so that fetched third-party # libraries (abseil, re2) are not compiled with sanitizer instrumentation. # Abseil's constexpr function-pointer comparisons are not compatible with GCC's diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index 219aa1ec..58da1e8a 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -19,9 +19,9 @@ target_sources(cucumber_cpp.library.cucumber_expression PRIVATE MatchRange.hpp ParameterRegistry.cpp ParameterRegistry.hpp - Re2RegexStrategy.cpp - Re2RegexStrategy.hpp RegexStrategy.hpp + RegexStrategyFactory.cpp + RegexStrategyFactory.hpp RegularExpression.cpp RegularExpression.hpp StdRegexStrategy.cpp @@ -36,9 +36,23 @@ target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC fmt::fmt - re2::re2 ) +if (CCR_HAS_RE2) + target_sources(cucumber_cpp.library.cucumber_expression PRIVATE + Re2RegexStrategy.cpp + Re2RegexStrategy.hpp + ) + + target_compile_definitions(cucumber_cpp.library.cucumber_expression PUBLIC + CCR_HAS_RE2 + ) + + target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC + re2::re2 + ) +endif() + if (CCR_BUILD_TESTS) add_subdirectory(test) endif() diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp new file mode 100644 index 00000000..2889980d --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp @@ -0,0 +1,19 @@ +#include "cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp" +#include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" +#ifdef CCR_HAS_RE2 +#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" +#endif +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + std::unique_ptr CreateRegexStrategy(std::string_view pattern) + { +#ifdef CCR_HAS_RE2 + return std::make_unique(pattern); +#else + return std::make_unique(pattern); +#endif + } +} diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp new file mode 100644 index 00000000..ab0d7e73 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp @@ -0,0 +1,13 @@ +#ifndef CUCUMBER_EXPRESSION_REGEXSTRATEGYFACTORY_HPP +#define CUCUMBER_EXPRESSION_REGEXSTRATEGYFACTORY_HPP + +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + [[nodiscard]] std::unique_ptr CreateRegexStrategy(std::string_view pattern); +} + +#endif diff --git a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt index 10b1a96e..3a86e8c0 100644 --- a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(cucumber_cpp.library.cucumber_expression.test PRIVATE TestExpressionParser.cpp TestExpressionTokenizer.cpp TestRegexStrategy.cpp + TestRegexStrategyFactory.cpp TestRegularExpression.cpp TestTransformation.cpp TestTreeRegexp.cpp diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp index 68d3cd4c..14b34a46 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp @@ -1,5 +1,7 @@ -#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" #include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" +#ifdef CCR_HAS_RE2 +#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" +#endif #include #include #include @@ -13,7 +15,11 @@ namespace cucumber_cpp::library::cucumber_expression struct TestRegexStrategy : testing::Test {}; +#ifdef CCR_HAS_RE2 using RegexStrategyTypes = testing::Types; +#else + using RegexStrategyTypes = testing::Types; +#endif TYPED_TEST_SUITE(TestRegexStrategy, RegexStrategyTypes); TYPED_TEST(TestRegexStrategy, ReturnsNulloptWhenNoMatch) @@ -67,9 +73,11 @@ namespace cucumber_cpp::library::cucumber_expression EXPECT_THAT(result->at(1), testing::StrEq("")); } +#ifdef CCR_HAS_RE2 TEST(Re2RegexStrategy, ThrowsOnInvalidPattern) { EXPECT_THROW(Re2RegexStrategy{ R"__([invalid)__" }, std::invalid_argument); } +#endif } diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp new file mode 100644 index 00000000..19647ac8 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp @@ -0,0 +1,43 @@ +#include "cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp" +#include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" +#ifdef CCR_HAS_RE2 +#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" +#endif +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + TEST(RegexStrategyFactory, ReturnsNonNullStrategy) + { + const auto strategy = CreateRegexStrategy(R"__((\d+))__"); + + EXPECT_THAT(strategy, testing::NotNull()); + } + + TEST(RegexStrategyFactory, ReturnedStrategyCanMatch) + { + const auto strategy = CreateRegexStrategy(R"__((\d+))__"); + + const auto result = strategy->Match("abc 42 def"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(1), testing::StrEq("42")); + } + +#ifdef CCR_HAS_RE2 + TEST(RegexStrategyFactory, ReturnsRe2Strategy) + { + const auto strategy = CreateRegexStrategy(R"__((\d+))__"); + + EXPECT_THAT(dynamic_cast(strategy.get()), testing::NotNull()); + } +#else + TEST(RegexStrategyFactory, ReturnsStdStrategy) + { + const auto strategy = CreateRegexStrategy(R"__((\d+))__"); + + EXPECT_THAT(dynamic_cast(strategy.get()), testing::NotNull()); + } +#endif +} diff --git a/external/google/CMakeLists.txt b/external/google/CMakeLists.txt index 090972dd..b307909f 100644 --- a/external/google/CMakeLists.txt +++ b/external/google/CMakeLists.txt @@ -1,3 +1,5 @@ -add_subdirectory(abseil) # before re2 +if (CCR_USE_RE2) + add_subdirectory(abseil) # before re2 + add_subdirectory(re2) +endif() add_subdirectory(googletest) -add_subdirectory(re2) From 54f7af797affaca34bcabc9594dfed4ff80a52b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 13:57:44 +0000 Subject: [PATCH 11/23] feat: update RegexStrategy::Match to return MatchGroup with positions; use CreateRegexStrategy in TreeRegexp Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/70304498-180d-4d51-a44e-648869d6529e Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- .../cucumber_expression/Re2RegexStrategy.cpp | 24 ++++++++-- .../cucumber_expression/Re2RegexStrategy.hpp | 2 +- .../cucumber_expression/RegexStrategy.hpp | 11 ++++- .../RegexStrategyFactory.cpp | 10 +++- .../cucumber_expression/StdRegexStrategy.cpp | 19 ++++++-- .../cucumber_expression/StdRegexStrategy.hpp | 2 +- .../cucumber_expression/TreeRegexp.cpp | 38 +++++++++++---- .../cucumber_expression/TreeRegexp.hpp | 14 ++++-- .../test/TestRegexStrategy.cpp | 48 ++++++++++++++++--- .../test/TestRegexStrategyFactory.cpp | 12 ++++- cucumber_cpp/library/support/StepRegistry.cpp | 2 +- cucumber_cpp/library/support/StepRegistry.hpp | 2 +- 12 files changed, 148 insertions(+), 36 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp index 04a7a707..fec0f71a 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" #include +#include #include #include #include @@ -17,7 +18,7 @@ namespace cucumber_cpp::library::cucumber_expression Re2RegexStrategy::~Re2RegexStrategy() = default; - std::optional> Re2RegexStrategy::Match(std::string_view text) const + std::optional> Re2RegexStrategy::Match(std::string_view text) const { const int nCaptures = re2->NumberOfCapturingGroups(); const int nSubmatch = nCaptures + 1; @@ -26,11 +27,26 @@ namespace cucumber_cpp::library::cucumber_expression if (!re2->Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) return std::nullopt; - std::vector result; + std::vector result; result.reserve(static_cast(nSubmatch)); for (const auto& piece : submatch) - result.emplace_back(piece.data(), piece.size()); - + { + if (piece.data() == nullptr) + { + result.push_back({ .matched = false, .value = {}, .start = 0, .end = 0 }); + } + else + { + const auto start = static_cast(piece.data() - text.data()); + const auto end = start + piece.size(); + result.push_back({ + .matched = true, + .value = std::string(piece), + .start = start, + .end = end, + }); + } + } return result; } } diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp index bfece97f..16f6cba6 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp @@ -20,7 +20,7 @@ namespace cucumber_cpp::library::cucumber_expression explicit Re2RegexStrategy(std::string_view pattern); ~Re2RegexStrategy() override; - [[nodiscard]] std::optional> Match(std::string_view text) const override; + [[nodiscard]] std::optional> Match(std::string_view text) const override; private: std::unique_ptr re2; diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp index 2cf0908b..64755619 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp @@ -1,6 +1,7 @@ #ifndef CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP #define CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP +#include #include #include #include @@ -8,6 +9,14 @@ namespace cucumber_cpp::library::cucumber_expression { + struct MatchGroup + { + bool matched; + std::string value; + std::size_t start; + std::size_t end; + }; + struct RegexStrategy { RegexStrategy() = default; @@ -17,7 +26,7 @@ namespace cucumber_cpp::library::cucumber_expression RegexStrategy& operator=(RegexStrategy&&) = delete; virtual ~RegexStrategy() = default; - [[nodiscard]] virtual std::optional> Match(std::string_view text) const = 0; + [[nodiscard]] virtual std::optional> Match(std::string_view text) const = 0; }; } diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp index 2889980d..bf09ef3c 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp @@ -2,6 +2,7 @@ #include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" #ifdef CCR_HAS_RE2 #include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" +#include #endif #include #include @@ -11,7 +12,14 @@ namespace cucumber_cpp::library::cucumber_expression std::unique_ptr CreateRegexStrategy(std::string_view pattern) { #ifdef CCR_HAS_RE2 - return std::make_unique(pattern); + try + { + return std::make_unique(pattern); + } + catch (const std::invalid_argument&) + { + return std::make_unique(pattern); + } #else return std::make_unique(pattern); #endif diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp index d13b440b..f761c137 100644 --- a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" +#include #include #include #include @@ -12,18 +13,26 @@ namespace cucumber_cpp::library::cucumber_expression { } - std::optional> StdRegexStrategy::Match(std::string_view text) const + std::optional> StdRegexStrategy::Match(std::string_view text) const { std::smatch match; const std::string textStr(text); if (!std::regex_search(textStr, match, regex)) return std::nullopt; - std::vector result; + std::vector result; result.reserve(match.size()); - for (const auto& m : match) - result.emplace_back(m.str()); - + for (std::size_t i = 0; i < match.size(); ++i) + { + const auto& m = match[i]; + const auto start = m.matched ? static_cast(match.position(static_cast(i))) : std::size_t{ 0 }; + result.push_back({ + .matched = m.matched, + .value = m.matched ? m.str() : std::string{}, + .start = start, + .end = start + static_cast(m.length()), + }); + } return result; } } diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp index 0b598910..a93a75f5 100644 --- a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp @@ -14,7 +14,7 @@ namespace cucumber_cpp::library::cucumber_expression { explicit StdRegexStrategy(std::string_view pattern); - [[nodiscard]] std::optional> Match(std::string_view text) const override; + [[nodiscard]] std::optional> Match(std::string_view text) const override; private: std::regex regex; diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp index 0a2b563f..917e58d2 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp @@ -1,12 +1,12 @@ #include "cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp" #include "cucumber_cpp/library/cucumber_expression/Group.hpp" +#include "cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp" #include #include #include #include #include #include -#include #include #include #include @@ -161,7 +161,7 @@ namespace cucumber_cpp::library::cucumber_expression return pattern; } - ArgumentGroup GroupBuilder::Build(const std::smatch& match, std::size_t& index) const + ArgumentGroup GroupBuilder::Build(const std::vector& match, std::size_t& index) const { const auto groupIndex = index++; const auto& matchGroup = match[groupIndex]; @@ -172,18 +172,38 @@ namespace cucumber_cpp::library::cucumber_expression }); return { - .value = matchGroup.matched ? std::make_optional(matchGroup.str()) : std::nullopt, - .start = matchGroup.matched ? std::make_optional(match.position(groupIndex)) : std::nullopt, + .value = matchGroup.matched ? std::make_optional(matchGroup.value) : std::nullopt, + .start = matchGroup.matched ? std::make_optional(matchGroup.start) : std::nullopt, + .end = matchGroup.matched ? std::make_optional(matchGroup.end) : std::nullopt, .children = std::vector(children.begin(), children.end()), }; } TreeRegexp::TreeRegexp(std::string_view pattern) - : rootGroupBuilder{ CreateGroupBuilder(pattern) } - , regex{ std::string(pattern) } + : storedPattern{ pattern } + , rootGroupBuilder{ CreateGroupBuilder(storedPattern) } + , regexStrategy{ CreateRegexStrategy(storedPattern) } { } + TreeRegexp::TreeRegexp(const TreeRegexp& other) + : storedPattern{ other.storedPattern } + , rootGroupBuilder{ other.rootGroupBuilder } + , regexStrategy{ CreateRegexStrategy(storedPattern) } + { + } + + TreeRegexp& TreeRegexp::operator=(const TreeRegexp& other) + { + if (this != &other) + { + storedPattern = other.storedPattern; + rootGroupBuilder = other.rootGroupBuilder; + regexStrategy = CreateRegexStrategy(storedPattern); + } + return *this; + } + const GroupBuilder& TreeRegexp::RootBuilder() const { return rootGroupBuilder; @@ -191,11 +211,11 @@ namespace cucumber_cpp::library::cucumber_expression std::optional TreeRegexp::MatchToGroup(const std::string& text) const { - std::smatch match; - if (!std::regex_search(text, match, regex)) + const auto matchResult = regexStrategy->Match(text); + if (!matchResult) return std::nullopt; std::size_t index = 0; - return rootGroupBuilder.Build(match, index); + return rootGroupBuilder.Build(*matchResult, index); } } diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp index bda28e47..932206c9 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp @@ -2,12 +2,14 @@ #define CUCUMBER_EXPRESSION_TREE_REGEXP_HPP #include "cucumber_cpp/library/cucumber_expression/Group.hpp" +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include #include +#include #include -#include #include #include +#include namespace cucumber_cpp::library::cucumber_expression { @@ -25,7 +27,7 @@ namespace cucumber_cpp::library::cucumber_expression [[nodiscard]] const std::list& Children() const; [[nodiscard]] std::string_view Pattern() const; - [[nodiscard]] ArgumentGroup Build(const std::smatch& match, std::size_t& index) const; + [[nodiscard]] ArgumentGroup Build(const std::vector& match, std::size_t& index) const; private: std::string_view pattern; @@ -36,14 +38,20 @@ namespace cucumber_cpp::library::cucumber_expression struct TreeRegexp { explicit TreeRegexp(std::string_view pattern); + TreeRegexp(const TreeRegexp& other); + TreeRegexp& operator=(const TreeRegexp& other); + TreeRegexp(TreeRegexp&&) = default; + TreeRegexp& operator=(TreeRegexp&&) = default; + ~TreeRegexp() = default; [[nodiscard]] const GroupBuilder& RootBuilder() const; [[nodiscard]] std::optional MatchToGroup(const std::string& text) const; private: + std::string storedPattern; GroupBuilder rootGroupBuilder; - std::regex regex; + std::unique_ptr regexStrategy; }; } diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp index 14b34a46..43a139b6 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp @@ -36,8 +36,8 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match("abc 42 def"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(0), testing::StrEq("42")); // whole match - EXPECT_THAT(result->at(1), testing::StrEq("42")); // first capture group + EXPECT_THAT(result->at(0).value, testing::StrEq("42")); // whole match + EXPECT_THAT(result->at(1).value, testing::StrEq("42")); // first capture group } TYPED_TEST(TestRegexStrategy, WholeMatchIsAtIndexZero) @@ -48,9 +48,9 @@ namespace cucumber_cpp::library::cucumber_expression ASSERT_THAT(result, testing::IsTrue()); ASSERT_THAT(result->size(), testing::Eq(3)); - EXPECT_THAT(result->at(0), testing::StrEq("hello world")); // whole match - EXPECT_THAT(result->at(1), testing::StrEq("hello")); // first capture group - EXPECT_THAT(result->at(2), testing::StrEq("world")); // second capture group + EXPECT_THAT(result->at(0).value, testing::StrEq("hello world")); // whole match + EXPECT_THAT(result->at(1).value, testing::StrEq("hello")); // first capture group + EXPECT_THAT(result->at(2).value, testing::StrEq("world")); // second capture group } TYPED_TEST(TestRegexStrategy, MatchesNegativeInteger) @@ -60,7 +60,7 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match("-22"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1), testing::StrEq("-22")); + EXPECT_THAT(result->at(1).value, testing::StrEq("-22")); } TYPED_TEST(TestRegexStrategy, MatchesEmptyCapture) @@ -70,7 +70,41 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match(R"__(The value equals "")__"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1), testing::StrEq("")); + EXPECT_THAT(result->at(1).value, testing::StrEq("")); + } + + TYPED_TEST(TestRegexStrategy, ReturnsStartPositionOfWholeMatch) + { + TypeParam strategy{ R"__((\d+))__" }; + + const auto result = strategy.Match("abc 42 def"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(0).start, testing::Eq(4)); // "42" starts at index 4 + EXPECT_THAT(result->at(0).end, testing::Eq(6)); // "42" ends after index 6 + } + + TYPED_TEST(TestRegexStrategy, ReturnsStartPositionOfCaptureGroup) + { + TypeParam strategy{ R"__((\w+)\s+(\w+))__" }; + + const auto result = strategy.Match("hello world"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(1).start, testing::Eq(0)); // "hello" starts at 0 + EXPECT_THAT(result->at(1).end, testing::Eq(5)); // "hello" ends at 5 + EXPECT_THAT(result->at(2).start, testing::Eq(6)); // "world" starts at 6 + EXPECT_THAT(result->at(2).end, testing::Eq(11)); // "world" ends at 11 + } + + TYPED_TEST(TestRegexStrategy, UnmatchedGroupHasMatchedFalse) + { + TypeParam strategy{ R"__(^Something( with an optional argument)?)__" }; + + const auto result = strategy.Match("Something"); + + ASSERT_THAT(result, testing::IsTrue()); + EXPECT_THAT(result->at(1).matched, testing::IsFalse()); } #ifdef CCR_HAS_RE2 diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp index 19647ac8..ed42cfac 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp @@ -22,16 +22,24 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy->Match("abc 42 def"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1), testing::StrEq("42")); + EXPECT_THAT(result->at(1).value, testing::StrEq("42")); } #ifdef CCR_HAS_RE2 - TEST(RegexStrategyFactory, ReturnsRe2Strategy) + TEST(RegexStrategyFactory, ReturnsRe2StrategyForSupportedPattern) { const auto strategy = CreateRegexStrategy(R"__((\d+))__"); EXPECT_THAT(dynamic_cast(strategy.get()), testing::NotNull()); } + + TEST(RegexStrategyFactory, FallsBackToStdStrategyForUnsupportedPattern) + { + // lookahead (?=) is not supported by RE2 + const auto strategy = CreateRegexStrategy(R"__((?=.*\d)\d+)__"); + + EXPECT_THAT(dynamic_cast(strategy.get()), testing::NotNull()); + } #else TEST(RegexStrategyFactory, ReturnsStdStrategy) { diff --git a/cucumber_cpp/library/support/StepRegistry.cpp b/cucumber_cpp/library/support/StepRegistry.cpp index 123aed19..78a6410e 100644 --- a/cucumber_cpp/library/support/StepRegistry.cpp +++ b/cucumber_cpp/library/support/StepRegistry.cpp @@ -71,7 +71,7 @@ namespace cucumber_cpp::library::support return idToDefinitionMap.at(id)->factory; } - StepRegistry::Definition StepRegistry::GetDefinitionById(const std::string& id) const + const StepRegistry::Definition& StepRegistry::GetDefinitionById(const std::string& id) const { return *idToDefinitionMap.at(id); } diff --git a/cucumber_cpp/library/support/StepRegistry.hpp b/cucumber_cpp/library/support/StepRegistry.hpp index e9d96081..9b07c30d 100644 --- a/cucumber_cpp/library/support/StepRegistry.hpp +++ b/cucumber_cpp/library/support/StepRegistry.hpp @@ -106,7 +106,7 @@ namespace cucumber_cpp::library::support [[nodiscard]] std::size_t Size() const; [[nodiscard]] util::StepFactory GetFactoryById(const std::string& id) const; - [[nodiscard]] Definition GetDefinitionById(const std::string& id) const; + [[nodiscard]] const Definition& GetDefinitionById(const std::string& id) const; [[nodiscard]] const std::list& StepDefinitions() const; From bf801be0d70eb6eb1813807279b6aab502b42b76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 14:37:01 +0000 Subject: [PATCH 12/23] refactor: remove matched bool from MatchGroup; use optional per group; remove empty RegexStrategy.cpp Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/90dcd0c9-48e6-4dc2-8df1-09eecb77b2d7 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- .../cucumber_expression/Re2RegexStrategy.cpp | 12 ++++---- .../cucumber_expression/Re2RegexStrategy.hpp | 2 +- .../cucumber_expression/RegexStrategy.cpp | 2 -- .../cucumber_expression/RegexStrategy.hpp | 3 +- .../cucumber_expression/StdRegexStrategy.cpp | 24 +++++++++------ .../cucumber_expression/StdRegexStrategy.hpp | 2 +- .../cucumber_expression/TreeRegexp.cpp | 10 +++---- .../cucumber_expression/TreeRegexp.hpp | 2 +- .../test/TestRegexStrategy.cpp | 30 +++++++++---------- .../test/TestRegexStrategyFactory.cpp | 2 +- 10 files changed, 45 insertions(+), 44 deletions(-) delete mode 100644 cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp index fec0f71a..0804bcad 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp @@ -18,7 +18,7 @@ namespace cucumber_cpp::library::cucumber_expression Re2RegexStrategy::~Re2RegexStrategy() = default; - std::optional> Re2RegexStrategy::Match(std::string_view text) const + std::optional>> Re2RegexStrategy::Match(std::string_view text) const { const int nCaptures = re2->NumberOfCapturingGroups(); const int nSubmatch = nCaptures + 1; @@ -27,23 +27,21 @@ namespace cucumber_cpp::library::cucumber_expression if (!re2->Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) return std::nullopt; - std::vector result; + std::vector> result; result.reserve(static_cast(nSubmatch)); for (const auto& piece : submatch) { if (piece.data() == nullptr) { - result.push_back({ .matched = false, .value = {}, .start = 0, .end = 0 }); + result.emplace_back(std::nullopt); } else { const auto start = static_cast(piece.data() - text.data()); - const auto end = start + piece.size(); - result.push_back({ - .matched = true, + result.emplace_back(MatchGroup{ .value = std::string(piece), .start = start, - .end = end, + .end = start + piece.size(), }); } } diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp index 16f6cba6..d46f3cc4 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp @@ -20,7 +20,7 @@ namespace cucumber_cpp::library::cucumber_expression explicit Re2RegexStrategy(std::string_view pattern); ~Re2RegexStrategy() override; - [[nodiscard]] std::optional> Match(std::string_view text) const override; + [[nodiscard]] std::optional>> Match(std::string_view text) const override; private: std::unique_ptr re2; diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp deleted file mode 100644 index e81e1917..00000000 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp +++ /dev/null @@ -1,2 +0,0 @@ -// This file has been superseded by StdRegexStrategy.cpp and Re2RegexStrategy.cpp. - diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp index 64755619..1277ce41 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp @@ -11,7 +11,6 @@ namespace cucumber_cpp::library::cucumber_expression { struct MatchGroup { - bool matched; std::string value; std::size_t start; std::size_t end; @@ -26,7 +25,7 @@ namespace cucumber_cpp::library::cucumber_expression RegexStrategy& operator=(RegexStrategy&&) = delete; virtual ~RegexStrategy() = default; - [[nodiscard]] virtual std::optional> Match(std::string_view text) const = 0; + [[nodiscard]] virtual std::optional>> Match(std::string_view text) const = 0; }; } diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp index f761c137..3a5aaa33 100644 --- a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp @@ -13,25 +13,31 @@ namespace cucumber_cpp::library::cucumber_expression { } - std::optional> StdRegexStrategy::Match(std::string_view text) const + std::optional>> StdRegexStrategy::Match(std::string_view text) const { std::smatch match; const std::string textStr(text); if (!std::regex_search(textStr, match, regex)) return std::nullopt; - std::vector result; + std::vector> result; result.reserve(match.size()); for (std::size_t i = 0; i < match.size(); ++i) { const auto& m = match[i]; - const auto start = m.matched ? static_cast(match.position(static_cast(i))) : std::size_t{ 0 }; - result.push_back({ - .matched = m.matched, - .value = m.matched ? m.str() : std::string{}, - .start = start, - .end = start + static_cast(m.length()), - }); + if (!m.matched) + { + result.emplace_back(std::nullopt); + } + else + { + const auto start = static_cast(match.position(static_cast(i))); + result.emplace_back(MatchGroup{ + .value = m.str(), + .start = start, + .end = start + static_cast(m.length()), + }); + } } return result; } diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp index a93a75f5..6060d388 100644 --- a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp @@ -14,7 +14,7 @@ namespace cucumber_cpp::library::cucumber_expression { explicit StdRegexStrategy(std::string_view pattern); - [[nodiscard]] std::optional> Match(std::string_view text) const override; + [[nodiscard]] std::optional>> Match(std::string_view text) const override; private: std::regex regex; diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp index 917e58d2..d148525b 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp @@ -161,10 +161,10 @@ namespace cucumber_cpp::library::cucumber_expression return pattern; } - ArgumentGroup GroupBuilder::Build(const std::vector& match, std::size_t& index) const + ArgumentGroup GroupBuilder::Build(const std::vector>& match, std::size_t& index) const { const auto groupIndex = index++; - const auto& matchGroup = match[groupIndex]; + const auto& matchGroupOpt = match[groupIndex]; const auto children = this->children | std::views::transform([&match, &index](const auto& child) { @@ -172,9 +172,9 @@ namespace cucumber_cpp::library::cucumber_expression }); return { - .value = matchGroup.matched ? std::make_optional(matchGroup.value) : std::nullopt, - .start = matchGroup.matched ? std::make_optional(matchGroup.start) : std::nullopt, - .end = matchGroup.matched ? std::make_optional(matchGroup.end) : std::nullopt, + .value = matchGroupOpt ? std::make_optional(matchGroupOpt->value) : std::nullopt, + .start = matchGroupOpt ? std::make_optional(matchGroupOpt->start) : std::nullopt, + .end = matchGroupOpt ? std::make_optional(matchGroupOpt->end) : std::nullopt, .children = std::vector(children.begin(), children.end()), }; } diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp index 932206c9..59f97e97 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp @@ -27,7 +27,7 @@ namespace cucumber_cpp::library::cucumber_expression [[nodiscard]] const std::list& Children() const; [[nodiscard]] std::string_view Pattern() const; - [[nodiscard]] ArgumentGroup Build(const std::vector& match, std::size_t& index) const; + [[nodiscard]] ArgumentGroup Build(const std::vector>& match, std::size_t& index) const; private: std::string_view pattern; diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp index 43a139b6..63c8eecd 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategy.cpp @@ -36,8 +36,8 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match("abc 42 def"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(0).value, testing::StrEq("42")); // whole match - EXPECT_THAT(result->at(1).value, testing::StrEq("42")); // first capture group + EXPECT_THAT(result->at(0)->value, testing::StrEq("42")); // whole match + EXPECT_THAT(result->at(1)->value, testing::StrEq("42")); // first capture group } TYPED_TEST(TestRegexStrategy, WholeMatchIsAtIndexZero) @@ -48,9 +48,9 @@ namespace cucumber_cpp::library::cucumber_expression ASSERT_THAT(result, testing::IsTrue()); ASSERT_THAT(result->size(), testing::Eq(3)); - EXPECT_THAT(result->at(0).value, testing::StrEq("hello world")); // whole match - EXPECT_THAT(result->at(1).value, testing::StrEq("hello")); // first capture group - EXPECT_THAT(result->at(2).value, testing::StrEq("world")); // second capture group + EXPECT_THAT(result->at(0)->value, testing::StrEq("hello world")); // whole match + EXPECT_THAT(result->at(1)->value, testing::StrEq("hello")); // first capture group + EXPECT_THAT(result->at(2)->value, testing::StrEq("world")); // second capture group } TYPED_TEST(TestRegexStrategy, MatchesNegativeInteger) @@ -60,7 +60,7 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match("-22"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1).value, testing::StrEq("-22")); + EXPECT_THAT(result->at(1)->value, testing::StrEq("-22")); } TYPED_TEST(TestRegexStrategy, MatchesEmptyCapture) @@ -70,7 +70,7 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match(R"__(The value equals "")__"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1).value, testing::StrEq("")); + EXPECT_THAT(result->at(1)->value, testing::StrEq("")); } TYPED_TEST(TestRegexStrategy, ReturnsStartPositionOfWholeMatch) @@ -80,8 +80,8 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match("abc 42 def"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(0).start, testing::Eq(4)); // "42" starts at index 4 - EXPECT_THAT(result->at(0).end, testing::Eq(6)); // "42" ends after index 6 + EXPECT_THAT(result->at(0)->start, testing::Eq(4)); // "42" starts at index 4 + EXPECT_THAT(result->at(0)->end, testing::Eq(6)); // "42" ends after index 6 } TYPED_TEST(TestRegexStrategy, ReturnsStartPositionOfCaptureGroup) @@ -91,20 +91,20 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy.Match("hello world"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1).start, testing::Eq(0)); // "hello" starts at 0 - EXPECT_THAT(result->at(1).end, testing::Eq(5)); // "hello" ends at 5 - EXPECT_THAT(result->at(2).start, testing::Eq(6)); // "world" starts at 6 - EXPECT_THAT(result->at(2).end, testing::Eq(11)); // "world" ends at 11 + EXPECT_THAT(result->at(1)->start, testing::Eq(0)); // "hello" starts at 0 + EXPECT_THAT(result->at(1)->end, testing::Eq(5)); // "hello" ends at 5 + EXPECT_THAT(result->at(2)->start, testing::Eq(6)); // "world" starts at 6 + EXPECT_THAT(result->at(2)->end, testing::Eq(11)); // "world" ends at 11 } - TYPED_TEST(TestRegexStrategy, UnmatchedGroupHasMatchedFalse) + TYPED_TEST(TestRegexStrategy, UnmatchedGroupIsNullopt) { TypeParam strategy{ R"__(^Something( with an optional argument)?)__" }; const auto result = strategy.Match("Something"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1).matched, testing::IsFalse()); + EXPECT_THAT(result->at(1), testing::IsFalse()); } #ifdef CCR_HAS_RE2 diff --git a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp index ed42cfac..6e991c46 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestRegexStrategyFactory.cpp @@ -22,7 +22,7 @@ namespace cucumber_cpp::library::cucumber_expression const auto result = strategy->Match("abc 42 def"); ASSERT_THAT(result, testing::IsTrue()); - EXPECT_THAT(result->at(1).value, testing::StrEq("42")); + EXPECT_THAT(result->at(1)->value, testing::StrEq("42")); } #ifdef CCR_HAS_RE2 From c38ec6bd733f6ee783a4acb1b776582b71227ed1 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 26 May 2026 15:14:22 +0000 Subject: [PATCH 13/23] refactor: update Match method signatures to use Matches type; adjust includes and access specifiers --- .../library/cucumber_expression/CMakeLists.txt | 2 +- .../library/cucumber_expression/Re2RegexStrategy.cpp | 11 ++++++----- .../library/cucumber_expression/Re2RegexStrategy.hpp | 4 +--- .../library/cucumber_expression/RegexStrategy.hpp | 8 +++----- .../library/cucumber_expression/StdRegexStrategy.cpp | 5 +++-- .../library/cucumber_expression/StdRegexStrategy.hpp | 4 +--- .../library/cucumber_expression/TreeRegexp.cpp | 4 +++- .../library/cucumber_expression/TreeRegexp.hpp | 4 ++-- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index 58da1e8a..c9dcd0b6 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -44,7 +44,7 @@ if (CCR_HAS_RE2) Re2RegexStrategy.hpp ) - target_compile_definitions(cucumber_cpp.library.cucumber_expression PUBLIC + target_compile_definitions(cucumber_cpp.library.cucumber_expression PRIVATE CCR_HAS_RE2 ) diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp index 0804bcad..dfb3ab35 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp @@ -1,7 +1,10 @@ #include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" -#include +#include "absl/strings/string_view.h" +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include +#include #include +#include #include #include #include @@ -18,7 +21,7 @@ namespace cucumber_cpp::library::cucumber_expression Re2RegexStrategy::~Re2RegexStrategy() = default; - std::optional>> Re2RegexStrategy::Match(std::string_view text) const + std::optional Re2RegexStrategy::Match(std::string_view text) const { const int nCaptures = re2->NumberOfCapturingGroups(); const int nSubmatch = nCaptures + 1; @@ -27,14 +30,12 @@ namespace cucumber_cpp::library::cucumber_expression if (!re2->Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) return std::nullopt; - std::vector> result; + Matches result; result.reserve(static_cast(nSubmatch)); for (const auto& piece : submatch) { if (piece.data() == nullptr) - { result.emplace_back(std::nullopt); - } else { const auto start = static_cast(piece.data() - text.data()); diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp index d46f3cc4..7e403a42 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp @@ -4,9 +4,7 @@ #include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include #include -#include #include -#include namespace re2 { @@ -20,7 +18,7 @@ namespace cucumber_cpp::library::cucumber_expression explicit Re2RegexStrategy(std::string_view pattern); ~Re2RegexStrategy() override; - [[nodiscard]] std::optional>> Match(std::string_view text) const override; + [[nodiscard]] std::optional Match(std::string_view text) const override; private: std::unique_ptr re2; diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp index 1277ce41..2c1f74a5 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp @@ -16,16 +16,14 @@ namespace cucumber_cpp::library::cucumber_expression std::size_t end; }; + using Matches = std::vector>; + struct RegexStrategy { RegexStrategy() = default; - RegexStrategy(const RegexStrategy&) = delete; - RegexStrategy& operator=(const RegexStrategy&) = delete; - RegexStrategy(RegexStrategy&&) = delete; - RegexStrategy& operator=(RegexStrategy&&) = delete; virtual ~RegexStrategy() = default; - [[nodiscard]] virtual std::optional>> Match(std::string_view text) const = 0; + [[nodiscard]] virtual std::optional Match(std::string_view text) const = 0; }; } diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp index 3a5aaa33..400b8a69 100644 --- a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include #include #include @@ -13,14 +14,14 @@ namespace cucumber_cpp::library::cucumber_expression { } - std::optional>> StdRegexStrategy::Match(std::string_view text) const + std::optional StdRegexStrategy::Match(std::string_view text) const { std::smatch match; const std::string textStr(text); if (!std::regex_search(textStr, match, regex)) return std::nullopt; - std::vector> result; + Matches result; result.reserve(match.size()); for (std::size_t i = 0; i < match.size(); ++i) { diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp index 6060d388..4bea05ff 100644 --- a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp @@ -4,9 +4,7 @@ #include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include #include -#include #include -#include namespace cucumber_cpp::library::cucumber_expression { @@ -14,7 +12,7 @@ namespace cucumber_cpp::library::cucumber_expression { explicit StdRegexStrategy(std::string_view pattern); - [[nodiscard]] std::optional>> Match(std::string_view text) const override; + [[nodiscard]] std::optional Match(std::string_view text) const override; private: std::regex regex; diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp index d148525b..c612dbd8 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp" #include "cucumber_cpp/library/cucumber_expression/Group.hpp" +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include "cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp" #include #include @@ -7,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -161,7 +163,7 @@ namespace cucumber_cpp::library::cucumber_expression return pattern; } - ArgumentGroup GroupBuilder::Build(const std::vector>& match, std::size_t& index) const + ArgumentGroup GroupBuilder::Build(std::span> match, std::size_t& index) const { const auto groupIndex = index++; const auto& matchGroupOpt = match[groupIndex]; diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp index 59f97e97..3b3b9ffa 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp @@ -7,9 +7,9 @@ #include #include #include +#include #include #include -#include namespace cucumber_cpp::library::cucumber_expression { @@ -27,7 +27,7 @@ namespace cucumber_cpp::library::cucumber_expression [[nodiscard]] const std::list& Children() const; [[nodiscard]] std::string_view Pattern() const; - [[nodiscard]] ArgumentGroup Build(const std::vector>& match, std::size_t& index) const; + [[nodiscard]] ArgumentGroup Build(std::span> match, std::size_t& index) const; private: std::string_view pattern; From a985e36249154e658e96102e737657cba5bd3171 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 26 May 2026 15:22:40 +0000 Subject: [PATCH 14/23] feat: add conditional compilation for RE2 support in CMakeLists.txt --- .../library/cucumber_expression/test/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt index 3a86e8c0..651932c8 100644 --- a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt @@ -19,6 +19,12 @@ target_sources(cucumber_cpp.library.cucumber_expression.test PRIVATE TestTreeRegexp.cpp ) +if (CCR_HAS_RE2) + target_compile_definitions(cucumber_cpp.library.cucumber_expression.test PRIVATE + CCR_HAS_RE2 + ) +endif() + add_custom_command( TARGET cucumber_cpp.library.cucumber_expression.test POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory From 9410cea21ccf715b7ab44e47f183929f41cb043a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 15:35:26 +0000 Subject: [PATCH 15/23] fix: rewrite float regex to be RE2-compatible, removing positive lookaheads Agent-Logs-Url: https://github.com/philips-software/amp-cucumber-cpp-runner/sessions/cf1e89a9-77fb-403c-b23d-0718a2e8caa3 Co-authored-by: daantimmer <8293597+daantimmer@users.noreply.github.com> --- cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index f2113466..f163dc86 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -80,7 +80,7 @@ namespace cucumber_cpp::library::cucumber_expression { const static std::string integerNegativeRegex{ R"__(-?\d+)__" }; const static std::string integerPositiveRegex{ R"__(\d+)__" }; - const static std::string floatRegex{ R"__((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][+-]?\d+)?)__" }; + const static std::string floatRegex{ R"__([-+]?(?:\d+(?:\.\d+)?|\.\d+)(?:[Ee][+-]?\d+)?)__" }; const static std::string stringRegex{ R"__("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)')__" }; const static std::string wordRegex{ R"__([^\s]+)__" }; const static std::string anonymousRegex{ R"__(.*)__" }; From 16b94469f73dbf822e1e92b174f6bd93158bc674 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 27 May 2026 06:49:22 +0000 Subject: [PATCH 16/23] refactor: update Match method parameters to use const references; improve exception handling in tests --- .../cucumber_expression/test/TestExpression.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index 987a5744..8dbb2f6a 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -49,10 +49,10 @@ namespace cucumber_cpp::library::cucumber_expression ParameterRegistry parameterRegistry{ {} }; template - std::optional Match(std::string expr, std::string text) + std::optional Match(const std::string& expr, const std::string& text) { - Expression expression{ std::move(expr), parameterRegistry }; - auto args = expression.MatchToArguments(std::move(text)); + Expression expression{ expr, parameterRegistry }; + auto args = expression.MatchToArguments(text); if (!args.has_value()) return std::nullopt; @@ -233,14 +233,15 @@ namespace cucumber_cpp::library::cucumber_expression TEST_F(TestExpression, ThrowUnknownParameterType) { - auto expr = "I have {doesnotexist} cuke(s)"; + using namespace std::string_literals; + const auto expr = "I have {doesnotexist} cuke(s)"s; try { Expression expression{ expr, parameterRegistry }; FAIL() << "Expected UndefinedParameterTypeError to be thrown"; } - catch (UndefinedParameterTypeError e) + catch (const UndefinedParameterTypeError& e) { EXPECT_THAT(e.what(), testing::StrEq("This Cucumber Expression has a problem at column 8:\n" "\n" From 3aa5c08880059f1792a80c52e4f4a7c7c69398be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 07:03:26 +0000 Subject: [PATCH 17/23] ci: add ubuntu no-re2 variant to test matrix --- .github/workflows/ci.yml | 12 +++++++++--- CMakePresets.json | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52d2a347..b83c702d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,12 @@ jobs: os: [macos, windows, ubuntu] version: [latest] type: [synchronous] + preset_suffix: [""] + include: + - os: ubuntu + version: latest + type: synchronous + preset_suffix: "-no-re2" fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -89,14 +95,14 @@ jobs: persist-credentials: false - uses: hendrikmuhs/ccache-action@33522472633dbd32578e909b315f5ee43ba878ce # v1.2.22 with: - key: ${{ github.job }}-${{ matrix.os }}-${{ matrix.type }} + key: ${{ github.job }}-${{ matrix.os }}-${{ matrix.type }}${{ matrix.preset_suffix }} max-size: 2G - uses: lukka/run-cmake@5d55ea7949e25f69f0ecb516d8d572297e03a956 # v10.9 with: - workflowPreset: "${{ matrix.os }}-Debug-${{ matrix.type }}" + workflowPreset: "${{ matrix.os }}-Debug-${{ matrix.type }}${{ matrix.preset_suffix }}" - name: Upload test logs if: ${{ failure() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: test-logs - path: .build/${{ matrix.os }}-Debug-${{ matrix.type }}/Testing/Temporary/ + path: .build/${{ matrix.os }}-Debug-${{ matrix.type }}${{ matrix.preset_suffix }}/Testing/Temporary/ diff --git a/CMakePresets.json b/CMakePresets.json index 76343f1c..76482193 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -57,6 +57,14 @@ "displayName": "Configuration for macOS Debug and Tests, Single Config Generator, synchronous", "inherits": "host-single-Debug-synchronous" }, + { + "name": "ubuntu-Debug-synchronous-no-re2", + "displayName": "Configuration for Linux Debug and Tests, Single Config Generator, synchronous, without RE2", + "inherits": "host-single-Debug-synchronous", + "cacheVariables": { + "CCR_USE_RE2": "Off" + } + }, { "name": "host-single-time-profile", "displayName": "Configuration time profiling", @@ -124,6 +132,11 @@ "configuration": "Debug", "configurePreset": "macos-Debug-synchronous" }, + { + "name": "ubuntu-Debug-synchronous-no-re2", + "configuration": "Debug", + "configurePreset": "ubuntu-Debug-synchronous-no-re2" + }, { "name": "host-single-time-profile", "configuration": "Debug", @@ -192,6 +205,12 @@ "configurePreset": "macos-Debug-synchronous", "configuration": "Debug", "inherits": "defaults" + }, + { + "name": "ubuntu-Debug-synchronous-no-re2", + "configurePreset": "ubuntu-Debug-synchronous-no-re2", + "configuration": "Debug", + "inherits": "defaults" } ], "workflowPresets": [ @@ -245,6 +264,23 @@ "name": "macos-Debug-synchronous" } ] + }, + { + "name": "ubuntu-Debug-synchronous-no-re2", + "steps": [ + { + "type": "configure", + "name": "ubuntu-Debug-synchronous-no-re2" + }, + { + "type": "build", + "name": "ubuntu-Debug-synchronous-no-re2" + }, + { + "type": "test", + "name": "ubuntu-Debug-synchronous-no-re2" + } + ] } ] } From a7c7ffbefedc77c408517437461c9b4af38b82e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 07:08:22 +0000 Subject: [PATCH 18/23] ci: extend no-re2 test matrix to windows and macos --- .github/workflows/ci.yml | 8 +++++ CMakePresets.json | 73 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b83c702d..6d7212f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,6 +88,14 @@ jobs: version: latest type: synchronous preset_suffix: "-no-re2" + - os: windows + version: latest + type: synchronous + preset_suffix: "-no-re2" + - os: macos + version: latest + type: synchronous + preset_suffix: "-no-re2" fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/CMakePresets.json b/CMakePresets.json index 76482193..4e9f5dca 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -65,6 +65,23 @@ "CCR_USE_RE2": "Off" } }, + { + "name": "windows-Debug-synchronous-no-re2", + "displayName": "Configuration for Windows Debug and Tests, Single Config Generator, synchronous, without RE2", + "inherits": "host-single-Debug-synchronous", + "toolchainFile": "${sourceDir}/cmake/Windows.MSVC.toolchain.cmake", + "cacheVariables": { + "CCR_USE_RE2": "Off" + } + }, + { + "name": "macos-Debug-synchronous-no-re2", + "displayName": "Configuration for macOS Debug and Tests, Single Config Generator, synchronous, without RE2", + "inherits": "host-single-Debug-synchronous", + "cacheVariables": { + "CCR_USE_RE2": "Off" + } + }, { "name": "host-single-time-profile", "displayName": "Configuration time profiling", @@ -137,6 +154,16 @@ "configuration": "Debug", "configurePreset": "ubuntu-Debug-synchronous-no-re2" }, + { + "name": "windows-Debug-synchronous-no-re2", + "configuration": "Debug", + "configurePreset": "windows-Debug-synchronous-no-re2" + }, + { + "name": "macos-Debug-synchronous-no-re2", + "configuration": "Debug", + "configurePreset": "macos-Debug-synchronous-no-re2" + }, { "name": "host-single-time-profile", "configuration": "Debug", @@ -211,6 +238,18 @@ "configurePreset": "ubuntu-Debug-synchronous-no-re2", "configuration": "Debug", "inherits": "defaults" + }, + { + "name": "windows-Debug-synchronous-no-re2", + "configurePreset": "windows-Debug-synchronous-no-re2", + "configuration": "Debug", + "inherits": "defaults" + }, + { + "name": "macos-Debug-synchronous-no-re2", + "configurePreset": "macos-Debug-synchronous-no-re2", + "configuration": "Debug", + "inherits": "defaults" } ], "workflowPresets": [ @@ -281,6 +320,40 @@ "name": "ubuntu-Debug-synchronous-no-re2" } ] + }, + { + "name": "windows-Debug-synchronous-no-re2", + "steps": [ + { + "type": "configure", + "name": "windows-Debug-synchronous-no-re2" + }, + { + "type": "build", + "name": "windows-Debug-synchronous-no-re2" + }, + { + "type": "test", + "name": "windows-Debug-synchronous-no-re2" + } + ] + }, + { + "name": "macos-Debug-synchronous-no-re2", + "steps": [ + { + "type": "configure", + "name": "macos-Debug-synchronous-no-re2" + }, + { + "type": "build", + "name": "macos-Debug-synchronous-no-re2" + }, + { + "type": "test", + "name": "macos-Debug-synchronous-no-re2" + } + ] } ] } From d8edcca50dd8be9d8877ee4be490a79736a8d781 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 07:14:47 +0000 Subject: [PATCH 19/23] ci: simplify no-re2 matrix by adding preset_suffix entry instead of includes --- .github/workflows/ci.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d7212f7..c8c8e542 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,20 +82,7 @@ jobs: os: [macos, windows, ubuntu] version: [latest] type: [synchronous] - preset_suffix: [""] - include: - - os: ubuntu - version: latest - type: synchronous - preset_suffix: "-no-re2" - - os: windows - version: latest - type: synchronous - preset_suffix: "-no-re2" - - os: macos - version: latest - type: synchronous - preset_suffix: "-no-re2" + preset_suffix: ["", "-no-re2"] fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 37f8bba20651e72d45c2b64ee660682a31320377 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 27 May 2026 21:55:04 +0000 Subject: [PATCH 20/23] refactor: simplify Re2RegexStrategy by removing unique_ptr and updating member access --- .../cucumber_expression/Re2RegexStrategy.cpp | 13 +++++-------- .../cucumber_expression/Re2RegexStrategy.hpp | 10 ++-------- .../cucumber_expression/RegexStrategyFactory.cpp | 3 +++ .../cucumber_expression/StdRegexStrategy.cpp | 10 +++------- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp index dfb3ab35..8b51d437 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp @@ -2,7 +2,6 @@ #include "absl/strings/string_view.h" #include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include -#include #include #include #include @@ -13,21 +12,19 @@ namespace cucumber_cpp::library::cucumber_expression { Re2RegexStrategy::Re2RegexStrategy(std::string_view pattern) - : re2{ std::make_unique(pattern) } + : re2{ pattern } { - if (!re2->ok()) - throw std::invalid_argument(re2->error()); + if (!re2.ok()) + throw std::invalid_argument(re2.error()); } - Re2RegexStrategy::~Re2RegexStrategy() = default; - std::optional Re2RegexStrategy::Match(std::string_view text) const { - const int nCaptures = re2->NumberOfCapturingGroups(); + const int nCaptures = re2.NumberOfCapturingGroups(); const int nSubmatch = nCaptures + 1; std::vector submatch(static_cast(nSubmatch)); - if (!re2->Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) + if (!re2.Match(text, 0, static_cast(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch)) return std::nullopt; Matches result; diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp index 7e403a42..1b4c800a 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp @@ -2,26 +2,20 @@ #define CUCUMBER_EXPRESSION_RE2REGEXSTRATEGY_HPP #include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" -#include +#include "re2/re2.h" #include #include -namespace re2 -{ - class RE2; -} - namespace cucumber_cpp::library::cucumber_expression { struct Re2RegexStrategy : RegexStrategy { explicit Re2RegexStrategy(std::string_view pattern); - ~Re2RegexStrategy() override; [[nodiscard]] std::optional Match(std::string_view text) const override; private: - std::unique_ptr re2; + re2::RE2 re2; }; } diff --git a/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp index bf09ef3c..fcf3baf0 100644 --- a/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp +++ b/cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.cpp @@ -1,9 +1,12 @@ #include "cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp" +#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp" #include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp" + #ifdef CCR_HAS_RE2 #include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp" #include #endif + #include #include diff --git a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp index 400b8a69..ecd527e8 100644 --- a/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp @@ -5,7 +5,6 @@ #include #include #include -#include namespace cucumber_cpp::library::cucumber_expression { @@ -23,16 +22,13 @@ namespace cucumber_cpp::library::cucumber_expression Matches result; result.reserve(match.size()); - for (std::size_t i = 0; i < match.size(); ++i) + for (std::smatch::size_type i = 0; i < match.size(); ++i) { - const auto& m = match[i]; - if (!m.matched) - { + if (const auto& m = match[i]; !m.matched) result.emplace_back(std::nullopt); - } else { - const auto start = static_cast(match.position(static_cast(i))); + const auto start = static_cast(match.position(i)); result.emplace_back(MatchGroup{ .value = m.str(), .start = start, From 977ef3b4514ad40a60febbf0af2c55f9186d0543 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 10:25:12 +0000 Subject: [PATCH 21/23] Gate find_package(absl) and find_package(re2) on CCR_USE_RE2 --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c22ae88..f97a7cad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,8 +76,10 @@ else() find_package(yaml-cpp REQUIRED) endif() - find_package(absl) - find_package(re2) + if (CCR_USE_RE2) + find_package(absl) + find_package(re2) + endif() endif() if (TARGET re2::re2) From 412b9ea83696a3b8f4f9cfb38e08fa408aaa1246 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 10:26:19 +0000 Subject: [PATCH 22/23] fix: construct RE2 with explicit absl::string_view --- cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp index 8b51d437..a91318ef 100644 --- a/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp +++ b/cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp @@ -12,7 +12,7 @@ namespace cucumber_cpp::library::cucumber_expression { Re2RegexStrategy::Re2RegexStrategy(std::string_view pattern) - : re2{ pattern } + : re2{ absl::string_view{ pattern.data(), pattern.size() } } { if (!re2.ok()) throw std::invalid_argument(re2.error()); From 928f84ecc9d89e6b23751bc53e87e4e2ddf05343 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 11:30:27 +0000 Subject: [PATCH 23/23] docs: document cmake options and fetch deps usage --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a6b5e56d..bd56cbc1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,21 @@ amp-cucumber-cpp-runner is known to build using: amp-cucumber-cpp-runner is not supposed to be used standalone. It is possible to add amp-cucumber-cpp-runner as a submodule to your own project or to use cmake's FetchContent to add amp-cucumber-cpp-runner as a cmake build dependency. -En example project is provided which shows most features of amp-cucumber-cpp-runner and how to configure a cmake project to use amp-cucumber-cpp-runner. The simplest solution is to simply add a dependency on `cucumber-cpp-runner` like so: +An example project is provided which shows most features of amp-cucumber-cpp-runner and how to configure a cmake project to use amp-cucumber-cpp-runner. + +### CMake options + +The following CMake options are supported: + +- `CCR_FETCH_DEPS` (default: `Off` when used as a dependency): fetch third-party dependencies via `FetchContent`. +- `CCR_BUILD_TESTS` (default: `Off` when used as a dependency): enable building unit/integration tests in this repository. +- `CCR_USE_RE2` (default: `Off` when used as a dependency): enable optional RE2 usage (`abseil-cpp` + `re2`). +- `CCR_ENABLE_COVERAGE` (default: `Off`): enable compiler flags for coverage measurements. +- `CCR_ENABLE_TIME_PROFILE` (default: `Off`): enable compiler time-trace profiling flags (Clang). + +### FetchContent example + +If you want this project to fetch its dependencies, set `CCR_FETCH_DEPS` explicitly to `On`: ```cmake FetchContent_Declare(cucumber_cpp @@ -25,6 +39,10 @@ FetchContent_Declare(cucumber_cpp GIT_TAG main ) +set(CCR_FETCH_DEPS On CACHE BOOL "Fetch amp-cucumber-cpp-runner dependencies") +# optional: +# set(CCR_USE_RE2 On CACHE BOOL "Enable RE2 backend") + FetchContent_MakeAvailable(cucumber_cpp) add_executable(my_test_runner)