use ocas_atom::{Atom, AtomNode, Symbol};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WildcardLevel {
Single,
Sequence,
NullSequence,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Pattern<'a> {
Literal(Atom<'a>),
Wildcard(Symbol, WildcardLevel),
Add(Vec<Pattern<'a>>),
Mul(Vec<Pattern<'a>>),
Pow(Box<(Pattern<'a>, Pattern<'a>)>),
Fun(Symbol, Vec<Pattern<'a>>),
}
impl<'a> Pattern<'a> {
pub fn from_atom(_ctx: &'a impl PatternAlloc<'a>, atom: Atom<'a>) -> Pattern<'a> {
match atom.node() {
AtomNode::Num(_) | AtomNode::Var(_) => {
if let AtomNode::Var(s) = atom.node() {
let name = s.as_str();
if let Some(level) = wildcard_level(name) {
let base = strip_underscores(name);
return Pattern::Wildcard(Symbol::new(base), level);
}
}
Pattern::Literal(atom)
}
AtomNode::Fun(name, args) => {
let pat_args: Vec<Pattern<'a>> =
args.iter().map(|a| Pattern::from_atom(_ctx, *a)).collect();
Pattern::Fun(*name, pat_args)
}
AtomNode::Add(args) => {
let pat_args: Vec<Pattern<'a>> =
args.iter().map(|a| Pattern::from_atom(_ctx, *a)).collect();
Pattern::Add(pat_args)
}
AtomNode::Mul(args) => {
let pat_args: Vec<Pattern<'a>> =
args.iter().map(|a| Pattern::from_atom(_ctx, *a)).collect();
Pattern::Mul(pat_args)
}
AtomNode::Pow(base, exp) => {
let base_pat = Box::new((
Pattern::from_atom(_ctx, *base),
Pattern::from_atom(_ctx, *exp),
));
Pattern::Pow(base_pat)
}
}
}
}
fn wildcard_level(name: &str) -> Option<WildcardLevel> {
if name.starts_with("___") || name.ends_with("___") {
Some(WildcardLevel::NullSequence)
} else if name.starts_with("__") || name.ends_with("__") {
Some(WildcardLevel::Sequence)
} else if name.starts_with('_') || name.ends_with('_') {
Some(WildcardLevel::Single)
} else {
None
}
}
fn strip_underscores(name: &str) -> &str {
let leading = name.bytes().take_while(|&b| b == b'_').count();
let trailing = name.bytes().rev().take_while(|&b| b == b'_').count();
let start = leading;
let end = name.len().saturating_sub(trailing);
if start >= end { "" } else { &name[start..end] }
}
pub trait PatternAlloc<'a> {
fn alloc_slice(&self, items: &[Pattern<'a>]) -> &'a [Pattern<'a>];
}
impl<'a> PatternAlloc<'a> for () {
fn alloc_slice(&self, _items: &[Pattern<'a>]) -> &'a [Pattern<'a>] {
Box::leak(_items.to_vec().into_boxed_slice())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ocas_atom::AtomArena;
use ocas_core::arena::Arena;
struct VecAlloc;
impl<'a> PatternAlloc<'a> for VecAlloc {
fn alloc_slice(&self, items: &[Pattern<'a>]) -> &'a [Pattern<'a>] {
let leaked: Box<[Pattern<'a>]> = items.to_vec().into_boxed_slice();
Box::leak(leaked)
}
}
#[test]
fn single_wildcard_from_var() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let alloc = VecAlloc;
let x_ = ctx.var("x_");
let pat = Pattern::from_atom(&alloc, x_);
assert!(matches!(pat, Pattern::Wildcard(s, WildcardLevel::Single) if s.as_str() == "x"));
}
#[test]
fn wildcard_utils_smoke() {
assert_eq!(wildcard_level("__x"), Some(WildcardLevel::Sequence));
assert_eq!(strip_underscores("__x"), "x");
}
#[test]
fn sequence_wildcard_from_var() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let alloc = VecAlloc;
let xs = ctx.var("__x");
let pat = Pattern::from_atom(&alloc, xs);
assert!(matches!(pat, Pattern::Wildcard(s, WildcardLevel::Sequence) if s.as_str() == "x"));
}
#[test]
fn literal_number_remains_literal() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let alloc = VecAlloc;
let n = ctx.num(42);
let pat = Pattern::from_atom(&alloc, n);
assert!(matches!(pat, Pattern::Literal(a) if a == n));
}
}