1use 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#[derive(Clone, PartialEq, Eq, Hash, Debug)]
15#[non_exhaustive]
16pub struct Name<'m> {
17 pub nfc: Cow<'m, str>,
19 pub spelling: &'m str,
21}
22
23#[derive(Clone, Default, PartialEq, Eq, Debug)]
25#[non_exhaustive]
26pub struct Analysis<'m> {
27 pub externals: Vec<Name<'m>>,
33 pub locals: Vec<Name<'m>>,
36 pub markup: Vec<Name<'m>>,
38 pub functions: Vec<Name<'m>>,
41}
42
43pub fn analyze<'m>(message: &'m Message<'_>) -> Analysis<'m> {
50 let mut a = Collector::default();
51 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#[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 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}