use std::collections::BTreeSet;
use std::sync::Arc;
use super::components::{Component, ParsingComponents};
use super::configuration::Flags;
use super::context::{ParsingContext, ParsingOption};
use super::js_date::JsDate;
use super::reference::ReferenceInput;
use super::regex::{JsRegex, RegexMatch};
use super::result::ParsingResult;
#[derive(Clone, Debug)]
pub enum Extracted {
None,
Components(ParsingComponents),
Result(ParsingResult),
Known(Vec<(Component, i64)>),
}
pub trait Parser: Send + Sync {
fn pattern(&self, context: &ParsingContext) -> JsRegex;
fn extract(&self, context: &ParsingContext, m: &mut RegexMatch) -> Extracted;
}
pub trait Refiner: Send + Sync {
fn refine(&self, context: &ParsingContext, results: Vec<ParsingResult>) -> Vec<ParsingResult>;
}
#[derive(Clone)]
pub struct NamedParser {
pub name: &'static str,
pub flags: Flags,
pub parser: Arc<dyn Parser>,
}
#[derive(Clone)]
pub struct NamedRefiner {
pub name: &'static str,
pub flags: Flags,
pub refiner: Arc<dyn Refiner>,
}
#[derive(Clone, Default)]
pub struct Chrono {
pub parsers: Vec<NamedParser>,
pub refiners: Vec<NamedRefiner>,
pub missing: Vec<&'static str>,
}
pub type StepTrace = (&'static str, Vec<ParsingResult>);
impl Chrono {
pub fn parse(&self, text: &str, reference: &ReferenceInput, option: &ParsingOption) -> Vec<ParsingResult> {
self.parse_with_now(text, reference, option, JsDate::now())
}
pub fn parse_with_now(
&self,
text: &str,
reference: &ReferenceInput,
option: &ParsingOption,
now: JsDate,
) -> Vec<ParsingResult> {
let context = ParsingContext::new(text, reference, option.clone(), now);
let mut results = Vec::new();
for p in &self.parsers {
results.extend(execute_parser(&context, p.parser.as_ref()));
}
results.sort_by_key(|r| r.index);
for r in &self.refiners {
results = r.refiner.refine(&context, results);
}
results
}
pub fn trace_with_now(
&self,
text: &str,
reference: &ReferenceInput,
option: &ParsingOption,
now: JsDate,
) -> (Vec<StepTrace>, Vec<StepTrace>) {
let context = ParsingContext::new(text, reference, option.clone(), now);
let mut parsers = Vec::new();
let mut results = Vec::new();
for p in &self.parsers {
let found = execute_parser(&context, p.parser.as_ref());
results.extend(found.iter().cloned());
parsers.push((p.name, found));
}
results.sort_by_key(|r| r.index);
let mut refiners = Vec::new();
for r in &self.refiners {
results = r.refiner.refine(&context, results);
refiners.push((r.name, results.clone()));
}
(parsers, refiners)
}
pub fn refine_isolated(
&self,
context: &ParsingContext,
step: &str,
occurrence: usize,
input: Vec<ParsingResult>,
) -> Option<Vec<ParsingResult>> {
let r = self.refiners.iter().filter(|r| r.name == step).nth(occurrence)?;
Some(r.refiner.refine(context, input))
}
pub fn parse_date(&self, text: &str, reference: &ReferenceInput, option: &ParsingOption) -> Option<JsDate> {
self.parse(text, reference, option).first().map(|r| r.start.date())
}
pub fn registered(&self) -> BTreeSet<&'static str> {
self.parsers
.iter()
.map(|p| p.name)
.chain(self.refiners.iter().map(|r| r.name))
.collect()
}
}
fn char_boundary_at_or_after(text: &str, i: usize) -> usize {
let mut i = i.min(text.len());
while !text.is_char_boundary(i) {
i += 1;
}
i
}
pub fn execute_parser(context: &ParsingContext, parser: &dyn Parser) -> Vec<ParsingResult> {
let mut results = Vec::new();
let pattern = parser.pattern(context);
let text = context.text.as_str();
let mut offset = 0;
let mut found = pattern.exec(text);
while let Some(mut m) = found {
m.index += offset;
let parsed = match parser.extract(context, &mut m) {
Extracted::None => {
let next = m.index + 1;
if next > text.len() {
break;
}
offset = char_boundary_at_or_after(text, next);
found = pattern.exec(&text[offset..]);
continue;
}
Extracted::Result(r) => r,
Extracted::Components(c) => {
let mut r = context.create_parsing_result(m.index, m.text(), None, None);
r.start = c;
r
}
Extracted::Known(known) => {
let start = context.create_parsing_components(&known);
context.create_parsing_result(m.index, m.text(), Some(start), None)
}
};
offset = char_boundary_at_or_after(text, parsed.index + parsed.text.len());
results.push(parsed);
found = pattern.exec(&text[offset..]);
}
results
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::components::Component;
struct EvenNumbers {
anchored: bool,
skip_whole_match: bool,
}
impl Parser for EvenNumbers {
fn pattern(&self, _: &ParsingContext) -> JsRegex {
JsRegex::new(if self.anchored { r"^\s*(\d+)" } else { r"(\d+)" }, "")
}
fn extract(&self, _: &ParsingContext, m: &mut RegexMatch) -> Extracted {
let n: i64 = m.get(1).unwrap().parse().unwrap();
if n % 2 == 1 {
if self.skip_whole_match {
m.index += m.text().len();
}
return Extracted::None;
}
Extracted::Known(vec![(Component::Day, n)])
}
}
fn run(p: EvenNumbers, text: &str) -> Vec<(usize, String)> {
let context = ParsingContext::new(
text,
&ReferenceInput::Date(JsDate::from_millis(0)),
ParsingOption::default(),
JsDate::INVALID,
);
execute_parser(&context, &p)
.into_iter()
.map(|r| (r.index, r.text))
.collect()
}
#[test]
fn retries_one_char_after_a_rejected_match() {
let out = run(
EvenNumbers {
anchored: false,
skip_whole_match: false,
},
"13 24 6",
);
assert_eq!(out, vec![(3, "24".into()), (6, "6".into())]);
let out = run(
EvenNumbers {
anchored: false,
skip_whole_match: false,
},
"14x",
);
assert_eq!(out, vec![(0, "14".into())]);
let out = run(
EvenNumbers {
anchored: false,
skip_whole_match: false,
},
"35x",
);
assert_eq!(out, vec![]);
}
#[test]
fn extractor_can_move_the_retry_point() {
let out = run(
EvenNumbers {
anchored: false,
skip_whole_match: true,
},
"35x",
);
assert_eq!(out, vec![]);
let out = run(
EvenNumbers {
anchored: false,
skip_whole_match: true,
},
"3 4x",
);
assert_eq!(out, vec![(2, "4".into())]);
}
#[test]
fn caret_anchors_at_the_remaining_slice() {
let out = run(
EvenNumbers {
anchored: true,
skip_whole_match: false,
},
"2 4 x 6",
);
assert_eq!(out, vec![(0, "2".into()), (1, " 4".into())]);
}
#[test]
fn multibyte_text_keeps_byte_indexes_on_char_boundaries() {
let out = run(
EvenNumbers {
anchored: false,
skip_whole_match: false,
},
"é1é2",
);
assert_eq!(out, vec![(5, "2".into())]);
}
#[test]
fn empty_configuration_finds_nothing() {
let c = Chrono::default();
assert!(c
.parse("tomorrow", &ReferenceInput::Now, &ParsingOption::default())
.is_empty());
}
}