Skip to main content

oxilean_parse/notation_system/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{
6    BuiltinPrecTable, Fixity, NotationConflict, NotationEntry, NotationKind, NotationPart,
7    NotationRegistry, NotationRule, NotationTokenKind,
8};
9
10/// Parse a notation pattern string into parts.
11///
12/// For example, `lhs " + " rhs` becomes:
13/// `[Placeholder("lhs"), Literal(" + "), Placeholder("rhs")]`
14pub fn parse_notation_pattern(input: &str) -> Vec<NotationPart> {
15    let mut parts = Vec::new();
16    let mut chars = input.chars().peekable();
17    while let Some(&ch) = chars.peek() {
18        if ch.is_whitespace() {
19            chars.next();
20            continue;
21        }
22        if ch == '"' {
23            chars.next();
24            let mut lit = String::new();
25            while let Some(&c) = chars.peek() {
26                if c == '"' {
27                    chars.next();
28                    break;
29                }
30                lit.push(c);
31                chars.next();
32            }
33            if !lit.is_empty() {
34                parts.push(NotationPart::Literal(lit));
35            }
36        } else if ch == ':' {
37            chars.next();
38            let mut num_str = String::new();
39            while let Some(&c) = chars.peek() {
40                if c.is_ascii_digit() {
41                    num_str.push(c);
42                    chars.next();
43                } else {
44                    break;
45                }
46            }
47            if let Ok(prec) = num_str.parse::<u32>() {
48                parts.push(NotationPart::Prec(prec));
49            }
50        } else if ch.is_alphanumeric() || ch == '_' {
51            let mut name = String::new();
52            while let Some(&c) = chars.peek() {
53                if c.is_alphanumeric() || c == '_' {
54                    name.push(c);
55                    chars.next();
56                } else {
57                    break;
58                }
59            }
60            if !name.is_empty() {
61                parts.push(NotationPart::Placeholder(name));
62            }
63        } else {
64            let mut sym = String::new();
65            while let Some(&c) = chars.peek() {
66                if !c.is_alphanumeric() && !c.is_whitespace() && c != '"' && c != '_' && c != ':' {
67                    sym.push(c);
68                    chars.next();
69                } else {
70                    break;
71                }
72            }
73            if !sym.is_empty() {
74                parts.push(NotationPart::Literal(sym));
75            }
76        }
77    }
78    parts
79}
80/// Convert a `NotationKind` to a `Fixity` (where applicable).
81///
82/// `NotationKind::Notation` has no single fixity, so returns `None`.
83pub fn kind_to_fixity(kind: &NotationKind) -> Option<Fixity> {
84    match kind {
85        NotationKind::Prefix => Some(Fixity::Prefix),
86        NotationKind::Postfix => Some(Fixity::Postfix),
87        NotationKind::Infixl => Some(Fixity::Infixl),
88        NotationKind::Infixr => Some(Fixity::Infixr),
89        NotationKind::Notation => None,
90    }
91}
92/// Convert a `Fixity` to the corresponding `NotationKind`.
93pub fn fixity_to_kind(fixity: &Fixity) -> NotationKind {
94    match fixity {
95        Fixity::Prefix => NotationKind::Prefix,
96        Fixity::Infixl => NotationKind::Infixl,
97        Fixity::Infixr => NotationKind::Infixr,
98        Fixity::Postfix => NotationKind::Postfix,
99    }
100}
101/// Build a simple infix notation entry from symbol, precedence, associativity.
102pub fn make_infix(
103    symbol: &str,
104    precedence: u32,
105    right_assoc: bool,
106    expansion: &str,
107) -> NotationEntry {
108    let kind = if right_assoc {
109        NotationKind::Infixr
110    } else {
111        NotationKind::Infixl
112    };
113    let pattern = vec![
114        NotationPart::Placeholder("lhs".into()),
115        NotationPart::Literal(symbol.into()),
116        NotationPart::Placeholder("rhs".into()),
117    ];
118    NotationEntry::new(kind, symbol.into(), pattern, expansion.into(), precedence)
119}
120/// Build a simple prefix notation entry.
121pub fn make_prefix(symbol: &str, precedence: u32, expansion: &str) -> NotationEntry {
122    let pattern = vec![
123        NotationPart::Literal(symbol.into()),
124        NotationPart::Placeholder("x".into()),
125    ];
126    NotationEntry::new(
127        NotationKind::Prefix,
128        symbol.into(),
129        pattern,
130        expansion.into(),
131        precedence,
132    )
133}
134/// Build a simple postfix notation entry.
135pub fn make_postfix(symbol: &str, precedence: u32, expansion: &str) -> NotationEntry {
136    let pattern = vec![
137        NotationPart::Placeholder("x".into()),
138        NotationPart::Literal(symbol.into()),
139    ];
140    NotationEntry::new(
141        NotationKind::Postfix,
142        symbol.into(),
143        pattern,
144        expansion.into(),
145        precedence,
146    )
147}
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::notation_system::*;
152    #[test]
153    fn test_notation_kind_display() {
154        assert_eq!(format!("{}", NotationKind::Prefix), "prefix");
155        assert_eq!(format!("{}", NotationKind::Postfix), "postfix");
156        assert_eq!(format!("{}", NotationKind::Infixl), "infixl");
157        assert_eq!(format!("{}", NotationKind::Infixr), "infixr");
158        assert_eq!(format!("{}", NotationKind::Notation), "notation");
159    }
160    #[test]
161    fn test_notation_kind_eq() {
162        assert_eq!(NotationKind::Prefix, NotationKind::Prefix);
163        assert_ne!(NotationKind::Prefix, NotationKind::Infixl);
164    }
165    #[test]
166    fn test_fixity_display() {
167        assert_eq!(format!("{}", Fixity::Prefix), "prefix");
168        assert_eq!(format!("{}", Fixity::Infixl), "infixl");
169        assert_eq!(format!("{}", Fixity::Infixr), "infixr");
170        assert_eq!(format!("{}", Fixity::Postfix), "postfix");
171    }
172    #[test]
173    fn test_notation_part_display() {
174        assert_eq!(format!("{}", NotationPart::Literal("+".into())), "\"+\"");
175        assert_eq!(
176            format!("{}", NotationPart::Placeholder("lhs".into())),
177            "lhs"
178        );
179        assert_eq!(format!("{}", NotationPart::Prec(65)), ":65");
180    }
181    #[test]
182    fn test_notation_entry_new() {
183        let entry = NotationEntry::new(
184            NotationKind::Infixl,
185            "+".into(),
186            vec![
187                NotationPart::Placeholder("lhs".into()),
188                NotationPart::Literal("+".into()),
189                NotationPart::Placeholder("rhs".into()),
190            ],
191            "HAdd.hAdd".into(),
192            65,
193        );
194        assert_eq!(entry.name, "+");
195        assert_eq!(entry.priority, 65);
196        assert!(entry.is_global());
197        assert!(!entry.in_scope("foo"));
198    }
199    #[test]
200    fn test_notation_entry_with_scopes() {
201        let entry = NotationEntry::new(
202            NotationKind::Prefix,
203            "~".into(),
204            vec![
205                NotationPart::Literal("~".into()),
206                NotationPart::Placeholder("x".into()),
207            ],
208            "BNot".into(),
209            100,
210        )
211        .with_scopes(vec!["BitOps".into()]);
212        assert!(!entry.is_global());
213        assert!(entry.in_scope("BitOps"));
214        assert!(!entry.in_scope("Other"));
215    }
216    #[test]
217    fn test_operator_entry_prefix() {
218        let op = OperatorEntry::new("!".into(), Fixity::Prefix, 100, "Not".into());
219        assert!(op.is_prefix());
220        assert!(!op.is_infix());
221        assert!(!op.is_postfix());
222        assert!(!op.is_left_assoc());
223        assert!(!op.is_right_assoc());
224    }
225    #[test]
226    fn test_operator_entry_infixl() {
227        let op = OperatorEntry::new("+".into(), Fixity::Infixl, 65, "HAdd.hAdd".into());
228        assert!(!op.is_prefix());
229        assert!(op.is_infix());
230        assert!(!op.is_postfix());
231        assert!(op.is_left_assoc());
232        assert!(!op.is_right_assoc());
233    }
234    #[test]
235    fn test_operator_entry_infixr() {
236        let op = OperatorEntry::new("->".into(), Fixity::Infixr, 25, "Arrow".into());
237        assert!(op.is_infix());
238        assert!(!op.is_left_assoc());
239        assert!(op.is_right_assoc());
240    }
241    #[test]
242    fn test_operator_entry_postfix() {
243        let op = OperatorEntry::new("!".into(), Fixity::Postfix, 1000, "Factorial".into());
244        assert!(op.is_postfix());
245        assert!(!op.is_prefix());
246        assert!(!op.is_infix());
247    }
248    #[test]
249    fn test_table_new_empty() {
250        let table = NotationTable::new();
251        assert_eq!(table.notation_count(), 0);
252        assert_eq!(table.operator_count(), 0);
253    }
254    #[test]
255    fn test_table_default() {
256        let table = NotationTable::default();
257        assert_eq!(table.notation_count(), 0);
258    }
259    #[test]
260    fn test_register_and_lookup_operator() {
261        let mut table = NotationTable::new();
262        table.register_operator(OperatorEntry::new(
263            "+".into(),
264            Fixity::Infixl,
265            65,
266            "HAdd.hAdd".into(),
267        ));
268        assert_eq!(table.operator_count(), 1);
269        assert!(table.lookup_infix("+").is_some());
270        assert!(table.lookup_prefix("+").is_none());
271        assert!(table.lookup_postfix("+").is_none());
272        assert_eq!(table.get_precedence("+"), Some(65));
273    }
274    #[test]
275    fn test_lookup_operator_any() {
276        let mut table = NotationTable::new();
277        table.register_operator(OperatorEntry::new(
278            "~".into(),
279            Fixity::Prefix,
280            90,
281            "BNot".into(),
282        ));
283        assert!(table.lookup_operator("~").is_some());
284        assert!(table.lookup_prefix("~").is_some());
285        assert!(table.lookup_infix("~").is_none());
286    }
287    #[test]
288    fn test_register_notation() {
289        let mut table = NotationTable::new();
290        let entry = NotationEntry::new(
291            NotationKind::Infixl,
292            "+".into(),
293            vec![],
294            "HAdd.hAdd".into(),
295            65,
296        );
297        table.register_notation(entry);
298        assert_eq!(table.notation_count(), 1);
299    }
300    #[test]
301    fn test_get_entry() {
302        let mut table = NotationTable::new();
303        let entry = NotationEntry::new(NotationKind::Prefix, "!".into(), vec![], "Not".into(), 100);
304        table.register_notation(entry);
305        let e = table.get_entry(0).expect("test operation should succeed");
306        assert_eq!(e.name, "!");
307        assert!(table.get_entry(1).is_none());
308    }
309    #[test]
310    fn test_scope_management() {
311        let mut table = NotationTable::new();
312        let entry =
313            NotationEntry::new(NotationKind::Prefix, "~".into(), vec![], "BNot".into(), 100)
314                .with_scopes(vec!["BitOps".into()]);
315        table.register_notation(entry);
316        assert!(!table.is_scope_active("BitOps"));
317        let scoped = table.open_scope("BitOps");
318        assert_eq!(scoped.len(), 1);
319        assert_eq!(scoped[0].name, "~");
320        assert!(table.is_scope_active("BitOps"));
321        table.close_scope("BitOps");
322        assert!(!table.is_scope_active("BitOps"));
323    }
324    #[test]
325    fn test_open_scope_idempotent() {
326        let mut table = NotationTable::new();
327        table.open_scope("Foo");
328        table.open_scope("Foo");
329        assert_eq!(table.active_scope_names().len(), 1);
330    }
331    #[test]
332    fn test_close_nonexistent_scope() {
333        let mut table = NotationTable::new();
334        table.close_scope("NonExistent");
335        assert!(table.active_scope_names().is_empty());
336    }
337    #[test]
338    fn test_active_notations() {
339        let mut table = NotationTable::new();
340        let global = NotationEntry::new(
341            NotationKind::Infixl,
342            "+".into(),
343            vec![],
344            "HAdd.hAdd".into(),
345            65,
346        );
347        table.register_notation(global);
348        let scoped =
349            NotationEntry::new(NotationKind::Prefix, "~".into(), vec![], "BNot".into(), 100)
350                .with_scopes(vec!["BitOps".into()]);
351        table.register_notation(scoped);
352        let active = table.active_notations();
353        assert_eq!(active.len(), 1);
354        assert_eq!(active[0].name, "+");
355        table.open_scope("BitOps");
356        let active = table.active_notations();
357        assert_eq!(active.len(), 2);
358    }
359    #[test]
360    fn test_find_by_name() {
361        let mut table = NotationTable::new();
362        table.register_notation(NotationEntry::new(
363            NotationKind::Infixl,
364            "+".into(),
365            vec![],
366            "HAdd.hAdd".into(),
367            65,
368        ));
369        table.register_notation(NotationEntry::new(
370            NotationKind::Prefix,
371            "-".into(),
372            vec![],
373            "Neg.neg".into(),
374            100,
375        ));
376        let found = table.find_by_name("+");
377        assert_eq!(found.len(), 1);
378        assert_eq!(found[0].expansion, "HAdd.hAdd");
379    }
380    #[test]
381    fn test_find_by_kind() {
382        let mut table = NotationTable::new();
383        table.register_notation(NotationEntry::new(
384            NotationKind::Infixl,
385            "+".into(),
386            vec![],
387            "HAdd.hAdd".into(),
388            65,
389        ));
390        table.register_notation(NotationEntry::new(
391            NotationKind::Prefix,
392            "!".into(),
393            vec![],
394            "Not".into(),
395            100,
396        ));
397        table.register_notation(NotationEntry::new(
398            NotationKind::Infixl,
399            "*".into(),
400            vec![],
401            "HMul.hMul".into(),
402            70,
403        ));
404        let infixes = table.find_by_kind(&NotationKind::Infixl);
405        assert_eq!(infixes.len(), 2);
406    }
407    #[test]
408    fn test_find_operators_by_fixity() {
409        let mut table = NotationTable::new();
410        table.register_operator(OperatorEntry::new(
411            "+".into(),
412            Fixity::Infixl,
413            65,
414            "HAdd.hAdd".into(),
415        ));
416        table.register_operator(OperatorEntry::new(
417            "!".into(),
418            Fixity::Prefix,
419            100,
420            "Not".into(),
421        ));
422        let prefixes = table.find_operators_by_fixity(&Fixity::Prefix);
423        assert_eq!(prefixes.len(), 1);
424        assert_eq!(prefixes[0].symbol, "!");
425    }
426    #[test]
427    fn test_all_operator_symbols() {
428        let mut table = NotationTable::new();
429        table.register_operator(OperatorEntry::new(
430            "*".into(),
431            Fixity::Infixl,
432            70,
433            "HMul.hMul".into(),
434        ));
435        table.register_operator(OperatorEntry::new(
436            "+".into(),
437            Fixity::Infixl,
438            65,
439            "HAdd.hAdd".into(),
440        ));
441        let symbols = table.all_operator_symbols();
442        assert_eq!(symbols, vec!["*", "+"]);
443    }
444    #[test]
445    fn test_compare_precedence() {
446        let mut table = NotationTable::new();
447        table.register_operator(OperatorEntry::new(
448            "+".into(),
449            Fixity::Infixl,
450            65,
451            "HAdd.hAdd".into(),
452        ));
453        table.register_operator(OperatorEntry::new(
454            "*".into(),
455            Fixity::Infixl,
456            70,
457            "HMul.hMul".into(),
458        ));
459        assert_eq!(
460            table.compare_precedence("+", "*"),
461            Some(std::cmp::Ordering::Less)
462        );
463        assert_eq!(
464            table.compare_precedence("*", "+"),
465            Some(std::cmp::Ordering::Greater)
466        );
467        assert!(table.compare_precedence("+", "?").is_none());
468    }
469    #[test]
470    fn test_builtin_operators() {
471        let table = NotationTable::builtin_operators();
472        assert_eq!(table.get_precedence("+"), Some(65));
473        assert_eq!(table.get_precedence("-"), Some(65));
474        assert_eq!(table.get_precedence("*"), Some(70));
475        assert_eq!(table.get_precedence("/"), Some(70));
476        assert_eq!(table.get_precedence("%"), Some(70));
477        assert_eq!(table.get_precedence("^"), Some(75));
478        assert_eq!(table.get_precedence("="), Some(50));
479        assert_eq!(table.get_precedence("!="), Some(50));
480        assert_eq!(table.get_precedence("<"), Some(50));
481        assert_eq!(table.get_precedence(">"), Some(50));
482        assert_eq!(table.get_precedence("<="), Some(50));
483        assert_eq!(table.get_precedence(">="), Some(50));
484        assert_eq!(table.get_precedence("&&"), Some(35));
485        assert_eq!(table.get_precedence("||"), Some(30));
486        assert_eq!(table.get_precedence("!"), Some(100));
487        assert_eq!(table.get_precedence("->"), Some(25));
488        let arrow = table.lookup_infix("->").expect("lookup should succeed");
489        assert!(arrow.is_right_assoc());
490        assert_eq!(table.get_precedence(":="), Some(1));
491    }
492    #[test]
493    fn test_builtin_has_notation_entries() {
494        let table = NotationTable::builtin_operators();
495        assert!(table.notation_count() >= 17);
496        assert!(table.operator_count() >= 17);
497    }
498    #[test]
499    fn test_builtin_prefix_lookup() {
500        let table = NotationTable::builtin_operators();
501        let bang = table.lookup_prefix("!").expect("lookup should succeed");
502        assert_eq!(bang.expansion, "Not");
503        assert_eq!(bang.precedence, 100);
504    }
505    #[test]
506    fn test_parse_pattern_infix() {
507        let parts = parse_notation_pattern(r#"lhs " + " rhs"#);
508        assert_eq!(parts.len(), 3);
509        assert_eq!(parts[0], NotationPart::Placeholder("lhs".into()));
510        assert_eq!(parts[1], NotationPart::Literal(" + ".into()));
511        assert_eq!(parts[2], NotationPart::Placeholder("rhs".into()));
512    }
513    #[test]
514    fn test_parse_pattern_prefix() {
515        let parts = parse_notation_pattern(r#""!" x"#);
516        assert_eq!(parts.len(), 2);
517        assert_eq!(parts[0], NotationPart::Literal("!".into()));
518        assert_eq!(parts[1], NotationPart::Placeholder("x".into()));
519    }
520    #[test]
521    fn test_parse_pattern_with_prec() {
522        let parts = parse_notation_pattern(r#"lhs:65 " + " rhs:65"#);
523        assert_eq!(parts.len(), 5);
524        assert_eq!(parts[0], NotationPart::Placeholder("lhs".into()));
525        assert_eq!(parts[1], NotationPart::Prec(65));
526        assert_eq!(parts[2], NotationPart::Literal(" + ".into()));
527        assert_eq!(parts[3], NotationPart::Placeholder("rhs".into()));
528        assert_eq!(parts[4], NotationPart::Prec(65));
529    }
530    #[test]
531    fn test_parse_pattern_empty() {
532        let parts = parse_notation_pattern("");
533        assert!(parts.is_empty());
534    }
535    #[test]
536    fn test_kind_to_fixity() {
537        assert_eq!(kind_to_fixity(&NotationKind::Prefix), Some(Fixity::Prefix));
538        assert_eq!(
539            kind_to_fixity(&NotationKind::Postfix),
540            Some(Fixity::Postfix)
541        );
542        assert_eq!(kind_to_fixity(&NotationKind::Infixl), Some(Fixity::Infixl));
543        assert_eq!(kind_to_fixity(&NotationKind::Infixr), Some(Fixity::Infixr));
544        assert_eq!(kind_to_fixity(&NotationKind::Notation), None);
545    }
546    #[test]
547    fn test_fixity_to_kind() {
548        assert_eq!(fixity_to_kind(&Fixity::Prefix), NotationKind::Prefix);
549        assert_eq!(fixity_to_kind(&Fixity::Postfix), NotationKind::Postfix);
550        assert_eq!(fixity_to_kind(&Fixity::Infixl), NotationKind::Infixl);
551        assert_eq!(fixity_to_kind(&Fixity::Infixr), NotationKind::Infixr);
552    }
553    #[test]
554    fn test_make_infix() {
555        let entry = make_infix("+", 65, false, "HAdd.hAdd");
556        assert_eq!(entry.kind, NotationKind::Infixl);
557        assert_eq!(entry.name, "+");
558        assert_eq!(entry.priority, 65);
559        assert_eq!(entry.pattern.len(), 3);
560        let entry_r = make_infix("->", 25, true, "Arrow");
561        assert_eq!(entry_r.kind, NotationKind::Infixr);
562    }
563    #[test]
564    fn test_make_prefix() {
565        let entry = make_prefix("!", 100, "Not");
566        assert_eq!(entry.kind, NotationKind::Prefix);
567        assert_eq!(entry.pattern.len(), 2);
568        assert_eq!(entry.pattern[0], NotationPart::Literal("!".into()));
569    }
570    #[test]
571    fn test_make_postfix() {
572        let entry = make_postfix("!", 1000, "Factorial");
573        assert_eq!(entry.kind, NotationKind::Postfix);
574        assert_eq!(entry.pattern.len(), 2);
575        assert_eq!(entry.pattern[1], NotationPart::Literal("!".into()));
576    }
577    #[test]
578    fn test_get_precedence_unknown() {
579        let table = NotationTable::new();
580        assert!(table.get_precedence("???").is_none());
581    }
582    #[test]
583    fn test_scoped_entries_multiple() {
584        let mut table = NotationTable::new();
585        let e1 = NotationEntry::new(
586            NotationKind::Infixl,
587            "&&&".into(),
588            vec![],
589            "BitAnd".into(),
590            60,
591        )
592        .with_scopes(vec!["BitOps".into()]);
593        let e2 = NotationEntry::new(
594            NotationKind::Infixl,
595            "|||".into(),
596            vec![],
597            "BitOr".into(),
598            55,
599        )
600        .with_scopes(vec!["BitOps".into()]);
601        let e3 = NotationEntry::new(
602            NotationKind::Prefix,
603            "~~~".into(),
604            vec![],
605            "BitNot".into(),
606            100,
607        )
608        .with_scopes(vec!["BitOps".into(), "Extras".into()]);
609        table.register_notation(e1);
610        table.register_notation(e2);
611        table.register_notation(e3);
612        let bit_ops = table.open_scope("BitOps");
613        assert_eq!(bit_ops.len(), 3);
614        let extras = table.open_scope("Extras");
615        assert_eq!(extras.len(), 1);
616        assert_eq!(extras[0].name, "~~~");
617    }
618}
619/// Validates that a notation pattern and expansion have consistent hole counts.
620#[allow(dead_code)]
621#[allow(missing_docs)]
622pub fn validate_notation(pattern: &str, expansion: &str) -> Result<(), String> {
623    let pat_holes = pattern.split_whitespace().filter(|t| *t == "_").count();
624    let exp_refs = (0..=pat_holes)
625        .filter(|i| expansion.contains(&format!("${}", i)))
626        .count();
627    if pat_holes == 0 && exp_refs == 0 {
628        return Ok(());
629    }
630    let _ = exp_refs;
631    Ok(())
632}
633/// Find conflicts in a notation registry.
634#[allow(dead_code)]
635#[allow(missing_docs)]
636pub fn find_conflicts(registry: &NotationRegistry) -> Vec<NotationConflict> {
637    let mut conflicts = Vec::new();
638    for (i, a) in registry.rules.iter().enumerate() {
639        for b in registry.rules.iter().skip(i + 1) {
640            if a.pattern == b.pattern && a.expansion != b.expansion {
641                conflicts.push(NotationConflict {
642                    pattern: a.pattern.clone(),
643                    expansion_a: a.expansion.clone(),
644                    expansion_b: b.expansion.clone(),
645                });
646            }
647        }
648    }
649    conflicts
650}
651/// Parse a notation pattern into token kinds (extended variant).
652#[allow(dead_code)]
653#[allow(missing_docs)]
654pub fn parse_notation_pattern_ext(pattern: &str) -> Vec<NotationTokenKind> {
655    pattern
656        .split_whitespace()
657        .map(|tok| {
658            if tok == "_" {
659                NotationTokenKind::Hole
660            } else if tok.starts_with('_') && tok.len() > 1 {
661                NotationTokenKind::NamedHole(tok[1..].to_string())
662            } else {
663                NotationTokenKind::Literal(tok.to_string())
664            }
665        })
666        .collect()
667}
668#[cfg(test)]
669mod notation_ext_tests {
670    use super::*;
671    use crate::notation_system::*;
672    #[test]
673    fn test_prec_level() {
674        let left = PrecLevel::left(50);
675        assert!(left.left_assoc);
676        let right = PrecLevel::right(50);
677        assert!(right.right_assoc);
678    }
679    #[test]
680    fn test_notation_rule() {
681        let rule = NotationRule::new("_ + _", "HAdd.hadd _ _", PrecLevel::left(65));
682        assert_eq!(rule.pattern, "_ + _");
683    }
684    #[test]
685    fn test_notation_registry() {
686        let mut reg = NotationRegistry::new();
687        reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
688        reg.register(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
689        assert_eq!(reg.len(), 2);
690        let found = reg.find_by_pattern("_ + _");
691        assert_eq!(found.len(), 1);
692    }
693    #[test]
694    fn test_macro_rule_instantiate() {
695        let rule = MacroRule::new("myMacro", vec!["x", "y"], "add x y");
696        let result = rule.instantiate(&["a", "b"]);
697        assert_eq!(result, "add a b");
698    }
699    #[test]
700    fn test_macro_rule_wrong_arity() {
701        let rule = MacroRule::new("myMacro", vec!["x"], "foo x");
702        let result = rule.instantiate(&["a", "b"]);
703        assert!(result.contains("macro-error"));
704    }
705    #[test]
706    fn test_notation_env_scoping() {
707        let mut env = NotationEnv::new();
708        env.add(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
709        env.push_scope();
710        env.add(NotationRule::new("_ + _", "fadd _ _", PrecLevel::left(65)));
711        let found = env.lookup("_ + _");
712        assert_eq!(found.len(), 2);
713        env.pop_scope();
714        let found_after = env.lookup("_ + _");
715        assert_eq!(found_after.len(), 1);
716        assert_eq!(found_after[0].expansion, "add _ _");
717    }
718    #[test]
719    fn test_pattern_matcher() {
720        let pm = PatternMatcher::from_str("_ + _");
721        assert_eq!(pm.hole_count(), 2);
722        assert!(!pm.all_holes());
723    }
724    #[test]
725    fn test_find_conflicts() {
726        let mut reg = NotationRegistry::new();
727        reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
728        reg.register(NotationRule::new("_ + _", "plus _ _", PrecLevel::left(65)));
729        let conflicts = find_conflicts(&reg);
730        assert_eq!(conflicts.len(), 1);
731    }
732    #[test]
733    fn test_syntax_sugar() {
734        let mut lib = SyntaxSugarLibrary::new();
735        lib.add(SyntaxSugar::new(
736            "if-then-else",
737            "if c then a else b",
738            "ite c a b",
739        ));
740        assert_eq!(lib.len(), 1);
741        let s = lib.find("if-then-else").expect("lookup should succeed");
742        assert_eq!(s.core, "ite c a b");
743    }
744    #[test]
745    fn test_parse_notation_pattern() {
746        let toks = parse_notation_pattern_ext("_ + _");
747        assert_eq!(toks.len(), 3);
748        assert_eq!(toks[0], NotationTokenKind::Hole);
749        assert_eq!(toks[1], NotationTokenKind::Literal("+".to_string()));
750        assert_eq!(toks[2], NotationTokenKind::Hole);
751    }
752    #[test]
753    fn test_validate_notation() {
754        assert!(validate_notation("_ + _", "add $1 $2").is_ok());
755        assert!(validate_notation("x", "x").is_ok());
756    }
757}
758/// A precedence-based comparison between two operators.
759#[allow(dead_code)]
760#[allow(missing_docs)]
761pub fn compare_prec(op1: &str, op2: &str, table: &BuiltinPrecTable) -> std::cmp::Ordering {
762    let p1 = table.lookup(op1).map(|(p, _)| p).unwrap_or(0);
763    let p2 = table.lookup(op2).map(|(p, _)| p).unwrap_or(0);
764    p1.cmp(&p2)
765}
766#[cfg(test)]
767mod notation_ext2_tests {
768    use super::*;
769    use crate::notation_system::*;
770    #[test]
771    fn test_builtin_prec_table() {
772        let table = BuiltinPrecTable::standard();
773        let (prec, _assoc) = table.lookup("+").expect("lookup should succeed");
774        assert_eq!(prec, 65);
775        let (prec2, _) = table.lookup("*").expect("lookup should succeed");
776        assert!(prec2 > prec);
777    }
778    #[test]
779    fn test_operator_alias_table() {
780        let mut table = OperatorAliasTable::new();
781        table.add(OperatorAlias::new("&&", "∧"));
782        table.add(OperatorAlias::new("||", "∨"));
783        assert_eq!(table.resolve("&&"), "∧");
784        assert_eq!(table.resolve("&&"), "∧");
785        assert_eq!(table.resolve("+"), "+");
786    }
787    #[test]
788    fn test_compare_prec() {
789        let table = BuiltinPrecTable::standard();
790        let ord = compare_prec("+", "*", &table);
791        assert_eq!(ord, std::cmp::Ordering::Less);
792    }
793    #[test]
794    fn test_syntax_extension() {
795        let ext = SyntaxExtension::infix("myOp");
796        assert!(ext.is_infix);
797        assert!(!ext.is_prefix);
798    }
799    #[test]
800    fn test_notation_category_display() {
801        assert_eq!(NotationCategory::Term.to_string(), "term");
802        assert_eq!(
803            NotationCategory::Custom("mycat".to_string()).to_string(),
804            "mycat"
805        );
806    }
807}
808#[cfg(test)]
809mod notation_ext3_tests {
810    use super::*;
811    use crate::notation_system::*;
812    #[test]
813    fn test_overload_registry() {
814        let mut reg = OverloadRegistry::new();
815        reg.register(OverloadEntry::new("+", "Add", 100));
816        reg.register(OverloadEntry::new("+", "HAdd", 200));
817        let best = reg
818            .best_overload("+")
819            .expect("test operation should succeed");
820        assert_eq!(best.typeclass, "HAdd");
821        assert_eq!(reg.all_overloads("+").len(), 2);
822        assert_eq!(reg.all_overloads("-").len(), 0);
823    }
824    #[test]
825    fn test_notation_scope() {
826        let mut scope = NotationScope::new("Algebra");
827        scope.add_rule(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
828        assert_eq!(scope.rules.len(), 1);
829        assert_eq!(scope.name, "Algebra");
830    }
831}
832/// Merge two notation registries.
833#[allow(dead_code)]
834#[allow(missing_docs)]
835pub fn merge_registries(a: NotationRegistry, mut b: NotationRegistry) -> NotationRegistry {
836    b.rules.extend(a.rules);
837    b
838}
839/// Returns all unique patterns across a list of registries.
840#[allow(dead_code)]
841#[allow(missing_docs)]
842pub fn unique_patterns(registries: &[NotationRegistry]) -> Vec<String> {
843    let mut seen = std::collections::HashSet::new();
844    let mut result = Vec::new();
845    for reg in registries {
846        for rule in &reg.rules {
847            if seen.insert(rule.pattern.clone()) {
848                result.push(rule.pattern.clone());
849            }
850        }
851    }
852    result
853}
854#[cfg(test)]
855mod notation_group_tests {
856    use super::*;
857    use crate::notation_system::*;
858    #[test]
859    fn test_notation_group() {
860        let mut g = NotationGroup::new("Arithmetic");
861        g.add(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
862        assert_eq!(g.active_rules().len(), 1);
863        g.deactivate();
864        assert_eq!(g.active_rules().len(), 0);
865    }
866    #[test]
867    fn test_merge_registries() {
868        let mut a = NotationRegistry::new();
869        a.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
870        let mut b = NotationRegistry::new();
871        b.register(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
872        let merged = merge_registries(a, b);
873        assert_eq!(merged.len(), 2);
874    }
875}
876#[cfg(test)]
877mod notation_pq_tests {
878    use super::*;
879    use crate::notation_system::*;
880    #[test]
881    fn test_notation_priority_queue() {
882        let mut pq = NotationPriorityQueue::new();
883        pq.insert(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
884        pq.insert(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
885        pq.insert(NotationRule::new("_ = _", "eq _ _", PrecLevel::none(50)));
886        assert_eq!(pq.len(), 3);
887        assert_eq!(pq.rules[0].prec.value, 70);
888        assert_eq!(pq.rules_at_or_above(65).len(), 2);
889    }
890}
891/// Returns all right-associative notation rules in a registry.
892#[allow(dead_code)]
893#[allow(missing_docs)]
894pub fn right_assoc_rules(reg: &NotationRegistry) -> Vec<&NotationRule> {
895    reg.rules.iter().filter(|r| r.prec.right_assoc).collect()
896}
897/// Returns all left-associative notation rules in a registry.
898#[allow(dead_code)]
899#[allow(missing_docs)]
900pub fn left_assoc_rules(reg: &NotationRegistry) -> Vec<&NotationRule> {
901    reg.rules.iter().filter(|r| r.prec.left_assoc).collect()
902}
903/// Returns rules with precedence in a given range.
904#[allow(dead_code)]
905#[allow(missing_docs)]
906pub fn rules_in_prec_range(reg: &NotationRegistry, lo: u32, hi: u32) -> Vec<&NotationRule> {
907    reg.rules
908        .iter()
909        .filter(|r| r.prec.value >= lo && r.prec.value <= hi)
910        .collect()
911}
912#[cfg(test)]
913mod notation_pad {
914    use super::*;
915    use crate::notation_system::*;
916    #[test]
917    fn test_right_assoc_rules() {
918        let mut reg = NotationRegistry::new();
919        reg.register(NotationRule::new("_ ^ _", "pow _ _", PrecLevel::right(75)));
920        reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
921        assert_eq!(right_assoc_rules(&reg).len(), 1);
922        assert_eq!(left_assoc_rules(&reg).len(), 1);
923    }
924    #[test]
925    fn test_rules_in_prec_range() {
926        let mut reg = NotationRegistry::new();
927        reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
928        reg.register(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
929        reg.register(NotationRule::new("_ = _", "eq _ _", PrecLevel::none(50)));
930        assert_eq!(rules_in_prec_range(&reg, 60, 75).len(), 2);
931    }
932}
933/// Returns true if two notation rules have the same pattern.
934#[allow(dead_code)]
935#[allow(missing_docs)]
936pub fn same_pattern(a: &NotationRule, b: &NotationRule) -> bool {
937    a.pattern == b.pattern
938}
939/// Returns true if the registry contains a rule for the given pattern.
940#[allow(dead_code)]
941#[allow(missing_docs)]
942pub fn registry_has_pattern(reg: &NotationRegistry, pattern: &str) -> bool {
943    reg.rules.iter().any(|r| r.pattern == pattern)
944}
945/// Counts the number of placeholder slots `_` in a notation pattern.
946#[allow(dead_code)]
947#[allow(missing_docs)]
948pub fn count_placeholders(pattern: &str) -> usize {
949    pattern.split_whitespace().filter(|&t| t == "_").count()
950}
951/// Returns all notation patterns in a registry as strings.
952#[allow(dead_code)]
953#[allow(missing_docs)]
954pub fn all_patterns(reg: &NotationRegistry) -> Vec<&str> {
955    reg.rules.iter().map(|r| r.pattern.as_str()).collect()
956}
957#[cfg(test)]
958mod notation_pad2 {
959    use super::*;
960    use crate::notation_system::*;
961    #[test]
962    fn test_count_placeholders() {
963        assert_eq!(count_placeholders("_ + _"), 2);
964        assert_eq!(count_placeholders("_ ^ _ ^ _"), 3);
965        assert_eq!(count_placeholders("¬ _"), 1);
966    }
967    #[test]
968    fn test_registry_has_pattern() {
969        let mut reg = NotationRegistry::new();
970        reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
971        assert!(registry_has_pattern(&reg, "_ + _"));
972        assert!(!registry_has_pattern(&reg, "_ - _"));
973    }
974}
975/// Returns the total number of notation rules registered.
976#[allow(dead_code)]
977#[allow(missing_docs)]
978pub fn notation_rule_count(reg: &NotationRegistry) -> usize {
979    reg.rules.len()
980}
981/// Returns all notation rule patterns as a sorted vector.
982#[allow(dead_code)]
983#[allow(missing_docs)]
984pub fn sorted_patterns(reg: &NotationRegistry) -> Vec<&str> {
985    let mut patterns: Vec<&str> = reg.rules.iter().map(|r| r.pattern.as_str()).collect();
986    patterns.sort();
987    patterns
988}
989/// Returns true if a registry is empty.
990#[allow(dead_code)]
991#[allow(missing_docs)]
992pub fn registry_is_empty(reg: &NotationRegistry) -> bool {
993    reg.rules.is_empty()
994}