use crate::{
Edge,
node::{self, Node},
};
pub(crate) use codegen::eval_stmts;
#[doc(inline)]
pub use codegen::{pull_eval_fn_name, push_eval_fn_name};
#[doc(inline)]
pub use meta::{EdgeKind, Meta};
use petgraph::visit::{
Data, Dfs, EdgeRef, GraphBase, GraphRef, IntoEdgesDirected, IntoNeighbors, IntoNodeReferences,
NodeIndexable, Topo, Visitable, Walker,
};
pub(crate) use rosetree::RoseTree;
use std::{
collections::{BTreeMap, HashSet},
hash::Hash,
};
use steel::parser::ast::ExprKind;
mod codegen;
mod meta;
mod rosetree;
pub trait Edges {
fn edges(&self) -> impl Iterator<Item = Edge>;
}
#[derive(Debug)]
struct EvalPlan<'a> {
meta: &'a Meta,
nested_steps: Vec<EvalStep>,
push_steps: BTreeMap<node::Id, Vec<EvalStep>>,
pull_steps: BTreeMap<node::Id, Vec<EvalStep>>,
}
#[derive(Debug)]
pub(crate) struct EvalStep {
pub(crate) node: node::Id,
pub(crate) inputs: Vec<Option<ExprInput>>,
pub(crate) outputs: Vec<bool>,
}
#[derive(Debug)]
pub(crate) struct ExprInput {
pub(crate) node: node::Id,
pub(crate) output: node::Output,
}
impl Edges for Edge {
fn edges(&self) -> impl Iterator<Item = Edge> {
std::iter::once(*self)
}
}
impl Edges for Vec<Edge> {
fn edges(&self) -> impl Iterator<Item = Edge> {
self.iter().copied()
}
}
fn eval_neighbors<G>(
g: G,
n: G::NodeId,
conns: &node::Conns,
src_conn: impl Fn(&Edge) -> usize,
) -> HashSet<G::NodeId>
where
G: IntoEdgesDirected,
G::EdgeWeight: Edges,
G::NodeId: Eq + Hash,
{
let mut set = HashSet::new();
for e_ref in g.edges_directed(n, petgraph::Outgoing) {
for edge in e_ref.weight().edges() {
let conn_ix = src_conn(&edge);
let include = conns.get(conn_ix).unwrap();
if include {
set.insert(e_ref.target());
}
}
}
set
}
fn push_eval_neighbors<G>(g: G, n: G::NodeId, ev: &node::Conns) -> HashSet<G::NodeId>
where
G: IntoEdgesDirected,
G::EdgeWeight: Edges,
G::NodeId: Eq + Hash,
{
eval_neighbors(g, n, ev, |edge| edge.output.0 as usize)
}
fn pull_eval_neighbors<G>(g: G, n: G::NodeId, ev: &node::Conns) -> HashSet<G::NodeId>
where
G: IntoEdgesDirected,
G::EdgeWeight: Edges,
G::NodeId: Eq + Hash,
{
let rev_g = petgraph::visit::Reversed(g);
eval_neighbors(rev_g, n, ev, |edge| edge.input.0 as usize)
}
fn reachable<G>(
g: G,
src: G::NodeId,
src_neighbors: &HashSet<G::NodeId>,
) -> impl Iterator<Item = G::NodeId>
where
G: IntoEdgesDirected + Visitable,
G::NodeId: Eq + Hash,
{
#[derive(Clone, Copy)]
struct EvalFilter<'a, G: GraphBase> {
g: G,
src: G::NodeId,
src_neighbors: &'a HashSet<G::NodeId>,
}
struct EvalFilterNeighbors<'a, I: Iterator> {
neighbors: I,
is_src: bool,
src_neighbors: &'a HashSet<I::Item>,
}
impl<'a, G> GraphBase for EvalFilter<'a, G>
where
G: GraphBase,
{
type NodeId = G::NodeId;
type EdgeId = G::EdgeId;
}
impl<'a, G: GraphRef> GraphRef for EvalFilter<'a, G> {}
impl<'a, G> Visitable for EvalFilter<'a, G>
where
G: GraphRef + Visitable,
{
type Map = G::Map;
fn visit_map(&self) -> Self::Map {
self.g.visit_map()
}
fn reset_map(&self, map: &mut Self::Map) {
self.g.reset_map(map);
}
}
impl<'a, I> Iterator for EvalFilterNeighbors<'a, I>
where
I: Iterator,
I::Item: Eq + Hash,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
while let Some(n) = self.neighbors.next() {
if !self.is_src || self.src_neighbors.contains(&n) {
return Some(n);
}
}
None
}
}
impl<'a, G> IntoNeighbors for EvalFilter<'a, G>
where
G: IntoNeighbors,
G::NodeId: Eq + Hash,
{
type Neighbors = EvalFilterNeighbors<'a, G::Neighbors>;
fn neighbors(self, a: Self::NodeId) -> Self::Neighbors {
let neighbors = self.g.neighbors(a);
EvalFilterNeighbors {
neighbors,
is_src: self.src == a,
src_neighbors: &self.src_neighbors,
}
}
}
let g = EvalFilter {
g,
src,
src_neighbors,
};
Dfs::new(g, src).iter(g)
}
fn push_reachable<G>(
g: G,
n: G::NodeId,
nbs: &HashSet<G::NodeId>,
) -> impl Iterator<Item = G::NodeId>
where
G: IntoEdgesDirected + Visitable,
G::NodeId: Eq + Hash,
{
reachable(g, n, nbs)
}
fn pull_reachable<G>(
g: G,
n: G::NodeId,
nbs: &HashSet<G::NodeId>,
) -> impl Iterator<Item = G::NodeId>
where
G: IntoEdgesDirected + Visitable,
G::NodeId: Eq + Hash,
{
let rev_g = petgraph::visit::Reversed(g);
reachable(rev_g, n, nbs)
}
fn push_eval_order<G>(
g: G,
n: G::NodeId,
nbs: &HashSet<G::NodeId>,
) -> impl Iterator<Item = G::NodeId>
where
G: IntoEdgesDirected + IntoNodeReferences + Visitable,
G::NodeId: Eq + Hash,
{
let dfs: HashSet<G::NodeId> = push_reachable(g, n, nbs).collect();
Topo::new(g).iter(g).filter(move |node| dfs.contains(&node))
}
fn pull_eval_order<G>(
g: G,
n: G::NodeId,
nbs: &HashSet<G::NodeId>,
) -> impl Iterator<Item = G::NodeId>
where
G: IntoEdgesDirected + IntoNodeReferences + Visitable,
G::NodeId: Eq + Hash,
{
let dfs: HashSet<G::NodeId> = pull_reachable(g, n, nbs).collect();
Topo::new(g).iter(g).filter(move |node| dfs.contains(&node))
}
pub(crate) fn eval_order<G, A, B>(g: G, push: A, pull: B) -> impl Iterator<Item = G::NodeId>
where
G: IntoEdgesDirected + IntoNodeReferences + Visitable,
G::EdgeWeight: Edges,
G::NodeId: Eq + Hash,
A: IntoIterator<Item = (G::NodeId, node::Conns)>,
B: IntoIterator<Item = (G::NodeId, node::Conns)>,
{
let mut reachable = HashSet::new();
reachable.extend(push.into_iter().flat_map(|(n, conns)| {
let ps = push_eval_neighbors(g, n, &conns);
push_reachable(g, n, &ps).collect::<Vec<_>>()
}));
reachable.extend(pull.into_iter().flat_map(|(n, conns)| {
let pl = pull_eval_neighbors(g, n, &conns);
pull_reachable(g, n, &pl).collect::<Vec<_>>()
}));
Topo::new(g).iter(g).filter(move |n| reachable.contains(&n))
}
pub(crate) fn eval_steps<I>(meta: &Meta, eval_order: I) -> impl Iterator<Item = EvalStep>
where
I: IntoIterator<Item = node::Id>,
{
let mut visited = HashSet::new();
eval_order.into_iter().map(move |n| {
visited.insert(n);
let n_inputs = meta.inputs.get(&n).copied().unwrap_or(0);
let mut inputs: Vec<_> = (0..n_inputs).map(|_| None).collect();
for e_ref in meta.graph.edges_directed(n, petgraph::Incoming) {
if !visited.contains(&e_ref.source()) {
continue;
}
for (edge, _kind) in e_ref.weight() {
let arg = ExprInput {
node: e_ref.source(),
output: edge.output,
};
inputs[edge.input.0 as usize] = Some(arg);
}
}
let n_outputs = meta.outputs.get(&n).copied().unwrap_or(0);
let mut outputs: Vec<_> = (0..n_outputs).map(|_| false).collect();
for e_ref in meta.graph.edges_directed(n, petgraph::Outgoing) {
for (edge, _kind) in e_ref.weight() {
outputs[edge.output.0 as usize] |= true;
}
}
EvalStep {
node: n,
inputs,
outputs,
}
})
}
fn eval_plan(meta: &Meta) -> EvalPlan {
let pull_steps = meta
.pull
.iter()
.flat_map(|(&n, confs)| {
confs.iter().map(move |conns| {
let nbs = pull_eval_neighbors(&meta.graph, n, conns);
let order = pull_eval_order(&meta.graph, n, &nbs);
let steps = eval_steps(meta, order).collect();
(n, steps)
})
})
.collect();
let push_steps = meta
.push
.iter()
.flat_map(|(&n, confs)| {
confs.iter().map(move |conns| {
let nbs = push_eval_neighbors(&meta.graph, n, conns);
let order = push_eval_order(&meta.graph, n, &nbs);
let steps = eval_steps(meta, order).collect();
(n, steps)
})
})
.collect();
let nested_steps = {
let order = eval_order(
&meta.graph,
meta.inlets
.iter()
.map(|&n| (n, node::Conns::connected(1).unwrap())),
meta.outlets
.iter()
.map(|&n| (n, node::Conns::connected(1).unwrap())),
);
eval_steps(meta, order).collect()
};
EvalPlan {
meta,
push_steps,
pull_steps,
nested_steps,
}
}
pub fn module<G>(g: G) -> Vec<ExprKind>
where
G: Data<EdgeWeight = Edge> + IntoEdgesDirected + IntoNodeReferences + NodeIndexable + Visitable,
G::NodeWeight: Node,
{
let mut meta_tree = RoseTree::<Meta>::default();
crate::graph::visit(g, &[], &mut meta_tree);
let eval_tree = meta_tree.map_ref(&mut eval_plan);
let node_confs_tree = codegen::node_confs_tree(&eval_tree);
let node_fns = codegen::node_fns(g, &node_confs_tree);
let eval_fns = codegen::eval_fns(&eval_tree);
node_fns.into_iter().chain(eval_fns).collect()
}