use std::collections::HashMap;
use crate::error::SampleError;
use crate::machine::renderer::{
Between, Binary, Formula, Grid, Index, Map, NodeRenderer, Site, SiteId, Slot, Stepped, Unary,
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Op {
Const(f64),
Time,
Wrap(Option<Stepped>),
Noise {
seed: u64,
at: Option<Map>,
},
Read {
slot: Slot,
at: Map,
},
ReadScaled {
slot: Slot,
at: Map,
by: f64,
},
Indexed {
slot: Slot,
at: usize,
arity: usize,
reach: Option<(i64, i64)>,
},
Instant {
at: usize,
arity: usize,
},
Formula {
at: usize,
},
Add(usize),
Mul(usize),
Sub,
Div,
Pow,
Map(Unary),
Zip(Binary),
Crop {
window: (i64, i64),
a: f64,
b: f64,
rise: f64,
fall: f64,
},
Join(usize),
Channel(usize),
Filter {
site: SiteId,
from: i64,
},
Physics {
site: SiteId,
from: i64,
arity: usize,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct Layout {
pub grid: Grid,
pub width: usize,
pub read_widths: Vec<usize>,
pub sites: Vec<Site>,
}
fn meet(a: usize, b: usize) -> Result<usize, SampleError> {
match (a, b) {
(a, b) if a == b => Ok(a),
(1, b) => Ok(b),
(a, 1) => Ok(a),
(left, right) => Err(SampleError::WidthMismatch { left, right }),
}
}
#[derive(Default)]
pub(crate) struct Lowered {
pub(crate) ops: Vec<Op>,
pub(crate) widths: Vec<usize>,
pub(crate) args: Vec<Vec<usize>>,
pub(crate) formulas: Vec<Formula>,
pub(crate) indices: Vec<Index<usize>>,
held: HashMap<String, usize>,
}
pub(crate) fn lowered(
renderer: &NodeRenderer,
layout: &Layout,
) -> Result<(Lowered, usize), SampleError> {
let mut out = Lowered::default();
let root = lower(renderer, layout, &mut out)?;
debug_assert_eq!(root, out.ops.len() - 1, "an op is never its own operand");
let width = out.widths[root];
Ok((out, width))
}
impl Lowered {
fn push(&mut self, op: Op, width: usize, args: Vec<usize>) -> usize {
let key = self.key(&op, &args);
if let Some(slot) = key.as_ref().and_then(|key| self.held.get(key)) {
if matches!(op, Op::Indexed { .. } | Op::Instant { .. }) {
self.indices.pop();
}
return *slot;
}
self.ops.push(op);
self.widths.push(width);
self.args.push(args);
let slot = self.ops.len() - 1;
if let Some(key) = key {
self.held.insert(key, slot);
}
slot
}
fn key(&self, op: &Op, args: &[usize]) -> Option<String> {
let bits = |v: &[f64]| v.iter().map(|v| v.to_bits()).collect::<Vec<u64>>();
let what = match op {
Op::Const(v) => format!("const {:?}", bits(&[*v])),
Op::ReadScaled { slot, at, by } => format!("scaled {slot:?} {at:?} {:?}", bits(&[*by])),
Op::Crop {
window,
a,
b,
rise,
fall,
} => format!("crop {window:?} {:?}", bits(&[*a, *b, *rise, *fall])),
Op::Indexed {
slot, at, reach, ..
} => format!("indexed {slot:?} {reach:?} {:?}", self.indices[*at]),
Op::Instant { at, .. } => format!("instant {:?}", self.indices[*at]),
Op::Formula { .. } | Op::Filter { .. } | Op::Physics { .. } => return None,
other => format!("{other:?}"),
};
Some(format!("{what} {args:?}"))
}
}
fn indexed(
index: &Index,
layout: &Layout,
out: &mut Lowered,
) -> Result<(usize, Vec<usize>), SampleError> {
let mut operands = Vec::new();
let program = index.mapped(&mut |time| {
operands.push(lower(time, layout, out)?);
Ok::<usize, SampleError>(operands.len() - 1)
})?;
out.indices.push(program);
Ok((out.indices.len() - 1, operands))
}
fn lower(r: &NodeRenderer, layout: &Layout, out: &mut Lowered) -> Result<usize, SampleError> {
if let NodeRenderer::Mul(parts) = r
&& let Some((slot, at, by)) = scaled_read(parts)
{
let width = slot_width(slot, layout);
return Ok(out.push(Op::ReadScaled { slot, at, by }, width, Vec::new()));
}
let (op, operands) = match r {
NodeRenderer::Formula { formula, time, .. } => {
let operands = vec![lower(time, layout, out)?];
out.formulas.push(formula.clone());
let at = out.formulas.len() - 1;
(Op::Formula { at }, operands)
}
NodeRenderer::Indexed { slot, index, reach } => {
let (at, operands) = indexed(index, layout, out)?;
let op = Op::Indexed {
slot: *slot,
at,
arity: operands.len(),
reach: *reach,
};
(op, operands)
}
NodeRenderer::Instant(index) => {
let (at, operands) = indexed(index, layout, out)?;
let arity = operands.len();
(Op::Instant { at, arity }, operands)
}
other => {
let operands = other
.operands()
.into_iter()
.map(|p| lower(p, layout, out))
.collect::<Result<Vec<_>, _>>()?;
(op_of(other, layout), operands)
}
};
let widths: Vec<usize> = operands.iter().map(|s| out.widths[*s]).collect();
let w = width(r, &widths, layout)?;
Ok(out.push(op, w, operands))
}
fn op_of(r: &NodeRenderer, layout: &Layout) -> Op {
match r {
NodeRenderer::Const(v) => Op::Const(*v),
NodeRenderer::Time => Op::Time,
NodeRenderer::Wrap(wrap) => Op::Wrap(wrap.on(layout.grid)),
NodeRenderer::Noise(seed) => Op::Noise {
seed: *seed,
at: Map::rounded(layout.grid.a, 0, layout.grid.d, Between::Even),
},
NodeRenderer::Read { slot, map } => Op::Read {
slot: *slot,
at: *map,
},
NodeRenderer::Add(parts) => Op::Add(parts.len()),
NodeRenderer::Mul(parts) => Op::Mul(parts.len()),
NodeRenderer::Join(parts) => Op::Join(parts.len()),
NodeRenderer::Sub(..) => Op::Sub,
NodeRenderer::Div(..) => Op::Div,
NodeRenderer::Pow(..) => Op::Pow,
NodeRenderer::Map(f, _) => Op::Map(*f),
NodeRenderer::Zip(f, ..) => Op::Zip(*f),
NodeRenderer::Crop {
window,
a,
b,
rise,
fall,
..
} => Op::Crop {
window: *window,
a: *a,
b: *b,
rise: *rise,
fall: *fall,
},
NodeRenderer::Channel { k, .. } => Op::Channel(*k),
NodeRenderer::Filter { site, from, .. } => Op::Filter {
site: *site,
from: *from,
},
NodeRenderer::Physics { site, from, args } => Op::Physics {
site: *site,
from: *from,
arity: args.len(),
},
NodeRenderer::Formula { .. } | NodeRenderer::Indexed { .. } | NodeRenderer::Instant(_) => {
unreachable!("lowered with the program tables they name")
}
}
}
pub(crate) fn width(
r: &NodeRenderer,
operands: &[usize],
layout: &Layout,
) -> Result<usize, SampleError> {
let met = |from: usize| operands.iter().try_fold(from, |w, &o| meet(w, o));
Ok(match r {
NodeRenderer::Const(_)
| NodeRenderer::Time
| NodeRenderer::Wrap(_)
| NodeRenderer::Noise(_) => 1,
NodeRenderer::Read { slot, .. } => slot_width(*slot, layout),
NodeRenderer::Formula { width, .. } => match operands {
[times] if *times != 1 && times != width => {
return Err(SampleError::WidthMismatch {
left: *times,
right: *width,
});
}
_ => *width,
},
NodeRenderer::Indexed { slot, .. } => slot_width(*slot, layout),
NodeRenderer::Instant(_) | NodeRenderer::Physics { .. } => 1,
NodeRenderer::Add(_) | NodeRenderer::Mul(_) => met(1)?,
NodeRenderer::Sub(..)
| NodeRenderer::Div(..)
| NodeRenderer::Pow(..)
| NodeRenderer::Zip(..)
| NodeRenderer::Filter { .. } => met(operands[0])?,
NodeRenderer::Map(..) | NodeRenderer::Crop { .. } => operands[0],
NodeRenderer::Join(_) => operands.iter().sum(),
NodeRenderer::Channel { k, .. } => match operands[0] {
w if *k >= w => return Err(SampleError::ChannelOutOfRange { k: *k, width: w }),
_ => 1,
},
})
}
fn slot_width(slot: Slot, layout: &Layout) -> usize {
match slot {
Slot::Read(id) => layout.read_widths[id.0 as usize],
Slot::Own => layout.width,
}
}
fn scaled_read(parts: &[NodeRenderer]) -> Option<(Slot, Map, f64)> {
match parts {
[NodeRenderer::Read { slot, map }, NodeRenderer::Const(by)]
| [NodeRenderer::Const(by), NodeRenderer::Read { slot, map }] => Some((*slot, *map, *by)),
_ => None,
}
}