1use alloc::borrow::Cow;
4use alloc::vec::Vec;
5use core::hash::{Hash, Hasher};
6
7use crate::expression::{Expression, Markup};
8
9#[derive(Clone, PartialEq, Eq, Hash, Debug)]
13#[non_exhaustive]
14pub enum PatternPart<'a> {
15 Text(Cow<'a, str>),
17 Expression(Expression<'a>),
19 Markup(Markup<'a>),
21}
22
23impl PatternPart<'_> {
24 pub fn into_owned(self) -> PatternPart<'static> {
26 match self {
27 PatternPart::Text(t) => PatternPart::Text(Cow::Owned(t.into_owned())),
28 PatternPart::Expression(e) => PatternPart::Expression(e.into_owned()),
29 PatternPart::Markup(m) => PatternPart::Markup(m.into_owned()),
30 }
31 }
32}
33
34#[derive(Clone, Default)]
40pub struct Pattern<'a>(Repr<'a>);
41
42#[derive(Clone, Default)]
46enum Repr<'a> {
47 #[default]
48 Empty,
49 One(PatternPart<'a>),
50 Many(Vec<PatternPart<'a>>),
51}
52
53impl<'a> Pattern<'a> {
54 pub const fn new() -> Self {
56 Pattern(Repr::Empty)
57 }
58
59 pub fn with_capacity(capacity: usize) -> Self {
62 if capacity < 2 {
63 Pattern(Repr::Empty)
64 } else {
65 Pattern(Repr::Many(Vec::with_capacity(capacity)))
66 }
67 }
68
69 pub fn from_text(text: Cow<'a, str>) -> Self {
72 if text.is_empty() {
73 Pattern(Repr::Empty)
74 } else {
75 Pattern(Repr::One(PatternPart::Text(text)))
76 }
77 }
78
79 pub fn push(&mut self, part: PatternPart<'a>) {
82 self.push_with_hint(part, 0);
83 }
84
85 fn push_with_hint(&mut self, part: PatternPart<'a>, more: usize) {
88 let part = match part {
89 PatternPart::Text(text) => {
90 if text.is_empty() {
91 return;
92 }
93 if let Some(PatternPart::Text(last)) = self.last_mut() {
94 last.to_mut().push_str(&text);
95 return;
96 }
97 PatternPart::Text(text)
98 }
99 other => other,
100 };
101 self.0 = match core::mem::take(&mut self.0) {
102 Repr::Empty => Repr::One(part),
103 Repr::One(first) => {
104 let mut v = Vec::with_capacity(2 + more);
105 v.push(first);
106 v.push(part);
107 Repr::Many(v)
108 }
109 Repr::Many(mut v) => {
110 v.push(part);
111 Repr::Many(v)
112 }
113 };
114 }
115
116 fn last_mut(&mut self) -> Option<&mut PatternPart<'a>> {
117 match &mut self.0 {
118 Repr::Empty => None,
119 Repr::One(p) => Some(p),
120 Repr::Many(v) => v.last_mut(),
121 }
122 }
123
124 pub fn parts(&self) -> &[PatternPart<'a>] {
126 match &self.0 {
127 Repr::Empty => &[],
128 Repr::One(p) => core::slice::from_ref(p),
129 Repr::Many(v) => v,
130 }
131 }
132
133 pub fn iter(&self) -> core::slice::Iter<'_, PatternPart<'a>> {
135 self.parts().iter()
136 }
137
138 pub fn len(&self) -> usize {
140 self.parts().len()
141 }
142
143 pub fn is_empty(&self) -> bool {
145 self.parts().is_empty()
146 }
147
148 pub fn as_simple_text(&self) -> Option<&str> {
151 match self.parts() {
152 [] => Some(""),
153 [PatternPart::Text(t)] => Some(t),
154 _ => None,
155 }
156 }
157
158 pub fn into_parts(self) -> Vec<PatternPart<'a>> {
160 match self.0 {
161 Repr::Empty => Vec::new(),
162 Repr::One(p) => alloc::vec![p],
163 Repr::Many(v) => v,
164 }
165 }
166
167 pub fn into_owned(self) -> Pattern<'static> {
169 Pattern(match self.0 {
170 Repr::Empty => Repr::Empty,
171 Repr::One(p) => Repr::One(p.into_owned()),
172 Repr::Many(v) => Repr::Many(v.into_iter().map(PatternPart::into_owned).collect()),
173 })
174 }
175}
176
177impl PartialEq for Pattern<'_> {
178 fn eq(&self, other: &Self) -> bool {
179 self.parts() == other.parts()
180 }
181}
182
183impl Eq for Pattern<'_> {}
184
185impl Hash for Pattern<'_> {
186 fn hash<H: Hasher>(&self, state: &mut H) {
187 self.parts().hash(state);
188 }
189}
190
191impl core::fmt::Debug for Pattern<'_> {
192 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
193 f.debug_list().entries(self.parts()).finish()
194 }
195}
196
197impl<'a> FromIterator<PatternPart<'a>> for Pattern<'a> {
198 fn from_iter<I: IntoIterator<Item = PatternPart<'a>>>(iter: I) -> Self {
199 let mut iter = iter.into_iter();
200 let mut pattern = Pattern::new();
201 while let Some(part) = iter.next() {
202 pattern.push_with_hint(part, iter.size_hint().0);
203 }
204 pattern
205 }
206}
207
208impl<'a> From<Vec<PatternPart<'a>>> for Pattern<'a> {
209 fn from(parts: Vec<PatternPart<'a>>) -> Self {
212 let mut previous_text = false;
213 let normal = parts.iter().all(|p| {
214 let (ok, is_text) = match p {
215 PatternPart::Text(t) => (!t.is_empty() && !previous_text, true),
216 _ => (true, false),
217 };
218 previous_text = is_text;
219 ok
220 });
221 if !normal {
222 return parts.into_iter().collect();
223 }
224 match parts.len() {
225 0 => Pattern::new(),
226 1 => parts.into_iter().collect(),
227 _ => Pattern(Repr::Many(parts)),
228 }
229 }
230}
231
232impl<'p, 'a> IntoIterator for &'p Pattern<'a> {
233 type Item = &'p PatternPart<'a>;
234 type IntoIter = core::slice::Iter<'p, PatternPart<'a>>;
235
236 fn into_iter(self) -> Self::IntoIter {
237 self.parts().iter()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use alloc::borrow::Cow;
244 use alloc::vec;
245
246 use super::{Pattern, PatternPart, Repr};
247 use crate::expression::{
248 Attributes, Expression, Markup, MarkupKind, Options, VariableExpression, VariableRef,
249 };
250
251 fn text(s: &str) -> PatternPart<'_> {
252 PatternPart::Text(Cow::Borrowed(s))
253 }
254
255 fn var(name: &str) -> PatternPart<'_> {
256 PatternPart::Expression(Expression::Variable(VariableExpression {
257 arg: VariableRef { name: name.into() },
258 function: None,
259 attributes: Attributes::new(),
260 }))
261 }
262
263 fn markup(name: &str) -> PatternPart<'_> {
264 PatternPart::Markup(Markup {
265 kind: MarkupKind::Open,
266 name: name.into(),
267 options: Options::new(),
268 attributes: Attributes::new(),
269 })
270 }
271
272 #[test]
273 fn empty_text_is_dropped() {
274 let mut p = Pattern::new();
275 p.push(text(""));
276 assert!(p.is_empty());
277 assert_eq!(p.as_simple_text(), Some(""));
278 assert!(Pattern::from_text(Cow::Borrowed("")).is_empty());
279 p.push(var("x"));
280 p.push(text(""));
281 assert_eq!(p.len(), 1);
282 }
283
284 #[test]
285 fn adjacent_text_merges() {
286 let mut p = Pattern::new();
287 p.push(text("a"));
288 p.push(text("b"));
289 assert_eq!(p.parts(), [text("ab")]);
290 assert!(matches!(&p.parts()[0], PatternPart::Text(Cow::Owned(_))));
291 p.push(var("x"));
292 p.push(text("c"));
293 p.push(text("d"));
294 assert_eq!(p.parts(), [text("ab"), var("x"), text("cd")]);
295 assert_eq!(p.as_simple_text(), None);
296 }
297
298 #[test]
299 fn one_part_is_stored_inline() {
300 let p = Pattern::from_text(Cow::Borrowed("Hello"));
301 assert!(matches!(p.0, Repr::One(_)));
302 assert_eq!(p.as_simple_text(), Some("Hello"));
303 let mut q = Pattern::new();
304 q.push(markup("b"));
305 assert!(matches!(q.0, Repr::One(_)));
306 q.push(text("x"));
307 assert!(matches!(q.0, Repr::Many(_)));
308 assert!(matches!(Pattern::with_capacity(1).0, Repr::Empty));
310 let mut r = Pattern::with_capacity(3);
311 r.push(text("t"));
312 assert_eq!(r.parts(), [text("t")]);
313 }
314
315 #[test]
316 fn equality_ignores_representation() {
317 let inline = Pattern::from_text(Cow::Borrowed("x"));
318 let mut many = Pattern::with_capacity(4);
319 many.push(text("x"));
320 assert!(matches!(many.0, Repr::Many(_)));
321 assert_eq!(inline, many);
322 assert_eq!(Pattern::new(), Pattern::with_capacity(8));
323 }
324
325 #[test]
326 #[allow(clippy::many_single_char_names)]
327 fn from_vec_and_iter_normalize() {
328 let p: Pattern<'_> = vec![text("a"), text(""), text("b"), var("x")].into();
329 assert_eq!(p.parts(), [text("ab"), var("x")]);
330 let q: Pattern<'_> = vec![text("a"), var("x"), text("b")].into();
331 assert_eq!(q.len(), 3);
332 let r: Pattern<'_> = vec![var("x")].into();
333 assert!(matches!(r.0, Repr::One(_)));
334 let s: Pattern<'_> = [text("a"), text("b")].into_iter().collect();
335 assert_eq!(s.parts(), [text("ab")]);
336 let t: Pattern<'_> = vec![].into();
337 assert!(t.is_empty());
338 let collected: alloc::vec::Vec<&PatternPart<'_>> = (&q).into_iter().collect();
339 assert_eq!(collected.len(), 3);
340 assert_eq!(q.clone().into_parts().len(), 3);
341 }
342
343 #[test]
344 fn into_owned_keeps_value() {
345 let mut p = Pattern::new();
346 p.push(text("a"));
347 p.push(var("x"));
348 let owned: Pattern<'static> = p.clone().into_owned();
349 assert_eq!(owned, p);
350 }
351}