Skip to main content

oxilean_parse/notation_system/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6
7/// A registered notation declaration.
8///
9/// This captures the full information from a `notation`, `prefix`, `infix`,
10/// etc. declaration.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct NotationEntry {
13    /// The kind of notation (prefix, infix, mixfix, etc.)
14    pub kind: NotationKind,
15    /// Name of the notation (often the operator symbol)
16    pub name: String,
17    /// The pattern parts describing the syntax
18    pub pattern: Vec<NotationPart>,
19    /// The expansion string (the right-hand side of `=>`)
20    pub expansion: String,
21    /// Priority / precedence level
22    pub priority: u32,
23    /// Scopes in which this notation is active
24    pub scopes: Vec<String>,
25}
26impl NotationEntry {
27    /// Create a new notation entry.
28    pub fn new(
29        kind: NotationKind,
30        name: String,
31        pattern: Vec<NotationPart>,
32        expansion: String,
33        priority: u32,
34    ) -> Self {
35        Self {
36            kind,
37            name,
38            pattern,
39            expansion,
40            priority,
41            scopes: Vec::new(),
42        }
43    }
44    /// Create a new notation entry with scopes.
45    pub fn with_scopes(mut self, scopes: Vec<String>) -> Self {
46        self.scopes = scopes;
47        self
48    }
49    /// Check whether this entry belongs to a given scope.
50    pub fn in_scope(&self, scope: &str) -> bool {
51        self.scopes.iter().any(|s| s == scope)
52    }
53    /// Check whether this entry is unscoped (global).
54    pub fn is_global(&self) -> bool {
55        self.scopes.is_empty()
56    }
57}
58/// A scope guard that automatically pops when dropped.
59#[allow(dead_code)]
60#[allow(missing_docs)]
61pub struct ScopeGuard<'a> {
62    pub(super) env: &'a mut NotationEnv,
63}
64impl<'a> ScopeGuard<'a> {
65    /// Create a new scope guard, pushing a new scope.
66    #[allow(dead_code)]
67    pub fn new(env: &'a mut NotationEnv) -> Self {
68        env.push_scope();
69        ScopeGuard { env }
70    }
71}
72/// An operator alias entry.
73#[allow(dead_code)]
74#[allow(missing_docs)]
75#[derive(Debug, Clone)]
76pub struct OperatorAlias {
77    /// The alias (surface notation)
78    pub alias: String,
79    /// The canonical form
80    pub canonical: String,
81}
82impl OperatorAlias {
83    /// Create a new alias.
84    #[allow(dead_code)]
85    pub fn new(alias: &str, canonical: &str) -> Self {
86        OperatorAlias {
87            alias: alias.to_string(),
88            canonical: canonical.to_string(),
89        }
90    }
91}
92/// A notation category (e.g. term, tactic, command).
93#[allow(dead_code)]
94#[allow(missing_docs)]
95#[derive(Debug, Clone, PartialEq, Eq, Hash)]
96pub enum NotationCategory {
97    /// Term-level notation
98    Term,
99    /// Tactic-level notation
100    Tactic,
101    /// Command-level notation
102    Command,
103    /// Custom category
104    Custom(String),
105}
106/// A registered operator (simpler than a full notation entry).
107///
108/// Operators are the common case: a single symbol with a fixity and precedence
109/// that expands to a function application.
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct OperatorEntry {
112    /// The operator symbol (e.g. `+`, `*`, `->`)
113    pub symbol: String,
114    /// The fixity of the operator
115    pub fixity: Fixity,
116    /// Precedence level
117    pub precedence: u32,
118    /// The expansion (fully qualified function name)
119    pub expansion: String,
120}
121impl OperatorEntry {
122    /// Create a new operator entry.
123    pub fn new(symbol: String, fixity: Fixity, precedence: u32, expansion: String) -> Self {
124        Self {
125            symbol,
126            fixity,
127            precedence,
128            expansion,
129        }
130    }
131    /// Check whether this operator is a prefix operator.
132    pub fn is_prefix(&self) -> bool {
133        self.fixity == Fixity::Prefix
134    }
135    /// Check whether this operator is an infix operator (left or right).
136    pub fn is_infix(&self) -> bool {
137        matches!(self.fixity, Fixity::Infixl | Fixity::Infixr)
138    }
139    /// Check whether this operator is a postfix operator.
140    pub fn is_postfix(&self) -> bool {
141        self.fixity == Fixity::Postfix
142    }
143    /// Check whether this operator is left-associative.
144    pub fn is_left_assoc(&self) -> bool {
145        self.fixity == Fixity::Infixl
146    }
147    /// Check whether this operator is right-associative.
148    pub fn is_right_assoc(&self) -> bool {
149        self.fixity == Fixity::Infixr
150    }
151}
152/// A registry of operator overloads.
153#[allow(dead_code)]
154#[allow(missing_docs)]
155pub struct OverloadRegistry {
156    /// All overload entries
157    pub entries: Vec<OverloadEntry>,
158}
159impl OverloadRegistry {
160    /// Create a new empty registry.
161    #[allow(dead_code)]
162    pub fn new() -> Self {
163        OverloadRegistry {
164            entries: Vec::new(),
165        }
166    }
167    /// Register an overload.
168    #[allow(dead_code)]
169    pub fn register(&mut self, entry: OverloadEntry) {
170        self.entries.push(entry);
171    }
172    /// Find the highest-priority overload for a symbol.
173    #[allow(dead_code)]
174    pub fn best_overload(&self, symbol: &str) -> Option<&OverloadEntry> {
175        self.entries
176            .iter()
177            .filter(|e| e.symbol == symbol)
178            .max_by_key(|e| e.priority)
179    }
180    /// Returns all overloads for a symbol.
181    #[allow(dead_code)]
182    pub fn all_overloads(&self, symbol: &str) -> Vec<&OverloadEntry> {
183        self.entries.iter().filter(|e| e.symbol == symbol).collect()
184    }
185}
186/// The main notation/operator table.
187///
188/// Contains all registered notations and operators, supports scoped activation,
189/// and provides fast lookup by symbol.
190#[derive(Debug, Clone)]
191pub struct NotationTable {
192    /// All registered notation entries.
193    entries: Vec<NotationEntry>,
194    /// Fast lookup by operator symbol.
195    operators: HashMap<String, OperatorEntry>,
196    /// Scope name -> indices into `entries` that belong to that scope.
197    scoped_entries: HashMap<String, Vec<usize>>,
198    /// Currently active (opened) scopes.
199    active_scopes: Vec<String>,
200}
201impl NotationTable {
202    /// Create an empty notation table.
203    pub fn new() -> Self {
204        Self {
205            entries: Vec::new(),
206            operators: HashMap::new(),
207            scoped_entries: HashMap::new(),
208            active_scopes: Vec::new(),
209        }
210    }
211    /// Register a notation entry.
212    pub fn register_notation(&mut self, entry: NotationEntry) {
213        let idx = self.entries.len();
214        for scope in &entry.scopes {
215            self.scoped_entries
216                .entry(scope.clone())
217                .or_default()
218                .push(idx);
219        }
220        self.entries.push(entry);
221    }
222    /// Register an operator entry.
223    pub fn register_operator(&mut self, entry: OperatorEntry) {
224        self.operators.insert(entry.symbol.clone(), entry);
225    }
226    /// Look up a prefix operator by symbol.
227    pub fn lookup_prefix(&self, symbol: &str) -> Option<&OperatorEntry> {
228        self.operators
229            .get(symbol)
230            .filter(|op| op.fixity == Fixity::Prefix)
231    }
232    /// Look up an infix operator by symbol (either left- or right-associative).
233    pub fn lookup_infix(&self, symbol: &str) -> Option<&OperatorEntry> {
234        self.operators
235            .get(symbol)
236            .filter(|op| matches!(op.fixity, Fixity::Infixl | Fixity::Infixr))
237    }
238    /// Look up a postfix operator by symbol.
239    pub fn lookup_postfix(&self, symbol: &str) -> Option<&OperatorEntry> {
240        self.operators
241            .get(symbol)
242            .filter(|op| op.fixity == Fixity::Postfix)
243    }
244    /// Look up any operator by symbol regardless of fixity.
245    pub fn lookup_operator(&self, symbol: &str) -> Option<&OperatorEntry> {
246        self.operators.get(symbol)
247    }
248    /// Get the precedence of an operator by symbol.
249    pub fn get_precedence(&self, symbol: &str) -> Option<u32> {
250        self.operators.get(symbol).map(|op| op.precedence)
251    }
252    /// Get a notation entry by index.
253    pub fn get_entry(&self, index: usize) -> Option<&NotationEntry> {
254        self.entries.get(index)
255    }
256    /// Get the number of registered notation entries.
257    pub fn notation_count(&self) -> usize {
258        self.entries.len()
259    }
260    /// Get the number of registered operators.
261    pub fn operator_count(&self) -> usize {
262        self.operators.len()
263    }
264    /// Open a scope, making its notations active.
265    ///
266    /// Returns the notation entries that belong to that scope.
267    pub fn open_scope(&mut self, scope: &str) -> Vec<&NotationEntry> {
268        if !self.active_scopes.contains(&scope.to_string()) {
269            self.active_scopes.push(scope.to_string());
270        }
271        self.scoped_entries
272            .get(scope)
273            .map(|indices| {
274                indices
275                    .iter()
276                    .filter_map(|&idx| self.entries.get(idx))
277                    .collect()
278            })
279            .unwrap_or_default()
280    }
281    /// Close a scope, deactivating its notations.
282    pub fn close_scope(&mut self, scope: &str) {
283        self.active_scopes.retain(|s| s != scope);
284    }
285    /// Check whether a scope is currently active.
286    pub fn is_scope_active(&self, scope: &str) -> bool {
287        self.active_scopes.contains(&scope.to_string())
288    }
289    /// Get all currently active scopes.
290    pub fn active_scope_names(&self) -> &[String] {
291        &self.active_scopes
292    }
293    /// Get all currently active notations (global + active scopes).
294    pub fn active_notations(&self) -> Vec<&NotationEntry> {
295        self.entries
296            .iter()
297            .filter(|entry| {
298                entry.is_global() || entry.scopes.iter().any(|s| self.active_scopes.contains(s))
299            })
300            .collect()
301    }
302    /// Find all notation entries matching a given name.
303    pub fn find_by_name(&self, name: &str) -> Vec<&NotationEntry> {
304        self.entries.iter().filter(|e| e.name == name).collect()
305    }
306    /// Find all notation entries of a given kind.
307    pub fn find_by_kind(&self, kind: &NotationKind) -> Vec<&NotationEntry> {
308        self.entries.iter().filter(|e| &e.kind == kind).collect()
309    }
310    /// Find all operators with a given fixity.
311    pub fn find_operators_by_fixity(&self, fixity: &Fixity) -> Vec<&OperatorEntry> {
312        self.operators
313            .values()
314            .filter(|op| &op.fixity == fixity)
315            .collect()
316    }
317    /// Get all operator symbols, sorted alphabetically.
318    pub fn all_operator_symbols(&self) -> Vec<&str> {
319        let mut symbols: Vec<&str> = self.operators.keys().map(|s| s.as_str()).collect();
320        symbols.sort();
321        symbols
322    }
323    /// Compare the precedence of two symbols.
324    ///
325    /// Returns `Some(Ordering)` if both symbols are registered, `None` otherwise.
326    pub fn compare_precedence(&self, a: &str, b: &str) -> Option<std::cmp::Ordering> {
327        let pa = self.get_precedence(a)?;
328        let pb = self.get_precedence(b)?;
329        Some(pa.cmp(&pb))
330    }
331    /// Create a notation table pre-populated with standard Lean 4 operators.
332    ///
333    /// Includes arithmetic, comparison, logical, arrow, and assignment operators
334    /// with their standard precedences.
335    pub fn builtin_operators() -> Self {
336        let mut table = Self::new();
337        table.register_operator(OperatorEntry::new(
338            "+".to_string(),
339            Fixity::Infixl,
340            65,
341            "HAdd.hAdd".to_string(),
342        ));
343        table.register_operator(OperatorEntry::new(
344            "-".to_string(),
345            Fixity::Infixl,
346            65,
347            "HSub.hSub".to_string(),
348        ));
349        table.register_operator(OperatorEntry::new(
350            "*".to_string(),
351            Fixity::Infixl,
352            70,
353            "HMul.hMul".to_string(),
354        ));
355        table.register_operator(OperatorEntry::new(
356            "/".to_string(),
357            Fixity::Infixl,
358            70,
359            "HDiv.hDiv".to_string(),
360        ));
361        table.register_operator(OperatorEntry::new(
362            "%".to_string(),
363            Fixity::Infixl,
364            70,
365            "HMod.hMod".to_string(),
366        ));
367        table.register_operator(OperatorEntry::new(
368            "^".to_string(),
369            Fixity::Infixr,
370            75,
371            "HPow.hPow".to_string(),
372        ));
373        table.register_operator(OperatorEntry::new(
374            "=".to_string(),
375            Fixity::Infixl,
376            50,
377            "Eq".to_string(),
378        ));
379        table.register_operator(OperatorEntry::new(
380            "!=".to_string(),
381            Fixity::Infixl,
382            50,
383            "Ne".to_string(),
384        ));
385        table.register_operator(OperatorEntry::new(
386            "<".to_string(),
387            Fixity::Infixl,
388            50,
389            "LT.lt".to_string(),
390        ));
391        table.register_operator(OperatorEntry::new(
392            ">".to_string(),
393            Fixity::Infixl,
394            50,
395            "GT.gt".to_string(),
396        ));
397        table.register_operator(OperatorEntry::new(
398            "<=".to_string(),
399            Fixity::Infixl,
400            50,
401            "LE.le".to_string(),
402        ));
403        table.register_operator(OperatorEntry::new(
404            ">=".to_string(),
405            Fixity::Infixl,
406            50,
407            "GE.ge".to_string(),
408        ));
409        table.register_operator(OperatorEntry::new(
410            "&&".to_string(),
411            Fixity::Infixl,
412            35,
413            "And".to_string(),
414        ));
415        table.register_operator(OperatorEntry::new(
416            "||".to_string(),
417            Fixity::Infixl,
418            30,
419            "Or".to_string(),
420        ));
421        table.register_operator(OperatorEntry::new(
422            "!".to_string(),
423            Fixity::Prefix,
424            100,
425            "Not".to_string(),
426        ));
427        table.register_operator(OperatorEntry::new(
428            "->".to_string(),
429            Fixity::Infixr,
430            25,
431            "Arrow".to_string(),
432        ));
433        table.register_operator(OperatorEntry::new(
434            ":=".to_string(),
435            Fixity::Infixl,
436            1,
437            "Assign".to_string(),
438        ));
439        let ops: Vec<(String, NotationKind, u32, String)> = vec![
440            ("+".into(), NotationKind::Infixl, 65, "HAdd.hAdd".into()),
441            ("-".into(), NotationKind::Infixl, 65, "HSub.hSub".into()),
442            ("*".into(), NotationKind::Infixl, 70, "HMul.hMul".into()),
443            ("/".into(), NotationKind::Infixl, 70, "HDiv.hDiv".into()),
444            ("%".into(), NotationKind::Infixl, 70, "HMod.hMod".into()),
445            ("^".into(), NotationKind::Infixr, 75, "HPow.hPow".into()),
446            ("=".into(), NotationKind::Infixl, 50, "Eq".into()),
447            ("!=".into(), NotationKind::Infixl, 50, "Ne".into()),
448            ("<".into(), NotationKind::Infixl, 50, "LT.lt".into()),
449            (">".into(), NotationKind::Infixl, 50, "GT.gt".into()),
450            ("<=".into(), NotationKind::Infixl, 50, "LE.le".into()),
451            (">=".into(), NotationKind::Infixl, 50, "GE.ge".into()),
452            ("&&".into(), NotationKind::Infixl, 35, "And".into()),
453            ("||".into(), NotationKind::Infixl, 30, "Or".into()),
454            ("!".into(), NotationKind::Prefix, 100, "Not".into()),
455            ("->".into(), NotationKind::Infixr, 25, "Arrow".into()),
456            (":=".into(), NotationKind::Infixl, 1, "Assign".into()),
457        ];
458        for (sym, kind, prec, expansion) in ops {
459            let pattern = match &kind {
460                NotationKind::Prefix => {
461                    vec![
462                        NotationPart::Literal(sym.clone()),
463                        NotationPart::Placeholder("x".into()),
464                    ]
465                }
466                NotationKind::Postfix => {
467                    vec![
468                        NotationPart::Placeholder("x".into()),
469                        NotationPart::Literal(sym.clone()),
470                    ]
471                }
472                _ => {
473                    vec![
474                        NotationPart::Placeholder("lhs".into()),
475                        NotationPart::Literal(sym.clone()),
476                        NotationPart::Placeholder("rhs".into()),
477                    ]
478                }
479            };
480            table.register_notation(NotationEntry::new(kind, sym, pattern, expansion, prec));
481        }
482        table
483    }
484}
485/// A simple notation formatter that expands `_ op _` patterns.
486#[allow(dead_code)]
487#[allow(missing_docs)]
488pub struct NotationFormatter {
489    /// Registry to use for expansion
490    pub registry: NotationRegistry,
491}
492impl NotationFormatter {
493    /// Create a new formatter.
494    #[allow(dead_code)]
495    pub fn new(reg: NotationRegistry) -> Self {
496        NotationFormatter { registry: reg }
497    }
498    /// Count total rules.
499    #[allow(dead_code)]
500    pub fn rule_count(&self) -> usize {
501        self.registry.rules.len()
502    }
503}
504/// A user-defined notation rule.
505#[allow(dead_code)]
506#[allow(missing_docs)]
507#[derive(Debug, Clone)]
508pub struct NotationRule {
509    /// The notation pattern (e.g. "_ + _")
510    pub pattern: String,
511    /// The expansion template
512    pub expansion: String,
513    /// The precedence level
514    pub prec: PrecLevel,
515    /// The namespace this rule belongs to
516    pub namespace: Option<String>,
517}
518impl NotationRule {
519    /// Create a new notation rule.
520    #[allow(dead_code)]
521    pub fn new(pattern: &str, expansion: &str, prec: PrecLevel) -> Self {
522        NotationRule {
523            pattern: pattern.to_string(),
524            expansion: expansion.to_string(),
525            prec,
526            namespace: None,
527        }
528    }
529    /// Set the namespace.
530    #[allow(dead_code)]
531    pub fn in_namespace(mut self, ns: &str) -> Self {
532        self.namespace = Some(ns.to_string());
533        self
534    }
535}
536/// A notation token kind for pattern matching.
537#[allow(dead_code)]
538#[allow(missing_docs)]
539#[derive(Debug, Clone, PartialEq, Eq)]
540pub enum NotationTokenKind {
541    /// A literal symbol to match exactly
542    Literal(String),
543    /// A hole that matches any expression
544    Hole,
545    /// A named hole
546    NamedHole(String),
547}
548/// An overloaded operator entry.
549#[allow(dead_code)]
550#[allow(missing_docs)]
551#[derive(Debug, Clone)]
552pub struct OverloadEntry {
553    /// The operator symbol
554    pub symbol: String,
555    /// The type class providing this operator
556    pub typeclass: String,
557    /// Priority (higher = preferred)
558    pub priority: u32,
559}
560impl OverloadEntry {
561    /// Create a new overload entry.
562    #[allow(dead_code)]
563    pub fn new(symbol: &str, typeclass: &str, priority: u32) -> Self {
564        OverloadEntry {
565            symbol: symbol.to_string(),
566            typeclass: typeclass.to_string(),
567            priority,
568        }
569    }
570}
571/// A simple notation registry that maps patterns to rules.
572#[allow(dead_code)]
573#[allow(missing_docs)]
574pub struct NotationRegistry {
575    /// All registered rules
576    pub rules: Vec<NotationRule>,
577}
578impl NotationRegistry {
579    /// Create an empty registry.
580    #[allow(dead_code)]
581    pub fn new() -> Self {
582        NotationRegistry { rules: Vec::new() }
583    }
584    /// Register a new rule.
585    #[allow(dead_code)]
586    pub fn register(&mut self, rule: NotationRule) {
587        self.rules.push(rule);
588    }
589    /// Find rules by pattern (exact match).
590    #[allow(dead_code)]
591    pub fn find_by_pattern(&self, pattern: &str) -> Vec<&NotationRule> {
592        self.rules.iter().filter(|r| r.pattern == pattern).collect()
593    }
594    /// Find rules in a given namespace.
595    #[allow(dead_code)]
596    pub fn find_by_namespace(&self, ns: &str) -> Vec<&NotationRule> {
597        self.rules
598            .iter()
599            .filter(|r| r.namespace.as_deref() == Some(ns))
600            .collect()
601    }
602    /// Returns the total number of rules.
603    #[allow(dead_code)]
604    pub fn len(&self) -> usize {
605        self.rules.len()
606    }
607    /// Returns true if the registry is empty.
608    #[allow(dead_code)]
609    pub fn is_empty(&self) -> bool {
610        self.rules.is_empty()
611    }
612}
613/// A precedence table for built-in operators.
614#[allow(dead_code)]
615#[allow(missing_docs)]
616pub struct BuiltinPrecTable {
617    /// Entries: (operator, precedence, left_assoc)
618    pub entries: Vec<(String, u32, bool)>,
619}
620impl BuiltinPrecTable {
621    /// Create the standard Lean-like precedence table.
622    #[allow(dead_code)]
623    pub fn standard() -> Self {
624        BuiltinPrecTable {
625            entries: vec![
626                ("=".to_string(), 50, false),
627                ("<".to_string(), 50, false),
628                (">".to_string(), 50, false),
629                ("<=".to_string(), 50, false),
630                (">=".to_string(), 50, false),
631                ("≠".to_string(), 50, false),
632                ("∧".to_string(), 35, true),
633                ("∨".to_string(), 30, true),
634                ("→".to_string(), 25, false),
635                ("↔".to_string(), 20, false),
636                ("+".to_string(), 65, true),
637                ("-".to_string(), 65, true),
638                ("*".to_string(), 70, true),
639                ("/".to_string(), 70, true),
640                ("%".to_string(), 70, true),
641                ("^".to_string(), 75, false),
642            ],
643        }
644    }
645    /// Look up the precedence of an operator.
646    #[allow(dead_code)]
647    pub fn lookup(&self, op: &str) -> Option<(u32, bool)> {
648        self.entries
649            .iter()
650            .find(|(o, _, _)| o == op)
651            .map(|(_, p, l)| (*p, *l))
652    }
653}
654/// A notation scope for open/close semantics.
655#[allow(dead_code)]
656#[allow(missing_docs)]
657pub struct NotationScope {
658    /// Name of this scope
659    pub name: String,
660    /// Rules added by opening this scope
661    pub rules: Vec<NotationRule>,
662}
663impl NotationScope {
664    /// Create a new notation scope.
665    #[allow(dead_code)]
666    pub fn new(name: &str) -> Self {
667        NotationScope {
668            name: name.to_string(),
669            rules: Vec::new(),
670        }
671    }
672    /// Add a rule to this scope.
673    #[allow(dead_code)]
674    pub fn add_rule(&mut self, rule: NotationRule) {
675        self.rules.push(rule);
676    }
677}
678/// A macro rule with parameters.
679#[allow(dead_code)]
680#[allow(missing_docs)]
681#[derive(Debug, Clone)]
682pub struct MacroRule {
683    /// The macro name
684    pub name: String,
685    /// Parameter names
686    pub params: Vec<String>,
687    /// The body template
688    pub body: String,
689}
690impl MacroRule {
691    /// Create a new macro rule.
692    #[allow(dead_code)]
693    pub fn new(name: &str, params: Vec<&str>, body: &str) -> Self {
694        MacroRule {
695            name: name.to_string(),
696            params: params.into_iter().map(|s| s.to_string()).collect(),
697            body: body.to_string(),
698        }
699    }
700    /// Instantiate the macro with given arguments (simple text substitution).
701    #[allow(dead_code)]
702    pub fn instantiate(&self, args: &[&str]) -> String {
703        if args.len() != self.params.len() {
704            return format!(
705                "(macro-error: {} expects {} args, got {})",
706                self.name,
707                self.params.len(),
708                args.len()
709            );
710        }
711        let mut result = self.body.clone();
712        for (param, arg) in self.params.iter().zip(args.iter()) {
713            result = result.replace(param.as_str(), arg);
714        }
715        result
716    }
717}
718/// A collection of syntax sugar definitions.
719#[allow(dead_code)]
720#[allow(missing_docs)]
721pub struct SyntaxSugarLibrary {
722    /// All registered sugars
723    pub sugars: Vec<SyntaxSugar>,
724}
725impl SyntaxSugarLibrary {
726    /// Create an empty library.
727    #[allow(dead_code)]
728    pub fn new() -> Self {
729        SyntaxSugarLibrary { sugars: Vec::new() }
730    }
731    /// Add a sugar.
732    #[allow(dead_code)]
733    pub fn add(&mut self, sugar: SyntaxSugar) {
734        self.sugars.push(sugar);
735    }
736    /// Find a sugar by name.
737    #[allow(dead_code)]
738    pub fn find(&self, name: &str) -> Option<&SyntaxSugar> {
739        self.sugars.iter().find(|s| s.name == name)
740    }
741    /// Returns the number of sugars.
742    #[allow(dead_code)]
743    pub fn len(&self) -> usize {
744        self.sugars.len()
745    }
746    /// Returns true if empty.
747    #[allow(dead_code)]
748    pub fn is_empty(&self) -> bool {
749        self.sugars.is_empty()
750    }
751}
752/// A syntax extension hook.
753#[allow(dead_code)]
754#[allow(missing_docs)]
755#[derive(Debug, Clone)]
756pub struct SyntaxExtension {
757    /// Name of the extension
758    pub name: String,
759    /// Whether this is an infix extension
760    pub is_infix: bool,
761    /// Whether this is a prefix extension
762    pub is_prefix: bool,
763    /// Whether this is a postfix extension
764    pub is_postfix: bool,
765}
766impl SyntaxExtension {
767    /// Create a new infix syntax extension.
768    #[allow(dead_code)]
769    pub fn infix(name: &str) -> Self {
770        SyntaxExtension {
771            name: name.to_string(),
772            is_infix: true,
773            is_prefix: false,
774            is_postfix: false,
775        }
776    }
777    /// Create a new prefix syntax extension.
778    #[allow(dead_code)]
779    pub fn prefix(name: &str) -> Self {
780        SyntaxExtension {
781            name: name.to_string(),
782            is_infix: false,
783            is_prefix: true,
784            is_postfix: false,
785        }
786    }
787}
788/// A collection of operator aliases.
789#[allow(dead_code)]
790#[allow(missing_docs)]
791pub struct OperatorAliasTable {
792    /// All entries
793    pub entries: Vec<OperatorAlias>,
794}
795impl OperatorAliasTable {
796    /// Create a new empty alias table.
797    #[allow(dead_code)]
798    pub fn new() -> Self {
799        OperatorAliasTable {
800            entries: Vec::new(),
801        }
802    }
803    /// Add an alias.
804    #[allow(dead_code)]
805    pub fn add(&mut self, alias: OperatorAlias) {
806        self.entries.push(alias);
807    }
808    /// Resolve an alias to its canonical form.
809    #[allow(dead_code)]
810    pub fn resolve(&self, op: &str) -> String {
811        self.entries
812            .iter()
813            .find(|e| e.alias == op)
814            .map(|e| e.canonical.clone())
815            .unwrap_or_else(|| op.to_string())
816    }
817    /// Returns the number of entries.
818    #[allow(dead_code)]
819    pub fn len(&self) -> usize {
820        self.entries.len()
821    }
822    /// Returns true if empty.
823    #[allow(dead_code)]
824    pub fn is_empty(&self) -> bool {
825        self.entries.is_empty()
826    }
827}
828/// A notation group that bundles related notation rules.
829#[allow(dead_code)]
830#[allow(missing_docs)]
831pub struct NotationGroup {
832    /// Group name
833    pub name: String,
834    /// Rules in this group
835    pub rules: Vec<NotationRule>,
836    /// Whether this group is currently active
837    pub active: bool,
838}
839impl NotationGroup {
840    /// Create a new active group.
841    #[allow(dead_code)]
842    pub fn new(name: &str) -> Self {
843        NotationGroup {
844            name: name.to_string(),
845            rules: Vec::new(),
846            active: true,
847        }
848    }
849    /// Add a rule.
850    #[allow(dead_code)]
851    pub fn add(&mut self, rule: NotationRule) {
852        self.rules.push(rule);
853    }
854    /// Deactivate this group.
855    #[allow(dead_code)]
856    pub fn deactivate(&mut self) {
857        self.active = false;
858    }
859    /// Returns active rules.
860    #[allow(dead_code)]
861    pub fn active_rules(&self) -> Vec<&NotationRule> {
862        if self.active {
863            self.rules.iter().collect()
864        } else {
865            Vec::new()
866        }
867    }
868}
869/// A priority queue for notation rules (by precedence).
870#[allow(dead_code)]
871#[allow(missing_docs)]
872pub struct NotationPriorityQueue {
873    /// Rules sorted by precedence (descending)
874    pub rules: Vec<NotationRule>,
875}
876impl NotationPriorityQueue {
877    /// Create a new empty queue.
878    #[allow(dead_code)]
879    pub fn new() -> Self {
880        NotationPriorityQueue { rules: Vec::new() }
881    }
882    /// Insert a rule (maintaining sort order).
883    #[allow(dead_code)]
884    pub fn insert(&mut self, rule: NotationRule) {
885        let pos = self
886            .rules
887            .partition_point(|r| r.prec.value >= rule.prec.value);
888        self.rules.insert(pos, rule);
889    }
890    /// Returns rules with at least the given precedence.
891    #[allow(dead_code)]
892    pub fn rules_at_or_above(&self, prec: u32) -> Vec<&NotationRule> {
893        self.rules.iter().filter(|r| r.prec.value >= prec).collect()
894    }
895    /// Returns the number of rules.
896    #[allow(dead_code)]
897    pub fn len(&self) -> usize {
898        self.rules.len()
899    }
900    /// Returns true if empty.
901    #[allow(dead_code)]
902    pub fn is_empty(&self) -> bool {
903        self.rules.is_empty()
904    }
905}
906/// The kind of a notation declaration.
907#[derive(Debug, Clone, PartialEq, Eq, Hash)]
908pub enum NotationKind {
909    /// Prefix operator (e.g. `!`, `-`)
910    Prefix,
911    /// Postfix operator (e.g. `!` factorial)
912    Postfix,
913    /// Left-associative infix operator (e.g. `+`, `-`)
914    Infixl,
915    /// Right-associative infix operator (e.g. `->`, `^`)
916    Infixr,
917    /// General mixfix notation (e.g. `if _ then _ else _`)
918    Notation,
919}
920/// An abstract syntax sugar definition.
921#[allow(dead_code)]
922#[allow(missing_docs)]
923#[derive(Debug, Clone)]
924pub struct SyntaxSugar {
925    /// Name of this syntactic sugar
926    pub name: String,
927    /// The surface form
928    pub surface: String,
929    /// The desugared core form
930    pub core: String,
931}
932impl SyntaxSugar {
933    /// Create a new syntax sugar definition.
934    #[allow(dead_code)]
935    pub fn new(name: &str, surface: &str, core: &str) -> Self {
936        SyntaxSugar {
937            name: name.to_string(),
938            surface: surface.to_string(),
939            core: core.to_string(),
940        }
941    }
942}
943/// A simple token pattern matcher for notation expansion.
944#[allow(dead_code)]
945#[allow(missing_docs)]
946#[derive(Debug, Clone)]
947pub struct PatternMatcher {
948    /// The pattern to match (tokens separated by spaces, `_` = hole)
949    pub pattern: Vec<String>,
950}
951impl PatternMatcher {
952    /// Create from a pattern string.
953    #[allow(dead_code)]
954    #[allow(clippy::should_implement_trait)]
955    pub fn from_str(s: &str) -> Self {
956        PatternMatcher {
957            pattern: s.split_whitespace().map(|t| t.to_string()).collect(),
958        }
959    }
960    /// Count holes (underscores) in the pattern.
961    #[allow(dead_code)]
962    pub fn hole_count(&self) -> usize {
963        self.pattern.iter().filter(|t| t.as_str() == "_").count()
964    }
965    /// Returns true if the pattern is entirely holes.
966    #[allow(dead_code)]
967    pub fn all_holes(&self) -> bool {
968        self.pattern.iter().all(|t| t.as_str() == "_")
969    }
970}
971/// Fixity of an operator entry.
972#[derive(Debug, Clone, PartialEq, Eq, Hash)]
973pub enum Fixity {
974    /// Prefix operator
975    Prefix,
976    /// Left-associative infix operator
977    Infixl,
978    /// Right-associative infix operator
979    Infixr,
980    /// Postfix operator
981    Postfix,
982}
983/// A notation conflict: two rules with the same pattern but different expansions.
984#[allow(dead_code)]
985#[allow(missing_docs)]
986#[derive(Debug, Clone)]
987pub struct NotationConflict {
988    /// The conflicting pattern
989    pub pattern: String,
990    /// First expansion
991    pub expansion_a: String,
992    /// Second expansion
993    pub expansion_b: String,
994}
995/// A notation environment with scoped rules.
996#[allow(dead_code)]
997#[allow(missing_docs)]
998pub struct NotationEnv {
999    /// Stack of scopes, outermost first
1000    pub scopes: Vec<Vec<NotationRule>>,
1001}
1002impl NotationEnv {
1003    /// Create a new notation environment with one empty scope.
1004    #[allow(dead_code)]
1005    pub fn new() -> Self {
1006        NotationEnv {
1007            scopes: vec![Vec::new()],
1008        }
1009    }
1010    /// Push a new scope.
1011    #[allow(dead_code)]
1012    pub fn push_scope(&mut self) {
1013        self.scopes.push(Vec::new());
1014    }
1015    /// Pop the current scope.
1016    #[allow(dead_code)]
1017    pub fn pop_scope(&mut self) {
1018        if self.scopes.len() > 1 {
1019            self.scopes.pop();
1020        }
1021    }
1022    /// Add a rule to the current scope.
1023    #[allow(dead_code)]
1024    pub fn add(&mut self, rule: NotationRule) {
1025        if let Some(scope) = self.scopes.last_mut() {
1026            scope.push(rule);
1027        }
1028    }
1029    /// Look up all rules for a pattern (innermost scope first).
1030    #[allow(dead_code)]
1031    pub fn lookup(&self, pattern: &str) -> Vec<&NotationRule> {
1032        let mut result = Vec::new();
1033        for scope in self.scopes.iter().rev() {
1034            for rule in scope {
1035                if rule.pattern == pattern {
1036                    result.push(rule);
1037                }
1038            }
1039        }
1040        result
1041    }
1042    /// Total number of rules across all scopes.
1043    #[allow(dead_code)]
1044    pub fn total_rules(&self) -> usize {
1045        self.scopes.iter().map(|s| s.len()).sum()
1046    }
1047}
1048/// A notation precedence level with associativity.
1049#[allow(dead_code)]
1050#[allow(missing_docs)]
1051#[derive(Debug, Clone, PartialEq, Eq)]
1052pub struct PrecLevel {
1053    /// The precedence value (higher = tighter binding)
1054    pub value: u32,
1055    /// Whether this is left-associative
1056    pub left_assoc: bool,
1057    /// Whether this is right-associative
1058    pub right_assoc: bool,
1059}
1060impl PrecLevel {
1061    /// Create a new left-associative precedence level.
1062    #[allow(dead_code)]
1063    pub fn left(value: u32) -> Self {
1064        PrecLevel {
1065            value,
1066            left_assoc: true,
1067            right_assoc: false,
1068        }
1069    }
1070    /// Create a new right-associative precedence level.
1071    #[allow(dead_code)]
1072    pub fn right(value: u32) -> Self {
1073        PrecLevel {
1074            value,
1075            left_assoc: false,
1076            right_assoc: true,
1077        }
1078    }
1079    /// Create a non-associative level.
1080    #[allow(dead_code)]
1081    pub fn none(value: u32) -> Self {
1082        PrecLevel {
1083            value,
1084            left_assoc: false,
1085            right_assoc: false,
1086        }
1087    }
1088}
1089/// A single part of a notation pattern.
1090///
1091/// Notation patterns are sequences of literals and placeholders:
1092/// ```text
1093/// notation:50 lhs " + " rhs => HAdd.hAdd lhs rhs
1094///              ^^^  ^^^^^  ^^^
1095///          Placeholder Literal Placeholder
1096/// ```
1097#[derive(Debug, Clone, PartialEq, Eq)]
1098pub enum NotationPart {
1099    /// A literal string token (e.g. `" + "`, `"if"`)
1100    Literal(String),
1101    /// A placeholder for a variable position (e.g. `lhs`, `rhs`)
1102    Placeholder(String),
1103    /// A precedence annotation on a placeholder
1104    Prec(u32),
1105}