uplc 1.0.2-alpha

Utilities for working with Untyped Plutus Core
Documentation
use std::rc::Rc;

use indexmap::IndexMap;
use itertools::Itertools;

use crate::{
    ast::{Name, Program, Term},
    builtins::DefaultFunction,
};
// use crate::builtins::{DefaultFunction};

#[derive(Eq, Hash, PartialEq, Clone)]
pub struct Occurrence {
    name: Rc<Name>,
    lambda_count: usize,
}

impl Program<Name> {
    pub fn lambda_reduce(self) -> Program<Name> {
        let mut term = self.term.clone();
        lambda_reduce(&mut term);
        Program {
            version: self.version,
            term,
        }
    }

    pub fn builtin_force_reduce(self) -> Program<Name> {
        let mut term = self.term.clone();
        let mut builtin_map = IndexMap::new();
        builtin_force_reduce(&mut term, &mut builtin_map);

        for default_func_index in builtin_map.keys().sorted().cloned() {
            let default_func: DefaultFunction = default_func_index.try_into().unwrap();

            term = term
                .lambda(format!("__{}_wrapped", default_func.aiken_name()))
                .apply(if default_func.force_count() == 1 {
                    Term::Builtin(default_func).force()
                } else {
                    Term::Builtin(default_func).force().force()
                });
        }

        Program {
            version: self.version,
            term,
        }
    }

    pub fn inline_reduce(self) -> Program<Name> {
        let mut term = self.term.clone();
        inline_basic_reduce(&mut term);
        Program {
            version: self.version,
            term,
        }
    }

    pub fn force_delay_reduce(self) -> Program<Name> {
        let mut term = self.term.clone();
        force_delay_reduce(&mut term);
        Program {
            version: self.version,
            term,
        }
    }
}

fn builtin_force_reduce(term: &mut Term<Name>, builtin_map: &mut IndexMap<u8, ()>) {
    match term {
        Term::Force(f) => {
            let f = Rc::make_mut(f);

            match f {
                Term::Force(inner_f) => {
                    if let Term::Builtin(func) = inner_f.as_ref() {
                        builtin_map.insert(*func as u8, ());
                        *term = Term::Var(
                            Name {
                                text: format!("__{}_wrapped", func.aiken_name()),
                                unique: 0.into(),
                            }
                            .into(),
                        );
                        return;
                    }
                }
                Term::Builtin(func) => {
                    builtin_map.insert(*func as u8, ());
                    *term = Term::Var(
                        Name {
                            text: format!("__{}_wrapped", func.aiken_name()),
                            unique: 0.into(),
                        }
                        .into(),
                    );

                    return;
                }
                _ => {}
            }
            builtin_force_reduce(f, builtin_map);
        }
        Term::Delay(d) => {
            let d = Rc::make_mut(d);
            builtin_force_reduce(d, builtin_map);
        }
        Term::Lambda { body, .. } => {
            let body = Rc::make_mut(body);
            builtin_force_reduce(body, builtin_map);
        }
        Term::Apply { function, argument } => {
            let func = Rc::make_mut(function);
            builtin_force_reduce(func, builtin_map);

            let arg = Rc::make_mut(argument);
            builtin_force_reduce(arg, builtin_map);
        }
        _ => {}
    }
}

fn force_delay_reduce(term: &mut Term<Name>) {
    match term {
        Term::Force(f) => {
            let f = Rc::make_mut(f);

            if let Term::Delay(body) = f {
                *term = body.as_ref().clone();
            } else {
                force_delay_reduce(f);
            }
        }
        Term::Delay(d) => {
            let d = Rc::make_mut(d);
            force_delay_reduce(d);
        }
        Term::Lambda { body, .. } => {
            let body = Rc::make_mut(body);
            force_delay_reduce(body);
        }
        Term::Apply { function, argument } => {
            let func = Rc::make_mut(function);
            force_delay_reduce(func);

            let arg = Rc::make_mut(argument);
            force_delay_reduce(arg);
        }
        _ => {}
    }
}

fn inline_basic_reduce(term: &mut Term<Name>) {
    match term {
        Term::Delay(d) => {
            let d = Rc::make_mut(d);
            inline_basic_reduce(d);
        }
        Term::Lambda { body, .. } => {
            let body = Rc::make_mut(body);
            inline_basic_reduce(body);
        }
        Term::Apply { function, argument } => {
            let arg = Rc::make_mut(argument);
            inline_basic_reduce(arg);

            let func = Rc::make_mut(function);
            inline_basic_reduce(func);

            if let Term::Lambda {
                parameter_name,
                body,
            } = func
            {
                let mut occurrences = 0;
                var_occurrences(body, parameter_name.clone(), &mut occurrences);
                if occurrences == 1 {
                    if let replace_term @ (Term::Var(_)
                    | Term::Constant(_)
                    | Term::Error
                    | Term::Delay(_)
                    | Term::Lambda { .. }) = argument.as_ref()
                    {
                        *term =
                            substitute_term(body.as_ref(), parameter_name.clone(), replace_term);
                    }
                }
            }
        }
        Term::Force(f) => {
            let f = Rc::make_mut(f);
            inline_basic_reduce(f);
        }
        _ => {}
    }
}

fn var_occurrences(term: &Term<Name>, search_for: Rc<Name>, occurrences: &mut usize) {
    match term {
        Term::Var(name) => {
            if name.as_ref() == search_for.as_ref() {
                *occurrences += 1;
            }
        }
        Term::Delay(body) => {
            var_occurrences(body.as_ref(), search_for, occurrences);
        }
        Term::Lambda {
            parameter_name,
            body,
        } => {
            if parameter_name.clone() != search_for {
                var_occurrences(body.as_ref(), search_for, occurrences);
            }
        }
        Term::Apply { function, argument } => {
            var_occurrences(function.as_ref(), search_for.clone(), occurrences);
            var_occurrences(argument.as_ref(), search_for, occurrences);
        }
        Term::Force(x) => {
            var_occurrences(x.as_ref(), search_for, occurrences);
        }
        _ => {}
    }
}

// fn error_occurrences(term: &Term<Name>, occurrences: &mut usize) {
//     match term {
//         Term::Delay(body) => {
//             error_occurrences(body.as_ref(), occurrences);
//         }
//         Term::Lambda { body, .. } => {
//             error_occurrences(body.as_ref(), occurrences);
//         }
//         Term::Apply { function, argument } => {
//             error_occurrences(function.as_ref(), occurrences);
//             error_occurrences(argument.as_ref(), occurrences);
//         }
//         Term::Force(x) => {
//             error_occurrences(x.as_ref(), occurrences);
//         }
//         Term::Error => {
//             *occurrences += 1;
//         }
//         _ => {}
//     }
// }

fn lambda_reduce(term: &mut Term<Name>) {
    match term {
        Term::Apply { function, argument } => {
            let func = Rc::make_mut(function);
            lambda_reduce(func);

            let arg = Rc::make_mut(argument);
            lambda_reduce(arg);

            if let Term::Lambda {
                parameter_name,
                body,
            } = func
            {
                if let replace_term @ (Term::Var(_) | Term::Constant(_)) = arg {
                    let body = Rc::make_mut(body);
                    *term = substitute_term(body, parameter_name.clone(), replace_term);
                }
            }
        }
        Term::Delay(d) => {
            let d = Rc::make_mut(d);
            lambda_reduce(d);
        }
        Term::Lambda { body, .. } => {
            let body = Rc::make_mut(body);
            lambda_reduce(body);
        }
        Term::Force(f) => {
            let f = Rc::make_mut(f);
            lambda_reduce(f);
        }
        _ => {}
    }
}

fn substitute_term(term: &Term<Name>, original: Rc<Name>, replace_with: &Term<Name>) -> Term<Name> {
    match term {
        Term::Var(name) => {
            if name.as_ref() == original.as_ref() {
                replace_with.clone()
            } else {
                Term::Var(name.clone())
            }
        }
        Term::Delay(body) => {
            Term::Delay(substitute_term(body.as_ref(), original, replace_with).into())
        }
        Term::Lambda {
            parameter_name,
            body,
        } => {
            if parameter_name.as_ref() != original.as_ref() {
                Term::Lambda {
                    parameter_name: parameter_name.clone(),
                    body: Rc::new(substitute_term(body.as_ref(), original, replace_with)),
                }
            } else {
                Term::Lambda {
                    parameter_name: parameter_name.clone(),
                    body: body.clone(),
                }
            }
        }
        Term::Apply { function, argument } => Term::Apply {
            function: Rc::new(substitute_term(
                function.as_ref(),
                original.clone(),
                replace_with,
            )),
            argument: Rc::new(substitute_term(argument.as_ref(), original, replace_with)),
        },
        Term::Force(x) => Term::Force(Rc::new(substitute_term(x.as_ref(), original, replace_with))),
        x => x.clone(),
    }
}