use crate::authoring::*;
#[rustfmt::skip]
pub const STACK_GAMUT: [OpParameter; 7] = [
OpParameter::Series { key: "push", default: Some("") },
OpParameter::Series { key: "pop", default: Some("") },
OpParameter::Series { key: "roll", default: Some("") },
OpParameter::Series { key: "unroll", default: Some("") },
OpParameter::Series { key: "flip", default: Some("") },
OpParameter::Flag { key: "swap" },
OpParameter::Flag { key: "drop" },
];
pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
let def = ¶meters.instantiated_as;
let mut params = ParsedParameters::new(parameters, &STACK_GAMUT)?;
let mut subcommands_given: usize = 0;
let valid_indices = [1., 2., 3., 4.];
if let Ok(push_args) = params.series("push") {
subcommands_given += 1;
for i in push_args.iter() {
if !valid_indices.contains(i) {
return Err(Error::BadParam("push".to_string(), i.to_string()));
}
}
params.text.insert("action", "push".to_string());
}
if let Ok(flip_args) = params.series("flip") {
subcommands_given += 1;
for i in flip_args.iter() {
if !valid_indices.contains(i) {
return Err(Error::BadParam("flip".to_string(), i.to_string()));
}
}
params.text.insert("action", "flip".to_string());
}
if let Ok(pop_args) = params.series("pop") {
subcommands_given += 1;
for i in pop_args.iter() {
if !valid_indices.contains(i) {
return Err(Error::BadParam("pop".to_string(), i.to_string()));
}
}
params.text.insert("action", "pop".to_string());
}
if let Ok(roll_args) = params.series("roll") {
subcommands_given += 1;
if roll_args.len() != 2
|| roll_args[0].fract() != 0.
|| roll_args[1].fract() != 0.
|| roll_args[0] <= roll_args[1].abs()
{
return Err(Error::MissingParam(
"roll takes exactly two integer parameters, ´(m,n): |n|<=m´".to_string(),
));
}
params.text.insert("action", "roll".to_string());
}
if let Ok(roll_args) = params.series("unroll") {
subcommands_given += 1;
if roll_args.len() != 2
|| roll_args[0].fract() != 0.
|| roll_args[1].fract() != 0.
|| roll_args[0] <= roll_args[1].abs()
{
return Err(Error::MissingParam(
"unroll takes exactly two integer parameters, ´(m,n): |n|<=m´".to_string(),
));
}
params.text.insert("action", "unroll".to_string());
}
if params.boolean("swap") {
subcommands_given += 1;
params.text.insert("action", "swap".to_string());
}
if params.boolean("drop") {
subcommands_given += 1;
params.text.insert("action", "drop".to_string());
}
if subcommands_given != 1 {
return Err(Error::MissingParam(
"stack: must specify exactly one of push/pop/roll/swap/unroll/drop".to_string(),
));
}
let descriptor = OpDescriptor::new(def, InnerOp::default(), Some(InnerOp::default()));
Ok(Op {
descriptor,
params,
steps: None,
})
}
pub(super) fn stack_fwd(
stack: &mut Vec<Vec<f64>>,
operands: &mut dyn CoordinateSet,
params: &ParsedParameters,
) -> usize {
let Some(action) = params.text.get("action") else {
return 0;
};
match action.as_str() {
"push" => {
let args = params.series_as_usize("push").unwrap();
stack_push(stack, operands, &args)
}
"pop" => {
let args = params.series_as_usize("pop").unwrap();
stack_pop(stack, operands, &args)
}
"roll" => {
let args = params.series_as_i64("roll").unwrap();
stack_roll(stack, operands, &args)
}
"unroll" => {
let mut args = params.series_as_i64("unroll").unwrap();
args[1] = args[0] - args[1];
stack_roll(stack, operands, &args)
}
"flip" => {
let args = params.series_as_usize("flip").unwrap();
stack_flip(stack, operands, &args)
}
"swap" => {
let n = stack.len();
if n > 1 {
stack.swap(n - 1, n - 2)
}
if n == 0 { 0 } else { stack[0].len() }
}
_ => 0,
}
}
pub(super) fn stack_inv(
stack: &mut Vec<Vec<f64>>,
operands: &mut dyn CoordinateSet,
params: &ParsedParameters,
) -> usize {
let Some(action) = params.text.get("action") else {
return 0;
};
match action.as_str() {
"push" => {
let mut args = params.series_as_usize("push").unwrap();
args.reverse();
stack_pop(stack, operands, &args)
}
"pop" => {
let mut args = params.series_as_usize("pop").unwrap();
args.reverse();
stack_push(stack, operands, &args)
}
"roll" => {
let mut args = params.series_as_i64("roll").unwrap();
args[1] = args[0] - args[1];
stack_roll(stack, operands, &args)
}
"unroll" => {
let args = params.series_as_i64("roll").unwrap();
stack_roll(stack, operands, &args)
}
"flip" => {
let args = params.series_as_usize("flip").unwrap();
stack_flip(stack, operands, &args)
}
"swap" => {
let n = stack.len();
if n > 1 {
stack.swap(n - 1, n - 2)
}
if n == 0 { 0 } else { stack[0].len() }
}
_ => 0,
}
}
fn stack_push(
stack: &mut Vec<Vec<f64>>,
operands: &mut dyn CoordinateSet,
args: &[usize],
) -> usize {
let number_of_pushes = args.len();
let number_of_operands = operands.len();
let mut ext = vec![vec![0f64; number_of_operands]; number_of_pushes];
#[allow(clippy::needless_range_loop)]
for i in 0..number_of_operands {
let coord = operands.get_coord(i);
for j in 0..number_of_pushes {
ext[j][i] = coord[args[j] - 1];
}
}
stack.extend(ext);
number_of_operands
}
fn stack_flip(stack: &mut [Vec<f64>], operands: &mut dyn CoordinateSet, args: &[usize]) -> usize {
let number_of_flips = args.len();
let number_of_operands = operands.len();
let stack_depth = stack.len();
if stack_depth < number_of_flips {
warn!("Stack flip underflow in pipeline");
operands.stomp();
return 0;
}
#[allow(clippy::needless_range_loop)]
for i in 0..number_of_operands {
let mut coord = operands.get_coord(i);
for j in 0..number_of_flips {
let flip = coord[args[j] - 1];
let depth = stack_depth - 1 - j;
coord[args[j] - 1] = stack[depth][i];
stack[depth][i] = flip;
}
operands.set_coord(i, &coord);
}
number_of_operands
}
fn stack_roll(stack: &mut Vec<Vec<f64>>, operands: &mut dyn CoordinateSet, args: &[i64]) -> usize {
let m = args[0].abs();
let mut n = args[1];
let depth = stack.len();
n = if n < 0 { m + n } else { n };
let m = m as usize;
let n = n as usize;
if m > depth {
warn!("Roll too deep");
operands.stomp();
return 0;
}
for _ in 0..n {
let e = stack.pop().unwrap();
stack.insert(depth - m, e);
}
operands.len()
}
fn stack_pop(stack: &mut Vec<Vec<f64>>, operands: &mut dyn CoordinateSet, args: &[usize]) -> usize {
let number_of_pops = args.len();
let number_of_operands = operands.len();
let stack_depth = stack.len();
if stack_depth < number_of_pops {
warn!("Stack underflow in pipeline");
operands.stomp();
return 0;
}
let mut ext = Vec::with_capacity(number_of_pops);
for _ in args {
ext.push(stack.pop().unwrap());
}
#[allow(clippy::needless_range_loop)]
for i in 0..number_of_operands {
let mut coord = operands.get_coord(i);
for j in 0..number_of_pops {
coord[args[j] - 1] = ext[j][i];
}
operands.set_coord(i, &coord);
}
number_of_operands
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stack() -> Result<(), Error> {
let mut ctx = Minimal::default();
let master_data = vec![Coor4D([11., 12., 13., 14.]), Coor4D([21., 22., 23., 24.])];
assert!(ctx.op("stack push=2,2,1,1,3,3,4,4,4,4,4,4,4").is_ok());
assert!(ctx.op("stack push=2,2,1,1 pop=1,1,2").is_err());
assert!(ctx.op("stack push=2,2,1,1 | stack pop=1,1,2").is_ok());
let mut data = master_data.clone();
let op = ctx.op("stack push=1,2|stack pop=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 12.);
assert_eq!(data[1][1], 21.);
ctx.apply(op, Inv, &mut data)?;
assert_eq!(data[0], master_data[0]);
assert_eq!(data[1], master_data[1]);
let op = ctx.op("stack push=2,1 | stack pop=2,1")?;
ctx.apply(op, Inv, &mut data)?;
assert_eq!(data[0][0], 12.);
assert_eq!(data[1][1], 21.);
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0], master_data[0]);
assert_eq!(data[1], master_data[1]);
let op = ctx.op("stack push=2,1 | stack swap | stack pop=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 12.);
assert_eq!(data[1][1], 21.);
ctx.apply(op, Inv, &mut data)?;
assert_eq!(data[0], master_data[0]);
assert_eq!(data[1], master_data[1]);
let op = ctx.op("stack push=1,1,1,2,1,3,1,4 | stack roll=8,2 | stack pop=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 13.);
assert_eq!(data[0][1], 11.);
assert_eq!(0, ctx.apply(op, Inv, &mut data)?);
let mut data = master_data.clone();
let op = ctx.op("stack push=1,2,3,4,1,2,3,4 | stack roll=8,6 | stack pop=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 12.);
assert_eq!(data[0][1], 11.);
let mut data = master_data.clone();
let op = ctx.op("stack push=1,2,3,4,1,2,3,4 | stack roll=3,2 | stack pop=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 12.);
assert_eq!(data[0][1], 14.);
let mut data = master_data.clone();
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,-2 | stack pop=2,1")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 12.);
assert_eq!(data[0][1], 13.);
let mut data = master_data.clone();
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack roll=3,1 | stack pop=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 14.);
assert_eq!(data[0][1], 13.);
let mut data = master_data.clone();
let op =
ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack unroll=3,2 | stack pop=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0][0], 14.);
assert_eq!(data[0][1], 13.);
Ok(())
}
#[test]
fn stack_examples_from_rumination_002() -> Result<(), Error> {
let mut ctx = Minimal::default();
let master_data = vec![Coor4D([1., 2., 3., 4.])];
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?;
let mut data = master_data.clone();
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 3., 4., 2.]);
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,-2 | stack pop=4,3,2,1")?;
let mut data = master_data.clone();
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 4., 2., 3.]);
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?;
let mut data = master_data.clone();
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 3., 4., 2.]);
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,1 | stack pop=4,3,2,1")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 2., 3., 4.]);
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?;
let mut data = master_data.clone();
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 4., 2., 3.]);
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,-2 | stack pop=4,3,2,1")?;
let mut data = master_data.clone();
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 3., 4., 2.]);
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 2., 3., 4.]);
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 3., 4., 2.]);
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [1., 2., 3., 4.]);
let op = ctx.op("stack push=1,2,3,4 | helmert x=4 y=4 z=4 | stack flip=1,2")?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [4., 3., 7., 4.]);
let mut data = master_data.clone();
let op = ctx.op(
"stack push=1,2,3,4 | helmert translation=4,4,4 | stack flip=1,2 | stack flip=1,2",
)?;
ctx.apply(op, Fwd, &mut data)?;
assert_eq!(data[0].0, [5., 6., 7., 4.]);
Ok(())
}
}