typed_ident/syntax/boundary/
standard.rs1#[cfg(test)]
7#[path = "standard.tests.rs"]
8mod tests;
9
10use 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
21pub struct Standard<O: Options = options::Default>(PhantomData<O>);
36
37impl<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)?; 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; 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
112impl<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}