Skip to main content

typed_ident/syntax/boundary/
standard.rs

1// =============================================================================
2// MODULES
3// =============================================================================
4
5// -----------------------------------------------------------------------------
6#[cfg(test)]
7#[path = "standard.tests.rs"]
8mod tests;
9
10// =============================================================================
11// USES
12// =============================================================================
13
14// -----------------------------------------------------------------------------
15use crate::syntax::boundary::{Boundary, Options, TrivialBoundary, options};
16use crate::syntax::segmentation::{GraphemeIndicesIterator, GraphemesIterator};
17use crate::syntax::{GraphemeCase, Segmentation};
18use core::marker::PhantomData;
19use core::num::NonZero;
20
21// =============================================================================
22// TYPES
23// =============================================================================
24
25/// Defines a standard boundary implementation.
26///
27/// This type is configurable over [`Options`] types, but it assumes the
28/// [`Default`] options when another options type is not provided.
29///
30/// For a description of how boundaries are formed in this implementation, see
31/// the [`boundary`](crate::syntax::boundary#options) module documentation.
32///
33/// [`Default`]: crate::syntax::boundary::options::Default
34/// [`Options`]: crate::syntax::boundary::options::Options
35pub struct Standard<O: Options = options::Default>(PhantomData<O>);
36
37// =============================================================================
38// TRAIT IMPLS
39// =============================================================================
40
41// -----------------------------------------------------------------------------
42impl<O: Options> Boundary for Standard<O> {
43    const CAN_FIND_BOUNDARIES: bool = O::CAMEL
44        || O::HAT
45        || O::DIGIT_TO_LOWER
46        || O::DIGIT_TO_UPPER
47        || O::LOWER_TO_DIGIT
48        || O::UPPER_TO_DIGIT;
49
50    #[inline]
51    fn find_boundary<S: Segmentation>(chunk: &str) -> Option<NonZero<usize>> {
52        match Self::CAN_FIND_BOUNDARIES {
53            true => {
54                let mut iter =
55                    S::GraphemeIndices::new(chunk).map(|(i, s)| (i, GraphemeCase::new(s)));
56                let mut prev = iter.next().map(|(_, c)| c)?; // Must be at least one grapheme.
57                let mut iter = iter.peekable();
58                while let Some((idx, curr)) = iter.next() {
59                    let next = iter.peek().copied().map(|(_, c)| c);
60                    if Self::is_boundary(prev, curr, next) {
61                        return NonZero::new(idx);
62                    }
63                    prev = curr;
64                }
65                None
66            }
67            false => None,
68        }
69    }
70
71    #[inline]
72    fn rfind_boundary<S: Segmentation>(chunk: &str) -> Option<NonZero<usize>> {
73        match Self::CAN_FIND_BOUNDARIES {
74            true => {
75                let iter = S::GraphemeIndices::new(chunk)
76                    .rev()
77                    .map(|(i, s)| (i, GraphemeCase::new(s)));
78                let mut next = None; // Recall this is from the right-side!
79                let mut iter = iter.peekable();
80                while let Some((idx, curr)) = iter.next() {
81                    let prev = iter.peek().copied().map(|(_, c)| c)?;
82                    if Self::is_boundary(prev, curr, next) {
83                        return NonZero::new(idx);
84                    }
85                    next = Some(curr);
86                }
87                None
88            }
89            false => None,
90        }
91    }
92
93    #[inline]
94    fn has_boundary_at<S: Segmentation>(chunk: &str, idx: usize) -> bool {
95        match Self::CAN_FIND_BOUNDARIES {
96            true => {
97                let (left, right) = chunk.split_at(idx);
98                let Some(prev) = S::Graphemes::new(left).next_back() else {
99                    return false;
100                };
101                let mut right = S::Graphemes::new(right);
102                let Some(curr) = right.next() else {
103                    return false;
104                };
105                Self::is_boundary_str(prev, curr, right.next())
106            }
107            false => false,
108        }
109    }
110}
111
112// -----------------------------------------------------------------------------
113impl<O: Options> TrivialBoundary for Standard<O> {
114    #[inline]
115    fn is_boundary(prev: GraphemeCase, curr: GraphemeCase, next: Option<GraphemeCase>) -> bool {
116        match curr {
117            GraphemeCase::Digit => match prev {
118                GraphemeCase::Lower => O::LOWER_TO_DIGIT,
119                GraphemeCase::TitleNonGreek => O::LOWER_TO_DIGIT,
120                GraphemeCase::Upper => O::UPPER_TO_DIGIT,
121                _ => false,
122            },
123            GraphemeCase::Lower => match prev {
124                GraphemeCase::Digit => O::DIGIT_TO_LOWER,
125                _ => false,
126            },
127            GraphemeCase::TitleNonGreek => {
128                let boundary = match prev {
129                    GraphemeCase::Digit => O::DIGIT_TO_UPPER,
130                    GraphemeCase::Lower => O::CAMEL,
131                    GraphemeCase::TitleNonGreek => O::CAMEL,
132                    _ => false,
133                };
134                boundary || O::HAT
135            }
136            GraphemeCase::Upper => {
137                let boundary = match prev {
138                    GraphemeCase::Digit => O::DIGIT_TO_UPPER,
139                    GraphemeCase::Lower => O::CAMEL,
140                    GraphemeCase::TitleNonGreek => O::CAMEL,
141                    _ => false,
142                };
143                boundary || (O::HAT && next.is_some_and(|k| k == GraphemeCase::Lower))
144            }
145            _ => false,
146        }
147    }
148}