use gantz_core::{
Edge,
compile::{Config, ModuleError, SourceMap, entry_fn_name, push_pull_entrypoints},
node::{self, ExprCtx, ExprResult, MetaCtx, Node, RegCtx, WithPushEval},
};
use std::fmt::Debug;
trait DebugNode: Debug + Node {}
impl<T> DebugNode for T where T: Debug + Node {}
fn no_lookup(_: &gantz_ca::ContentAddr) -> Option<&'static dyn Node> {
None
}
fn node_push() -> node::Push<node::Expr> {
node::expr("'()").unwrap().with_push_eval()
}
#[derive(Debug)]
struct RequiresNode {
expr: node::Expr,
modules: Vec<String>,
}
fn node_requires(src: &str, modules: &[&str]) -> RequiresNode {
RequiresNode {
expr: node::expr(src).unwrap(),
modules: modules.iter().map(|m| m.to_string()).collect(),
}
}
impl Node for RequiresNode {
fn n_inputs(&self, ctx: MetaCtx) -> usize {
self.expr.n_inputs(ctx)
}
fn n_outputs(&self, ctx: MetaCtx) -> usize {
self.expr.n_outputs(ctx)
}
fn expr(&self, ctx: ExprCtx<'_, '_>) -> ExprResult {
self.expr.expr(ctx)
}
fn stateful(&self, ctx: MetaCtx) -> bool {
self.expr.stateful(ctx)
}
fn register(&self, ctx: RegCtx<'_, '_>) {
self.expr.register(ctx)
}
fn required_modules(&self, _ctx: MetaCtx) -> Vec<String> {
self.modules.clone()
}
}
#[test]
fn no_requires_without_declarations() {
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::expr("(begin $push 6)").unwrap()) as Box<_>);
g.add_edge(push, int, Edge::from((0, 0)));
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Config::default()).unwrap();
let src = gantz_core::vm::fmt_module(&module);
assert!(!src.contains("(require"));
}
#[test]
fn requires_deduped_and_lead_the_module() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let a = g.add_node(Box::new(node_requires(
"(begin $push (unwrap-or (Some 1) 0))",
&["gantz/option"],
)) as Box<_>);
let b =
g.add_node(Box::new(node_requires("(unwrap-or (Some $x) 0)", &["gantz/option"])) as Box<_>);
g.add_edge(push, a, Edge::from((0, 0)));
g.add_edge(a, b, Edge::from((0, 0)));
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &Config::default()).unwrap();
let src = gantz_core::vm::fmt_module(&module);
assert_eq!(src.matches("gantz/option").count(), 1);
assert!(src.starts_with("(require"));
let map = SourceMap::parse(&src);
assert_eq!(map.defs().len(), module.len());
let named = map.defs().iter().filter(|d| d.name.is_some()).count();
assert_eq!(named, module.len() - 1);
for n in [a, b] {
let path = vec![n.index()];
let spans = map.node_spans(&path);
assert!(!spans.defs.is_empty(), "no defs for {path:?}");
for range in spans.defs.iter().chain(&spans.refs) {
assert_eq!(map.node_at(range.clone()), Some(path.clone()));
}
}
}
#[test]
fn off_eval_path_node_still_requires_its_module() {
for config in [
Config::default(),
Config {
emit_all_node_fns: true,
..Config::default()
},
] {
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::expr("(begin $push 6)").unwrap()) as Box<_>);
g.add_edge(push, int, Edge::from((0, 0)));
let _orphan = g
.add_node(
Box::new(node_requires("(unwrap-or (Some $x) 0)", &["gantz/option"])) as Box<_>,
);
let eps = push_pull_entrypoints(&no_lookup, &g);
let module = gantz_core::compile::module(&no_lookup, &g, &eps, &config).unwrap();
let src = gantz_core::vm::fmt_module(&module);
assert_eq!(src.matches("gantz/option").count(), 1);
gantz_core::vm::init(&no_lookup, &g, &eps, &config).unwrap();
}
}
#[test]
fn required_module_bindings_evaluate() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let unwrap = g.add_node(Box::new(node_requires(
"(begin $push (unwrap-or (Some 40) 2))",
&["gantz/option"],
)) as Box<_>);
let check = g.add_node(Box::new(node::expr("(assert! (equal? $x 40))").unwrap()) as Box<_>);
g.add_edge(push, unwrap, Edge::from((0, 0)));
g.add_edge(unwrap, check, Edge::from((0, 0)));
let eps = push_pull_entrypoints(&no_lookup, &g);
let (mut vm, _compiled) =
gantz_core::vm::init(&no_lookup, &g, &eps, &Config::default()).unwrap();
let fn_name = entry_fn_name(&eps[0].id());
vm.call_function_by_name_with_args(&fn_name, vec![])
.unwrap();
}
#[test]
fn expr_requires_field_evaluates() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let unwrap = g.add_node(Box::new(
node::expr("(begin $push (unwrap-or (None) 42))")
.unwrap()
.with_requires(["gantz/option"]),
) as Box<_>);
let check = g.add_node(Box::new(node::expr("(assert! (equal? $x 42))").unwrap()) as Box<_>);
g.add_edge(push, unwrap, Edge::from((0, 0)));
g.add_edge(unwrap, check, Edge::from((0, 0)));
let eps = push_pull_entrypoints(&no_lookup, &g);
let (mut vm, compiled) =
gantz_core::vm::init(&no_lookup, &g, &eps, &Config::default()).unwrap();
assert!(compiled.src.starts_with("(require"));
let fn_name = entry_fn_name(&eps[0].id());
vm.call_function_by_name_with_args(&fn_name, vec![])
.unwrap();
}
#[test]
fn invalid_module_name_errors() {
let mut g = petgraph::graph::DiGraph::new();
let push = g.add_node(Box::new(node_push()) as Box<dyn DebugNode>);
let bad = g.add_node(Box::new(node_requires("(begin $push 1)", &["bad\"name"])) as Box<_>);
g.add_edge(push, bad, Edge::from((0, 0)));
let eps = push_pull_entrypoints(&no_lookup, &g);
let err = gantz_core::compile::module(&no_lookup, &g, &eps, &Config::default()).unwrap_err();
assert!(matches!(err, ModuleError::InvalidModuleName { .. }));
}