use ocas_atom::{Atom, AtomArena};
use crate::matcher::{self, Bindings};
#[derive(Debug, Clone, Copy, Default)]
pub struct ReplaceSettings {
pub once: bool,
pub bottom_up: bool,
pub nested: bool,
}
#[derive(Clone)]
pub enum Condition<'a> {
Predicate(std::sync::Arc<dyn Fn(&Bindings<'a>) -> bool + 'a>),
}
impl<'a> Condition<'a> {
pub fn new<F: Fn(&Bindings<'a>) -> bool + 'a>(f: F) -> Self {
Condition::Predicate(std::sync::Arc::new(f))
}
fn eval(&self, bindings: &Bindings<'a>) -> bool {
match self {
Condition::Predicate(f) => f(bindings),
}
}
}
pub struct Replacement<'a, F>
where
F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>,
{
pub pattern: crate::pattern::Pattern<'a>,
pub replacement: F,
pub condition: Option<Condition<'a>>,
}
pub fn replace_once<'a, F>(
ctx: &'a AtomArena<'a>,
atom: Atom<'a>,
pattern: crate::pattern::Pattern<'a>,
replacement: F,
) -> Atom<'a>
where
F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>,
{
replace_top_down(ctx, atom, &pattern, &replacement, &None, true)
}
pub fn replace_all<'a, F>(
ctx: &'a AtomArena<'a>,
atom: Atom<'a>,
pattern: crate::pattern::Pattern<'a>,
replacement: F,
) -> Atom<'a>
where
F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>,
{
replace_top_down(ctx, atom, &pattern, &replacement, &None, false)
}
pub fn replace_all_multiple<'a, F>(
ctx: &'a AtomArena<'a>,
atom: Atom<'a>,
replacements: &[Replacement<'a, F>],
) -> Atom<'a>
where
F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>,
{
replace_multiple_top_down(ctx, atom, replacements, false)
}
fn replace_top_down<'a, F>(
ctx: &'a AtomArena<'a>,
atom: Atom<'a>,
pattern: &crate::pattern::Pattern<'a>,
replacement: &F,
condition: &Option<Condition<'a>>,
once: bool,
) -> Atom<'a>
where
F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>,
{
if let Ok(bindings) = matcher::match_pattern(pattern.clone(), atom)
&& condition.as_ref().is_none_or(|c| c.eval(&bindings))
{
return replacement(&bindings, ctx);
}
use ocas_atom::AtomNode;
match atom.node() {
AtomNode::Num(_) | AtomNode::Var(_) => atom,
AtomNode::Add(args) => {
let new_args = try_replace_child(args, ctx, pattern, replacement, condition, once);
ctx.add(&new_args)
}
AtomNode::Mul(args) => {
let new_args = try_replace_child(args, ctx, pattern, replacement, condition, once);
ctx.mul(&new_args)
}
AtomNode::Pow(base, exp) => {
let new_base = replace_top_down(ctx, *base, pattern, replacement, condition, once);
let new_exp = replace_top_down(ctx, *exp, pattern, replacement, condition, once);
ctx.pow(new_base, new_exp)
}
AtomNode::Fun(name, args) => {
let new_args = try_replace_child(args, ctx, pattern, replacement, condition, once);
ctx.fun(name.as_str(), &new_args)
}
}
}
fn try_replace_child<'a, F>(
args: &'a [Atom<'a>],
ctx: &'a AtomArena<'a>,
pattern: &crate::pattern::Pattern<'a>,
replacement: &F,
condition: &Option<Condition<'a>>,
once: bool,
) -> Vec<Atom<'a>>
where
F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>,
{
if !once {
return args
.iter()
.map(|a| replace_top_down(ctx, *a, pattern, replacement, condition, false))
.collect();
}
let mut found = false;
args.iter()
.map(|a| {
if found {
*a
} else {
let result = replace_top_down(ctx, *a, pattern, replacement, condition, true);
if result != *a {
found = true;
}
result
}
})
.collect()
}
fn replace_multiple_top_down<'a, F>(
ctx: &'a AtomArena<'a>,
atom: Atom<'a>,
replacements: &[Replacement<'a, F>],
once: bool,
) -> Atom<'a>
where
F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>,
{
for repl in replacements {
if let Ok(bindings) = matcher::match_pattern(repl.pattern.clone(), atom)
&& repl.condition.as_ref().is_none_or(|c| c.eval(&bindings))
{
return (repl.replacement)(&bindings, ctx);
}
}
if once {
return atom;
}
use ocas_atom::AtomNode;
match atom.node() {
AtomNode::Num(_) | AtomNode::Var(_) => atom,
AtomNode::Add(args) => {
let new_args: Vec<Atom<'a>> = args
.iter()
.map(|a| replace_multiple_top_down(ctx, *a, replacements, once))
.collect();
ctx.add(&new_args)
}
AtomNode::Mul(args) => {
let new_args: Vec<Atom<'a>> = args
.iter()
.map(|a| replace_multiple_top_down(ctx, *a, replacements, once))
.collect();
ctx.mul(&new_args)
}
AtomNode::Pow(base, exp) => {
let new_base = replace_multiple_top_down(ctx, *base, replacements, once);
let new_exp = replace_multiple_top_down(ctx, *exp, replacements, once);
ctx.pow(new_base, new_exp)
}
AtomNode::Fun(name, args) => {
let new_args: Vec<Atom<'a>> = args
.iter()
.map(|a| replace_multiple_top_down(ctx, *a, replacements, once))
.collect();
ctx.fun(name.as_str(), &new_args)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pattern::Pattern;
use ocas_atom::AtomArena;
use ocas_core::arena::Arena;
#[test]
fn replace_once_simple() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let y = ctx.var("y");
let sum = ctx.add(&[x, y]);
let pat = Pattern::Literal(x);
let result = replace_once(&ctx, sum, pat, |_, ctx| ctx.num(42));
assert_eq!(result.to_string(), "42 + y");
}
#[test]
fn replace_all_replaces_all() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let y = ctx.var("y");
let sum = ctx.add(&[x, ctx.add(&[y, x])]);
let pat = Pattern::Literal(x);
let result = replace_all(&ctx, sum, pat, |_, ctx| ctx.num(1));
let s = result.to_string();
assert!(s.contains("1") && !s.contains('x'), "result: {s}");
}
}