1use ocas_atom::{Atom, AtomNode, Symbol};
9use ocas_core::FastHashMap as HashMap;
10
11use crate::pattern::{Pattern, WildcardLevel};
12
13pub const DEFAULT_MAX_BACKTRACKS: usize = 10_000;
15
16#[derive(Debug, Clone, Default)]
18pub struct Bindings<'a> {
19 map: HashMap<Symbol, MatchValue<'a>>,
20}
21
22impl<'a> Bindings<'a> {
23 pub fn new() -> Self {
24 Self::default()
25 }
26 pub fn get(&self, name: Symbol) -> Option<&MatchValue<'a>> {
27 self.map.get(&name)
28 }
29 fn insert_single(&mut self, name: Symbol, value: Atom<'a>) -> Result<(), MatchError> {
30 match self.map.get(&name) {
31 Some(MatchValue::Single(existing)) if *existing == value => Ok(()),
32 Some(_) => Err(MatchError::InconsistentBinding),
33 None => {
34 self.map.insert(name, MatchValue::Single(value));
35 Ok(())
36 }
37 }
38 }
39 fn insert_sequence(&mut self, name: Symbol, value: &'a [Atom<'a>]) -> Result<(), MatchError> {
40 match self.map.get(&name) {
41 Some(MatchValue::Sequence(existing)) if *existing == value => Ok(()),
42 Some(_) => Err(MatchError::InconsistentBinding),
43 None => {
44 self.map.insert(name, MatchValue::Sequence(value));
45 Ok(())
46 }
47 }
48 }
49 fn remove(&mut self, name: Symbol) {
50 self.map.remove(&name);
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum MatchValue<'a> {
56 Single(Atom<'a>),
57 Sequence(&'a [Atom<'a>]),
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum MatchError {
62 NoMatch,
63 InconsistentBinding,
64 BudgetExhausted,
65}
66
67impl std::fmt::Display for MatchError {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 match self {
70 MatchError::NoMatch => write!(f, "pattern did not match"),
71 MatchError::InconsistentBinding => write!(f, "inconsistent wildcard binding"),
72 MatchError::BudgetExhausted => write!(f, "backtrack budget exhausted"),
73 }
74 }
75}
76impl std::error::Error for MatchError {}
77
78pub fn match_pattern<'a>(pattern: Pattern<'a>, atom: Atom<'a>) -> Result<Bindings<'a>, MatchError> {
79 match_pattern_with_budget(pattern, atom, DEFAULT_MAX_BACKTRACKS)
80}
81
82pub fn match_pattern_with_budget<'a>(
83 pattern: Pattern<'a>,
84 atom: Atom<'a>,
85 max_backtracks: usize,
86) -> Result<Bindings<'a>, MatchError> {
87 let mut bindings = Bindings::new();
88 let mut backtrack_count = 0usize;
89 match_atom(
90 &mut bindings,
91 pattern,
92 atom,
93 &mut backtrack_count,
94 max_backtracks,
95 )?;
96 Ok(bindings)
97}
98
99fn match_atom<'a>(
100 bindings: &mut Bindings<'a>,
101 pattern: Pattern<'a>,
102 atom: Atom<'a>,
103 backtrack_count: &mut usize,
104 max_backtracks: usize,
105) -> Result<(), MatchError> {
106 if *backtrack_count >= max_backtracks {
107 return Err(MatchError::BudgetExhausted);
108 }
109 match pattern {
110 Pattern::Literal(p) => {
111 if p == atom {
112 Ok(())
113 } else {
114 Err(MatchError::NoMatch)
115 }
116 }
117 Pattern::Wildcard(name, WildcardLevel::Single) => bindings.insert_single(name, atom),
118 Pattern::Wildcard(name, WildcardLevel::Sequence) => match atom.node() {
119 AtomNode::Add(args) | AtomNode::Mul(args) | AtomNode::Fun(_, args) => {
120 if args.is_empty() {
121 Err(MatchError::NoMatch)
122 } else {
123 bindings.insert_sequence(name, args)
124 }
125 }
126 _ => Err(MatchError::NoMatch),
127 },
128 Pattern::Wildcard(name, WildcardLevel::NullSequence) => match atom.node() {
129 AtomNode::Add(args) | AtomNode::Mul(args) | AtomNode::Fun(_, args) => {
130 bindings.insert_sequence(name, args)
131 }
132 _ => Err(MatchError::NoMatch),
133 },
134 Pattern::Add(pats) => match atom.node() {
135 AtomNode::Add(args) => {
136 match_nary(bindings, &pats, args, true, backtrack_count, max_backtracks)
137 }
138 _ => Err(MatchError::NoMatch),
139 },
140 Pattern::Mul(pats) => match atom.node() {
141 AtomNode::Mul(args) => {
142 match_nary(bindings, &pats, args, true, backtrack_count, max_backtracks)
143 }
144 _ => Err(MatchError::NoMatch),
145 },
146 Pattern::Pow(p_box) => match atom.node() {
147 AtomNode::Pow(base, exp) => {
148 let (p_base, p_exp) = *p_box;
149 match_atom(bindings, p_base, *base, backtrack_count, max_backtracks)?;
150 match_atom(bindings, p_exp, *exp, backtrack_count, max_backtracks)
151 }
152 _ => Err(MatchError::NoMatch),
153 },
154 Pattern::Fun(p_name, p_args) => match atom.node() {
155 AtomNode::Fun(name, args) if *name == p_name => match_nary(
156 bindings,
157 &p_args,
158 args,
159 false,
160 backtrack_count,
161 max_backtracks,
162 ),
163 _ => Err(MatchError::NoMatch),
164 },
165 }
166}
167
168fn match_nary<'a>(
169 bindings: &'_ mut Bindings<'a>,
170 patterns: &[Pattern<'a>],
171 atoms: &'a [Atom<'a>],
172 associative_commutative: bool,
173 backtrack_count: &mut usize,
174 max_backtracks: usize,
175) -> Result<(), MatchError> {
176 if *backtrack_count >= max_backtracks {
177 return Err(MatchError::BudgetExhausted);
178 }
179 if patterns.is_empty() {
180 return if atoms.is_empty() {
181 Ok(())
182 } else {
183 Err(MatchError::NoMatch)
184 };
185 }
186 if associative_commutative {
187 let mut sorted: Vec<Atom<'a>> = atoms.to_vec();
188 sorted.sort();
189 let mut used = vec![false; sorted.len()];
190 match_nary_ac(
191 bindings,
192 patterns,
193 &sorted,
194 &mut used,
195 0,
196 backtrack_count,
197 max_backtracks,
198 )
199 } else {
200 match_nary_ordered(
201 bindings,
202 patterns,
203 atoms,
204 0,
205 0,
206 backtrack_count,
207 max_backtracks,
208 )
209 }
210}
211
212fn match_nary_ordered<'a>(
215 bindings: &mut Bindings<'a>,
216 patterns: &[Pattern<'a>],
217 atoms: &'a [Atom<'a>],
218 pat_idx: usize,
219 atom_idx: usize,
220 backtrack_count: &mut usize,
221 max_backtracks: usize,
222) -> Result<(), MatchError> {
223 if *backtrack_count >= max_backtracks {
224 return Err(MatchError::BudgetExhausted);
225 }
226 if pat_idx >= patterns.len() {
227 return if atom_idx >= atoms.len() {
228 Ok(())
229 } else {
230 Err(MatchError::NoMatch)
231 };
232 }
233 let pat = &patterns[pat_idx];
234 match pat {
235 Pattern::Wildcard(name, WildcardLevel::NullSequence) => {
236 let remaining = atoms.len().saturating_sub(atom_idx);
237 for len in 0..=remaining {
238 let slice = &atoms[atom_idx..atom_idx + len];
239 let mut probe = bindings.clone();
240 if probe.insert_sequence(*name, slice).is_err() {
241 continue;
242 }
243 *backtrack_count += 1;
244 match match_nary_ordered(
245 &mut probe,
246 patterns,
247 atoms,
248 pat_idx + 1,
249 atom_idx + len,
250 backtrack_count,
251 max_backtracks,
252 ) {
253 Ok(()) => {
254 *bindings = probe;
255 return Ok(());
256 }
257 Err(MatchError::BudgetExhausted) => return Err(MatchError::BudgetExhausted),
258 Err(_) => {}
259 }
260 }
261 Err(MatchError::NoMatch)
262 }
263 Pattern::Wildcard(name, WildcardLevel::Sequence) => {
264 let remaining = atoms.len().saturating_sub(atom_idx);
265 if remaining == 0 {
266 return Err(MatchError::NoMatch);
267 }
268 for len in 1..=remaining {
269 let slice = &atoms[atom_idx..atom_idx + len];
270 let mut probe = bindings.clone();
271 if probe.insert_sequence(*name, slice).is_err() {
272 continue;
273 }
274 *backtrack_count += 1;
275 match match_nary_ordered(
276 &mut probe,
277 patterns,
278 atoms,
279 pat_idx + 1,
280 atom_idx + len,
281 backtrack_count,
282 max_backtracks,
283 ) {
284 Ok(()) => {
285 *bindings = probe;
286 return Ok(());
287 }
288 Err(MatchError::BudgetExhausted) => return Err(MatchError::BudgetExhausted),
289 Err(_) => {}
290 }
291 }
292 Err(MatchError::NoMatch)
293 }
294 Pattern::Wildcard(name, WildcardLevel::Single) => {
295 if atom_idx >= atoms.len() {
296 return Err(MatchError::NoMatch);
297 }
298 let atom = atoms[atom_idx];
299 let mut probe = bindings.clone();
300 if probe.insert_single(*name, atom).is_err() {
301 return Err(MatchError::NoMatch);
302 }
303 *backtrack_count += 1;
304 match_nary_ordered(
305 &mut probe,
306 patterns,
307 atoms,
308 pat_idx + 1,
309 atom_idx + 1,
310 backtrack_count,
311 max_backtracks,
312 )
313 .map(|()| {
314 *bindings = probe;
315 })
316 }
317 _ => {
318 if atom_idx >= atoms.len() {
319 return Err(MatchError::NoMatch);
320 }
321 let mut probe = bindings.clone();
322 match_atom(
323 &mut probe,
324 pat.clone(),
325 atoms[atom_idx],
326 backtrack_count,
327 max_backtracks,
328 )?;
329 *backtrack_count += 1;
330 match_nary_ordered(
331 &mut probe,
332 patterns,
333 atoms,
334 pat_idx + 1,
335 atom_idx + 1,
336 backtrack_count,
337 max_backtracks,
338 )
339 .map(|()| {
340 *bindings = probe;
341 })
342 }
343 }
344}
345
346fn match_nary_ac<'a>(
349 bindings: &mut Bindings<'a>,
350 patterns: &[Pattern<'a>],
351 sorted_atoms: &[Atom<'a>],
352 used: &mut [bool],
353 pat_idx: usize,
354 backtrack_count: &mut usize,
355 max_backtracks: usize,
356) -> Result<(), MatchError> {
357 if *backtrack_count >= max_backtracks {
358 return Err(MatchError::BudgetExhausted);
359 }
360 if pat_idx >= patterns.len() {
361 return if used.iter().all(|&u| u) {
362 Ok(())
363 } else {
364 Err(MatchError::NoMatch)
365 };
366 }
367
368 let pat = &patterns[pat_idx];
369 match pat {
370 Pattern::Literal(v) => {
371 for i in 0..sorted_atoms.len() {
372 if used[i] || sorted_atoms[i] != *v {
373 continue;
374 }
375 used[i] = true;
376 *backtrack_count += 1;
377 match match_nary_ac(
378 bindings,
379 patterns,
380 sorted_atoms,
381 used,
382 pat_idx + 1,
383 backtrack_count,
384 max_backtracks,
385 ) {
386 Ok(()) => return Ok(()),
387 Err(MatchError::BudgetExhausted) => return Err(MatchError::BudgetExhausted),
388 Err(_) => {
389 used[i] = false;
390 }
391 }
392 }
393 Err(MatchError::NoMatch)
394 }
395 Pattern::Wildcard(name, WildcardLevel::Single) => {
396 for i in 0..sorted_atoms.len() {
397 if used[i] {
398 continue;
399 }
400 let atom = sorted_atoms[i];
401 match bindings.get(*name).copied() {
402 Some(MatchValue::Single(existing)) if existing == atom => {
403 used[i] = true;
404 match match_nary_ac(
405 bindings,
406 patterns,
407 sorted_atoms,
408 used,
409 pat_idx + 1,
410 backtrack_count,
411 max_backtracks,
412 ) {
413 Ok(()) => return Ok(()),
414 Err(MatchError::BudgetExhausted) => {
415 used[i] = false;
416 return Err(MatchError::BudgetExhausted);
417 }
418 Err(_) => {
419 used[i] = false;
420 }
421 }
422 }
423 Some(_) => continue,
424 None => {
425 used[i] = true;
426 let _ = bindings.insert_single(*name, atom);
427 *backtrack_count += 1;
428 match match_nary_ac(
429 bindings,
430 patterns,
431 sorted_atoms,
432 used,
433 pat_idx + 1,
434 backtrack_count,
435 max_backtracks,
436 ) {
437 Ok(()) => return Ok(()),
438 Err(MatchError::BudgetExhausted) => {
439 bindings.remove(*name);
440 used[i] = false;
441 return Err(MatchError::BudgetExhausted);
442 }
443 Err(_) => {
444 bindings.remove(*name);
445 used[i] = false;
446 }
447 }
448 }
449 }
450 }
451 Err(MatchError::NoMatch)
452 }
453 Pattern::Wildcard(name, WildcardLevel::NullSequence) => {
454 let free: Vec<usize> = (0..sorted_atoms.len()).filter(|&i| !used[i]).collect();
455 enumerate_subsets(
456 bindings,
457 patterns,
458 sorted_atoms,
459 used,
460 pat_idx,
461 *name,
462 &free,
463 0,
464 free.len(),
465 0,
466 true,
467 backtrack_count,
468 max_backtracks,
469 )
470 }
471 Pattern::Wildcard(name, WildcardLevel::Sequence) => {
472 let free: Vec<usize> = (0..sorted_atoms.len()).filter(|&i| !used[i]).collect();
473 if free.is_empty() {
474 return Err(MatchError::NoMatch);
475 }
476 enumerate_subsets(
477 bindings,
478 patterns,
479 sorted_atoms,
480 used,
481 pat_idx,
482 *name,
483 &free,
484 0,
485 free.len(),
486 1,
487 false,
488 backtrack_count,
489 max_backtracks,
490 )
491 }
492 _ => {
493 for i in 0..sorted_atoms.len() {
494 if used[i] {
495 continue;
496 }
497 let mut probe = bindings.clone();
498 if let Ok(()) = match_atom(
499 &mut probe,
500 pat.clone(),
501 sorted_atoms[i],
502 backtrack_count,
503 max_backtracks,
504 ) {
505 used[i] = true;
506 *backtrack_count += 1;
507 match match_nary_ac(
508 &mut probe,
509 patterns,
510 sorted_atoms,
511 used,
512 pat_idx + 1,
513 backtrack_count,
514 max_backtracks,
515 ) {
516 Ok(()) => {
517 *bindings = probe;
518 return Ok(());
519 }
520 Err(MatchError::BudgetExhausted) => {
521 used[i] = false;
522 return Err(MatchError::BudgetExhausted);
523 }
524 Err(_) => {
525 used[i] = false;
526 }
527 }
528 }
529 }
530 Err(MatchError::NoMatch)
531 }
532 }
533}
534
535#[allow(clippy::too_many_arguments)]
536fn enumerate_subsets<'a>(
537 bindings: &mut Bindings<'a>,
538 patterns: &[Pattern<'a>],
539 sorted_atoms: &[Atom<'a>],
540 used: &mut [bool],
541 pat_idx: usize,
542 name: Symbol,
543 free: &[usize],
544 start: usize,
545 total_free: usize,
546 min_size: usize,
547 allow_empty: bool,
548 backtrack_count: &mut usize,
549 max_backtracks: usize,
550) -> Result<(), MatchError> {
551 if *backtrack_count >= max_backtracks {
552 return Err(MatchError::BudgetExhausted);
553 }
554 let rem_pats = patterns.len().saturating_sub(pat_idx + 1);
555 let max_size = total_free
556 .saturating_sub(start)
557 .saturating_sub(rem_pats)
558 .min(total_free.saturating_sub(start));
559 let min = if allow_empty { 0 } else { min_size };
560 for size in min..=max_size {
561 let mut chosen: Vec<usize> = Vec::with_capacity(size);
562 let result = enumerate_combinations_step(
563 bindings,
564 patterns,
565 sorted_atoms,
566 used,
567 pat_idx,
568 name,
569 free,
570 start,
571 size,
572 0,
573 &mut chosen,
574 backtrack_count,
575 max_backtracks,
576 );
577 match result {
578 SubsetResult::Found => return Ok(()),
579 SubsetResult::BudgetExhausted => return Err(MatchError::BudgetExhausted),
580 SubsetResult::NoMatch => {}
581 }
582 }
583 Err(MatchError::NoMatch)
584}
585
586enum SubsetResult {
587 Found,
588 NoMatch,
589 BudgetExhausted,
590}
591
592#[allow(clippy::too_many_arguments)]
593fn enumerate_combinations_step<'a>(
594 bindings: &mut Bindings<'a>,
595 patterns: &[Pattern<'a>],
596 sorted_atoms: &[Atom<'a>],
597 used: &mut [bool],
598 pat_idx: usize,
599 name: Symbol,
600 free: &[usize],
601 start: usize,
602 remaining: usize,
603 _depth: usize,
604 chosen: &mut Vec<usize>,
605 backtrack_count: &mut usize,
606 max_backtracks: usize,
607) -> SubsetResult {
608 if *backtrack_count >= max_backtracks {
609 return SubsetResult::BudgetExhausted;
610 }
611 if remaining == 0 {
612 for &idx in chosen.iter() {
613 used[idx] = true;
614 }
615 let slice: Vec<Atom<'a>> = chosen.iter().map(|&i| sorted_atoms[i]).collect();
616 let leaked: &'a [Atom<'a>] = Vec::leak(slice);
617 let mut probe = bindings.clone();
618 if let Ok(()) = probe.insert_sequence(name, leaked) {
619 *backtrack_count += 1;
620 match match_nary_ac(
621 &mut probe,
622 patterns,
623 sorted_atoms,
624 used,
625 pat_idx + 1,
626 backtrack_count,
627 max_backtracks,
628 ) {
629 Ok(()) => {
630 *bindings = probe;
631 return SubsetResult::Found;
632 }
633 Err(MatchError::BudgetExhausted) => {
634 for &idx in chosen.iter() {
635 used[idx] = false;
636 }
637 return SubsetResult::BudgetExhausted;
638 }
639 Err(_) => {}
640 }
641 }
642 for &idx in chosen.iter() {
643 used[idx] = false;
644 }
645 return SubsetResult::NoMatch;
646 }
647 let needed = remaining;
648 let available = free.len().saturating_sub(start);
649 if available < needed {
650 return SubsetResult::NoMatch;
651 }
652 for i in start..=free.len().saturating_sub(needed) {
653 chosen.push(free[i]);
654 match enumerate_combinations_step(
655 bindings,
656 patterns,
657 sorted_atoms,
658 used,
659 pat_idx,
660 name,
661 free,
662 i + 1,
663 remaining - 1,
664 _depth + 1,
665 chosen,
666 backtrack_count,
667 max_backtracks,
668 ) {
669 SubsetResult::Found => return SubsetResult::Found,
670 SubsetResult::BudgetExhausted => {
671 chosen.pop();
672 return SubsetResult::BudgetExhausted;
673 }
674 SubsetResult::NoMatch => {}
675 }
676 chosen.pop();
677 }
678 SubsetResult::NoMatch
679}
680
681#[cfg(test)]
686mod tests {
687 use super::*;
688 use ocas_atom::AtomArena;
689 use ocas_core::arena::Arena;
690
691 struct VecAlloc;
692 impl<'a> crate::pattern::PatternAlloc<'a> for VecAlloc {
693 fn alloc_slice(&self, items: &[Pattern<'a>]) -> &'a [Pattern<'a>] {
694 Box::leak(items.to_vec().into_boxed_slice())
695 }
696 }
697
698 fn pat_expr<'a>(ctx: &'a AtomArena<'a>, _alloc: &'a VecAlloc, s: &'a str) -> Pattern<'a> {
699 use ocas_parse;
700 let atom = ocas_parse::parse(ctx, s).expect("parse");
701 Pattern::from_atom(&(), atom)
702 }
703
704 #[test]
705 fn match_single_wildcard() {
706 let arena = Arena::new();
707 let ctx = AtomArena::new(&arena);
708 let x = ctx.var("x");
709 let pat = Pattern::Wildcard(Symbol::new("w"), WildcardLevel::Single);
710 let bindings = match_pattern(pat, x).unwrap();
711 assert!(matches!(bindings.get(Symbol::new("w")), Some(MatchValue::Single(v)) if *v == x));
712 }
713
714 #[test]
715 fn match_add_two_singles() {
716 let arena = Arena::new();
717 let ctx = AtomArena::new(&arena);
718 let x = ctx.var("x");
719 let y = ctx.var("y");
720 let sum = ctx.add(&[x, y]);
721 let pat = Pattern::Add(vec![
722 Pattern::Wildcard(Symbol::new("a"), WildcardLevel::Single),
723 Pattern::Wildcard(Symbol::new("b"), WildcardLevel::Single),
724 ]);
725 let bindings = match_pattern(pat, sum).unwrap();
726 assert!(matches!(bindings.get(Symbol::new("a")), Some(MatchValue::Single(v)) if *v == x));
727 assert!(matches!(bindings.get(Symbol::new("b")), Some(MatchValue::Single(v)) if *v == y));
728 }
729
730 #[test]
731 fn ac_match_with_literal() {
732 let arena = Arena::new();
733 let ctx = AtomArena::new(&arena);
734 let x = ctx.var("x");
735 let y = ctx.var("y");
736 let sum = ctx.add(&[x, y]);
737 let pat = Pattern::Add(vec![
738 Pattern::Wildcard(Symbol::new("a"), WildcardLevel::Single),
739 Pattern::Literal(y),
740 ]);
741 let bindings = match_pattern(pat, sum).unwrap();
742 assert!(matches!(bindings.get(Symbol::new("a")), Some(MatchValue::Single(v)) if *v == x));
743 }
744
745 #[test]
746 fn ac_mismatched_count_fails() {
747 let arena = Arena::new();
748 let ctx = AtomArena::new(&arena);
749 let x = ctx.var("x");
750 let y = ctx.var("y");
751 let z = ctx.var("z");
752 let sum = ctx.add(&[x, y, z]);
753 let pat = Pattern::Add(vec![
754 Pattern::Wildcard(Symbol::new("a"), WildcardLevel::Single),
755 Pattern::Wildcard(Symbol::new("b"), WildcardLevel::Single),
756 ]);
757 assert!(matches!(match_pattern(pat, sum), Err(MatchError::NoMatch)));
758 }
759
760 #[test]
761 fn inconsistent_binding_fails() {
762 let arena = Arena::new();
763 let ctx = AtomArena::new(&arena);
764 let x = ctx.var("x");
765 let y = ctx.var("y");
766 let sum = ctx.add(&[x, y]);
767 let pat = Pattern::Add(vec![
768 Pattern::Wildcard(Symbol::new("w"), WildcardLevel::Single),
769 Pattern::Wildcard(Symbol::new("w"), WildcardLevel::Single),
770 ]);
771 assert!(matches!(
772 match_pattern(pat, sum),
773 Err(MatchError::InconsistentBinding | MatchError::NoMatch)
774 ));
775 }
776
777 #[test]
778 fn fun_trailing_null_sequence() {
779 let arena = Arena::new();
780 let ctx = AtomArena::new(&arena);
781 let x = ctx.var("x");
782 let y = ctx.var("y");
783 let z = ctx.var("z");
784 let f = ctx.fun("f", &[x, y, z]);
785 let alloc = VecAlloc;
786 let pattern_str = "f(x_, ___rest)";
787 let pat = pat_expr(&ctx, &alloc, pattern_str);
788 let bindings = match_pattern(pat, f).unwrap();
789 let rest = bindings.get(Symbol::new("rest")).unwrap();
790 assert!(matches!(rest, MatchValue::Sequence(s) if s.len() == 2));
791 }
792
793 #[test]
794 fn ac_three_wildcards_four_terms() {
795 let arena = Arena::new();
796 let ctx = AtomArena::new(&arena);
797 let w = ctx.var("w");
798 let x = ctx.var("x");
799 let y = ctx.var("y");
800 let z = ctx.var("z");
801 let sum = ctx.add(&[w, x, y, z]);
802 let pat = Pattern::Add(vec![
803 Pattern::Wildcard(Symbol::new("a"), WildcardLevel::Single),
804 Pattern::Wildcard(Symbol::new("b"), WildcardLevel::Single),
805 Pattern::Wildcard(Symbol::new("c"), WildcardLevel::Single),
806 ]);
807 assert!(matches!(match_pattern(pat, sum), Err(MatchError::NoMatch)));
808 }
809
810 #[test]
811 fn ac_sequence_wildcard_in_add() {
812 let arena = Arena::new();
813 let ctx = AtomArena::new(&arena);
814 let a = ctx.var("a");
815 let b = ctx.var("b");
816 let c = ctx.var("c");
817 let sum = ctx.add(&[a, b, c]);
818 let pat = Pattern::Add(vec![
819 Pattern::Wildcard(Symbol::new("x"), WildcardLevel::Single),
820 Pattern::Wildcard(Symbol::new("rest"), WildcardLevel::Sequence),
821 ]);
822 let bindings = match_pattern(pat, sum).unwrap();
823 let rest = bindings.get(Symbol::new("rest")).unwrap();
824 assert!(matches!(rest, MatchValue::Sequence(s) if s.len() == 2));
825 }
826
827 #[test]
828 fn ac_null_sequence_consumes_all() {
829 let arena = Arena::new();
830 let ctx = AtomArena::new(&arena);
831 let a = ctx.var("a");
832 let sum = ctx.add(&[a]);
833 let pat = Pattern::Add(vec![Pattern::Wildcard(
834 Symbol::new("rest"),
835 WildcardLevel::NullSequence,
836 )]);
837 let bindings = match_pattern(pat, sum).unwrap();
838 let rest = bindings.get(Symbol::new("rest")).unwrap();
839 assert!(matches!(rest, MatchValue::Sequence(s) if s.len() == 1));
840 }
841
842 #[test]
843 fn ordered_sequence_mid_function() {
844 let arena = Arena::new();
845 let ctx = AtomArena::new(&arena);
846 let a = ctx.var("a");
847 let b = ctx.var("b");
848 let c = ctx.var("c");
849 let f = ctx.fun("f", &[a, b, c]);
850 let alloc = VecAlloc;
851 let pattern_str = "f(x_, __mid, z_)";
852 let pat = pat_expr(&ctx, &alloc, pattern_str);
853 let bindings = match_pattern(pat, f).unwrap();
854 let mid = bindings.get(Symbol::new("mid")).unwrap();
855 assert!(matches!(mid, MatchValue::Sequence(s) if s.len() == 1));
856 }
857}