gantz_core 0.4.1

The core types and traits for gantz, an environment for creative systems.
Documentation
// Tests for the Fn and Apply nodes - first-class functions in gantz.

use gantz_core::compile::{entry_fn_name, entrypoint, push_pull_entrypoints};
use gantz_core::node::{self, Apply, Fn, Node, Ref, WithPullEval, graph};
use gantz_core::{Edge, ROOT_STATE};
use std::collections::HashMap;
use std::fmt::Debug;
use steel::SteelVal;
use steel::steel_vm::engine::Engine;

fn node_bang() -> node::expr::Expr {
    node::expr("'bang").unwrap()
}

fn node_int(i: i32) -> node::expr::Expr {
    node::expr(format!("{}", i)).unwrap()
}

fn node_list_single() -> node::expr::Expr {
    node::expr("(list $x)").unwrap()
}

fn node_assert_eq() -> node::expr::Expr {
    node::expr("(assert! (equal? $l $r))").unwrap()
}

// Helper trait for debugging
trait DebugNode: Debug + Node + gantz_ca::CaHash {}
impl<T> DebugNode for T where T: Debug + Node + gantz_ca::CaHash {}

// Test that Fn can wrap the identity function and Apply can call it
//
//    --------
//    | bang |
//    -+------
//     |
//     |
//    -+------
//    |  fn  |  ------
//    | (id) |  | 42 |
//    -+------  -+----
//     |         |
//     |     -----
//     |     |
//    -+-----+-
//    | apply |
//    -+-------
//     |
//     |
//    ----------
//    | result |
//    ----------
#[test]
fn test_fn_apply_identity() {
    let mut g = petgraph::graph::DiGraph::new();

    // Setup the node registry as a HashMap.
    let mut nodes: HashMap<gantz_ca::ContentAddr, Box<dyn DebugNode>> = HashMap::new();

    // Just add the identity node.
    let id = gantz_core::node::Identity;
    let id_ca = gantz_ca::content_addr(&id);
    nodes.insert(id_ca, Box::new(id) as Box<dyn DebugNode>);

    // Create closure for node lookup.
    let get_node = |ca: &gantz_ca::ContentAddr| -> Option<&dyn Node> {
        nodes.get(ca).map(|b| &**b as &dyn Node)
    };

    // Create nodes
    let bang = node_bang();
    let fn_node = Fn::new(Ref::new(id_ca));
    let apply_node = Apply;
    let value = node_int(42);
    let list = node_list_single(); // Wrap value in list for apply
    let expected = node_int(42);
    let assert_eq = node_assert_eq().with_pull_eval();

    // Add nodes to graph
    let bang = g.add_node(Box::new(bang) as Box<dyn DebugNode>);
    let fn_node = g.add_node(Box::new(fn_node) as Box<_>);
    let apply_node = g.add_node(Box::new(apply_node) as Box<_>);
    let value = g.add_node(Box::new(value) as Box<_>);
    let list = g.add_node(Box::new(list) as Box<_>);
    let expected = g.add_node(Box::new(expected) as Box<_>);
    let assert_eq = g.add_node(Box::new(assert_eq) as Box<_>);

    // Bang triggers fn to emit lambda.
    g.add_edge(bang, fn_node, Edge::from((0, 0)));
    // Fn output (lambda) goes to apply's function input.
    g.add_edge(fn_node, apply_node, Edge::from((0, 0)));
    // Value goes to list to wrap it.
    g.add_edge(value, list, Edge::from((0, 0)));
    // List goes to apply's argument input.
    g.add_edge(list, apply_node, Edge::from((0, 1)));
    // Apply output goes to assert_eq.
    g.add_edge(apply_node, assert_eq, Edge::from((0, 0)));
    // Expected value goes to assert_eq.
    g.add_edge(expected, assert_eq, Edge::from((0, 1)));

    // Generate the module.
    let ctx = node::MetaCtx::new(&get_node);
    let eps = push_pull_entrypoints(&get_node, &g);
    let module = gantz_core::compile::module(&get_node, &g, &eps, &Default::default()).unwrap();

    // Create and setup VM.
    let mut vm = Engine::new_base();
    vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
    gantz_core::graph::register(&get_node, &g, &[], &mut vm);

    // Register all functions
    for expr in module {
        vm.run(expr.to_pretty(80)).unwrap();
    }

    // Execute pull evaluation from assert_eq
    let ep = entrypoint::pull(vec![assert_eq.index()], g[assert_eq].n_inputs(ctx) as u8);
    vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
        .unwrap();
}

// Test that Fn can wrap a graph node (not just a primitive) and Apply can call it.
// This tests that the NodeFns visitor correctly recurses into the graph via Fn's
// visit() delegation to generate the nested node functions.
//
// The "double" graph: inlet -> add (both inputs from inlet) -> outlet
// Result: double(x) = x + x
//
//    --------
//    | bang |
//    -+------
//     |
//     |
//    -+--------
//    |   fn   |  ------
//    | (dbl)  |  | 21 |
//    -+--------  -+----
//     |           |
//     |       -----
//     |       |
//    -+-------+-
//    |  apply  |
//    -+---------
//     |
//     |
//    ----------
//    | result |  (should be 42)
//    ----------
#[test]
fn test_fn_apply_graph() {
    // First, create the "double" graph: inlet -> add -> outlet
    let mut double_graph = graph::Graph::<Box<dyn DebugNode>>::default();

    let inlet = double_graph.add_node(Box::new(graph::Inlet::default()) as Box<dyn DebugNode>);
    let add = double_graph.add_node(Box::new(node::expr("(+ $l $r)").unwrap()) as Box<_>);
    let outlet = double_graph.add_node(Box::new(graph::Outlet::default()) as Box<_>);

    // Connect inlet to both inputs of add.
    double_graph.add_edge(inlet, add, Edge::from((0, 0)));
    double_graph.add_edge(inlet, add, Edge::from((0, 1)));
    // Connect add output to outlet.
    double_graph.add_edge(add, outlet, Edge::from((0, 0)));

    // The nested "double" graph is referenced by content address. A bare
    // `Graph` implements `Node`, so the `get_node` lookup returns it directly
    // (no inline `GraphNode` wrapper).
    let double_ca: gantz_ca::ContentAddr = gantz_ca::graph_addr(&double_graph).into();
    let get_node = |ca: &gantz_ca::ContentAddr| -> Option<&dyn Node> {
        (*ca == double_ca).then_some(&double_graph as &dyn Node)
    };

    // Now create the main graph that uses Fn<Ref> to wrap the double graph.
    let mut g = petgraph::graph::DiGraph::new();

    let bang = node_bang();
    let fn_node = Fn::new(Ref::new(double_ca));
    let apply_node = Apply;
    let value = node_int(21);
    let list = node_list_single();
    let expected = node_int(42);
    let assert_eq = node_assert_eq().with_pull_eval();

    let bang = g.add_node(Box::new(bang) as Box<dyn DebugNode>);
    let fn_node = g.add_node(Box::new(fn_node) as Box<_>);
    let apply_node = g.add_node(Box::new(apply_node) as Box<_>);
    let value = g.add_node(Box::new(value) as Box<_>);
    let list = g.add_node(Box::new(list) as Box<_>);
    let expected = g.add_node(Box::new(expected) as Box<_>);
    let assert_eq = g.add_node(Box::new(assert_eq) as Box<_>);

    // Bang triggers fn to emit lambda.
    g.add_edge(bang, fn_node, Edge::from((0, 0)));
    // Fn output (lambda) goes to apply's function input.
    g.add_edge(fn_node, apply_node, Edge::from((0, 0)));
    // Value goes to list to wrap it.
    g.add_edge(value, list, Edge::from((0, 0)));
    // List goes to apply's argument input.
    g.add_edge(list, apply_node, Edge::from((0, 1)));
    // Apply output goes to assert_eq.
    g.add_edge(apply_node, assert_eq, Edge::from((0, 0)));
    // Expected value goes to assert_eq.
    g.add_edge(expected, assert_eq, Edge::from((0, 1)));

    // Generate the module.
    let ctx = node::MetaCtx::new(&get_node);
    let eps = push_pull_entrypoints(&get_node, &g);
    let module = gantz_core::compile::module(&get_node, &g, &eps, &Default::default()).unwrap();

    // Create and setup VM.
    let mut vm = Engine::new_base();
    vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
    gantz_core::graph::register(&get_node, &g, &[], &mut vm);

    // Register all functions.
    for expr in module {
        vm.run(expr.to_pretty(80)).unwrap();
    }

    // Execute pull evaluation from assert_eq.
    let ep = entrypoint::pull(vec![assert_eq.index()], g[assert_eq].n_inputs(ctx) as u8);
    vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
        .unwrap();
}