use crate::{
Edge,
compile::{EvalPlan, EvalStep, RoseTree, codegen::path_string},
node::{self, Node},
visit::{self, Visitor},
};
use petgraph::visit::{Data, IntoEdgesDirected, IntoNodeReferences, NodeIndexable, Visitable};
use std::collections::BTreeSet;
use steel::{parser::ast::ExprKind, steel_vm::engine::Engine};
type NodeConfs = BTreeSet<(node::Id, NodeConf)>;
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
pub(crate) struct NodeConf {
inputs: Vec<bool>,
outputs: Vec<bool>,
}
struct NodeFns<'a> {
tree: &'a RoseTree<NodeConfs>,
fns: Vec<ExprKind>,
}
impl<'a> NodeFns<'a> {
fn new(tree: &'a RoseTree<NodeConfs>) -> Self {
let fns = vec![];
Self { tree, fns }
}
}
impl<'pl> Visitor for NodeFns<'pl> {
fn visit_post(&mut self, ctx: visit::Ctx, node: &dyn Node) {
use std::ops::Bound::{Excluded, Included};
let node_path = ctx.path();
let plan_path = &node_path[..node_path.len() - 1];
let tree = self.tree.tree(&plan_path).unwrap();
let id = ctx.id();
let empty_conf = NodeConf {
inputs: vec![],
outputs: vec![],
};
let start = (id, empty_conf.clone());
let end = (id.checked_add(1).expect("node id out of range"), empty_conf);
let range = (Included(start), Excluded(end));
let input_confs = tree.elem.range(range);
for (_id, conf) in input_confs {
self.fns.push(node_fn(node, node_path, &conf));
}
}
}
fn node_confs<'a, I>(eval_stepss: I) -> NodeConfs
where
I: IntoIterator<Item = &'a [EvalStep]>,
{
eval_stepss
.into_iter()
.flat_map(|steps| {
steps.iter().map(|step| {
let inputs = step.inputs.iter().map(|input| input.is_some()).collect();
let outputs = step.outputs.clone();
let conf = NodeConf { inputs, outputs };
(step.node, conf)
})
})
.collect()
}
pub(crate) fn node_confs_tree(eval_tree: &RoseTree<EvalPlan>) -> RoseTree<NodeConfs> {
eval_tree.map_ref(&mut |eval| {
let all_steps = eval
.pull_steps
.values()
.chain(eval.push_steps.values())
.chain(Some(&eval.nested_steps))
.map(|v| &v[..]);
node_confs(all_steps)
})
}
pub(crate) fn name(node_path: &[node::Id], inputs: &node::Conns, outputs: &node::Conns) -> String {
let path_string = path_string(node_path);
let inputs_prefix = if inputs.is_empty() { "" } else { "-i" };
let outputs_prefix = if outputs.is_empty() { "" } else { "-o" };
let inputs_string = format!("{inputs}");
let outputs_string = format!("{outputs}");
format!("node-fn-{path_string}{inputs_prefix}{inputs_string}{outputs_prefix}{outputs_string}")
}
pub(crate) fn node_fn(node: &dyn Node, node_path: &[node::Id], conf: &NodeConf) -> ExprKind {
const STATE: &str = "state";
fn input_name(i: usize) -> String {
format!("input{i}")
}
let mut input_args = conf
.inputs
.iter()
.enumerate()
.filter_map(|(i, b)| b.then(|| input_name(i)))
.collect::<Vec<_>>();
let input_exprs: Vec<Option<String>> = conf
.inputs
.iter()
.enumerate()
.map(|(i, b)| b.then(|| input_name(i)))
.collect();
let ctx = node::ExprCtx::new(node_path, &input_exprs, &conf.outputs);
let node_expr = node.expr(ctx);
let inputs = node::Conns::try_from_slice(&conf.inputs).unwrap();
let outputs = node::Conns::try_from_slice(&conf.outputs).unwrap();
let fn_name = name(node_path, &inputs, &outputs);
let fn_body = if node.stateful() {
input_args.push(STATE.to_string());
format!("(let ((output {node_expr})) (list output state))")
} else {
format!("{node_expr}")
};
let fn_args = input_args.join(" ");
let fn_def = format!("(define ({fn_name} {fn_args}) {fn_body})");
Engine::emit_ast(&fn_def)
.expect("Failed to emit AST for node function")
.into_iter()
.next()
.unwrap()
}
pub(crate) fn node_fns<G>(g: G, node_confs_tree: &RoseTree<NodeConfs>) -> Vec<ExprKind>
where
G: Data<EdgeWeight = Edge> + IntoEdgesDirected + IntoNodeReferences + NodeIndexable + Visitable,
G::NodeWeight: Node,
{
let mut node_fns = NodeFns::new(&node_confs_tree);
crate::graph::visit(g, &[], &mut node_fns);
node_fns.fns
}