Skip to main content

chrono_node/common/
word_boundary.rs

1//! upstream `common/parsers/AbstractParserWithWordBoundary.ts`.
2//!
3//! Implement [`WordBoundaryParser`] and wrap it: `Box::new(WithWordBoundary::new(MyParser))`.
4
5use std::sync::Mutex;
6
7use crate::core::chrono::{Extracted, Parser};
8use crate::core::context::ParsingContext;
9use crate::core::regex::{JsRegex, RegexMatch};
10
11/// upstream `AbstractParserWithWordBoundaryChecking`'s abstract part.
12pub trait WordBoundaryParser: Send + Sync {
13    /// `innerPattern(context)`.
14    fn inner_pattern(&self, context: &ParsingContext) -> JsRegex;
15
16    /// `innerExtract(context, match)`: `m` already has the boundary group removed.
17    fn inner_extract(&self, context: &ParsingContext, m: &mut RegexMatch) -> Extracted;
18
19    /// `patternLeftBoundary()`.
20    fn pattern_left_boundary(&self) -> &str {
21        r"(\W|^)"
22    }
23}
24
25/// The concrete `AbstractParserWithWordBoundaryChecking`: prefixes the left-boundary group and
26/// strips it from the match before calling `inner_extract`.
27pub struct WithWordBoundary<P> {
28    inner: P,
29    cache: Mutex<Option<(String, String, JsRegex)>>,
30}
31
32impl<P: WordBoundaryParser> WithWordBoundary<P> {
33    pub fn new(inner: P) -> WithWordBoundary<P> {
34        WithWordBoundary {
35            inner,
36            cache: Mutex::new(None),
37        }
38    }
39}
40
41impl<P: WordBoundaryParser> Parser for WithWordBoundary<P> {
42    fn pattern(&self, context: &ParsingContext) -> JsRegex {
43        let inner = self.inner.inner_pattern(context);
44        let mut cache = self.cache.lock().unwrap_or_else(|e| e.into_inner());
45        if let Some((source, flags, pattern)) = cache.as_ref() {
46            if source == inner.source() && flags == inner.flags() {
47                return pattern.clone();
48            }
49        }
50        let pattern = JsRegex::new(
51            &format!("{}{}", self.inner.pattern_left_boundary(), inner.source()),
52            inner.flags(),
53        );
54        *cache = Some((inner.source().to_string(), inner.flags().to_string(), pattern.clone()));
55        pattern
56    }
57
58    fn extract(&self, context: &ParsingContext, m: &mut RegexMatch) -> Extracted {
59        let header_len = m.get(1).map_or(0, str::len);
60        m.index += header_len;
61        if let Some(Some(full)) = m.groups.first_mut() {
62            *full = full[header_len..].to_string();
63        }
64        // Like upstream: shift groups down by one; the last slot keeps its old value.
65        for i in 2..m.groups.len() {
66            m.groups[i - 1] = m.groups[i].clone();
67        }
68        self.inner.inner_extract(context, m)
69    }
70}