1use crate::Expr;
2use crate::Expr::{AnyValueExpr, CronExpr, LastValueExpr, ListExpr, NoOp, PerExpr, RangeExpr, ValueExpr};
3use oni_comb_parser_rs::prelude::*;
4
5fn min_digit<'a>() -> Parser<'a, u8, Expr> {
6 (elm_of(b"12345") + elm_of(b"0123456789"))
7 .attempt()
8 .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
9 | (elm(b'0') * elm_of(b"0123456789")).attempt().map(|e| ValueExpr(e - 48))
10 | (elm_of(b"0123456789")).map(|e| ValueExpr(e - 48))
11}
12
13fn hour_digit<'a>() -> Parser<'a, u8, Expr> {
14 (elm(b'2') + elm_of(b"0123"))
15 .attempt()
16 .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
17 | (elm(b'1') + elm_of(b"0123456789"))
18 .attempt()
19 .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
20 | (elm(b'0') * elm_of(b"0123456789")).attempt().map(|e| ValueExpr(e - 48))
21 | elm_of(b"0123456789").map(|e| ValueExpr(e - 48))
22}
23
24fn day_digit<'a>() -> Parser<'a, u8, Expr> {
25 (elm(b'3') + elm_of(b"01"))
26 .attempt()
27 .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
28 | (elm_of(b"12") + elm_of(b"0123456789"))
29 .attempt()
30 .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
31 | (elm(b'0') * elm_of(b"123456789")).attempt().map(|e| ValueExpr(e - 48))
32 | elm_of(b"123456789").map(|e| ValueExpr(e - 48))
33}
34
35fn month_digit<'a>() -> Parser<'a, u8, Expr> {
36 (elm(b'1') + elm_of(b"012"))
37 .attempt()
38 .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
39 | (elm(b'0') * elm_of(b"123456789")).attempt().map(|e| ValueExpr(e - 48))
40 | elm_of(b"123456789").map(|e| ValueExpr(e - 48))
41}
42
43fn day_of_week_digit<'a>() -> Parser<'a, u8, Expr> {
44 seq(b"SUN").attempt().map(|_| ValueExpr(1))
45 | seq(b"MON").attempt().map(|_| ValueExpr(2))
46 | seq(b"TUE").attempt().map(|_| ValueExpr(3))
47 | seq(b"WED").attempt().map(|_| ValueExpr(4))
48 | seq(b"THU").attempt().map(|_| ValueExpr(5))
49 | seq(b"FRI").attempt().map(|_| ValueExpr(6))
50 | seq(b"SAT").attempt().map(|_| ValueExpr(7))
51 | elm(b'L').map(|_| LastValueExpr)
52}
53
54fn day_of_week_text<'a>() -> Parser<'a, u8, Expr> {
55 elm_of(b"1234567").map(|e| ValueExpr(e as u8 - 48))
56}
57
58fn asterisk<'a>() -> Parser<'a, u8, Expr> {
59 elm(b'*').map(|_| AnyValueExpr)
60}
61
62fn per(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
63 elm(b'/') * p
64}
65
66fn asterisk_per(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
67 (asterisk() + per(p)).map(|(d, op)| PerExpr {
68 digit: Box::from(d.clone()),
69 option: Box::from(op.clone()),
70 })
71}
72
73fn range_per(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
74 per(p).opt().map(|e| match e {
75 None => NoOp,
76 Some(s) => s,
77 })
78}
79
80macro_rules! range {
81 ( $x:expr ) => {
82 ($x - elm(b'-') + $x + range_per($x)).map(|((e1, e2), e3)| RangeExpr {
83 from: Box::from(e1),
84 to: Box::from(e2),
85 per_option: Box::from(e3),
86 })
87 };
88}
89
90fn list(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
91 p.of_many0_sep(elm(b',')).map(|e| match e {
92 e if e.len() == 1 => e.get(0).unwrap().clone(),
93 e => ListExpr(e),
94 })
95}
96
97macro_rules! digit_instruction {
98 ( $x:expr ) => {
99 asterisk_per($x).attempt() | asterisk().attempt() | list(range!($x).attempt() | $x)
100 };
101}
102
103fn instruction<'a>() -> Parser<'a, u8, Expr> {
104 (digit_instruction!(min_digit()) - elm(b' ') + digit_instruction!(hour_digit()) - elm(b' ')
105 + digit_instruction!(day_digit())
106 - elm(b' ')
107 + digit_instruction!(month_digit())
108 - elm(b' ')
109 + digit_instruction!(day_of_week_text().attempt() | day_of_week_digit()))
110 .map(|((((mins, hours), days), months), day_of_weeks)| CronExpr {
111 mins: Box::from(mins),
112 hours: Box::from(hours),
113 days: Box::from(days),
114 months: Box::from(months),
115 day_of_weeks: Box::from(day_of_weeks),
116 })
117}
118
119pub struct CronParser;
120
121impl CronParser {
122 pub fn parse(source: &str) -> ParseResult<u8, Expr> {
123 (instruction() - end()).parse(source.as_bytes())
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_instruction() {
133 let result = (instruction() - end()).parse(b"* * * * *").to_result().unwrap();
134 assert_eq!(
135 result,
136 CronExpr {
137 mins: Box::from(AnyValueExpr),
138 hours: Box::from(AnyValueExpr),
139 days: Box::from(AnyValueExpr),
140 months: Box::from(AnyValueExpr),
141 day_of_weeks: Box::from(AnyValueExpr)
142 }
143 );
144 let result = (instruction() - end()).parse(b"1 1 1 1 1").to_result().unwrap();
145 assert_eq!(
146 result,
147 CronExpr {
148 mins: Box::from(ValueExpr(1)),
149 hours: Box::from(ValueExpr(1)),
150 days: Box::from(ValueExpr(1)),
151 months: Box::from(ValueExpr(1)),
152 day_of_weeks: Box::from(ValueExpr(1))
153 }
154 );
155 }
156
157 #[test]
158 fn test_digit_instruction() {
159 let result = (digit_instruction!(min_digit()) - end())
160 .parse(b"*")
161 .to_result()
162 .unwrap();
163 assert_eq!(result, AnyValueExpr);
164 let result = (digit_instruction!(min_digit()) - end())
165 .parse(b"*/2")
166 .to_result()
167 .unwrap();
168 assert_eq!(
169 result,
170 PerExpr {
171 digit: Box::from(AnyValueExpr),
172 option: Box::from(ValueExpr(2))
173 }
174 );
175 let result = (digit_instruction!(min_digit()) - end())
176 .parse(b"1-10/2")
177 .to_result()
178 .unwrap();
179 assert_eq!(
180 result,
181 RangeExpr {
182 from: Box::from(ValueExpr(1)),
183 to: Box::from(ValueExpr(10)),
184 per_option: Box::from(ValueExpr(2))
185 }
186 );
187 let result = (digit_instruction!(min_digit()) - end())
188 .parse(b"1,2,3")
189 .to_result()
190 .unwrap();
191 assert_eq!(result, ListExpr(vec![ValueExpr(1), ValueExpr(2), ValueExpr(3)]));
192 let result = (digit_instruction!(min_digit()) - end())
193 .parse(b"1")
194 .to_result()
195 .unwrap();
196 assert_eq!(result, ValueExpr(1));
197 }
198
199 #[test]
200 fn test_list() {
201 let s = (0..=59).map(|v| v.to_string()).collect::<Vec<_>>().join(",");
202 let result = (list(min_digit()) - end()).parse(s.as_bytes()).to_result().unwrap();
203 let values = (0..=59).map(|v| ValueExpr(v)).collect::<Vec<_>>();
204 assert_eq!(result, ListExpr(values));
205 }
206
207 #[test]
208 fn test_range() {
209 for n2 in 1..=59 {
210 let option = n2 / 2;
211 let n1 = n2 - 1;
212 let s: &str = &format!("{:<02}-{:<02}/{:<02}", n1, n2, option);
213 println!("{}", s);
214 let result = (range!(min_digit()) - end()).parse(s.as_bytes()).to_result().unwrap();
215 assert_eq!(
216 result,
217 RangeExpr {
218 from: Box::from(ValueExpr(n1)),
219 to: Box::from(ValueExpr(n2)),
220 per_option: Box::from(ValueExpr(option)),
221 }
222 );
223 }
224 }
225
226 #[test]
227 fn test_asterisk_per() {
228 for n in 0..59 {
229 let s: &str = &format!("*/{:<02}", n);
230 let result = (asterisk_per(min_digit()) - end())
231 .parse(s.as_bytes())
232 .to_result()
233 .unwrap();
234 assert_eq!(
235 result,
236 PerExpr {
237 digit: Box::from(AnyValueExpr),
238 option: Box::from(ValueExpr(n)),
239 }
240 );
241 }
242 }
243
244 #[test]
245 fn test_per() {
246 let result = (per(min_digit()) - end()).parse(b"/2").to_result().unwrap();
247 assert_eq!(result, ValueExpr(2));
248 }
249
250 #[test]
251 fn test_min_digit() {
252 for n in 0..59 {
253 let s: &str = &format!("{:<02}", n);
254 let result = (min_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
255 assert_eq!(result, ValueExpr(n));
256 }
257 let result = (min_digit() - end()).parse(b"60").to_result();
258 assert_eq!(result.is_err(), true);
259 }
260
261 #[test]
262 fn test_hour_digit() {
263 for n in 0..=23 {
264 if n < 10 {
265 let s: &str = &n.to_string();
266 let result: Expr = (hour_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
267 assert_eq!(result, ValueExpr(n));
268 }
269 let s: &str = &format!("{:<02}", n);
270 let result: Expr = (hour_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
271 assert_eq!(result, ValueExpr(n));
272 }
273 let result = (hour_digit() - end()).parse(b"24").to_result();
274 assert_eq!(result.is_err(), true);
275 }
276
277 #[test]
278 fn test_day_digit() {
279 for n in 1..=31 {
280 if n < 10 {
281 let s: &str = &n.to_string();
282 let result: Expr = (day_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
283 assert_eq!(result, ValueExpr(n));
284 }
285 let s: &str = &format!("{:<02}", n);
286 let result: Expr = (day_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
287 assert_eq!(result, ValueExpr(n));
288 }
289 let result = (day_digit() - end()).parse(b"32").to_result();
290 assert_eq!(result.is_err(), true);
291 }
292
293 #[test]
294 fn test_month_digit() {
295 for n in 1..=12 {
296 if n < 10 {
297 let s: &str = &n.to_string();
298 let result: Expr = (month_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
299 assert_eq!(result, ValueExpr(n));
300 }
301 let s: &str = &format!("{:<02}", n);
302 let result: Expr = (month_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
303 assert_eq!(result, ValueExpr(n));
304 }
305 let result = (month_digit() - end()).parse(b"13").to_result();
306 assert_eq!(result.is_err(), true);
307 }
308}