Skip to main content

mf2_model/
expression.rs

1//! Expressions, literals, variables, functions, options, markup and
2//! attributes (`spec/data-model/README.md`, "Pattern Model" to "Attribute
3//! Model").
4
5use alloc::borrow::Cow;
6use alloc::vec::Vec;
7
8/// An expression: an operand with an optional function, or a function alone.
9///
10/// Not exhaustive: the spec says future versions may add expression shapes.
11#[derive(Clone, PartialEq, Eq, Hash, Debug)]
12#[non_exhaustive]
13pub enum Expression<'a> {
14    /// `{|literal| :fn …}` or `{literal}`.
15    Literal(LiteralExpression<'a>),
16    /// `{$var :fn …}` or `{$var}`.
17    Variable(VariableExpression<'a>),
18    /// `{:fn …}`.
19    Function(FunctionExpression<'a>),
20}
21
22/// An expression whose operand is a literal.
23#[derive(Clone, PartialEq, Eq, Hash, Debug)]
24pub struct LiteralExpression<'a> {
25    /// The operand.
26    pub arg: Literal<'a>,
27    /// The function applied to it, if any.
28    pub function: Option<FunctionRef<'a>>,
29    /// The expression's attributes.
30    pub attributes: Attributes<'a>,
31}
32
33/// An expression whose operand is a variable.
34#[derive(Clone, PartialEq, Eq, Hash, Debug)]
35pub struct VariableExpression<'a> {
36    /// The operand.
37    pub arg: VariableRef<'a>,
38    /// The function applied to it, if any.
39    pub function: Option<FunctionRef<'a>>,
40    /// The expression's attributes.
41    pub attributes: Attributes<'a>,
42}
43
44/// An expression with a function and no operand.
45#[derive(Clone, PartialEq, Eq, Hash, Debug)]
46pub struct FunctionExpression<'a> {
47    /// The function.
48    pub function: FunctionRef<'a>,
49    /// The expression's attributes.
50    pub attributes: Attributes<'a>,
51}
52
53impl<'a> Expression<'a> {
54    /// The function, if the expression has one.
55    pub fn function(&self) -> Option<&FunctionRef<'a>> {
56        match self {
57            Expression::Literal(e) => e.function.as_ref(),
58            Expression::Variable(e) => e.function.as_ref(),
59            Expression::Function(e) => Some(&e.function),
60        }
61    }
62
63    /// The expression's attributes.
64    pub fn attributes(&self) -> &Attributes<'a> {
65        match self {
66            Expression::Literal(e) => &e.attributes,
67            Expression::Variable(e) => &e.attributes,
68            Expression::Function(e) => &e.attributes,
69        }
70    }
71
72    /// The same expression with every string owned.
73    pub fn into_owned(self) -> Expression<'static> {
74        match self {
75            Expression::Literal(e) => Expression::Literal(e.into_owned()),
76            Expression::Variable(e) => Expression::Variable(e.into_owned()),
77            Expression::Function(e) => Expression::Function(e.into_owned()),
78        }
79    }
80}
81
82impl LiteralExpression<'_> {
83    /// The same expression with every string owned.
84    pub fn into_owned(self) -> LiteralExpression<'static> {
85        LiteralExpression {
86            arg: self.arg.into_owned(),
87            function: self.function.map(FunctionRef::into_owned),
88            attributes: self.attributes.into_owned(),
89        }
90    }
91}
92
93impl VariableExpression<'_> {
94    /// The same expression with every string owned.
95    pub fn into_owned(self) -> VariableExpression<'static> {
96        VariableExpression {
97            arg: self.arg.into_owned(),
98            function: self.function.map(FunctionRef::into_owned),
99            attributes: self.attributes.into_owned(),
100        }
101    }
102}
103
104impl FunctionExpression<'_> {
105    /// The same expression with every string owned.
106    pub fn into_owned(self) -> FunctionExpression<'static> {
107        FunctionExpression {
108            function: self.function.into_owned(),
109            attributes: self.attributes.into_owned(),
110        }
111    }
112}
113
114fn own(s: Cow<'_, str>) -> Cow<'static, str> {
115    Cow::Owned(s.into_owned())
116}
117
118/// A literal: its **cooked** value (escapes processed). Whether it was
119/// quoted is not data-model information and is not kept.
120#[derive(Clone, PartialEq, Eq, Hash, Debug)]
121pub struct Literal<'a> {
122    /// The string value.
123    pub value: Cow<'a, str>,
124}
125
126impl Literal<'_> {
127    /// The same literal, owned.
128    pub fn into_owned(self) -> Literal<'static> {
129        Literal {
130            value: own(self.value),
131        }
132    }
133}
134
135/// A variable reference, by name (without the `$` sigil).
136#[derive(Clone, PartialEq, Eq, Hash, Debug)]
137pub struct VariableRef<'a> {
138    /// The name, as written, without bidi marks.
139    pub name: Cow<'a, str>,
140}
141
142impl VariableRef<'_> {
143    /// The same reference, owned.
144    pub fn into_owned(self) -> VariableRef<'static> {
145        VariableRef {
146            name: own(self.name),
147        }
148    }
149}
150
151/// A function reference: its identifier and options.
152#[derive(Clone, PartialEq, Eq, Hash, Debug)]
153pub struct FunctionRef<'a> {
154    /// The identifier, e.g. `"number"` or `"ns:fn"`, without the `:` sigil.
155    pub name: Cow<'a, str>,
156    /// The options, in source order.
157    pub options: Options<'a>,
158}
159
160impl FunctionRef<'_> {
161    /// The same reference, owned.
162    pub fn into_owned(self) -> FunctionRef<'static> {
163        FunctionRef {
164            name: own(self.name),
165            options: self.options.into_owned(),
166        }
167    }
168}
169
170/// An option's value: a literal or a variable.
171#[derive(Clone, PartialEq, Eq, Hash, Debug)]
172// Future versions of MF2 may define new structures (the specification's
173// stability policy); a new one is a new variant, which a 1.x minor may add.
174#[non_exhaustive]
175pub enum OptionValue<'a> {
176    /// `opt=|literal|` or `opt=literal`.
177    Literal(Literal<'a>),
178    /// `opt=$var`.
179    Variable(VariableRef<'a>),
180}
181
182impl OptionValue<'_> {
183    /// The same value, owned.
184    pub fn into_owned(self) -> OptionValue<'static> {
185        match self {
186            OptionValue::Literal(l) => OptionValue::Literal(l.into_owned()),
187            OptionValue::Variable(v) => OptionValue::Variable(v.into_owned()),
188        }
189    }
190}
191
192/// The options of a function or markup, in source order.
193///
194/// Duplicates are representable, so that validation can report *Duplicate
195/// Option Name*; a valid message has none.
196#[derive(Clone, Default, PartialEq, Eq, Hash, Debug)]
197pub struct Options<'a>(pub(crate) Vec<(Cow<'a, str>, OptionValue<'a>)>);
198
199impl<'a> Options<'a> {
200    /// No options (does not allocate).
201    pub const fn new() -> Self {
202        Options(Vec::new())
203    }
204
205    /// No options, with room for `capacity` without reallocating.
206    pub fn with_capacity(capacity: usize) -> Self {
207        Options(Vec::with_capacity(capacity))
208    }
209
210    /// Appends an option (duplicates are kept).
211    pub fn push(&mut self, name: Cow<'a, str>, value: OptionValue<'a>) {
212        self.0.push((name, value));
213    }
214
215    /// `(identifier, value)` pairs, in source order.
216    pub fn iter(&self) -> impl Iterator<Item = (&str, &OptionValue<'a>)> {
217        self.0.iter().map(|(k, v)| (k.as_ref(), v))
218    }
219
220    /// The value of the first option named exactly `name`.
221    pub fn get(&self, name: &str) -> Option<&OptionValue<'a>> {
222        self.0.iter().find(|(k, _)| k == name).map(|(_, v)| v)
223    }
224
225    /// How many options (duplicates included).
226    pub fn len(&self) -> usize {
227        self.0.len()
228    }
229
230    /// No options.
231    pub fn is_empty(&self) -> bool {
232        self.0.is_empty()
233    }
234
235    /// The same options, owned.
236    pub fn into_owned(self) -> Options<'static> {
237        Options(
238            self.0
239                .into_iter()
240                .map(|(k, v)| (own(k), v.into_owned()))
241                .collect(),
242        )
243    }
244}
245
246impl<'a> FromIterator<(Cow<'a, str>, OptionValue<'a>)> for Options<'a> {
247    fn from_iter<I: IntoIterator<Item = (Cow<'a, str>, OptionValue<'a>)>>(iter: I) -> Self {
248        Options(iter.into_iter().collect())
249    }
250}
251
252/// Markup: `{#name …}`, `{#name … /}` or `{/name …}`.
253#[derive(Clone, PartialEq, Eq, Hash, Debug)]
254pub struct Markup<'a> {
255    /// Open, standalone or close.
256    pub kind: MarkupKind,
257    /// The identifier, without the `#` / `/` sigils.
258    pub name: Cow<'a, str>,
259    /// The options, in source order.
260    pub options: Options<'a>,
261    /// The attributes, in source order.
262    pub attributes: Attributes<'a>,
263}
264
265impl Markup<'_> {
266    /// The same markup, owned.
267    pub fn into_owned(self) -> Markup<'static> {
268        Markup {
269            kind: self.kind,
270            name: own(self.name),
271            options: self.options.into_owned(),
272            attributes: self.attributes.into_owned(),
273        }
274    }
275}
276
277/// The three forms of markup.
278#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
279pub enum MarkupKind {
280    /// `{#name}`.
281    Open,
282    /// `{#name /}`.
283    Standalone,
284    /// `{/name}`.
285    Close,
286}
287
288/// The attributes of an expression or markup, in source order; a `None`
289/// value is the spec's `true` (an attribute written without a value).
290#[derive(Clone, Default, PartialEq, Eq, Hash, Debug)]
291pub struct Attributes<'a>(pub(crate) Vec<(Cow<'a, str>, Option<Literal<'a>>)>);
292
293impl<'a> Attributes<'a> {
294    /// No attributes (does not allocate).
295    pub const fn new() -> Self {
296        Attributes(Vec::new())
297    }
298
299    /// No attributes, with room for `capacity` without reallocating.
300    pub fn with_capacity(capacity: usize) -> Self {
301        Attributes(Vec::with_capacity(capacity))
302    }
303
304    /// Appends an attribute (duplicates are kept).
305    pub fn push(&mut self, name: Cow<'a, str>, value: Option<Literal<'a>>) {
306        self.0.push((name, value));
307    }
308
309    /// `(identifier, value)` pairs, in source order.
310    pub fn iter(&self) -> impl Iterator<Item = (&str, Option<&Literal<'a>>)> {
311        self.0.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
312    }
313
314    /// The value of the last attribute named exactly `name` — all but the
315    /// last are ignored (`syntax.md`, "Attributes"): `None` if there is
316    /// none, `Some(None)` if it has no value.
317    pub fn get(&self, name: &str) -> Option<Option<&Literal<'a>>> {
318        self.0
319            .iter()
320            .rfind(|(k, _)| k == name)
321            .map(|(_, v)| v.as_ref())
322    }
323
324    /// How many attributes (duplicates included).
325    pub fn len(&self) -> usize {
326        self.0.len()
327    }
328
329    /// No attributes.
330    pub fn is_empty(&self) -> bool {
331        self.0.is_empty()
332    }
333
334    /// The same attributes, owned.
335    pub fn into_owned(self) -> Attributes<'static> {
336        Attributes(
337            self.0
338                .into_iter()
339                .map(|(k, v)| (own(k), v.map(Literal::into_owned)))
340                .collect(),
341        )
342    }
343}
344
345impl<'a> FromIterator<(Cow<'a, str>, Option<Literal<'a>>)> for Attributes<'a> {
346    fn from_iter<I: IntoIterator<Item = (Cow<'a, str>, Option<Literal<'a>>)>>(iter: I) -> Self {
347        Attributes(iter.into_iter().collect())
348    }
349}
350
351/// Splits an identifier at its namespace separator: `"ns:name"` →
352/// `(Some("ns"), "name")`, `"name"` → `(None, "name")`. A name cannot contain
353/// `:`, so the first colon is the separator.
354pub fn split_identifier(identifier: &str) -> (Option<&str>, &str) {
355    match identifier.split_once(':') {
356        Some((ns, name)) => (Some(ns), name),
357        None => (None, identifier),
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use alloc::borrow::Cow;
364
365    use super::{
366        Attributes, Expression, FunctionExpression, FunctionRef, Literal, OptionValue, Options,
367        VariableExpression, VariableRef, split_identifier,
368    };
369
370    fn lit(s: &str) -> Literal<'_> {
371        Literal {
372            value: Cow::Borrowed(s),
373        }
374    }
375
376    #[test]
377    fn options_keep_order_and_duplicates() {
378        let mut o = Options::new();
379        assert!(o.is_empty());
380        o.push("b".into(), OptionValue::Literal(lit("1")));
381        o.push(
382            "a".into(),
383            OptionValue::Variable(VariableRef { name: "x".into() }),
384        );
385        o.push("b".into(), OptionValue::Literal(lit("2")));
386        assert_eq!(o.len(), 3);
387        let names: alloc::vec::Vec<&str> = o.iter().map(|(k, _)| k).collect();
388        assert_eq!(names, ["b", "a", "b"]);
389        // `get` returns the first exact match.
390        assert_eq!(o.get("b"), Some(&OptionValue::Literal(lit("1"))));
391        assert_eq!(
392            o.get("a"),
393            Some(&OptionValue::Variable(VariableRef { name: "x".into() }))
394        );
395        assert_eq!(o.get("c"), None);
396        // Exact, not normalized: "é" precomposed vs decomposed differ.
397        let mut n = Options::new();
398        n.push("\u{e9}".into(), OptionValue::Literal(lit("1")));
399        assert!(n.get("e\u{301}").is_none());
400    }
401
402    #[test]
403    fn attributes_keep_order_and_duplicates() {
404        let mut a = Attributes::new();
405        a.push("flag".into(), None);
406        a.push("x".into(), Some(lit("1")));
407        a.push("flag".into(), Some(lit("2")));
408        assert_eq!(a.len(), 3);
409        // The last of a repeated name is the one that counts.
410        assert_eq!(a.get("flag"), Some(Some(&lit("2"))));
411        assert_eq!(a.get("x"), Some(Some(&lit("1"))));
412        assert_eq!(a.get("y"), None);
413        let all: alloc::vec::Vec<(&str, Option<&Literal<'_>>)> = a.iter().collect();
414        assert_eq!(all[2], ("flag", Some(&lit("2"))));
415    }
416
417    #[test]
418    fn split_identifiers() {
419        assert_eq!(split_identifier("ns:name"), (Some("ns"), "name"));
420        assert_eq!(split_identifier("name"), (None, "name"));
421        assert_eq!(split_identifier("u:dir"), (Some("u"), "dir"));
422        assert_eq!(split_identifier(""), (None, ""));
423    }
424
425    #[test]
426    fn expression_accessors_and_owning() {
427        let mut attrs = Attributes::new();
428        attrs.push("a".into(), None);
429        let f = FunctionRef {
430            name: "number".into(),
431            options: Options::new(),
432        };
433        let e = Expression::Variable(VariableExpression {
434            arg: VariableRef { name: "n".into() },
435            function: Some(f.clone()),
436            attributes: attrs.clone(),
437        });
438        assert_eq!(e.function(), Some(&f));
439        assert_eq!(e.attributes(), &attrs);
440        let fe = Expression::Function(FunctionExpression {
441            function: f.clone(),
442            attributes: Attributes::new(),
443        });
444        assert_eq!(fe.function().map(|f| f.name.as_ref()), Some("number"));
445        let owned: Expression<'static> = e.clone().into_owned();
446        assert_eq!(owned, e);
447        if let Expression::Variable(v) = owned {
448            assert!(matches!(v.arg.name, Cow::Owned(_)));
449        }
450    }
451}