From 1e9a5eda48c65096b482b396d550d139a4c2e61d Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 26 Jul 2022 23:17:06 +0200 Subject: [PATCH] Rework array methods --- src/eval/array.rs | 169 +++++++++++++++++-------------- src/eval/methods.rs | 17 +++- tests/typ/utility/collection.typ | 28 +++-- 3 files changed, 125 insertions(+), 89 deletions(-) diff --git a/src/eval/array.rs b/src/eval/array.rs index aea1b0be3..e54e7e19d 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -3,9 +3,9 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::{ops, Args, Cast, Func, Machine, Value}; +use super::{ops, Args, Func, Machine, Value}; use crate::diag::{At, StrResult, TypResult}; -use crate::syntax::{Span, Spanned}; +use crate::syntax::Spanned; use crate::util::ArcExt; /// Create a new [`Array`] from values. @@ -35,16 +35,21 @@ impl Array { Self(Arc::new(vec)) } - /// Whether the array is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - /// The length of the array. pub fn len(&self) -> i64 { self.0.len() as i64 } + /// The first value in the array. + pub fn first(&self) -> Option<&Value> { + self.0.first() + } + + /// The last value in the array. + pub fn last(&self) -> Option<&Value> { + self.0.last() + } + /// Borrow the value at the given index. pub fn get(&self, index: i64) -> StrResult<&Value> { self.locate(index) @@ -67,7 +72,7 @@ impl Array { /// Remove the last value in the array. pub fn pop(&mut self) -> StrResult<()> { - Arc::make_mut(&mut self.0).pop().ok_or_else(|| "array is empty")?; + Arc::make_mut(&mut self.0).pop().ok_or_else(array_is_empty)?; Ok(()) } @@ -95,11 +100,6 @@ impl Array { return Ok(()); } - /// Whether the array contains a specific value. - pub fn contains(&self, value: &Value) -> bool { - self.0.contains(value) - } - /// Extract a contigous subregion of the array. pub fn slice(&self, start: i64, end: Option) -> StrResult { let len = self.len(); @@ -118,22 +118,33 @@ impl Array { Ok(Self::from_vec(self.0[start .. end].to_vec())) } - /// Transform each item in the array with a function. - pub fn map(&self, vm: &mut Machine, f: Spanned) -> TypResult { - let enumerate = f.v.argc() == Some(2); - Ok(self - .iter() - .cloned() - .enumerate() - .map(|(i, item)| { - let mut args = Args::new(f.span, []); - if enumerate { - args.push(f.span, Value::Int(i as i64)); - } - args.push(f.span, item); - f.v.call(vm, args) - }) - .collect::>()?) + /// Whether the array contains a specific value. + pub fn contains(&self, value: &Value) -> bool { + self.0.contains(value) + } + + /// Return the first matching element. + pub fn find(&self, vm: &mut Machine, f: Spanned) -> TypResult> { + for item in self.iter() { + let args = Args::new(f.span, [item.clone()]); + if f.v.call(vm, args)?.cast::().at(f.span)? { + return Ok(Some(item.clone())); + } + } + + Ok(None) + } + + /// Return the index of the first matching element. + pub fn position(&self, vm: &mut Machine, f: Spanned) -> TypResult> { + for (i, item) in self.iter().enumerate() { + let args = Args::new(f.span, [item.clone()]); + if f.v.call(vm, args)?.cast::().at(f.span)? { + return Ok(Some(i as i64)); + } + } + + Ok(None) } /// Return a new array with only those elements for which the function @@ -141,17 +152,55 @@ impl Array { pub fn filter(&self, vm: &mut Machine, f: Spanned) -> TypResult { let mut kept = vec![]; for item in self.iter() { - if f.v - .call(vm, Args::new(f.span, [item.clone()]))? - .cast::() - .at(f.span)? - { + let args = Args::new(f.span, [item.clone()]); + if f.v.call(vm, args)?.cast::().at(f.span)? { kept.push(item.clone()) } } Ok(Self::from_vec(kept)) } + /// Transform each item in the array with a function. + pub fn map(&self, vm: &mut Machine, f: Spanned) -> TypResult { + let enumerate = f.v.argc() == Some(2); + Ok(self + .iter() + .enumerate() + .map(|(i, item)| { + let mut args = Args::new(f.span, []); + if enumerate { + args.push(f.span, Value::Int(i as i64)); + } + args.push(f.span, item.clone()); + f.v.call(vm, args) + }) + .collect::>()?) + } + + /// Whether any element matches. + pub fn any(&self, vm: &mut Machine, f: Spanned) -> TypResult { + for item in self.iter() { + let args = Args::new(f.span, [item.clone()]); + if f.v.call(vm, args)?.cast::().at(f.span)? { + return Ok(true); + } + } + + Ok(false) + } + + /// Whether all elements match. + pub fn all(&self, vm: &mut Machine, f: Spanned) -> TypResult { + for item in self.iter() { + let args = Args::new(f.span, [item.clone()]); + if !f.v.call(vm, args)?.cast::().at(f.span)? { + return Ok(false); + } + } + + Ok(true) + } + /// Return a new array with all items from this and nested arrays. pub fn flatten(&self) -> Self { let mut flat = Vec::with_capacity(self.0.len()); @@ -165,15 +214,9 @@ impl Array { Self::from_vec(flat) } - /// Return the index of the element if it is part of the array. - pub fn find(&self, vm: &mut Machine, target: Target) -> TypResult> { - for (i, item) in self.iter().enumerate() { - if target.matches(vm, item)? { - return Ok(Some(i as i64)); - } - } - - Ok(None) + /// Returns a new array with reversed order. + pub fn rev(&self) -> Self { + self.0.iter().cloned().rev().collect() } /// Join all values in the array, optionally with separator and last @@ -256,6 +299,12 @@ fn out_of_bounds(index: i64, len: i64) -> String { format!("array index out of bounds (index: {}, len: {})", index, len) } +/// The error message when the array is empty. +#[cold] +fn array_is_empty() -> String { + "array is empty".into() +} + impl Debug for Array { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_char('(')?; @@ -319,37 +368,3 @@ impl<'a> IntoIterator for &'a Array { self.iter() } } - -/// Something that can be found. -pub enum Target { - /// A bare value. - Value(Value), - /// A function that returns a boolean. - Func(Func, Span), -} - -impl Target { - /// Whether the value is the search target. - pub fn matches(&self, vm: &mut Machine, other: &Value) -> TypResult { - match self { - Self::Value(value) => Ok(value == other), - Self::Func(f, span) => f - .call(vm, Args::new(*span, [other.clone()]))? - .cast::() - .at(*span), - } - } -} - -impl Cast> for Target { - fn is(_: &Spanned) -> bool { - true - } - - fn cast(value: Spanned) -> StrResult { - Ok(match value.v { - Value::Func(v) => Self::Func(v, value.span), - v => Self::Value(v), - }) - } -} diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 1ac0ce611..0e6f5af45 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -27,6 +27,8 @@ pub fn call( Value::Array(array) => match method { "len" => Value::Int(array.len()), + "first" => array.first().cloned().unwrap_or(Value::None), + "last" => array.last().cloned().unwrap_or(Value::None), "slice" => { let start = args.expect("start")?; let mut end = args.eat()?; @@ -35,12 +37,17 @@ pub fn call( } Value::Array(array.slice(start, end).at(span)?) } - "map" => Value::Array(array.map(vm, args.expect("function")?)?), - "filter" => Value::Array(array.filter(vm, args.expect("function")?)?), - "flatten" => Value::Array(array.flatten()), - "find" => array - .find(vm, args.expect("value or function")?)? + "contains" => Value::Bool(array.contains(&args.expect("value")?)), + "find" => array.find(vm, args.expect("function")?)?.unwrap_or(Value::None), + "position" => array + .position(vm, args.expect("function")?)? .map_or(Value::None, Value::Int), + "filter" => Value::Array(array.filter(vm, args.expect("function")?)?), + "map" => Value::Array(array.map(vm, args.expect("function")?)?), + "any" => Value::Bool(array.any(vm, args.expect("function")?)?), + "all" => Value::Bool(array.all(vm, args.expect("function")?)?), + "flatten" => Value::Array(array.flatten()), + "rev" => Value::Array(array.rev()), "join" => { let sep = args.eat()?; let last = args.named("last")?; diff --git a/tests/typ/utility/collection.typ b/tests/typ/utility/collection.typ index 42c369061..46ff97abe 100644 --- a/tests/typ/utility/collection.typ +++ b/tests/typ/utility/collection.typ @@ -8,6 +8,15 @@ #test("Hello World!".len(), 12) #test((a: 1, b: 2).len(), 2) +--- +// The the `first` and `last` methods. +#test(().first(), none) +#test(().last(), none) +#test((1,).first(), 1) +#test((2,).last(), 2) +#test((1, 2, 3).first(), 1) +#test((1, 2, 3).last(), 3) + --- // Test the `push` and `pop` methods. { @@ -29,16 +38,16 @@ } --- -// Test the `find` method. -#test(("Hi", "❤️", "Love").find("❤️"), 1) -#test(("Bye", "💘", "Apart").find("❤️"), none) -#test(("A", "B", "CDEF", "G").find(v => v.len() > 2), 2) +// Error: 2:17-2:19 missing argument: index +#let numbers = () +{ numbers.insert() } --- // Test the `slice` method. #test((1, 2, 3, 4).slice(2), (3, 4)) #test(range(10).slice(2, 6), (2, 3, 4, 5)) #test(range(10).slice(4, count: 3), (4, 5, 6)) +#test(range(10).slice(-5, count: 2), (5, 6)) #test((1, 2, 3).slice(2, -2), ()) #test((1, 2, 3).slice(-2, 2), (2,)) #test((1, 2, 3).slice(-3, 2), (1, 2)) @@ -53,9 +62,14 @@ { (1, 2, 3).slice(0, -4) } --- -// Error: 2:17-2:19 missing argument: index -#let numbers = () -{ numbers.insert() } +// Test the `position` method. +#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1) +#test(("Bye", "💘", "Apart").position(s => s == "❤️"), none) +#test(("A", "B", "CDEF", "G").position(v => v.len() > 2), 2) + +--- +// Test the `rev` method. +#test(range(3).rev(), (2, 1, 0)) --- // Test the `join` method.