Skip to content

Commit 5d65c5e

Browse files
committed
stack roll
1 parent 0b36c2e commit 5d65c5e

File tree

2 files changed

+125
-5
lines changed

2 files changed

+125
-5
lines changed

ruminations/002-rumination.md

+26-2
Original file line numberDiff line numberDiff line change
@@ -935,16 +935,17 @@ Note: Rust Geodesy does not support modifying the ellipsoid with an `R` paramete
935935

936936
### Operator `stack`
937937

938-
**Purpose:** Push/pop/swap coordinate dimensions onto the stack
938+
**Purpose:** Push/pop/roll/swap coordinate dimensions onto the stack
939939

940940
**Description:**
941-
Take a copy of one or more coordinate dimensions and push, pop or swap them onto the stack.
941+
Take a copy of one or more coordinate dimensions and/or push, pop, roll or swap them onto the stack.
942942

943943

944944
| Argument | Description |
945945
|------------|--------------------------------------------|
946946
| `push=...` | push a comma separated list of coordinate dimensions onto the stack |
947947
| `pop=...` | pop a comma separated list of coordinate dimensions off the stack, into an operand |
948+
| `roll=m,n` | On the sub-stack consisting of the m upper elements, roll n elements from the top, to the bottom of the sub-stack |
948949
| `swap` | swap the top-of-stack and the next-to-top-of-stack |
949950

950951
The arguments to `push` and `pop` are handled from left to right, i.e. in latin reading order,
@@ -963,6 +964,29 @@ while the second will get that of the 2OS.
963964

964965
All in all, that amounts to a swapping of the first two coordinate elements of the operand.
965966

967+
#### `stack roll`
968+
969+
Essentially, `roll=m,n` is a [big swap](https://stackoverflow.com/a/15997537/618276), essentially
970+
flipping the `n` upper elements with the `m - n` lower, as seen from these examples:
971+
972+
| Stack before | Instruction | Stack after |
973+
|----------------|--------------------------------------------|
974+
| 1,2,3,4 | roll=3,2 | 1,3,4,2 |
975+
| 1,2,3,4 | roll=3,-2 | 1,3,4,2 |
976+
| 1,3,4,2 | roll=3,1 | 1,2,3,4 |
977+
978+
Note that the last example shows that `roll=m,m-n` is the opposite of `roll=m,n`
979+
980+
#### Inverse operation
981+
982+
`stack` does not support the `inv` modifier. Instead use these substitutions:
983+
984+
| Forward | Inverse |
985+
|---------|-----------|
986+
| push | pop |
987+
| pop | push |
988+
| swap | swap |
989+
| roll=m,n| roll=m,m-n|
966990

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

src/inner_op/stack.rs

+99-3
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error>
5353
if roll_args.len() != 2
5454
|| roll_args[0].fract() != 0.
5555
|| roll_args[1].fract() != 0.
56-
|| roll_args[0] < roll_args[1].abs()
56+
|| roll_args[0] <= roll_args[1].abs()
5757
{
5858
return Err(Error::MissingParam(
59-
"roll takes exactly two integer parameters".to_string(),
59+
"roll takes exactly two integer parameters, ´(m,n): |n|<m´".to_string(),
6060
));
6161
}
6262
params.text.insert("action", "roll".to_string());
@@ -124,6 +124,16 @@ pub(super) fn stack_fwd(
124124
stack_pop(stack, operands, &args)
125125
}
126126

127+
"roll" => {
128+
let args: Vec<i64> = params
129+
.series("roll")
130+
.unwrap()
131+
.iter()
132+
.map(|i| *i as i64)
133+
.collect();
134+
stack_roll(stack, operands, &args)
135+
}
136+
127137
"swap" => {
128138
let n = stack.len();
129139
if n > 1 {
@@ -179,6 +189,17 @@ pub(super) fn stack_inv(
179189
return stack_push(stack, operands, &args);
180190
}
181191

192+
"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];
200+
stack_roll(stack, operands, &args)
201+
}
202+
182203
"swap" => {
183204
let n = stack.len();
184205
if n > 1 {
@@ -222,6 +243,43 @@ fn stack_push(
222243
number_of_operands
223244
}
224245

246+
/// roll m,n: On the sub-stack consisting of the m upper elements,
247+
/// 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
250+
fn stack_roll(stack: &mut Vec<Vec<f64>>, operands: &mut dyn CoordinateSet, args: &[i64]) -> usize {
251+
let m = args[0].abs();
252+
let mut n = args[1];
253+
let depth = stack.len();
254+
//dbg!(&stack);
255+
dbg!(&args);
256+
257+
// Negative n: count the number of rolled elements from the bottom,
258+
// i.e. roll 3,-2 = roll 3,1
259+
n = if n < 0 { m + n } else { n };
260+
261+
// The remaining becomes simpler if m, n and depth are all usize
262+
let m = m as usize;
263+
let n = n as usize;
264+
265+
if m > depth {
266+
warn!("Roll too deep");
267+
let nanny = Coor4D::nan();
268+
for i in 0..operands.len() {
269+
operands.set_coord(i, &nanny);
270+
}
271+
return 0;
272+
}
273+
274+
for _ in 0..n {
275+
let e = stack.pop().unwrap();
276+
stack.insert(depth - m, e);
277+
}
278+
//dbg!(&stack);
279+
280+
operands.len()
281+
}
282+
225283
/// Pop elements from the stack into elements of a CoordinateSet
226284
fn stack_pop(stack: &mut Vec<Vec<f64>>, operands: &mut dyn CoordinateSet, args: &[usize]) -> usize {
227285
let number_of_pops = args.len();
@@ -290,7 +348,7 @@ mod tests {
290348
// use case, that would require code complication to disallow)
291349
assert!(ctx.op("stack push=2,2,1,1 | stack pop=1,1,2").is_ok());
292350

293-
// ----- Three tests of the actual functionality -----
351+
// ----- Four tests of the actual functionality -----
294352

295353
let mut data = master_data.clone();
296354

@@ -342,6 +400,44 @@ mod tests {
342400
assert_eq!(data[0], master_data[0]);
343401
assert_eq!(data[1], master_data[1]);
344402

403+
// 4: Test the `roll` subcommand
404+
let op = ctx.op("stack push=1,1,1,2,1,3,1,4 | stack roll=8,2 | stack pop=1,2")?;
405+
ctx.apply(op, Fwd, &mut data)?;
406+
assert_eq!(data[0][0], 13.);
407+
assert_eq!(data[0][1], 11.);
408+
409+
// Then we do the inverse. We must, however, redo, since the push-pop asymmetry
410+
// would otherwise wreak havoc:
411+
412+
// Just calling apply in the inverse direction leads to underflow:
413+
assert_eq!(0, ctx.apply(op, Inv, &mut data)?);
414+
415+
// Instead, we must substitute (m,n) with (m,m-n)
416+
let mut data = master_data.clone();
417+
let op = ctx.op("stack push=1,2,3,4,1,2,3,4 | stack roll=8,6 | stack pop=1,2")?;
418+
ctx.apply(op, Fwd, &mut data)?;
419+
assert_eq!(data[0][0], 12.);
420+
assert_eq!(data[0][1], 11.);
421+
422+
let mut data = master_data.clone();
423+
let op = ctx.op("stack push=1,2,3,4,1,2,3,4 | stack roll=3,2 | stack pop=1,2")?;
424+
ctx.apply(op, Fwd, &mut data)?;
425+
assert_eq!(data[0][0], 12.);
426+
assert_eq!(data[0][1], 14.);
427+
428+
let mut data = master_data.clone();
429+
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,-2 | stack pop=2,1")?;
430+
ctx.apply(op, Fwd, &mut data)?;
431+
assert_eq!(data[0][0], 12.);
432+
assert_eq!(data[0][1], 13.);
433+
434+
// Roundrip roll
435+
let mut data = master_data.clone();
436+
let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack roll=3,1 | stack pop=1,2")?;
437+
ctx.apply(op, Fwd, &mut data)?;
438+
assert_eq!(data[0][0], 14.);
439+
assert_eq!(data[0][1], 13.);
440+
345441
Ok(())
346442
}
347443
}

0 commit comments

Comments
 (0)