Skip to main content

mf2_syntax/
serialize.rs

1//! The serializer: data model → canonical MF2 source.
2//!
3//! Canonical form:
4//!
5//! * a pattern message without declarations is a **simple message** — unless
6//!   its text, after leading whitespace and bidi marks, starts with `.`
7//!   (which would read as a keyword); then it is a quoted pattern `{{…}}`;
8//! * otherwise one declaration per line (`.input {$x :number}`,
9//!   `.local $y = {$x}`), then `{{…}}`, or `.match $a $b` and one variant per
10//!   line (`key key {{…}}`);
11//! * single spaces between the parts of an expression (`{$x :fn opt=v @a}`),
12//!   `{#name /}` for standalone markup;
13//! * literals unquoted when they match `unquoted-literal`, else `|…|` with `\`
14//!   and `|` escaped; text escapes exactly `\`, `{` and `}`.
15//!
16//! `parse_model(serialize(m)?)` gives back `m` for every model a parse can
17//! produce. A catch-all key's `value` (set only by other formats) is not
18//! representable in MF2 syntax and is dropped: the key is written `*`.
19
20use alloc::string::String;
21
22use mf2_model::{
23    Attributes, Declaration, Expression, FunctionRef, Key, Literal, Markup, MarkupKind, Message,
24    OptionValue, Options, Pattern, PatternPart,
25};
26
27use crate::chars;
28use crate::error::{Error, NameRole};
29
30/// Serializes `message` as canonical MF2 source.
31///
32/// Fails if MF2 syntax cannot represent the model: U+0000 in text or a
33/// literal, an invalid name, an input declaration whose name differs from its
34/// variable, or a select message without selectors, variants or keys.
35pub fn serialize(message: &Message<'_>) -> Result<String, Error> {
36    let mut w = Writer(String::new());
37    match message {
38        Message::Pattern(m) if m.declarations.is_empty() => {
39            w.pattern(&m.pattern)?;
40            if reads_as_complex(&w.0) {
41                let mut quoted = String::with_capacity(w.0.len() + 4);
42                quoted.push_str("{{");
43                quoted.push_str(&w.0);
44                quoted.push_str("}}");
45                return Ok(quoted);
46            }
47        }
48        Message::Pattern(m) => {
49            w.declarations(&m.declarations)?;
50            w.quoted_pattern(&m.pattern)?;
51        }
52        Message::Select(m) => {
53            if m.selectors.is_empty() {
54                return Err(Error::NoSelectors);
55            }
56            if m.variants.is_empty() {
57                return Err(Error::NoVariants);
58            }
59            w.declarations(&m.declarations)?;
60            w.0.push_str(".match");
61            for s in &m.selectors {
62                w.0.push_str(" $");
63                w.name(&s.name, NameRole::Variable)?;
64            }
65            for v in &m.variants {
66                if v.keys.is_empty() {
67                    return Err(Error::NoKeys);
68                }
69                w.0.push('\n');
70                for (i, k) in v.keys.iter().enumerate() {
71                    if i > 0 {
72                        w.0.push(' ');
73                    }
74                    match k {
75                        Key::CatchAll(_) => w.0.push('*'),
76                        Key::Literal(l) => w.literal(l)?,
77                        _ => return Err(Error::UnknownNode),
78                    }
79                }
80                w.0.push(' ');
81                w.quoted_pattern(&v.value)?;
82            }
83        }
84        _ => return Err(Error::UnknownNode),
85    }
86    Ok(w.0)
87}
88
89/// Whether text written as a simple message would parse as a complex one:
90/// its first character after whitespace and bidi marks is `.`.
91fn reads_as_complex(s: &str) -> bool {
92    let b = s.as_bytes();
93    let mut i = 0;
94    while let Some((n, _)) = chars::trivia_at(b, i) {
95        i += n;
96    }
97    b.get(i) == Some(&b'.')
98}
99
100struct Writer(String);
101
102impl Writer {
103    fn declarations(&mut self, declarations: &[Declaration<'_>]) -> Result<(), Error> {
104        for d in declarations {
105            match d {
106                Declaration::Input(x) => {
107                    if x.name != x.value.arg.name {
108                        return Err(Error::InputNameMismatch);
109                    }
110                    self.0.push_str(".input {$");
111                    self.name(&x.value.arg.name, NameRole::Variable)?;
112                    self.expression_tail(x.value.function.as_ref(), &x.value.attributes)?;
113                }
114                Declaration::Local(x) => {
115                    self.0.push_str(".local $");
116                    self.name(&x.name, NameRole::Variable)?;
117                    self.0.push_str(" = ");
118                    self.expression(&x.value)?;
119                }
120                _ => return Err(Error::UnknownNode),
121            }
122            self.0.push('\n');
123        }
124        Ok(())
125    }
126
127    fn quoted_pattern(&mut self, p: &Pattern<'_>) -> Result<(), Error> {
128        self.0.push_str("{{");
129        self.pattern(p)?;
130        self.0.push_str("}}");
131        Ok(())
132    }
133
134    fn pattern(&mut self, p: &Pattern<'_>) -> Result<(), Error> {
135        for part in p.parts() {
136            match part {
137                PatternPart::Text(t) => self.text(t)?,
138                PatternPart::Expression(e) => self.expression(e)?,
139                PatternPart::Markup(m) => self.markup(m)?,
140                _ => return Err(Error::UnknownNode),
141            }
142        }
143        Ok(())
144    }
145
146    fn text(&mut self, t: &str) -> Result<(), Error> {
147        for c in t.chars() {
148            match c {
149                '\0' => return Err(Error::Nul),
150                '\\' | '{' | '}' => {
151                    self.0.push('\\');
152                    self.0.push(c);
153                }
154                c => self.0.push(c),
155            }
156        }
157        Ok(())
158    }
159
160    fn expression(&mut self, e: &Expression<'_>) -> Result<(), Error> {
161        self.0.push('{');
162        match e {
163            Expression::Literal(x) => {
164                self.literal(&x.arg)?;
165                self.expression_tail(x.function.as_ref(), &x.attributes)
166            }
167            Expression::Variable(x) => {
168                self.0.push('$');
169                self.name(&x.arg.name, NameRole::Variable)?;
170                self.expression_tail(x.function.as_ref(), &x.attributes)
171            }
172            Expression::Function(x) => {
173                self.function(&x.function)?;
174                self.attributes(&x.attributes)?;
175                self.0.push('}');
176                Ok(())
177            }
178            _ => Err(Error::UnknownNode),
179        }
180    }
181
182    /// ` :function opts @attrs}` after an operand.
183    fn expression_tail(
184        &mut self,
185        function: Option<&FunctionRef<'_>>,
186        attributes: &Attributes<'_>,
187    ) -> Result<(), Error> {
188        if let Some(f) = function {
189            self.0.push(' ');
190            self.function(f)?;
191        }
192        self.attributes(attributes)?;
193        self.0.push('}');
194        Ok(())
195    }
196
197    fn function(&mut self, f: &FunctionRef<'_>) -> Result<(), Error> {
198        self.0.push(':');
199        self.name(&f.name, NameRole::Function)?;
200        self.options(&f.options)
201    }
202
203    fn options(&mut self, options: &Options<'_>) -> Result<(), Error> {
204        for (name, value) in options.iter() {
205            self.0.push(' ');
206            self.name(name, NameRole::Option)?;
207            self.0.push('=');
208            match value {
209                OptionValue::Literal(l) => self.literal(l)?,
210                OptionValue::Variable(v) => {
211                    self.0.push('$');
212                    self.name(&v.name, NameRole::Variable)?;
213                }
214                _ => return Err(Error::UnknownNode),
215            }
216        }
217        Ok(())
218    }
219
220    fn attributes(&mut self, attributes: &Attributes<'_>) -> Result<(), Error> {
221        for (name, value) in attributes.iter() {
222            self.0.push_str(" @");
223            self.name(name, NameRole::Attribute)?;
224            if let Some(l) = value {
225                self.0.push('=');
226                self.literal(l)?;
227            }
228        }
229        Ok(())
230    }
231
232    fn markup(&mut self, m: &Markup<'_>) -> Result<(), Error> {
233        self.0.push_str(match m.kind {
234            MarkupKind::Close => "{/",
235            MarkupKind::Open | MarkupKind::Standalone => "{#",
236        });
237        self.name(&m.name, NameRole::Markup)?;
238        self.options(&m.options)?;
239        self.attributes(&m.attributes)?;
240        self.0.push_str(match m.kind {
241            MarkupKind::Standalone => " /}",
242            MarkupKind::Open | MarkupKind::Close => "}",
243        });
244        Ok(())
245    }
246
247    fn literal(&mut self, l: &Literal<'_>) -> Result<(), Error> {
248        if chars::is_unquoted_literal(&l.value) {
249            self.0.push_str(&l.value);
250            return Ok(());
251        }
252        self.0.push('|');
253        for c in l.value.chars() {
254            match c {
255                '\0' => return Err(Error::Nul),
256                '\\' | '|' => {
257                    self.0.push('\\');
258                    self.0.push(c);
259                }
260                c => self.0.push(c),
261            }
262        }
263        self.0.push('|');
264        Ok(())
265    }
266
267    /// A variable name must be a `name`; the others may be namespaced
268    /// `identifier`s.
269    fn name(&mut self, name: &str, role: NameRole) -> Result<(), Error> {
270        let ok = match role {
271            NameRole::Variable => chars::is_name(name),
272            _ => chars::is_identifier(name),
273        };
274        if !ok {
275            return Err(Error::InvalidName(role));
276        }
277        self.0.push_str(name);
278        Ok(())
279    }
280}