wolf-derivation-graph 0.1.0

Adds support for memoizing data flow graphs to wolf-graph.
Documentation
use std::rc::Rc;
use wolf_graph::prelude::*;
use anyhow::{anyhow, Result};

use wolf_derivation_graph::{DerivationGraph, OpInputs, Operation};

#[derive(Debug, Clone, PartialEq, Eq)]
enum Value {
    Int(i32),
    String(String),
}

type Graph = DerivationGraph<Value>;
type Inputs = OpInputs<Value>;
type Op = Operation<Value>;

fn input(inputs: &Inputs, key: &str) -> Result<Value> {
    inputs.get(key).cloned().ok_or_else(|| anyhow!("key not found: '{key}'"))
}

fn input_int(inputs: &Inputs, key: &str) -> Result<i32> {
    i32::try_from(input(inputs, key)?)
}

fn input_string(inputs: &Inputs, key: &str) -> Result<String> {
    String::try_from(input(inputs, key)?)
}

fn output(graph: &Graph, node: &NodeID) -> Value {
    graph.derived_node_value(node).unwrap()
}

fn output_int(graph: &Graph, node: &NodeID) -> i32 {
    i32::try_from(output(graph, node)).unwrap()
}

fn output_string(graph: &Graph, node: &NodeID) -> String {
    String::try_from(output(graph, node)).unwrap()
}

impl From<i32> for Value {
    fn from(value: i32) -> Self {
        Value::Int(value)
    }
}

impl From<String> for Value {
    fn from(value: String) -> Self {
        Value::String(value)
    }
}

impl From<&str> for Value {
    fn from(value: &str) -> Self {
        Value::String(value.to_string())
    }
}

impl TryFrom<Value> for i32 {
    type Error = anyhow::Error;

    fn try_from(value: Value) -> Result<Self> {
        match value {
            Value::Int(i) => Ok(i),
            _ => Err(anyhow!("not an integer")),
        }
    }
}

impl TryFrom<Value> for String {
    type Error = anyhow::Error;

    fn try_from(value: Value) -> Result<Self> {
        match value {
            Value::String(s) => Ok(s),
            _ => Err(anyhow!("not a string")),
        }
    }
}

/// Takes two inputs `lhs<int>` and `rhs<int>`, and returns lhs + rhs.
fn add_op() -> Op {
    Rc::new(|inputs| {
        let lhs = input_int(&inputs, "lhs")?;
        let rhs = input_int(&inputs, "rhs")?;
        Ok((lhs + rhs).into())
    })
}

/// Takes two inputs `lhs<int>` and `rhs<int>`, and returns lhs - rhs.
fn sub_op() -> Op {
    Rc::new(|inputs| {
        let lhs = input_int(&inputs, "lhs")?;
        let rhs = input_int(&inputs, "rhs")?;
        Ok((lhs - rhs).into())
    })
}

/// Takes two inputs `lhs<int>` and `rhs<int>`, and returns lhs * rhs.
fn mul_op() -> Op {
    Rc::new(|inputs| {
        let lhs = input_int(&inputs, "lhs")?;
        let rhs = input_int(&inputs, "rhs")?;
        Ok((lhs * rhs).into())
    })
}

/// Takes two inputs, `count<int>` and `value<string>`, and returns a new string that is
/// the input string repeated `count` times.
fn replicate_op() -> Op {
    Rc::new(|inputs| {
        let count = input_int(&inputs, "count")?;
        let value = input_string(&inputs, "value")?;
        if count < 0 {
            return Err(anyhow!("count must be non-negative"));
        }
        Ok(value.repeat(count as usize).into())
    })
}

#[test]
fn test() {
    let mut graph = DerivationGraph::<Value>::new();

    let int_1 = NodeID::new();
    graph.add_node_with_value(&int_1, "Int 1", 2).unwrap();
    assert_eq!(graph.node_label(&int_1).unwrap(), "Int 1");

    let int_2 = NodeID::new();
    graph.add_node_with_value(&int_2, "Int 2", 3).unwrap();

    let arith_op = NodeID::new();
    graph.add_node_with_operation(&arith_op, "ArithOp", add_op()).unwrap();
    let eid1 = EdgeID::new();
    graph.add_edge(&eid1, &int_1, &arith_op, "lhs").unwrap();
    graph.add_edge(EdgeID::new(), &int_2, &arith_op, "rhs").unwrap();
    assert_eq!(graph.edge_label(&eid1).unwrap(), "lhs");

    let animal = NodeID::new();
    graph.add_node_with_value(&animal, "Animal", "Cat").unwrap();

    let string_op = NodeID::new();
    graph.add_node_with_operation(&string_op, "StringOp", replicate_op()).unwrap();
    graph.add_edge(EdgeID::new(), &arith_op, &string_op, "count").unwrap();
    graph.add_edge(EdgeID::new(), &animal, &string_op, "value").unwrap();

    assert_eq!(output_string(&graph, &string_op), "CatCatCatCatCat");

    graph.set_node_value(&animal, "Dog").unwrap();
    assert_eq!(output_string(&graph, &string_op), "DogDogDogDogDog");

    graph.set_operation(&arith_op, mul_op()).unwrap();
    assert_eq!(output_string(&graph, &string_op), "DogDogDogDogDogDog");

    assert_eq!(output_int(&graph, &arith_op), 6);

    graph.clear_all_node_values();
    graph.set_node_value(&int_1, 12).unwrap();
    graph.set_node_value(&int_2, 7).unwrap();
    graph.set_operation(&arith_op, sub_op()).unwrap();
    graph.set_node_value(&animal, "🐺").unwrap();
    assert_eq!(output_string(&graph, &string_op), "🐺🐺🐺🐺🐺");
}