Skip to content

Commit 8592f89

Browse files
committed
allow negative repeat
1 parent abe98c0 commit 8592f89

File tree

11 files changed

+131
-46
lines changed

11 files changed

+131
-46
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This version is not yet released. If you are reading this on the website, then t
1717
- [`keep ▽`](https://uiua.org/docs/keep) now works with non-integer scalar counts to scale an array
1818
- [`join ⊂`](https://uiua.org/docs/join) will rank differences greater than 1 can now extend the smaller array
1919
- [`un °`](https://uiua.org/docs/un) [`join ⊂`](https://uiua.org/docs/join) is now easier to combine with other inverses
20+
- [`repeat ⍥`](https://uiua.org/docs/repeat) can now repeat a negative number of times, which will repeat the inverse
2021
- Add the experimental [`triangle ◹`](https://uiua.org/docs/triangle) modifier, which calls a function on shrinking suffixes of an array's rows
2122
- Add the experimental [`orient`](https://uiua.org/docs/orient) function, which arranges an array's axes in a specified order
2223
- Add the experimental [`fft`](https://uiua.org/docs/fft) function, which performs the Fast Fourier transform

src/algorithm/invert.rs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2100,58 +2100,74 @@ fn invert_repeat_pattern<'a>(
21002100
input: &'a [Instr],
21012101
comp: &mut Compiler,
21022102
) -> Option<(&'a [Instr], EcoVec<Instr>)> {
2103-
let [Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] = input
2104-
else {
2105-
return None;
2106-
};
2107-
let instrs = f.instrs(&comp.asm).to_vec();
2108-
let inverse = invert_instrs(&instrs, comp)?;
2109-
let inverse = make_fn(inverse, *span, comp)?;
2110-
Some((input, eco_vec![Instr::PushFunc(inverse), repeat.clone()]))
2103+
match input {
2104+
[Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] => {
2105+
let instrs = f.instrs(&comp.asm).to_vec();
2106+
let inverse = invert_instrs(&instrs, comp)?;
2107+
let inverse = make_fn(inverse, *span, comp)?;
2108+
Some((input, eco_vec![Instr::PushFunc(inverse), repeat.clone()]))
2109+
}
2110+
[Instr::PushFunc(inv), Instr::PushFunc(f), Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, span), input @ ..] => {
2111+
Some((
2112+
input,
2113+
eco_vec![
2114+
Instr::PushFunc(f.clone()),
2115+
Instr::PushFunc(inv.clone()),
2116+
Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, *span),
2117+
],
2118+
))
2119+
}
2120+
_ => None,
2121+
}
21112122
}
21122123

21132124
fn under_repeat_pattern<'a>(
21142125
input: &'a [Instr],
21152126
g_sig: Signature,
21162127
comp: &mut Compiler,
21172128
) -> Option<(&'a [Instr], Under)> {
2129+
use ImplPrimitive::*;
2130+
use Instr::*;
2131+
use Primitive::*;
21182132
Some(match input {
2119-
[Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] => {
2133+
[PushFunc(f), Prim(Repeat, span), input @ ..]
2134+
| [PushFunc(_), PushFunc(f), ImplPrim(RepeatWithInverse, span), input @ ..] => {
21202135
let instrs = f.instrs(&comp.asm).to_vec();
21212136
let (befores, afters) = under_instrs(&instrs, g_sig, comp)?;
21222137
let befores = eco_vec![
2123-
Instr::CopyToTemp {
2138+
CopyToTemp {
21242139
stack: TempStack::Under,
21252140
count: 1,
21262141
span: *span
21272142
},
2128-
Instr::PushFunc(make_fn(befores, *span, comp)?),
2129-
repeat.clone()
2143+
PushFunc(make_fn(befores, *span, comp)?),
2144+
Prim(Repeat, *span)
21302145
];
21312146
let afters = eco_vec![
2132-
Instr::PopTemp {
2147+
PopTemp {
21332148
stack: TempStack::Under,
21342149
count: 1,
21352150
span: *span
21362151
},
2137-
Instr::PushFunc(make_fn(afters, *span, comp)?),
2138-
repeat.clone()
2152+
PushFunc(make_fn(afters, *span, comp)?),
2153+
Prim(Repeat, *span)
21392154
];
21402155
(input, (befores, afters))
21412156
}
2142-
[push @ Instr::Push(_), Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] =>
2157+
[push @ Push(_), PushFunc(f), Prim(Repeat, span), input @ ..]
2158+
| [push @ Push(_), PushFunc(_), PushFunc(f), ImplPrim(RepeatWithInverse, span), input @ ..] =>
21432159
{
21442160
let instrs = f.instrs(&comp.asm).to_vec();
21452161
let (befores, afters) = under_instrs(&instrs, g_sig, comp)?;
21462162
let befores = eco_vec![
21472163
push.clone(),
2148-
Instr::PushFunc(make_fn(befores, *span, comp)?),
2149-
repeat.clone()
2164+
PushFunc(make_fn(befores, *span, comp)?),
2165+
Prim(Repeat, *span)
21502166
];
21512167
let afters = eco_vec![
21522168
push.clone(),
2153-
Instr::PushFunc(make_fn(afters, *span, comp)?),
2154-
repeat.clone()
2169+
PushFunc(make_fn(afters, *span, comp)?),
2170+
Prim(Repeat, *span)
21552171
];
21562172
(input, (befores, afters))
21572173
}

src/algorithm/loops.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ pub fn flip<A, B, C>(f: impl Fn(A, B) -> C + Copy) -> impl Fn(B, A) -> C + Copy
2323
move |b, a| f(a, b)
2424
}
2525

26-
pub fn repeat(env: &mut Uiua) -> UiuaResult {
26+
pub fn repeat(with_inverse: bool, env: &mut Uiua) -> UiuaResult {
2727
crate::profile_function!();
2828
let f = env.pop_function()?;
29+
let inv = with_inverse.then(|| env.pop_function()).transpose()?;
2930
let n = env.pop("repetition count")?;
3031
fn rep_count(value: Value, env: &Uiua) -> UiuaResult<Array<f64>> {
3132
Ok(match value {
@@ -44,7 +45,7 @@ pub fn repeat(env: &mut Uiua) -> UiuaResult {
4445
if n.rank() == 0 {
4546
// Scalar repeat
4647
let n = rep_count(n, env)?;
47-
repeat_impl(f, n.data[0], env)
48+
repeat_impl(f, inv, n.data[0], env)
4849
} else {
4950
// Array
5051
let sig = f.signature();
@@ -109,7 +110,7 @@ pub fn repeat(env: &mut Uiua) -> UiuaResult {
109110
// println!(" row: {:?}", row);
110111
env.push(row);
111112
}
112-
repeat_impl(f.clone(), elem, env)?;
113+
repeat_impl(f.clone(), inv.clone(), elem, env)?;
113114
for i in 0..sig.outputs {
114115
let res = env.pop("repeat output")?;
115116
// println!(" res: {:?}", res);
@@ -131,7 +132,7 @@ pub fn repeat(env: &mut Uiua) -> UiuaResult {
131132
}
132133
}
133134

134-
fn repeat_impl(f: Function, n: f64, env: &mut Uiua) -> UiuaResult {
135+
fn repeat_impl(f: Function, inv: Option<Function>, n: f64, env: &mut Uiua) -> UiuaResult {
135136
let sig = f.signature();
136137
if n.is_infinite() {
137138
// Converging repeat
@@ -171,10 +172,15 @@ fn repeat_impl(f: Function, n: f64, env: &mut Uiua) -> UiuaResult {
171172
}
172173
} else {
173174
// Normal repeat
174-
if n < 0.0 || n.fract() != 0.0 {
175-
return Err(env.error("Repetitions must be a natural number or infinity"));
175+
if n.fract() != 0.0 {
176+
return Err(env.error("Repetitions must be an integer or infinity"));
176177
}
177-
let n = n as usize;
178+
let (f, n) = if n >= 0.0 {
179+
(f, n as usize)
180+
} else {
181+
let f = inv.ok_or_else(|| env.error("No inverse found"))?;
182+
(f, (-n) as usize)
183+
};
178184
if sig.outputs > sig.args {
179185
let delta = sig.outputs - sig.args;
180186
if validate_size_impl(size_of::<Value>(), [n, delta]).is_err() {

src/check.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -519,25 +519,39 @@ impl<'a> VirtualEnv<'a> {
519519
self.handle_args_outputs(args, outputs)?;
520520
}
521521
},
522-
Instr::ImplPrim(ImplPrimitive::ReduceContent | ImplPrimitive::ReduceDepth(_), _) => {
523-
let sig = self.pop_func()?.signature();
524-
let args = sig.args.saturating_sub(sig.outputs);
525-
self.handle_args_outputs(args, sig.outputs)?;
526-
}
527-
Instr::ImplPrim(prim, _) => {
528-
let args = prim.args();
529-
for _ in 0..prim.modifier_args().unwrap_or(0) {
530-
self.pop_func()?;
522+
Instr::ImplPrim(prim, _) => match prim {
523+
ImplPrimitive::ReduceContent | ImplPrimitive::ReduceDepth(_) => {
524+
let sig = self.pop_func()?.signature();
525+
let args = sig.args.saturating_sub(sig.outputs);
526+
self.handle_args_outputs(args, sig.outputs)?;
531527
}
532-
for _ in 0..args {
533-
self.pop()?;
528+
ImplPrimitive::RepeatWithInverse => {
529+
let f = self.pop_func()?;
530+
let inv = self.pop_func()?;
531+
if f.signature().inverse() != inv.signature() {
532+
return Err(SigCheckError::from(
533+
"repeat inverse does not have inverse signature",
534+
)
535+
.ambiguous());
536+
}
537+
let n = self.pop()?;
538+
self.repeat(f.signature(), n)?;
534539
}
535-
self.set_min_height();
536-
let outputs = prim.outputs();
537-
for _ in 0..outputs {
538-
self.stack.push(BasicValue::Other);
540+
prim => {
541+
let args = prim.args();
542+
for _ in 0..prim.modifier_args().unwrap_or(0) {
543+
self.pop_func()?;
544+
}
545+
for _ in 0..args {
546+
self.pop()?;
547+
}
548+
self.set_min_height();
549+
let outputs = prim.outputs();
550+
for _ in 0..outputs {
551+
self.stack.push(BasicValue::Other);
552+
}
539553
}
540-
}
554+
},
541555
Instr::PushSig(_) | Instr::PopSig => {
542556
panic!("PushSig and PopSig should have been handled higher up")
543557
}

src/compile/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1372,7 +1372,13 @@ code:
13721372
// from the signature check, but it is not okay in an array.
13731373
if let Some(i) = instrs
13741374
.iter()
1375-
.position(|instr| matches!(instr, Instr::Prim(Primitive::Repeat, _)))
1375+
.position(|instr| {
1376+
matches!(
1377+
instr,
1378+
Instr::Prim(Primitive::Repeat, _)
1379+
| Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, _)
1380+
)
1381+
})
13761382
.filter(|&i| i > 0)
13771383
{
13781384
if let Instr::PushFunc(f) = &instrs[i - 1] {

src/compile/modifier.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,38 @@ impl Compiler {
713713
self.push_instr(Instr::PushFunc(func));
714714
}
715715
}
716+
Repeat => {
717+
let operand = modified.code_operands().next().unwrap().clone();
718+
let (instrs, sig) = self.compile_operand_word(operand)?;
719+
let spandex = self.add_span(modified.modifier.span.clone());
720+
let instrs = if let Some((inverse, inv_sig)) = invert_instrs(&instrs, self)
721+
.and_then(|inv| instrs_signature(&inv).ok().map(|sig| (inv, sig)))
722+
{
723+
// If an inverse for repeat's function exists we use a special
724+
// implementation that allows for negative repeatition counts
725+
let id = FunctionId::Anonymous(modified.modifier.span.clone());
726+
let func = self.make_function(id, sig, instrs);
727+
let inv_id = FunctionId::Anonymous(modified.modifier.span.clone());
728+
let inv = self.make_function(inv_id, inv_sig, inverse);
729+
eco_vec![
730+
Instr::PushFunc(inv),
731+
Instr::PushFunc(func),
732+
Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, spandex)
733+
]
734+
} else {
735+
let id = FunctionId::Anonymous(modified.modifier.span.clone());
736+
let func = self.make_function(id, sig, instrs);
737+
eco_vec![Instr::PushFunc(func), Instr::Prim(Repeat, spandex)]
738+
};
739+
if call {
740+
self.push_all_instrs(instrs);
741+
} else {
742+
let sig = self.sig_of(&instrs, &modified.modifier.span)?;
743+
let func =
744+
self.make_function(modified.modifier.span.clone().into(), sig, instrs);
745+
self.push_instr(Instr::PushFunc(func));
746+
}
747+
}
716748
Un if !self.in_inverse => {
717749
let mut operands = modified.code_operands().cloned();
718750
let f = operands.next().unwrap();

src/function.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,10 @@ impl Signature {
669669
let outputs = self.outputs + other.outputs.saturating_sub(self.args);
670670
Self::new(args, outputs)
671671
}
672+
/// Get the inverse of this signature
673+
pub fn inverse(self) -> Self {
674+
Self::new(self.outputs, self.args)
675+
}
672676
}
673677

674678
impl PartialEq<(usize, usize)> for Signature {

src/primitive/defs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,8 @@ primitive!(
15811581
/// ex: F ← ⍥(×10)<10.
15821582
/// : F 5
15831583
/// : F 12
1584+
/// [repeat]ing a negative number of times will repeat the function's [un]-inverse.
1585+
/// ex: ⍥(×2)¯5 1024
15841586
///
15851587
/// [repeat]'s glyph is a combination of a circle, representing a loop, and the 𝄇 symbol from musical notation.
15861588
([1], Repeat, IteratingModifier, ("repeat", '⍥')),
@@ -2797,4 +2799,6 @@ impl_primitive!(
27972799
(1, CountUnique),
27982800
(1, EndRandArray, Impure),
27992801
(1(2)[3], AstarFirst),
2802+
// Implementation details
2803+
(1[2], RepeatWithInverse),
28002804
);

src/primitive/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ impl fmt::Display for ImplPrimitive {
254254
}
255255
Ok(())
256256
}
257+
RepeatWithInverse => write!(f, "{Repeat}"),
257258
}
258259
}
259260
}
@@ -620,7 +621,7 @@ impl Primitive {
620621
Primitive::Rows => zip::rows(env)?,
621622
Primitive::Table => table::table(env)?,
622623
Primitive::Inventory => zip::inventory(env)?,
623-
Primitive::Repeat => loops::repeat(env)?,
624+
Primitive::Repeat => loops::repeat(false, env)?,
624625
Primitive::Do => loops::do_(env)?,
625626
Primitive::Group => loops::group(env)?,
626627
Primitive::Partition => loops::partition(env)?,
@@ -1079,6 +1080,7 @@ impl ImplPrimitive {
10791080
ImplPrimitive::AstarFirst => algorithm::astar_first(env)?,
10801081
&ImplPrimitive::ReduceDepth(depth) => reduce::reduce(depth, env)?,
10811082
&ImplPrimitive::TransposeN(n) => env.monadic_mut(|val| val.transpose_depth(0, n))?,
1083+
ImplPrimitive::RepeatWithInverse => loops::repeat(true, env)?,
10821084
}
10831085
Ok(())
10841086
}

tests/loops.ua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ A ← ↯2_3_4⇡24
227227
⍤⟜≍: [+1.+1. 6_7_8] ⍥(+1) [+1.+1. 1_2_3] 5
228228
⍤⟜≍: [+1.+1. 2_4_6] ⍥(+1) [+1.+1. 1_2_3] ¤[1 2 3]
229229
⍤⟜≍: [..4_5_6] ⍥(+1) [+1.+1. 1_2_3] [1 2 3]
230+
⍤⟜≍: 5 ⍥(+1) ¯5 10
230231

231232
# Do
232233
⍤⟜≍: 1024 ⍢(×2)(<1000) 1

0 commit comments

Comments
 (0)