Skip to content

Commit

Permalink
let repeat and do reuse stack value
Browse files Browse the repository at this point in the history
  • Loading branch information
kaikalii committed Dec 27, 2024
1 parent 3a1bc2f commit a635f8f
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 64 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Uiua is not yet stable.
## 0.15.0 - 2025-??-??
This version is not yet released. If you are reading this on the website, then these changes are live here.
### Language
- **Breaking Change** - [`repeat ⍥`](https://uiua.org/docs/repeat) and [`do ⍢`](https://uiua.org/docs/do) with net-negative signatures now preserve lower stack values between iterations
- Signature comments can now use a `$` rather than a `?` to automatically label arguments and outputs
- Add sided subscripts for [`reach 𝄐`](https://uiua.org/docs/reach)

Expand Down
22 changes: 15 additions & 7 deletions src/algorithm/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ fn repeat_impl(f: SigNode, inv: Option<SigNode>, n: f64, env: &mut Uiua) -> Uiua
let f = inv.ok_or_else(|| env.error("No inverse found"))?;
(f, -n)
};
let preserve_count = f.sig.args.saturating_sub(f.sig.outputs);
let preserved = env.copy_n_down(preserve_count, f.sig.args)?;
let mut convergence_count = 0;
if n.is_infinite() {
// Converging repeat
Expand All @@ -180,6 +182,9 @@ fn repeat_impl(f: SigNode, inv: Option<SigNode>, n: f64, env: &mut Uiua) -> Uiua
let mut prev = env.pop(1)?;
env.push(prev.clone());
loop {
if preserve_count > 0 {
env.insert_stack(sig.outputs, preserved.iter().cloned())?;
}
env.exec(f.clone())?;
let next = env.pop("converging function result")?;
let converged = next == prev;
Expand Down Expand Up @@ -208,9 +213,13 @@ fn repeat_impl(f: SigNode, inv: Option<SigNode>, n: f64, env: &mut Uiua) -> Uiua
}
}
for _ in 0..n {
if preserve_count > 0 {
env.insert_stack(sig.outputs, preserved.iter().cloned())?;
}
env.exec(f.clone())?;
}
}
env.remove_n(preserve_count, sig.args)?;
Ok(convergence_count)
}

Expand Down Expand Up @@ -239,14 +248,10 @@ pub fn do_(ops: Ops, env: &mut Uiua) -> UiuaResult {
{} and {}, minus the condition, is {comp_sig}",
body.sig, cond.sig
))),
Ordering::Greater => Some(env.error(format!(
"Do's functions cannot have a negative net stack \
change, but the composed signature of {} and \
{}, minus the condition, is {comp_sig}",
body.sig, cond.sig
))),
_ => None,
};
let preserve_count = comp_sig.args.saturating_sub(comp_sig.outputs);
let preserved = env.copy_n_down(preserve_count, comp_sig.args)?;
loop {
// Make sure there are enough values
if env.stack().len() < copy_count {
Expand All @@ -271,12 +276,15 @@ pub fn do_(ops: Ops, env: &mut Uiua) -> UiuaResult {
break;
}
// Call body
if preserve_count > 0 {
env.insert_stack(comp_sig.outputs, preserved.iter().cloned())?;
}
env.exec(body.clone())?;
if let Some(err) = sig_err {
return Err(err);
}
}
Ok(())
env.remove_n(preserve_count, comp_sig.args)
}

pub fn split_by(f: SigNode, by_scalar: bool, keep_empty: bool, env: &mut Uiua) -> UiuaResult {
Expand Down
78 changes: 32 additions & 46 deletions src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use std::{
array,
cell::RefCell,
cmp::Ordering,
collections::HashMap,
fmt,
hash::{DefaultHasher, Hash, Hasher},
Expand Down Expand Up @@ -711,69 +710,56 @@ impl VirtualEnv {
&SigNode { sig, ref node }: &SigNode,
n: BasicValue,
) -> Result<(), SigCheckError> {
if let BasicValue::Num(n) = n {
// If n is a known natural number, then the function can have any signature.
let sig = if n >= 0.0 { sig } else { sig.inverse() };
if n.fract() == 0.0 {
let n = n.abs() as usize;
if n > 0 {
if n <= 100 {
for _ in 0..n {
self.node(node)?;
}
} else {
let (args, outputs) = match sig.args.cmp(&sig.outputs) {
Ordering::Equal => (sig.args, sig.outputs),
Ordering::Less => (sig.args, n * (sig.outputs - sig.args) + sig.args),
Ordering::Greater => {
((n - 1) * (sig.args - sig.outputs) + sig.args, sig.outputs)
if sig.args < sig.outputs {
// More outputs than arguments
if let BasicValue::Num(n) = n {
let sig = if n >= 0.0 { sig } else { sig.inverse() };
if n.fract() == 0.0 {
// If n is a known natural number, then it's fine
let n = n.abs() as usize;
if n > 0 {
if n <= 100 {
for _ in 0..n {
self.node(node)?;
}
} else {
let args = sig.args;
let outputs = n * (sig.outputs - sig.args) + sig.args;
if validate_size_of::<BasicValue>([outputs]).is_err() {
return Err("repeat with excessive outputs".into());
}
};
if validate_size_of::<BasicValue>([outputs]).is_err() {
return Err("repeat with excessive outputs".into());
self.handle_args_outputs(args, outputs);
}
self.handle_args_outputs(args, outputs);
}
}
} else if n.is_infinite() {
match sig.args.cmp(&sig.outputs) {
Ordering::Greater => {
return Err(SigCheckError::from(format!(
"repeat with infinity and a function with signature {sig}"
))
.loop_overreach());
}
Ordering::Less if self.array_depth == 0 => {
} else if n.is_infinite() {
// If n is infinite, then we must be in an array
if self.array_depth == 0 {
self.handle_args_outputs(sig.args, sig.outputs);
return Err(SigCheckError::from(format!(
"repeat with infinity and a function with signature {sig}"
))
.loop_variable(self.stack.sig().args));
} else {
self.handle_sig(sig);
}
_ => self.handle_sig(sig),
} else {
return Err("repeat without an integer or infinity".into());
}
} else {
return Err("repeat without an integer or infinity".into());
}
} else {
// If n is unknown, then what we do depends on the signature
match sig.args.cmp(&sig.outputs) {
Ordering::Equal => self.handle_sig(sig),
Ordering::Greater => {
return Err(SigCheckError::from(format!(
"repeat with no number and a function with signature {sig}"
))
.loop_overreach());
}
Ordering::Less if self.array_depth == 0 => {
// If there is no number, then we must be in an array
if self.array_depth == 0 {
self.handle_args_outputs(sig.args, sig.outputs);
return Err(SigCheckError::from(format!(
"repeat with no number and a function with signature {sig}"
))
.loop_variable(self.stack.sig().args));
} else {
self.handle_sig(sig);
}
Ordering::Less => self.handle_sig(sig),
}
} else {
// Non-positive case
self.handle_sig(sig);
}
Ok(())
}
Expand Down
22 changes: 17 additions & 5 deletions src/primitive/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,16 @@ primitive!(
///
/// ex: ⍥(+2)5 0
/// ex: ⍥(⊂2)5 []
/// If the net stack change of the function is negative, then lower stack values will be preserved between iterations.
/// ex: ⍥+5 3 10
/// ex: ⍥⊂5 1 2
/// If the net stack change of the function is positive, then either the number of repetitions must be static, or the [repeat] must be wrapped in an array.
/// ex: F ← ⍥(⊢.)2
/// : F [1_2 3_4]
/// ex! F ← ⍥(⊢.)⊙[1_2 3_4]
/// : F 2
/// ex: F ← {⍥(⊢.)⊙[1_2 3_4]}
/// : F 2
/// Repeating [infinity] times will do a fixed-point iteration.
/// The loop will end when the top value of the function's output is equal to the top value of the function's input.
/// For example, this could be used to flatten a deeply nested array.
Expand Down Expand Up @@ -2122,18 +2132,20 @@ primitive!(
/// Consider this equivalence:
/// ex: ⍢(×3|<100) 1
/// : ⍢(×3|<100.) 1
/// The net stack change of the two functions, minus the condition, must be 0.
/// The net stack change of the two functions, minus the condition, is called the *composed signature*.
/// The composed signatures of the above examples all have a net stack change of `0`.
/// A positive composed signature net stack change is only allowed inside an array.
/// ex! ⍢(×2.|<1000) 1
/// This requirement is relaxed inside an array.
/// ex: [⍢(×2.|<1000)] 1
/// Alternatively, you can [join] the items to an initial list.
/// ex: ◌⍢(⊃(×2)⊂|<100) 1 []
/// A negative composed signature net stack change will reuse values lower on the stack.
/// ex: ⍢(×|<100) 1 2
/// ex: ⍢(⊂⤚(×⊢)|<100⊢) 1 2
///
/// Even if signatures are invalid, [do] will alway run its condition function at least once.
/// If the condition returns true, it will always run its body function at least once.
/// This is helpful when initially setting up a loop so that you can debug if necessary.
/// ex! ⍢(+|?) 5 3
/// ex! ⍢(?+|>2) 5 3
/// ex! ⍢(?..+|>2) 5 3
([2], Do, IteratingModifier, ("do", '⍢')),
/// Set the fill value for a function
///
Expand Down
29 changes: 29 additions & 0 deletions src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,14 @@ impl Uiua {
let height = self.require_height(n)?;
Ok(self.rt.stack[height..].to_vec())
}
/// Copy some values down the stack
///
/// `depth` must be greater than or equal to `n`
pub fn copy_n_down(&self, n: usize, depth: usize) -> UiuaResult<Vec<Value>> {
debug_assert!(depth >= n);
let height = self.require_height(depth)?;
Ok(self.rt.stack[height..][..n].to_vec())
}
/// Prepare to fork and return the arguments to f
pub fn prepare_fork(&mut self, f_args: usize, g_args: usize) -> UiuaResult<Vec<Value>> {
if f_args > g_args {
Expand Down Expand Up @@ -1095,6 +1103,7 @@ impl Uiua {
///
/// `depth` must be greater than or equal to `n`
pub fn dup_values(&mut self, n: usize, depth: usize) -> UiuaResult {
debug_assert!(depth >= n);
let start = self.require_height(depth)?;
for i in 0..n {
self.rt.stack.push(self.rt.stack[start + i].clone());
Expand All @@ -1104,6 +1113,26 @@ impl Uiua {
}
Ok(())
}
/// Insert some values into the stack
pub fn insert_stack(
&mut self,
depth: usize,
values: impl IntoIterator<Item = Value>,
) -> UiuaResult {
let start = self.require_height(depth)?;
self.rt.stack.extend(values);
self.rt.stack[start..].rotate_left(depth);
Ok(())
}
/// Remove some values from the stack
///
/// `depth` must be greater than or equal to `n`
pub fn remove_n(&mut self, n: usize, depth: usize) -> UiuaResult {
debug_assert!(depth >= n);
let start = self.require_height(depth)?;
self.rt.stack.drain(start..start + n);
Ok(())
}
/// Rotate the stack up at some depth
pub fn rotate_up(&mut self, n: usize, depth: usize) -> UiuaResult {
let start = self.require_height(depth)?;
Expand Down
4 changes: 3 additions & 1 deletion tests/loops.ua
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,14 @@ A ← ↯2_3_4⇡24
⍤⤙≍ +10 ⟜⍥+₁ ¤⇡0 ¤¤10
⍤⤙≍ +1⇡7 ⍥/◇⊂∞ {1 {2 3} {4 {5 6 {7}}}}
⍤⤙≍ {4 +1⇡7} {°⍥°/◇⊂} {1 {2 3} {4 {5 6 {7}}}}
⍤⤙≍ 52 ⍥+⊙(2 10) 5

# Do
⍤⤙≍ 1024 ⍢(×2|<1000) 1
⍤⤙≍ 1024 ⍢(×2|<1000.) 1
⍤⤙≍ [1 2 4 8 16 32 64] ◌⍢(⊃(×2|⊂:)|<100) 1 []
⍤⤙≍ [1 2 4 8 16 5] ◌⍢⊂(¬∊:,,⨬(+1×3|÷2)=0◿2.⊢.) [5]
⍤⤙≍ [1 2 4 8 16 5] ◌⍢⊂(¬∊◡:⨬(+1×3|÷2)=0◿2.⊢.) [5]
⍤⤙≍ 128 ⍢(×|<100) 1 2

# Partition noadic
F ← ⊜?≠@l.
Expand Down
2 changes: 2 additions & 0 deletions tests/signature.ua
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ F ← |1 ⍣⋕∘
F ← ⍣(°$"_-_"⊙◌|⍤.$"Invalid string on line _: \"_\"":)
# Repeat
F ← |1 {⍥(⊜⧻..)∞}
F ← |3 ⍥+
# Do
C ← ⨬(+1×3|÷2)=0◿2.
F ← |1 ◌⍢(|2 ⊂||1.3 ¬∊:,,C⊢.)
Expand All @@ -45,6 +46,7 @@ F ← |1 ⍢(|1 ×2||1.2 <1000.)
◌F 1
F ← |2 ⍢(+|?)
G ← |2 ⍢(?+|>2)
F ← |2 ⍢(×|<100)
# On
F ← |1 ⟜()
# By
Expand Down
2 changes: 0 additions & 2 deletions tests_special/error.ua
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ F"oops"
F ← [⋅◌ ⍥0] 1
F "oops"

F ← ⌊×10[◌◌⍥gen]⧻:0

⊕⊢ [0 2 2] [1 2 3]

+ ¤[1 2 3] [10_20_30_40 50_60_70_80]
Expand Down
3 changes: 0 additions & 3 deletions todo.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# Uiua Todo

# 0.15
- Mixed subscripts
- `bind` and `ref` modifiers
- Make `un by` more robust
- Multidimensional `group`
- `un`/`anti` `stencil`
- `on`/`by` subscripts
Expand Down

0 comments on commit a635f8f

Please sign in to comment.