diff options
author | amatgil <amatgilvinyes@gmail.com> | 2024-10-01 14:03:11 +0200 |
---|---|---|
committer | amatgil <amatgilvinyes@gmail.com> | 2024-10-01 14:03:11 +0200 |
commit | 924584f8a90266a449756d8564c4bd497ade8bf1 (patch) | |
tree | 70f6f8c33e53abee5526b029638523a665165395 | |
parent | f5ddf9ea15949c325a8581705aa471803250f37d (diff) | |
parent | 6c10c63d472add83cbc9295f2a198d0f8e2097f9 (diff) |
Merge branch 'main' of https://github.com/uiua-lang/uiua into parse_extension
-rw-r--r-- | changelog.md | 4 | ||||
-rw-r--r-- | site/blog_rss.ua | 3 | ||||
-rw-r--r-- | site/src/other_tutorial.rs | 4 | ||||
-rw-r--r-- | src/algorithm/dyadic/mod.rs | 11 | ||||
-rw-r--r-- | src/algorithm/invert.rs | 135 | ||||
-rw-r--r-- | src/algorithm/mod.rs | 63 | ||||
-rw-r--r-- | src/algorithm/monadic.rs | 82 | ||||
-rw-r--r-- | src/algorithm/pervade.rs | 701 | ||||
-rw-r--r-- | src/assembly.rs | 118 | ||||
-rw-r--r-- | src/check.rs | 25 | ||||
-rw-r--r-- | src/compile/mod.rs | 80 | ||||
-rw-r--r-- | src/compile/modifier.rs | 32 | ||||
-rw-r--r-- | src/format.rs | 40 | ||||
-rw-r--r-- | src/function.rs | 183 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | src/primitive/defs.rs | 4 | ||||
-rw-r--r-- | src/value.rs | 21 | ||||
-rw-r--r-- | tests/under.ua | 6 | ||||
-rw-r--r-- | tests/units.ua | 8 | ||||
-rw-r--r-- | todo.md | 8 |
20 files changed, 965 insertions, 569 deletions
diff --git a/changelog.md b/changelog.md index 98cd7280..ba60e470 100644 --- a/changelog.md +++ b/changelog.md @@ -75,7 +75,11 @@ This version is not yet released. If you are reading this on the website, then t - Remove previously deprecated function strands ### Interpreter - Add the `uiua find` command, which finds Uiua code that matches the given unformatted text +- Add checking for end-of-line signature comments + - These are documented in the [Documenting Code](https://uiua.org/tutorial/documentation) tutorial - Some optimizations + - Pervasive function machinery has been totally rewritten + - Observed performance improvements of up to 12x - [`rows ≡`](https://uiua.org/docs/rows) [`on ⟜`](https://uiua.org/docs/on)/[`by ⊸`](https://uiua.org/docs/by) [`random ⚂`](https://uiua.org/docs/random)/`constant` - [`memberof ∈`](https://uiua.org/docs/memberof)[`range ⇡`](https://uiua.org/docs/range) for scalar inputs to [`range ⇡`](https://uiua.org/docs/range) - Tweak the formatter to reduce excess vertical space diff --git a/site/blog_rss.ua b/site/blog_rss.ua index 05bbcd41..9d47bb06 100644 --- a/site/blog_rss.ua +++ b/site/blog_rss.ua @@ -21,7 +21,8 @@ Tag Rss Tag "channel" ⊂ { Tag "description" $ Blog of the Uiua Programming Language Tag "link" $ https://uiua.org/blog Tag AtomLink "" - Tag "lastBuildDate" Date now} ⍚( + Tag "lastBuildDate" Date now +} ⍚( ⊙⊙(Date °datetime ⋕⊜□⊸≠@-) # Convert date Tag "item" { ⊓(Tag "link" $"https://uiua.org/blog/_" diff --git a/site/src/other_tutorial.rs b/site/src/other_tutorial.rs index 9491f33f..6ecf13b1 100644 --- a/site/src/other_tutorial.rs +++ b/site/src/other_tutorial.rs @@ -211,6 +211,10 @@ pub fn Documentation() -> impl IntoView { <p>"The "<code>"?"</code>" is similar to the "<Prim prim=Primitive::Stack/>" function because the arguments indicate the intended state of the stack before the function is called."</p> <p>"If you also want to give names to a function's outputs, you can list them in front of the "<code>"?"</code>". This lets you read the comment signature right-to-left, the same way as normal Uiua code."</p> <Editor example="# Quotient Remainder ? Divisor Dividend\nDivRem ← ⌊⊃÷◿\nDivRem 3 7"/> + <p>"These kinds of comments can also be put at the end of lines. The signature of the line will be checked against the signature specified in the comment."</p> + <Editor example="1 2 # A B ?\n+⌵ # Sum ? A B\n⇡+5 # Res ? Foo Bar"/> // Should fail + <p>"These can be put in functions as well."</p> + <Editor example="# Get the average of a list\n# ? List\nAvg ← (\n ⟜/+ # List Sum ? List\n ⧻ # Length ? List\n ÷ # Avg ? Length List\n)"/> <Hd id="track-caller"><code>"# Track caller!"</code></Hd> diff --git a/src/algorithm/dyadic/mod.rs b/src/algorithm/dyadic/mod.rs index c8345b58..03115cc2 100644 --- a/src/algorithm/dyadic/mod.rs +++ b/src/algorithm/dyadic/mod.rs @@ -26,8 +26,7 @@ use crate::{ }; use super::{ - pervade::ArrayRef, shape_prefixes_match, validate_size, validate_size_of, ArrayCmpSlice, - FillContext, SizeError, + shape_prefixes_match, validate_size, validate_size_of, ArrayCmpSlice, FillContext, SizeError, }; impl Value { @@ -1425,11 +1424,13 @@ impl Array<f64> { let mut i = 0; for b_row in b.row_slices() { _ = bin_pervade_recursive( - ArrayRef::new(&a_row_shape, a_row), - ArrayRef::new(&b_row_shape, b_row), + (a_row, &a_row_shape), + (b_row, &b_row_shape), &mut prod_row, - env, + None, + None, InfalliblePervasiveFn::new(pervade::mul::num_num), + env, ); let (sum, rest) = prod_row.split_at_mut(prod_elems); for chunk in rest.chunks_exact(prod_elems) { diff --git a/src/algorithm/invert.rs b/src/algorithm/invert.rs index a75257cb..e90b4119 100644 --- a/src/algorithm/invert.rs +++ b/src/algorithm/invert.rs @@ -12,12 +12,12 @@ use ecow::{eco_vec, EcoString, EcoVec}; use regex::Regex; use crate::{ - check::{instrs_clean_signature, instrs_signature, SigCheckError}, + check::{instrs_clean_signature, instrs_signature, naive_under_sig, SigCheckError}, instrs_are_pure, primitive::{ImplPrimitive, Primitive}, Array, Assembly, BindingKind, Compiler, Complex, FmtInstrs, Function, FunctionFlags, FunctionId, Instr, NewFunction, Purity, Signature, Span, SysOp, TempStack, Uiua, UiuaResult, - Value, + UnderedFunctions, Value, }; use super::IgnoreError; @@ -31,6 +31,9 @@ pub enum InversionError { TooManyInstructions, Signature(SigCheckError), InnerFunc(Vec<FunctionId>, Box<Self>), + AsymmetricUnderSig(Signature), + ComplexInvertedUnder, + UnderExperimental, } type InversionResult<T = ()> = Result<T, InversionError>; @@ -51,6 +54,24 @@ impl fmt::Display for InversionError { let inner = inner.to_string().to_lowercase(); write!(f, " {inner}") } + InversionError::AsymmetricUnderSig(sig) => { + write!( + f, + "Cannot invert under with asymmetric \ + second function signature {sig}" + ) + } + InversionError::ComplexInvertedUnder => { + write!(f, "This under itself is too complex to invert") + } + InversionError::UnderExperimental => { + write!( + f, + "Inversion of {} is experimental. To enable it, \ + add `# Experimental!` to the top of the file.", + Primitive::Under.format() + ) + } } } } @@ -267,6 +288,7 @@ static INVERT_PATTERNS: &[&dyn InvertPattern] = { use ImplPrimitive::*; use Primitive::*; &[ + &InvertPatternFn(invert_under_pattern, "under"), &InvertPatternFn(invert_flip_pattern, "flip"), &InvertPatternFn(invert_join_val_pattern, "join_val"), &InvertPatternFn(invert_join_pattern, "join"), @@ -589,6 +611,7 @@ pub(crate) fn under_instrs( let patterns: &[&dyn UnderPattern] = &[ // Hand-written patterns + &UnderPatternFn(under_under_pattern, "under"), &maybe_val!(UnderPatternFn(under_fill_pattern, "fill")), &UnderPatternFn(under_call_pattern, "call"), &UnderPatternFn(under_dump_pattern, "dump"), @@ -846,7 +869,7 @@ pub(crate) fn under_instrs( comp.asm.instrs = comp_instrs_backup; // dbgln!("under {:?} failed with remaining {:?}", instrs, curr_instrs); - generic() + Err(error) } fn resolve_uns(instrs: EcoVec<Instr>, comp: &mut Compiler) -> InversionResult<EcoVec<Instr>> { @@ -961,6 +984,112 @@ invert_pat!( |input, comp| (input, EcoVec::from(f.instrs(&comp.asm))) ); +fn get_undered<'a>( + input: &'a [Instr], + comp: &mut Compiler, +) -> InversionResult<(UnderedFunctions, &'a [Instr])> { + let res = if let [Instr::PushFunc(g), Instr::PushFunc(f), Instr::Prim(Primitive::Under, span), input @ ..] = + input + { + Ok(( + UnderedFunctions { + f: EcoVec::from(f.instrs(&comp.asm)), + f_sig: f.signature(), + g: EcoVec::from(g.instrs(&comp.asm)), + g_sig: g.signature(), + span: *span, + }, + input, + )) + } else if input.len() < 3 { + generic() + } else { + (0..=input.len()) + .rev() + .find_map(|i| { + comp.undered_funcs + .get(&input[..i]) + .map(|u| (u.clone(), &input[i..])) + }) + .ok_or(Generic) + }; + if res.is_ok() && !comp.scope.experimental { + return Err(InversionError::UnderExperimental); + } + res +} + +fn invert_under_pattern<'a>( + input: &'a [Instr], + comp: &mut Compiler, +) -> InversionResult<(&'a [Instr], EcoVec<Instr>)> { + let (u, input) = get_undered(input, comp)?; + let UnderedFunctions { + f, f_sig, g, g_sig, .. + } = u; + if g_sig.args != g_sig.outputs { + return Err(InversionError::AsymmetricUnderSig(g_sig)); + } + let (f_before, f_after) = under_instrs(&f, g_sig, comp)?; + let g_inv = invert_instrs(&g, comp)?; + let mut inverse = f_before; + inverse.extend(g_inv); + inverse.extend(f_after); + if instrs_signature(&inverse)? != naive_under_sig(f_sig, g_sig) { + return Err(InversionError::ComplexInvertedUnder); + } + Ok((input, inverse)) +} + +fn under_under_pattern<'a>( + input: &'a [Instr], + outer_g_sig: Signature, + comp: &mut Compiler, +) -> InversionResult<(&'a [Instr], Under)> { + let (u, input) = get_undered(input, comp)?; + let UnderedFunctions { + f, + f_sig, + g, + g_sig, + span, + } = u; + if g_sig.args != g_sig.outputs { + return Err(InversionError::AsymmetricUnderSig(g_sig)); + } + let (f_before, f_after) = under_instrs(&f, g_sig, comp)?; + let (g_before, g_after) = under_instrs(&g, outer_g_sig, comp)?; + let context_size = f_sig.args.saturating_sub(f_sig.outputs); + + let mut before = EcoVec::new(); + if context_size > 0 { + before.push(Instr::CopyToTemp { + stack: TempStack::Under, + count: context_size, + span, + }); + } + before.extend(f_before.iter().cloned()); + before.extend(g_before); + before.extend(f_after.iter().cloned()); + if instrs_signature(&before)? != naive_under_sig(f_sig, g_sig) { + return Err(InversionError::ComplexInvertedUnder); + } + + let mut after = EcoVec::new(); + if context_size > 0 { + after.push(Instr::PopTemp { + stack: TempStack::Under, + count: context_size, + span, + }); + } + after.extend(f_before); + after.extend(g_after); + after.extend(f_after); + Ok((input, (before, after))) +} + fn under_un_pattern<'a>( input: &'a [Instr], g_sig: Signature, diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index 96d332df..b887e13c 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -389,69 +389,6 @@ where } } -pub(crate) fn fill_array_shapes<A, B, C>( - a: &mut Array<A>, - b: &mut Array<B>, - a_depth: usize, - b_depth: usize, - ctx: &C, -) -> Result<(), C::Error> -where - A: ArrayValue, - B: ArrayValue, - C: FillContext, -{ - let a_depth = a_depth.min(a.rank()); - let b_depth = b_depth.min(b.rank()); - match (a_depth, b_depth) { - (0, 0) => { - if shape_prefixes_match(&a.shape, &b.shape) { - return Ok(()); - } - let a_shape = a.shape().clone(); - let b_shape = b.shape().clone(); - let a_err = fill_array_shape(a, &b_shape, true, ctx).err(); - let b_err = fill_array_shape(b, &a_shape, true, ctx).err(); - if shape_prefixes_match(&a.shape, &b.shape) { - Ok(()) - } else { - Err(C::fill_error(ctx.error(match (a_err, b_err) { - (Some(FillShapeError::Size(e)), _) | (_, Some(FillShapeError::Size(e))) => { - e.to_string() - } - (Some(e), _) | (_, Some(e)) => { - format!("Shapes {} and {} do not match{e}", a.shape(), b.shape()) - } - (None, None) => { - format!("Shapes {} and {} do not match", a.shape(), b.shape()) - } - }))) - } - } - (_, _) => { - if a.row_count() != b.row_count() { - return Err(C::fill_error(ctx.error(format!( - "Shapes {} and {} do not match", - a.shape(), - b.shape(), - )))); - } - if !shape_prefixes_match(&a.shape[a_depth..], &b.shape[b_depth..]) { - let mut new_a_rows = Vec::with_capacity(a.row_count()); - let mut new_b_rows = Vec::with_capacity(b.row_count()); - for (mut a_row, mut b_row) in a.rows().zip(b.rows()) { - fill_array_shapes(&mut a_row, &mut b_row, a_depth - 1, b_depth - 1, ctx)?; - new_a_rows.push(a_row); - new_b_rows.push(b_row); - } - *a = Array::from_row_arrays_infallible(new_a_rows); - *b = Array::from_row_arrays_infallible(new_b_rows); - } - Ok(()) - } - } -} - pub fn switch( count: usize, sig: Signature, diff --git a/src/algorithm/monadic.rs b/src/algorithm/monadic.rs index a695a977..c20c7312 100644 --- a/src/algorithm/monadic.rs +++ b/src/algorithm/monadic.rs @@ -5,6 +5,8 @@ use std::{ collections::{HashMap, HashSet}, convert::identity, f64::consts::{PI, TAU}, + fmt, + io::Write, iter, mem::size_of, ops::Neg, @@ -186,27 +188,77 @@ impl Value { value => Ok(value.format().into()), }; } + + fn padded<T: fmt::Display>(c: char, arr: &Array<T>, env: &Uiua) -> UiuaResult<Array<char>> { + let mut buf = Vec::new(); + let mut max_whole = 0; + let mut max_dec = 0; + for v in &arr.data { + buf.clear(); + write!(&mut buf, "{v}").unwrap(); + if let Some(i) = buf.iter().position(|&c| c == b'.') { + max_whole = max_whole.max(i); + max_dec = max_dec.max(buf.len() - i - 1); + } else { + max_whole = max_whole.max(buf.len()); + } + } + let max_len = if max_dec == 0 { + max_whole + } else { + max_whole + max_dec + 1 + }; + let mut new_shape = arr.shape().clone(); + new_shape.push(max_len); + let elem_count = validate_size::<char>(new_shape.iter().copied(), env)?; + let mut new_data = eco_vec![c; elem_count]; + if max_len > 0 { + for (i, s) in new_data.make_mut().chunks_exact_mut(max_len).enumerate() { + let n = arr.data[i].to_string(); + let dot_pos = n.find('.'); + let skip = dot_pos + .map(|i| max_dec - (n.len() - i - 1)) + .unwrap_or(max_dec + 1); + for (s, c) in s.iter_mut().rev().skip(skip).zip(n.chars().rev()) { + *s = c; + } + if dot_pos.is_none() && max_dec > 0 && c.is_ascii_digit() { + s[max_whole] = '.'; + } + } + } + Ok(Array::new(new_shape, new_data)) + } + Ok(match self { - Value::Num(nums) => { - let new_data: CowSlice<Boxed> = (nums.data.iter().map(|v| v.to_string())) - .map(Value::from) - .map(Boxed) - .collect(); - Array::new(nums.shape.clone(), new_data).into() + Value::Num(arr) => { + if let Ok(c) = env.char_scalar_fill() { + padded(c, arr, env)?.into() + } else { + let new_data: CowSlice<Boxed> = (arr.data.iter().map(|v| v.to_string())) + .map(Value::from) + .map(Boxed) + .collect(); + Array::new(arr.shape.clone(), new_data).into() + } } - Value::Byte(bytes) => { - let new_data: CowSlice<Boxed> = (bytes.data.iter().map(|v| v.to_string())) - .map(Value::from) - .map(Boxed) - .collect(); - Array::new(bytes.shape.clone(), new_data).into() + Value::Byte(arr) => { + if let Ok(c) = env.char_scalar_fill() { + padded(c, arr, env)?.into() + } else { + let new_data: CowSlice<Boxed> = (arr.data.iter().map(|v| v.to_string())) + .map(Value::from) + .map(Boxed) + .collect(); + Array::new(arr.shape.clone(), new_data).into() + } } - Value::Complex(complexes) => { - let new_data: CowSlice<Boxed> = (complexes.data.iter().map(|v| v.to_string())) + Value::Complex(arr) => { + let new_data: CowSlice<Boxed> = (arr.data.iter().map(|v| v.to_string())) .map(Value::from) .map(Boxed) .collect(); - Array::new(complexes.shape.clone(), new_data).into() + Array::new(arr.shape.clone(), new_data).into() } val => return Err(env.error(format!("Cannot unparse {} array", val.type_name()))), }) diff --git a/src/algorithm/pervade.rs b/src/algorithm/pervade.rs index fc764e6a..6e785030 100644 --- a/src/algorithm/pervade.rs +++ b/src/algorithm/pervade.rs @@ -4,47 +4,17 @@ use std::{ cmp::{self, Ordering}, convert::Infallible, fmt::Display, + iter::repeat, marker::PhantomData, - slice::{self, ChunksExact}, + slice, }; use ecow::eco_vec; -use crate::{array::*, Uiua, UiuaError, UiuaResult}; +use crate::{algorithm::loops::flip, array::*, Uiua, UiuaError, UiuaResult}; use crate::{Complex, Shape}; -use super::{fill_array_shapes, FillContext}; - -pub(crate) struct ArrayRef<'a, T> { - shape: &'a [usize], - data: &'a [T], -} - -impl<'a, T> Clone for ArrayRef<'a, T> { - fn clone(&self) -> Self { - *self - } -} - -impl<'a, T> Copy for ArrayRef<'a, T> {} - -impl<'a, T> ArrayRef<'a, T> { - pub fn new(shape: &'a [usize], data: &'a [T]) -> Self { - Self { shape, data } - } - fn row_len(&self) -> usize { - self.shape.iter().skip(1).product() - } - fn rows(&self) -> ChunksExact<T> { - self.data.chunks_exact(self.row_len().max(1)) - } -} - -impl<'a, T> From<&'a Array<T>> for ArrayRef<'a, T> { - fn from(a: &'a Array<T>) -> Self { - Self::new(a.shape(), a.data.as_slice()) - } -} +use super::FillContext; pub trait PervasiveFn<A, B> { type Output; @@ -92,9 +62,21 @@ where } } +fn pervade_dim(a: usize, b: usize) -> usize { + if a == b { + a + } else if a == 1 { + b + } else if b == 1 { + a + } else { + a.max(b) + } +} + pub fn bin_pervade<A, B, C, F>( - mut a: Array<A>, - mut b: Array<B>, + a: Array<A>, + b: Array<B>, a_depth: usize, b_depth: usize, env: &Uiua, @@ -107,138 +89,176 @@ where F: PervasiveFn<A, B, Output = C> + Clone, F::Error: Into<UiuaError>, { - // Fast fixed cases - if a_depth == 0 && b_depth == 0 && env.scalar_fill::<C>().is_err() { - // A is fixed - if a.row_count() == 1 && b.row_count() != 1 { - let fix_count = a.shape.iter().take_while(|&&d| d == 1).count(); - if b.rank() > fix_count && b.rank() >= a.rank() { - if (a.shape().iter()) - .zip(b.shape()) - .skip(fix_count) - .any(|(a, b)| a != b) - { - return Err(env.error(format!( - "Shapes {} and {} do not match", - a.shape(), - b.shape() - ))); - } - let mut data = eco_vec![C::default(); b.element_count()]; - let a_row_shape = &a.shape()[fix_count..]; - let b_row_shape = &b.shape()[fix_count..]; - let b_row_len = b_row_shape.iter().product(); - if b_row_len > 0 { - for (b, c) in (b.data.chunks_exact(b_row_len)) - .zip(data.make_mut().chunks_exact_mut(b_row_len)) - { - bin_pervade_recursive( - ArrayRef::new(a_row_shape, &a.data), - ArrayRef::new(b_row_shape, b), - c, - env, - f.clone(), - ) - .map_err(Into::into)?; + let _a_depth = a_depth.min(a.rank()); + let _b_depth = b_depth.min(b.rank()); + + let new_rank = a.rank().max(b.rank()); + let mut new_shape = Shape::with_capacity(new_rank); + let a_fill = env.scalar_fill::<A>(); + let b_fill = env.scalar_fill::<B>(); + for i in 0..new_rank { + let c = match (a.shape.get(i).copied(), b.shape.get(i).copied()) { + (None, None) => unreachable!(), + (Some(a), None) => a, + (None, Some(b)) => b, + (Some(ad), Some(bd)) => { + if ad == bd || ad == 1 || bd == 1 { + pervade_dim(ad, bd) + } else if ad < bd { + match &a_fill { + Ok(_) => pervade_dim(ad, bd), + Err(e) => { + return Err(env.error(format!( + "Shapes {} and {} are not compatible{e}", + a.shape, b.shape + ))) + } } - } - return Ok(Array::new(b.shape, data)); - } - } - // B is fixed - if a.row_count() != 1 && b.row_count() == 1 { - let fix_count = b.shape.iter().take_while(|&&d| d == 1).count(); - if a.rank() > fix_count && a.rank() >= b.rank() { - if (a.shape().iter()) - .zip(b.shape()) - .skip(fix_count) - .any(|(a, b)| a != b) - { - return Err(env.error(format!( - "Shapes {} and {} do not match", - a.shape(), - b.shape() - ))); - } - let mut data = eco_vec![C::default(); a.element_count()]; - let a_row_shape = &a.shape()[fix_count..]; - let b_row_shape = &b.shape()[fix_count..]; - let a_row_len = a_row_shape.iter().product(); - if a_row_len > 0 { - for (a, c) in (a.data.chunks_exact(a_row_len)) - .zip(data.make_mut().chunks_exact_mut(a_row_len)) - { - bin_pervade_recursive( - ArrayRef::new(a_row_shape, a), - ArrayRef::new(b_row_shape, &b.data), - c, - env, - f.clone(), - ) - .map_err(Into::into)?; + } else { + match &b_fill { + Ok(_) => pervade_dim(ad, bd), + Err(e) => { + return Err(env.error(format!( + "Shapes {} and {} are not compatible{e}", + a.shape, b.shape + ))) + } } } - return Ok(Array::new(a.shape, data)); } - } + }; + new_shape.push(c); } - // Fill - fill_array_shapes(&mut a, &mut b, a_depth, b_depth, env)?; - // Pervade - let shape = a.shape().max(b.shape()).clone(); - let mut data = eco_vec![C::default(); shape.elements()]; - bin_pervade_recursive((&a).into(), (&b).into(), data.make_mut(), env, f).map_err(Into::into)?; - Ok(Array::new(shape, data)) + + // dbg!(&a.shape, &b.shape, &new_shape); + + let mut new_data = eco_vec![C::default(); new_shape.elements()]; + if !new_shape.contains(&0) { + let slice = new_data.make_mut(); + let a_fill = a_fill.as_ref().ok(); + let b_fill = b_fill.as_ref().ok(); + bin_pervade_recursive( + (&a.data, &a.shape), + (&b.data, &b.shape), + slice, + a_fill, + b_fill, + f, + env, + ) + .map_err(Into::into)?; + } + Ok(Array::new(new_shape, new_data)) } -pub fn bin_pervade_recursive<A, B, C, F>( - a: ArrayRef<A>, - b: ArrayRef<B>, +#[allow(clippy::too_many_arguments)] +pub(crate) fn bin_pervade_recursive<A, B, C, F>( + (a, ash): (&[A], &[usize]), + (b, bsh): (&[B], &[usize]), c: &mut [C], - env: &Uiua, + a_fill: Option<&A>, + b_fill: Option<&B>, f: F, + env: &Uiua, ) -> Result<(), F::Error> where - A: ArrayValue, - B: ArrayValue, - C: ArrayValue, + A: ArrayValue + Clone, + B: ArrayValue + Clone, + C: ArrayValue + Clone, F: PervasiveFn<A, B, Output = C> + Clone, { - match (a.shape, b.shape) { - ([], []) => c[0] = f.call(a.data[0].clone(), b.data[0].clone(), env)?, - (ash, bsh) if ash.contains(&0) || bsh.contains(&0) => {} - (ash, bsh) if ash == bsh => { - for ((a, b), c) in a.data.iter().zip(b.data).zip(c) { - *c = f.call(a.clone(), b.clone(), env)?; - } + let recur = |a: &[A], ash: &[usize], b: &[B], bsh: &[usize], c: &mut [C]| { + bin_pervade_recursive((a, ash), (b, bsh), c, a_fill, b_fill, f.clone(), env) + }; + match (ash, bsh) { + ([], []) => { + let (a, b) = (a[0].clone(), b[0].clone()); + c[0] = f.call(a, b, env)?; } - ([], bsh) => { - for (brow, crow) in b.rows().zip(c.chunks_exact_mut(b.row_len())) { - bin_pervade_recursive(a, ArrayRef::new(&bsh[1..], brow), crow, env, f.clone())?; + // Scalar A + ([], [_, ..]) => { + let a = &a[0]; + for (b, c) in b.iter().zip(c) { + *c = f.call(a.clone(), b.clone(), env)?; } } - (ash, []) => { - for (arow, crow) in a.rows().zip(c.chunks_exact_mut(a.row_len())) { - bin_pervade_recursive(ArrayRef::new(&ash[1..], arow), b, crow, env, f.clone())?; + // Scalar B + ([_, ..], []) => { + let b = &b[0]; + for (a, c) in a.iter().zip(c) { + *c = f.call(a.clone(), b.clone(), env)?; } } - (ash, bsh) => { - for ((arow, brow), crow) in - (a.rows().zip(b.rows())).zip(c.chunks_exact_mut(a.row_len().max(b.row_len()))) - { - bin_pervade_recursive( - ArrayRef::new(&ash[1..], arow), - ArrayRef::new(&bsh[1..], brow), - crow, - env, - f.clone(), - )?; + ([al, ash @ ..], [bl, bsh @ ..]) => { + let a_row_len = a.len() / al; + let b_row_len = b.len() / bl; + let c_row_len = c.len() / al.max(bl); + if al == bl { + if ash == bsh { + for ((a, b), c) in a.iter().zip(b).zip(c) { + *c = f.call(a.clone(), b.clone(), env)?; + } + } else { + for ((a, b), c) in a + .chunks_exact(a_row_len) + .zip(b.chunks_exact(b_row_len)) + .zip(c.chunks_exact_mut(c_row_len)) + { + recur(a, ash, b, bsh, c)?; + } + } + } else if al < bl { + if let Some(a_fill) = a_fill { + let a_fill_row = vec![a_fill.clone(); a_row_len]; + let a_iter = a + .chunks_exact(a_row_len) + .chain(repeat(a_fill_row.as_slice())); + for ((a, b), c) in a_iter + .zip(b.chunks_exact(b_row_len)) + .zip(c.chunks_exact_mut(c_row_len)) + { + recur(a, ash, b, bsh, c)?; + } + } else if ash == bsh { + for (b, c) in b.chunks_exact(b_row_len).zip(c.chunks_exact_mut(c_row_len)) { + for ((a, b), c) in a.iter().zip(b).zip(c) { + *c = f.call(a.clone(), b.clone(), env)?; + } + } + } else { + for (b, c) in b.chunks_exact(b_row_len).zip(c.chunks_exact_mut(c_row_len)) { + recur(a, ash, b, bsh, c)?; + } + } + } else if let Some(b_fill) = b_fill { + let b_fill_row = vec![b_fill.clone(); b_row_len]; + let b_iter = b + .chunks_exact(b_row_len) + .chain(repeat(b_fill_row.as_slice())); + for ((a, b), c) in a + .chunks_exact(a_row_len) + .zip(b_iter) + .zip(c.chunks_exact_mut(c_row_len)) + { + recur(a, ash, b, bsh, c)?; + } + } else if ash == bsh { + for (a, c) in a.chunks_exact(a_row_len).zip(c.chunks_exact_mut(c_row_len)) { + for ((a, b), c) in a.iter().zip(b).zip(c) { + *c = f.call(a.clone(), b.clone(), env)?; + } + } + } else { + for (a, c) in a.chunks_exact(a_row_len).zip(c.chunks_exact_mut(c_row_len)) { + recur(a, ash, b, bsh, c)?; + } } } } Ok(()) } +/// Pervade an operation where both input types and the output type are all the same pub fn bin_pervade_mut<T>( mut a: Array<T>, b: &mut Array<T>, @@ -250,188 +270,257 @@ pub fn bin_pervade_mut<T>( where T: ArrayValue + Copy, { - // Fast case if A is fixed - if a.row_count() == 1 && b.row_count() != 1 && env.scalar_fill::<T>().is_err() { - let fix_count = a.shape.iter().take_while(|&&d| d == 1).count(); - if b.rank() > fix_count && b.rank() >= a.rank() { - if (a.shape().iter()) - .zip(b.shape()) - .skip(fix_count) - .any(|(a, b)| a != b) - { - return Err(env.error(format!( - "Shapes {} and {} do not match", - a.shape(), - b.shape() - ))); - } - let a_row_shape = &a.shape()[fix_count..]; - let b_row_shape = b.shape()[fix_count..].to_vec(); - let b_row_len = b_row_shape.iter().product(); - if b_row_len > 0 { - for b in b.data.as_mut_slice().chunks_exact_mut(b_row_len) { - bin_pervade_recursive_mut_right( - a.data.as_slice(), - a_row_shape, - b, - &b_row_shape, - f, - ); + let _a_depth = a_depth.min(a.rank()); + let _b_depth = b_depth.min(b.rank()); + + let new_rank = a.rank().max(b.rank()); + let mut new_shape = Shape::with_capacity(new_rank); + let fill = env.scalar_fill::<T>(); + let mut requires_fill = false; + for i in 0..new_rank { + let c = match (a.shape.get(i).copied(), b.shape.get(i).copied()) { + (None, None) => unreachable!(), + (Some(a), None) => a, + (None, Some(b)) => b, + (Some(ad), Some(bd)) => { + if ad == bd || ad == 1 || bd == 1 { + requires_fill |= ad != bd && fill.is_ok(); + pervade_dim(ad, bd) + } else { + match &fill { + Ok(_) => { + requires_fill = true; + pervade_dim(ad, bd) + } + Err(e) => { + return Err(env.error(format!( + "Shapes {} and {} are not compatible{e}", + a.shape, b.shape + ))) + } + } } } - return Ok(()); - } + }; + new_shape.push(c); } - // Fill - fill_array_shapes(&mut a, b, a_depth, b_depth, env)?; - // Pervade - let ash = a.shape.dims(); - let bsh = b.shape.dims(); - // Try to avoid copying when possible - if ash == bsh { - if a.data.is_copy_of(&b.data) { - drop(a); - let b_data = b.data.as_mut_slice(); - for b in b_data { - *b = f(*b, *b); - } - } else if a.data.is_unique() { - let a_data = a.data.as_mut_slice(); - let b_data = b.data.as_slice(); - for (a, b) in a_data.iter_mut().zip(b_data) { - *a = f(*a, *b); - } - *b = a; - } else { - let a_data = a.data.as_slice(); - let b_data = b.data.as_mut_slice(); - for (a, b) in a_data.iter().zip(b_data) { - *b = f(*a, *b); - } - } - } else if ash.contains(&0) || bsh.contains(&0) { - if ash.len() > bsh.len() { - *b = a; - } - } else { - match ash.len().cmp(&bsh.len()) { - Ordering::Greater => { - let a_data = a.data.as_mut_slice(); - let b_data = b.data.as_slice(); - bin_pervade_recursive_mut_left(a_data, ash, b_data, bsh, f); - *b = a; + + let fill = if requires_fill { fill.ok() } else { None }; + + // dbg!(&a.shape, &b.shape, &new_shape); + + fn reuse_no_fill<T: ArrayValue + Copy>( + a: &[T], + b: &mut [T], + ash: &[usize], + bsh: &[usize], + f: impl Fn(T, T) -> T + Copy, + ) { + match (ash, bsh) { + ([], _) => { + for b in b { + *b = f(a[0], *b); + } } - Ordering::Less => { - let a_data = a.data.as_slice(); - let b_data = b.data.as_mut_slice(); - bin_pervade_recursive_mut_right(a_data, ash, b_data, bsh, f); + ([1, ash @ ..], [bl, bsh @ ..]) => { + let b_row_len = b.len() / bl; + if ash == bsh { + for b in b.chunks_exact_mut(b_row_len) { + for (a, b) in a.iter().zip(b) { + *b = f(*a, *b); + } + } + } else { + for b in b.chunks_exact_mut(b_row_len) { + reuse_no_fill(a, b, ash, bsh, f); + } + } } - Ordering::Equal => { - let a_data = a.data.as_mut_slice(); - let b_data = b.data.as_mut_slice(); - bin_pervade_recursive_mut(a_data, ash, b_data, bsh, f); + ([al, ash @ ..], [bl, bsh @ ..]) => { + debug_assert_eq!(al, bl); + if ash == bsh { + for (a, b) in a.iter().zip(b) { + *b = f(*a, *b); + } + } else { + let a_row_len = a.len() / al; + let b_row_len = b.len() / bl; + for (a, b) in a.chunks_exact(a_row_len).zip(b.chunks_exact_mut(b_row_len)) { + reuse_no_fill(a, b, ash, bsh, f); + } + } } + _ => unreachable!(), } } - Ok(()) -} - -fn bin_pervade_recursive_mut<T>( - a_data: &mut [T], - a_shape: &[usize], - b_data: &mut [T], - b_shape: &[usize], - f: impl Fn(T, T) -> T + Copy, -) where - T: Copy, -{ - match (a_shape, b_shape) { - ([], []) => { - panic!("should never call `bin_pervade_recursive_mut` with scalars") - } - (_, []) => { - let b_scalar = b_data[0]; - for a in a_data { - *a = f(*a, b_scalar); - } - } - ([], _) => { - let a_scalar = a_data[0]; - for b in b_data { - *b = f(a_scalar, *b); + fn reuse_fill<T: ArrayValue + Copy>( + a: &[T], + b: &mut [T], + ash: &[usize], + bsh: &[usize], + fill: T, + f: impl Fn(T, T) -> T + Copy, + ) { + match (ash, bsh) { + ([], _) => { + for b in b { + *b = f(a[0], *b); + } } - } - (ash, bsh) => { - let a_row_len = a_data.len() / ash[0]; - let b_row_len = b_data.len() / bsh[0]; - for (a, b) in a_data - .chunks_exact_mut(a_row_len) - .zip(b_data.chunks_exact_mut(b_row_len)) - { - bin_pervade_recursive_mut(a, &ash[1..], b, &bsh[1..], f); + ([al, ash @ ..], [bl, bsh @ ..]) => { + let a_row_len = a.len() / al; + let b_row_len = b.len() / bl; + if al == bl { + for (a, b) in a.chunks_exact(a_row_len).zip(b.chunks_exact_mut(b_row_len)) { + reuse_fill(a, b, ash, bsh, fill, f); + } + } else { + let fill_row = vec![fill; a_row_len]; + for (a, b) in a + .chunks_exact(a_row_len) + .chain(repeat(fill_row.as_slice())) + .zip(b.chunks_exact_mut(b_row_len)) + { + reuse_fill(a, b, ash, bsh, fill, f); + } + } } + _ => unreachable!(), } } -} -fn bin_pervade_recursive_mut_left<T>( - a_data: &mut [T], - a_shape: &[usize], - b_data: &[T], - b_shape: &[usize], - f: impl Fn(T, T) -> T + Copy, -) where - T: Copy, -{ - match (a_shape, b_shape) { - ([], _) => a_data[0] = f(a_data[0], b_data[0]), - (_, []) => { - let b_scalar = b_data[0]; - for a in a_data { - *a = f(*a, b_scalar); + if new_shape.contains(&0) { + b.shape = new_shape; + b.data = Default::default(); + } else if new_shape == b.shape { + // The existing array can be used + if a.shape == b.shape { + // Try to avoid copying when possible + if a.data.is_copy_of(&b.data) { + drop(a); + let b_data = b.data.as_mut_slice(); + for b in b_data { + *b = f(*b, *b); + } + } else if a.data.is_unique() { + let a_data = a.data.as_mut_slice(); + let b_data = b.data.as_slice(); + for (a, b) in a_data.iter_mut().zip(b_data) { + *a = f(*a, *b); + } + b.data = a.data; + } else { + let a_data = a.data.as_slice(); + let b_data = b.data.as_mut_slice(); + for (a, b) in a_data.iter().zip(b_data) { + *b = f(*a, *b); + } } + } else if let Some(fill) = fill { + reuse_fill(&a.data, b.data.as_mut_slice(), &a.shape, &b.shape, fill, f); + } else { + reuse_no_fill(&a.data, b.data.as_mut_slice(), &a.shape, &b.shape, f); } - (ash, bsh) => { - let a_row_len = a_data.len() / ash[0]; - let b_row_len = b_data.len() / bsh[0]; - for (a, b) in a_data - .chunks_exact_mut(a_row_len) - .zip(b_data.chunks_exact(b_row_len)) - { - bin_pervade_recursive_mut_left(a, &ash[1..], b, &bsh[1..], f); - } + } else if new_shape == a.shape { + // An existing array can be used, but things need to be flipped + if let Some(fill) = fill { + reuse_fill( + &b.data, + a.data.as_mut_slice(), + &b.shape, + &a.shape, + fill, + flip(f), + ); + } else { + reuse_no_fill(&b.data, a.data.as_mut_slice(), &b.shape, &a.shape, flip(f)); } - } -} + *b = a; + } else { + // Allocate a new array + let mut new_data = eco_vec![T::default(); new_shape.elements()]; + let slice = new_data.make_mut(); + let fill = fill.expect("There should be a fill if the new shape is not b's shape"); -fn bin_pervade_recursive_mut_right<T>( - a_data: &[T], - a_shape: &[usize], - b_data: &mut [T], - b_shape: &[usize], - f: impl Fn(T, T) -> T + Copy, -) where - T: Copy, -{ - match (a_shape, b_shape) { - (_, []) => b_data[0] = f(a_data[0], b_data[0]), - ([], _) => { - let a_scalar = a_data[0]; - for b in b_data { - *b = f(a_scalar, *b); - } - } - (ash, bsh) => { - let a_row_len = a_data.len() / ash[0]; - let b_row_len = b_data.len() / bsh[0]; - for (a, b) in a_data - .chunks_exact(a_row_len) - .zip(b_data.chunks_exact_mut(b_row_len)) - { - bin_pervade_recursive_mut_right(a, &ash[1..], b, &bsh[1..], f); + #[allow(clippy::too_many_arguments)] + fn inner<T: ArrayValue + Copy>( + a: &[T], + b: &[T], + c: &mut [T], + ash: &[usize], + bsh: &[usize], + fill: T, + f: impl Fn(T, T) -> T + Copy, + ) { + match (ash, bsh) { + ([], []) => c[0] = f(a[0], b[0]), + ([], [_, ..]) => { + let a = a[0]; + for (b, c) in b.iter().zip(c) { + *c = f(a, *b); + } + } + ([_, ..], []) => { + let b = b[0]; + for (a, c) in a.iter().zip(c) { + *c = f(*a, b); + } + } + ([al, ash @ ..], [bl, bsh @ ..]) => { + let a_row_len = a.len() / al; + let b_row_len = b.len() / bl; + let c_row_len = c.len() / al.max(bl); + match al.cmp(bl) { + Ordering::Equal => { + if ash == bsh { + for ((a, b), c) in a.iter().zip(b).zip(c) { + *c = f(*a, *b); + } + } else { + for ((a, b), c) in a + .chunks_exact(a_row_len) + .zip(b.chunks_exact(b_row_len)) + .zip(c.chunks_exact_mut(c_row_len)) + { + inner(a, b, c, ash, bsh, fill, f); + } + } + } + Ordering::Less => { + let a_fill_row = vec![fill; a_row_len]; + let a_iter = a + .chunks_exact(a_row_len) + .chain(repeat(a_fill_row.as_slice())); + for ((a, b), c) in a_iter + .zip(b.chunks_exact(b_row_len)) + .zip(c.chunks_exact_mut(c_row_len)) + { + inner(a, b, c, ash, bsh, fill, f); + } + } + Ordering::Greater => { + let b_fill_row = vec![fill; b_row_len]; + let b_iter = b + .chunks_exact(b_row_len) + .chain(repeat(b_fill_row.as_slice())); + for ((a, b), c) in a + .chunks_exact(a_row_len) + .zip(b_iter) + .zip(c.chunks_exact_mut(c_row_len)) + { + inner(a, b, c, ash, bsh, fill, f); + } + } + } + } } } + inner(&a.data, &b.data, slice, &a.shape, &b.shape, fill, f); + b.data = new_data.into(); + b.shape = new_shape; } + + Ok(()) } pub mod not { diff --git a/src/assembly.rs b/src/assembly.rs index 45ded052..94be0eee 100644 --- a/src/assembly.rs +++ b/src/assembly.rs @@ -1,4 +1,4 @@ -use std::{fmt, iter::once, path::PathBuf, sync::Arc}; +use std::{fmt, path::PathBuf, str::FromStr, sync::Arc}; use dashmap::DashMap; use ecow::{eco_vec, EcoString, EcoVec}; @@ -321,7 +321,7 @@ impl Assembly { } } } - serde_json::Value::String(s) => { + serde_json::Value::String(s) if !s.trim().is_empty() => { uasm.push_str(s); uasm.push('\n'); continue; @@ -518,6 +518,66 @@ pub struct DocCommentArg { pub ty: Option<EcoString>, } +impl FromStr for DocCommentSig { + type Err = (); + fn from_str(s: &str) -> Result<Self, Self::Err> { + if s.trim_end().ends_with('?') && !s.trim_end().ends_with(" ?") + || !(s.chars()).all(|c| c.is_whitespace() || "?:".contains(c) || is_ident_char(c)) + { + return Err(()); + } + // Split into args and outputs + let (mut outputs_text, mut args_text) = s.split_once('?').ok_or(())?; + outputs_text = outputs_text.trim(); + args_text = args_text.trim(); + // Parse args and outputs + let mut args = Vec::new(); + let mut outputs = Vec::new(); + for (args, text) in [(&mut args, args_text), (&mut outputs, outputs_text)] { + // Tokenize text + let mut tokens = Vec::new(); + for frag in text.split_whitespace() { + for (i, token) in frag.split(':').enumerate() { + if i > 0 { + tokens.push(":"); + } + tokens.push(token); + } + } + // Parse tokens into args + let mut curr_arg_name = None; + let mut tokens = tokens.into_iter().peekable(); + while let Some(token) = tokens.next() { + if token == ":" { + let ty = tokens.next().unwrap_or_default(); + args.push(DocCommentArg { + name: curr_arg_name.take().unwrap_or_default(), + ty: if ty.is_empty() { None } else { Some(ty.into()) }, + }); + } else { + if let Some(curr) = curr_arg_name.take() { + args.push(DocCommentArg { + name: curr, + ty: None, + }); + } + curr_arg_name = Some(token.into()); + } + } + if let Some(curr) = curr_arg_name.take() { + args.push(DocCommentArg { + name: curr, + ty: None, + }); + } + } + Ok(DocCommentSig { + args, + outputs: (!outputs.is_empty()).then_some(outputs), + }) + } +} + impl From<String> for DocComment { fn from(text: String) -> Self { Self::from(text.as_str()) @@ -533,60 +593,10 @@ impl From<&str> for DocComment { && (line.chars()).all(|c| c.is_whitespace() || "?:".contains(c) || is_ident_char(c)) }); let raw_text = if let Some(i) = sig_line { - let sig_text = text.lines().nth(i).unwrap(); - // Split into args and outputs - let (mut outputs_text, mut args_text) = sig_text.split_once('?').unwrap(); - outputs_text = outputs_text.trim(); - args_text = args_text.trim(); - // Parse args and outputs - let mut args = Vec::new(); - let mut outputs = Vec::new(); - for (args, text) in [(&mut args, args_text), (&mut outputs, outputs_text)] { - // Tokenize text - let mut tokens = Vec::new(); - for frag in text.split_whitespace() { - for (i, token) in frag.split(':').enumerate() { - if i > 0 { - tokens.push(":"); - } - tokens.push(token); - } - } - // Parse tokens into args - let mut curr_arg_name = None; - let mut tokens = tokens.into_iter().peekable(); - while let Some(token) = tokens.next() { - if token == ":" { - let ty = tokens.next().unwrap_or_default(); - args.push(DocCommentArg { - name: curr_arg_name.take().unwrap_or_default(), - ty: if ty.is_empty() { None } else { Some(ty.into()) }, - }); - } else { - if let Some(curr) = curr_arg_name.take() { - args.push(DocCommentArg { - name: curr, - ty: None, - }); - } - curr_arg_name = Some(token.into()); - } - } - if let Some(curr) = curr_arg_name.take() { - args.push(DocCommentArg { - name: curr, - ty: None, - }); - } - } - - sig = Some(DocCommentSig { - args, - outputs: (!outputs.is_empty()).then_some(outputs), - }); + sig = text.lines().nth(i).unwrap().parse().ok(); let mut text: EcoString = (text.lines().take(i)) - .chain(once("\n")) + .chain(["\n"]) .chain(text.lines().skip(i + 1)) .flat_map(|s| s.chars().chain(Some('\n'))) .collect(); diff --git a/src/check.rs b/src/check.rs index d559ed67..f06533ed 100644 --- a/src/check.rs +++ b/src/check.rs @@ -80,6 +80,26 @@ pub(crate) fn instrs_all_signatures(instrs: &[Instr]) -> Result<AllSignatures, S }) } +pub(crate) fn naive_under_sig(f: Signature, g: Signature) -> Signature { + let f_inv = if f.outputs > 1 { + f.inverse() + } else { + Signature::new(f.args.min(1), f.outputs) + }; + let mut curr = 0i32; + let mut min = 0i32; + curr -= f.args as i32; + min = min.min(curr); + curr += f.outputs as i32; + curr -= g.args as i32; + min = min.min(curr); + curr += g.outputs as i32; + curr -= f_inv.args as i32; + min = min.min(curr); + curr += f_inv.outputs as i32; + Signature::new(min.unsigned_abs() as usize, (curr - min) as usize) +} + /// An environment that emulates the runtime but only keeps track of the stack. struct VirtualEnv { stack: Vec<BasicValue>, @@ -414,6 +434,11 @@ impl VirtualEnv { let sig = self.pop_func()?; self.handle_args_outputs(sig.args, sig.outputs)?; } + Under => { + let f = self.pop_func()?; + let g = self.pop_func()?; + self.handle_sig(naive_under_sig(f, g))?; + } Fold => { let f = self.pop_func()?; self.handle_sig(f)?; diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 382003c8..33fa41ae 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -35,10 +35,10 @@ use crate::{ lsp::{CodeMeta, ImportSrc, SigDecl}, optimize::{optimize_instrs, optimize_instrs_mut}, parse::{count_placeholders, flip_unsplit_lines, parse, split_words}, - Array, Assembly, BindingKind, Boxed, Diagnostic, DiagnosticKind, DocComment, GitTarget, Ident, - ImplPrimitive, InputSrc, IntoInputSrc, IntoSysBackend, Primitive, RunMode, SemanticComment, - SysBackend, Uiua, UiuaError, UiuaErrorKind, UiuaResult, Value, CONSTANTS, EXAMPLE_UA, - SUBSCRIPT_NUMS, VERSION, + Array, Assembly, BindingKind, Boxed, Diagnostic, DiagnosticKind, DocComment, DocCommentSig, + GitTarget, Ident, ImplPrimitive, InputSrc, IntoInputSrc, IntoSysBackend, Primitive, RunMode, + SemanticComment, SysBackend, Uiua, UiuaError, UiuaErrorKind, UiuaResult, Value, CONSTANTS, + EXAMPLE_UA, SUBSCRIPT_NUMS, VERSION, }; /// The Uiua compiler @@ -53,7 +53,7 @@ pub struct Compiler { /// The index of the next global binding next_global: usize, /// The current scope - scope: Scope, + pub(crate) scope: Scope, /// Ancestor scopes of the current one higher_scopes: Vec<Scope>, /// Determines which How test scopes are run @@ -74,6 +74,10 @@ pub struct Compiler { in_try: bool, /// Whether the compiler is in a test in_test: bool, + /// Map of instructions to undered functions that created them + /// + /// This is used to under functions that have already been inlined + pub(crate) undered_funcs: HashMap<EcoVec<Instr>, UnderedFunctions>, /// Accumulated errors errors: Vec<UiuaError>, /// Primitives that have emitted errors because they are deprecated @@ -109,6 +113,7 @@ impl Default for Compiler { in_inverse: false, in_test: false, in_try: false, + undered_funcs: HashMap::new(), errors: Vec::new(), deprecated_prim_errors: HashSet::new(), diagnostics: BTreeSet::new(), @@ -202,6 +207,15 @@ struct CurrentBinding { global_index: usize, } +#[derive(Debug, Clone)] +pub(crate) struct UnderedFunctions { + pub f: EcoVec<Instr>, + pub f_sig: Signature, + pub g: EcoVec<Instr>, + pub g_sig: Signature, + pub span: usize, +} + /// A scope where names are defined #[derive(Clone)] pub(crate) struct Scope { @@ -213,7 +227,7 @@ pub(crate) struct Scope { /// Map local names to global indices names: IndexMap<Ident, LocalName>, /// Whether to allow experimental features - experimental: bool, + pub experimental: bool, /// Whether an error has been emitted for experimental features experimental_error: bool, /// Whether an error has been emitted for fill function signatures @@ -579,7 +593,7 @@ code: // Compile top-level words // Populate prelude - for line in &lines { + for line in &mut lines { let mut words = line.iter().filter(|w| !matches!(w.value, Word::Spaces)); if words.clone().count() == 1 { let word = words.next().unwrap(); @@ -591,6 +605,7 @@ code: } else { prelude.comment = Some(c.as_str().into()); } + line.clear(); } Word::SemanticComment(SemanticComment::NoInline) => { prelude.flags |= FunctionFlags::NO_INLINE; @@ -625,6 +640,7 @@ code: Word::Char(_) | Word::Number(..) | Word::String(_) | Word::MultilineString(_) ) }); + let line_sig_comment = line_sig(&line); // Compile the words let instr_count_before = self.asm.instrs.len(); let binding_count_before = self.asm.bindings.len(); @@ -635,6 +651,16 @@ code: let mut line_eval_errored = false; match instrs_signature(&new_func.instrs) { Ok(sig) => { + // Check doc comment sig + if let Some(comment_sig) = line_sig_comment { + if !comment_sig.value.matches_sig(sig) { + self.emit_diagnostic( + format!("Line signature {sig} does not match comment"), + DiagnosticKind::Warning, + comment_sig.span.clone(), + ); + } + } // Update scope stack height if let Ok(height) = &mut self.scope.stack_height { *height = (*height + sig.outputs).saturating_sub(sig.args); @@ -909,6 +935,23 @@ code: } Ok(self.new_functions.pop().unwrap()) } + // Compile a line, checking an end-of-line signature comment + fn compile_line(&mut self, line: Vec<Sp<Word>>, call: bool) -> UiuaResult<NewFunction> { + let comment_sig = line_sig(&line); + let new_func = self.compile_words(line, call)?; + if let Some(comment_sig) = comment_sig { + if let Ok(sig) = instrs_signature(&new_func.instrs) { + if !comment_sig.value.matches_sig(sig) { + self.emit_diagnostic( + format!("Line signature {sig} does not match comment"), + DiagnosticKind::Warning, + comment_sig.span.clone(), + ); + } + } + } + Ok(new_func) + } fn compile_operand_word(&mut self, word: Sp<Word>) -> UiuaResult<(NewFunction, Signature)> { let span = word.span.clone(); let mut new_func = self.compile_words(vec![word], true)?; @@ -1275,8 +1318,8 @@ code: let mut inner = Vec::new(); let mut flags = FunctionFlags::default(); let line_count = arr.lines.len(); - for lines in arr.lines.into_iter().rev() { - let nf = self.compile_words(lines, true)?; + for line in arr.lines.into_iter().rev() { + let nf = self.compile_line(line, true)?; inner.extend(nf.instrs); flags |= nf.flags; } @@ -1843,7 +1886,7 @@ code: ) -> UiuaResult<(FunctionId, Option<Signature>, NewFunction)> { let mut new_func = NewFunction::default(); for line in func.lines { - let nf = self.compile_words(line, true)?; + let nf = self.compile_line(line, true)?; new_func.instrs.extend(nf.instrs); new_func.flags |= nf.flags; } @@ -2730,3 +2773,20 @@ fn recurse_words_mut(words: &mut Vec<Sp<Word>>, f: &mut dyn FnMut(&mut Sp<Word>) } } } + +fn line_sig(line: &[Sp<Word>]) -> Option<Sp<DocCommentSig>> { + line.split_last() + .and_then(|(last, mut rest)| match &last.value { + Word::Comment(c) => c.parse::<DocCommentSig>().ok().map(|sig| { + while rest.last().is_some_and(|w| matches!(w.value, Word::Spaces)) { + rest = &rest[..rest.len() - 1]; + } + let span = (rest.first()) + .zip(rest.last()) + .map(|(f, l)| f.span.clone().merge(l.span.clone())) + .unwrap_or_else(|| last.span.clone()); + span.sp(sig) + }), + _ => None, + }) +} diff --git a/src/compile/modifier.rs b/src/compile/modifier.rs index 74fe9908..88b68dd0 100644 --- a/src/compile/modifier.rs +++ b/src/compile/modifier.rs @@ -175,7 +175,7 @@ impl Compiler { } } #[allow(clippy::collapsible_match)] - pub(super) fn modified( + pub(crate) fn modified( &mut self, mut modified: Modified, subscript: Option<usize>, @@ -987,9 +987,10 @@ impl Compiler { let f = modified.code_operands().next().unwrap().clone(); let span = f.span.clone(); - self.in_inverse = !self.in_inverse; + let in_inverse = self.in_inverse; + self.in_inverse = !in_inverse; let f_res = self.compile_operand_word(f); - self.in_inverse = !self.in_inverse; + self.in_inverse = in_inverse; let (mut new_func, _) = f_res?; self.add_span(span.clone()); @@ -1006,9 +1007,10 @@ impl Compiler { let f = modified.code_operands().next().unwrap().clone(); let span = f.span.clone(); - self.in_inverse = !self.in_inverse; + let in_inverse = self.in_inverse; + self.in_inverse = !in_inverse; let f_res = self.compile_operand_word(f); - self.in_inverse = !self.in_inverse; + self.in_inverse = in_inverse; let (mut new_func, f_sig) = f_res?; if f_sig.args < 2 { self.emit_diagnostic( @@ -1032,7 +1034,7 @@ impl Compiler { Err(e) => return Err(self.fatal_error(span, e)), } } - Under => { + Under if !self.in_inverse => { let mut operands = modified.code_operands().cloned(); let f = operands.next().unwrap(); let f_span = f.span.clone(); @@ -1045,7 +1047,7 @@ impl Compiler { self.in_inverse = !self.in_inverse; let f_res = self.compile_operand_word(f); self.in_inverse = !self.in_inverse; - let (f_new_func, _) = f_res?; + let (f_new_func, f_new_sig) = f_res?; // Under pop diagnostic if let [Instr::Prim(Pop, _)] = f_new_func.instrs.as_slice() { @@ -1059,8 +1061,22 @@ impl Compiler { match under_instrs(&f_new_func.instrs, g_sig, self) { Ok((f_before, f_after)) => { let mut instrs = f_before; - instrs.extend(g_new_func.instrs); + instrs.extend(g_new_func.instrs.iter().cloned()); instrs.extend(f_after); + + // Register undered functions + let span = self.add_span(modified.modifier.span.clone()); + self.undered_funcs.insert( + instrs.clone(), + UnderedFunctions { + f: f_new_func.instrs, + f_sig: f_new_sig, + g: g_new_func.instrs, + g_sig, + span, + }, + ); + let new_func = NewFunction { instrs, flags: f_new_func.flags | g_new_func.flags, diff --git a/src/format.rs b/src/format.rs index 7a2751f2..66a6c48e 100644 --- a/src/format.rs +++ b/src/format.rs @@ -807,22 +807,23 @@ impl<'a> Formatter<'a> { let words = trim_spaces(words, trim_end); for (i, word) in words.iter().enumerate() { self.format_word(word, depth); - if word_is_multiline(&word.value) - && self.output.ends_with(')') - && !self.output.ends_with("()") - && i < words.len() - 1 - { - self.output.pop(); - while self.output.ends_with(' ') { - self.output.pop(); - } - if !self.output.ends_with('\n') { - self.output.push('\n'); - } - for _ in 0..self.config.multiline_indent * depth { - self.output.push(' '); + if word_is_multiline(&word.value) && i < words.len() - 1 { + for (end, empty) in [(')', "()"), (']', "[]"), ('}', "{}")] { + if self.output.ends_with(end) && !self.output.ends_with(empty) { + self.output.pop(); + while self.output.ends_with(' ') { + self.output.pop(); + } + if !self.output.ends_with('\n') { + self.output.push('\n'); + } + for _ in 0..self.config.multiline_indent * depth { + self.output.push(' '); + } + self.output.push(end); + break; + } } - self.output.push(')'); } } } @@ -971,14 +972,7 @@ impl<'a> Formatter<'a> { let indent = self.config.multiline_indent * depth; let allow_compact = start_indent <= indent + 2; - self.format_multiline_words( - &arr.lines, - allow_compact, - true, - false, - true, - depth + 1, - ); + self.format_multiline_words(&arr.lines, allow_compact, true, true, true, depth + 1); if arr.boxes { self.output.push('}'); } else { diff --git a/src/function.rs b/src/function.rs index 33e3e658..4c3a95ef 100644 --- a/src/function.rs +++ b/src/function.rs @@ -148,70 +148,124 @@ impl fmt::Display for TempStack { impl PartialEq for Instr { fn eq(&self, other: &Self) -> bool { + // Comparison ignores spans match (self, other) { + (Self::Comment(a), Self::Comment(b)) => a == b, (Self::Push(a), Self::Push(b)) => a == b, + ( + Self::CallGlobal { + index: index_a, + call: call_a, + sig: sig_a, + }, + Self::CallGlobal { + index: index_b, + call: call_b, + sig: sig_b, + }, + ) => index_a == index_b && call_a == call_b && sig_a == sig_b, + (Self::BindGlobal { index: a, .. }, Self::BindGlobal { index: b, .. }) => a == b, (Self::BeginArray, Self::BeginArray) => true, (Self::EndArray { boxed: a, .. }, Self::EndArray { boxed: b, .. }) => a == b, (Self::Prim(a, _), Self::Prim(b, _)) => a == b, (Self::ImplPrim(a, _), Self::ImplPrim(b, _)) => a == b, - (Self::Call(a), Self::Call(b)) => a == b, - (Self::Format { parts: a, .. }, Self::Format { parts: b, .. }) => a == b, - (Self::MatchFormatPattern { parts: a, .. }, Self::Format { parts: b, .. }) => a == b, + (Self::Call(_), Self::Call(_)) => true, + (Self::CallRecursive(_), Self::CallRecursive(_)) => true, + (Self::Recur(_), Self::Recur(_)) => true, (Self::PushFunc(a), Self::PushFunc(b)) => a == b, - (Self::PushTemp { count: a, .. }, Self::PushTemp { count: b, .. }) => a == b, - (Self::PopTemp { count: a, .. }, Self::PopTemp { count: b, .. }) => a == b, - (Self::TouchStack { count: a, .. }, Self::TouchStack { count: b, .. }) => a == b, - (Self::Comment(a), Self::Comment(b)) => a == b, - (Self::CallGlobal { index: a, .. }, Self::CallGlobal { index: b, .. }) => a == b, - (Self::BindGlobal { index: a, .. }, Self::BindGlobal { index: b, .. }) => a == b, ( Self::Switch { - count: a, - under_cond: au, - sig: asig, + count: count_a, + sig: sig_a, + under_cond: under_cond_a, .. }, Self::Switch { - count: b, - under_cond: bu, - sig: bsig, + count: count_b, + sig: sig_b, + under_cond: under_cond_b, .. }, - ) => a == b && au == bu && asig == bsig, + ) => count_a == count_b && sig_a == sig_b && under_cond_a == under_cond_b, + (Self::Format { parts: a, .. }, Self::Format { parts: b, .. }) => a == b, + ( + Self::MatchFormatPattern { parts: a, .. }, + Self::MatchFormatPattern { parts: b, .. }, + ) => a == b, + (Self::StackSwizzle(a, _), Self::StackSwizzle(b, _)) => a == b, (Self::Label { label: a, .. }, Self::Label { label: b, .. }) => a == b, + ( + Self::ValidateType { + index: index_a, + type_num: type_num_a, + name: name_a, + .. + }, + Self::ValidateType { + index: index_b, + type_num: type_num_b, + name: name_b, + .. + }, + ) => index_a == index_b && type_num_a == type_num_b && name_a == name_b, (Self::Dynamic(a), Self::Dynamic(b)) => a == b, ( Self::Unpack { - count: a, - unbox: au, + count: count_a, + unbox: unbox_a, .. }, Self::Unpack { - count: b, - unbox: bu, + count: count_b, + unbox: unbox_b, .. }, - ) => a == b && au == bu, - (Self::SetOutputComment { i: ai, n: an }, Self::SetOutputComment { i: bi, n: bn }) => { - ai == bi && an == bn + ) => count_a == count_b && unbox_a == unbox_b, + (Self::TouchStack { count: count_a, .. }, Self::TouchStack { count: count_b, .. }) => { + count_a == count_b } - (Self::PushSig(a), Self::PushSig(b)) => a == b, - (Self::PopSig, Self::PopSig) => true, - (Self::StackSwizzle(a, _), Self::StackSwizzle(b, _)) => a == b, ( - Self::ValidateType { - index: ai, - name: a, - type_num: an, + Self::PushTemp { + stack: stack_a, + count: count_a, .. }, - Self::ValidateType { - index: bi, - name: b, - type_num: bn, + Self::PushTemp { + stack: stack_b, + count: count_b, .. }, - ) => ai == bi && a == b && an == bn, + ) => stack_a == stack_b && count_a == count_b, + ( + Self::PopTemp { + stack: stack_a, + count: count_a, + .. + }, + Self::PopTemp { + stack: stack_b, + count: count_b, + .. + }, + ) => stack_a == stack_b && count_a == count_b, + ( + Self::CopyToTemp { + stack: stack_a, + count: count_a, + .. + }, + Self::CopyToTemp { + stack: stack_b, + count: count_b, + .. + }, + ) => stack_a == stack_b && count_a == count_b, + ( + Self::SetOutputComment { i: i_a, n: n_a, .. }, + Self::SetOutputComment { i: i_b, n: n_b, .. }, + ) => i_a == i_b && n_a == n_b, + (Self::PushSig(sig_a), Self::PushSig(sig_b)) => sig_a == sig_b, + (Self::PopSig, Self::PopSig) => true, _ => false, } } @@ -221,44 +275,45 @@ impl Eq for Instr {} impl Hash for Instr { fn hash<H: Hasher>(&self, state: &mut H) { + // Hashing ignores spans match self { - Instr::Push(val) => (0, val).hash(state), - Instr::BeginArray => 1.hash(state), - Instr::EndArray { boxed, .. } => (2, boxed).hash(state), - Instr::Prim(prim, _) => (3, prim).hash(state), - Instr::ImplPrim(prim, _) => (4, prim).hash(state), - Instr::Call(_) => 5.hash(state), - Instr::CallRecursive(_) => 29.hash(state), - Instr::Recur(_) => 30.hash(state), - Instr::Format { parts, .. } => (6, parts).hash(state), - Instr::MatchFormatPattern { parts, .. } => (28, parts).hash(state), - Instr::PushFunc(func) => (7, func).hash(state), - Instr::PushTemp { count, stack, .. } => (8, count, stack).hash(state), - Instr::PopTemp { count, stack, .. } => (9, count, stack).hash(state), - Instr::CopyToTemp { count, stack, .. } => (10, count, stack).hash(state), - Instr::TouchStack { count, .. } => (13, count).hash(state), - Instr::Comment(_) => (14, 0).hash(state), - Instr::CallGlobal { index, call, .. } => (15, index, call).hash(state), - Instr::BindGlobal { index, .. } => (16, index).hash(state), + Instr::Comment(comment) => (0, comment).hash(state), + Instr::Push(val) => (1, val).hash(state), + Instr::CallGlobal { index, call, sig } => (2, index, call, sig).hash(state), + Instr::BindGlobal { index, .. } => (3, index).hash(state), + Instr::BeginArray => (4).hash(state), + Instr::EndArray { boxed, .. } => (5, boxed).hash(state), + Instr::Prim(prim, _) => (6, prim).hash(state), + Instr::ImplPrim(prim, _) => (7, prim).hash(state), + Instr::Call(_) => 8.hash(state), + Instr::CallRecursive(_) => 9.hash(state), + Instr::Recur(_) => 10.hash(state), + Instr::PushFunc(func) => (11, func).hash(state), Instr::Switch { count, - under_cond, sig, + under_cond, .. - } => (17, count, under_cond, sig).hash(state), - Instr::Label { label, remove, .. } => (18, label, remove).hash(state), - Instr::Dynamic(df) => (19, df).hash(state), - Instr::Unpack { count, unbox, .. } => (23, count, unbox).hash(state), - Instr::SetOutputComment { i, n, .. } => (24, i, n).hash(state), - Instr::PushSig(sig) => (25, sig).hash(state), - Instr::PopSig => 26.hash(state), + } => (12, count, sig, under_cond).hash(state), + Instr::Format { parts, .. } => (13, parts).hash(state), + Instr::MatchFormatPattern { parts, .. } => (14, parts).hash(state), + Instr::StackSwizzle(swizzle, _) => (15, swizzle).hash(state), + Instr::Label { label, .. } => (16, label).hash(state), Instr::ValidateType { index, - name, type_num, + name, .. - } => (27, index, name, type_num).hash(state), - Instr::StackSwizzle(swizzle, _) => (31, swizzle).hash(state), + } => (17, index, type_num, name).hash(state), + Instr::Dynamic(df) => (18, df).hash(state), + Instr::Unpack { count, unbox, .. } => (19, count, unbox).hash(state), + Instr::TouchStack { count, .. } => (20, count).hash(state), + Instr::PushTemp { stack, count, .. } => (21, stack, count).hash(state), + Instr::PopTemp { stack, count, .. } => (22, stack, count).hash(state), + Instr::CopyToTemp { stack, count, .. } => (23, stack, count).hash(state), + Instr::SetOutputComment { i, n, .. } => (24, i, n).hash(state), + Instr::PushSig(sig) => (25, sig).hash(state), + Instr::PopSig => 26.hash(state), } } } @@ -333,8 +333,10 @@ mod tests { return; } let contents = std::fs::read_to_string(path).unwrap(); - if contents.contains("dbg!") { - panic!("File {} contains a dbg! macro", path.display()); + for line in contents.lines() { + if line.contains("dbg!") && !line.trim().starts_with("//") { + panic!("File {} contains a dbg! macro", path.display()); + } } }); if crate::algorithm::invert::DEBUG { diff --git a/src/primitive/defs.rs b/src/primitive/defs.rs index ee592251..d7a5216e 100644 --- a/src/primitive/defs.rs +++ b/src/primitive/defs.rs @@ -1135,6 +1135,9 @@ primitive!( /// /// [fill][parse] sets a default value for failed parses. /// ex: ⬚5⋕ {"13" "124" "not a number"} + /// [fill][un][parse] pads the strings to make a character array instead of a box array. + /// ex: ⬚@ °⋕ +9÷4⇡10 + /// ex: ⬚@0°⋕ +9÷4⇡10 (1, Parse, Misc, ("parse", '⋕')), /// Check if two arrays are exactly the same /// @@ -1393,7 +1396,6 @@ primitive!( /// [orient]`¯1` is equivalent to [un][transpose]. /// ex: °△ 2_3_4 /// : ∩△ ⊃°⍉(⤸¯1) - /// Currently, all uses of [orient] can be written with sequences of [transpose] and [rows]. (2, Orient, DyadicArray, ("orient", '⤸')), /// The n-wise windows of an array /// diff --git a/src/value.rs b/src/value.rs index 62d787df..20857984 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1529,6 +1529,18 @@ impl Value { } } +fn optimize_types(a: Value, b: Value) -> (Value, Value) { + match (a, b) { + (Value::Num(a), Value::Byte(b)) if a.element_count() > b.element_count() => { + (a.into(), b.convert::<f64>().into()) + } + (Value::Byte(a), Value::Num(b)) if a.element_count() < b.element_count() => { + (a.convert::<f64>().into(), b.into()) + } + (a, b) => (a, b), + } +} + macro_rules! value_bin_impl { ($name:ident, $( $(($na:ident, $nb:ident, $f1:ident))* @@ -1536,10 +1548,11 @@ macro_rules! value_bin_impl { ),* ) => { impl Value { #[allow(unreachable_patterns, unused_mut, clippy::wrong_self_convention)] - pub(crate) fn $name(mut self, mut other: Self, a_depth: usize, b_depth: usize, env: &Uiua) -> UiuaResult<Self> { - self.match_fill(env); - other.match_fill(env); - self.keep_metas(other, |a, b| { Ok(match (a, b) { + pub(crate) fn $name(self, other: Self, a_depth: usize, b_depth: usize, env: &Uiua) -> UiuaResult<Self> { + let (mut a, mut b) = optimize_types(self, other); + a.match_fill(env); + b.match_fill(env); + a.keep_metas(b, |a, b| { Ok(match (a, b) { $($((Value::$ip(mut a), Value::$ip(mut b)) $(if { let f = |$meta: &ArrayMeta| $pred; f(a.meta()) && f(b.meta()) diff --git a/tests/under.ua b/tests/under.ua index 7fd70565..edfd5f86 100644 --- a/tests/under.ua +++ b/tests/under.ua @@ -73,10 +73,12 @@ # Transpose ⍤⤙≍ { [0_1_2 6_7_8 12_13_14] - [3_4_5 9_10_11 15_16_17]} {⍜⍉°⊟} °△3_2_3 + [3_4_5 9_10_11 15_16_17] +} {⍜⍉°⊟} °△3_2_3 ⍤⤙≍ { [0_3 6_9 12_15] - [[1_2 4_5] [7_8 10_11] [13_14 16_17]]} {⍜°⍉°⊂} °△3_2_3 + [[1_2 4_5] [7_8 10_11] [13_14 16_17]] +} {⍜°⍉°⊂} °△3_2_3 # Where ⍤⤙≍ [0_0_0 0_0_1 0_0_0] ⍜⊙⊚⊂ 1_2 ↯⟜⊚3 diff --git a/tests/units.ua b/tests/units.ua index 0735e3ca..0b2702fb 100644 --- a/tests/units.ua +++ b/tests/units.ua @@ -29,6 +29,8 @@ ⍤⤙≍ [11_22_33 41_52_63] + ¤[1 2 3] [10_20_30 40_50_60] ⍤⤙≍ ⊃≡++ ¤ [10 20 30] ↯4_3_2⇡24 ⍤⤙≍ ⊃≡++ ¤¤ [10 20] ↯4_3_2⇡24 +⍤⤙≍ ≡≡(⊟.) [0_2 2_4] - °△ 2_1_2 °△ 2_2_2 +⍤⤙≍ ≡≡(⊟.) ¯[0_2 2_4] - °△ 2_2_2 °△ 2_1_2 # Filled pervasive math ⍤⤙≍ [1_1 0_0] ⬚0↥ ↯ 2_2 0 ↯ 1_2 1 @@ -215,7 +217,8 @@ ⍤⤙≍ [ℂ4 ¯12 ℂ4 12 ℂ¯4 12 ℂ¯4 12 ℂ4 12] ⋕{"-12r4i" "12r4i" "12-4i" "12r-4i" "12r4i"} ⍤⤙≍ [ℂ`∞ π ℂ∞ π ℂτ π ℂ`τ π ℂτ `π] ⋕{"πr-∞i" "πr∞i" "πrτi" "πr-τi" "-πrτi"} ⍤⤙≍ [ℂ`∞ π ℂ∞ π ℂτ π ℂ`τ π ℂτ `π] ⋕{"π-∞i" "π+∞i" "π+τi" "π-τi" "-π+τi"} - +⍤⟜≍: ["12.1 " " 3 " " 6.25"] ⬚@ °⋕ [12.1 3 6.25] +⍤⟜≍: ["12.10" "03.00" "06.25"] ⬚@0°⋕ [12.1 3 6.25] # Switch ⍤⤙≍ [¯1 2 ¯3 4 ¯5] ⨬(¯|∘) =0◿2.[1 2 3 4 5] @@ -309,7 +312,8 @@ F ← ⨬(¯|+1|×10|∞) ⍤⤙≍ [[[1_2_3 5_6_7]] [[9_10_11 13_14_15]]] ◫¤2_3 +1°△4_4 ⍤⤙≍ [ [1_2_3 5_6_7]_[4_0_0 8_0_0] - [9_10_11 13_14_15]_[12_0_0 16_0_0]] ⬚0◫¤2_3 +1°△4_4 + [9_10_11 13_14_15]_[12_0_0 16_0_0] +] ⬚0◫¤2_3 +1°△4_4 # Escape sequences ⍤⤙≍ 0 -@\0 @\0 @@ -1,12 +1,8 @@ # Uiua Todo - 0.13 - - End-of-line signature comments - - Align multi-line arrays in formatter - - Allow trailing newline in arrays - - Make `un under` work... - - `un` for `under` arg extensions - - `un dip` + - Better testing + - `under (join dip/flip val)` - Non-scalar `fill take` - Placeholder spans and highlighting - `setinv/setund` unification |