diff --git a/changelog.md b/changelog.md index e39325d48..83e2416e5 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ This version is not yet released. If you are reading this on the website, then t - [`keep ▽`](https://uiua.org/docs/keep) now works with non-integer scalar counts to scale an array - [`join ⊂`](https://uiua.org/docs/join) will rank differences greater than 1 can now extend the smaller array - [`un °`](https://uiua.org/docs/un) [`join ⊂`](https://uiua.org/docs/join) is now easier to combine with other inverses +- [`repeat ⍥`](https://uiua.org/docs/repeat) can now repeat a negative number of times, which will repeat the inverse - Add the experimental [`triangle ◹`](https://uiua.org/docs/triangle) modifier, which calls a function on shrinking suffixes of an array's rows - Add the experimental [`orient`](https://uiua.org/docs/orient) function, which arranges an array's axes in a specified order - Add the experimental [`fft`](https://uiua.org/docs/fft) function, which performs the Fast Fourier transform diff --git a/src/algorithm/invert.rs b/src/algorithm/invert.rs index b5828e8f9..43e952189 100644 --- a/src/algorithm/invert.rs +++ b/src/algorithm/invert.rs @@ -2100,14 +2100,25 @@ fn invert_repeat_pattern<'a>( input: &'a [Instr], comp: &mut Compiler, ) -> Option<(&'a [Instr], EcoVec)> { - let [Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] = input - else { - return None; - }; - let instrs = f.instrs(&comp.asm).to_vec(); - let inverse = invert_instrs(&instrs, comp)?; - let inverse = make_fn(inverse, *span, comp)?; - Some((input, eco_vec![Instr::PushFunc(inverse), repeat.clone()])) + match input { + [Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] => { + let instrs = f.instrs(&comp.asm).to_vec(); + let inverse = invert_instrs(&instrs, comp)?; + let inverse = make_fn(inverse, *span, comp)?; + Some((input, eco_vec![Instr::PushFunc(inverse), repeat.clone()])) + } + [Instr::PushFunc(inv), Instr::PushFunc(f), Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, span), input @ ..] => { + Some(( + input, + eco_vec![ + Instr::PushFunc(f.clone()), + Instr::PushFunc(inv.clone()), + Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, *span), + ], + )) + } + _ => None, + } } fn under_repeat_pattern<'a>( @@ -2115,43 +2126,48 @@ fn under_repeat_pattern<'a>( g_sig: Signature, comp: &mut Compiler, ) -> Option<(&'a [Instr], Under)> { + use ImplPrimitive::*; + use Instr::*; + use Primitive::*; Some(match input { - [Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] => { + [PushFunc(f), Prim(Repeat, span), input @ ..] + | [PushFunc(_), PushFunc(f), ImplPrim(RepeatWithInverse, span), input @ ..] => { let instrs = f.instrs(&comp.asm).to_vec(); let (befores, afters) = under_instrs(&instrs, g_sig, comp)?; let befores = eco_vec![ - Instr::CopyToTemp { + CopyToTemp { stack: TempStack::Under, count: 1, span: *span }, - Instr::PushFunc(make_fn(befores, *span, comp)?), - repeat.clone() + PushFunc(make_fn(befores, *span, comp)?), + Prim(Repeat, *span) ]; let afters = eco_vec![ - Instr::PopTemp { + PopTemp { stack: TempStack::Under, count: 1, span: *span }, - Instr::PushFunc(make_fn(afters, *span, comp)?), - repeat.clone() + PushFunc(make_fn(afters, *span, comp)?), + Prim(Repeat, *span) ]; (input, (befores, afters)) } - [push @ Instr::Push(_), Instr::PushFunc(f), repeat @ Instr::Prim(Primitive::Repeat, span), input @ ..] => + [push @ Push(_), PushFunc(f), Prim(Repeat, span), input @ ..] + | [push @ Push(_), PushFunc(_), PushFunc(f), ImplPrim(RepeatWithInverse, span), input @ ..] => { let instrs = f.instrs(&comp.asm).to_vec(); let (befores, afters) = under_instrs(&instrs, g_sig, comp)?; let befores = eco_vec![ push.clone(), - Instr::PushFunc(make_fn(befores, *span, comp)?), - repeat.clone() + PushFunc(make_fn(befores, *span, comp)?), + Prim(Repeat, *span) ]; let afters = eco_vec![ push.clone(), - Instr::PushFunc(make_fn(afters, *span, comp)?), - repeat.clone() + PushFunc(make_fn(afters, *span, comp)?), + Prim(Repeat, *span) ]; (input, (befores, afters)) } diff --git a/src/algorithm/loops.rs b/src/algorithm/loops.rs index c83eaac6c..1f91fa340 100644 --- a/src/algorithm/loops.rs +++ b/src/algorithm/loops.rs @@ -23,9 +23,10 @@ pub fn flip(f: impl Fn(A, B) -> C + Copy) -> impl Fn(B, A) -> C + Copy move |b, a| f(a, b) } -pub fn repeat(env: &mut Uiua) -> UiuaResult { +pub fn repeat(with_inverse: bool, env: &mut Uiua) -> UiuaResult { crate::profile_function!(); let f = env.pop_function()?; + let inv = with_inverse.then(|| env.pop_function()).transpose()?; let n = env.pop("repetition count")?; fn rep_count(value: Value, env: &Uiua) -> UiuaResult> { Ok(match value { @@ -44,7 +45,7 @@ pub fn repeat(env: &mut Uiua) -> UiuaResult { if n.rank() == 0 { // Scalar repeat let n = rep_count(n, env)?; - repeat_impl(f, n.data[0], env) + repeat_impl(f, inv, n.data[0], env) } else { // Array let sig = f.signature(); @@ -109,7 +110,7 @@ pub fn repeat(env: &mut Uiua) -> UiuaResult { // println!(" row: {:?}", row); env.push(row); } - repeat_impl(f.clone(), elem, env)?; + repeat_impl(f.clone(), inv.clone(), elem, env)?; for i in 0..sig.outputs { let res = env.pop("repeat output")?; // println!(" res: {:?}", res); @@ -131,7 +132,7 @@ pub fn repeat(env: &mut Uiua) -> UiuaResult { } } -fn repeat_impl(f: Function, n: f64, env: &mut Uiua) -> UiuaResult { +fn repeat_impl(f: Function, inv: Option, n: f64, env: &mut Uiua) -> UiuaResult { let sig = f.signature(); if n.is_infinite() { // Converging repeat @@ -171,10 +172,15 @@ fn repeat_impl(f: Function, n: f64, env: &mut Uiua) -> UiuaResult { } } else { // Normal repeat - if n < 0.0 || n.fract() != 0.0 { - return Err(env.error("Repetitions must be a natural number or infinity")); + if n.fract() != 0.0 { + return Err(env.error("Repetitions must be an integer or infinity")); } - let n = n as usize; + let (f, n) = if n >= 0.0 { + (f, n as usize) + } else { + let f = inv.ok_or_else(|| env.error("No inverse found"))?; + (f, (-n) as usize) + }; if sig.outputs > sig.args { let delta = sig.outputs - sig.args; if validate_size_impl(size_of::(), [n, delta]).is_err() { diff --git a/src/check.rs b/src/check.rs index 090b858cb..ada4bff06 100644 --- a/src/check.rs +++ b/src/check.rs @@ -519,25 +519,39 @@ impl<'a> VirtualEnv<'a> { self.handle_args_outputs(args, outputs)?; } }, - Instr::ImplPrim(ImplPrimitive::ReduceContent | ImplPrimitive::ReduceDepth(_), _) => { - let sig = self.pop_func()?.signature(); - let args = sig.args.saturating_sub(sig.outputs); - self.handle_args_outputs(args, sig.outputs)?; - } - Instr::ImplPrim(prim, _) => { - let args = prim.args(); - for _ in 0..prim.modifier_args().unwrap_or(0) { - self.pop_func()?; + Instr::ImplPrim(prim, _) => match prim { + ImplPrimitive::ReduceContent | ImplPrimitive::ReduceDepth(_) => { + let sig = self.pop_func()?.signature(); + let args = sig.args.saturating_sub(sig.outputs); + self.handle_args_outputs(args, sig.outputs)?; } - for _ in 0..args { - self.pop()?; + ImplPrimitive::RepeatWithInverse => { + let f = self.pop_func()?; + let inv = self.pop_func()?; + if f.signature().inverse() != inv.signature() { + return Err(SigCheckError::from( + "repeat inverse does not have inverse signature", + ) + .ambiguous()); + } + let n = self.pop()?; + self.repeat(f.signature(), n)?; } - self.set_min_height(); - let outputs = prim.outputs(); - for _ in 0..outputs { - self.stack.push(BasicValue::Other); + prim => { + let args = prim.args(); + for _ in 0..prim.modifier_args().unwrap_or(0) { + self.pop_func()?; + } + for _ in 0..args { + self.pop()?; + } + self.set_min_height(); + let outputs = prim.outputs(); + for _ in 0..outputs { + self.stack.push(BasicValue::Other); + } } - } + }, Instr::PushSig(_) | Instr::PopSig => { panic!("PushSig and PopSig should have been handled higher up") } diff --git a/src/compile/mod.rs b/src/compile/mod.rs index b0ad686ba..717d0bb92 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -1372,7 +1372,13 @@ code: // from the signature check, but it is not okay in an array. if let Some(i) = instrs .iter() - .position(|instr| matches!(instr, Instr::Prim(Primitive::Repeat, _))) + .position(|instr| { + matches!( + instr, + Instr::Prim(Primitive::Repeat, _) + | Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, _) + ) + }) .filter(|&i| i > 0) { if let Instr::PushFunc(f) = &instrs[i - 1] { diff --git a/src/compile/modifier.rs b/src/compile/modifier.rs index 023cebf77..400d8d441 100644 --- a/src/compile/modifier.rs +++ b/src/compile/modifier.rs @@ -713,6 +713,38 @@ impl Compiler { self.push_instr(Instr::PushFunc(func)); } } + Repeat => { + let operand = modified.code_operands().next().unwrap().clone(); + let (instrs, sig) = self.compile_operand_word(operand)?; + let spandex = self.add_span(modified.modifier.span.clone()); + let instrs = if let Some((inverse, inv_sig)) = invert_instrs(&instrs, self) + .and_then(|inv| instrs_signature(&inv).ok().map(|sig| (inv, sig))) + { + // If an inverse for repeat's function exists we use a special + // implementation that allows for negative repeatition counts + let id = FunctionId::Anonymous(modified.modifier.span.clone()); + let func = self.make_function(id, sig, instrs); + let inv_id = FunctionId::Anonymous(modified.modifier.span.clone()); + let inv = self.make_function(inv_id, inv_sig, inverse); + eco_vec![ + Instr::PushFunc(inv), + Instr::PushFunc(func), + Instr::ImplPrim(ImplPrimitive::RepeatWithInverse, spandex) + ] + } else { + let id = FunctionId::Anonymous(modified.modifier.span.clone()); + let func = self.make_function(id, sig, instrs); + eco_vec![Instr::PushFunc(func), Instr::Prim(Repeat, spandex)] + }; + if call { + self.push_all_instrs(instrs); + } else { + let sig = self.sig_of(&instrs, &modified.modifier.span)?; + let func = + self.make_function(modified.modifier.span.clone().into(), sig, instrs); + self.push_instr(Instr::PushFunc(func)); + } + } Un if !self.in_inverse => { let mut operands = modified.code_operands().cloned(); let f = operands.next().unwrap(); diff --git a/src/function.rs b/src/function.rs index b60d1d0eb..b17b2fddf 100644 --- a/src/function.rs +++ b/src/function.rs @@ -669,6 +669,10 @@ impl Signature { let outputs = self.outputs + other.outputs.saturating_sub(self.args); Self::new(args, outputs) } + /// Get the inverse of this signature + pub fn inverse(self) -> Self { + Self::new(self.outputs, self.args) + } } impl PartialEq<(usize, usize)> for Signature { diff --git a/src/primitive/defs.rs b/src/primitive/defs.rs index 0e8b9c548..2733465a3 100644 --- a/src/primitive/defs.rs +++ b/src/primitive/defs.rs @@ -1581,6 +1581,8 @@ primitive!( /// ex: F ← ⍥(×10)<10. /// : F 5 /// : F 12 + /// [repeat]ing a negative number of times will repeat the function's [un]-inverse. + /// ex: ⍥(×2)¯5 1024 /// /// [repeat]'s glyph is a combination of a circle, representing a loop, and the 𝄇 symbol from musical notation. ([1], Repeat, IteratingModifier, ("repeat", '⍥')), @@ -2797,4 +2799,6 @@ impl_primitive!( (1, CountUnique), (1, EndRandArray, Impure), (1(2)[3], AstarFirst), + // Implementation details + (1[2], RepeatWithInverse), ); diff --git a/src/primitive/mod.rs b/src/primitive/mod.rs index 03353ef21..b0c3a53ae 100644 --- a/src/primitive/mod.rs +++ b/src/primitive/mod.rs @@ -254,6 +254,7 @@ impl fmt::Display for ImplPrimitive { } Ok(()) } + RepeatWithInverse => write!(f, "{Repeat}"), } } } @@ -620,7 +621,7 @@ impl Primitive { Primitive::Rows => zip::rows(env)?, Primitive::Table => table::table(env)?, Primitive::Inventory => zip::inventory(env)?, - Primitive::Repeat => loops::repeat(env)?, + Primitive::Repeat => loops::repeat(false, env)?, Primitive::Do => loops::do_(env)?, Primitive::Group => loops::group(env)?, Primitive::Partition => loops::partition(env)?, @@ -1079,6 +1080,7 @@ impl ImplPrimitive { ImplPrimitive::AstarFirst => algorithm::astar_first(env)?, &ImplPrimitive::ReduceDepth(depth) => reduce::reduce(depth, env)?, &ImplPrimitive::TransposeN(n) => env.monadic_mut(|val| val.transpose_depth(0, n))?, + ImplPrimitive::RepeatWithInverse => loops::repeat(true, env)?, } Ok(()) } diff --git a/tests/loops.ua b/tests/loops.ua index 7f94c566c..76371bb6b 100644 --- a/tests/loops.ua +++ b/tests/loops.ua @@ -227,6 +227,7 @@ A ← ↯2_3_4⇡24 ⍤⟜≍: [+1.+1. 6_7_8] ⍥(+1) [+1.+1. 1_2_3] 5 ⍤⟜≍: [+1.+1. 2_4_6] ⍥(+1) [+1.+1. 1_2_3] ¤[1 2 3] ⍤⟜≍: [..4_5_6] ⍥(+1) [+1.+1. 1_2_3] [1 2 3] +⍤⟜≍: 5 ⍥(+1) ¯5 10 # Do ⍤⟜≍: 1024 ⍢(×2)(<1000) 1 diff --git a/todo.md b/todo.md index 493d27d8c..ffae7d7ef 100644 --- a/todo.md +++ b/todo.md @@ -1,7 +1,6 @@ # Uiua Todo - 0.12 - - Negative repeat - Add `by` to tutorial - Stack-source locality tutorial - Rewrite .uasm format