From ce589b4f1791948793d4545918c7d8f17129ec27 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 10 Jun 2026 04:08:33 +0300 Subject: [PATCH] added compile time pattern parsing --- include/omath/utility/pattern_scan.hpp | 134 +++++++++++++++++- tests/general/unit_test_pattern_scan.cpp | 38 ++++- .../general/unit_test_pattern_scan_extra.cpp | 10 ++ 3 files changed, 174 insertions(+), 8 deletions(-) diff --git a/include/omath/utility/pattern_scan.hpp b/include/omath/utility/pattern_scan.hpp index 9f8dd93..d3d5679 100644 --- a/include/omath/utility/pattern_scan.hpp +++ b/include/omath/utility/pattern_scan.hpp @@ -3,9 +3,12 @@ // #pragma once +#include +#include #include #include #include +#include #include #include @@ -15,6 +18,8 @@ class unit_test_pattern_scan_corner_case_1_Test; class unit_test_pattern_scan_corner_case_2_Test; class unit_test_pattern_scan_corner_case_3_Test; class unit_test_pattern_scan_corner_case_4_Test; +class unit_test_pattern_scan_consteval_read_test_Test; +class unit_test_pattern_scan_consteval_spacing_and_case_Test; // ReSharper restore CppInconsistentNaming namespace omath { @@ -29,8 +34,21 @@ namespace omath friend unit_test_pattern_scan_corner_case_2_Test; friend unit_test_pattern_scan_corner_case_3_Test; friend unit_test_pattern_scan_corner_case_4_Test; + friend unit_test_pattern_scan_consteval_read_test_Test; + friend unit_test_pattern_scan_consteval_spacing_and_case_Test; public: + template + struct fixed_string final + { + char value[N]{}; + + constexpr fixed_string(const char (&text)[N]) + { + std::ranges::copy(text, value); + } + }; + [[nodiscard]] static std::span::iterator scan_for_pattern(const std::span& range, const std::string_view& pattern); @@ -49,9 +67,26 @@ namespace omath if (!parsed_pattern) [[unlikely]] return end; + return scan_for_parsed_pattern(begin, end, parsed_pattern.value()); + } + template + requires std::input_or_output_iterator> + static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end) + { + constexpr auto parsed_pattern = parse_pattern(); + + return scan_for_parsed_pattern(begin, end, parsed_pattern); + } + + private: + template + requires std::input_or_output_iterator> + static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end, + const ParsedPattern& parsed_pattern) + { const auto whole_range_size = static_cast(std::distance(begin, end)); - const auto pattern_size = static_cast(parsed_pattern->size()); + const auto pattern_size = static_cast(parsed_pattern.size()); const std::ptrdiff_t scan_size = whole_range_size - pattern_size; if (scan_size < 0) @@ -61,9 +96,9 @@ namespace omath { bool found = true; - for (std::ptrdiff_t j = 0; j < static_cast(parsed_pattern->size()); j++) + for (std::ptrdiff_t j = 0; j < static_cast(parsed_pattern.size()); j++) { - found = parsed_pattern->at(j) == std::nullopt || parsed_pattern->at(j) == *(begin + i + j); + found = parsed_pattern.at(j) == std::nullopt || parsed_pattern.at(j) == *(begin + i + j); if (!found) break; @@ -73,10 +108,97 @@ namespace omath } return end; } - - private: [[nodiscard]] static std::expected>, PatternScanError> parse_pattern(const std::string_view& pattern_string); + + [[nodiscard]] + constexpr static bool is_space(const char c) + { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + + [[nodiscard]] + constexpr static int hex_value(const char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; + } + template + [[nodiscard]] + static consteval std::size_t signature_size() + { + std::size_t count = 0; + bool in_token = false; + + for (std::size_t i = 0; i + 1 < sizeof(Pattern.value); ++i) + { + if (is_space(Pattern.value[i])) + { + in_token = false; + } + else if (!in_token) + { + ++count; + in_token = true; + } + } + + return count; + } + + template + static consteval std::array, signature_size()> parse_pattern() + { + std::array, signature_size()> result{}; + std::size_t out = 0; + std::size_t i = 0; + + while (i + 1 < sizeof(Pattern.value)) + { + while (i + 1 < sizeof(Pattern.value) && is_space(Pattern.value[i])) + ++i; + + const std::size_t token_start = i; + + while (i + 1 < sizeof(Pattern.value) && !is_space(Pattern.value[i])) + ++i; + + const std::size_t token_size = i - token_start; + + if (token_size == 0) + continue; + + // ReSharper disable once CppTooWideScope + const bool is_wildcard = (token_size == 1 || token_size == 2) && Pattern.value[token_start] == '?'; + + if (is_wildcard) + { + if (token_size == 2 && Pattern.value[token_start + 1] != '?') + throw std::logic_error("invalid wildcard token"); + + result[out++] = std::nullopt; + continue; + } + + if (token_size != 2) + throw std::logic_error("invalid byte token"); + + const int high = hex_value(Pattern.value[token_start]); + const int low = hex_value(Pattern.value[token_start + 1]); + + if (high < 0 || low < 0) + throw std::logic_error("invalid hex byte"); + + result[out++] = static_cast((high << 4) | low); + } + + return result; + } }; -} // namespace omath \ No newline at end of file +} // namespace omath diff --git a/tests/general/unit_test_pattern_scan.cpp b/tests/general/unit_test_pattern_scan.cpp index b974c40..c74091d 100644 --- a/tests/general/unit_test_pattern_scan.cpp +++ b/tests/general/unit_test_pattern_scan.cpp @@ -4,8 +4,8 @@ #include "omath/utility/pe_pattern_scan.hpp" #include "gtest/gtest.h" #include -#include #include +#include TEST(unit_test_pattern_scan, read_test) { const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9"); @@ -50,4 +50,38 @@ TEST(unit_test_pattern_scan, corner_case_4) { const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 "); EXPECT_FALSE(result.has_value()); -} \ No newline at end of file +} + +TEST(unit_test_pattern_scan, consteval_read_test) +{ + constexpr auto result = omath::PatternScanner::parse_pattern<"FF ? ?? E9">(); + + static_assert(result.size() == 4); + static_assert(result[0] == static_cast(0xFF)); + static_assert(result[1] == std::nullopt); + static_assert(result[2] == std::nullopt); + static_assert(result[3] == static_cast(0xE9)); + + EXPECT_EQ(result[0], static_cast(0xFF)); + EXPECT_EQ(result[1], std::nullopt); + EXPECT_EQ(result[2], std::nullopt); + EXPECT_EQ(result[3], static_cast(0xE9)); +} + +TEST(unit_test_pattern_scan, consteval_spacing_and_case) +{ + constexpr auto result = omath::PatternScanner::parse_pattern<" \tde\nAD\r?? ? ef ">(); + + static_assert(result.size() == 5); + static_assert(result[0] == static_cast(0xDE)); + static_assert(result[1] == static_cast(0xAD)); + static_assert(result[2] == std::nullopt); + static_assert(result[3] == std::nullopt); + static_assert(result[4] == static_cast(0xEF)); + + EXPECT_EQ(result[0], static_cast(0xDE)); + EXPECT_EQ(result[1], static_cast(0xAD)); + EXPECT_EQ(result[2], std::nullopt); + EXPECT_EQ(result[3], std::nullopt); + EXPECT_EQ(result[4], static_cast(0xEF)); +} diff --git a/tests/general/unit_test_pattern_scan_extra.cpp b/tests/general/unit_test_pattern_scan_extra.cpp index cb37173..d39a591 100644 --- a/tests/general/unit_test_pattern_scan_extra.cpp +++ b/tests/general/unit_test_pattern_scan_extra.cpp @@ -14,6 +14,16 @@ TEST(unit_test_pattern_scan_extra, IteratorScanFound) EXPECT_EQ(std::distance(buf.begin(), it), 0); } +TEST(unit_test_pattern_scan_extra, ConstevalIteratorScan) +{ + std::vector buf = {static_cast(0x00), static_cast(0xDE), + static_cast(0xAD), static_cast(0xBE), + static_cast(0xEF), static_cast(0x11)}; + const auto it = PatternScanner::scan_for_pattern<"DE ?? BE EF">(buf.begin(), buf.end()); + EXPECT_NE(it, buf.end()); + EXPECT_EQ(std::distance(buf.begin(), it), 1); +} + TEST(unit_test_pattern_scan_extra, IteratorScanNotFound) { std::vector buf = {static_cast(0x00), static_cast(0x11),