use gantz_core::{
Edge,
compile::{Name, SourceMap, push_pull_entrypoints},
node::{self, Node, WithPushEval},
};
use std::fmt::Debug;
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_mul() -> node::Expr {
node::expr("(* $l $r)").unwrap()
}
fn node_add() -> node::Expr {
node::expr("(+ $l $r)").unwrap()
}
trait DebugNode: Debug + Node {}
impl<T> DebugNode for T where T: Debug + Node {}
type Nested = node::graph::Graph<Box<dyn DebugNode>>;
fn no_lookup(_: &gantz_ca::ContentAddr) -> Option<&'static dyn Node> {
None
}
#[test]
fn source_map_roundtrip() {
let mut ga = Nested::default();
let inlet_a = ga.add_node(Box::new(node::graph::Inlet::default()) as Box<dyn DebugNode>);
let inlet_b = ga.add_node(Box::new(node::graph::Inlet::default()) as Box<_>);
let mul = ga.add_node(Box::new(node_mul()) as Box<_>);
let outlet = ga.add_node(Box::new(node::graph::Outlet::default()) as Box<_>);
ga.add_edge(inlet_a, mul, Edge::from((0, 0)));
ga.add_edge(inlet_b, mul, Edge::from((0, 1)));
ga.add_edge(mul, outlet, Edge::from((0, 0)));
let mut gb = petgraph::graph::DiGraph::new();
let push = gb.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let six = gb.add_node(Box::new(node_int(6)) as Box<_>);
let seven = gb.add_node(Box::new(node_int(7)) as Box<_>);
let graph_a = gb.add_node(Box::new(ga) as Box<_>);
let forty_two = gb.add_node(Box::new(node_int(42)) as Box<_>);
let add = gb.add_node(Box::new(node_add()) as Box<_>);
gb.add_edge(push, six, Edge::from((0, 0)));
gb.add_edge(push, seven, Edge::from((0, 0)));
gb.add_edge(push, forty_two, Edge::from((0, 0)));
gb.add_edge(six, graph_a, Edge::from((0, 0)));
gb.add_edge(seven, graph_a, Edge::from((0, 1)));
gb.add_edge(graph_a, add, Edge::from((0, 0)));
gb.add_edge(forty_two, add, Edge::from((0, 1)));
let eps = push_pull_entrypoints(&no_lookup, &gb);
let module = gantz_core::compile::module(&no_lookup, &gb, &eps, &Default::default()).unwrap();
let src = gantz_core::vm::fmt_module(&module);
let map = SourceMap::parse(&src);
assert_eq!(map.defs().len(), module.len());
for def in map.defs() {
assert!(
def.name.is_some(),
"unrecognised define `{}`",
&src[def.name_range.clone()]
);
assert!(src[def.range.clone()].starts_with("(define"));
}
let kind_count = |f: &dyn Fn(&Name) -> bool| {
map.defs()
.iter()
.filter(|d| d.name.as_ref().is_some_and(f))
.count()
};
assert!(kind_count(&|n| matches!(n, Name::NodeFn { .. })) >= 4);
assert_eq!(kind_count(&|n| matches!(n, Name::GraphFn { .. })), 1);
assert_eq!(kind_count(&|n| matches!(n, Name::EntryFn { .. })), 1);
let mul_path = vec![graph_a.index(), mul.index()];
let mul_spans = map.node_spans(&mul_path);
assert_eq!(mul_spans.defs.len(), 1, "one node fn def for mul");
assert!(!mul_spans.refs.is_empty(), "mul referenced in its level");
for range in mul_spans.defs.iter().chain(&mul_spans.refs) {
assert_eq!(map.node_at(range.clone()), Some(mul_path.clone()));
}
assert!(src[mul_spans.defs[0].clone()].starts_with("(define node-fn-"));
assert!(src[mul_spans.defs[0].clone()].contains("*"));
let ga_path = vec![graph_a.index()];
let ga_spans = map.node_spans(&ga_path);
assert_eq!(ga_spans.defs.len(), 2, "node fn wrapper + graph fn");
assert!(!ga_spans.refs.is_empty(), "graph fn called from entry fn");
for range in &ga_spans.refs {
assert_eq!(map.node_at(range.clone()), Some(ga_path.clone()));
}
let add_path = vec![add.index()];
let add_spans = map.node_spans(&add_path);
assert_eq!(add_spans.defs.len(), 1);
assert!(!add_spans.refs.is_empty());
for range in &add_spans.refs {
assert_eq!(map.node_at(range.clone()), Some(add_path.clone()));
}
}
#[test]
fn source_map_level_fn() {
let mut ga = Nested::default();
let push = ga.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let int = ga.add_node(Box::new(node_int(7)) as Box<_>);
let outlet = ga.add_node(Box::new(node::graph::Outlet::default()) as Box<_>);
ga.add_edge(push, int, Edge::from((0, 0)));
ga.add_edge(int, outlet, Edge::from((0, 0)));
let mut gb = petgraph::graph::DiGraph::new();
let graph_a = gb.add_node(Box::new(ga) as Box<dyn DebugNode>);
let add = gb.add_node(Box::new(node_add()) as Box<_>);
gb.add_edge(graph_a, add, Edge::from((0, 0)));
let eps = push_pull_entrypoints(&no_lookup, &gb);
let module = gantz_core::compile::module(&no_lookup, &gb, &eps, &Default::default()).unwrap();
let src = gantz_core::vm::fmt_module(&module);
let map = SourceMap::parse(&src);
let ga_path = vec![graph_a.index()];
let lvl_defs: Vec<_> = map
.defs()
.iter()
.filter(|d| matches!(&d.name, Some(Name::LvlFn { path, .. }) if *path == ga_path))
.collect();
assert_eq!(lvl_defs.len(), 1, "one level fn for the nested push");
let spans = map.node_spans(&ga_path);
assert!(spans.defs.iter().any(|r| *r == lvl_defs[0].range));
assert!(!spans.refs.is_empty(), "level fn called from entry fn");
assert_eq!(
map.node_at(lvl_defs[0].range.clone()),
Some(ga_path.clone())
);
let push_path = vec![graph_a.index(), push.index()];
let int_path = vec![graph_a.index(), int.index()];
for path in [&push_path, &int_path] {
let spans = map.node_spans(path);
assert!(
!(spans.defs.is_empty() && spans.refs.is_empty()),
"no spans for {path:?}"
);
for range in spans.defs.iter().chain(&spans.refs) {
assert_eq!(map.node_at(range.clone()), Some(path.clone()));
}
}
}