From 6a3385e4e77ce7672bcc80941bf02c8218f344a2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 13 Aug 2021 16:39:52 +0200 Subject: [PATCH] Argument collection and spreading --- src/eval/function.rs | 36 +++++---- src/eval/mod.rs | 150 +++++++++++++++++++------------------- src/eval/value.rs | 6 +- src/parse/mod.rs | 29 ++++++-- src/pretty.rs | 9 +++ src/syntax/expr.rs | 28 +++++-- src/syntax/visit.rs | 2 + src/util/eco.rs | 2 - tests/typ/code/spread.typ | 71 ++++++++++++++++++ 9 files changed, 231 insertions(+), 102 deletions(-) create mode 100644 tests/typ/code/spread.typ diff --git a/src/eval/function.rs b/src/eval/function.rs index d2ec1bb92..550db59df 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), - }; + // 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) + } - let value = self.items.remove(index).value; - let span = value.span; - - 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 0932dc716..4e7a0e843 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::>>()?, - }) - } -} + let mut items = Vec::with_capacity(self.items.len()); -impl Eval for CallArg { - type Output = FuncArg; + 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()), - }; + 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()) + } + }); + } - ctx.scopes.def_mut(¶m.name, value); + // 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 9bab067cb..958077da0 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 } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 03026df4a..2e05dd9e7 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -406,6 +406,10 @@ fn parenthesized(p: &mut Parser) -> Option { }, [CallArg::Pos(_), ..] => array(p, items, span), [CallArg::Named(_), ..] => dict(p, items, span), + [CallArg::Spread(expr), ..] => { + p.error(expr.span(), "spreading is not allowed here"); + return None; + } }) } @@ -443,6 +447,10 @@ fn collection(p: &mut Parser) -> (Vec, bool) { /// Parse an expression or a named pair. fn item(p: &mut Parser) -> Option { + if p.eat_if(Token::Dots) { + return expr(p).map(CallArg::Spread); + } + let first = expr(p)?; if p.eat_if(Token::Colon) { if let Expr::Ident(name) = first { @@ -457,7 +465,8 @@ fn item(p: &mut Parser) -> Option { } } -/// Convert a collection into an array, producing errors for named items. +/// Convert a collection into an array, producing errors for anything other than +/// expressions. fn array(p: &mut Parser, items: Vec, span: Span) -> Expr { let iter = items.into_iter().filter_map(|item| match item { CallArg::Pos(expr) => Some(expr), @@ -465,11 +474,16 @@ fn array(p: &mut Parser, items: Vec, span: Span) -> Expr { p.error(item.span(), "expected expression, found named pair"); None } + CallArg::Spread(_) => { + p.error(item.span(), "spreading is not allowed here"); + None + } }); Expr::Array(ArrayExpr { span, items: iter.collect() }) } -/// Convert a collection into a dictionary, producing errors for expressions. +/// Convert a collection into a dictionary, producing errors for anything other +/// than named pairs. fn dict(p: &mut Parser, items: Vec, span: Span) -> Expr { let iter = items.into_iter().filter_map(|item| match item { CallArg::Named(named) => Some(named), @@ -477,18 +491,23 @@ fn dict(p: &mut Parser, items: Vec, span: Span) -> Expr { p.error(item.span(), "expected named pair, found expression"); None } + CallArg::Spread(_) => { + p.error(item.span(), "spreading is not allowed here"); + None + } }); Expr::Dict(DictExpr { span, items: iter.collect() }) } /// Convert a collection into a list of parameters, producing errors for -/// anything other than identifiers and named pairs. +/// anything other than identifiers, spread operations and named pairs. fn params(p: &mut Parser, items: Vec) -> Vec { let iter = items.into_iter().filter_map(|item| match item { - CallArg::Pos(Expr::Ident(id)) => Some(ClosureParam::Pos(id)), + CallArg::Pos(Expr::Ident(ident)) => Some(ClosureParam::Pos(ident)), CallArg::Named(named) => Some(ClosureParam::Named(named)), + CallArg::Spread(Expr::Ident(ident)) => Some(ClosureParam::Sink(ident)), _ => { - p.error(item.span(), "expected parameter"); + p.error(item.span(), "expected identifier"); None } }); diff --git a/src/pretty.rs b/src/pretty.rs index 523005462..179e5d2dd 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -357,6 +357,10 @@ impl Pretty for CallArg { match self { Self::Pos(expr) => expr.pretty(p), Self::Named(named) => named.pretty(p), + Self::Spread(expr) => { + p.push_str(".."); + expr.pretty(p); + } } } } @@ -375,6 +379,10 @@ impl Pretty for ClosureParam { match self { Self::Pos(ident) => ident.pretty(p), Self::Named(named) => named.pretty(p), + Self::Sink(ident) => { + p.push_str(".."); + ident.pretty(p); + } } } } @@ -496,6 +504,7 @@ impl Pretty for Value { Self::Dict(v) => v.pretty(p), Self::Template(v) => v.pretty(p), Self::Func(v) => v.pretty(p), + Self::Args(v) => v.pretty(p), Self::Dyn(v) => v.pretty(p), } } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 9292d5b6c..5b751280e 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -408,13 +408,15 @@ pub struct CallArgs { pub items: Vec, } -/// An argument to a function call: `12` or `draw: false`. +/// An argument to a function call. #[derive(Debug, Clone, PartialEq)] pub enum CallArg { - /// A positional argument. + /// A positional argument: `12`. Pos(Expr), - /// A named argument. + /// A named argument: `draw: false`. Named(Named), + /// A spreaded argument: `..things`. + Spread(Expr), } impl CallArg { @@ -423,6 +425,7 @@ impl CallArg { match self { Self::Pos(expr) => expr.span(), Self::Named(named) => named.span(), + Self::Spread(expr) => expr.span(), } } } @@ -442,13 +445,26 @@ pub struct ClosureExpr { pub body: Rc, } -/// An parameter to a closure: `x` or `draw: false`. +/// An parameter to a closure. #[derive(Debug, Clone, PartialEq)] pub enum ClosureParam { - /// A positional parameter. + /// A positional parameter: `x`. Pos(Ident), - /// A named parameter with a default value. + /// A named parameter with a default value: `draw: false`. Named(Named), + /// A parameter sink: `..args`. + Sink(Ident), +} + +impl ClosureParam { + /// The source code location. + pub fn span(&self) -> Span { + match self { + Self::Pos(ident) => ident.span, + Self::Named(named) => named.span(), + Self::Sink(ident) => ident.span, + } + } } /// A with expression: `f with (x, y: 1)`. diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index bc9359b3f..e39d1f23d 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -202,6 +202,7 @@ impl_visitors! { match arg { CallArg::Pos(expr) => v.visit_expr(expr), CallArg::Named(named) => v.visit_expr(r!(named.expr)), + CallArg::Spread(expr) => v.visit_expr(expr), } } @@ -219,6 +220,7 @@ impl_visitors! { v.visit_binding(r!(named.name)); v.visit_expr(r!(named.expr)); } + ClosureParam::Sink(binding) => v.visit_binding(binding), } } diff --git a/src/util/eco.rs b/src/util/eco.rs index 2e326f322..08c2117d1 100644 --- a/src/util/eco.rs +++ b/src/util/eco.rs @@ -1,5 +1,3 @@ -//! An economical string. - use std::borrow::Borrow; use std::cmp::Ordering; use std::convert::TryFrom; diff --git a/tests/typ/code/spread.typ b/tests/typ/code/spread.typ new file mode 100644 index 000000000..f1414313a --- /dev/null +++ b/tests/typ/code/spread.typ @@ -0,0 +1,71 @@ +// Test argument sinks and spreading. +// Ref: false + +--- +// Test standard argument overriding. +{ + let font(style: normal, weight: regular) = { + "(style: " + repr(style) + ", weight: " + repr(weight) + ")" + } + + let myfont(..args) = font(weight: bold, ..args) + test(myfont(), "(style: normal, weight: bold)") + test(myfont(weight: 100), "(style: normal, weight: 100)") + test(myfont(style: italic), "(style: italic, weight: bold)") +} + +--- +// Test multiple calls. +{ + let f(b, c: "!") = b + c + let g(a, ..sink) = a + f(..sink) + test(g("a", "b", c: "c"), "abc") +} + +--- +// Test storing arguments in a variable. +{ + let args + let save(..sink) = { + args = sink + } + + save(1, 2, three: true) + test(type(args), "arguments") + test(repr(args), "(1, 2, three: true)") +} + +--- +// Test spreading array and dictionary. +{ + let more = (3, -3, 6, 10) + test(min(1, 2, ..more), -3) + test(max(..more, 9), 10) + test(max(..more, 11), 11) +} + +{ + let more = (c: 3, d: 4) + let tostr(..args) = repr(args) + test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)") +} + +--- +// Error: 8-14 cannot spread string +#min(.."nope") + +--- +// Error: 10-14 expected identifier +#let f(..true) = none + +--- +// Error: 15-16 only one argument sink is allowed +#let f(..a, ..b) = none + +--- +// Error: 5-6 spreading is not allowed here +{(..x)} + +--- +// Error: 11-17 spreading is not allowed here +{(1, 2, ..(1, 2))}