1use std::collections::HashMap;
9
10use ocas_atom::{Atom, AtomNode, Symbol};
11
12use crate::pattern::{Pattern, WildcardLevel};
13
14#[derive(Debug, Clone, Default)]
34pub struct Bindings<'a> {
35 map: HashMap<Symbol, MatchValue<'a>>,
36}
37
38impl<'a> Bindings<'a> {
39 pub fn new() -> Self {
41 Self::default()
42 }
43
44 pub fn get(&self, name: Symbol) -> Option<&MatchValue<'a>> {
46 self.map.get(&name)
47 }
48
49 fn insert_single(&mut self, name: Symbol, value: Atom<'a>) -> Result<(), MatchError> {
52 match self.map.get(&name) {
53 Some(MatchValue::Single(existing)) if *existing == value => Ok(()),
54 Some(_) => Err(MatchError::InconsistentBinding),
55 None => {
56 self.map.insert(name, MatchValue::Single(value));
57 Ok(())
58 }
59 }
60 }
61
62 fn insert_sequence(&mut self, name: Symbol, value: &'a [Atom<'a>]) -> Result<(), MatchError> {
64 match self.map.get(&name) {
65 Some(MatchValue::Sequence(existing)) if *existing == value => Ok(()),
66 Some(_) => Err(MatchError::InconsistentBinding),
67 None => {
68 self.map.insert(name, MatchValue::Sequence(value));
69 Ok(())
70 }
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum MatchValue<'a> {
78 Single(Atom<'a>),
80 Sequence(&'a [Atom<'a>]),
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum MatchError {
105 NoMatch,
107 InconsistentBinding,
109}
110
111impl std::fmt::Display for MatchError {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 MatchError::NoMatch => write!(f, "pattern did not match"),
115 MatchError::InconsistentBinding => write!(f, "inconsistent wildcard binding"),
116 }
117 }
118}
119
120impl std::error::Error for MatchError {}
121
122pub fn match_pattern<'a>(pattern: Pattern<'a>, atom: Atom<'a>) -> Result<Bindings<'a>, MatchError> {
154 let mut bindings = Bindings::new();
155 match_atom(&mut bindings, pattern, atom)?;
156 Ok(bindings)
157}
158
159fn match_atom<'a>(
160 bindings: &mut Bindings<'a>,
161 pattern: Pattern<'a>,
162 atom: Atom<'a>,
163) -> Result<(), MatchError> {
164 match pattern {
165 Pattern::Literal(p) => {
166 if p == atom {
167 Ok(())
168 } else {
169 Err(MatchError::NoMatch)
170 }
171 }
172 Pattern::Wildcard(name, WildcardLevel::Single) => bindings.insert_single(name, atom),
173 Pattern::Wildcard(name, WildcardLevel::Sequence) => match atom.node() {
174 AtomNode::Add(args) | AtomNode::Mul(args) | AtomNode::Fun(_, args) => {
175 if args.is_empty() {
176 Err(MatchError::NoMatch)
177 } else {
178 bindings.insert_sequence(name, args)
179 }
180 }
181 _ => Err(MatchError::NoMatch),
182 },
183 Pattern::Wildcard(name, WildcardLevel::NullSequence) => match atom.node() {
184 AtomNode::Add(args) | AtomNode::Mul(args) | AtomNode::Fun(_, args) => {
185 bindings.insert_sequence(name, args)
186 }
187 _ => Err(MatchError::NoMatch),
188 },
189 Pattern::Add(pats) => match atom.node() {
190 AtomNode::Add(args) => match_nary(bindings, &pats, args, true),
191 _ => Err(MatchError::NoMatch),
192 },
193 Pattern::Mul(pats) => match atom.node() {
194 AtomNode::Mul(args) => match_nary(bindings, &pats, args, true),
195 _ => Err(MatchError::NoMatch),
196 },
197 Pattern::Pow(p_box) => match atom.node() {
198 AtomNode::Pow(base, exp) => {
199 let (p_base, p_exp) = *p_box;
200 match_atom(bindings, p_base, *base)?;
201 match_atom(bindings, p_exp, *exp)
202 }
203 _ => Err(MatchError::NoMatch),
204 },
205 Pattern::Fun(p_name, p_args) => match atom.node() {
206 AtomNode::Fun(name, args) if *name == p_name => {
207 match_nary(bindings, &p_args, args, false)
208 }
209 _ => Err(MatchError::NoMatch),
210 },
211 }
212}
213
214fn match_nary<'a>(
219 bindings: &mut Bindings<'a>,
220 patterns: &'_ [Pattern<'a>],
221 atoms: &'a [Atom<'a>],
222 associative_commutative: bool,
223) -> Result<(), MatchError> {
224 if associative_commutative {
225 match_nary_ac(bindings, patterns, atoms)
226 } else {
227 match_nary_ordered(bindings, patterns, atoms)
228 }
229}
230
231fn match_nary_ordered<'a>(
234 bindings: &mut Bindings<'a>,
235 patterns: &'_ [Pattern<'a>],
236 atoms: &'a [Atom<'a>],
237) -> Result<(), MatchError> {
238 let mut pat_idx = 0;
239 let mut atom_idx = 0;
240 while pat_idx < patterns.len() {
241 let pat = &patterns[pat_idx];
242 match pat {
243 Pattern::Wildcard(name, WildcardLevel::NullSequence)
244 if pat_idx == patterns.len() - 1 =>
245 {
246 let rest = &atoms[atom_idx..];
247 bindings.insert_sequence(*name, rest)?;
248 atom_idx = atoms.len();
249 pat_idx += 1;
250 }
251 Pattern::Wildcard(name, WildcardLevel::Single) => {
252 if atom_idx >= atoms.len() {
253 return Err(MatchError::NoMatch);
254 }
255 bindings.insert_single(*name, atoms[atom_idx])?;
256 atom_idx += 1;
257 pat_idx += 1;
258 }
259 _ => {
260 if atom_idx >= atoms.len() {
261 return Err(MatchError::NoMatch);
262 }
263 match_atom(bindings, pat.clone(), atoms[atom_idx])?;
264 atom_idx += 1;
265 pat_idx += 1;
266 }
267 }
268 }
269 if atom_idx == atoms.len() {
270 Ok(())
271 } else {
272 Err(MatchError::NoMatch)
273 }
274}
275
276fn match_nary_ac<'a>(
282 bindings: &mut Bindings<'a>,
283 patterns: &'_ [Pattern<'a>],
284 atoms: &'a [Atom<'a>],
285) -> Result<(), MatchError> {
286 if patterns.is_empty() {
287 return if atoms.is_empty() {
288 Ok(())
289 } else {
290 Err(MatchError::NoMatch)
291 };
292 }
293
294 if patterns.iter().any(|p| {
295 matches!(
296 p,
297 Pattern::Wildcard(_, WildcardLevel::Sequence | WildcardLevel::NullSequence)
298 )
299 }) {
300 return Err(MatchError::NoMatch);
301 }
302
303 let mut sorted_atoms: Vec<Atom<'a>> = atoms.to_vec();
304 sorted_atoms.sort();
305
306 let mut matched = vec![false; sorted_atoms.len()];
307 let mut single_wildcards: Vec<usize> = Vec::new();
308 let mut literal_indices: Vec<usize> = Vec::new();
309
310 for (i, pat) in patterns.iter().enumerate() {
311 match pat {
312 Pattern::Wildcard(_, WildcardLevel::Single) => single_wildcards.push(i),
313 _ => literal_indices.push(i),
314 }
315 }
316
317 for pat_idx in literal_indices {
319 let pat = &patterns[pat_idx];
320 let mut found = false;
321 for (i, atom) in sorted_atoms.iter().enumerate() {
322 if matched[i] {
323 continue;
324 }
325 let mut probe = bindings.clone();
326 if match_atom(&mut probe, pat.clone(), *atom).is_ok() {
327 *bindings = probe;
328 matched[i] = true;
329 found = true;
330 break;
331 }
332 }
333 if !found {
334 return Err(MatchError::NoMatch);
335 }
336 }
337
338 let remaining: Vec<Atom<'a>> = sorted_atoms
340 .iter()
341 .enumerate()
342 .filter(|(i, _)| !matched[*i])
343 .map(|(_, a)| *a)
344 .collect();
345 if remaining.len() != single_wildcards.len() {
346 return Err(MatchError::NoMatch);
347 }
348 for (pat_idx, atom) in single_wildcards.iter().zip(remaining.iter()) {
349 if let Pattern::Wildcard(name, WildcardLevel::Single) = &patterns[*pat_idx] {
350 bindings.insert_single(*name, *atom)?;
351 }
352 }
353
354 Ok(())
355}
356
357impl<'a> Pattern<'a> {
358 fn _wildcard_level(&self) -> Option<WildcardLevel> {
359 match self {
360 Pattern::Wildcard(_, level) => Some(*level),
361 _ => None,
362 }
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use ocas_atom::AtomArena;
370 use ocas_core::arena::Arena;
371
372 struct VecAlloc;
373
374 impl<'a> crate::pattern::PatternAlloc<'a> for VecAlloc {
375 fn alloc_slice(&self, items: &[Pattern<'a>]) -> &'a [Pattern<'a>] {
376 let leaked: Box<[Pattern<'a>]> = items.to_vec().into_boxed_slice();
377 Box::leak(leaked)
378 }
379 }
380
381 fn pat_atom<'a>(ctx: &'a AtomArena<'a>, alloc: &'a VecAlloc, s: &'a str) -> Pattern<'a> {
382 let atom = ocas_parse::parse(ctx, s).unwrap();
383 Pattern::from_atom(alloc, atom)
384 }
385
386 #[test]
387 fn single_wildcard_binds_any_atom() {
388 let arena = Arena::new();
389 let ctx = AtomArena::new(&arena);
390 let alloc = VecAlloc;
391 let pat = pat_atom(&ctx, &alloc, "x_");
392 let y = ctx.var("y");
393 let bindings = match_pattern(pat, y).unwrap();
394 assert!(matches!(bindings.get(Symbol::new("x")), Some(MatchValue::Single(a)) if *a == y));
395 }
396
397 #[test]
398 fn add_pattern_matches_two_atoms() {
399 let arena = Arena::new();
400 let ctx = AtomArena::new(&arena);
401 let alloc = VecAlloc;
402 let pat = pat_atom(&ctx, &alloc, "x_ + y_");
403 let x = ctx.var("x");
404 let y = ctx.var("y");
405 let sum = ctx.add(&[x, y]);
406 let bindings = match_pattern(pat, sum).unwrap();
407 assert!(matches!(
408 bindings.get(Symbol::new("x")),
409 Some(MatchValue::Single(a)) if *a == x
410 ));
411 assert!(matches!(
412 bindings.get(Symbol::new("y")),
413 Some(MatchValue::Single(a)) if *a == y
414 ));
415 }
416
417 #[test]
418 fn add_pattern_ignores_order() {
419 let arena = Arena::new();
420 let ctx = AtomArena::new(&arena);
421 let alloc = VecAlloc;
422 let pat = pat_atom(&ctx, &alloc, "x_ + y_");
423 let a = ctx.var("a");
424 let b = ctx.var("b");
425 let sum = ctx.add(&[b, a]);
426 let bindings = match_pattern(pat, sum).unwrap();
427 assert!(matches!(
428 bindings.get(Symbol::new("x")),
429 Some(MatchValue::Single(atom)) if *atom == a
430 ));
431 assert!(matches!(
432 bindings.get(Symbol::new("y")),
433 Some(MatchValue::Single(atom)) if *atom == b
434 ));
435 }
436
437 #[test]
438 fn add_pattern_requires_exact_match_count() {
439 let arena = Arena::new();
440 let ctx = AtomArena::new(&arena);
441 let alloc = VecAlloc;
442 let pat = pat_atom(&ctx, &alloc, "x_ + y_");
443 let a = ctx.var("a");
444 let b = ctx.var("b");
445 let c = ctx.var("c");
446 let sum = ctx.add(&[a, b, c]);
447 assert!(match_pattern(pat, sum).is_err());
448 }
449
450 #[test]
451 fn inconsistent_binding_fails() {
452 let arena = Arena::new();
453 let ctx = AtomArena::new(&arena);
454 let alloc = VecAlloc;
455 let pat = pat_atom(&ctx, &alloc, "x_ + x_");
456 let a = ctx.var("a");
457 let b = ctx.var("b");
458 let sum = ctx.add(&[a, b]);
459 assert!(match_pattern(pat, sum).is_err());
460 }
461
462 #[test]
463 fn sequence_wildcard_in_fun_absorbs_remainder() {
464 let arena = Arena::new();
465 let ctx = AtomArena::new(&arena);
466 let alloc = VecAlloc;
467 let pat = pat_atom(&ctx, &alloc, "f(x_, rest___)");
468 let a = ctx.var("a");
469 let b = ctx.var("b");
470 let c = ctx.var("c");
471 let f = ctx.fun("f", &[a, b, c]);
472 let bindings = match_pattern(pat, f).unwrap();
473 assert!(matches!(
474 bindings.get(Symbol::new("x")),
475 Some(MatchValue::Single(atom)) if *atom == a
476 ));
477 let rest = bindings.get(Symbol::new("rest")).unwrap();
478 assert!(matches!(rest, MatchValue::Sequence(s) if s.len() == 2));
479 }
480}