Skip to content

Commit

Permalink
allow negative repeat
Browse files Browse the repository at this point in the history
  • Loading branch information
kaikalii committed Jun 18, 2024
1 parent abe98c0 commit 8592f89
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 46 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 36 additions & 20 deletions src/algorithm/invert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2100,58 +2100,74 @@ fn invert_repeat_pattern<'a>(
input: &'a [Instr],
comp: &mut Compiler,
) -> Option<(&'a [Instr], EcoVec<Instr>)> {
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>(
input: &'a [Instr],
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))
}
Expand Down
20 changes: 13 additions & 7 deletions src/algorithm/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ pub fn flip<A, B, C>(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<Array<f64>> {
Ok(match value {
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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<Function>, n: f64, env: &mut Uiua) -> UiuaResult {
let sig = f.signature();
if n.is_infinite() {
// Converging repeat
Expand Down Expand Up @@ -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::<Value>(), [n, delta]).is_err() {
Expand Down
46 changes: 30 additions & 16 deletions src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
8 changes: 7 additions & 1 deletion src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down
32 changes: 32 additions & 0 deletions src/compile/modifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions src/primitive/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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", '⍥')),
Expand Down Expand Up @@ -2797,4 +2799,6 @@ impl_primitive!(
(1, CountUnique),
(1, EndRandArray, Impure),
(1(2)[3], AstarFirst),
// Implementation details
(1[2], RepeatWithInverse),
);
4 changes: 3 additions & 1 deletion src/primitive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ impl fmt::Display for ImplPrimitive {
}
Ok(())
}
RepeatWithInverse => write!(f, "{Repeat}"),
}
}
}
Expand Down Expand Up @@ -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)?,
Expand Down Expand Up @@ -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(())
}
Expand Down
1 change: 1 addition & 0 deletions tests/loops.ua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion todo.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Uiua Todo

- 0.12
- Negative repeat
- Add `by` to tutorial
- Stack-source locality tutorial
- Rewrite .uasm format
Expand Down

0 comments on commit 8592f89

Please sign in to comment.