calc-graph 0.4.2

Efficient calculations on a graph of values
Documentation
extern crate itertools;

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::error::Error;

use itertools::Itertools;

fn main() -> Result<(), Box<Error>> {
    let filename = Path::new(&env::var("OUT_DIR")?).join("funcs.rs");
    let mut file = File::create(filename)?;

    for i in 1..9 {
        write!(
            file,
            r#"
/// Calculates a value from {doc_suffix}.
pub struct Func{i}<{c}, T, F> {{
    f: F,
    id: Option<NonZeroUsize>,
    value: Option<(NonZeroUsize, T)>,
    precs: ({c},),
}}

/// Calculates a value from the value currently in this node and {doc_suffix}.
pub struct Update{i}<{c}, T, F> {{
    f: F,
    id: Option<NonZeroUsize>,
    version: Option<NonZeroUsize>,
    value: T,
    precs: ({c},),
}}

impl<{c_calc}, T: Clone + PartialEq, F: FnMut({c_value}) -> T> Calc
    for Func{i}<{c}, T, F>
{{
    type Value = T;

    fn eval(&mut self, dirty: &mut BitSet) -> (NonZeroUsize, T) {{
        {precs_borrow}
        let f = &mut self.f;
        eval_func(
            dirty,
            self.id,
            &mut self.value,
            |dirty| {{
                {precs_eval}
                let prec_version = prec0_version;
                {version_max}
                (prec_version, ({value}))
            }},
            |({value})| f({value}),
        )
    }}

    fn add_dep(&mut self, seen: &mut BitSet, dep: NonZeroUsize) {{
        if let Some(id) = self.id {{
            if seen.insert(id.get()) {{
                {add_dep}
            }}
        }}
    }}
}}

impl<{c_calc}, T: Clone, F: FnMut(&mut T, {c_value}) -> bool> Calc
    for Update{i}<{c}, T, F>
{{
    type Value = T;

    fn eval(&mut self, dirty: &mut BitSet) -> (NonZeroUsize, T) {{
        {precs_borrow}
        let f = &mut self.f;
        eval_update(
            dirty,
            self.id,
            &mut self.version,
            &mut self.value,
            |dirty| {{
                {precs_eval}
                let prec_version = prec0_version;
                {version_max}
                (prec_version, ({value}))
            }},
            |value_cell, ({value})| f(value_cell, {value}),
        )
    }}

    fn add_dep(&mut self, seen: &mut BitSet, dep: NonZeroUsize) {{
        if let Some(id) = self.id {{
            if seen.insert(id.get()) {{
                {add_dep}
            }}
        }}
    }}
}}

impl<C1: Calc> Node<C1> {{
    /// Returns a new node whose value is calculated from this node{doc2_suffix}.
    pub fn {map_zip}<{c2_calc} T, F: FnMut({c_value}) -> T>(
        self,
        {prec2_arg}
        f: F,
    ) -> Node<Func{i}<{c}, T, F>> {{
        let prec1 = self;
        {prec_destructure}
        let graph = None;
        {prec_graph}
        let mut graph = graph;

        let id = graph.as_mut().map(|graph| {{
            let id = alloc_id(&graph);
            let mut seen = BitSet::with_capacity(id.get());
            {prec_add_dep}
            id
        }});

        Node {{
            calc: Func{i} {{
                f,
                value: None,
                id,
                precs: ({prec_calc}),
            }},
            graph,
        }}
    }}

    /// Returns a new node whose value is calculated from this node{doc2_suffix}. The `FnMut` that performs the
    /// calculation can update the value in place.
    pub fn {map_zip}_update<{c2_calc} T, F: FnMut(&mut T, {c_value}) -> bool>(
        self,
        {prec2_arg}
        initial_value: T,
        f: F,
    ) -> Node<Update{i}<{c}, T, F>> {{
        let prec1 = self;
        {prec_destructure}
        let graph = None;
        {prec_graph}
        let mut graph = graph;

        let id = graph.as_mut().map(|graph| {{
            let id = alloc_id(&graph);
            let mut seen = BitSet::with_capacity(id.get());
            {prec_add_dep}
            id
        }});

        Node {{
            calc: Update{i} {{
                f,
                id,
                version: None,
                value: initial_value,
                precs: ({prec_calc}),
            }},
            graph,
        }}
    }}
}}

#[cfg(test)]
#[test]
fn test_{map_zip}_gen() {{
    {declare_const}
    let mut {map_zip} = const1.{map_zip}({const2} |{const_}| {const_add});
    assert_eq!({const_sum}, {map_zip}.get_mut());
}}

#[cfg(test)]
#[test]
fn test_{map_zip}_update_gen() {{
    {declare_const}
    let mut {map_zip}_update = const1.{map_zip}_update({const2} -1, |value, {const_}| {{
        *value = {const_add};
        true
    }});

    assert_eq!({const_sum}, {map_zip}_update.get_mut());
}}
        "#,
            i = i,
            c = (1..i + 1).map(|j| format!("C{j}", j = j)).join(", "),
            c_calc = (1..i + 1).map(|j| format!("C{j}: Calc", j = j)).join(", "),
            c_value = (1..i + 1).map(|j| format!("C{j}::Value", j = j)).join(", "),
            precs_borrow = (0..i)
                .map(|j| format!("let prec{j} = &mut self.precs.{j};", j = j))
                .join("\n"),
            precs_eval = (0..i)
                .map(|j| format!(
                    "let (prec{j}_version, prec{j}_value) = prec{j}.eval(dirty);",
                    j = j
                ))
                .join("\n"),
            version_max = (1..i)
                .map(|j| format!(
                    "let prec_version = prec_version.max(prec{j}_version);",
                    j = j
                ))
                .join("\n"),
            value = (0..i).map(|j| format!("prec{j}_value,", j = j)).join(" "),
            add_dep = (0..i)
                .map(|j| format!("self.precs.{j}.add_dep(seen, dep);", j = j))
                .join("\n"),
            doc_suffix = match i {
                1 => "another node".to_owned(),
                _ => format!("{i} nodes", i = i),
            },
            doc2_suffix = match i {
                1 => "".to_owned(),
                2 => " and another node".to_owned(),
                _ => format!(" and {i} other nodes", i = i - 1),
            },
            map_zip = match i {
                1 => "map".to_owned(),
                2 => "zip".to_owned(),
                _ => format!("zip{i}", i = i),
            },
            c2_calc = (2..i + 1).map(|j| format!("C{j}: Calc,", j = j)).join(" "),
            prec2_arg = (2..i + 1)
                .map(|j| format!("prec{j}: Node<C{j}>,", j = j))
                .join(" "),
            prec_destructure = (1..i + 1)
                .map(|j| format!(
                    "let Node {{ calc: mut prec{j}_calc, graph: prec{j}_graph }} = prec{j};",
                    j = j
                ))
                .join("\n"),
            prec_graph = (1..i + 1)
                .map(|j| format!("let graph = graph.or(prec{j}_graph);", j = j))
                .join("\n"),
            prec_add_dep = (1..i + 1)
                .map(|j| format!("prec{j}_calc.add_dep(&mut seen, id);", j = j))
                .join("\n"),
            prec_calc = (1..i + 1)
                .map(|j| format!("prec{j}_calc,", j = j))
                .join(" "),
            declare_const = (1..i + 1)
                .map(|j| format!("let const{j} = const_({j});", j = j))
                .join("\n"),
            const2 = (2..i + 1).map(|j| format!("const{j},", j = j)).join(" "),
            const_ = (1..i + 1).map(|j| format!("const{j}", j = j)).join(", "),
            const_add = (1..i + 1).map(|j| format!("const{j}", j = j)).join(" + "),
            const_sum = (1..i + 1).sum::<i32>(),
        )?;
    }

    Ok(())
}