1use crate::tokens::{Span, Token, TokenKind};
6
7use super::types::{MacroRule, MacroSubst, MacroTemplateNodeExt, MacroToken, MacroVarExt};
8
9pub 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}
96pub(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}
112pub 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}
168pub(super) fn dummy_span() -> Span {
170 Span::new(0, 0, 0, 0)
171}
172pub fn try_match_rule(rule: &MacroRule, input: &[Token]) -> Option<Vec<(String, Vec<Token>)>> {
176 match_pattern(&rule.pattern, input)
177}
178pub fn substitute(template: &[MacroToken], bindings: &[(String, Vec<Token>)]) -> Vec<Token> {
182 expand_template(template, bindings)
183}
184#[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#[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#[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#[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#[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#[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}