Skip to main content

mf2_syntax/
analyze.rs

1//! Variable analysis: what a message needs from its caller — the input to
2//! the build's manifest (plans/05-tooling.md §3).
3
4use alloc::borrow::Cow;
5use alloc::collections::BTreeSet;
6use alloc::vec::Vec;
7
8use mf2_model::{Declaration, Expression, Message, OptionValue, Options, Pattern, PatternPart};
9
10use crate::norm::nfc;
11
12/// A name used by a message: its NFC form (what comparisons and the manifest
13/// use) and its spelling as written (first occurrence in source order).
14#[derive(Clone, PartialEq, Eq, Hash, Debug)]
15#[non_exhaustive]
16pub struct Name<'m> {
17    /// The name in Unicode Normalization Form C.
18    pub nfc: Cow<'m, str>,
19    /// The name as first written in the message.
20    pub spelling: &'m str,
21}
22
23/// What [`analyze`] found.
24#[derive(Clone, Default, PartialEq, Eq, Debug)]
25#[non_exhaustive]
26pub struct Analysis<'m> {
27    /// External variables — every variable the caller must supply: the
28    /// variables of `.input` declarations and every variable referenced
29    /// where no `.local` of that name is in scope (including variables used
30    /// only in options, selectors or markup options). Unique under NFC, in
31    /// ascending bytewise order of the NFC form: the manifest's slot order.
32    pub externals: Vec<Name<'m>>,
33    /// Local variables (`.local` declarations), in declaration order, unique
34    /// under NFC.
35    pub locals: Vec<Name<'m>>,
36    /// Markup names, unique under NFC, ascending by NFC form.
37    pub markup: Vec<Name<'m>>,
38    /// Function identifiers (`number`, `ns:fn`), unique under NFC, ascending
39    /// by NFC form.
40    pub functions: Vec<Name<'m>>,
41}
42
43/// Analyzes `message`.
44///
45/// Scope follows the declarations: a reference is local if a `.local` of
46/// that name comes *before* it (a declaration's own expression does not see
47/// the variable it binds). For a valid message this is the same as "every
48/// referenced name that no `.local` declares".
49pub fn analyze<'m>(message: &'m Message<'_>) -> Analysis<'m> {
50    let mut a = Collector::default();
51    // NFC names of the locals declared so far (in scope from here on).
52    let mut locals: BTreeSet<Cow<'m, str>> = BTreeSet::new();
53    for d in message.declarations() {
54        match d {
55            Declaration::Input(x) => {
56                a.external(&x.name, &locals);
57                if let Some(f) = &x.value.function {
58                    a.functions.insert(&f.name);
59                    a.options(&f.options, &locals);
60                }
61            }
62            Declaration::Local(x) => {
63                a.expression(&x.value, &locals);
64                a.locals.insert(&x.name);
65                locals.insert(nfc(&x.name));
66            }
67            _ => {}
68        }
69    }
70    match message {
71        Message::Pattern(m) => a.pattern(&m.pattern, &locals),
72        Message::Select(m) => {
73            for s in &m.selectors {
74                a.external(&s.name, &locals);
75            }
76            for v in &m.variants {
77                a.pattern(&v.value, &locals);
78            }
79        }
80        _ => {}
81    }
82    a.finish()
83}
84
85/// Names unique under NFC, in first-seen order.
86#[derive(Default)]
87struct Names<'m> {
88    order: Vec<Name<'m>>,
89    seen: BTreeSet<Cow<'m, str>>,
90}
91
92impl<'m> Names<'m> {
93    fn insert(&mut self, name: &'m str) {
94        let n = nfc(name);
95        if !self.seen.contains(&n) {
96            self.seen.insert(n.clone());
97            self.order.push(Name {
98                nfc: n,
99                spelling: name,
100            });
101        }
102    }
103
104    /// In ascending bytewise order of the NFC form.
105    fn sorted(mut self) -> Vec<Name<'m>> {
106        self.order
107            .sort_by(|a, b| a.nfc.as_bytes().cmp(b.nfc.as_bytes()));
108        self.order
109    }
110}
111
112#[derive(Default)]
113struct Collector<'m> {
114    externals: Names<'m>,
115    locals: Names<'m>,
116    markup: Names<'m>,
117    functions: Names<'m>,
118}
119
120impl<'m> Collector<'m> {
121    fn external(&mut self, name: &'m str, locals: &BTreeSet<Cow<'m, str>>) {
122        if !locals.contains(&nfc(name)) {
123            self.externals.insert(name);
124        }
125    }
126
127    fn options(&mut self, options: &'m Options<'_>, locals: &BTreeSet<Cow<'m, str>>) {
128        for (_, v) in options.iter() {
129            if let OptionValue::Variable(v) = v {
130                self.external(&v.name, locals);
131            }
132        }
133    }
134
135    fn expression(&mut self, e: &'m Expression<'_>, locals: &BTreeSet<Cow<'m, str>>) {
136        if let Expression::Variable(v) = e {
137            self.external(&v.arg.name, locals);
138        }
139        if let Some(f) = e.function() {
140            self.functions.insert(&f.name);
141            self.options(&f.options, locals);
142        }
143    }
144
145    fn pattern(&mut self, p: &'m Pattern<'_>, locals: &BTreeSet<Cow<'m, str>>) {
146        for part in p.parts() {
147            match part {
148                PatternPart::Expression(e) => self.expression(e, locals),
149                PatternPart::Markup(m) => {
150                    self.markup.insert(&m.name);
151                    self.options(&m.options, locals);
152                }
153                _ => {}
154            }
155        }
156    }
157
158    fn finish(self) -> Analysis<'m> {
159        Analysis {
160            externals: self.externals.sorted(),
161            locals: self.locals.order,
162            markup: self.markup.sorted(),
163            functions: self.functions.sorted(),
164        }
165    }
166}