1use std::collections::BTreeSet;
4use std::sync::Arc;
5
6use super::components::{Component, ParsingComponents};
7use super::configuration::Flags;
8use super::context::{ParsingContext, ParsingOption};
9use super::js_date::JsDate;
10use super::reference::ReferenceInput;
11use super::regex::{JsRegex, RegexMatch};
12use super::result::ParsingResult;
13
14#[derive(Clone, Debug)]
16pub enum Extracted {
17 None,
19 Components(ParsingComponents),
20 Result(ParsingResult),
21 Known(Vec<(Component, i64)>),
23}
24
25pub trait Parser: Send + Sync {
27 fn pattern(&self, context: &ParsingContext) -> JsRegex;
29
30 fn extract(&self, context: &ParsingContext, m: &mut RegexMatch) -> Extracted;
34}
35
36pub trait Refiner: Send + Sync {
38 fn refine(&self, context: &ParsingContext, results: Vec<ParsingResult>) -> Vec<ParsingResult>;
39}
40
41#[derive(Clone)]
43pub struct NamedParser {
44 pub name: &'static str,
45 pub flags: Flags,
46 pub parser: Arc<dyn Parser>,
47}
48
49#[derive(Clone)]
50pub struct NamedRefiner {
51 pub name: &'static str,
52 pub flags: Flags,
53 pub refiner: Arc<dyn Refiner>,
54}
55
56#[derive(Clone, Default)]
58pub struct Chrono {
59 pub parsers: Vec<NamedParser>,
60 pub refiners: Vec<NamedRefiner>,
61 pub missing: Vec<&'static str>,
63}
64
65pub type StepTrace = (&'static str, Vec<ParsingResult>);
67impl Chrono {
68 pub fn parse(&self, text: &str, reference: &ReferenceInput, option: &ParsingOption) -> Vec<ParsingResult> {
70 self.parse_with_now(text, reference, option, JsDate::now())
71 }
72
73 pub fn parse_with_now(
75 &self,
76 text: &str,
77 reference: &ReferenceInput,
78 option: &ParsingOption,
79 now: JsDate,
80 ) -> Vec<ParsingResult> {
81 let context = ParsingContext::new(text, reference, option.clone(), now);
82 let mut results = Vec::new();
83 for p in &self.parsers {
84 results.extend(execute_parser(&context, p.parser.as_ref()));
85 }
86 results.sort_by_key(|r| r.index);
88 for r in &self.refiners {
89 results = r.refiner.refine(&context, results);
90 }
91 results
92 }
93
94 pub fn trace_with_now(
98 &self,
99 text: &str,
100 reference: &ReferenceInput,
101 option: &ParsingOption,
102 now: JsDate,
103 ) -> (Vec<StepTrace>, Vec<StepTrace>) {
104 let context = ParsingContext::new(text, reference, option.clone(), now);
105 let mut parsers = Vec::new();
106 let mut results = Vec::new();
107 for p in &self.parsers {
108 let found = execute_parser(&context, p.parser.as_ref());
109 results.extend(found.iter().cloned());
110 parsers.push((p.name, found));
111 }
112 results.sort_by_key(|r| r.index);
113 let mut refiners = Vec::new();
114 for r in &self.refiners {
115 results = r.refiner.refine(&context, results);
116 refiners.push((r.name, results.clone()));
117 }
118 (parsers, refiners)
119 }
120
121 pub fn refine_isolated(
125 &self,
126 context: &ParsingContext,
127 step: &str,
128 occurrence: usize,
129 input: Vec<ParsingResult>,
130 ) -> Option<Vec<ParsingResult>> {
131 let r = self.refiners.iter().filter(|r| r.name == step).nth(occurrence)?;
132 Some(r.refiner.refine(context, input))
133 }
134
135 pub fn parse_date(&self, text: &str, reference: &ReferenceInput, option: &ParsingOption) -> Option<JsDate> {
137 self.parse(text, reference, option).first().map(|r| r.start.date())
138 }
139
140 pub fn registered(&self) -> BTreeSet<&'static str> {
142 self.parsers
143 .iter()
144 .map(|p| p.name)
145 .chain(self.refiners.iter().map(|r| r.name))
146 .collect()
147 }
148}
149
150fn char_boundary_at_or_after(text: &str, i: usize) -> usize {
151 let mut i = i.min(text.len());
152 while !text.is_char_boundary(i) {
153 i += 1;
154 }
155 i
156}
157
158pub fn execute_parser(context: &ParsingContext, parser: &dyn Parser) -> Vec<ParsingResult> {
161 let mut results = Vec::new();
162 let pattern = parser.pattern(context);
163 let text = context.text.as_str();
164 let mut offset = 0;
165 let mut found = pattern.exec(text);
166
167 while let Some(mut m) = found {
168 m.index += offset;
169 let parsed = match parser.extract(context, &mut m) {
170 Extracted::None => {
171 let next = m.index + 1;
173 if next > text.len() {
174 break;
175 }
176 offset = char_boundary_at_or_after(text, next);
177 found = pattern.exec(&text[offset..]);
178 continue;
179 }
180 Extracted::Result(r) => r,
181 Extracted::Components(c) => {
182 let mut r = context.create_parsing_result(m.index, m.text(), None, None);
183 r.start = c;
184 r
185 }
186 Extracted::Known(known) => {
187 let start = context.create_parsing_components(&known);
188 context.create_parsing_result(m.index, m.text(), Some(start), None)
189 }
190 };
191 offset = char_boundary_at_or_after(text, parsed.index + parsed.text.len());
192 results.push(parsed);
193 found = pattern.exec(&text[offset..]);
194 }
195 results
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::core::components::Component;
202
203 struct EvenNumbers {
205 anchored: bool,
206 skip_whole_match: bool,
207 }
208
209 impl Parser for EvenNumbers {
210 fn pattern(&self, _: &ParsingContext) -> JsRegex {
211 JsRegex::new(if self.anchored { r"^\s*(\d+)" } else { r"(\d+)" }, "")
212 }
213 fn extract(&self, _: &ParsingContext, m: &mut RegexMatch) -> Extracted {
214 let n: i64 = m.get(1).unwrap().parse().unwrap();
215 if n % 2 == 1 {
216 if self.skip_whole_match {
217 m.index += m.text().len();
218 }
219 return Extracted::None;
220 }
221 Extracted::Known(vec![(Component::Day, n)])
222 }
223 }
224
225 fn run(p: EvenNumbers, text: &str) -> Vec<(usize, String)> {
226 let context = ParsingContext::new(
227 text,
228 &ReferenceInput::Date(JsDate::from_millis(0)),
229 ParsingOption::default(),
230 JsDate::INVALID,
231 );
232 execute_parser(&context, &p)
233 .into_iter()
234 .map(|r| (r.index, r.text))
235 .collect()
236 }
237
238 #[test]
242 fn retries_one_char_after_a_rejected_match() {
243 let out = run(
244 EvenNumbers {
245 anchored: false,
246 skip_whole_match: false,
247 },
248 "13 24 6",
249 );
250 assert_eq!(out, vec![(3, "24".into()), (6, "6".into())]);
251 let out = run(
252 EvenNumbers {
253 anchored: false,
254 skip_whole_match: false,
255 },
256 "14x",
257 );
258 assert_eq!(out, vec![(0, "14".into())]);
259 let out = run(
260 EvenNumbers {
261 anchored: false,
262 skip_whole_match: false,
263 },
264 "35x",
265 );
266 assert_eq!(out, vec![]);
267 }
268
269 #[test]
270 fn extractor_can_move_the_retry_point() {
271 let out = run(
272 EvenNumbers {
273 anchored: false,
274 skip_whole_match: true,
275 },
276 "35x",
277 );
278 assert_eq!(out, vec![]);
279 let out = run(
280 EvenNumbers {
281 anchored: false,
282 skip_whole_match: true,
283 },
284 "3 4x",
285 );
286 assert_eq!(out, vec![(2, "4".into())]);
287 }
288
289 #[test]
290 fn caret_anchors_at_the_remaining_slice() {
291 let out = run(
293 EvenNumbers {
294 anchored: true,
295 skip_whole_match: false,
296 },
297 "2 4 x 6",
298 );
299 assert_eq!(out, vec![(0, "2".into()), (1, " 4".into())]);
300 }
301
302 #[test]
303 fn multibyte_text_keeps_byte_indexes_on_char_boundaries() {
304 let out = run(
305 EvenNumbers {
306 anchored: false,
307 skip_whole_match: false,
308 },
309 "é1é2",
310 );
311 assert_eq!(out, vec![(5, "2".into())]);
312 }
313
314 #[test]
315 fn empty_configuration_finds_nothing() {
316 let c = Chrono::default();
317 assert!(c
318 .parse("tomorrow", &ReferenceInput::Now, &ParsingOption::default())
319 .is_empty());
320 }
321}