1use alloc::borrow::Cow;
27use alloc::collections::{BTreeMap, BTreeSet};
28use alloc::vec::Vec;
29
30use mf2_model::{
31 Declaration, Diagnostic, Diagnostics, ErrorKind, Expression, Key, Message, OptionValue,
32 Options, Pattern, PatternPart, Variant,
33};
34
35use crate::code;
36use crate::norm::{nfc, nfc_eq};
37
38pub(crate) const SMALL: usize = 16;
40
41#[derive(Clone, Copy, PartialEq, Eq, Debug)]
43pub(crate) enum Loc {
44 Declaration(usize),
46 Selector(usize),
48 Variant(usize),
50 Matcher,
52 Option { expr: ExprLoc, index: usize },
54}
55
56#[derive(Clone, Copy, PartialEq, Eq, Debug)]
58pub(crate) enum ExprLoc {
59 Declaration(usize),
61 Part { variant: Option<usize>, part: usize },
64}
65
66pub fn validate(message: &Message<'_>) -> Diagnostics {
68 let mut out = Diagnostics::new();
69 check(message, &mut |kind, code, _| {
70 out.push(Diagnostic::new(kind, code, None));
71 });
72 out
73}
74
75pub(crate) fn check(message: &Message<'_>, report: &mut impl FnMut(ErrorKind, u16, Loc)) {
78 let declarations = message.declarations();
79 let duplicates = duplicate_declarations(declarations);
80 for (i, d) in declarations.iter().enumerate() {
81 let duplicate = if let Some(set) = &duplicates {
82 set.binary_search(&i).is_ok()
83 } else {
84 earlier_declarations_use(declarations, i, d.name())
85 };
86 if duplicate {
87 report(
88 ErrorKind::DuplicateDeclaration,
89 code::DUPLICATE_DECLARATION,
90 Loc::Declaration(i),
91 );
92 } else if uses_itself(d) {
93 report(
94 ErrorKind::DuplicateDeclaration,
95 code::SELF_REFERENCING_DECLARATION,
96 Loc::Declaration(i),
97 );
98 }
99 let function = match d {
100 Declaration::Input(x) => x.value.function.as_ref(),
101 Declaration::Local(x) => x.value.function(),
102 _ => None,
103 };
104 if let Some(f) = function {
105 check_options(&f.options, ExprLoc::Declaration(i), report);
106 }
107 }
108 match message {
109 Message::Pattern(p) => check_pattern(&p.pattern, None, report),
110 Message::Select(s) => {
111 let annotated = Annotations::new(declarations);
112 for (i, selector) in s.selectors.iter().enumerate() {
113 if !annotated.is_annotated(declarations, &selector.name) {
114 report(
115 ErrorKind::MissingSelectorAnnotation,
116 code::MISSING_SELECTOR_ANNOTATION,
117 Loc::Selector(i),
118 );
119 }
120 }
121 let has_fallback = s
122 .variants
123 .iter()
124 .any(|v| v.keys.iter().all(|k| matches!(k, Key::CatchAll(_))));
125 if !has_fallback {
126 report(
127 ErrorKind::MissingFallbackVariant,
128 code::MISSING_FALLBACK_VARIANT,
129 Loc::Matcher,
130 );
131 }
132 let duplicates = duplicate_variants(&s.variants);
133 for (i, v) in s.variants.iter().enumerate() {
134 if v.keys.len() != s.selectors.len() {
135 report(
136 ErrorKind::VariantKeyMismatch,
137 code::VARIANT_KEY_MISMATCH,
138 Loc::Variant(i),
139 );
140 }
141 let duplicate = if let Some(set) = &duplicates {
142 set.binary_search(&i).is_ok()
143 } else {
144 let earlier = s.variants.get(..i).unwrap_or_default();
145 earlier.iter().any(|w| keys_equal(&w.keys, &v.keys))
146 };
147 if duplicate {
148 report(
149 ErrorKind::DuplicateVariant,
150 code::DUPLICATE_VARIANT,
151 Loc::Variant(i),
152 );
153 }
154 check_pattern(&v.value, Some(i), report);
155 }
156 }
157 _ => {}
158 }
159}
160
161fn earlier_declarations_use(declarations: &[Declaration<'_>], i: usize, name: &str) -> bool {
166 let earlier = declarations.get(..i).unwrap_or_default();
167 earlier
168 .iter()
169 .any(|p| nfc_eq(p.name(), name) || variables_used(p).any(|v| nfc_eq(v, name)))
170}
171
172fn duplicate_declarations(declarations: &[Declaration<'_>]) -> Option<Vec<usize>> {
176 if declarations.len() <= SMALL {
177 return None;
178 }
179 let mut seen: BTreeSet<Cow<'_, str>> = BTreeSet::new();
180 let mut out = Vec::new();
181 for (i, d) in declarations.iter().enumerate() {
182 let name = nfc(d.name());
183 if seen.contains(&name) {
184 out.push(i);
185 }
186 seen.insert(name);
187 for v in variables_used(d) {
188 seen.insert(nfc(v));
189 }
190 }
191 Some(out)
192}
193
194fn variables_used<'d>(d: &'d Declaration<'_>) -> impl Iterator<Item = &'d str> {
198 let (operand, function) = match d {
199 Declaration::Input(x) => (None, x.value.function.as_ref()),
200 Declaration::Local(x) => (
201 match &x.value {
202 Expression::Variable(v) => Some(&*v.arg.name),
203 _ => None,
204 },
205 x.value.function(),
206 ),
207 _ => (None, None),
208 };
209 let options = function.into_iter().flat_map(|f| {
210 f.options.iter().filter_map(|(_, v)| match v {
211 OptionValue::Variable(v) => Some(&*v.name),
212 _ => None,
213 })
214 });
215 operand.into_iter().chain(options)
216}
217
218fn uses_itself(d: &Declaration<'_>) -> bool {
220 let name = d.name();
221 variables_used(d).any(|v| nfc_eq(v, name))
222}
223
224enum Annotations<'d> {
227 Walk,
229 Table {
232 annotated: Vec<bool>,
233 last: BTreeMap<Cow<'d, str>, usize>,
234 },
235}
236
237impl<'d> Annotations<'d> {
238 fn new(declarations: &'d [Declaration<'_>]) -> Self {
239 if declarations.len() <= SMALL {
240 return Annotations::Walk;
241 }
242 let mut annotated = Vec::with_capacity(declarations.len());
243 let mut last: BTreeMap<Cow<'d, str>, usize> = BTreeMap::new();
244 for (i, d) in declarations.iter().enumerate() {
245 let a = match d {
246 Declaration::Input(x) => x.value.function.is_some(),
247 Declaration::Local(x) => {
248 x.value.function().is_some()
249 || match &x.value {
250 Expression::Variable(v) => last
251 .get(&nfc(&v.arg.name))
252 .is_some_and(|&j| annotated.get(j).copied().unwrap_or(false)),
253 _ => false,
254 }
255 }
256 _ => false,
257 };
258 annotated.push(a);
259 last.insert(nfc(d.name()), i);
260 }
261 Annotations::Table { annotated, last }
262 }
263
264 fn is_annotated(&self, declarations: &[Declaration<'_>], name: &str) -> bool {
265 match self {
266 Annotations::Walk => walk_annotation(declarations, name),
267 Annotations::Table { annotated, last } => last
268 .get(&nfc(name))
269 .is_some_and(|&j| annotated.get(j).copied().unwrap_or(false)),
270 }
271 }
272}
273
274fn walk_annotation(declarations: &[Declaration<'_>], name: &str) -> bool {
278 let mut name = name;
279 let mut upto = declarations.len();
280 loop {
281 let candidates = declarations.get(..upto).unwrap_or_default();
282 let Some(i) = candidates.iter().rposition(|d| nfc_eq(d.name(), name)) else {
283 return false;
284 };
285 match candidates.get(i) {
286 Some(Declaration::Input(d)) => return d.value.function.is_some(),
287 Some(Declaration::Local(d)) => {
288 if d.value.function().is_some() {
289 return true;
290 }
291 match &d.value {
292 Expression::Variable(v) => {
293 name = &v.arg.name;
294 upto = i;
295 }
296 _ => return false,
297 }
298 }
299 _ => return false,
300 }
301 }
302}
303
304fn keys_equal(a: &[Key<'_>], b: &[Key<'_>]) -> bool {
307 a.len() == b.len()
308 && a.iter().zip(b).all(|(x, y)| match (x, y) {
309 (Key::CatchAll(_), Key::CatchAll(_)) => true,
310 (Key::Literal(x), Key::Literal(y)) => nfc_eq(&x.value, &y.value),
311 _ => false,
312 })
313}
314
315fn duplicate_variants(variants: &[Variant<'_>]) -> Option<Vec<usize>> {
319 if variants.len() <= SMALL {
320 return None;
321 }
322 let mut keyed: Vec<(Vec<Option<Cow<'_, str>>>, usize)> = variants
323 .iter()
324 .enumerate()
325 .map(|(i, v)| {
326 let keys = v
327 .keys
328 .iter()
329 .map(|k| match k {
330 Key::Literal(l) => Some(nfc(&l.value)),
331 _ => None,
332 })
333 .collect();
334 (keys, i)
335 })
336 .collect();
337 keyed.sort();
338 Some(later_duplicates(&keyed))
339}
340
341fn later_duplicates<K: PartialEq>(sorted: &[(K, usize)]) -> Vec<usize> {
344 let mut out: Vec<usize> = sorted
345 .windows(2)
346 .filter(|w| w[0].0 == w[1].0)
347 .map(|w| w[1].1)
348 .collect();
349 out.sort_unstable();
350 out
351}
352
353fn check_pattern(
356 pattern: &Pattern<'_>,
357 variant: Option<usize>,
358 report: &mut impl FnMut(ErrorKind, u16, Loc),
359) {
360 for (part, p) in pattern.parts().iter().enumerate() {
361 let options = match p {
362 PatternPart::Expression(e) => e.function().map(|f| &f.options),
363 PatternPart::Markup(m) => Some(&m.options),
364 _ => None,
365 };
366 if let Some(options) = options {
367 check_options(options, ExprLoc::Part { variant, part }, report);
368 }
369 }
370}
371
372fn check_options(
373 options: &Options<'_>,
374 expr: ExprLoc,
375 report: &mut impl FnMut(ErrorKind, u16, Loc),
376) {
377 let mut emit = |index| {
378 report(
379 ErrorKind::DuplicateOptionName,
380 code::DUPLICATE_OPTION_NAME,
381 Loc::Option { expr, index },
382 );
383 };
384 if options.len() <= SMALL {
385 for (index, (name, _)) in options.iter().enumerate() {
386 if options.iter().take(index).any(|(p, _)| nfc_eq(p, name)) {
387 emit(index);
388 }
389 }
390 } else {
391 let mut keyed: Vec<(Cow<'_, str>, usize)> = options
392 .iter()
393 .enumerate()
394 .map(|(i, (name, _))| (nfc(name), i))
395 .collect();
396 keyed.sort();
397 for index in later_duplicates(&keyed) {
398 emit(index);
399 }
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use alloc::format;
406 use alloc::string::String;
407 use alloc::vec::Vec;
408 use core::fmt::Write as _;
409
410 use mf2_model::{Diagnostics, ErrorKind, Message};
411
412 use super::{SMALL, validate};
413
414 fn kinds(d: &Diagnostics) -> Vec<ErrorKind> {
415 d.iter().map(|x| x.kind).collect()
416 }
417
418 fn parse(src: &str) -> Message<'_> {
419 crate::parse_model(src).message.expect("parses")
420 }
421
422 #[test]
426 fn both_forms_of_every_check_agree() {
427 for n in [SMALL - 1, SMALL, SMALL + 1, 3 * SMALL] {
428 let mut src = String::from("{:f");
431 for i in 0..n {
432 let name = if i % 3 == 2 {
433 format!("o{}", i - 2)
434 } else {
435 format!("o{i}")
436 };
437 let _ = write!(src, " {name}=1");
438 }
439 src.push_str(" \u{e9}=1 e\u{301}=2}");
440 let d = validate(&parse(&src));
441 assert_eq!(d.len(), n / 3 + 1, "options, n = {n}");
442 assert!(
443 kinds(&d)
444 .iter()
445 .all(|k| *k == ErrorKind::DuplicateOptionName)
446 );
447
448 let mut src = String::new();
450 for i in 0..n {
451 let _ = write!(src, ".local $v{i} = {{$e{i}}} ");
452 }
453 src.push_str(".local $v1 = {1} .local $e2 = {2} .local $w = {$w} {{}}");
454 let d = validate(&parse(&src));
455 assert_eq!(
456 kinds(&d),
457 [ErrorKind::DuplicateDeclaration; 3],
458 "declarations, n = {n}"
459 );
460
461 let mut src = String::from(".input {$x :f} .match $x ");
463 for i in 0..n {
464 let _ = write!(src, "k{i} {{{{}}}} ");
465 }
466 src.push_str("k1 {{}} |*| {{}} \u{e9} {{}} e\u{301} {{}} * {{}} * {{}}");
467 let d = validate(&parse(&src));
468 assert_eq!(
469 kinds(&d),
470 [ErrorKind::DuplicateVariant; 3],
471 "variants, n = {n}"
472 );
473
474 let mut src = String::from(".input {$a0 :f} ");
476 for i in 1..n {
477 let _ = write!(src, ".local $a{i} = {{$a{}}} ", i - 1);
478 }
479 let _ = write!(src, ".local $z = {{1}} .match $a{} $z * * {{{{}}}}", n - 1);
480 let d = validate(&parse(&src));
481 assert_eq!(
482 kinds(&d),
483 [ErrorKind::MissingSelectorAnnotation],
484 "selectors, n = {n}"
485 );
486 }
487 }
488}