added compile time pattern parsing

This commit is contained in:
2026-06-10 04:08:33 +03:00
parent 8f6341e840
commit ce589b4f17
3 changed files with 174 additions and 8 deletions
+127 -5
View File
@@ -3,9 +3,12 @@
// //
#pragma once #pragma once
#include <algorithm>
#include <array>
#include <expected> #include <expected>
#include <optional> #include <optional>
#include <span> #include <span>
#include <stdexcept>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -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_2_Test;
class unit_test_pattern_scan_corner_case_3_Test; class unit_test_pattern_scan_corner_case_3_Test;
class unit_test_pattern_scan_corner_case_4_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 // ReSharper restore CppInconsistentNaming
namespace omath namespace omath
{ {
@@ -29,8 +34,21 @@ namespace omath
friend unit_test_pattern_scan_corner_case_2_Test; 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_3_Test;
friend unit_test_pattern_scan_corner_case_4_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: public:
template<std::size_t N>
struct fixed_string final
{
char value[N]{};
constexpr fixed_string(const char (&text)[N])
{
std::ranges::copy(text, value);
}
};
[[nodiscard]] [[nodiscard]]
static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range, static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range,
const std::string_view& pattern); const std::string_view& pattern);
@@ -49,9 +67,26 @@ namespace omath
if (!parsed_pattern) [[unlikely]] if (!parsed_pattern) [[unlikely]]
return end; return end;
return scan_for_parsed_pattern(begin, end, parsed_pattern.value());
}
template<fixed_string Pattern, class IteratorType>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end)
{
constexpr auto parsed_pattern = parse_pattern<Pattern>();
return scan_for_parsed_pattern(begin, end, parsed_pattern);
}
private:
template<class IteratorType, class ParsedPattern>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end,
const ParsedPattern& parsed_pattern)
{
const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end)); const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size()); const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern.size());
const std::ptrdiff_t scan_size = whole_range_size - pattern_size; const std::ptrdiff_t scan_size = whole_range_size - pattern_size;
if (scan_size < 0) if (scan_size < 0)
@@ -61,9 +96,9 @@ namespace omath
{ {
bool found = true; bool found = true;
for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern->size()); j++) for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(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) if (!found)
break; break;
@@ -73,10 +108,97 @@ namespace omath
} }
return end; return end;
} }
private:
[[nodiscard]] [[nodiscard]]
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError> static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
parse_pattern(const std::string_view& pattern_string); 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<fixed_string Pattern>
[[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<fixed_string Pattern>
static consteval std::array<std::optional<std::byte>, signature_size<Pattern>()> parse_pattern()
{
std::array<std::optional<std::byte>, signature_size<Pattern>()> 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<std::byte>((high << 4) | low);
}
return result;
}
}; };
} // namespace omath } // namespace omath
+35 -1
View File
@@ -4,8 +4,8 @@
#include "omath/utility/pe_pattern_scan.hpp" #include "omath/utility/pe_pattern_scan.hpp"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include <omath/utility/pattern_scan.hpp> #include <omath/utility/pattern_scan.hpp>
#include <source_location>
#include <print> #include <print>
#include <source_location>
TEST(unit_test_pattern_scan, read_test) TEST(unit_test_pattern_scan, read_test)
{ {
const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9"); const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9");
@@ -51,3 +51,37 @@ TEST(unit_test_pattern_scan, corner_case_4)
const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 "); const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 ");
EXPECT_FALSE(result.has_value()); EXPECT_FALSE(result.has_value());
} }
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<std::byte>(0xFF));
static_assert(result[1] == std::nullopt);
static_assert(result[2] == std::nullopt);
static_assert(result[3] == static_cast<std::byte>(0xE9));
EXPECT_EQ(result[0], static_cast<std::byte>(0xFF));
EXPECT_EQ(result[1], std::nullopt);
EXPECT_EQ(result[2], std::nullopt);
EXPECT_EQ(result[3], static_cast<std::byte>(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<std::byte>(0xDE));
static_assert(result[1] == static_cast<std::byte>(0xAD));
static_assert(result[2] == std::nullopt);
static_assert(result[3] == std::nullopt);
static_assert(result[4] == static_cast<std::byte>(0xEF));
EXPECT_EQ(result[0], static_cast<std::byte>(0xDE));
EXPECT_EQ(result[1], static_cast<std::byte>(0xAD));
EXPECT_EQ(result[2], std::nullopt);
EXPECT_EQ(result[3], std::nullopt);
EXPECT_EQ(result[4], static_cast<std::byte>(0xEF));
}
@@ -14,6 +14,16 @@ TEST(unit_test_pattern_scan_extra, IteratorScanFound)
EXPECT_EQ(std::distance(buf.begin(), it), 0); EXPECT_EQ(std::distance(buf.begin(), it), 0);
} }
TEST(unit_test_pattern_scan_extra, ConstevalIteratorScan)
{
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0xDE),
static_cast<std::byte>(0xAD), static_cast<std::byte>(0xBE),
static_cast<std::byte>(0xEF), static_cast<std::byte>(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) TEST(unit_test_pattern_scan_extra, IteratorScanNotFound)
{ {
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11), std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11),