mirror of
https://github.com/orange-cpp/omath.git
synced 2026-06-10 09:14:34 +00:00
added compile time pattern parsing
This commit is contained in:
@@ -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
|
||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user