use crate::{
Edge, compile,
node::{self, Node},
visit,
};
use gantz_ca::CaHash;
use gantz_nodetag::NodeTag;
use petgraph::{
Directed,
graph::{EdgeIndex, NodeIndex},
visit::{Data, IntoEdgesDirected, IntoNodeReferences, NodeIndexable, NodeRef, Visitable},
};
use serde::{Deserialize, Serialize};
use std::hash::Hash;
pub type Graph<N> = petgraph::graph::Graph<N, Edge, Directed, Index>;
pub type Index = usize;
pub type NodeIx = NodeIndex<Index>;
pub type EdgeIx = EdgeIndex<Index>;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, CaHash, NodeTag)]
#[cahash("gantz.inlet")]
pub struct Inlet {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub ty: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, CaHash, NodeTag)]
#[cahash("gantz.outlet")]
pub struct Outlet {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub ty: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
}
impl<N: Node> Node for Graph<N> {
fn n_inputs(&self, ctx: node::MetaCtx) -> usize {
self.node_references()
.filter(|n_ref| n_ref.weight().inlet(ctx))
.count()
}
fn n_outputs(&self, ctx: node::MetaCtx) -> usize {
self.node_references()
.filter(|n_ref| n_ref.weight().outlet(ctx))
.count()
}
fn branches(&self, ctx: node::MetaCtx) -> Vec<node::EvalConf> {
graph_branches(ctx.get_node(), self)
.unwrap_or_default()
.into_iter()
.map(node::EvalConf::Set)
.collect()
}
fn expr(&self, ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
graph_call_expr(
ctx.get_node(),
self,
ctx.path(),
ctx.inputs(),
ctx.outputs(),
)
}
fn stateful(&self, ctx: node::MetaCtx) -> bool {
self.node_references()
.any(|n_ref| n_ref.weight().stateful(ctx))
}
fn register(&self, _ctx: node::RegCtx<'_, '_>) {
}
fn visit(&self, ctx: visit::Ctx<'_, '_>, visitor: &mut dyn node::Visitor) {
crate::graph::visit(ctx.get_node(), self, ctx.path(), visitor);
}
}
impl Node for Inlet {
fn expr(&self, _ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
node::parse_expr("'()")
}
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
0
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn inlet(&self, _ctx: node::MetaCtx) -> bool {
true
}
}
impl Node for Outlet {
fn expr(&self, _ctx: node::ExprCtx<'_, '_>) -> node::ExprResult {
node::parse_expr("'()")
}
fn n_inputs(&self, _ctx: node::MetaCtx) -> usize {
1
}
fn n_outputs(&self, _ctx: node::MetaCtx) -> usize {
0
}
fn outlet(&self, _ctx: node::MetaCtx) -> bool {
true
}
}
fn graph_branches<'a, G>(
get_node: node::GetNode<'a>,
g: G,
) -> Result<Vec<node::Conns>, node::ExprError>
where
G: IntoEdgesDirected + IntoNodeReferences + NodeIndexable + Visitable + Data<EdgeWeight = Edge>,
G::NodeWeight: Node,
G::NodeId: Eq + Hash,
{
let meta = compile::Meta::from_graph(get_node, g).map_err(node::ExprError::custom)?;
compile::level_branch_patterns(&meta).map_err(node::ExprError::custom)
}
pub fn graph_call_expr<'a, G>(
get_node: node::GetNode<'a>,
g: G,
path: &[node::Id],
inputs: &[Option<String>],
outputs: &node::Conns,
) -> node::ExprResult
where
G: IntoEdgesDirected + IntoNodeReferences + NodeIndexable + Visitable + Data<EdgeWeight = Edge>,
G::NodeWeight: Node,
G::NodeId: Eq + Hash,
{
let imask = node::Conns::try_from_iter(inputs.iter().map(Option::is_some))
.map_err(node::ExprError::custom)?;
let fn_name = compile::graph_fn_name(path, &imask);
let mut args: Vec<String> = inputs.iter().flatten().cloned().collect();
let meta_ctx = node::MetaCtx::new(get_node);
let stateful = g
.node_references()
.any(|n_ref| n_ref.weight().stateful(meta_ctx));
let call = if stateful {
args.push("state".to_string());
format!(
"(let ((%gantz-r ({fn_name} {})))
(set! state (list-ref %gantz-r 1))
(list-ref %gantz-r 0))",
args.join(" "),
)
} else {
format!("({fn_name} {})", args.join(" "))
};
let n_outlets = g
.node_references()
.filter(|n_ref| n_ref.weight().outlet(meta_ctx))
.count();
let active: Vec<usize> = outputs
.iter()
.enumerate()
.filter_map(|(o, b)| b.then_some(o))
.collect();
let branching = !graph_branches(get_node, g)?.is_empty();
if branching || n_outlets <= 1 || active.is_empty() || active.len() == n_outlets {
return node::parse_expr(&call);
}
let selection = match active.as_slice() {
[k] => format!("(list-ref %gantz-out {k})"),
ks => {
let refs: Vec<String> = ks
.iter()
.map(|k| format!("(list-ref %gantz-out {k})"))
.collect();
format!("(list {})", refs.join(" "))
}
};
node::parse_expr(&format!("(let ((%gantz-out {call})) {selection})"))
}