Skip to main content

oxilean_parse/macro_parser/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::tokens::{Span, Token, TokenKind};
6
7use super::types::{MacroRule, MacroSubst, MacroTemplateNodeExt, MacroToken, MacroVarExt};
8
9/// Match a macro pattern against input tokens.
10///
11/// Returns a map from variable names to the tokens they matched,
12/// or `None` if the pattern does not match.
13pub fn match_pattern(pattern: &[MacroToken], input: &[Token]) -> Option<Vec<(String, Vec<Token>)>> {
14    let mut bindings: Vec<(String, Vec<Token>)> = Vec::new();
15    let mut input_pos = 0;
16    for pat_tok in pattern {
17        match pat_tok {
18            MacroToken::Literal(expected_kind) => {
19                if input_pos >= input.len() {
20                    return None;
21                }
22                if &input[input_pos].kind != expected_kind {
23                    return None;
24                }
25                input_pos += 1;
26            }
27            MacroToken::Var(name) => {
28                if input_pos >= input.len() {
29                    return None;
30                }
31                let bound = collect_var_tokens(pattern, pat_tok, input, input_pos);
32                let count = bound.len();
33                if count == 0 {
34                    return None;
35                }
36                bindings.push((name.clone(), bound));
37                input_pos += count;
38            }
39            MacroToken::Repeat(sub_pattern) => {
40                if sub_pattern.is_empty() {
41                    let rest: Vec<Token> = input[input_pos..].to_vec();
42                    if !rest.is_empty() {
43                        bindings.push(("_repeat".to_string(), rest));
44                    }
45                    input_pos = input.len();
46                } else {
47                    loop {
48                        if input_pos >= input.len() {
49                            break;
50                        }
51                        let sub_input = &input[input_pos..];
52                        if let Some(sub_bindings) = match_pattern(sub_pattern, sub_input) {
53                            let consumed: usize =
54                                sub_bindings.iter().map(|(_, toks)| toks.len()).sum();
55                            if consumed == 0 {
56                                break;
57                            }
58                            for (k, v) in sub_bindings {
59                                if let Some(existing) = bindings.iter_mut().find(|(n, _)| n == &k) {
60                                    existing.1.extend(v);
61                                } else {
62                                    bindings.push((k, v));
63                                }
64                            }
65                            input_pos += consumed;
66                        } else {
67                            break;
68                        }
69                    }
70                }
71            }
72            MacroToken::Optional(sub_pattern) => {
73                if !sub_pattern.is_empty() && input_pos < input.len() {
74                    let sub_input = &input[input_pos..];
75                    if let Some(sub_bindings) = match_pattern(sub_pattern, sub_input) {
76                        let consumed: usize = sub_bindings.iter().map(|(_, toks)| toks.len()).sum();
77                        for (k, v) in sub_bindings {
78                            bindings.push((k, v));
79                        }
80                        input_pos += consumed;
81                    }
82                }
83            }
84            MacroToken::Antiquote(name) => {
85                if input_pos >= input.len() {
86                    return None;
87                }
88                bindings.push((name.clone(), vec![input[input_pos].clone()]));
89                input_pos += 1;
90            }
91            MacroToken::Quote(_) | MacroToken::SpliceArray(_) => {}
92        }
93    }
94    Some(bindings)
95}
96/// Helper: determine how many tokens a `Var` should consume.
97///
98/// If the next pattern element is a `Literal`, we consume tokens from `input`
99/// starting at `start` until we find that literal. Otherwise we consume exactly one.
100pub(super) fn collect_var_tokens(
101    _pattern: &[MacroToken],
102    _current_pat: &MacroToken,
103    input: &[Token],
104    start: usize,
105) -> Vec<Token> {
106    if start < input.len() {
107        vec![input[start].clone()]
108    } else {
109        Vec::new()
110    }
111}
112/// Expand a macro template with bindings.
113///
114/// Replaces variable references (`$name`) in the template with the tokens
115/// bound during pattern matching.
116pub fn expand_template(template: &[MacroToken], bindings: &[(String, Vec<Token>)]) -> Vec<Token> {
117    let mut result = Vec::new();
118    for tok in template {
119        match tok {
120            MacroToken::Literal(kind) => {
121                result.push(Token::new(kind.clone(), dummy_span()));
122            }
123            MacroToken::Var(name) | MacroToken::Antiquote(name) => {
124                if let Some((_, bound)) = bindings.iter().find(|(n, _)| n == name) {
125                    result.extend(bound.iter().cloned());
126                }
127            }
128            MacroToken::Repeat(sub_template) => {
129                if sub_template.is_empty() {
130                    continue;
131                }
132                let first_var = sub_template.iter().find_map(|t| match t {
133                    MacroToken::Var(n) | MacroToken::Antiquote(n) => Some(n.clone()),
134                    _ => None,
135                });
136                if let Some(var_name) = first_var {
137                    if let Some((_, bound)) = bindings.iter().find(|(n, _)| n == &var_name) {
138                        for tok_item in bound {
139                            let iter_bindings = vec![(var_name.clone(), vec![tok_item.clone()])];
140                            result.extend(expand_template(sub_template, &iter_bindings));
141                        }
142                    }
143                }
144            }
145            MacroToken::Optional(sub_template) => {
146                let has_any = sub_template.iter().any(|t| match t {
147                    MacroToken::Var(n) | MacroToken::Antiquote(n) => {
148                        bindings.iter().any(|(bn, bv)| bn == n && !bv.is_empty())
149                    }
150                    _ => false,
151                });
152                if has_any {
153                    result.extend(expand_template(sub_template, bindings));
154                }
155            }
156            MacroToken::SpliceArray(name) => {
157                if let Some((_, bound)) = bindings.iter().find(|(n, _)| n == name) {
158                    result.extend(bound.iter().cloned());
159                }
160            }
161            MacroToken::Quote(inner) => {
162                result.extend(expand_template(inner, bindings));
163            }
164        }
165    }
166    result
167}
168/// Create a dummy span for generated tokens.
169pub(super) fn dummy_span() -> Span {
170    Span::new(0, 0, 0, 0)
171}
172/// Try to match a macro rule's pattern against input tokens.
173///
174/// Returns the variable bindings if the pattern matches.
175pub fn try_match_rule(rule: &MacroRule, input: &[Token]) -> Option<Vec<(String, Vec<Token>)>> {
176    match_pattern(&rule.pattern, input)
177}
178/// Substitute bindings into a template to produce expanded tokens.
179///
180/// This is a convenience wrapper around [`expand_template`].
181pub fn substitute(template: &[MacroToken], bindings: &[(String, Vec<Token>)]) -> Vec<Token> {
182    expand_template(template, bindings)
183}
184/// Create a token with a dummy span (for testing).
185#[cfg(test)]
186pub(super) fn make_token(kind: TokenKind) -> Token {
187    Token::new(kind, dummy_span())
188}
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use crate::macro_parser::*;
193    fn mk(kind: TokenKind) -> Token {
194        make_token(kind)
195    }
196    fn mk_ident(s: &str) -> Token {
197        mk(TokenKind::Ident(s.to_string()))
198    }
199    fn mk_eof() -> Token {
200        mk(TokenKind::Eof)
201    }
202    fn dummy_hygiene() -> HygieneInfo {
203        HygieneInfo::new(0, Span::new(0, 0, 1, 1))
204    }
205    #[test]
206    fn test_macro_token_var() {
207        let token = MacroToken::Var("x".to_string());
208        assert_eq!(token, MacroToken::Var("x".to_string()));
209    }
210    #[test]
211    fn test_macro_token_literal() {
212        let token = MacroToken::Literal(TokenKind::Plus);
213        assert!(matches!(token, MacroToken::Literal(TokenKind::Plus)));
214    }
215    #[test]
216    fn test_macro_token_repeat() {
217        let token = MacroToken::Repeat(vec![]);
218        assert!(matches!(token, MacroToken::Repeat(_)));
219    }
220    #[test]
221    fn test_macro_token_quote() {
222        let q = MacroToken::Quote(vec![MacroToken::Var("x".into())]);
223        assert!(matches!(q, MacroToken::Quote(_)));
224    }
225    #[test]
226    fn test_macro_token_antiquote() {
227        let a = MacroToken::Antiquote("y".into());
228        assert!(matches!(a, MacroToken::Antiquote(_)));
229    }
230    #[test]
231    fn test_macro_token_splice_array() {
232        let s = MacroToken::SpliceArray("xs".into());
233        assert!(matches!(s, MacroToken::SpliceArray(_)));
234    }
235    #[test]
236    fn test_macro_token_display() {
237        assert_eq!(format!("{}", MacroToken::Var("x".into())), "$x");
238        assert_eq!(format!("{}", MacroToken::Literal(TokenKind::Plus)), "+");
239        assert_eq!(format!("{}", MacroToken::Antiquote("y".into())), "$y");
240        assert_eq!(
241            format!("{}", MacroToken::SpliceArray("xs".into())),
242            "$[xs]*"
243        );
244    }
245    #[test]
246    fn test_macro_parser_create() {
247        let tokens = vec![];
248        let parser = MacroParser::new(tokens);
249        assert_eq!(parser.pos, 0);
250    }
251    #[test]
252    fn test_macro_parser_parse_simple_rule() {
253        let tokens = vec![
254            mk_ident("$x"),
255            mk(TokenKind::Plus),
256            mk_ident("$y"),
257            mk(TokenKind::Arrow),
258            mk_ident("add"),
259            mk_ident("$x"),
260            mk_ident("$y"),
261            mk_eof(),
262        ];
263        let mut parser = MacroParser::new(tokens);
264        let rule = parser.parse_rule().expect("test operation should succeed");
265        assert_eq!(rule.pattern.len(), 3);
266        assert_eq!(rule.template.len(), 3);
267    }
268    #[test]
269    fn test_macro_parser_parse_multiple_rules() {
270        let tokens = vec![
271            mk_ident("$x"),
272            mk(TokenKind::Arrow),
273            mk_ident("foo"),
274            mk_ident("$x"),
275            mk(TokenKind::Bar),
276            mk_ident("$y"),
277            mk(TokenKind::Plus),
278            mk_ident("$z"),
279            mk(TokenKind::Arrow),
280            mk_ident("bar"),
281            mk_ident("$y"),
282            mk_ident("$z"),
283            mk_eof(),
284        ];
285        let mut parser = MacroParser::new(tokens);
286        let rules = parser.parse_rules().expect("test operation should succeed");
287        assert_eq!(rules.len(), 2);
288    }
289    #[test]
290    fn test_hygiene_info() {
291        let h = HygieneInfo::new(42, Span::new(10, 20, 3, 5));
292        assert_eq!(h.scope_id, 42);
293        assert_eq!(h.def_site.start, 10);
294    }
295    #[test]
296    fn test_macro_def_new() {
297        let def = MacroDef::new("myMacro".into(), vec![], dummy_hygiene());
298        assert_eq!(def.name, "myMacro");
299        assert_eq!(def.rule_count(), 0);
300        assert!(def.doc.is_none());
301    }
302    #[test]
303    fn test_macro_def_with_doc() {
304        let def = MacroDef::new("myMacro".into(), vec![], dummy_hygiene())
305            .with_doc("A test macro".into());
306        assert_eq!(def.doc.as_deref(), Some("A test macro"));
307    }
308    #[test]
309    fn test_syntax_kind_display() {
310        assert_eq!(format!("{}", SyntaxKind::Term), "term");
311        assert_eq!(format!("{}", SyntaxKind::Command), "command");
312        assert_eq!(format!("{}", SyntaxKind::Tactic), "tactic");
313        assert_eq!(format!("{}", SyntaxKind::Level), "level");
314        assert_eq!(format!("{}", SyntaxKind::Attr), "attr");
315    }
316    #[test]
317    fn test_syntax_item_display() {
318        let item = SyntaxItem::Token(TokenKind::Plus);
319        assert_eq!(format!("{}", item), "+");
320        let cat = SyntaxItem::Category("term".into());
321        assert_eq!(format!("{}", cat), "term");
322        let opt = SyntaxItem::Optional(Box::new(SyntaxItem::Category("ident".into())));
323        assert_eq!(format!("{}", opt), "(ident)?");
324        let many = SyntaxItem::Many(Box::new(SyntaxItem::Token(TokenKind::Comma)));
325        assert_eq!(format!("{}", many), "(,)*");
326        let group = SyntaxItem::Group(vec![
327            SyntaxItem::Token(TokenKind::LParen),
328            SyntaxItem::Category("term".into()),
329            SyntaxItem::Token(TokenKind::RParen),
330        ]);
331        assert_eq!(format!("{}", group), "(( term ))");
332    }
333    #[test]
334    fn test_syntax_def_new() {
335        let def = SyntaxDef::new(
336            "myIf".into(),
337            SyntaxKind::Term,
338            vec![
339                SyntaxItem::Token(TokenKind::If),
340                SyntaxItem::Category("term".into()),
341                SyntaxItem::Token(TokenKind::Then),
342                SyntaxItem::Category("term".into()),
343                SyntaxItem::Token(TokenKind::Else),
344                SyntaxItem::Category("term".into()),
345            ],
346        );
347        assert_eq!(def.name, "myIf");
348        assert_eq!(def.kind, SyntaxKind::Term);
349        assert_eq!(def.item_count(), 6);
350    }
351    #[test]
352    fn test_macro_error_display() {
353        let err = MacroError::new(MacroErrorKind::UnknownMacro, "not found".into());
354        let msg = format!("{}", err);
355        assert!(msg.contains("unknown macro"));
356        assert!(msg.contains("not found"));
357    }
358    #[test]
359    fn test_macro_error_with_span() {
360        let err = MacroError::new(MacroErrorKind::PatternMismatch, "oops".into())
361            .with_span(Span::new(5, 10, 2, 3));
362        assert!(err.span.is_some());
363        let sp = err.span.expect("span should be present");
364        assert_eq!(sp.start, 5);
365    }
366    #[test]
367    fn test_macro_error_kind_display() {
368        assert_eq!(format!("{}", MacroErrorKind::UnknownMacro), "unknown macro");
369        assert_eq!(
370            format!("{}", MacroErrorKind::PatternMismatch),
371            "pattern mismatch"
372        );
373        assert_eq!(
374            format!("{}", MacroErrorKind::HygieneViolation),
375            "hygiene violation"
376        );
377        assert_eq!(
378            format!("{}", MacroErrorKind::AmbiguousMatch),
379            "ambiguous match"
380        );
381        assert_eq!(
382            format!("{}", MacroErrorKind::ExpansionError),
383            "expansion error"
384        );
385    }
386    #[test]
387    fn test_match_pattern_empty() {
388        let result = match_pattern(&[], &[]);
389        assert!(result.is_some());
390        assert!(result.expect("test operation should succeed").is_empty());
391    }
392    #[test]
393    fn test_match_pattern_literal_match() {
394        let pattern = vec![MacroToken::Literal(TokenKind::Plus)];
395        let input = vec![mk(TokenKind::Plus)];
396        let result = match_pattern(&pattern, &input);
397        assert!(result.is_some());
398    }
399    #[test]
400    fn test_match_pattern_literal_mismatch() {
401        let pattern = vec![MacroToken::Literal(TokenKind::Plus)];
402        let input = vec![mk(TokenKind::Minus)];
403        let result = match_pattern(&pattern, &input);
404        assert!(result.is_none());
405    }
406    #[test]
407    fn test_match_pattern_var_binding() {
408        let pattern = vec![
409            MacroToken::Var("x".into()),
410            MacroToken::Literal(TokenKind::Plus),
411            MacroToken::Var("y".into()),
412        ];
413        let input = vec![mk_ident("a"), mk(TokenKind::Plus), mk_ident("b")];
414        let result = match_pattern(&pattern, &input);
415        assert!(result.is_some());
416        let bindings = result.expect("test operation should succeed");
417        assert_eq!(bindings.len(), 2);
418        assert_eq!(bindings[0].0, "x");
419        assert_eq!(bindings[1].0, "y");
420    }
421    #[test]
422    fn test_match_pattern_too_short_input() {
423        let pattern = vec![
424            MacroToken::Var("x".into()),
425            MacroToken::Literal(TokenKind::Plus),
426        ];
427        let input = vec![mk_ident("a")];
428        let result = match_pattern(&pattern, &input);
429        assert!(result.is_none());
430    }
431    #[test]
432    fn test_match_pattern_optional_present() {
433        let pattern = vec![
434            MacroToken::Var("x".into()),
435            MacroToken::Optional(vec![MacroToken::Literal(TokenKind::Plus)]),
436        ];
437        let input = vec![mk_ident("a"), mk(TokenKind::Plus)];
438        let result = match_pattern(&pattern, &input);
439        assert!(result.is_some());
440    }
441    #[test]
442    fn test_match_pattern_optional_absent() {
443        let pattern = vec![
444            MacroToken::Var("x".into()),
445            MacroToken::Optional(vec![MacroToken::Literal(TokenKind::Plus)]),
446        ];
447        let input = vec![mk_ident("a")];
448        let result = match_pattern(&pattern, &input);
449        assert!(result.is_some());
450    }
451    #[test]
452    fn test_expand_template_empty() {
453        let result = expand_template(&[], &[]);
454        assert!(result.is_empty());
455    }
456    #[test]
457    fn test_expand_template_literal() {
458        let template = vec![
459            MacroToken::Literal(TokenKind::LParen),
460            MacroToken::Literal(TokenKind::RParen),
461        ];
462        let result = expand_template(&template, &[]);
463        assert_eq!(result.len(), 2);
464        assert_eq!(result[0].kind, TokenKind::LParen);
465        assert_eq!(result[1].kind, TokenKind::RParen);
466    }
467    #[test]
468    fn test_expand_template_var_substitution() {
469        let template = vec![
470            MacroToken::Literal(TokenKind::Ident("add".into())),
471            MacroToken::Var("x".into()),
472            MacroToken::Var("y".into()),
473        ];
474        let bindings = vec![
475            ("x".into(), vec![mk_ident("a")]),
476            ("y".into(), vec![mk_ident("b")]),
477        ];
478        let result = expand_template(&template, &bindings);
479        assert_eq!(result.len(), 3);
480        assert_eq!(result[0].kind, TokenKind::Ident("add".into()));
481        assert_eq!(result[1].kind, TokenKind::Ident("a".into()));
482        assert_eq!(result[2].kind, TokenKind::Ident("b".into()));
483    }
484    #[test]
485    fn test_expand_template_missing_var() {
486        let template = vec![MacroToken::Var("missing".into())];
487        let result = expand_template(&template, &[]);
488        assert!(result.is_empty());
489    }
490    #[test]
491    fn test_expand_template_splice_array() {
492        let template = vec![
493            MacroToken::Literal(TokenKind::LBracket),
494            MacroToken::SpliceArray("xs".into()),
495            MacroToken::Literal(TokenKind::RBracket),
496        ];
497        let bindings = vec![(
498            "xs".into(),
499            vec![mk_ident("a"), mk(TokenKind::Comma), mk_ident("b")],
500        )];
501        let result = expand_template(&template, &bindings);
502        assert_eq!(result.len(), 5);
503    }
504    #[test]
505    fn test_expander_new() {
506        let exp = MacroExpander::new();
507        assert_eq!(exp.macro_count(), 0);
508    }
509    #[test]
510    fn test_expander_default() {
511        let exp = MacroExpander::default();
512        assert_eq!(exp.macro_count(), 0);
513    }
514    #[test]
515    fn test_expander_register_and_lookup() {
516        let mut exp = MacroExpander::new();
517        let def = MacroDef::new("test_mac".into(), vec![], dummy_hygiene());
518        exp.register_macro(def);
519        assert!(exp.has_macro("test_mac"));
520        assert!(!exp.has_macro("other"));
521        assert_eq!(exp.macro_count(), 1);
522    }
523    #[test]
524    fn test_expander_unregister() {
525        let mut exp = MacroExpander::new();
526        exp.register_macro(MacroDef::new("test_mac".into(), vec![], dummy_hygiene()));
527        let removed = exp.unregister_macro("test_mac");
528        assert!(removed.is_some());
529        assert!(!exp.has_macro("test_mac"));
530    }
531    #[test]
532    fn test_expander_fresh_scope() {
533        let mut exp = MacroExpander::new();
534        let s1 = exp.fresh_scope();
535        let s2 = exp.fresh_scope();
536        assert_ne!(s1, s2);
537        assert_eq!(s2, s1 + 1);
538    }
539    #[test]
540    fn test_expander_unknown_macro() {
541        let mut exp = MacroExpander::new();
542        let result = exp.expand("nonexistent", &[]);
543        assert!(result.is_err());
544        let err = result.unwrap_err();
545        assert_eq!(err.kind, MacroErrorKind::UnknownMacro);
546    }
547    #[test]
548    fn test_expander_pattern_mismatch() {
549        let mut exp = MacroExpander::new();
550        let rule = MacroRule {
551            pattern: vec![MacroToken::Literal(TokenKind::Plus)],
552            template: vec![MacroToken::Literal(TokenKind::Minus)],
553        };
554        exp.register_macro(MacroDef::new("myMac".into(), vec![rule], dummy_hygiene()));
555        let result = exp.expand("myMac", &[mk(TokenKind::Star)]);
556        assert!(result.is_err());
557        assert_eq!(result.unwrap_err().kind, MacroErrorKind::PatternMismatch);
558    }
559    #[test]
560    fn test_expander_successful_expansion() {
561        let mut exp = MacroExpander::new();
562        let rule = MacroRule {
563            pattern: vec![
564                MacroToken::Var("x".into()),
565                MacroToken::Literal(TokenKind::Plus),
566                MacroToken::Var("y".into()),
567            ],
568            template: vec![
569                MacroToken::Literal(TokenKind::Ident("add".into())),
570                MacroToken::Var("x".into()),
571                MacroToken::Var("y".into()),
572            ],
573        };
574        exp.register_macro(MacroDef::new(
575            "addMacro".into(),
576            vec![rule],
577            dummy_hygiene(),
578        ));
579        let input = vec![mk_ident("a"), mk(TokenKind::Plus), mk_ident("b")];
580        let result = exp
581            .expand("addMacro", &input)
582            .expect("test operation should succeed");
583        assert_eq!(result.len(), 3);
584        assert_eq!(result[0].kind, TokenKind::Ident("add".into()));
585        assert_eq!(result[1].kind, TokenKind::Ident("a".into()));
586        assert_eq!(result[2].kind, TokenKind::Ident("b".into()));
587    }
588    #[test]
589    fn test_expander_macro_names() {
590        let mut exp = MacroExpander::new();
591        exp.register_macro(MacroDef::new("beta".into(), vec![], dummy_hygiene()));
592        exp.register_macro(MacroDef::new("alpha".into(), vec![], dummy_hygiene()));
593        let names = exp.macro_names();
594        assert_eq!(names, vec!["alpha", "beta"]);
595    }
596    #[test]
597    fn test_expander_syntax_defs() {
598        let mut exp = MacroExpander::new();
599        exp.register_syntax(SyntaxDef::new(
600            "myIf".into(),
601            SyntaxKind::Term,
602            vec![SyntaxItem::Token(TokenKind::If)],
603        ));
604        exp.register_syntax(SyntaxDef::new(
605            "myTac".into(),
606            SyntaxKind::Tactic,
607            vec![SyntaxItem::Category("tactic".into())],
608        ));
609        let term_defs = exp.syntax_defs_for(&SyntaxKind::Term);
610        assert_eq!(term_defs.len(), 1);
611        assert_eq!(term_defs[0].name, "myIf");
612        let tactic_defs = exp.syntax_defs_for(&SyntaxKind::Tactic);
613        assert_eq!(tactic_defs.len(), 1);
614    }
615    #[test]
616    fn test_expander_max_depth() {
617        let mut exp = MacroExpander::new();
618        exp.set_max_depth(5);
619        assert_eq!(exp.max_depth, 5);
620    }
621    #[test]
622    fn test_parse_repeat_group_star() {
623        let tokens = vec![
624            mk(TokenKind::Ident("$".to_string())),
625            mk(TokenKind::LParen),
626            mk(TokenKind::Ident("$x".to_string())),
627            mk(TokenKind::RParen),
628            mk(TokenKind::Star),
629            mk(TokenKind::Eof),
630        ];
631        let mut parser = MacroParser::new(tokens);
632        let tok = parser
633            .parse_macro_token()
634            .expect("test operation should succeed");
635        assert!(matches!(tok, MacroToken::Repeat(ref inner) if inner.len() == 1));
636        if let MacroToken::Repeat(inner) = tok {
637            assert!(matches!(inner[0], MacroToken::Var(ref n) if n == "x"));
638        }
639    }
640    #[test]
641    fn test_parse_repeat_group_question() {
642        let tokens = vec![
643            mk(TokenKind::Ident("$".to_string())),
644            mk(TokenKind::LParen),
645            mk(TokenKind::Ident("$x".to_string())),
646            mk(TokenKind::RParen),
647            mk(TokenKind::Question),
648            mk(TokenKind::Eof),
649        ];
650        let mut parser = MacroParser::new(tokens);
651        let tok = parser
652            .parse_macro_token()
653            .expect("test operation should succeed");
654        assert!(matches!(tok, MacroToken::Optional(ref inner) if inner.len() == 1));
655    }
656    #[test]
657    fn test_parse_repeat_group_with_separator() {
658        let tokens = vec![
659            mk(TokenKind::Ident("$".to_string())),
660            mk(TokenKind::LParen),
661            mk(TokenKind::Ident("$x".to_string())),
662            mk(TokenKind::Comma),
663            mk(TokenKind::RParen),
664            mk(TokenKind::Star),
665            mk(TokenKind::Eof),
666        ];
667        let mut parser = MacroParser::new(tokens);
668        let tok = parser
669            .parse_macro_token()
670            .expect("test operation should succeed");
671        if let MacroToken::Repeat(inner) = tok {
672            assert_eq!(inner.len(), 2);
673            assert!(matches!(inner[0], MacroToken::Var(_)));
674            assert!(matches!(inner[1], MacroToken::Literal(TokenKind::Comma)));
675        } else {
676            panic!("expected Repeat");
677        }
678    }
679    #[test]
680    fn test_match_pattern_with_repeat() {
681        let pattern = vec![MacroToken::Repeat(vec![MacroToken::Var("x".into())])];
682        let input = vec![mk_ident("a"), mk_ident("b"), mk_ident("c")];
683        let result = match_pattern(&pattern, &input);
684        assert!(result.is_some());
685        let bindings = result.expect("test operation should succeed");
686        assert!(bindings.iter().any(|(k, _)| k == "x"));
687    }
688    #[test]
689    fn test_match_pattern_with_repeat_empty() {
690        let pattern = vec![MacroToken::Repeat(vec![MacroToken::Var("x".into())])];
691        let input = vec![];
692        let result = match_pattern(&pattern, &input);
693        assert!(result.is_some());
694    }
695    #[test]
696    fn test_try_match_rule_success() {
697        let rule = MacroRule {
698            pattern: vec![MacroToken::Var("x".into())],
699            template: vec![MacroToken::Var("x".into())],
700        };
701        let input = vec![mk_ident("hello")];
702        let result = try_match_rule(&rule, &input);
703        assert!(result.is_some());
704    }
705    #[test]
706    fn test_substitute_identity() {
707        let template = vec![MacroToken::Var("x".into())];
708        let bindings = vec![("x".into(), vec![mk_ident("hello")])];
709        let result = substitute(&template, &bindings);
710        assert_eq!(result.len(), 1);
711        assert_eq!(result[0].kind, TokenKind::Ident("hello".into()));
712    }
713}
714/// Parse a simple macro template string into nodes.
715#[allow(dead_code)]
716#[allow(missing_docs)]
717pub fn parse_simple_template_ext(s: &str) -> Vec<MacroTemplateNodeExt> {
718    s.split_whitespace()
719        .map(|tok| {
720            if let Some(name) = tok.strip_prefix('$') {
721                MacroTemplateNodeExt::Var(MacroVarExt::simple(name))
722            } else {
723                MacroTemplateNodeExt::Literal(tok.to_string())
724            }
725        })
726        .collect()
727}
728/// Expand a simple macro template with variable substitutions.
729#[allow(dead_code)]
730#[allow(missing_docs)]
731pub fn expand_template_ext(
732    nodes: &[MacroTemplateNodeExt],
733    env: &std::collections::HashMap<String, String>,
734) -> String {
735    let mut result = Vec::new();
736    for node in nodes {
737        match node {
738            MacroTemplateNodeExt::Literal(s) => result.push(s.clone()),
739            MacroTemplateNodeExt::Var(v) => {
740                if let Some(val) = env.get(&v.name) {
741                    result.push(val.clone());
742                } else {
743                    result.push(format!("${}?", v.name));
744                }
745            }
746            MacroTemplateNodeExt::Rep { .. } => result.push("(...)".to_string()),
747            MacroTemplateNodeExt::Group(inner) => result.push(expand_template_ext(inner, env)),
748        }
749    }
750    result.join(" ")
751}
752#[cfg(test)]
753mod macro_parser_ext_tests {
754    use super::*;
755    use crate::macro_parser::*;
756    #[test]
757    fn test_macro_var() {
758        let v = MacroVarExt::simple("x");
759        assert_eq!(v.name, "x");
760        assert!(!v.is_rep);
761        let rv = MacroVarExt::rep("xs");
762        assert!(rv.is_rep);
763    }
764    #[test]
765    fn test_parse_simple_template() {
766        let nodes = parse_simple_template_ext("if $cond then $body else $alt");
767        assert_eq!(nodes.len(), 6);
768    }
769    #[test]
770    fn test_expand_template() {
771        let nodes = parse_simple_template_ext("add $x $y");
772        let mut env = std::collections::HashMap::new();
773        env.insert("x".to_string(), "1".to_string());
774        env.insert("y".to_string(), "2".to_string());
775        let result = expand_template_ext(&nodes, &env);
776        assert_eq!(result, "add 1 2");
777    }
778    #[test]
779    fn test_macro_definition_expand() {
780        let def = MacroDefinitionExt::new("myMacro", vec!["a", "b"], "$a + $b");
781        let result = def.expand(&["x", "y"]);
782        assert_eq!(result, "x + y");
783    }
784    #[test]
785    fn test_macro_definition_wrong_arity() {
786        let def = MacroDefinitionExt::new("myMacro", vec!["a"], "$a");
787        let result = def.expand(&["x", "y"]);
788        assert!(result.contains("error"));
789    }
790    #[test]
791    fn test_macro_environment() {
792        let mut env = MacroEnvironmentExt::new();
793        env.define(MacroDefinitionExt::new("add", vec!["a", "b"], "$a + $b"));
794        assert_eq!(env.len(), 1);
795        let result = env
796            .expand_call("add", &["1", "2"])
797            .expect("test operation should succeed");
798        assert_eq!(result, "1 + 2");
799        assert_eq!(env.expand_call("unknown", &[]), None);
800    }
801    #[test]
802    fn test_macro_expansion_trace() {
803        let mut trace = MacroExpansionTraceExt::new();
804        trace.record("step 1");
805        trace.record("step 2");
806        let out = trace.format();
807        assert!(out.contains("0: step 1"));
808        assert!(out.contains("1: step 2"));
809    }
810    #[test]
811    fn test_macro_call_site() {
812        let site = MacroCallSiteExt::new("myMacro", vec!["a", "b"], 10);
813        assert_eq!(site.macro_name, "myMacro");
814        assert_eq!(site.args.len(), 2);
815        assert_eq!(site.offset, 10);
816    }
817}
818#[cfg(test)]
819mod macro_parser_ext2_tests {
820    use super::*;
821    use crate::macro_parser::*;
822    #[test]
823    fn test_hygiene_context() {
824        let ctx = HygieneContext::new(42);
825        let nested = ctx.nested();
826        assert_eq!(nested.depth, 1);
827        let name = nested.hygienic_name("x");
828        assert!(name.starts_with("x__42"));
829    }
830    #[test]
831    fn test_macro_expansion_error() {
832        let err = MacroExpansionError::new("myMacro", "arity mismatch", 2);
833        let formatted = err.format();
834        assert!(formatted.contains("myMacro"));
835        assert!(formatted.contains("arity mismatch"));
836        assert!(formatted.contains("depth 2"));
837    }
838    #[test]
839    fn test_macro_expansion_result() {
840        let ok = MacroExpansionResult::Ok("result".to_string());
841        assert!(ok.is_ok());
842        let ok2 = MacroExpansionResult::Ok("value".to_string());
843        match ok2 {
844            MacroExpansionResult::Ok(v) => assert_eq!(v, "value"),
845            _ => panic!("expected Ok variant"),
846        };
847        let err = MacroExpansionResult::Err(MacroExpansionError::new("m", "e", 0));
848        assert!(!err.is_ok());
849        assert_eq!(err.unwrap_or("default"), "default");
850    }
851    #[test]
852    fn test_macro_stats() {
853        let mut stats = MacroStats::new();
854        stats.record_success(3);
855        stats.record_success(5);
856        stats.record_error();
857        assert_eq!(stats.expansions, 2);
858        assert_eq!(stats.errors, 1);
859        assert_eq!(stats.max_depth, 5);
860    }
861}
862#[cfg(test)]
863mod macro_library_tests {
864    use super::*;
865    use crate::macro_parser::*;
866    #[test]
867    fn test_macro_matcher() {
868        let m = MacroMatcher::from_str("if $cond then $body");
869        assert_eq!(m.hole_count(), 2);
870        assert_eq!(m.literal_count(), 2);
871    }
872    #[test]
873    fn test_macro_library() {
874        let mut lib = MacroLibrary::new();
875        lib.add_to_group("logic", MacroDefinitionExt::new("myMacro", vec!["x"], "$x"));
876        assert_eq!(lib.total_macros(), 1);
877        assert_eq!(lib.group("logic").len(), 1);
878        assert_eq!(lib.group("missing").len(), 0);
879    }
880}
881/// Apply a list of substitutions to a template string.
882#[allow(dead_code)]
883#[allow(missing_docs)]
884pub fn apply_substitutions(template: &str, substs: &[MacroSubst]) -> String {
885    let mut result = template.to_string();
886    for s in substs {
887        let placeholder = format!("${}", s.var);
888        result = result.replace(&placeholder, &s.value);
889    }
890    result
891}
892#[cfg(test)]
893mod macro_final_tests {
894    use super::*;
895    use crate::macro_parser::*;
896    #[test]
897    fn test_depth_limited_expander() {
898        let mut exp = DepthLimitedExpander::new(3);
899        assert!(exp.try_expand());
900        assert!(exp.try_expand());
901        assert!(exp.try_expand());
902        assert!(!exp.try_expand());
903        exp.exit();
904        assert!(exp.try_expand());
905    }
906    #[test]
907    fn test_apply_substitutions() {
908        let substs = vec![MacroSubst::new("x", "1"), MacroSubst::new("y", "2")];
909        let result = apply_substitutions("$x + $y", &substs);
910        assert_eq!(result, "1 + 2");
911    }
912}
913/// Strips macro invocations from a source string (very simplified).
914#[allow(dead_code)]
915#[allow(missing_docs)]
916pub fn strip_macro_invocations(src: &str) -> String {
917    src.lines()
918        .map(|line| {
919            if line.contains('!') {
920                line.split('!')
921                    .next()
922                    .unwrap_or(line)
923                    .trim_end()
924                    .to_string()
925            } else {
926                line.to_string()
927            }
928        })
929        .collect::<Vec<_>>()
930        .join("\n")
931}
932/// Count macro invocations in a source string.
933#[allow(dead_code)]
934#[allow(missing_docs)]
935pub fn count_macro_invocations(src: &str) -> usize {
936    src.matches('!').count()
937}
938#[cfg(test)]
939mod macro_pad {
940    use super::*;
941    use crate::macro_parser::*;
942    #[test]
943    fn test_count_macro_invocations() {
944        assert_eq!(count_macro_invocations("dbg!(x) + dbg!(y)"), 2);
945        assert_eq!(count_macro_invocations("no macros here"), 0);
946    }
947    #[test]
948    fn test_macro_signature() {
949        let sig = MacroSignature::new("myMacro", 3);
950        assert_eq!(sig.format(), "myMacro/3");
951    }
952}
953#[cfg(test)]
954mod macro_pad2 {
955    use super::*;
956    use crate::macro_parser::*;
957    #[test]
958    fn test_hygiene_context() {
959        let mut ctx = HygieneContextExt2::new("_x");
960        assert_eq!(ctx.fresh(), "_x0");
961        assert_eq!(ctx.fresh(), "_x1");
962        assert_eq!(ctx.generated_count(), 2);
963    }
964    #[test]
965    fn test_macro_expansion_error() {
966        let e = MacroExpansionErrorExt2::new("myMacro", "arity mismatch", 3);
967        assert!(e.format().contains("myMacro"));
968        assert!(e.format().contains("depth 3"));
969    }
970    #[test]
971    fn test_macro_expansion_result() {
972        let r = MacroExpansionResultExt2::Success("x + 1".to_string());
973        assert!(r.is_success());
974        assert_eq!(r.as_str(), Some("x + 1"));
975        let e = MacroExpansionResultExt2::NoMatch;
976        assert!(!e.is_success());
977    }
978    #[test]
979    fn test_macro_stats() {
980        let mut s = MacroStatsExt2::default();
981        s.record_success(3);
982        s.record_failure();
983        assert_eq!(s.attempts, 2);
984        assert!((s.success_rate() - 0.5).abs() < 1e-9);
985    }
986}