use crate::{
compile::{
Meta,
error::{LowerError, TooManyConns},
ir::{Atom, Body, Join, JoinId, Step, Tail, Var},
lower::{self, LevelSources, OutletVal},
},
node,
};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Fire {
Fired,
Unfired,
}
type Env = BTreeMap<Var, Fire>;
type Joins<'a> = BTreeMap<JoinId, &'a Join>;
pub(crate) fn level_branch_patterns(meta: &Meta) -> Result<Vec<node::Conns>, LowerError> {
if meta.branches.is_empty() {
return Ok(vec![]);
}
let all: BTreeSet<node::Id> = meta.inlets.iter().copied().collect();
let cx = lower::Cx {
meta,
extra_branches: BTreeMap::new(),
prebound: BTreeSet::new(),
};
let out = lower::level_body(&cx, &LevelSources::Inlets(all))?;
let patterns = outlet_patterns(&out.body, &out.outlets).map_err(|error| LowerError::Conns {
node: None,
error: error.into(),
})?;
Ok(patterns)
}
pub(crate) fn outlet_patterns(
body: &Body,
outlets: &[OutletVal],
) -> Result<Vec<node::Conns>, TooManyConns> {
let n = outlets.len();
let mut masks: BTreeSet<node::Conns> = BTreeSet::new();
for (_, env) in walk(&body.steps, &body.tail, Env::new(), &Joins::new()) {
let mut conns = node::Conns::unconnected(n).map_err(|_| TooManyConns(n))?;
for (i, o) in outlets.iter().enumerate() {
let fired = match o.atom {
None => false,
Some(ref atom) => fire_of(&env, atom) == Fire::Fired,
};
if fired {
conns.set(i, true).map_err(|_| TooManyConns(n))?;
}
}
masks.insert(conns);
}
if masks.len() < 2 {
return Ok(vec![]);
}
Ok(masks.into_iter().collect())
}
fn fire_of(env: &Env, atom: &Atom) -> Fire {
match atom {
Atom::Unfired => Fire::Unfired,
Atom::Unit => Fire::Fired,
Atom::Var(v) => env.get(v).copied().unwrap_or(Fire::Fired),
}
}
fn walk<'a>(
steps: &'a [Step],
tail: &'a Tail,
env: Env,
joins: &Joins<'a>,
) -> Vec<(Vec<Fire>, Env)> {
let Some((step, rest)) = steps.split_first() else {
return finish(tail, env, joins);
};
match step {
Step::Node { dst, .. } => {
let mut env = env;
for &v in dst {
env.insert(v, Fire::Fired);
}
walk(rest, tail, env, joins)
}
Step::DelayRead { node } => {
let mut env = env;
let var = Var::Output {
node: *node,
output: 0,
};
env.insert(var, Fire::Fired);
walk(rest, tail, env, joins)
}
Step::DelayWrite { .. } => walk(rest, tail, env, joins),
Step::Join(join) => {
let mut joins = joins.clone();
joins.insert(join.id, join);
walk(rest, tail, env, &joins)
}
Step::Branch { dst, arms, .. } => {
let mut outcomes = Vec::new();
for arm in arms {
let mut arm_env = env.clone();
for &b in &arm.binds {
arm_env.insert(b, Fire::Fired);
}
for (yields, arm_env) in walk(&arm.body.steps, &arm.body.tail, arm_env, joins) {
let mut env = arm_env;
for (&v, f) in dst.iter().zip(yields) {
env.insert(v, f);
}
outcomes.extend(walk(rest, tail, env, joins));
}
}
outcomes
}
}
}
fn finish<'a>(tail: &'a Tail, env: Env, joins: &Joins<'a>) -> Vec<(Vec<Fire>, Env)> {
match tail {
Tail::Ret(atoms) => {
let yields = atoms.iter().map(|a| fire_of(&env, a)).collect();
vec![(yields, env)]
}
Tail::Jump { join, args } => {
let join = joins[join];
assert!(!join.rec, "outlet analysis cannot walk rec joins yet");
let mut env = env;
for (¶m, arg) in join.params.iter().zip(args) {
let fire = fire_of(&env, arg);
env.insert(param, fire);
}
walk(&join.body.steps, &join.body.tail, env, joins)
}
}
}