Skip to content

Commit fc0e4da

Browse files
committed
introduced bundled preprocessing
1 parent be6ba3a commit fc0e4da

13 files changed

Lines changed: 187 additions & 179 deletions

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ file(GLOB_RECURSE ORYX_CHRON_HEADERS
6767
target_sources(${PROJECT_NAME}
6868
PRIVATE
6969
src/clock.cpp
70+
src/preprocessor.cpp
7071
src/randomization.cpp
7172
src/schedule.cpp
7273
src/task.cpp

include/oryx/chron/details/parser.hpp

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
#include <oryx/chron/chron_data.hpp>
1010
#include <oryx/chron/time_types.hpp>
1111

12-
#include "string_split.hpp"
13-
#include "time_types.hpp"
1412
#include "string_cast.hpp"
1513
#include "in_range.hpp"
1614
#include "to_underlying.hpp"
@@ -150,27 +148,11 @@ struct Parser {
150148
return false; // Invalid format
151149
}
152150

153-
template <chron::traits::TimeType T>
154-
static auto ProcessParts(auto&& parts, std::set<T>& numbers) -> bool {
155-
return std::ranges::all_of(parts, [&numbers](auto&& part) {
156-
if constexpr (std::is_convertible_v<decltype(part), std::string_view>)
157-
return ConvertFromStringRangeToNumberRange<T>(part, numbers);
158-
else
159-
return ConvertFromStringRangeToNumberRange(std::string_view(&*part.begin(), part.size()), numbers);
160-
});
161-
}
162-
163151
template <chron::traits::TimeType T>
164152
static auto ValidateNumeric(std::string_view s, std::set<T>& numbers) -> bool {
165-
return ProcessParts(std::views::split(s, ','), numbers);
166-
}
167-
168-
template <chron::traits::TimeType T>
169-
static auto ValidateLiteral(const std::string& s, std::set<T>& numbers, std::span<const std::string_view> names)
170-
-> bool {
171-
auto parts = details::StringSplit(s, ',');
172-
std::ranges::for_each(parts, [&names](auto&& part) { details::ReplaceWithNumeric<T>(part, names); });
173-
return ProcessParts(parts, numbers);
153+
return std::ranges::all_of(std::views::split(s, ','), [&numbers](auto&& part) {
154+
return ConvertFromStringRangeToNumberRange(std::string_view(&*part.begin(), part.size()), numbers);
155+
});
174156
}
175157

176158
static auto CheckDomVsDow(std::string_view dom, std::string_view dow) -> bool {
@@ -193,7 +175,7 @@ struct Parser {
193175

194176
// If only day 31 is selected, ensure at least one month allows it
195177
if (data.days.size() == 1 && data.days.contains(MonthDays::Last)) {
196-
if (!std::ranges::any_of(details::kMonthsWith31, [&data](Months m) { return data.months.contains(m); })) {
178+
if (!std::ranges::any_of(kMonthsWith31, [&data](Months m) { return data.months.contains(m); })) {
197179
return false;
198180
}
199181
}

include/oryx/chron/details/string_split.hpp

Lines changed: 0 additions & 21 deletions
This file was deleted.

include/oryx/chron/details/time_types.hpp

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#pragma once
2+
3+
#include <oryx/chron/traits.hpp>
4+
5+
namespace oryx::chron {
6+
7+
template <traits::Processor... Ps>
8+
struct Processors;
9+
10+
template <>
11+
struct Processors<> {
12+
static void Process(std::string&) noexcept {}
13+
};
14+
15+
template <traits::Processor Curr, traits::Processor... Rest>
16+
struct Processors<Curr, Rest...> {
17+
static auto Process(std::string data) noexcept -> std::string {
18+
if constexpr (sizeof...(Rest) == 0)
19+
return Curr::Process(std::move(data));
20+
else
21+
return Processors<Rest...>::Process(Curr::Process(std::move(data)));
22+
}
23+
};
24+
25+
struct DollarExpressionProcessor {
26+
static auto Process(std::string data) noexcept -> std::string;
27+
};
28+
29+
struct WeekMonthDayLiteralProcessor {
30+
static auto Process(std::string data) noexcept -> std::string;
31+
};
32+
33+
template <traits::Processor... Ps>
34+
auto PreprocessExpression(std::string data) noexcept -> std::string {
35+
return Processors<Ps...>::Process(data);
36+
}
37+
38+
static_assert(traits::Processor<DollarExpressionProcessor>);
39+
static_assert(traits::Processor<WeekMonthDayLiteralProcessor>);
40+
41+
} // namespace oryx::chron

include/oryx/chron/time_types.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <cstdint>
4+
#include <array>
45

56
namespace oryx::chron {
67

@@ -41,4 +42,7 @@ enum class Months : uint8_t {
4142
Last = December
4243
};
4344

45+
inline constexpr std::array<Months, 7> kMonthsWith31{Months::January, Months::March, Months::May, Months::July,
46+
Months::August, Months::October, Months::December};
47+
4448
} // namespace oryx::chron

include/oryx/chron/traits.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <concepts>
55
#include <optional>
66
#include <string_view>
7+
#include <type_traits>
78

89
#include "chrono_types.hpp"
910
#include "chron_data.hpp"
@@ -33,4 +34,9 @@ concept Parser = requires(T t, std::string_view sv) {
3334
{ t(sv) } -> std::same_as<std::optional<ChronData>>;
3435
};
3536

37+
template <typename T>
38+
concept Processor = requires(T t, std::string s) {
39+
{ T::Process(s) };
40+
};
41+
3642
} // namespace oryx::chron::traits

src/parser.cpp

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,29 @@
33
#include <optional>
44

55
#include <oryx/chron/details/parser.hpp>
6+
#include <oryx/chron/preprocessor.hpp>
67

78
namespace oryx::chron {
8-
namespace {
9-
10-
struct DollarExprPair {
11-
std::string_view expr;
12-
std::string_view cron;
13-
};
14-
15-
} // namespace
169

1710
auto ExpressionParser::operator()(std::string_view cron_expression) const -> std::optional<ChronData> {
18-
static constexpr std::array<DollarExprPair, 6> kShortcuts{
19-
DollarExprPair("@yearly", "0 0 0 1 1 *"), DollarExprPair("@annually", "0 0 0 1 1 *"),
20-
DollarExprPair("@monthly", "0 0 0 1 * *"), DollarExprPair("@weekly", "0 0 0 * * 0"),
21-
DollarExprPair("@daily", "0 0 0 * * ?"), DollarExprPair("@hourly", "0 0 * * * ?")};
22-
23-
if (!cron_expression.empty() && cron_expression[0] == '@') {
24-
auto it = std::ranges::find(kShortcuts, cron_expression, &DollarExprPair::expr);
25-
if (it != kShortcuts.end()) [[likely]] {
26-
cron_expression = it->cron;
27-
}
11+
static constexpr auto matcher = ctre::match<R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#">;
12+
13+
auto preprocessed =
14+
PreprocessExpression<DollarExpressionProcessor, WeekMonthDayLiteralProcessor>(std::string(cron_expression));
15+
auto match = matcher(std::move(preprocessed));
16+
if (!match) [[unlikely]] {
17+
return std::nullopt;
2818
}
2919

30-
bool valid{};
3120
ChronData data{};
32-
if (auto match = ctre::match<R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#">(cron_expression))
33-
[[likely]] {
34-
valid = details::Parser::ValidateNumeric<Seconds>(match.get<1>().to_view(), data.seconds);
35-
valid &= details::Parser::ValidateNumeric<Minutes>(match.get<2>().to_view(), data.minutes);
36-
valid &= details::Parser::ValidateNumeric<Hours>(match.get<3>().to_view(), data.hours);
37-
valid &= details::Parser::ValidateNumeric<MonthDays>(match.get<4>().to_view(), data.days);
38-
valid &=
39-
details::Parser::ValidateLiteral<Months>(match.get<5>().to_string(), data.months, details::kMonthNames);
40-
valid &= details::Parser::ValidateLiteral<Weekdays>(match.get<6>().to_string(), data.weeks, details::kDayNames);
41-
valid &= details::Parser::CheckDomVsDow(match.get<4>().to_view(), match.get<6>().to_view());
42-
valid &= details::Parser::ValidateDateVsMonths(data);
43-
}
21+
bool valid = details::Parser::ValidateNumeric<Seconds>(match.get<1>().to_view(), data.seconds);
22+
valid &= details::Parser::ValidateNumeric<Minutes>(match.get<2>().to_view(), data.minutes);
23+
valid &= details::Parser::ValidateNumeric<Hours>(match.get<3>().to_view(), data.hours);
24+
valid &= details::Parser::ValidateNumeric<MonthDays>(match.get<4>().to_view(), data.days);
25+
valid &= details::Parser::ValidateNumeric<Months>(match.get<5>().to_view(), data.months);
26+
valid &= details::Parser::ValidateNumeric<Weekdays>(match.get<6>().to_view(), data.weeks);
27+
valid &= details::Parser::CheckDomVsDow(match.get<4>().to_view(), match.get<6>().to_view());
28+
valid &= details::Parser::ValidateDateVsMonths(data);
4429

4530
if (!valid) [[unlikely]] {
4631
return std::nullopt;

src/preprocessor.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include <oryx/chron/preprocessor.hpp>
2+
3+
#include <algorithm>
4+
#include <string_view>
5+
#include <span>
6+
7+
#include <oryx/chron/details/ctre.hpp>
8+
#include <oryx/chron/details/to_underlying.hpp>
9+
10+
namespace oryx::chron {
11+
namespace {
12+
13+
struct DollarExpressionPair {
14+
std::string_view expr;
15+
std::string_view cron;
16+
};
17+
18+
template <typename Enum>
19+
requires std::is_enum_v<Enum>
20+
auto ReplaceWithNumeric(std::string data, std::span<const std::string_view> names) -> std::string {
21+
static auto find_icase = [](std::string_view haystack, std::string_view needle) {
22+
return std::ranges::search(
23+
haystack, needle, [](const int lhs, const int rhs) { return lhs == rhs; },
24+
[](const int lhs) { return std::toupper(lhs); });
25+
};
26+
27+
auto value = details::to_underlying(Enum::First);
28+
29+
std::string cached_str_value;
30+
for (auto& name : names) {
31+
std::string_view view{data};
32+
size_t search_start = 0;
33+
34+
while (search_start < view.size()) {
35+
auto subview = view.substr(search_start);
36+
auto found = find_icase(subview, name);
37+
38+
if (found.empty()) {
39+
break;
40+
}
41+
42+
if (cached_str_value.empty()) {
43+
cached_str_value = std::to_string(value);
44+
}
45+
46+
auto found_pos = search_start + std::distance(subview.begin(), found.begin());
47+
data.replace(found_pos, name.size(), cached_str_value);
48+
49+
// Update part_view to reflect the changes and move search position
50+
view = data;
51+
search_start = found_pos + cached_str_value.size();
52+
}
53+
cached_str_value.clear();
54+
value++;
55+
}
56+
return data;
57+
}
58+
59+
} // namespace
60+
61+
auto DollarExpressionProcessor::Process(std::string data) noexcept -> std::string {
62+
static constexpr std::array<DollarExpressionPair, 6> kExpressions{
63+
DollarExpressionPair("@yearly", "0 0 0 1 1 *"), DollarExpressionPair("@annually", "0 0 0 1 1 *"),
64+
DollarExpressionPair("@monthly", "0 0 0 1 * *"), DollarExpressionPair("@weekly", "0 0 0 * * 0"),
65+
DollarExpressionPair("@daily", "0 0 0 * * ?"), DollarExpressionPair("@hourly", "0 0 * * * ?")};
66+
67+
if (!data.empty() && data[0] == '@') {
68+
auto it = std::ranges::find(kExpressions, data, &DollarExpressionPair::expr);
69+
if (it != kExpressions.end()) [[likely]] {
70+
return std::string(it->cron);
71+
}
72+
}
73+
return data;
74+
}
75+
76+
auto WeekMonthDayLiteralProcessor::Process(std::string data) noexcept -> std::string {
77+
static constexpr std::array<std::string_view, 12> kMonthNames{"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
78+
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
79+
80+
static constexpr std::array<std::string_view, 7> kDayNames{"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
81+
static constexpr auto matcher = ctre::match<R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#">;
82+
83+
auto match = matcher(data);
84+
if (!match) [[unlikely]] {
85+
return data;
86+
}
87+
88+
auto month = ReplaceWithNumeric<Months>(match.get<5>().to_string(), kMonthNames);
89+
auto dow = ReplaceWithNumeric<Weekdays>(match.get<6>().to_string(), kDayNames);
90+
return std::format("{} {} {} {} {} {}", match.get<1>().to_view(), match.get<2>().to_view(),
91+
match.get<3>().to_view(), match.get<4>().to_view(), month, dow);
92+
}
93+
94+
} // namespace oryx::chron

0 commit comments

Comments
 (0)