recursion-visualize 0.5.0

visualized cache-aware stack safe recursion
Documentation
mod expr1 {
    use std::fmt::Display;

    use recursion::*;
    use recursion_visualize::visualize::*;
    #[derive(Debug, PartialEq, Eq)]
    pub enum Expr {
        Add(Box<Expr>, Box<Expr>),
        Sub(Box<Expr>, Box<Expr>),
        Mul(Box<Expr>, Box<Expr>),
        LiteralInt(i64),
    }
    impl Display for Expr {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                Expr::Add(a, b) => write!(f, "{} + {}", a, b),
                Expr::Sub(a, b) => write!(f, "{} - {}", a, b),
                Expr::Mul(a, b) => write!(f, "{} * {}", a, b),
                Expr::LiteralInt(x) => write!(f, "{}", x),
            }
        }
    }

    pub fn add(a: Expr, b: Expr) -> Expr {
        Expr::Add(Box::new(a), Box::new(b))
    }
    pub fn subtract(a: Expr, b: Expr) -> Expr {
        Expr::Sub(Box::new(a), Box::new(b))
    }
    pub fn multiply(a: Expr, b: Expr) -> Expr {
        Expr::Mul(Box::new(a), Box::new(b))
    }
    pub fn literal(n: i64) -> Expr {
        Expr::LiteralInt(n)
    }
    pub enum ExprFrame<A> {
        Add(A, A),
        Sub(A, A),
        Mul(A, A),
        LiteralInt(i64),
    }

    impl Display for ExprFrame<()> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                ExprFrame::Add(_, _) => write!(f, "_ + _",),
                ExprFrame::Sub(_, _) => write!(f, "_ - _",),
                ExprFrame::Mul(_, _) => write!(f, "_ * _",),
                ExprFrame::LiteralInt(x) => write!(f, "{}", x),
            }
        }
    }

    impl MappableFrame for ExprFrame<PartiallyApplied> {
        type Frame<X> = ExprFrame<X>;
        fn map_frame<A, B>(input: Self::Frame<A>, mut f: impl FnMut(A) -> B) -> Self::Frame<B> {
            match input {
                ExprFrame::Add(a, b) => ExprFrame::Add(f(a), f(b)),
                ExprFrame::Sub(a, b) => ExprFrame::Sub(f(a), f(b)),
                ExprFrame::Mul(a, b) => ExprFrame::Mul(f(a), f(b)),
                ExprFrame::LiteralInt(x) => ExprFrame::LiteralInt(x),
            }
        }
    }
    impl<'a> Collapsible for &'a Expr {
        type FrameToken = ExprFrame<PartiallyApplied>;
        fn into_frame(self) -> <Self::FrameToken as MappableFrame>::Frame<Self> {
            match self {
                Expr::Add(a, b) => ExprFrame::Add(a, b),
                Expr::Sub(a, b) => ExprFrame::Sub(a, b),
                Expr::Mul(a, b) => ExprFrame::Mul(a, b),
                Expr::LiteralInt(x) => ExprFrame::LiteralInt(*x),
            }
        }
    }
    impl Expandable for Expr {
        type FrameToken = ExprFrame<PartiallyApplied>;
        fn from_frame(val: <Self::FrameToken as MappableFrame>::Frame<Self>) -> Self {
            match val {
                ExprFrame::Add(a, b) => Expr::Add(Box::new(a), Box::new(b)),
                ExprFrame::Sub(a, b) => Expr::Sub(Box::new(a), Box::new(b)),
                ExprFrame::Mul(a, b) => Expr::Mul(Box::new(a), Box::new(b)),
                ExprFrame::LiteralInt(x) => Expr::LiteralInt(x),
            }
        }
    }

    pub fn eval(e: &Expr) -> (i64, Viz) {
        e.collapse_frames_v(|frame| match frame {
            ExprFrame::Add(a, b) => a + b,
            ExprFrame::Sub(a, b) => a - b,
            ExprFrame::Mul(a, b) => a * b,
            ExprFrame::LiteralInt(x) => x,
        })
    }

    pub fn build_expr(depth: usize) -> (Expr, Viz) {
        Expr::expand_frames_v(depth, |depth| {
            if depth > 0 {
                ExprFrame::Add(depth - 1, depth - 1)
            } else {
                ExprFrame::LiteralInt(1)
            }
        })
    }
}

mod expr2 {
    use std::fmt::Display;

    use recursion::*;
    use recursion_visualize::visualize::{CollapsibleVizExt, Viz};
    pub enum Expr {
        Add(Box<Expr>, Box<Expr>),
        Sub(Box<Expr>, Box<Expr>),
        Mul(Box<Expr>, Box<Expr>),
        Div(Box<Expr>, Box<Expr>),
        LiteralInt(i64),
    }
    impl Display for Expr {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                Expr::Add(a, b) => write!(f, "{} + {}", a, b),
                Expr::Sub(a, b) => write!(f, "{} - {}", a, b),
                Expr::Mul(a, b) => write!(f, "{} * {}", a, b),
                Expr::Div(a, b) => write!(f, "{} / {}", a, b),
                Expr::LiteralInt(x) => write!(f, "{}", x),
            }
        }
    }
    pub fn add(a: Expr, b: Expr) -> Expr {
        Expr::Add(Box::new(a), Box::new(b))
    }
    pub fn subtract(a: Expr, b: Expr) -> Expr {
        Expr::Sub(Box::new(a), Box::new(b))
    }
    pub fn multiply(a: Expr, b: Expr) -> Expr {
        Expr::Mul(Box::new(a), Box::new(b))
    }
    pub fn divide(a: Expr, b: Expr) -> Expr {
        Expr::Div(Box::new(a), Box::new(b))
    }
    pub fn literal(n: i64) -> Expr {
        Expr::LiteralInt(n)
    }
    pub enum ExprFrame<A> {
        Add(A, A),
        Sub(A, A),
        Mul(A, A),
        Div(A, A),
        LiteralInt(i64),
    }
    impl Display for ExprFrame<()> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                ExprFrame::Add(_, _) => write!(f, "_ + _",),
                ExprFrame::Sub(_, _) => write!(f, "_ - _",),
                ExprFrame::Mul(_, _) => write!(f, "_ * _",),
                ExprFrame::Div(_, _) => write!(f, "_ / _",),
                ExprFrame::LiteralInt(x) => write!(f, "{}", x),
            }
        }
    }
    impl MappableFrame for ExprFrame<PartiallyApplied> {
        type Frame<X> = ExprFrame<X>;
        fn map_frame<A, B>(input: Self::Frame<A>, mut f: impl FnMut(A) -> B) -> Self::Frame<B> {
            match input {
                ExprFrame::Add(a, b) => ExprFrame::Add(f(a), f(b)),
                ExprFrame::Sub(a, b) => ExprFrame::Sub(f(a), f(b)),
                ExprFrame::Mul(a, b) => ExprFrame::Mul(f(a), f(b)),
                ExprFrame::Div(a, b) => ExprFrame::Div(f(a), f(b)),
                ExprFrame::LiteralInt(x) => ExprFrame::LiteralInt(x),
            }
        }
    }
    impl<'a> Collapsible for &'a Expr {
        type FrameToken = ExprFrame<PartiallyApplied>;
        fn into_frame(self) -> <Self::FrameToken as MappableFrame>::Frame<Self> {
            match self {
                Expr::Add(a, b) => ExprFrame::Add(a, b),
                Expr::Sub(a, b) => ExprFrame::Sub(a, b),
                Expr::Mul(a, b) => ExprFrame::Mul(a, b),
                Expr::Div(a, b) => ExprFrame::Div(a, b),
                Expr::LiteralInt(x) => ExprFrame::LiteralInt(*x),
            }
        }
    }

    pub fn try_eval(e: &Expr) -> (Result<i64, &str>, Viz) {
        e.try_collapse_frames_v(|frame| match frame {
            ExprFrame::Add(a, b) => Ok(a + b),
            ExprFrame::Sub(a, b) => Ok(a - b),
            ExprFrame::Mul(a, b) => Ok(a * b),
            ExprFrame::Div(a, b) => {
                if b == 0 {
                    Err("cannot divide by zero")
                } else {
                    Ok(a / b)
                }
            }
            ExprFrame::LiteralInt(x) => Ok(x),
        })
    }
}

fn main() {
    {
        use expr1::*;
        let expr = multiply(subtract(literal(1), literal(2)), literal(3));

        let (evaluated, viz) = eval(&expr);
        assert_eq!(evaluated, -3);

        viz.label("Evaluate Expr".to_string(), "(1 - 2) * 3".to_string())
            .write("eval.html".to_string());

        let (built_expr, viz) = build_expr(2);
        let expected = add(add(literal(1), literal(1)), add(literal(1), literal(1)));

        assert_eq!(built_expr, expected);

        viz.label("Build Expr".to_string(), "1 + 1 + 1 + 1".to_string())
            .write("build_expr.html".to_string());
    }

    {
        use expr2::*;

        let valid_expr = divide(subtract(literal(1), literal(7)), literal(3));
        let invalid_expr = divide(multiply(literal(2), literal(3)), literal(0));

        let (valid_res, valid_viz) = try_eval(&valid_expr);
        let (invalid_res, invalid_viz) = try_eval(&invalid_expr);

        assert_eq!(valid_res, Ok(-2));
        assert_eq!(invalid_res, Err("cannot divide by zero"));

        valid_viz
            .label("Try Eval Valid Expr".to_string(), "(1 - 7) / 3".to_string())
            .write("try_eval_valid.html".to_string());
        invalid_viz
            .label("Try Eval Expr".to_string(), "(2 * 3) / 0".to_string())
            .write("try_eval_invalid.html".to_string());
    }
}