Skip to content

Commit 85e0982

Browse files
committed
Better series handling in ParsedParameters
ParsedParameters now supports two additional accessors for series data: series_as_i64 and series_as_usize. Testing and development of these fetures have been in tandem with the development of the inner_op/stack functionality, where the code has improved significantly through this extension. For further testing, inner_op/stack has been extended with an unroll action, improved documentation in Rumination #2, which has also contributed to the implementation of an extended test suite
1 parent 89eeddc commit 85e0982

File tree

3 files changed

+180
-65
lines changed

3 files changed

+180
-65
lines changed

ruminations/002-rumination.md

+39-9
Original file line numberDiff line numberDiff line change
@@ -965,27 +965,57 @@ All in all, that amounts to a swapping of the first two coordinate elements of t
965965

966966
#### `stack roll`
967967

968-
Essentially, `roll=m,n` is a [big swap](https://stackoverflow.com/a/15997537/618276), essentially
969-
flipping the `n` upper elements with the `m - n` lower, as seen from these examples:
968+
Essentially, `roll=m,n` is a [big swap](https://stackoverflow.com/a/15997537/618276), hence
969+
swapping the `n` upper elements with the `m - n` lower.
970970

971-
| Stack before | Instruction | Stack after |
972-
|----------------|--------------------------------------------|
973-
| 1,2,3,4 | roll=3,2 | 1,3,4,2 |
974-
| 1,2,3,4 | roll=3,-2 | 1,3,4,2 |
975-
| 1,3,4,2 | roll=3,1 | 1,2,3,4 |
971+
If `n < 0`, the split between the lower and upper blocks is counted from the bottom of the
972+
substack, by implicitly setting `n = m + n` before operating, as seen from these examples:
976973

977-
Note that the last example shows that `roll=m,m-n` is the opposite of `roll=m,n`
974+
| Stack before | Instruction | Stack after |
975+
| -------------- | ----------- | ---------------- |
976+
| 1,2,3,4 | roll=3,-2 | 1,4,2,3 |
977+
| 1,2,3,4 | roll=3,1 | 1,4,2,3 |
978+
| 1,2,3,4 | roll=3,2 | 1,3,4,2 |
979+
| 1,3,4,2 | roll=3,1 | 1,2,3,4 |
980+
981+
Note that the first two examples show that for negative `n`, `roll=m,n`
982+
is the same as `roll=m,m+n`, while the last two examples show that
983+
`roll=m,m-n` is the opposite of `roll=m,n`.
984+
985+
#### `stack unroll`
986+
987+
For easier construction of "the opposite case", above, `stack unroll`
988+
is the tool. Essentially, `unroll=m,n` is the same as `roll=m,m-n`,
989+
i.e. a [big swap](https://stackoverflow.com/a/15997537/618276),
990+
swapping the `n` *lower* elements with the `m - n` *upper*,
991+
as seen from these examples:
992+
993+
| Stack before | Instruction | Stack after |
994+
| -------------- | ------------ | --------------- |
995+
| 1,2,3,4 | unroll=3,2 | 1,4,2,3 |
996+
| 1,2,3,4 | unroll=3,-2 | 1,3,4,2 |
997+
| 1,3,4,2 | unroll=3,2 | 1,2,3,4 |
998+
| 1,2,3,4 | roll=3,2 | 1,3,4,2 |
999+
| 1,3,4,2 | unroll=3,2 | 1,2,3,4 |
1000+
1001+
Note that the last example shows that `unroll=m,n` is the opposite of `roll=m,n`
9781002

9791003
#### Inverse operation
9801004

9811005
`stack` does not support the `inv` modifier. Instead use these substitutions:
9821006

9831007
| Forward | Inverse |
984-
|---------|-----------|
1008+
| ------- | --------- |
9851009
| push | pop |
9861010
| pop | push |
9871011
| swap | swap |
9881012
| roll=m,n| roll=m,m-n|
1013+
| roll=m,n| unroll=m,n|
1014+
1015+
#### Swapping two 2D coordinates packed in a 4D
1016+
1017+
- `stack push=1,2,3,4 | stack roll=4,2 | stack pop=2,1,4,3` or
1018+
- `stack push=1,2,3,4 | stack pop=4,3,2,1`
9891019

9901020
**See also:** [`pop`](#operator-pop) (deprecated), [`push`](#operator-push) (deprecated)
9911021

src/inner_op/stack.rs

+107-56
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use crate::authoring::*;
33

44
// NOTE: roll and drop are not implemented yet
55
#[rustfmt::skip]
6-
pub const STACK_GAMUT: [OpParameter; 5] = [
6+
pub const STACK_GAMUT: [OpParameter; 6] = [
77
OpParameter::Series { key: "push", default: Some("") },
88
OpParameter::Series { key: "pop", default: Some("") },
99
OpParameter::Series { key: "roll", default: Some("") },
10+
OpParameter::Series { key: "unroll", default: Some("") },
1011
OpParameter::Flag { key: "swap" },
1112
OpParameter::Flag { key: "drop" },
1213
];
@@ -56,12 +57,26 @@ pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error>
5657
|| roll_args[0] <= roll_args[1].abs()
5758
{
5859
return Err(Error::MissingParam(
59-
"roll takes exactly two integer parameters, ´(m,n): |n|<m´".to_string(),
60+
"roll takes exactly two integer parameters, ´(m,n): |n|<=m´".to_string(),
6061
));
6162
}
6263
params.text.insert("action", "roll".to_string());
6364
}
6465

66+
if let Ok(roll_args) = params.series("unroll") {
67+
subcommands_given += 1;
68+
if roll_args.len() != 2
69+
|| roll_args[0].fract() != 0.
70+
|| roll_args[1].fract() != 0.
71+
|| roll_args[0] <= roll_args[1].abs()
72+
{
73+
return Err(Error::MissingParam(
74+
"unroll takes exactly two integer parameters, ´(m,n): |n|<=m´".to_string(),
75+
));
76+
}
77+
params.text.insert("action", "unroll".to_string());
78+
}
79+
6580
if params.boolean("swap") {
6681
subcommands_given += 1;
6782
params.text.insert("action", "swap".to_string());
@@ -74,7 +89,7 @@ pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error>
7489

7590
if subcommands_given != 1 {
7691
return Err(Error::MissingParam(
77-
"stack: must specify exactly one of push/pop/roll/swap/drop".to_string(),
92+
"stack: must specify exactly one of push/pop/roll/swap/unroll/drop".to_string(),
7893
));
7994
}
8095

@@ -103,34 +118,24 @@ pub(super) fn stack_fwd(
103118

104119
let successes = match action.as_str() {
105120
"push" => {
106-
// Turn f64 dimensions 1-4 into usize indices 0-3
107-
let args: Vec<usize> = params
108-
.series("push")
109-
.unwrap()
110-
.iter()
111-
.map(|i| *i as usize - 1)
112-
.collect();
121+
let args = params.series_as_usize("push").unwrap();
113122
stack_push(stack, operands, &args)
114123
}
115124

116125
"pop" => {
117-
// Turn f64 dimensions 1-4 into usize indices 0-3
118-
let args: Vec<usize> = params
119-
.series("pop")
120-
.unwrap()
121-
.iter()
122-
.map(|i| *i as usize - 1)
123-
.collect();
126+
let args = params.series_as_usize("pop").unwrap();
124127
stack_pop(stack, operands, &args)
125128
}
126129

127130
"roll" => {
128-
let args: Vec<i64> = params
129-
.series("roll")
130-
.unwrap()
131-
.iter()
132-
.map(|i| *i as i64)
133-
.collect();
131+
let args = params.series_as_i64("roll").unwrap();
132+
stack_roll(stack, operands, &args)
133+
}
134+
135+
"unroll" => {
136+
let mut args = params.series_as_i64("unroll").unwrap();
137+
args[1] = args[0] - args[1];
138+
dbg!(&args);
134139
stack_roll(stack, operands, &args)
135140
}
136141

@@ -165,38 +170,28 @@ pub(super) fn stack_inv(
165170
};
166171

167172
let successes = match action.as_str() {
173+
// An inverse push is a pop with reversed args
168174
"push" => {
169-
// Turn f64 dimensions 1-4 into **reversed** usize indices 0-3 ******
170-
let args: Vec<usize> = params
171-
.series("push")
172-
.unwrap()
173-
.iter()
174-
.rev()
175-
.map(|i| *i as usize - 1)
176-
.collect();
175+
let mut args = params.series_as_usize("push").unwrap();
176+
args.reverse();
177177
stack_pop(stack, operands, &args)
178178
}
179179

180+
// And an inverse pop is a push with reversed args
180181
"pop" => {
181-
// Turn f64 dimensions 1-4 into **reversed** usize indices 0-3
182-
let args: Vec<usize> = params
183-
.series("pop")
184-
.unwrap()
185-
.iter()
186-
.rev()
187-
.map(|i| *i as usize - 1)
188-
.collect();
189-
return stack_push(stack, operands, &args);
182+
let mut args = params.series_as_usize("pop").unwrap();
183+
args.reverse();
184+
stack_push(stack, operands, &args)
190185
}
191186

192187
"roll" => {
193-
let mut args: Vec<i64> = params
194-
.series("roll")
195-
.unwrap()
196-
.iter()
197-
.map(|i| *i as i64)
198-
.collect();
199-
args[1] = -args[1];
188+
let mut args = params.series_as_i64("roll").unwrap();
189+
args[1] = args[0] - args[1];
190+
stack_roll(stack, operands, &args)
191+
}
192+
193+
"unroll" => {
194+
let args = params.series_as_i64("roll").unwrap();
200195
stack_roll(stack, operands, &args)
201196
}
202197

@@ -234,7 +229,8 @@ fn stack_push(
234229
for i in 0..number_of_operands {
235230
let coord = operands.get_coord(i);
236231
for j in 0..number_of_pushes {
237-
ext[j][i] = coord[args[j]];
232+
// args are 1 based so we adjust
233+
ext[j][i] = coord[args[j] - 1];
238234
}
239235
}
240236

@@ -245,8 +241,8 @@ fn stack_push(
245241

246242
/// roll m,n: On the sub-stack consisting of the m upper elements,
247243
/// roll n elements from the top, to the bottom of the sub-stack.
248-
/// Hence, roll is a "large flip", essentially flipping the n upper
249-
/// elements with the m - n lower
244+
/// Hence, roll is a "big swap", essentially swapping the n upper
245+
/// elements with the m - n lower.
250246
fn stack_roll(stack: &mut Vec<Vec<f64>>, operands: &mut dyn CoordinateSet, args: &[i64]) -> usize {
251247
let m = args[0].abs();
252248
let mut n = args[1];
@@ -294,21 +290,18 @@ fn stack_pop(stack: &mut Vec<Vec<f64>>, operands: &mut dyn CoordinateSet, args:
294290
}
295291

296292
// Remove the correct number of elements and obtain a reversed version.
297-
// Incidentally, this is both the easiest way to obtain the popped
298-
// subset, and the easiest way to make the top-of-stack (i.e. the
299-
// element first popped) have the index 0, which makes the 'for j...'
300-
// loop below slightly more straightforward
301293
let mut ext = Vec::with_capacity(number_of_pops);
302294
for _ in args {
303295
ext.push(stack.pop().unwrap());
304296
}
305297

306-
// Extract the required stack elements into the proper
298+
// Inject the required stack elements into the proper
307299
// positions of the coordinate elements
308300
for i in 0..number_of_operands {
309301
let mut coord = operands.get_coord(i);
310302
for j in 0..number_of_pops {
311-
coord[args[j]] = ext[j][i];
303+
// args are 1 based so we adjust
304+
coord[args[j] - 1] = ext[j][i];
312305
}
313306
operands.set_coord(i, &coord);
314307
}
@@ -435,6 +428,64 @@ mod tests {
435428
assert_eq!(data[0][0], 14.);
436429
assert_eq!(data[0][1], 13.);
437430

431+
// Roundrip roll using the unroll syntactic sugar
432+
let mut data = master_data.clone();
433+
let op =
434+
ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack unroll=3,2 | stack pop=1,2")?;
435+
ctx.apply(op, Fwd, &mut data)?;
436+
assert_eq!(data[0][0], 14.);
437+
assert_eq!(data[0][1], 13.);
438+
439+
Ok(())
440+
}
441+
442+
#[test]
443+
fn stack_examples_from_rumination_002() -> Result<(), Error> {
444+
let mut ctx = Minimal::default();
445+
let master_data = vec![Coor4D([1., 2., 3., 4.])];
446+
447+
// Roll
448+
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?;
449+
let mut data = master_data.clone();
450+
ctx.apply(op, Fwd, &mut data)?;
451+
assert_eq!(data[0].0, [1., 3., 4., 2.]);
452+
453+
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,-2 | stack pop=4,3,2,1")?;
454+
let mut data = master_data.clone();
455+
ctx.apply(op, Fwd, &mut data)?;
456+
assert_eq!(data[0].0, [1., 4., 2., 3.]);
457+
458+
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?;
459+
let mut data = master_data.clone();
460+
ctx.apply(op, Fwd, &mut data)?;
461+
assert_eq!(data[0].0, [1., 3., 4., 2.]);
462+
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,1 | stack pop=4,3,2,1")?;
463+
ctx.apply(op, Fwd, &mut data)?;
464+
assert_eq!(data[0].0, [1., 2., 3., 4.]);
465+
466+
// Unroll
467+
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?;
468+
let mut data = master_data.clone();
469+
ctx.apply(op, Fwd, &mut data)?;
470+
assert_eq!(data[0].0, [1., 4., 2., 3.]);
471+
472+
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,-2 | stack pop=4,3,2,1")?;
473+
let mut data = master_data.clone();
474+
ctx.apply(op, Fwd, &mut data)?;
475+
assert_eq!(data[0].0, [1., 3., 4., 2.]);
476+
477+
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?;
478+
ctx.apply(op, Fwd, &mut data)?;
479+
assert_eq!(data[0].0, [1., 2., 3., 4.]);
480+
481+
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?;
482+
ctx.apply(op, Fwd, &mut data)?;
483+
assert_eq!(data[0].0, [1., 3., 4., 2.]);
484+
485+
let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?;
486+
ctx.apply(op, Fwd, &mut data)?;
487+
assert_eq!(data[0].0, [1., 2., 3., 4.]);
488+
438489
Ok(())
439490
}
440491
}

0 commit comments

Comments
 (0)