diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-03-18 23:36:18 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-03-18 23:43:58 +0100 |
| commit | beca01c826ee51c9ee6d5eadd7e5ef10f7fb9f58 (patch) | |
| tree | e0ebb40b8775bba3b4be7bc47dceda3d349e2ac0 /src/eval | |
| parent | 77d153d315a2a5909840ebcd47491e4cef14428b (diff) | |
Methods
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/args.rs | 19 | ||||
| -rw-r--r-- | src/eval/array.rs | 145 | ||||
| -rw-r--r-- | src/eval/dict.rs | 43 | ||||
| -rw-r--r-- | src/eval/mod.rs | 150 | ||||
| -rw-r--r-- | src/eval/ops.rs | 19 | ||||
| -rw-r--r-- | src/eval/scope.rs | 8 | ||||
| -rw-r--r-- | src/eval/str.rs | 37 | ||||
| -rw-r--r-- | src/eval/value.rs | 121 |
8 files changed, 441 insertions, 101 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs index 67da9865..40454ff5 100644 --- a/src/eval/args.rs +++ b/src/eval/args.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Debug, Formatter, Write}; -use super::{Cast, Value}; +use super::{Array, Cast, Dict, Value}; use crate::diag::{At, TypResult}; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -147,6 +147,23 @@ impl Args { Ok(()) } + /// Extract the positional arguments as an array. + pub fn to_positional(&self) -> Array { + self.items + .iter() + .filter(|item| item.name.is_none()) + .map(|item| item.value.v.clone()) + .collect() + } + + /// Extract the named arguments as a dictionary. + pub fn to_named(&self) -> Dict { + self.items + .iter() + .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone()))) + .collect() + } + /// Reinterpret these arguments as actually being an array index. pub fn into_index(self) -> TypResult<i64> { self.into_castable("index") diff --git a/src/eval/array.rs b/src/eval/array.rs index 2da1a5f4..6fb278e3 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -3,9 +3,11 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::Value; -use crate::diag::StrResult; +use super::{Args, Func, Value}; +use crate::diag::{At, StrResult, TypResult}; +use crate::syntax::Spanned; use crate::util::ArcExt; +use crate::Context; /// Create a new [`Array`] from values. #[allow(unused_macros)] @@ -66,36 +68,134 @@ impl Array { Arc::make_mut(&mut self.0).push(value); } + /// 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")?; + Ok(()) + } + + /// Insert a value at the specified index. + pub fn insert(&mut self, index: i64, value: Value) -> StrResult<()> { + let len = self.len(); + let i = usize::try_from(index) + .ok() + .filter(|&i| i <= self.0.len()) + .ok_or_else(|| out_of_bounds(index, len))?; + + Arc::make_mut(&mut self.0).insert(i, value); + Ok(()) + } + + /// Remove and return the value at the specified index. + pub fn remove(&mut self, index: i64) -> StrResult<()> { + let len = self.len(); + let i = usize::try_from(index) + .ok() + .filter(|&i| i < self.0.len()) + .ok_or_else(|| out_of_bounds(index, len))?; + + Arc::make_mut(&mut self.0).remove(i); + return Ok(()); + } + /// Whether the array contains a specific value. pub fn contains(&self, value: &Value) -> bool { self.0.contains(value) } - /// Clear the array. - pub fn clear(&mut self) { - if Arc::strong_count(&self.0) == 1 { - Arc::make_mut(&mut self.0).clear(); - } else { - *self = Self::new(); + /// Extract a contigous subregion of the array. + pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { + let len = self.len(); + let start = usize::try_from(start) + .ok() + .filter(|&start| start <= self.0.len()) + .ok_or_else(|| out_of_bounds(start, len))?; + + let end = end.unwrap_or(self.len()); + let end = usize::try_from(end) + .ok() + .filter(|&end| end <= self.0.len()) + .ok_or_else(|| out_of_bounds(end, len))?; + + Ok(Self::from_vec(self.0[start .. end].to_vec())) + } + + /// Transform each item in the array with a function. + pub fn map(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Self> { + Ok(self + .iter() + .cloned() + .map(|item| f.v.call(ctx, Args::from_values(f.span, [item]))) + .collect::<TypResult<_>>()?) + } + + /// Return a new array with only those elements for which the function + /// return true. + pub fn filter(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Self> { + let mut kept = vec![]; + for item in self.iter() { + if f.v + .call(ctx, Args::from_values(f.span, [item.clone()]))? + .cast::<bool>() + .at(f.span)? + { + kept.push(item.clone()) + } } + Ok(Self::from_vec(kept)) } - /// Iterate over references to the contained values. - pub fn iter(&self) -> std::slice::Iter<Value> { - self.0.iter() + /// Return a new array with all items from this and nested arrays. + pub fn flatten(&self) -> Self { + let mut flat = vec![]; + for item in self.iter() { + if let Value::Array(nested) = item { + flat.extend(nested.flatten().into_iter()); + } else { + flat.push(item.clone()); + } + } + Self::from_vec(flat) } - /// Extracts a slice of the whole array. - pub fn as_slice(&self) -> &[Value] { - self.0.as_slice() + /// Return the index of the element if it is part of the array. + pub fn find(&self, value: Value) -> Option<i64> { + self.0.iter().position(|x| *x == value).map(|i| i as i64) + } + + /// Join all values in the array, optionally with separator and last + /// separator (between the final two items). + pub fn join(&self, sep: Option<Value>, mut last: Option<Value>) -> StrResult<Value> { + let len = self.0.len(); + let sep = sep.unwrap_or(Value::None); + + let mut result = Value::None; + for (i, value) in self.iter().cloned().enumerate() { + if i > 0 { + if i + 1 == len { + if let Some(last) = last.take() { + result = result.join(last)?; + } else { + result = result.join(sep.clone())?; + } + } else { + result = result.join(sep.clone())?; + } + } + + result = result.join(value)?; + } + + Ok(result) } /// Return a sorted version of this array. /// /// Returns an error if two values could not be compared. - pub fn sorted(mut self) -> StrResult<Self> { + pub fn sorted(&self) -> StrResult<Self> { let mut result = Ok(()); - Arc::make_mut(&mut self.0).sort_by(|a, b| { + let mut vec = (*self.0).clone(); + vec.sort_by(|a, b| { a.partial_cmp(b).unwrap_or_else(|| { if result.is_ok() { result = Err(format!( @@ -107,7 +207,7 @@ impl Array { Ordering::Equal }) }); - result.map(|_| self) + result.map(|_| Self::from_vec(vec)) } /// Repeat this array `n` times. @@ -119,6 +219,17 @@ impl Array { Ok(self.iter().cloned().cycle().take(count).collect()) } + + /// Extract a slice of the whole array. + pub fn as_slice(&self) -> &[Value] { + self.0.as_slice() + } + + + /// Iterate over references to the contained values. + pub fn iter(&self) -> std::slice::Iter<Value> { + self.0.iter() + } } /// The out of bounds access error message. diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 9127b2eb..b630fc63 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -3,9 +3,11 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::Value; -use crate::diag::StrResult; +use super::{Args, Array, Func, Value}; +use crate::diag::{StrResult, TypResult}; +use crate::syntax::Spanned; use crate::util::{ArcExt, EcoString}; +use crate::Context; /// Create a new [`Dict`] from key-value pairs. #[allow(unused_macros)] @@ -56,14 +58,22 @@ impl Dict { Arc::make_mut(&mut self.0).entry(key).or_default() } + /// Whether the dictionary contains a specific key. + pub fn contains(&self, key: &str) -> bool { + self.0.contains_key(key) + } + /// Insert a mapping from the given `key` to the given `value`. pub fn insert(&mut self, key: EcoString, value: Value) { Arc::make_mut(&mut self.0).insert(key, value); } - /// Whether the dictionary contains a specific key. - pub fn contains_key(&self, key: &str) -> bool { - self.0.contains_key(key) + /// Remove a mapping by `key`. + pub fn remove(&mut self, key: EcoString) -> StrResult<()> { + match Arc::make_mut(&mut self.0).remove(&key) { + Some(_) => Ok(()), + None => Err(missing_key(&key)), + } } /// Clear the dictionary. @@ -75,6 +85,29 @@ impl Dict { } } + /// Return the keys of the dictionary as an array. + pub fn keys(&self) -> Array { + self.iter().map(|(key, _)| Value::Str(key.clone())).collect() + } + + /// Return the values of the dictionary as an array. + pub fn values(&self) -> Array { + self.iter().map(|(_, value)| value.clone()).collect() + } + + /// Transform each pair in the array with a function. + pub fn map(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Array> { + Ok(self + .iter() + .map(|(key, value)| { + f.v.call( + ctx, + Args::from_values(f.span, [Value::Str(key.clone()), value.clone()]), + ) + }) + .collect::<TypResult<_>>()?) + } + /// Iterate over pairs of references to the contained keys and values. pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> { self.0.iter() diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2c864036..564dca20 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -19,7 +19,9 @@ mod module; mod ops; mod scope; mod show; +mod str; +pub use self::str::*; pub use args::*; pub use array::*; pub use capture::*; @@ -35,6 +37,7 @@ pub use show::*; pub use styles::*; pub use value::*; +use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard}; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult}; @@ -207,9 +210,9 @@ impl Eval for Expr { Self::Array(v) => v.eval(ctx, scp).map(Value::Array), Self::Dict(v) => v.eval(ctx, scp).map(Value::Dict), Self::Group(v) => v.eval(ctx, scp), - Self::Call(v) => v.eval(ctx, scp), + Self::FuncCall(v) => v.eval(ctx, scp), + Self::MethodCall(v) => v.eval(ctx, scp), Self::Closure(v) => v.eval(ctx, scp), - Self::With(v) => v.eval(ctx, scp), Self::Unary(v) => v.eval(ctx, scp), Self::Binary(v) => v.eval(ctx, scp), Self::Let(v) => v.eval(ctx, scp), @@ -254,7 +257,7 @@ impl Eval for Ident { fn eval(&self, _: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { match scp.get(self) { - Some(slot) => Ok(slot.read().unwrap().clone()), + Some(slot) => Ok(slot.read().clone()), None => bail!(self.span(), "unknown variable"), } } @@ -384,47 +387,62 @@ impl BinaryExpr { op: fn(Value, Value) -> StrResult<Value>, ) -> EvalResult<Value> { let rhs = self.rhs().eval(ctx, scp)?; - self.lhs().access( - ctx, - scp, - Box::new(|target| { - let lhs = std::mem::take(&mut *target); - *target = op(lhs, rhs).at(self.span())?; - Ok(()) - }), - )?; + let lhs = self.lhs(); + let mut location = lhs.access(ctx, scp)?; + let lhs = std::mem::take(&mut *location); + *location = op(lhs, rhs).at(self.span())?; Ok(Value::None) } } -impl Eval for CallExpr { +impl Eval for FuncCall { type Output = Value; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { - let span = self.callee().span(); let callee = self.callee().eval(ctx, scp)?; let args = self.args().eval(ctx, scp)?; Ok(match callee { Value::Array(array) => { - array.get(args.into_index()?).map(Value::clone).at(self.span()) + array.get(args.into_index()?).map(Value::clone).at(self.span())? } Value::Dict(dict) => { - dict.get(args.into_key()?).map(Value::clone).at(self.span()) + dict.get(args.into_key()?).map(Value::clone).at(self.span())? } Value::Func(func) => { let point = || Tracepoint::Call(func.name().map(ToString::to_string)); - func.call(ctx, args).trace(point, self.span()) + func.call(ctx, args).trace(point, self.span())? } v => bail!( - span, + self.callee().span(), "expected callable or collection, found {}", v.type_name(), ), - }?) + }) + } +} + +impl Eval for MethodCall { + type Output = Value; + + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { + let span = self.span(); + let method = self.method(); + let point = || Tracepoint::Call(Some(method.to_string())); + + Ok(if Value::is_mutable_method(&method) { + let args = self.args().eval(ctx, scp)?; + let mut receiver = self.receiver().access(ctx, scp)?; + receiver.call_mut(ctx, &method, span, args).trace(point, span)?; + Value::None + } else { + let receiver = self.receiver().eval(ctx, scp)?; + let args = self.args().eval(ctx, scp)?; + receiver.call(ctx, &method, span, args).trace(point, span)? + }) } } @@ -527,17 +545,6 @@ impl Eval for ClosureExpr { } } -impl Eval for WithExpr { - type Output = Value; - - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { - let callee = self.callee(); - let func = callee.eval(ctx, scp)?.cast::<Func>().at(callee.span())?; - let args = self.args().eval(ctx, scp)?; - Ok(Value::Func(func.with(args))) - } -} - impl Eval for LetExpr { type Output = Value; @@ -694,13 +701,13 @@ impl Eval for ImportExpr { match self.imports() { Imports::Wildcard => { for (var, slot) in module.scope.iter() { - scp.top.def_mut(var, slot.read().unwrap().clone()); + scp.top.def_mut(var, slot.read().clone()); } } Imports::Items(idents) => { for ident in idents { if let Some(slot) = module.scope.get(&ident) { - scp.top.def_mut(ident.take(), slot.read().unwrap().clone()); + scp.top.def_mut(ident.take(), slot.read().clone()); } else { bail!(ident.span(), "unresolved import"); } @@ -773,56 +780,85 @@ impl Eval for ReturnExpr { } } -/// Try to mutably access the value an expression points to. -/// -/// This only works if the expression is a valid lvalue. +/// Access an expression mutably. pub trait Access { - /// Try to access the value. - fn access(&self, ctx: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()>; + /// Access the value. + fn access<'a>( + &self, + ctx: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>>; } -/// Process an accessed value. -type Handler<'a> = Box<dyn FnOnce(&mut Value) -> TypResult<()> + 'a>; - impl Access for Expr { - fn access(&self, ctx: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()> { + fn access<'a>( + &self, + ctx: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>> { match self { - Expr::Ident(ident) => ident.access(ctx, scp, f), - Expr::Call(call) => call.access(ctx, scp, f), - _ => bail!(self.span(), "cannot access this expression mutably"), + Expr::Ident(ident) => ident.access(ctx, scp), + Expr::FuncCall(call) => call.access(ctx, scp), + _ => bail!(self.span(), "cannot mutate a temporary value"), } } } impl Access for Ident { - fn access(&self, _: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()> { + fn access<'a>( + &self, + _: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>> { match scp.get(self) { Some(slot) => match slot.try_write() { - Ok(mut guard) => f(&mut guard), - Err(_) => bail!(self.span(), "cannot mutate a constant"), + Some(guard) => Ok(RwLockWriteGuard::map(guard, |v| v)), + None => bail!(self.span(), "cannot mutate a constant"), }, None => bail!(self.span(), "unknown variable"), } } } -impl Access for CallExpr { - fn access(&self, ctx: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()> { +impl Access for FuncCall { + fn access<'a>( + &self, + ctx: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>> { let args = self.args().eval(ctx, scp)?; - self.callee().access( - ctx, - scp, - Box::new(|value| match value { + let guard = self.callee().access(ctx, scp)?; + try_map(guard, |value| { + Ok(match value { Value::Array(array) => { - f(array.get_mut(args.into_index()?).at(self.span())?) + array.get_mut(args.into_index()?).at(self.span())? } - Value::Dict(dict) => f(dict.get_mut(args.into_key()?)), + Value::Dict(dict) => dict.get_mut(args.into_key()?), v => bail!( self.callee().span(), "expected collection, found {}", v.type_name(), ), - }), - ) + }) + }) } } + +/// A mutable location. +type Location<'a> = MappedRwLockWriteGuard<'a, Value>; + +/// Map a reader-writer lock with a function. +fn try_map<F>(location: Location, f: F) -> EvalResult<Location> +where + F: FnOnce(&mut Value) -> EvalResult<&mut Value>, +{ + let mut error = None; + MappedRwLockWriteGuard::try_map(location, |value| match f(value) { + Ok(value) => Some(value), + Err(err) => { + error = Some(err); + None + } + }) + .map_err(|_| error.unwrap()) +} diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 6a8f5284..9b46e8f6 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,9 +1,8 @@ use std::cmp::Ordering; -use super::{Dynamic, Value}; +use super::{Dynamic, StrExt, Value}; use crate::diag::StrResult; use crate::geom::{Align, Spec, SpecAxis}; -use crate::util::EcoString; use Value::*; /// Bail with a type mismatch error. @@ -174,8 +173,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { (Fractional(a), Float(b)) => Fractional(a * b), (Int(a), Fractional(b)) => Fractional(a as f64 * b), - (Str(a), Int(b)) => Str(repeat_str(a, b)?), - (Int(a), Str(b)) => Str(repeat_str(b, a)?), + (Str(a), Int(b)) => Str(StrExt::repeat(&a, b)?), + (Int(a), Str(b)) => Str(StrExt::repeat(&b, a)?), (Array(a), Int(b)) => Array(a.repeat(b)?), (Int(a), Array(b)) => Array(b.repeat(a)?), (Content(a), Int(b)) => Content(a.repeat(b)?), @@ -185,16 +184,6 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { }) } -/// Repeat a string a number of times. -fn repeat_str(string: EcoString, n: i64) -> StrResult<EcoString> { - let n = usize::try_from(n) - .ok() - .and_then(|n| string.len().checked_mul(n).map(|_| n)) - .ok_or_else(|| format!("cannot repeat this string {} times", n))?; - - Ok(string.repeat(n)) -} - /// Compute the quotient of two values. pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> { Ok(match (lhs, rhs) { @@ -358,7 +347,7 @@ pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> { Some(match (lhs, rhs) { (Value::Str(a), Value::Str(b)) => b.contains(a.as_str()), - (Value::Str(a), Value::Dict(b)) => b.contains_key(a), + (Value::Str(a), Value::Dict(b)) => b.contains(a), (a, Value::Array(b)) => b.contains(a), _ => return Option::None, }) diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 19899cae..8acaa431 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -2,7 +2,9 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::iter; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; + +use parking_lot::RwLock; use super::{Args, Func, Node, Value}; use crate::diag::TypResult; @@ -113,7 +115,7 @@ impl Hash for Scope { self.values.len().hash(state); for (name, value) in self.values.iter() { name.hash(state); - value.read().unwrap().hash(state); + value.read().hash(state); } } } @@ -122,7 +124,7 @@ impl Debug for Scope { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Scope ")?; f.debug_map() - .entries(self.values.iter().map(|(k, v)| (k, v.read().unwrap()))) + .entries(self.values.iter().map(|(k, v)| (k, v.read()))) .finish() } } diff --git a/src/eval/str.rs b/src/eval/str.rs new file mode 100644 index 00000000..3b4349a1 --- /dev/null +++ b/src/eval/str.rs @@ -0,0 +1,37 @@ +use super::{Array, Value}; +use crate::diag::StrResult; +use crate::util::EcoString; + +/// Extra methods on strings. +pub trait StrExt { + /// Repeat a string a number of times. + fn repeat(&self, n: i64) -> StrResult<EcoString>; + + /// Split this string at whitespace or a specific pattern. + fn split(&self, at: Option<EcoString>) -> Array; +} + +impl StrExt for EcoString { + fn repeat(&self, n: i64) -> StrResult<EcoString> { + let n = usize::try_from(n) + .ok() + .and_then(|n| self.len().checked_mul(n).map(|_| n)) + .ok_or_else(|| format!("cannot repeat this string {} times", n))?; + + Ok(self.repeat(n)) + } + + fn split(&self, at: Option<EcoString>) -> Array { + if let Some(pat) = at { + self.as_str() + .split(pat.as_str()) + .map(|s| Value::Str(s.into())) + .collect() + } else { + self.as_str() + .split_whitespace() + .map(|s| Value::Str(s.into())) + .collect() + } + } +} diff --git a/src/eval/value.rs b/src/eval/value.rs index 0e0d08a8..a76b377d 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,10 +4,10 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{ops, Args, Array, Content, Dict, Func, Layout}; -use crate::diag::{with_alternative, StrResult}; +use super::{ops, Args, Array, Content, Context, Dict, Func, Layout, StrExt}; +use crate::diag::{with_alternative, At, StrResult, TypResult}; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; -use crate::syntax::Spanned; +use crate::syntax::{Span, Spanned}; use crate::util::EcoString; /// A computational value. @@ -120,6 +120,121 @@ impl Value { v => Content::Text(v.repr()).monospaced(), } } + + /// Call a method on the value. + pub fn call( + &self, + ctx: &mut Context, + method: &str, + span: Span, + mut args: Args, + ) -> TypResult<Self> { + let name = self.type_name(); + let missing = || Err(missing_method(name, method)).at(span); + + let output = match self { + Value::Str(string) => match method { + "len" => Value::Int(string.len() as i64), + "trim" => Value::Str(string.trim().into()), + "split" => Value::Array(string.split(args.eat()?)), + _ => missing()?, + }, + + Value::Array(array) => match method { + "len" => Value::Int(array.len()), + "slice" => { + let start = args.expect("start")?; + let mut end = args.eat()?; + if end.is_none() { + end = args.named("count")?.map(|c: i64| start + c); + } + Value::Array(array.slice(start, end).at(span)?) + } + "map" => Value::Array(array.map(ctx, args.expect("function")?)?), + "filter" => Value::Array(array.filter(ctx, args.expect("function")?)?), + "flatten" => Value::Array(array.flatten()), + "find" => { + array.find(args.expect("value")?).map_or(Value::None, Value::Int) + } + "join" => { + let sep = args.eat()?; + let last = args.named("last")?; + array.join(sep, last).at(span)? + } + "sorted" => Value::Array(array.sorted().at(span)?), + _ => missing()?, + }, + + Value::Dict(dict) => match method { + "len" => Value::Int(dict.len()), + "keys" => Value::Array(dict.keys()), + "values" => Value::Array(dict.values()), + "pairs" => Value::Array(dict.map(ctx, args.expect("function")?)?), + _ => missing()?, + }, + + Value::Func(func) => match method { + "with" => Value::Func(func.clone().with(args.take())), + _ => missing()?, + }, + + Value::Args(args) => match method { + "positional" => Value::Array(args.to_positional()), + "named" => Value::Dict(args.to_named()), + _ => missing()?, + }, + + _ => missing()?, + }; + + args.finish()?; + Ok(output) + } + + /// Call a mutating method on the value. + pub fn call_mut( + &mut self, + _: &mut Context, + method: &str, + span: Span, + mut args: Args, + ) -> TypResult<()> { + let name = self.type_name(); + let missing = || Err(missing_method(name, method)).at(span); + + match self { + Value::Array(array) => match method { + "push" => array.push(args.expect("value")?), + "pop" => array.pop().at(span)?, + "insert" => { + array.insert(args.expect("index")?, args.expect("value")?).at(span)? + } + "remove" => array.remove(args.expect("index")?).at(span)?, + _ => missing()?, + }, + + Value::Dict(dict) => match method { + "remove" => dict.remove(args.expect("key")?).at(span)?, + _ => missing()?, + }, + + _ => missing()?, + } + + args.finish()?; + Ok(()) + } + + /// Whether a specific method is mutable. + pub fn is_mutable_method(method: &str) -> bool { + matches!(method, "push" | "pop" | "insert" | "remove") + } +} + +/// The missing method error message. +#[cold] +fn missing_method(type_name: &str, method: &str) -> String { + format!("type {type_name} has no method `{method}`") } impl Default for Value { |
