1use crate::{CodePattern, NameMatcher, NodeKind, PatternExpr};
34use std::collections::HashMap;
35use thiserror::Error;
36
37#[derive(Debug, Error)]
39pub enum ParseError {
40 #[error("Failed to parse pattern: {0}")]
42 SynError(#[from] syn::Error),
43
44 #[error("Unsupported syntax: {0}")]
46 UnsupportedSyntax(String),
47
48 #[error("Invalid metavariable: {0}")]
50 InvalidMetavariable(String),
51}
52
53pub type ParseResult<T> = Result<T, ParseError>;
55
56pub fn parse_pattern(input: &str) -> ParseResult<CodePattern> {
58 let parser = ConcreteParser::new();
59 parser.parse(input)
60}
61
62const METAVAR_PREFIX: &str = "__mv_";
64const ELLIPSIS_PREFIX: &str = "__ell_";
66
67pub struct ConcreteParser {
69 }
71
72impl ConcreteParser {
73 pub fn new() -> Self {
75 Self {}
76 }
77
78 pub fn parse(&self, input: &str) -> ParseResult<CodePattern> {
80 let preprocessed = self.preprocess(input);
82
83 if let Ok(expr) = syn::parse_str::<syn::Expr>(&preprocessed) {
85 return self.convert_expr(&expr);
86 }
87
88 if let Ok(stmt) = syn::parse_str::<syn::Stmt>(&preprocessed) {
90 return self.convert_stmt(&stmt);
91 }
92
93 if let Ok(item) = syn::parse_str::<syn::Item>(&preprocessed) {
95 return self.convert_item(&item);
96 }
97
98 Err(ParseError::UnsupportedSyntax(format!(
99 "Could not parse as expression, statement, or item: {}",
100 input
101 )))
102 }
103
104 fn preprocess(&self, input: &str) -> String {
106 let mut result = String::with_capacity(input.len() * 2);
107 let mut chars = input.chars().peekable();
108
109 while let Some(c) = chars.next() {
110 if c == '$' {
111 let mut is_ellipsis = false;
113 let mut dots = String::new();
114
115 while chars.peek() == Some(&'.') {
116 dots.push(chars.next().unwrap());
117 }
118
119 if dots.len() >= 3 {
120 is_ellipsis = true;
121 } else {
122 }
125
126 let mut var_name = String::new();
128 while let Some(&ch) = chars.peek() {
129 if ch.is_alphanumeric() || ch == '_' {
130 var_name.push(chars.next().unwrap());
131 } else {
132 break;
133 }
134 }
135
136 if is_ellipsis {
137 result.push_str(ELLIPSIS_PREFIX);
138 result.push_str(&var_name);
139 } else if !var_name.is_empty() {
140 result.push_str(METAVAR_PREFIX);
141 result.push_str(&dots); result.push_str(&var_name);
143 } else {
144 result.push('$');
146 result.push_str(&dots);
147 }
148 } else {
149 result.push(c);
150 }
151 }
152
153 result
154 }
155
156 fn convert_expr(&self, expr: &syn::Expr) -> ParseResult<CodePattern> {
158 match expr {
159 syn::Expr::MethodCall(mc) => self.convert_method_call(mc),
160 syn::Expr::Call(call) => self.convert_call(call),
161 syn::Expr::Macro(mac) => self.convert_macro(mac),
162 syn::Expr::Path(path) => self.convert_path(path),
163 syn::Expr::If(if_expr) => self.convert_if(if_expr),
164 syn::Expr::Match(match_expr) => self.convert_match(match_expr),
165 syn::Expr::Block(block) => self.convert_block(block),
166 syn::Expr::Binary(bin) => self.convert_binary(bin),
167 syn::Expr::Unary(unary) => self.convert_unary(unary),
168 syn::Expr::Lit(lit) => self.convert_literal(lit),
169 syn::Expr::Try(try_expr) => self.convert_try(try_expr),
170 syn::Expr::Return(ret) => self.convert_return(ret),
171 syn::Expr::Await(await_expr) => self.convert_await(await_expr),
172 syn::Expr::Closure(closure) => self.convert_closure(closure),
173 _ => Err(ParseError::UnsupportedSyntax(
174 "Unsupported expression type".to_string(),
175 )),
176 }
177 }
178
179 fn convert_method_call(&self, mc: &syn::ExprMethodCall) -> ParseResult<CodePattern> {
181 let mut pattern = CodePattern::new(NodeKind::MethodCall);
182 let mut children = HashMap::new();
183
184 if let Some(capture) = self.extract_metavar_from_expr(&mc.receiver) {
186 children.insert(
187 "receiver".to_string(),
188 PatternExpr::Capture(to_metavar_name(&capture)),
189 );
190 } else {
191 let receiver_pattern = self.convert_expr(&mc.receiver)?;
192 children.insert(
193 "receiver".to_string(),
194 PatternExpr::Pattern(Box::new(receiver_pattern)),
195 );
196 }
197
198 let method_name = mc.method.to_string();
200 if is_metavariable(&method_name) {
201 children.insert(
202 "method".to_string(),
203 PatternExpr::Capture(to_metavar_name(&method_name)),
204 );
205 } else {
206 children.insert(
207 "method".to_string(),
208 PatternExpr::Name(NameMatcher::Exact(method_name)),
209 );
210 }
211
212 if !mc.args.is_empty() {
214 let args_pattern = self.convert_args(&mc.args)?;
215 children.insert("args".to_string(), args_pattern);
216 }
217
218 pattern.children = children;
219 Ok(pattern)
220 }
221
222 fn convert_call(&self, call: &syn::ExprCall) -> ParseResult<CodePattern> {
224 let mut pattern = CodePattern::new(NodeKind::FunctionCall);
225 let mut children = HashMap::new();
226
227 if let Some(capture) = self.extract_metavar_from_expr(&call.func) {
229 children.insert(
230 "func".to_string(),
231 PatternExpr::Capture(to_metavar_name(&capture)),
232 );
233 } else {
234 let func_pattern = self.convert_expr(&call.func)?;
235 children.insert(
236 "func".to_string(),
237 PatternExpr::Pattern(Box::new(func_pattern)),
238 );
239 }
240
241 if !call.args.is_empty() {
243 let args_pattern = self.convert_args(&call.args)?;
244 children.insert("args".to_string(), args_pattern);
245 }
246
247 pattern.children = children;
248 Ok(pattern)
249 }
250
251 fn convert_macro(&self, mac: &syn::ExprMacro) -> ParseResult<CodePattern> {
253 let mut pattern = CodePattern::new(NodeKind::MacroCall);
254 let mut children = HashMap::new();
255
256 let macro_name = mac
258 .mac
259 .path
260 .segments
261 .last()
262 .map(|s| s.ident.to_string())
263 .unwrap_or_default();
264
265 children.insert(
266 "name".to_string(),
267 PatternExpr::Name(NameMatcher::Exact(macro_name)),
268 );
269
270 let tokens_str = mac.mac.tokens.to_string();
272 if let Some(ellipsis_var) = extract_ellipsis_var(&tokens_str) {
273 pattern.ellipsis = true;
274 pattern.capture = Some(ellipsis_var);
275 }
276
277 pattern.children = children;
278 Ok(pattern)
279 }
280
281 fn convert_path(&self, path: &syn::ExprPath) -> ParseResult<CodePattern> {
283 let path_str = path_to_string(&path.path);
284
285 if is_metavariable(&path_str) {
286 let mut pattern = CodePattern::new(NodeKind::Expr);
288 pattern.capture = Some(to_metavar_name(&path_str));
289 Ok(pattern)
290 } else {
291 let mut pattern = CodePattern::new(NodeKind::Path);
292 pattern.children.insert(
293 "path".to_string(),
294 PatternExpr::Name(NameMatcher::Exact(path_str)),
295 );
296 Ok(pattern)
297 }
298 }
299
300 fn convert_if(&self, if_expr: &syn::ExprIf) -> ParseResult<CodePattern> {
302 let mut pattern = CodePattern::new(NodeKind::If);
303 let mut children = HashMap::new();
304
305 if let Some(capture) = self.extract_metavar_from_expr(&if_expr.cond) {
307 children.insert(
308 "condition".to_string(),
309 PatternExpr::Capture(to_metavar_name(&capture)),
310 );
311 } else {
312 let cond_pattern = self.convert_expr(&if_expr.cond)?;
313 children.insert(
314 "condition".to_string(),
315 PatternExpr::Pattern(Box::new(cond_pattern)),
316 );
317 }
318
319 pattern.children = children;
320 Ok(pattern)
321 }
322
323 fn convert_match(&self, match_expr: &syn::ExprMatch) -> ParseResult<CodePattern> {
325 let mut pattern = CodePattern::new(NodeKind::Match);
326 let mut children = HashMap::new();
327
328 if let Some(capture) = self.extract_metavar_from_expr(&match_expr.expr) {
330 children.insert(
331 "scrutinee".to_string(),
332 PatternExpr::Capture(to_metavar_name(&capture)),
333 );
334 } else {
335 let scrutinee_pattern = self.convert_expr(&match_expr.expr)?;
336 children.insert(
337 "scrutinee".to_string(),
338 PatternExpr::Pattern(Box::new(scrutinee_pattern)),
339 );
340 }
341
342 children.insert(
344 "arms_count".to_string(),
345 PatternExpr::Literal(serde_json::json!(match_expr.arms.len())),
346 );
347
348 pattern.children = children;
349 Ok(pattern)
350 }
351
352 fn convert_block(&self, _block: &syn::ExprBlock) -> ParseResult<CodePattern> {
354 let pattern = CodePattern::new(NodeKind::Block);
355 Ok(pattern)
357 }
358
359 fn convert_binary(&self, bin: &syn::ExprBinary) -> ParseResult<CodePattern> {
361 let mut pattern = CodePattern::new(NodeKind::BinaryOp);
362 let mut children = HashMap::new();
363
364 if let Some(capture) = self.extract_metavar_from_expr(&bin.left) {
366 children.insert(
367 "left".to_string(),
368 PatternExpr::Capture(to_metavar_name(&capture)),
369 );
370 } else {
371 let left_pattern = self.convert_expr(&bin.left)?;
372 children.insert(
373 "left".to_string(),
374 PatternExpr::Pattern(Box::new(left_pattern)),
375 );
376 }
377
378 let op_str = binop_to_string(&bin.op);
380 children.insert(
381 "op".to_string(),
382 PatternExpr::Literal(serde_json::json!(op_str)),
383 );
384
385 if let Some(capture) = self.extract_metavar_from_expr(&bin.right) {
387 children.insert(
388 "right".to_string(),
389 PatternExpr::Capture(to_metavar_name(&capture)),
390 );
391 } else {
392 let right_pattern = self.convert_expr(&bin.right)?;
393 children.insert(
394 "right".to_string(),
395 PatternExpr::Pattern(Box::new(right_pattern)),
396 );
397 }
398
399 pattern.children = children;
400 Ok(pattern)
401 }
402
403 fn convert_unary(&self, unary: &syn::ExprUnary) -> ParseResult<CodePattern> {
405 let mut pattern = CodePattern::new(NodeKind::UnaryOp);
406 let mut children = HashMap::new();
407
408 if let Some(capture) = self.extract_metavar_from_expr(&unary.expr) {
410 children.insert(
411 "operand".to_string(),
412 PatternExpr::Capture(to_metavar_name(&capture)),
413 );
414 } else {
415 let operand_pattern = self.convert_expr(&unary.expr)?;
416 children.insert(
417 "operand".to_string(),
418 PatternExpr::Pattern(Box::new(operand_pattern)),
419 );
420 }
421
422 pattern.children = children;
423 Ok(pattern)
424 }
425
426 fn convert_literal(&self, lit: &syn::ExprLit) -> ParseResult<CodePattern> {
428 let mut pattern = CodePattern::new(NodeKind::Literal);
429
430 let value = match &lit.lit {
431 syn::Lit::Str(s) => serde_json::json!(s.value()),
432 syn::Lit::Int(i) => serde_json::json!(i.base10_parse::<i64>().unwrap_or(0)),
433 syn::Lit::Float(f) => serde_json::json!(f.base10_parse::<f64>().unwrap_or(0.0)),
434 syn::Lit::Bool(b) => serde_json::json!(b.value()),
435 syn::Lit::Char(c) => serde_json::json!(c.value().to_string()),
436 _ => serde_json::json!(null),
437 };
438
439 pattern
440 .children
441 .insert("value".to_string(), PatternExpr::Literal(value));
442 Ok(pattern)
443 }
444
445 fn convert_try(&self, try_expr: &syn::ExprTry) -> ParseResult<CodePattern> {
447 let mut pattern = CodePattern::new(NodeKind::Try);
448 let mut children = HashMap::new();
449
450 if let Some(capture) = self.extract_metavar_from_expr(&try_expr.expr) {
451 children.insert(
452 "expr".to_string(),
453 PatternExpr::Capture(to_metavar_name(&capture)),
454 );
455 } else {
456 let expr_pattern = self.convert_expr(&try_expr.expr)?;
457 children.insert(
458 "expr".to_string(),
459 PatternExpr::Pattern(Box::new(expr_pattern)),
460 );
461 }
462
463 pattern.children = children;
464 Ok(pattern)
465 }
466
467 fn convert_return(&self, ret: &syn::ExprReturn) -> ParseResult<CodePattern> {
469 let mut pattern = CodePattern::new(NodeKind::Return);
470
471 if let Some(expr) = &ret.expr {
472 if let Some(capture) = self.extract_metavar_from_expr(expr) {
473 pattern.children.insert(
474 "value".to_string(),
475 PatternExpr::Capture(to_metavar_name(&capture)),
476 );
477 } else {
478 let expr_pattern = self.convert_expr(expr)?;
479 pattern.children.insert(
480 "value".to_string(),
481 PatternExpr::Pattern(Box::new(expr_pattern)),
482 );
483 }
484 }
485
486 Ok(pattern)
487 }
488
489 fn convert_await(&self, await_expr: &syn::ExprAwait) -> ParseResult<CodePattern> {
491 let mut pattern = CodePattern::new(NodeKind::Await);
492 let mut children = HashMap::new();
493
494 if let Some(capture) = self.extract_metavar_from_expr(&await_expr.base) {
495 children.insert(
496 "base".to_string(),
497 PatternExpr::Capture(to_metavar_name(&capture)),
498 );
499 } else {
500 let base_pattern = self.convert_expr(&await_expr.base)?;
501 children.insert(
502 "base".to_string(),
503 PatternExpr::Pattern(Box::new(base_pattern)),
504 );
505 }
506
507 pattern.children = children;
508 Ok(pattern)
509 }
510
511 fn convert_closure(&self, _closure: &syn::ExprClosure) -> ParseResult<CodePattern> {
513 let pattern = CodePattern::new(NodeKind::Closure);
514 Ok(pattern)
516 }
517
518 fn convert_stmt(&self, stmt: &syn::Stmt) -> ParseResult<CodePattern> {
520 match stmt {
521 syn::Stmt::Expr(expr, _) => self.convert_expr(expr),
522 syn::Stmt::Local(local) => self.convert_local(local),
523 _ => Err(ParseError::UnsupportedSyntax(
524 "Unsupported statement type".to_string(),
525 )),
526 }
527 }
528
529 fn convert_local(&self, local: &syn::Local) -> ParseResult<CodePattern> {
531 let mut pattern = CodePattern::new(NodeKind::LetExpr);
532
533 if let syn::Pat::Ident(ident) = &local.pat {
535 let name = ident.ident.to_string();
536 if is_metavariable(&name) {
537 pattern.children.insert(
538 "pattern".to_string(),
539 PatternExpr::Capture(to_metavar_name(&name)),
540 );
541 }
542 }
543
544 if let Some(init) = &local.init {
546 if let Some(capture) = self.extract_metavar_from_expr(&init.expr) {
547 pattern.children.insert(
548 "init".to_string(),
549 PatternExpr::Capture(to_metavar_name(&capture)),
550 );
551 } else {
552 let init_pattern = self.convert_expr(&init.expr)?;
553 pattern.children.insert(
554 "init".to_string(),
555 PatternExpr::Pattern(Box::new(init_pattern)),
556 );
557 }
558 }
559
560 Ok(pattern)
561 }
562
563 fn convert_item(&self, item: &syn::Item) -> ParseResult<CodePattern> {
565 match item {
566 syn::Item::Fn(func) => self.convert_fn(func),
567 syn::Item::Struct(s) => self.convert_struct(s),
568 _ => Err(ParseError::UnsupportedSyntax(
569 "Unsupported item type".to_string(),
570 )),
571 }
572 }
573
574 fn convert_fn(&self, func: &syn::ItemFn) -> ParseResult<CodePattern> {
576 let mut pattern = CodePattern::new(NodeKind::Function);
577 let mut children = HashMap::new();
578
579 let name = func.sig.ident.to_string();
580 if is_metavariable(&name) {
581 children.insert(
582 "name".to_string(),
583 PatternExpr::Capture(to_metavar_name(&name)),
584 );
585 } else {
586 children.insert(
587 "name".to_string(),
588 PatternExpr::Name(NameMatcher::Exact(name)),
589 );
590 }
591
592 pattern.children = children;
593 Ok(pattern)
594 }
595
596 fn convert_struct(&self, s: &syn::ItemStruct) -> ParseResult<CodePattern> {
598 let mut pattern = CodePattern::new(NodeKind::Struct);
599 let mut children = HashMap::new();
600
601 let name = s.ident.to_string();
602 if is_metavariable(&name) {
603 children.insert(
604 "name".to_string(),
605 PatternExpr::Capture(to_metavar_name(&name)),
606 );
607 } else {
608 children.insert(
609 "name".to_string(),
610 PatternExpr::Name(NameMatcher::Exact(name)),
611 );
612 }
613
614 pattern.children = children;
615 Ok(pattern)
616 }
617
618 fn convert_args(
620 &self,
621 args: &syn::punctuated::Punctuated<syn::Expr, syn::token::Comma>,
622 ) -> ParseResult<PatternExpr> {
623 for arg in args {
625 if let Some(ellipsis_var) = self.extract_ellipsis_from_expr(arg) {
626 return Ok(PatternExpr::Capture(to_metavar_name(&ellipsis_var)));
627 }
628 }
629
630 let mut arg_patterns = Vec::new();
632 for arg in args {
633 if let Some(capture) = self.extract_metavar_from_expr(arg) {
634 arg_patterns.push(serde_json::json!({ "capture": to_metavar_name(&capture) }));
635 } else {
636 let pattern = self.convert_expr(arg)?;
637 arg_patterns.push(serde_json::to_value(&pattern).unwrap_or_default());
638 }
639 }
640
641 Ok(PatternExpr::Literal(serde_json::json!(arg_patterns)))
642 }
643
644 fn extract_metavar_from_expr(&self, expr: &syn::Expr) -> Option<String> {
646 if let syn::Expr::Path(path) = expr {
647 let path_str = path_to_string(&path.path);
648 if is_metavariable(&path_str) {
649 return Some(path_str);
650 }
651 }
652 None
653 }
654
655 fn extract_ellipsis_from_expr(&self, expr: &syn::Expr) -> Option<String> {
657 if let syn::Expr::Path(path) = expr {
658 let path_str = path_to_string(&path.path);
659 if is_ellipsis_metavariable(&path_str) {
660 return Some(path_str);
661 }
662 }
663 None
664 }
665}
666
667impl Default for ConcreteParser {
668 fn default() -> Self {
669 Self::new()
670 }
671}
672
673fn is_metavariable(s: &str) -> bool {
677 s.starts_with(METAVAR_PREFIX) || s.starts_with(ELLIPSIS_PREFIX)
678}
679
680fn is_ellipsis_metavariable(s: &str) -> bool {
682 s.starts_with(ELLIPSIS_PREFIX)
683}
684
685fn to_metavar_name(s: &str) -> String {
687 if let Some(stripped) = s.strip_prefix(ELLIPSIS_PREFIX) {
688 format!("$...{}", stripped)
689 } else if let Some(stripped) = s.strip_prefix(METAVAR_PREFIX) {
690 format!("${}", stripped)
691 } else {
692 s.to_string()
693 }
694}
695
696fn extract_ellipsis_var(tokens: &str) -> Option<String> {
698 let trimmed = tokens.trim();
700 if trimmed.starts_with(ELLIPSIS_PREFIX) {
701 Some(to_metavar_name(trimmed))
702 } else if trimmed.starts_with(METAVAR_PREFIX) {
703 Some(to_metavar_name(trimmed))
705 } else {
706 None
707 }
708}
709
710fn path_to_string(path: &syn::Path) -> String {
712 path.segments
713 .iter()
714 .map(|s| s.ident.to_string())
715 .collect::<Vec<_>>()
716 .join("::")
717}
718
719fn binop_to_string(op: &syn::BinOp) -> &'static str {
721 match op {
722 syn::BinOp::Add(_) => "+",
723 syn::BinOp::Sub(_) => "-",
724 syn::BinOp::Mul(_) => "*",
725 syn::BinOp::Div(_) => "/",
726 syn::BinOp::Rem(_) => "%",
727 syn::BinOp::And(_) => "&&",
728 syn::BinOp::Or(_) => "||",
729 syn::BinOp::BitXor(_) => "^",
730 syn::BinOp::BitAnd(_) => "&",
731 syn::BinOp::BitOr(_) => "|",
732 syn::BinOp::Shl(_) => "<<",
733 syn::BinOp::Shr(_) => ">>",
734 syn::BinOp::Eq(_) => "==",
735 syn::BinOp::Lt(_) => "<",
736 syn::BinOp::Le(_) => "<=",
737 syn::BinOp::Ne(_) => "!=",
738 syn::BinOp::Ge(_) => ">=",
739 syn::BinOp::Gt(_) => ">",
740 syn::BinOp::AddAssign(_) => "+=",
741 syn::BinOp::SubAssign(_) => "-=",
742 syn::BinOp::MulAssign(_) => "*=",
743 syn::BinOp::DivAssign(_) => "/=",
744 syn::BinOp::RemAssign(_) => "%=",
745 syn::BinOp::BitXorAssign(_) => "^=",
746 syn::BinOp::BitAndAssign(_) => "&=",
747 syn::BinOp::BitOrAssign(_) => "|=",
748 syn::BinOp::ShlAssign(_) => "<<=",
749 syn::BinOp::ShrAssign(_) => ">>=",
750 _ => "?",
751 }
752}
753
754#[cfg(test)]
755mod tests {
756 use super::*;
757
758 #[test]
759 fn test_preprocess() {
760 let parser = ConcreteParser::new();
761
762 assert_eq!(parser.preprocess("$var"), "__mv_var");
764 assert_eq!(
765 parser.preprocess("$receiver.unwrap()"),
766 "__mv_receiver.unwrap()"
767 );
768
769 assert_eq!(parser.preprocess("$...args"), "__ell_args");
771
772 assert_eq!(parser.preprocess("$a + $b"), "__mv_a + __mv_b");
774 }
775
776 #[test]
777 fn test_parse_method_call() {
778 let pattern = parse_pattern("$receiver.unwrap()").unwrap();
779 assert_eq!(pattern.node, NodeKind::MethodCall);
780 assert!(pattern.children.contains_key("receiver"));
781 assert!(pattern.children.contains_key("method"));
782
783 if let Some(PatternExpr::Capture(name)) = pattern.children.get("receiver") {
785 assert_eq!(name, "$receiver");
786 } else {
787 panic!("Expected receiver to be a capture");
788 }
789 }
790
791 #[test]
792 fn test_parse_method_call_with_args() {
793 let pattern = parse_pattern("$receiver.expect($msg)").unwrap();
794 assert_eq!(pattern.node, NodeKind::MethodCall);
795 assert!(pattern.children.contains_key("args"));
796 }
797
798 #[test]
799 fn test_parse_macro_call() {
800 let pattern = parse_pattern("panic!($msg)").unwrap();
801 assert_eq!(pattern.node, NodeKind::MacroCall);
802 assert!(pattern.children.contains_key("name"));
803 }
804
805 #[test]
806 fn test_parse_function_call() {
807 let pattern = parse_pattern("foo($a, $b)").unwrap();
808 assert_eq!(pattern.node, NodeKind::FunctionCall);
809 }
810
811 #[test]
812 fn test_parse_binary_op() {
813 let pattern = parse_pattern("$a + $b").unwrap();
814 assert_eq!(pattern.node, NodeKind::BinaryOp);
815 assert!(pattern.children.contains_key("left"));
816 assert!(pattern.children.contains_key("right"));
817
818 if let Some(PatternExpr::Capture(name)) = pattern.children.get("left") {
820 assert_eq!(name, "$a");
821 }
822 if let Some(PatternExpr::Capture(name)) = pattern.children.get("right") {
823 assert_eq!(name, "$b");
824 }
825 }
826
827 #[test]
828 fn test_parse_if_expr() {
829 let pattern = parse_pattern("if $cond { }").unwrap();
830 assert_eq!(pattern.node, NodeKind::If);
831 assert!(pattern.children.contains_key("condition"));
832 }
833
834 #[test]
835 fn test_parse_try_expr() {
836 let pattern = parse_pattern("$expr?").unwrap();
837 assert_eq!(pattern.node, NodeKind::Try);
838 }
839
840 #[test]
841 fn test_metavariable_detection() {
842 assert!(is_metavariable("__mv_VAR"));
844 assert!(is_metavariable("__mv_receiver"));
845 assert!(is_metavariable("__ell_args"));
846 assert!(!is_metavariable("var"));
847 }
848
849 #[test]
850 fn test_ellipsis_detection() {
851 assert!(is_ellipsis_metavariable("__ell_args"));
852 assert!(!is_ellipsis_metavariable("__mv_args"));
853 }
854
855 #[test]
856 fn test_to_metavar_name() {
857 assert_eq!(to_metavar_name("__mv_receiver"), "$receiver");
858 assert_eq!(to_metavar_name("__ell_args"), "$...args");
859 assert_eq!(to_metavar_name("normal"), "normal");
860 }
861
862 #[test]
863 fn test_parse_qualified_path() {
864 let pattern = parse_pattern("Filter::Recurse").unwrap();
865 assert_eq!(pattern.node, NodeKind::Path);
866 if let Some(PatternExpr::Name(NameMatcher::Exact(path))) = pattern.children.get("path") {
868 assert_eq!(path, "Filter::Recurse");
869 } else {
870 panic!(
871 "Expected Name(Exact(\"Filter::Recurse\")) under \"path\" key, got: {:?}",
872 pattern.children
873 );
874 }
875 }
876
877 #[test]
878 fn test_parse_simple_path() {
879 let pattern = parse_pattern("foo").unwrap();
880 assert_eq!(pattern.node, NodeKind::Path);
881 if let Some(PatternExpr::Name(NameMatcher::Exact(path))) = pattern.children.get("path") {
882 assert_eq!(path, "foo");
883 } else {
884 panic!("Expected Name(Exact(\"foo\")) under \"path\" key");
885 }
886 }
887}