diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-12-30 15:13:28 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-12-30 16:45:41 +0100 |
| commit | f70cea508cd30fa40770ea989fe2a19e715a357b (patch) | |
| tree | 731bb96b375dc8fd0f7e5a2a7e1d1fe5cb2a600e /src | |
| parent | fe1f4400693690b68db5a7ec0976ba998624a740 (diff) | |
Remove index syntax in favor of accessor methods
Diffstat (limited to 'src')
| -rw-r--r-- | src/model/args.rs | 30 | ||||
| -rw-r--r-- | src/model/array.rs | 55 | ||||
| -rw-r--r-- | src/model/dict.rs | 9 | ||||
| -rw-r--r-- | src/model/eval.rs | 57 | ||||
| -rw-r--r-- | src/model/methods.rs | 47 | ||||
| -rw-r--r-- | src/model/str.rs | 40 |
6 files changed, 161 insertions, 77 deletions
diff --git a/src/model/args.rs b/src/model/args.rs index fe5f8254..4aaaded4 100644 --- a/src/model/args.rs +++ b/src/model/args.rs @@ -165,36 +165,6 @@ impl Args { .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) -> SourceResult<i64> { - self.into_castable("index") - } - - /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> SourceResult<Str> { - self.into_castable("key") - } - - /// Reinterpret these arguments as actually being a single castable thing. - fn into_castable<T: Cast>(self, what: &str) -> SourceResult<T> { - let mut iter = self.items.into_iter(); - let value = match iter.next() { - Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?, - None => { - bail!(self.span, "missing {}", what); - } - Some(Arg { name: Some(_), span, .. }) => { - bail!(span, "named pair is not allowed here"); - } - }; - - if let Some(arg) = iter.next() { - bail!(arg.span, "only one {} is allowed", what); - } - - Ok(value) - } } impl Debug for Args { diff --git a/src/model/array.rs b/src/model/array.rs index 02607547..fb740a13 100644 --- a/src/model/array.rs +++ b/src/model/array.rs @@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign}; use std::sync::Arc; use super::{ops, Args, Func, Value, Vm}; -use crate::diag::{At, SourceResult, StrResult}; +use crate::diag::{bail, At, SourceResult, StrResult}; use crate::syntax::Spanned; use crate::util::{format_eco, ArcExt, EcoString}; @@ -45,24 +45,34 @@ impl Array { } /// The first value in the array. - pub fn first(&self) -> Option<&Value> { - self.0.first() + pub fn first(&self) -> StrResult<&Value> { + self.0.first().ok_or_else(array_is_empty) + } + + /// Mutably borrow the first value in the array. + pub fn first_mut(&mut self) -> StrResult<&mut Value> { + Arc::make_mut(&mut self.0).first_mut().ok_or_else(array_is_empty) } /// The last value in the array. - pub fn last(&self) -> Option<&Value> { - self.0.last() + pub fn last(&self) -> StrResult<&Value> { + self.0.last().ok_or_else(array_is_empty) + } + + /// Mutably borrow the last value in the array. + pub fn last_mut(&mut self) -> StrResult<&mut Value> { + Arc::make_mut(&mut self.0).last_mut().ok_or_else(array_is_empty) } /// Borrow the value at the given index. - pub fn get(&self, index: i64) -> StrResult<&Value> { + pub fn at(&self, index: i64) -> StrResult<&Value> { self.locate(index) .and_then(|i| self.0.get(i)) .ok_or_else(|| out_of_bounds(index, self.len())) } /// Mutably borrow the value at the given index. - pub fn get_mut(&mut self, index: i64) -> StrResult<&mut Value> { + pub fn at_mut(&mut self, index: i64) -> StrResult<&mut Value> { let len = self.len(); self.locate(index) .and_then(move |i| Arc::make_mut(&mut self.0).get_mut(i)) @@ -128,6 +138,9 @@ impl Array { /// Return the first matching element. pub fn find(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<Value>> { + if f.v.argc().map_or(false, |count| count != 1) { + bail!(f.span, "function must have exactly one parameter"); + } for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { @@ -140,6 +153,9 @@ impl Array { /// Return the index of the first matching element. pub fn position(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<i64>> { + if f.v.argc().map_or(false, |count| count != 1) { + bail!(f.span, "function must have exactly one parameter"); + } 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)? { @@ -153,6 +169,9 @@ impl Array { /// Return a new array with only those elements for which the function /// returns true. pub fn filter(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> { + if f.v.argc().map_or(false, |count| count != 1) { + bail!(f.span, "function must have exactly one parameter"); + } let mut kept = vec![]; for item in self.iter() { let args = Args::new(f.span, [item.clone()]); @@ -165,6 +184,9 @@ impl Array { /// Transform each item in the array with a function. pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> { + if f.v.argc().map_or(false, |count| count < 1 || count > 2) { + bail!(f.span, "function must have one or two parameters"); + } let enumerate = f.v.argc() == Some(2); self.iter() .enumerate() @@ -179,8 +201,24 @@ impl Array { .collect() } + /// Fold all of the array's elements into one with a function. + pub fn fold(&self, vm: &Vm, init: Value, f: Spanned<Func>) -> SourceResult<Value> { + if f.v.argc().map_or(false, |count| count != 2) { + bail!(f.span, "function must have exactly two parameters"); + } + let mut acc = init; + for item in self.iter() { + let args = Args::new(f.span, [acc, item.clone()]); + acc = f.v.call(vm, args)?; + } + Ok(acc) + } + /// Whether any element matches. pub fn any(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> { + if f.v.argc().map_or(false, |count| count != 1) { + bail!(f.span, "function must have exactly one parameter"); + } for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { @@ -193,6 +231,9 @@ impl Array { /// Whether all elements match. pub fn all(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> { + if f.v.argc().map_or(false, |count| count != 1) { + bail!(f.span, "function must have exactly one parameter"); + } for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if !f.v.call(vm, args)?.cast::<bool>().at(f.span)? { diff --git a/src/model/dict.rs b/src/model/dict.rs index e3c5454e..83c16824 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign}; use std::sync::Arc; use super::{Args, Array, Func, Str, Value, Vm}; -use crate::diag::{SourceResult, StrResult}; +use crate::diag::{bail, SourceResult, StrResult}; use crate::syntax::is_ident; use crate::syntax::Spanned; use crate::util::{format_eco, ArcExt, EcoString}; @@ -50,7 +50,7 @@ impl Dict { } /// Borrow the value the given `key` maps to. - pub fn get(&self, key: &str) -> StrResult<&Value> { + pub fn at(&self, key: &str) -> StrResult<&Value> { self.0.get(key).ok_or_else(|| missing_key(key)) } @@ -58,7 +58,7 @@ impl Dict { /// /// This inserts the key with [`None`](Value::None) as the value if not /// present so far. - pub fn get_mut(&mut self, key: Str) -> &mut Value { + pub fn at_mut(&mut self, key: Str) -> &mut Value { Arc::make_mut(&mut self.0).entry(key).or_default() } @@ -108,6 +108,9 @@ impl Dict { /// Transform each pair in the dictionary with a function. pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Array> { + if f.v.argc().map_or(false, |count| count != 1) { + bail!(f.span, "function must have exactly two parameters"); + } self.iter() .map(|(key, value)| { let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]); diff --git a/src/model/eval.rs b/src/model/eval.rs index 54007e76..ab89f9c2 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -780,7 +780,7 @@ impl Eval for ast::FieldAccess { let field = self.field().take(); Ok(match object { - Value::Dict(dict) => dict.get(&field).at(span)?.clone(), + Value::Dict(dict) => dict.at(&field).at(span)?.clone(), Value::Content(content) => content .field(&field) .ok_or_else(|| format!("unknown field {field:?}")) @@ -798,22 +798,11 @@ impl Eval for ast::FuncCall { bail!(self.span(), "maximum function call depth exceeded"); } - let callee = self.callee().eval(vm)?; + let callee = self.callee(); + let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?; let args = self.args().eval(vm)?; - - Ok(match callee { - Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(), - Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(), - Value::Func(func) => { - let point = || Tracepoint::Call(func.name().map(Into::into)); - func.call(vm, args).trace(vm.world, point, self.span())? - } - v => bail!( - self.callee().span(), - "expected callable or collection, found {}", - v.type_name(), - ), - }) + let point = || Tracepoint::Call(callee.name().map(Into::into)); + callee.call(vm, args).trace(vm.world, point, self.span()) } } @@ -1246,9 +1235,13 @@ impl Access for ast::Expr { fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { match self { Self::Ident(v) => v.access(vm), + Self::Parenthesized(v) => v.access(vm), Self::FieldAccess(v) => v.access(vm), - Self::FuncCall(v) => v.access(vm), - _ => bail!(self.span(), "cannot mutate a temporary value"), + Self::MethodCall(v) => v.access(vm), + _ => { + let _ = self.eval(vm)?; + bail!(self.span(), "cannot mutate a temporary value"); + } } } } @@ -1259,10 +1252,16 @@ impl Access for ast::Ident { } } +impl Access for ast::Parenthesized { + fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { + self.expr().access(vm) + } +} + impl Access for ast::FieldAccess { fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { Ok(match self.target().access(vm)? { - Value::Dict(dict) => dict.get_mut(self.field().take().into()), + Value::Dict(dict) => dict.at_mut(self.field().take().into()), v => bail!( self.target().span(), "expected dictionary, found {}", @@ -1272,17 +1271,17 @@ impl Access for ast::FieldAccess { } } -impl Access for ast::FuncCall { +impl Access for ast::MethodCall { fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { + let span = self.span(); + let method = self.method(); let args = self.args().eval(vm)?; - Ok(match self.callee().access(vm)? { - Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?, - Value::Dict(dict) => dict.get_mut(args.into_key()?), - v => bail!( - self.callee().span(), - "expected collection, found {}", - v.type_name(), - ), - }) + if methods::is_accessor(&method) { + let value = self.target().access(vm)?; + methods::call_access(value, &method, args, span) + } else { + let _ = self.eval(vm)?; + bail!(span, "cannot mutate a temporary value"); + } } } diff --git a/src/model/methods.rs b/src/model/methods.rs index dac36be2..8155685c 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -1,6 +1,6 @@ //! Methods on values. -use super::{Args, Value, Vm}; +use super::{Args, Str, Value, Vm}; use crate::diag::{At, SourceResult}; use crate::syntax::Span; use crate::util::EcoString; @@ -26,6 +26,9 @@ pub fn call( Value::Str(string) => match method { "len" => Value::Int(string.len() as i64), + "first" => Value::Str(string.first().at(span)?), + "last" => Value::Str(string.last().at(span)?), + "at" => Value::Str(string.at(args.expect("index")?).at(span)?), "slice" => { let start = args.expect("start")?; let mut end = args.eat()?; @@ -65,8 +68,9 @@ 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), + "first" => array.first().at(span)?.clone(), + "last" => array.last().at(span)?.clone(), + "at" => array.at(args.expect("index")?).at(span)?.clone(), "slice" => { let start = args.expect("start")?; let mut end = args.eat()?; @@ -82,6 +86,9 @@ pub fn call( .map_or(Value::None, Value::Int), "filter" => Value::Array(array.filter(vm, args.expect("function")?)?), "map" => Value::Array(array.map(vm, args.expect("function")?)?), + "fold" => { + array.fold(vm, args.expect("initial value")?, 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()), @@ -97,6 +104,7 @@ pub fn call( Value::Dict(dict) => match method { "len" => Value::Int(dict.len()), + "at" => dict.at(&args.expect::<Str>("key")?).cloned().at(span)?, "keys" => Value::Array(dict.keys()), "values" => Value::Array(dict.values()), "pairs" => Value::Array(dict.map(vm, args.expect("function")?)?), @@ -158,11 +166,44 @@ pub fn call_mut( Ok(output) } +/// Call an accessor method on a value. +pub fn call_access<'a>( + value: &'a mut Value, + method: &str, + mut args: Args, + span: Span, +) -> SourceResult<&'a mut Value> { + let name = value.type_name(); + let missing = || Err(missing_method(name, method)).at(span); + + let slot = match value { + Value::Array(array) => match method { + "first" => array.first_mut().at(span)?, + "last" => array.last_mut().at(span)?, + "at" => array.at_mut(args.expect("index")?).at(span)?, + _ => return missing(), + }, + Value::Dict(dict) => match method { + "at" => dict.at_mut(args.expect("index")?), + _ => return missing(), + }, + _ => return missing(), + }; + + args.finish()?; + Ok(slot) +} + /// Whether a specific method is mutating. pub fn is_mutating(method: &str) -> bool { matches!(method, "push" | "pop" | "insert" | "remove") } +/// Whether a specific method is an accessor. +pub fn is_accessor(method: &str) -> bool { + matches!(method, "first" | "last" | "at") +} + /// The missing method error message. #[cold] fn missing_method(type_name: &str, method: &str) -> String { diff --git a/src/model/str.rs b/src/model/str.rs index d1bf9d23..9196a35a 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -42,16 +42,40 @@ impl Str { self } - /// The codepoints the string consists of. - pub fn codepoints(&self) -> Array { - self.as_str().chars().map(|c| Value::Str(c.into())).collect() - } - /// The grapheme clusters the string consists of. pub fn graphemes(&self) -> Array { self.as_str().graphemes(true).map(|s| Value::Str(s.into())).collect() } + /// Extract the first grapheme cluster. + pub fn first(&self) -> StrResult<Self> { + self.0 + .graphemes(true) + .next() + .map(Into::into) + .ok_or_else(string_is_empty) + } + + /// Extract the last grapheme cluster. + pub fn last(&self) -> StrResult<Self> { + self.0 + .graphemes(true) + .next_back() + .map(Into::into) + .ok_or_else(string_is_empty) + } + + /// Extract the grapheme cluster at the given index. + pub fn at(&self, index: i64) -> StrResult<Self> { + let len = self.len(); + let grapheme = self + .locate(index) + .filter(|&index| index <= self.0.len()) + .and_then(|index| self.0[index..].graphemes(true).next()) + .ok_or_else(|| out_of_bounds(index, len))?; + Ok(grapheme.into()) + } + /// Extract a contigous substring. pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { let len = self.len(); @@ -270,6 +294,12 @@ fn out_of_bounds(index: i64, len: i64) -> String { format!("string index out of bounds (index: {}, len: {})", index, len) } +/// The error message when the string is empty. +#[cold] +fn string_is_empty() -> EcoString { + "string is empty".into() +} + /// Convert an item of std's `match_indices` to a dictionary. fn match_to_dict((start, text): (usize, &str)) -> Dict { dict! { |
