summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoramatgil <amatgilvinyes@gmail.com>2024-10-01 14:03:11 +0200
committeramatgil <amatgilvinyes@gmail.com>2024-10-01 14:03:11 +0200
commit924584f8a90266a449756d8564c4bd497ade8bf1 (patch)
tree70f6f8c33e53abee5526b029638523a665165395
parentf5ddf9ea15949c325a8581705aa471803250f37d (diff)
parent6c10c63d472add83cbc9295f2a198d0f8e2097f9 (diff)
Merge branch 'main' of https://github.com/uiua-lang/uiua into parse_extension
-rw-r--r--changelog.md4
-rw-r--r--site/blog_rss.ua3
-rw-r--r--site/src/other_tutorial.rs4
-rw-r--r--src/algorithm/dyadic/mod.rs11
-rw-r--r--src/algorithm/invert.rs135
-rw-r--r--src/algorithm/mod.rs63
-rw-r--r--src/algorithm/monadic.rs82
-rw-r--r--src/algorithm/pervade.rs701
-rw-r--r--src/assembly.rs118
-rw-r--r--src/check.rs25
-rw-r--r--src/compile/mod.rs80
-rw-r--r--src/compile/modifier.rs32
-rw-r--r--src/format.rs40
-rw-r--r--src/function.rs183
-rw-r--r--src/lib.rs6
-rw-r--r--src/primitive/defs.rs4
-rw-r--r--src/value.rs21
-rw-r--r--tests/under.ua6
-rw-r--r--tests/units.ua8
-rw-r--r--todo.md8
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),
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 0ce8f803..15c012f3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
diff --git a/todo.md b/todo.md
index 08f46a79..6c6f96a3 100644
--- a/todo.md
+++ b/todo.md
@@ -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