Argument collection and spreading

This commit is contained in:
Laurenz 2021-08-13 16:39:52 +02:00
parent 144f208821
commit 6a3385e4e7
9 changed files with 231 additions and 102 deletions

View File

@ -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),
};
// 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

View File

@ -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<_>>>()?,
})
}
}
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<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()),
};
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())
}
});
}
ctx.scopes.def_mut(&param.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<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 {}",

View File

@ -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 }

View File

@ -406,6 +406,10 @@ fn parenthesized(p: &mut Parser) -> Option<Expr> {
},
[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<CallArg>, bool) {
/// Parse an expression or a named pair.
fn item(p: &mut Parser) -> Option<CallArg> {
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<CallArg> {
}
}
/// 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<CallArg>, 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<CallArg>, 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<CallArg>, 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<CallArg>, 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<CallArg>) -> Vec<ClosureParam> {
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
}
});

View File

@ -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),
}
}

View File

@ -408,13 +408,15 @@ pub struct CallArgs {
pub items: Vec<CallArg>,
}
/// 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<Expr>,
}
/// 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)`.

View File

@ -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),
}
}

View File

@ -1,5 +1,3 @@
//! An economical string.
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::convert::TryFrom;

71
tests/typ/code/spread.typ Normal file
View File

@ -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))}