From 6a3385e4e77ce7672bcc80941bf02c8218f344a2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 13 Aug 2021 16:39:52 +0200 Subject: Argument collection and spreading --- src/eval/function.rs | 36 +++++++----- src/eval/mod.rs | 154 ++++++++++++++++++++++++++------------------------- src/eval/value.rs | 6 +- 3 files changed, 105 insertions(+), 91 deletions(-) (limited to 'src/eval') 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, } @@ -118,20 +118,28 @@ impl FuncArgs { where T: Cast>, { - 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 { - Ok(FuncArgs { - span: self.span, - items: self - .items - .iter() - .map(|arg| arg.eval(ctx)) - .collect::>>()?, - }) - } -} - -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 { - 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 { - struct FuncParam { - name: EcoString, - default: Option, - } - - // 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::>()?; + 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 ¶ms { - let value = match ¶m.default { - None => args.expect::(¶m.name)?, - Some(default) => args - .named::(¶m.name)? - .unwrap_or_else(|| default.clone()), - }; - - ctx.scopes.def_mut(¶m.name, value); + for (param, default) in ¶ms { + ctx.scopes.def_mut(param, match default { + None => args.expect::(param)?, + Some(default) => { + args.named::(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 { let callee = self.callee.eval(ctx)?.cast::().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), } 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::::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: "arguments", Args } primitive! { f64: "float", Float, Int(v) => v as f64 } -- cgit v1.2.3