summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-13 16:39:52 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-13 16:55:45 +0200
commit6a3385e4e77ce7672bcc80941bf02c8218f344a2 (patch)
tree7ae5d668cf7d734f81f1e9d604bbe46fa0775ed8 /src/eval
parent144f20882136ef81b79d77bd8a68f42b76c66676 (diff)
Argument collection and spreading
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/function.rs36
-rw-r--r--src/eval/mod.rs154
-rw-r--r--src/eval/value.rs6
3 files changed, 105 insertions, 91 deletions
diff --git a/src/eval/function.rs b/src/eval/function.rs
index d2ec1bb9..550db59d 100644
--- a/src/eval/function.rs
+++ b/src/eval/function.rs
@@ -60,7 +60,7 @@ impl PartialEq for Function {
pub struct FuncArgs {
/// The span of the whole argument list.
pub span: Span,
- /// The positional arguments.
+ /// The positional and named arguments.
pub items: Vec<FuncArg>,
}
@@ -118,20 +118,28 @@ impl FuncArgs {
where
T: Cast<Spanned<Value>>,
{
- let index = match self
- .items
- .iter()
- .filter_map(|arg| arg.name.as_deref())
- .position(|other| name == other)
- {
- Some(index) => index,
- None => return Ok(None),
- };
-
- let value = self.items.remove(index).value;
- let span = value.span;
+ // We don't quit once we have a match because when multiple matches
+ // exist, we want to remove all of them and use the last one.
+ let mut i = 0;
+ let mut found = None;
+ while i < self.items.len() {
+ if self.items[i].name.as_deref() == Some(name) {
+ let value = self.items.remove(i).value;
+ let span = value.span;
+ found = Some(T::cast(value).at(span)?);
+ } else {
+ i += 1;
+ }
+ }
+ Ok(found)
+ }
- T::cast(value).map(Some).at(span)
+ /// Take out all arguments into a new instance.
+ pub fn take(&mut self) -> Self {
+ Self {
+ span: self.span,
+ items: std::mem::take(&mut self.items),
+ }
}
/// Return an "unexpected argument" error if there is any remaining
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 0932dc71..4e7a0e84 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -386,33 +386,49 @@ impl Eval for CallArgs {
type Output = FuncArgs;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
- Ok(FuncArgs {
- span: self.span,
- items: self
- .items
- .iter()
- .map(|arg| arg.eval(ctx))
- .collect::<TypResult<Vec<_>>>()?,
- })
- }
-}
-
-impl Eval for CallArg {
- type Output = FuncArg;
+ let mut items = Vec::with_capacity(self.items.len());
+
+ for arg in &self.items {
+ let span = arg.span();
+ match arg {
+ CallArg::Pos(expr) => {
+ items.push(FuncArg {
+ span,
+ name: None,
+ value: Spanned::new(expr.eval(ctx)?, expr.span()),
+ });
+ }
+ CallArg::Named(Named { name, expr }) => {
+ items.push(FuncArg {
+ span,
+ name: Some(name.string.clone()),
+ value: Spanned::new(expr.eval(ctx)?, expr.span()),
+ });
+ }
+ CallArg::Spread(expr) => match expr.eval(ctx)? {
+ Value::Args(args) => {
+ items.extend(args.items.iter().cloned());
+ }
+ Value::Array(array) => {
+ items.extend(array.into_iter().map(|value| FuncArg {
+ span,
+ name: None,
+ value: Spanned::new(value, span),
+ }));
+ }
+ Value::Dict(dict) => {
+ items.extend(dict.into_iter().map(|(key, value)| FuncArg {
+ span,
+ name: Some(key),
+ value: Spanned::new(value, span),
+ }));
+ }
+ v => bail!(expr.span(), "cannot spread {}", v.type_name()),
+ },
+ }
+ }
- fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
- Ok(match self {
- Self::Pos(expr) => FuncArg {
- span: self.span(),
- name: None,
- value: Spanned::new(expr.eval(ctx)?, expr.span()),
- },
- Self::Named(Named { name, expr }) => FuncArg {
- span: self.span(),
- name: Some(name.string.clone()),
- value: Spanned::new(expr.eval(ctx)?, expr.span()),
- },
- })
+ Ok(FuncArgs { span: self.span, items })
}
}
@@ -420,25 +436,7 @@ impl Eval for ClosureExpr {
type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
- struct FuncParam {
- name: EcoString,
- default: Option<Value>,
- }
-
- // Evaluate default values for named parameters.
- let params: Vec<_> = self
- .params
- .iter()
- .map(|param| match param {
- ClosureParam::Pos(name) => {
- Ok(FuncParam { name: name.string.clone(), default: None })
- }
- ClosureParam::Named(Named { name, expr }) => Ok(FuncParam {
- name: name.string.clone(),
- default: Some(expr.eval(ctx)?),
- }),
- })
- .collect::<TypResult<_>>()?;
+ let name = self.name.as_ref().map(|name| name.string.clone());
// Collect captured variables.
let captured = {
@@ -447,10 +445,30 @@ impl Eval for ClosureExpr {
visitor.finish()
};
+ let mut sink = None;
+ let mut params = Vec::with_capacity(self.params.len());
+
+ // Collect parameters and an optional sink parameter.
+ for param in &self.params {
+ match param {
+ ClosureParam::Pos(name) => {
+ params.push((name.string.clone(), None));
+ }
+ ClosureParam::Named(Named { name, expr }) => {
+ params.push((name.string.clone(), Some(expr.eval(ctx)?)));
+ }
+ ClosureParam::Sink(name) => {
+ if sink.is_some() {
+ bail!(name.span, "only one argument sink is allowed");
+ }
+ sink = Some(name.string.clone());
+ }
+ }
+ }
+
// Clone the body expression so that we don't have a lifetime
// dependence on the AST.
let body = Rc::clone(&self.body);
- let name = self.name.as_ref().map(|name| name.string.clone());
// Define the actual function.
let func = Function::new(name, move |ctx, args| {
@@ -460,19 +478,21 @@ impl Eval for ClosureExpr {
ctx.scopes.top = captured.clone();
// Parse the arguments according to the parameter list.
- for param in &params {
- let value = match &param.default {
- None => args.expect::<Value>(&param.name)?,
- Some(default) => args
- .named::<Value>(&param.name)?
- .unwrap_or_else(|| default.clone()),
- };
-
- ctx.scopes.def_mut(&param.name, value);
+ for (param, default) in &params {
+ ctx.scopes.def_mut(param, match default {
+ None => args.expect::<Value>(param)?,
+ Some(default) => {
+ args.named::<Value>(param)?.unwrap_or_else(|| default.clone())
+ }
+ });
}
- let value = body.eval(ctx)?;
+ // Put the remaining arguments into the sink.
+ if let Some(sink) = &sink {
+ ctx.scopes.def_mut(sink, Rc::new(args.take()));
+ }
+ let value = body.eval(ctx)?;
ctx.scopes = prev_scopes;
Ok(value)
});
@@ -486,27 +506,11 @@ impl Eval for WithExpr {
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let callee = self.callee.eval(ctx)?.cast::<Function>().at(self.callee.span())?;
-
- let name = callee.name().cloned();
let applied = self.args.eval(ctx)?;
+ let name = callee.name().cloned();
let func = Function::new(name, move |ctx, args| {
- // Remove named arguments that were overridden.
- let kept: Vec<_> = applied
- .items
- .iter()
- .filter(|arg| {
- arg.name.is_none()
- || args.items.iter().all(|other| arg.name != other.name)
- })
- .cloned()
- .collect();
-
- // Preprend the applied arguments so that the positional arguments
- // are in the right order.
- args.items.splice(.. 0, kept);
-
- // Call the original function.
+ args.items.splice(.. 0, applied.items.iter().cloned());
callee(ctx, args)
});
@@ -726,9 +730,7 @@ impl Access for CallExpr {
RefMut::try_map(guard, |value| match value {
Value::Array(array) => array.get_mut(args.into_index()?).at(self.span),
-
Value::Dict(dict) => Ok(dict.get_mut(args.into_key()?)),
-
v => bail!(
self.callee.span(),
"expected collection, found {}",
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 9bab067c..958077da 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -3,7 +3,7 @@ use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter};
use std::rc::Rc;
-use super::{ops, Array, Dict, Function, Template, TemplateFunc};
+use super::{ops, Array, Dict, FuncArgs, Function, Template, TemplateFunc};
use crate::color::{Color, RgbaColor};
use crate::diag::StrResult;
use crate::exec::ExecContext;
@@ -48,6 +48,8 @@ pub enum Value {
Func(Function),
/// A dynamic value.
Dyn(Dynamic),
+ /// Captured arguments to a function.
+ Args(Rc<FuncArgs>),
}
impl Value {
@@ -78,6 +80,7 @@ impl Value {
Self::Dict(_) => Dict::TYPE_NAME,
Self::Template(_) => Template::TYPE_NAME,
Self::Func(_) => Function::TYPE_NAME,
+ Self::Args(_) => Rc::<FuncArgs>::TYPE_NAME,
Self::Dyn(v) => v.type_name(),
}
}
@@ -387,4 +390,5 @@ primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
primitive! { Template: "template", Template }
primitive! { Function: "function", Func }
+primitive! { Rc<FuncArgs>: "arguments", Args }
primitive! { f64: "float", Float, Int(v) => v as f64 }