mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Rework array methods
This commit is contained in:
parent
ef866b0cd1
commit
1e9a5eda48
@ -3,9 +3,9 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::sync::Arc;
|
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::diag::{At, StrResult, TypResult};
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::Spanned;
|
||||||
use crate::util::ArcExt;
|
use crate::util::ArcExt;
|
||||||
|
|
||||||
/// Create a new [`Array`] from values.
|
/// Create a new [`Array`] from values.
|
||||||
@ -35,16 +35,21 @@ impl Array {
|
|||||||
Self(Arc::new(vec))
|
Self(Arc::new(vec))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the array is empty.
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.0.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The length of the array.
|
/// The length of the array.
|
||||||
pub fn len(&self) -> i64 {
|
pub fn len(&self) -> i64 {
|
||||||
self.0.len() as 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.
|
/// Borrow the value at the given index.
|
||||||
pub fn get(&self, index: i64) -> StrResult<&Value> {
|
pub fn get(&self, index: i64) -> StrResult<&Value> {
|
||||||
self.locate(index)
|
self.locate(index)
|
||||||
@ -67,7 +72,7 @@ impl Array {
|
|||||||
|
|
||||||
/// Remove the last value in the array.
|
/// Remove the last value in the array.
|
||||||
pub fn pop(&mut self) -> StrResult<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,11 +100,6 @@ impl Array {
|
|||||||
return Ok(());
|
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.
|
/// Extract a contigous subregion of the array.
|
||||||
pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> {
|
pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
@ -118,22 +118,33 @@ impl Array {
|
|||||||
Ok(Self::from_vec(self.0[start .. end].to_vec()))
|
Ok(Self::from_vec(self.0[start .. end].to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform each item in the array with a function.
|
/// Whether the array contains a specific value.
|
||||||
pub fn map(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Self> {
|
pub fn contains(&self, value: &Value) -> bool {
|
||||||
let enumerate = f.v.argc() == Some(2);
|
self.0.contains(value)
|
||||||
Ok(self
|
}
|
||||||
.iter()
|
|
||||||
.cloned()
|
/// Return the first matching element.
|
||||||
.enumerate()
|
pub fn find(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Option<Value>> {
|
||||||
.map(|(i, item)| {
|
for item in self.iter() {
|
||||||
let mut args = Args::new(f.span, []);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if enumerate {
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
args.push(f.span, Value::Int(i as i64));
|
return Ok(Some(item.clone()));
|
||||||
}
|
}
|
||||||
args.push(f.span, item);
|
}
|
||||||
f.v.call(vm, args)
|
|
||||||
})
|
Ok(None)
|
||||||
.collect::<TypResult<_>>()?)
|
}
|
||||||
|
|
||||||
|
/// Return the index of the first matching element.
|
||||||
|
pub fn position(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Option<i64>> {
|
||||||
|
for (i, item) in self.iter().enumerate() {
|
||||||
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
|
return Ok(Some(i as i64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a new array with only those elements for which the function
|
/// 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<Func>) -> TypResult<Self> {
|
pub fn filter(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Self> {
|
||||||
let mut kept = vec![];
|
let mut kept = vec![];
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
if f.v
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
.call(vm, Args::new(f.span, [item.clone()]))?
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
.cast::<bool>()
|
|
||||||
.at(f.span)?
|
|
||||||
{
|
|
||||||
kept.push(item.clone())
|
kept.push(item.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self::from_vec(kept))
|
Ok(Self::from_vec(kept))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transform each item in the array with a function.
|
||||||
|
pub fn map(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Self> {
|
||||||
|
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::<TypResult<_>>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether any element matches.
|
||||||
|
pub fn any(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<bool> {
|
||||||
|
for item in self.iter() {
|
||||||
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether all elements match.
|
||||||
|
pub fn all(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<bool> {
|
||||||
|
for item in self.iter() {
|
||||||
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
|
if !f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a new array with all items from this and nested arrays.
|
/// Return a new array with all items from this and nested arrays.
|
||||||
pub fn flatten(&self) -> Self {
|
pub fn flatten(&self) -> Self {
|
||||||
let mut flat = Vec::with_capacity(self.0.len());
|
let mut flat = Vec::with_capacity(self.0.len());
|
||||||
@ -165,15 +214,9 @@ impl Array {
|
|||||||
Self::from_vec(flat)
|
Self::from_vec(flat)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index of the element if it is part of the array.
|
/// Returns a new array with reversed order.
|
||||||
pub fn find(&self, vm: &mut Machine, target: Target) -> TypResult<Option<i64>> {
|
pub fn rev(&self) -> Self {
|
||||||
for (i, item) in self.iter().enumerate() {
|
self.0.iter().cloned().rev().collect()
|
||||||
if target.matches(vm, item)? {
|
|
||||||
return Ok(Some(i as i64));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join all values in the array, optionally with separator and last
|
/// 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)
|
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 {
|
impl Debug for Array {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_char('(')?;
|
f.write_char('(')?;
|
||||||
@ -319,37 +368,3 @@ impl<'a> IntoIterator for &'a Array {
|
|||||||
self.iter()
|
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<bool> {
|
|
||||||
match self {
|
|
||||||
Self::Value(value) => Ok(value == other),
|
|
||||||
Self::Func(f, span) => f
|
|
||||||
.call(vm, Args::new(*span, [other.clone()]))?
|
|
||||||
.cast::<bool>()
|
|
||||||
.at(*span),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cast<Spanned<Value>> for Target {
|
|
||||||
fn is(_: &Spanned<Value>) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
|
||||||
Ok(match value.v {
|
|
||||||
Value::Func(v) => Self::Func(v, value.span),
|
|
||||||
v => Self::Value(v),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -27,6 +27,8 @@ pub fn call(
|
|||||||
|
|
||||||
Value::Array(array) => match method {
|
Value::Array(array) => match method {
|
||||||
"len" => Value::Int(array.len()),
|
"len" => Value::Int(array.len()),
|
||||||
|
"first" => array.first().cloned().unwrap_or(Value::None),
|
||||||
|
"last" => array.last().cloned().unwrap_or(Value::None),
|
||||||
"slice" => {
|
"slice" => {
|
||||||
let start = args.expect("start")?;
|
let start = args.expect("start")?;
|
||||||
let mut end = args.eat()?;
|
let mut end = args.eat()?;
|
||||||
@ -35,12 +37,17 @@ pub fn call(
|
|||||||
}
|
}
|
||||||
Value::Array(array.slice(start, end).at(span)?)
|
Value::Array(array.slice(start, end).at(span)?)
|
||||||
}
|
}
|
||||||
"map" => Value::Array(array.map(vm, args.expect("function")?)?),
|
"contains" => Value::Bool(array.contains(&args.expect("value")?)),
|
||||||
"filter" => Value::Array(array.filter(vm, args.expect("function")?)?),
|
"find" => array.find(vm, args.expect("function")?)?.unwrap_or(Value::None),
|
||||||
"flatten" => Value::Array(array.flatten()),
|
"position" => array
|
||||||
"find" => array
|
.position(vm, args.expect("function")?)?
|
||||||
.find(vm, args.expect("value or function")?)?
|
|
||||||
.map_or(Value::None, Value::Int),
|
.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" => {
|
"join" => {
|
||||||
let sep = args.eat()?;
|
let sep = args.eat()?;
|
||||||
let last = args.named("last")?;
|
let last = args.named("last")?;
|
||||||
|
@ -8,6 +8,15 @@
|
|||||||
#test("Hello World!".len(), 12)
|
#test("Hello World!".len(), 12)
|
||||||
#test((a: 1, b: 2).len(), 2)
|
#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.
|
// Test the `push` and `pop` methods.
|
||||||
{
|
{
|
||||||
@ -29,16 +38,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `find` method.
|
// Error: 2:17-2:19 missing argument: index
|
||||||
#test(("Hi", "❤️", "Love").find("❤️"), 1)
|
#let numbers = ()
|
||||||
#test(("Bye", "💘", "Apart").find("❤️"), none)
|
{ numbers.insert() }
|
||||||
#test(("A", "B", "CDEF", "G").find(v => v.len() > 2), 2)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `slice` method.
|
// Test the `slice` method.
|
||||||
#test((1, 2, 3, 4).slice(2), (3, 4))
|
#test((1, 2, 3, 4).slice(2), (3, 4))
|
||||||
#test(range(10).slice(2, 6), (2, 3, 4, 5))
|
#test(range(10).slice(2, 6), (2, 3, 4, 5))
|
||||||
#test(range(10).slice(4, count: 3), (4, 5, 6))
|
#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), ())
|
||||||
#test((1, 2, 3).slice(-2, 2), (2,))
|
#test((1, 2, 3).slice(-2, 2), (2,))
|
||||||
#test((1, 2, 3).slice(-3, 2), (1, 2))
|
#test((1, 2, 3).slice(-3, 2), (1, 2))
|
||||||
@ -53,9 +62,14 @@
|
|||||||
{ (1, 2, 3).slice(0, -4) }
|
{ (1, 2, 3).slice(0, -4) }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2:17-2:19 missing argument: index
|
// Test the `position` method.
|
||||||
#let numbers = ()
|
#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1)
|
||||||
{ numbers.insert() }
|
#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.
|
// Test the `join` method.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user