use gantz_core::compile::{entry_fn_name, entrypoint, push_pull_entrypoints, push_source};
use gantz_core::node::{self, Node, WithPullEval, WithPushEval};
use gantz_core::{Edge, ROOT_STATE};
use std::fmt::Debug;
use steel::SteelVal;
use steel::steel_vm::engine::Engine;
fn node_push() -> node::Push<node::Expr> {
node::expr("'()").unwrap().with_push_eval()
}
fn node_int(i: i32) -> node::Expr {
node::expr(format!("(begin $push {})", i)).unwrap()
}
fn node_add() -> node::Expr {
node::expr("(+ $l $r)").unwrap()
}
fn node_assert_eq() -> node::Expr {
node::expr("(assert! (equal? $l $r))").unwrap()
}
fn node_number() -> node::Expr {
node::expr(
"
(let ((x $x))
(set! state (if (number? x) x state))
state)
",
)
.unwrap()
}
trait DebugNode: Debug + Node {}
impl<T> DebugNode for T where T: Debug + Node {}
fn no_lookup(_: &gantz_ca::ContentAddr) -> Option<&'static dyn Node> {
None
}
#[test]
fn test_graph_push_eval() {
let mut g = petgraph::graph::DiGraph::new();
let push = node_push();
let one = node_int(1);
let add = node_add();
let two = node_int(2);
let assert_eq = node_assert_eq();
let push = g.add_node(Box::new(push) as Box<dyn DebugNode>);
let one = g.add_node(Box::new(one) as Box<_>);
let add = g.add_node(Box::new(add) as Box<_>);
let two = g.add_node(Box::new(two) as Box<_>);
let assert_eq = g.add_node(Box::new(assert_eq) as Box<_>);
g.add_edge(push, one, Edge::from((0, 0)));
g.add_edge(push, two, Edge::from((0, 0)));
g.add_edge(one, add, Edge::from((0, 0)));
g.add_edge(one, add, Edge::from((0, 1)));
g.add_edge(add, assert_eq, Edge::from((0, 0)));
g.add_edge(two, assert_eq, Edge::from((0, 1)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
assert_eq!(module.len(), g.node_count() + 1);
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in module {
vm.run(format!("{f}")).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
}
#[test]
fn test_expr_trigger_input() {
let mut g = petgraph::graph::DiGraph::new();
let push = node_push();
let left = node::expr("(+ 1 2)").unwrap();
let right = node::expr("3").unwrap();
let assert_eq = node_assert_eq();
let push = g.add_node(Box::new(push) as Box<dyn DebugNode>);
let left = g.add_node(Box::new(left) as Box<_>);
let right = g.add_node(Box::new(right) as Box<_>);
let assert_eq = g.add_node(Box::new(assert_eq) as Box<_>);
g.add_edge(push, left, Edge::from((0, 0)));
g.add_edge(push, right, Edge::from((0, 0)));
g.add_edge(left, assert_eq, Edge::from((0, 0)));
g.add_edge(right, assert_eq, Edge::from((0, 1)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in module {
vm.run(format!("{f}")).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
}
#[test]
fn test_graph_pull_eval() {
let mut g = petgraph::graph::DiGraph::new();
let one = node_int(1);
let add = node_add();
let two = node_int(2);
let assert_eq = node_assert_eq().with_pull_eval();
let one = g.add_node(Box::new(one) as Box<dyn DebugNode>);
let add = g.add_node(Box::new(add) as Box<_>);
let two = g.add_node(Box::new(two) as Box<_>);
let assert_eq = g.add_node(Box::new(assert_eq) as Box<_>);
g.add_edge(one, add, Edge::from((0, 0)));
g.add_edge(one, add, Edge::from((0, 1)));
g.add_edge(add, assert_eq, Edge::from((0, 0)));
g.add_edge(two, assert_eq, Edge::from((0, 1)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for expr in module {
vm.run(expr.to_pretty(100)).unwrap();
}
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]
fn test_graph_push_cond_eval() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
let expr = format!(
r#"
(if (equal? 0 {x})
(list 0 '()) ; 0 index for left branch, '() for empty value
(list 1 '())) ; 1 index for right branch, '() for empty value
"#
);
node::parse_expr(&expr)
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = node_int(0).with_push_eval();
let push_1 = node_int(1).with_push_eval();
let select = Select;
let six = node_int(6);
let seven = node_int(7);
let number = node_number();
let push_0 = g.add_node(Box::new(push_0) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(push_1) as Box<_>);
let select = g.add_node(Box::new(select) as Box<_>);
let six = g.add_node(Box::new(six) as Box<_>);
let seven = g.add_node(Box::new(seven) as Box<_>);
let number = g.add_node(Box::new(number) as Box<_>);
g.add_edge(push_0, select, Edge::from((0, 0)));
g.add_edge(push_1, select, Edge::from((0, 0)));
g.add_edge(select, six, Edge::from((0, 0)));
g.add_edge(select, seven, Edge::from((1, 0)));
g.add_edge(six, number, Edge::from((0, 0)));
g.add_edge(seven, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
assert_eq!(module.len(), g.node_count() + 2);
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let number_state = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract number state")
.expect("number state was `None`");
assert_eq!(number_state, 6);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let number_state = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract number state")
.expect("number state was `None`");
assert_eq!(number_state, 7);
}
#[test]
fn test_graph_cond_eval_no_join_duplication() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!("(if (equal? 0 {x}) (list 0 '()) (list 1 '()))"))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let _push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<dyn DebugNode>);
let select = g.add_node(Box::new(Select) as Box<_>);
let six = g.add_node(Box::new(node_int(6)) as Box<_>);
let seven = g.add_node(Box::new(node_int(7)) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select, Edge::from((0, 0)));
g.add_edge(select, six, Edge::from((0, 0)));
g.add_edge(select, seven, Edge::from((1, 0)));
g.add_edge(six, number, Edge::from((0, 0)));
g.add_edge(seven, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let ep =
gantz_core::compile::entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
let module = gantz_core::compile::module(&no_lookup, &g, &[ep], &Default::default()).unwrap();
let module_str = module
.iter()
.map(|e| format!("{e}"))
.collect::<Vec<_>>()
.join("\n");
let number_fn_prefix = format!("node-fn-{}", number.index());
let count = module_str.matches(&number_fn_prefix).count();
assert!(
count <= 2,
"number node fn '{}' appears {} times - join is duplicated!\nGenerated module:\n{}",
number_fn_prefix,
count,
module_str,
);
}
#[test]
fn test_graph_branch_target_is_join() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!("(if (equal? 0 {x}) (list 0 6) (list 1 {x}))"))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<dyn DebugNode>);
let select = g.add_node(Box::new(Select) as Box<_>);
let seven = g.add_node(Box::new(node_int(7)) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select, Edge::from((0, 0)));
g.add_edge(push_1, select, Edge::from((0, 0)));
g.add_edge(select, number, Edge::from((0, 0))); g.add_edge(select, seven, Edge::from((1, 0))); g.add_edge(seven, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let state = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("state was None");
assert_eq!(state, 6);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let state = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("state was None");
assert_eq!(state, 7);
let module_str = module
.iter()
.map(|e| format!("{e}"))
.collect::<Vec<_>>()
.join("\n");
let number_fn_prefix = format!("node-fn-{}", number.index());
let count = module_str.matches(&number_fn_prefix).count();
assert!(
count <= 3, "number node fn appears {} times - join duplicated!\n{}",
count,
module_str,
);
}
#[test]
fn test_graph_branch_both_outputs_same_target() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!("(if (equal? 0 {x}) (list 0 42) (list 1 99))"))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<_>);
let select = g.add_node(Box::new(Select) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select, Edge::from((0, 0)));
g.add_edge(push_1, select, Edge::from((0, 0)));
g.add_edge(select, number, Edge::from((0, 0)));
g.add_edge(select, number, Edge::from((1, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("number was None");
assert_eq!(val, 42);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("number was None");
assert_eq!(val, 99);
}
#[test]
fn test_graph_nested_diamond() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!("(if (equal? 0 {x}) (list 0 '()) (list 1 '()))"))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<dyn DebugNode>);
let select_outer = g.add_node(Box::new(Select) as Box<_>);
let select_inner = g.add_node(Box::new(Select) as Box<_>);
let six = g.add_node(Box::new(node_int(6)) as Box<_>);
let seven = g.add_node(Box::new(node_int(7)) as Box<_>);
let inner_result = g.add_node(Box::new(node_number()) as Box<_>);
let eight = g.add_node(Box::new(node_int(8)) as Box<_>);
let outer_result = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select_outer, Edge::from((0, 0)));
g.add_edge(push_1, select_outer, Edge::from((0, 0)));
g.add_edge(select_outer, select_inner, Edge::from((0, 0)));
g.add_edge(select_outer, eight, Edge::from((1, 0)));
g.add_edge(select_inner, six, Edge::from((0, 0)));
g.add_edge(select_inner, seven, Edge::from((1, 0)));
g.add_edge(six, inner_result, Edge::from((0, 0)));
g.add_edge(seven, inner_result, Edge::from((0, 0)));
g.add_edge(inner_result, outer_result, Edge::from((0, 0)));
g.add_edge(eight, outer_result, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let inner = node::state::extract::<u32>(&vm, &[inner_result.index()])
.expect("failed to extract")
.expect("inner_result state was None");
assert_eq!(inner, 7);
let outer = node::state::extract::<u32>(&vm, &[outer_result.index()])
.expect("failed to extract")
.expect("outer_result state was None");
assert_eq!(outer, 7);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let inner = node::state::extract::<u32>(&vm, &[inner_result.index()])
.expect("failed to extract")
.expect("inner_result state was None");
assert_eq!(inner, 7); let outer = node::state::extract::<u32>(&vm, &[outer_result.index()])
.expect("failed to extract")
.expect("outer_result state was None");
assert_eq!(outer, 8);
let module_str = module
.iter()
.map(|e| format!("{e}"))
.collect::<Vec<_>>()
.join("\n");
for (name, ix) in [
("inner_result", inner_result.index()),
("outer_result", outer_result.index()),
] {
let prefix = format!("node-fn-{ix}");
let count = module_str.matches(&prefix).count();
assert!(
count <= 3, "{name} fn '{prefix}' appears {count} times - join duplicated!\n{module_str}",
);
}
}
#[test]
fn test_graph_lattice_reconvergence() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!("(if (equal? 0 {x}) (list 0 '()) (list 1 '()))"))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<dyn DebugNode>);
let select_outer = g.add_node(Box::new(Select) as Box<_>);
let sel_l = g.add_node(Box::new(Select) as Box<_>);
let sel_r = g.add_node(Box::new(Select) as Box<_>);
let six = g.add_node(Box::new(node_int(6)) as Box<_>);
let seven = g.add_node(Box::new(node_int(7)) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select_outer, Edge::from((0, 0)));
g.add_edge(push_1, select_outer, Edge::from((0, 0)));
g.add_edge(select_outer, sel_l, Edge::from((0, 0)));
g.add_edge(select_outer, sel_r, Edge::from((1, 0)));
g.add_edge(sel_l, six, Edge::from((0, 0)));
g.add_edge(sel_l, seven, Edge::from((1, 0)));
g.add_edge(sel_r, six, Edge::from((0, 0)));
g.add_edge(sel_r, seven, Edge::from((1, 0)));
g.add_edge(six, number, Edge::from((0, 0)));
g.add_edge(seven, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let state = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("state was None");
assert_eq!(state, 7);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let state = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("state was None");
assert_eq!(state, 7);
let module_str = module
.iter()
.map(|e| format!("{e}"))
.collect::<Vec<_>>()
.join("\n");
let number_fn_prefix = format!("node-fn-{}", number.index());
let count = module_str.matches(&number_fn_prefix).count();
assert!(
count <= 3, "number fn '{number_fn_prefix}' appears {count} times - join duplicated!\n{module_str}",
);
}
#[test]
#[should_panic]
fn test_graph_eval_should_panic() {
let mut g = petgraph::graph::DiGraph::new();
let one = node_int(1);
let add = node_add();
let assert_eq = node_assert_eq().with_pull_eval();
let one = g.add_node(Box::new(one) as Box<dyn DebugNode>);
let add = g.add_node(Box::new(add) as Box<_>);
let assert_eq = g.add_node(Box::new(assert_eq) as Box<_>);
g.add_edge(one, add, Edge::from((0, 0)));
g.add_edge(one, add, Edge::from((0, 1)));
g.add_edge(add, assert_eq, Edge::from((0, 0)));
g.add_edge(one, assert_eq, Edge::from((0, 1)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for expr in module {
vm.run(expr.to_pretty(100)).unwrap();
}
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]
#[ignore = "Originally attempted to get this working with push/pull eval \
configurations, but realising it would be cleaner to get general conditional \
eval working first."]
fn test_graph_push_eval_subset() {
let mut g = petgraph::graph::DiGraph::new();
#[derive(Debug)]
struct Src(u32, u32);
impl Node for Src {
fn push_eval(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
node::EvalConf::Set([true, true].try_into().unwrap()),
]
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let Src(a, b) = *self;
let outputs = ctx.outputs();
let expr = match (outputs.get(0).unwrap(), outputs.get(1).unwrap()) {
(true, false) => format!("(begin {a})"),
(false, true) => format!("(begin {b})"),
_ => format!("(list {a} {b})"),
};
node::parse_expr(&expr)
}
}
let source = Src(6, 7);
let store_a = node::expr("(begin (set! state $x) state)").unwrap();
let store_b = node::expr("(begin (set! state $x) state)").unwrap();
let source = g.add_node(Box::new(source) as Box<dyn DebugNode>);
let store_a = g.add_node(Box::new(store_a) as Box<_>);
let store_b = g.add_node(Box::new(store_b) as Box<_>);
g.add_edge(source, store_a, Edge::from((0, 0)));
g.add_edge(source, store_b, Edge::from((1, 0)));
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in module {
vm.run(f.to_pretty(100)).unwrap();
}
let ep = &eps[0]; vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
let store_a_val = node::state::extract::<i32>(&vm, &[store_a.index()]).unwrap();
let store_b_val = node::state::extract::<i32>(&vm, &[store_b.index()]).unwrap();
assert_eq!(store_a_val, Some(6));
assert_eq!(store_b_val, None);
}
#[test]
fn test_graph_multi_source_push() {
let mut g = petgraph::graph::DiGraph::new();
let push_a = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let int_a = g.add_node(Box::new(node_int(42)) as Box<_>);
let num_a = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_a, int_a, Edge::from((0, 0)));
g.add_edge(int_a, num_a, Edge::from((0, 0)));
let push_b = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let int_b = g.add_node(Box::new(node_int(7)) as Box<_>);
let num_b = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_b, int_b, Edge::from((0, 0)));
g.add_edge(int_b, num_b, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let combined = entrypoint::from_sources([
push_source(vec![push_a.index()], g[push_a].n_outputs(ctx) as u8),
push_source(vec![push_b.index()], g[push_b].n_outputs(ctx) as u8),
]);
let module =
gantz_core::compile::module(&no_lookup, &g, &[combined.clone()], &Default::default())
.unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let fn_name = entry_fn_name(&combined.id());
vm.call_function_by_name_with_args(&fn_name, vec![])
.unwrap();
let a = node::state::extract::<u32>(&vm, &[num_a.index()])
.expect("failed to extract num_a state")
.expect("num_a state was None");
let b = node::state::extract::<u32>(&vm, &[num_b.index()])
.expect("failed to extract num_b state")
.expect("num_b state was None");
assert_eq!(a, 42);
assert_eq!(b, 7);
}
#[test]
fn test_entrypoint_naming_consistency() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let int = g.add_node(Box::new(node_int(1)) as Box<_>);
g.add_edge(push, int, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let manual = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
let default_ep = eps
.iter()
.find(|ep| ep.0.iter().any(|s| s.path == vec![push.index()]))
.expect("push_pull_entrypoints should contain push node");
assert_eq!(default_ep.id(), manual.id());
assert_eq!(entry_fn_name(&default_ep.id()), entry_fn_name(&manual.id()));
}
#[test]
fn test_graph_multi_output_expr() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let pair = node::expr("(begin $push (list 6 7))")
.unwrap()
.with_outputs(2);
let pair = g.add_node(Box::new(pair) as Box<_>);
let num_a = g.add_node(Box::new(node_number()) as Box<_>);
let num_b = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push, pair, Edge::from((0, 0)));
g.add_edge(pair, num_a, Edge::from((0, 0))); g.add_edge(pair, num_b, Edge::from((1, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
let a = node::state::extract::<u32>(&vm, &[num_a.index()])
.expect("failed to extract num_a state")
.expect("num_a state was None");
let b = node::state::extract::<u32>(&vm, &[num_b.index()])
.expect("failed to extract num_b state")
.expect("num_b state was None");
assert_eq!(a, 6);
assert_eq!(b, 7);
}
#[test]
fn test_graph_zero_output_leaf_nodes() {
#[derive(Debug)]
struct Effect;
impl Node for Effect {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let input = ctx.inputs()[0].as_deref().unwrap_or("'()");
node::parse_expr(&format!("(begin {input} '())"))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let effect1 = g.add_node(Box::new(Effect) as Box<dyn DebugNode>);
let effect2 = g.add_node(Box::new(Effect) as Box<dyn DebugNode>);
g.add_edge(push, effect1, Edge::from((0, 0)));
g.add_edge(push, effect2, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(f.to_pretty(100)).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
}
#[test]
fn test_graph_branch_node() {
let branch = node::Branch::new(
"(if (equal? 0 $x) (list 0 '()) (list 1 '()))",
vec![
node::Conns::try_from([true, false]).unwrap(),
node::Conns::try_from([false, true]).unwrap(),
],
)
.unwrap();
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<_>);
let branch_ix = g.add_node(Box::new(branch) as Box<_>);
let six = g.add_node(Box::new(node_int(6)) as Box<_>);
let seven = g.add_node(Box::new(node_int(7)) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, branch_ix, Edge::from((0, 0)));
g.add_edge(push_1, branch_ix, Edge::from((0, 0)));
g.add_edge(branch_ix, six, Edge::from((0, 0)));
g.add_edge(branch_ix, seven, Edge::from((1, 0)));
g.add_edge(six, number, Edge::from((0, 0)));
g.add_edge(seven, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 6);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 7);
}
#[test]
fn test_graph_multi_edge_input_list() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let three = g.add_node(Box::new(node_int(3)) as Box<_>);
let four = g.add_node(Box::new(node_int(4)) as Box<_>);
let sum = g.add_node(Box::new(node::expr("(apply + $x)").unwrap()) as Box<_>);
let store = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push, three, Edge::from((0, 0)));
g.add_edge(push, four, Edge::from((0, 0)));
g.add_edge(three, sum, Edge::from((0, 0)));
g.add_edge(four, sum, Edge::from((0, 0)));
g.add_edge(sum, store, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let module_str = module
.iter()
.map(|e| format!("{e}"))
.collect::<Vec<_>>()
.join("\n");
assert!(
module_str.contains("(list"),
"expected a (list ...) binding for multi-edge input\n{module_str}",
);
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[store.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 7);
}
#[test]
fn test_graph_branch_divergent_terminal() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!("(if (equal? 0 {x}) (list 0 42) (list 1 99))"))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<_>);
let select = g.add_node(Box::new(Select) as Box<_>);
let store_a = g.add_node(Box::new(node_number()) as Box<_>);
let store_b = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select, Edge::from((0, 0)));
g.add_edge(push_1, select, Edge::from((0, 0)));
g.add_edge(select, store_a, Edge::from((0, 0)));
g.add_edge(select, store_b, Edge::from((1, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let val_a = node::state::extract::<u32>(&vm, &[store_a.index()])
.expect("failed to extract")
.expect("store_a was None");
assert_eq!(val_a, 42);
let val_b = node::state::extract::<u32>(&vm, &[store_b.index()])
.ok()
.flatten();
assert!(val_b.is_none(), "store_b should not have been evaluated");
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let val_a = node::state::extract::<u32>(&vm, &[store_a.index()])
.expect("failed to extract")
.expect("store_a was None");
assert_eq!(val_a, 42, "store_a should be unchanged");
let val_b = node::state::extract::<u32>(&vm, &[store_b.index()])
.expect("failed to extract")
.expect("store_b was None");
assert_eq!(val_b, 99);
}
#[test]
fn test_graph_multi_edge_in_branch_arm() {
#[derive(Debug)]
struct Select;
impl Node for Select {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
2
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false].try_into().unwrap()),
node::EvalConf::Set([false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!(
"(if (equal? 0 {x}) (list 0 '() '()) (list 1 '() '()))"
))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<_>);
let select = g.add_node(Box::new(Select) as Box<_>);
let three = g.add_node(Box::new(node_int(3)) as Box<_>);
let four = g.add_node(Box::new(node_int(4)) as Box<_>);
let sum = g.add_node(Box::new(node::expr("(apply + $x)").unwrap()) as Box<_>);
let eight = g.add_node(Box::new(node_int(8)) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select, Edge::from((0, 0)));
g.add_edge(push_1, select, Edge::from((0, 0)));
g.add_edge(select, three, Edge::from((0, 0)));
g.add_edge(select, four, Edge::from((0, 0)));
g.add_edge(three, sum, Edge::from((0, 0)));
g.add_edge(four, sum, Edge::from((0, 0)));
g.add_edge(select, eight, Edge::from((1, 0)));
g.add_edge(sum, number, Edge::from((0, 0)));
g.add_edge(eight, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 7);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 8);
}
#[test]
fn test_graph_optional_input_unconnected() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_int(5).with_push_eval()) as Box<dyn DebugNode>);
let add_opt = g.add_node(Box::new(
node::expr("(+ $a (if (Some? $?b) (Some->value $?b) 0))").unwrap(),
) as Box<_>);
let store = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push, add_opt, Edge::from((0, 0)));
g.add_edge(add_opt, store, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[store.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 5);
}
#[test]
fn test_graph_optional_input_connected() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_int(5).with_push_eval()) as Box<dyn DebugNode>);
let three = g.add_node(Box::new(node_int(3)) as Box<_>);
let add_opt = g.add_node(Box::new(
node::expr("(+ $a (if (Some? $?b) (Some->value $?b) 0))").unwrap(),
) as Box<_>);
let store = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push, add_opt, Edge::from((0, 0)));
g.add_edge(push, three, Edge::from((0, 0)));
g.add_edge(three, add_opt, Edge::from((0, 1)));
g.add_edge(add_opt, store, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[store.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 8);
}
#[test]
fn test_graph_three_way_branch() {
#[derive(Debug)]
struct Select3;
impl Node for Select3 {
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
3
}
fn branches(&self, _ctx: node::MetaCtx) -> Vec<node::EvalConf> {
vec![
node::EvalConf::Set([true, false, false].try_into().unwrap()),
node::EvalConf::Set([false, true, false].try_into().unwrap()),
node::EvalConf::Set([false, false, true].try_into().unwrap()),
]
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
let x = ctx.inputs()[0].as_deref().expect("must have one input");
node::parse_expr(&format!(
"(if (equal? 0 {x}) (list 0 '() '() '()) \
(if (equal? 1 {x}) (list 1 '() '() '()) \
(list 2 '() '() '())))"
))
}
}
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<_>);
let push_2 = g.add_node(Box::new(node_int(2).with_push_eval()) as Box<_>);
let select = g.add_node(Box::new(Select3) as Box<_>);
let six = g.add_node(Box::new(node_int(6)) as Box<_>);
let seven = g.add_node(Box::new(node_int(7)) as Box<_>);
let eight = g.add_node(Box::new(node_int(8)) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, select, Edge::from((0, 0)));
g.add_edge(push_1, select, Edge::from((0, 0)));
g.add_edge(push_2, select, Edge::from((0, 0)));
g.add_edge(select, six, Edge::from((0, 0)));
g.add_edge(select, seven, Edge::from((1, 0)));
g.add_edge(select, eight, Edge::from((2, 0)));
g.add_edge(six, number, Edge::from((0, 0)));
g.add_edge(seven, number, Edge::from((0, 0)));
g.add_edge(eight, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 6);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 7);
let ep_2 = entrypoint::push(vec![push_2.index()], g[push_2].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_2.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 8);
}
#[test]
fn test_graph_branch_single_output_dead_branch() {
let branch = node::Branch::new(
"(if (equal? 0 $x) (list 0 42) (list 1 99))",
vec![
node::Conns::try_from([true]).unwrap(),
node::Conns::try_from([false]).unwrap(),
],
)
.unwrap();
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<_>);
let branch_ix = g.add_node(Box::new(branch) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, branch_ix, Edge::from((0, 0)));
g.add_edge(push_1, branch_ix, Edge::from((0, 0)));
g.add_edge(branch_ix, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 42);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(
val, 42,
"number should still be 42 - dead branch must not propagate"
);
}
#[test]
fn test_graph_branch_two_outputs_one_dead() {
let branch = node::Branch::new(
"(if (equal? 0 $x) (list 0 (list 42 43)) (list 1 (list 99 100)))",
vec![
node::Conns::try_from([true, true]).unwrap(),
node::Conns::try_from([false, false]).unwrap(),
],
)
.unwrap();
let mut g = petgraph::graph::DiGraph::new();
let push_0 = g.add_node(Box::new(node_int(0).with_push_eval()) as Box<dyn DebugNode>);
let push_1 = g.add_node(Box::new(node_int(1).with_push_eval()) as Box<_>);
let branch_ix = g.add_node(Box::new(branch) as Box<_>);
let store_a = g.add_node(Box::new(node_number()) as Box<_>);
let store_b = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_0, branch_ix, Edge::from((0, 0)));
g.add_edge(push_1, branch_ix, Edge::from((0, 0)));
g.add_edge(branch_ix, store_a, Edge::from((0, 0)));
g.add_edge(branch_ix, store_b, Edge::from((1, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_0 = entrypoint::push(vec![push_0.index()], g[push_0].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_0.id()), vec![])
.unwrap();
let val_a = node::state::extract::<u32>(&vm, &[store_a.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val_a, 42);
let val_b = node::state::extract::<u32>(&vm, &[store_b.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val_b, 43);
let ep_1 = entrypoint::push(vec![push_1.index()], g[push_1].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_1.id()), vec![])
.unwrap();
let val_a = node::state::extract::<u32>(&vm, &[store_a.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val_a, 42, "store_a should be unchanged after dead branch");
let val_b = node::state::extract::<u32>(&vm, &[store_b.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val_b, 43, "store_b should be unchanged after dead branch");
}
#[test]
fn test_graph_branch_all_dead() {
let branch = node::Branch::new(
"(begin (set! state $x) (if (equal? 0 $x) (list 0 '()) (list 1 '())))",
vec![
node::Conns::try_from([false]).unwrap(),
node::Conns::try_from([false]).unwrap(),
],
)
.unwrap();
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_int(42).with_push_eval()) as Box<dyn DebugNode>);
let branch_ix = g.add_node(Box::new(branch) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push, branch_ix, Edge::from((0, 0)));
g.add_edge(branch_ix, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep = entrypoint::push(vec![push.index()], g[push].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep.id()), vec![])
.unwrap();
let branch_state = node::state::extract::<u32>(&vm, &[branch_ix.index()])
.expect("failed to extract")
.expect("branch state was None");
assert_eq!(branch_state, 42);
let number_state = node::state::extract::<u32>(&vm, &[number.index()])
.ok()
.flatten();
assert!(
number_state.is_none(),
"number should not have been evaluated"
);
}
#[test]
fn test_graph_branch_optional_input_pd_add() {
let pd_add = node::Branch::new(
"(begin \
(if (Some? $?b) (set! state (Some->value $?b)) '()) \
(if (number? $a) \
(list 0 (+ $a (if (number? state) state 0))) \
(list 1 '())))",
vec![
node::Conns::try_from([true]).unwrap(), node::Conns::try_from([false]).unwrap(), ],
)
.unwrap();
let mut g = petgraph::graph::DiGraph::new();
let push_hot = g.add_node(Box::new(node_int(5).with_push_eval()) as Box<dyn DebugNode>);
let push_cold = g.add_node(Box::new(node_int(3).with_push_eval()) as Box<_>);
let pd_add_ix = g.add_node(Box::new(pd_add) as Box<_>);
let number = g.add_node(Box::new(node_number()) as Box<_>);
g.add_edge(push_cold, pd_add_ix, Edge::from((0, 0)));
g.add_edge(push_hot, pd_add_ix, Edge::from((0, 1)));
g.add_edge(pd_add_ix, number, Edge::from((0, 0)));
let ctx = node::MetaCtx::new(&no_lookup);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Default::default()).unwrap();
let mut vm = Engine::new_base();
vm.register_value(ROOT_STATE, SteelVal::empty_hashmap());
gantz_core::graph::register(&no_lookup, &g, &[], &mut vm);
for f in &module {
vm.run(format!("{f}")).unwrap();
}
let ep_cold = entrypoint::push(vec![push_cold.index()], g[push_cold].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_cold.id()), vec![])
.unwrap();
let number_state = node::state::extract::<u32>(&vm, &[number.index()])
.ok()
.flatten();
assert!(
number_state.is_none(),
"number should not run on cold inlet push"
);
let ep_hot = entrypoint::push(vec![push_hot.index()], g[push_hot].n_outputs(ctx) as u8);
vm.call_function_by_name_with_args(&entry_fn_name(&ep_hot.id()), vec![])
.unwrap();
let val = node::state::extract::<u32>(&vm, &[number.index()])
.expect("failed to extract")
.expect("was None");
assert_eq!(val, 8, "hot inlet should emit 5 + 3 = 8");
}