use crate::{
GRAPH_STATE, ROOT_STATE,
compile::{EvalPlan, EvalStep, RoseTree},
node,
};
pub(crate) use node_fn::{node_confs_tree, node_fns};
use std::collections::{BTreeSet, HashMap};
use steel::{parser::ast::ExprKind, steel_vm::engine::Engine};
mod node_fn;
const STATE: &str = "state";
fn node_fn_call(
node_path: &[node::Id],
inputs: &[Option<String>],
outputs: &node::Conns,
stateful: bool,
) -> ExprKind {
let mut args: Vec<String> = inputs.iter().filter_map(Clone::clone).collect();
if stateful {
args.push(STATE.to_string());
}
let node_inputs = node::Conns::try_from_iter(inputs.iter().map(|arg| arg.is_some())).unwrap();
let node_fn_name = node_fn::name(&node_path, &node_inputs, outputs);
let node_fn_call_expr_str = format!("({node_fn_name} {})", args.join(" "));
Engine::emit_ast(&node_fn_call_expr_str)
.expect("failed to emit AST")
.into_iter()
.next()
.unwrap()
}
fn eval_stmt(
node_path: &[node::Id],
inputs: &[Option<String>],
outputs: &node::Conns,
stateful: bool,
) -> (ExprKind, Vec<String>) {
fn var_name(node_ix: node::Id, out_ix: u16) -> String {
format!("node-{}-o{}", node_ix, out_ix)
}
fn node_state_key(node_id: usize) -> ExprKind {
let key_str = format!("'{node_id}");
Engine::emit_ast(&key_str)
.expect("failed to emit AST")
.into_iter()
.next()
.unwrap()
}
fn wrap_node_fn_call_with_state(call_expr: &str, node_ix: usize) -> String {
const NEWSTATE: &str = "newstate";
const OUTPUT: &str = "output";
let key = node_state_key(node_ix);
format!(
"(let (({STATE} (hash-ref {GRAPH_STATE} {key})))
(let ((results {call_expr}))
(let (({OUTPUT} (car results)) ({NEWSTATE} (car (cdr results))))
(set! {GRAPH_STATE} (hash-insert {GRAPH_STATE} {key} {NEWSTATE}))
{OUTPUT})))"
)
}
fn create_define_expr(var_name: String, value_expr: ExprKind) -> ExprKind {
let s = format!("(define {var_name} {})", value_expr);
Engine::emit_ast(&s)
.expect("failed to emit AST")
.into_iter()
.next()
.unwrap()
}
fn create_define_values_expr(var_names: Vec<String>, value_expr: ExprKind) -> ExprKind {
let s = format!("(define-values ({}) {})", var_names.join(" "), value_expr);
Engine::emit_ast(&s)
.expect("failed to emit AST")
.into_iter()
.next()
.unwrap()
}
let node_ix = *node_path.last().expect("node_path must not be empty");
let output_vars: Vec<_> = (0..outputs.len())
.map(|i| var_name(node_ix, i as u16))
.collect();
let node_fn_call_expr = node_fn_call(node_path, inputs, outputs, stateful);
let mut node_fn_call_expr_str = format!("{node_fn_call_expr}");
if stateful {
node_fn_call_expr_str = wrap_node_fn_call_with_state(&node_fn_call_expr_str, node_ix);
};
let node_fn_call_expr = Engine::emit_ast(&node_fn_call_expr_str)
.expect("failed to emit AST")
.into_iter()
.next()
.unwrap();
let stmt = match outputs.len() {
0 => node_fn_call_expr,
1 => {
let output_var = var_name(node_ix, 0);
let define_expr = create_define_expr(output_var, node_fn_call_expr);
define_expr
}
_ => {
let output_vars: Vec<String> = (0..outputs.len())
.map(|i| var_name(node_ix, i as u16))
.collect();
let define_values_expr = create_define_values_expr(output_vars, node_fn_call_expr);
define_values_expr
}
};
(stmt, output_vars)
}
pub(crate) fn eval_stmts(
path: &[node::Id],
steps: &[EvalStep],
stateful: &BTreeSet<node::Id>,
) -> Vec<ExprKind> {
let mut output_vars: HashMap<(node::Id, node::Output), String> = HashMap::new();
let mut stmts = Vec::new();
for step in steps {
let inputs: Vec<_> = step
.inputs
.iter()
.map(|arg_opt| {
arg_opt.as_ref().map(|arg| {
let var_name = output_vars.get(&(arg.node, arg.output)).unwrap();
var_name.to_string()
})
})
.collect();
let node_path: Vec<_> = path.iter().copied().chain(Some(step.node)).collect();
let stateful = stateful.contains(&step.node);
let outputs = node::Conns::try_from_slice(&step.outputs).unwrap();
let (stmt, stmt_outputs) = eval_stmt(&node_path, &inputs, &outputs, stateful);
for (out_ix, out_var) in stmt_outputs.into_iter().enumerate() {
let output = node::Output(out_ix.try_into().unwrap());
output_vars.insert((step.node, output), out_var);
}
stmts.push(stmt);
}
stmts
}
fn eval_fn(eval_fn_name: &str, stmts: Vec<ExprKind>) -> ExprKind {
let stmts_str = stmts
.iter()
.map(|stmt| stmt.to_string())
.chain(Some("'()".to_string()))
.collect::<Vec<_>>()
.join(" ");
let fn_def = format!(
"(define ({}) \
(define {GRAPH_STATE} {ROOT_STATE}) \
{stmts_str} \
(set! {ROOT_STATE} {GRAPH_STATE}))",
eval_fn_name
);
Engine::emit_ast(&fn_def)
.expect("Failed to emit AST for function")
.into_iter()
.next()
.unwrap()
}
fn path_string(path: &[node::Id]) -> String {
path.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(":")
}
pub fn pull_eval_fn_name(path: &[node::Id]) -> String {
format!("pull-fn-{}", path_string(path))
}
pub fn push_eval_fn_name(path: &[node::Id]) -> String {
format!("push-fn-{}", path_string(path))
}
pub(crate) fn eval_fns(eval_tree: &RoseTree<EvalPlan>) -> Vec<ExprKind> {
let mut eval_fns = vec![];
eval_tree.visit(&[], &mut |path, eval| {
let pull_steps = eval.pull_steps.iter().map(|(&id, steps)| {
let node_path: Vec<_> = path.iter().copied().chain(Some(id)).collect();
let name = pull_eval_fn_name(&node_path);
(name, steps)
});
let push_steps = eval.push_steps.iter().map(|(&id, steps)| {
let node_path: Vec<_> = path.iter().copied().chain(Some(id)).collect();
let name = push_eval_fn_name(&node_path);
(name, steps)
});
let fns = pull_steps.chain(push_steps).map(|(name, steps)| {
let stmts = eval_stmts(path, &steps, &eval.meta.stateful);
eval_fn(&name, stmts)
});
eval_fns.extend(fns);
});
eval_fns
}