diff --git a/changelog.md b/changelog.md index f54a76b7f..a8081b509 100644 --- a/changelog.md +++ b/changelog.md @@ -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) diff --git a/src/algorithm/loops.rs b/src/algorithm/loops.rs index c983a8d66..bb76e68cf 100644 --- a/src/algorithm/loops.rs +++ b/src/algorithm/loops.rs @@ -168,6 +168,8 @@ fn repeat_impl(f: SigNode, inv: Option, 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 @@ -180,6 +182,9 @@ fn repeat_impl(f: SigNode, inv: Option, 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; @@ -208,9 +213,13 @@ fn repeat_impl(f: SigNode, inv: Option, 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) } @@ -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 { @@ -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 { diff --git a/src/check.rs b/src/check.rs index 368154583..7f26c4dca 100644 --- a/src/check.rs +++ b/src/check.rs @@ -3,7 +3,6 @@ use std::{ array, cell::RefCell, - cmp::Ordering, collections::HashMap, fmt, hash::{DefaultHasher, Hash, Hasher}, @@ -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::([outputs]).is_err() { + return Err("repeat with excessive outputs".into()); } - }; - if validate_size_of::([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(()) } diff --git a/src/primitive/defs.rs b/src/primitive/defs.rs index cbcb5ceb9..6d65481fd 100644 --- a/src/primitive/defs.rs +++ b/src/primitive/defs.rs @@ -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. @@ -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 /// diff --git a/src/run.rs b/src/run.rs index a6427f877..519050b02 100644 --- a/src/run.rs +++ b/src/run.rs @@ -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> { + 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> { if f_args > g_args { @@ -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()); @@ -1104,6 +1113,26 @@ impl Uiua { } Ok(()) } + /// Insert some values into the stack + pub fn insert_stack( + &mut self, + depth: usize, + values: impl IntoIterator, + ) -> 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)?; diff --git a/tests/loops.ua b/tests/loops.ua index 52d43d8f0..bbb9f19b9 100644 --- a/tests/loops.ua +++ b/tests/loops.ua @@ -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. diff --git a/tests/signature.ua b/tests/signature.ua index e960fc214..889e88371 100644 --- a/tests/signature.ua +++ b/tests/signature.ua @@ -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⊒.) @@ -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 diff --git a/tests_special/error.ua b/tests_special/error.ua index ac2541d03..1dcba0fc1 100644 --- a/tests_special/error.ua +++ b/tests_special/error.ua @@ -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] diff --git a/todo.md b/todo.md index ded7f0890..03db542a6 100644 --- a/todo.md +++ b/todo.md @@ -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