Skip to main content

mf2_model/
pattern.rs

1//! Patterns: sequences of text, expressions and markup.
2
3use alloc::borrow::Cow;
4use alloc::vec::Vec;
5use core::hash::{Hash, Hasher};
6
7use crate::expression::{Expression, Markup};
8
9/// One element of a [`Pattern`].
10///
11/// Not exhaustive: the spec says future versions may add placeholder kinds.
12#[derive(Clone, PartialEq, Eq, Hash, Debug)]
13#[non_exhaustive]
14pub enum PatternPart<'a> {
15    /// Literal text, escapes processed. Never empty inside a [`Pattern`].
16    Text(Cow<'a, str>),
17    /// An expression placeholder.
18    Expression(Expression<'a>),
19    /// A markup placeholder.
20    Markup(Markup<'a>),
21}
22
23impl PatternPart<'_> {
24    /// The same part with every string owned.
25    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/// A pattern: a sequence of parts with no empty `Text` and no two adjacent
35/// `Text` parts ([`Pattern::push`] merges and drops accordingly).
36///
37/// The representation stores one part inline, so a placeholder-free message
38/// needs no allocation (the D1 target).
39#[derive(Clone, Default)]
40pub struct Pattern<'a>(Repr<'a>);
41
42// Private: `Many` may hold any number of parts (it comes from
43// `with_capacity` or `From<Vec<_>>`); every observer goes through `parts()`,
44// so two representations of the same parts are indistinguishable.
45#[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    /// The empty pattern (does not allocate).
55    pub const fn new() -> Self {
56        Pattern(Repr::Empty)
57    }
58
59    /// An empty pattern with room for `capacity` parts: no allocation for 0 or
60    /// 1 part, one allocation of exactly `capacity` parts otherwise.
61    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    /// The pattern holding just `text`; `""` gives the empty pattern. Does not
70    /// allocate.
71    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    /// Appends `part`: an empty `Text` is dropped, and a `Text` after a `Text`
80    /// is merged into it (which makes the merged text owned).
81    pub fn push(&mut self, part: PatternPart<'a>) {
82        self.push_with_hint(part, 0);
83    }
84
85    /// [`Pattern::push`], reserving room for `more` further parts when the
86    /// pattern first needs a vector.
87    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    /// The parts, in order.
125    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    /// Iterates over the parts, in order.
134    pub fn iter(&self) -> core::slice::Iter<'_, PatternPart<'a>> {
135        self.parts().iter()
136    }
137
138    /// How many parts.
139    pub fn len(&self) -> usize {
140        self.parts().len()
141    }
142
143    /// No parts.
144    pub fn is_empty(&self) -> bool {
145        self.parts().is_empty()
146    }
147
148    /// `Some` for the empty pattern (`""`) and for a pattern that is a single
149    /// `Text` part; `None` if it has a placeholder.
150    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    /// The parts as a vector (allocates unless the pattern already holds one).
159    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    /// The same pattern with every string owned.
168    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    /// Keeps the vector's allocation when it already satisfies the pattern
210    /// invariants (no empty or adjacent `Text`); otherwise normalizes.
211    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        // `with_capacity` below 2 does not allocate either.
309        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}