summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/model/args.rs30
-rw-r--r--src/model/array.rs55
-rw-r--r--src/model/dict.rs9
-rw-r--r--src/model/eval.rs57
-rw-r--r--src/model/methods.rs47
-rw-r--r--src/model/str.rs40
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! {