Named arguments for user defined functions
This commit is contained in:
parent
ccb4be4da4
commit
d002cdf451
@ -71,12 +71,12 @@ pub struct EvalContext<'a> {
|
||||
pub images: &'a mut ImageStore,
|
||||
/// Caches evaluated modules.
|
||||
pub modules: &'a mut ModuleCache,
|
||||
/// The active scopes.
|
||||
pub scopes: Scopes<'a>,
|
||||
/// The id of the currently evaluated source file.
|
||||
pub source: SourceId,
|
||||
/// The stack of imported files that led to evaluation of the current file.
|
||||
pub route: Vec<SourceId>,
|
||||
/// The active scopes.
|
||||
pub scopes: Scopes<'a>,
|
||||
/// The expression map for the currently built template.
|
||||
pub map: ExprMap,
|
||||
}
|
||||
@ -89,9 +89,9 @@ impl<'a> EvalContext<'a> {
|
||||
sources: &mut ctx.sources,
|
||||
images: &mut ctx.images,
|
||||
modules: &mut ctx.modules,
|
||||
scopes: Scopes::new(Some(&ctx.std)),
|
||||
source,
|
||||
route: vec![],
|
||||
scopes: Scopes::new(Some(&ctx.std)),
|
||||
map: ExprMap::new(),
|
||||
}
|
||||
}
|
||||
@ -439,31 +439,66 @@ impl Eval for ClosureExpr {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
let file = ctx.source;
|
||||
let params = Rc::clone(&self.params);
|
||||
let body = Rc::clone(&self.body);
|
||||
struct FuncParam {
|
||||
name: EcoString,
|
||||
default: Option<Value>,
|
||||
}
|
||||
|
||||
// Collect the captured variables.
|
||||
let file = ctx.source;
|
||||
let name = self.name.as_ref().map(|name| name.string.clone());
|
||||
|
||||
// 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<_>>()?;
|
||||
|
||||
// Collect captured variables.
|
||||
let captured = {
|
||||
let mut visitor = CapturesVisitor::new(&ctx.scopes);
|
||||
visitor.visit_closure(self);
|
||||
visitor.finish()
|
||||
};
|
||||
|
||||
let name = self.name.as_ref().map(|name| 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);
|
||||
|
||||
// Define the actual function.
|
||||
let func = Function::new(name, move |ctx, args| {
|
||||
let prev_file = mem::replace(&mut ctx.source, file);
|
||||
|
||||
// Don't leak the scopes from the call site. Instead, we use the
|
||||
// scope of captured variables we collected earlier.
|
||||
let prev_scopes = mem::take(&mut ctx.scopes);
|
||||
let prev_file = mem::replace(&mut ctx.source, file);
|
||||
ctx.scopes.top = captured.clone();
|
||||
|
||||
for param in params.iter() {
|
||||
let value = args.expect::<Value>(param.as_str())?;
|
||||
ctx.scopes.def_mut(param.as_str(), value);
|
||||
}
|
||||
let mut try_eval = || {
|
||||
// Parse the arguments according to the parameter list.
|
||||
for param in ¶ms {
|
||||
let value = match ¶m.default {
|
||||
None => args.expect::<Value>(¶m.name)?,
|
||||
Some(default) => args
|
||||
.named::<Value>(¶m.name)?
|
||||
.unwrap_or_else(|| default.clone()),
|
||||
};
|
||||
|
||||
let result = body.eval(ctx);
|
||||
ctx.scopes.def_mut(¶m.name, value);
|
||||
}
|
||||
|
||||
body.eval(ctx)
|
||||
};
|
||||
|
||||
let result = try_eval();
|
||||
ctx.scopes = prev_scopes;
|
||||
ctx.source = prev_file;
|
||||
result
|
||||
@ -483,9 +518,9 @@ impl Eval for WithExpr {
|
||||
.cast::<Function>()
|
||||
.map_err(Error::at(ctx.source, 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
|
||||
|
@ -312,7 +312,7 @@ fn primary(p: &mut Parser, atomic: bool) -> Option<Expr> {
|
||||
Expr::Closure(ClosureExpr {
|
||||
span: ident.span.join(body.span()),
|
||||
name: None,
|
||||
params: Rc::new(vec![ident]),
|
||||
params: vec![ClosureParam::Pos(ident)],
|
||||
body: Rc::new(body),
|
||||
})
|
||||
} else {
|
||||
@ -385,12 +385,12 @@ fn parenthesized(p: &mut Parser) -> Option<Expr> {
|
||||
|
||||
// Arrow means this is a closure's parameter list.
|
||||
if p.eat_if(Token::Arrow) {
|
||||
let params = idents(p, items);
|
||||
let params = params(p, items);
|
||||
let body = expr(p)?;
|
||||
return Some(Expr::Closure(ClosureExpr {
|
||||
span: span.join(body.span()),
|
||||
name: None,
|
||||
params: Rc::new(params),
|
||||
params,
|
||||
body: Rc::new(body),
|
||||
}));
|
||||
}
|
||||
@ -459,41 +459,53 @@ fn item(p: &mut Parser) -> Option<CallArg> {
|
||||
|
||||
/// Convert a collection into an array, producing errors for named items.
|
||||
fn array(p: &mut Parser, items: Vec<CallArg>, span: Span) -> Expr {
|
||||
let items = items.into_iter().filter_map(|item| match item {
|
||||
let iter = items.into_iter().filter_map(|item| match item {
|
||||
CallArg::Pos(expr) => Some(expr),
|
||||
CallArg::Named(_) => {
|
||||
p.error(item.span(), "expected expression, found named pair");
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
Expr::Array(ArrayExpr { span, items: items.collect() })
|
||||
Expr::Array(ArrayExpr { span, items: iter.collect() })
|
||||
}
|
||||
|
||||
/// Convert a collection into a dictionary, producing errors for expressions.
|
||||
fn dict(p: &mut Parser, items: Vec<CallArg>, span: Span) -> Expr {
|
||||
let items = items.into_iter().filter_map(|item| match item {
|
||||
let iter = items.into_iter().filter_map(|item| match item {
|
||||
CallArg::Named(named) => Some(named),
|
||||
CallArg::Pos(_) => {
|
||||
p.error(item.span(), "expected named pair, found expression");
|
||||
None
|
||||
}
|
||||
});
|
||||
Expr::Dict(DictExpr { span, items: iter.collect() })
|
||||
}
|
||||
|
||||
Expr::Dict(DictExpr { span, items: items.collect() })
|
||||
/// Convert a collection into a list of parameters, producing errors for
|
||||
/// anything other than identifiers 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::Named(named) => Some(ClosureParam::Named(named)),
|
||||
_ => {
|
||||
p.error(item.span(), "expected parameter");
|
||||
None
|
||||
}
|
||||
});
|
||||
iter.collect()
|
||||
}
|
||||
|
||||
/// Convert a collection into a list of identifiers, producing errors for
|
||||
/// anything other than identifiers.
|
||||
fn idents(p: &mut Parser, items: Vec<CallArg>) -> Vec<Ident> {
|
||||
let items = items.into_iter().filter_map(|item| match item {
|
||||
let iter = items.into_iter().filter_map(|item| match item {
|
||||
CallArg::Pos(Expr::Ident(id)) => Some(id),
|
||||
_ => {
|
||||
p.error(item.span(), "expected identifier");
|
||||
None
|
||||
}
|
||||
});
|
||||
items.collect()
|
||||
iter.collect()
|
||||
}
|
||||
|
||||
// Parse a template value: `[...]`.
|
||||
@ -594,28 +606,28 @@ fn let_expr(p: &mut Parser) -> Option<Expr> {
|
||||
init = with_expr(p, Expr::Ident(binding.clone()));
|
||||
} else {
|
||||
// If a parenthesis follows, this is a function definition.
|
||||
let mut params = None;
|
||||
let mut maybe_params = None;
|
||||
if p.peek_direct() == Some(Token::LeftParen) {
|
||||
p.start_group(Group::Paren, TokenMode::Code);
|
||||
let items = collection(p).0;
|
||||
params = Some(idents(p, items));
|
||||
maybe_params = Some(params(p, items));
|
||||
p.end_group();
|
||||
}
|
||||
|
||||
if p.eat_if(Token::Eq) {
|
||||
init = expr(p);
|
||||
} else if params.is_some() {
|
||||
} else if maybe_params.is_some() {
|
||||
// Function definitions must have a body.
|
||||
p.expected_at(p.prev_end(), "body");
|
||||
}
|
||||
|
||||
// Rewrite into a closure expression if it's a function definition.
|
||||
if let Some(params) = params {
|
||||
if let Some(params) = maybe_params {
|
||||
let body = init?;
|
||||
init = Some(Expr::Closure(ClosureExpr {
|
||||
span: binding.span.join(body.span()),
|
||||
name: Some(binding.clone()),
|
||||
params: Rc::new(params),
|
||||
params,
|
||||
body: Rc::new(body),
|
||||
}));
|
||||
}
|
||||
|
@ -370,6 +370,15 @@ impl Pretty for ClosureExpr {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for ClosureParam {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
Self::Pos(ident) => ident.pretty(p),
|
||||
Self::Named(named) => named.pretty(p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for WithExpr {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
self.callee.pretty(p);
|
||||
|
@ -437,11 +437,20 @@ pub struct ClosureExpr {
|
||||
/// This only exists if you use the function syntax sugar: `let f(x) = y`.
|
||||
pub name: Option<Ident>,
|
||||
/// The parameter bindings.
|
||||
pub params: Rc<Vec<Ident>>,
|
||||
pub params: Vec<ClosureParam>,
|
||||
/// The body of the closure.
|
||||
pub body: Rc<Expr>,
|
||||
}
|
||||
|
||||
/// An parameter to a closure: `x` or `draw: false`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ClosureParam {
|
||||
/// A positional parameter.
|
||||
Pos(Ident),
|
||||
/// A named parameter with a default value.
|
||||
Named(Named),
|
||||
}
|
||||
|
||||
/// A with expression: `f with (x, y: 1)`.
|
||||
///
|
||||
/// Applies arguments to a function.
|
||||
|
@ -192,13 +192,6 @@ impl_visitors! {
|
||||
v.visit_args(r!(call.args));
|
||||
}
|
||||
|
||||
visit_closure(v, closure: ClosureExpr) {
|
||||
for param in r!(rc: closure.params) {
|
||||
v.visit_binding(param);
|
||||
}
|
||||
v.visit_expr(r!(rc: closure.body));
|
||||
}
|
||||
|
||||
visit_args(v, args: CallArgs) {
|
||||
for arg in r!(args.items) {
|
||||
v.visit_arg(arg);
|
||||
@ -212,6 +205,23 @@ impl_visitors! {
|
||||
}
|
||||
}
|
||||
|
||||
visit_closure(v, closure: ClosureExpr) {
|
||||
for param in r!(closure.params) {
|
||||
v.visit_param(param);
|
||||
}
|
||||
v.visit_expr(r!(rc: closure.body));
|
||||
}
|
||||
|
||||
visit_param(v, param: ClosureParam) {
|
||||
match param {
|
||||
ClosureParam::Pos(binding) => v.visit_binding(binding),
|
||||
ClosureParam::Named(named) => {
|
||||
v.visit_binding(r!(named.name));
|
||||
v.visit_expr(r!(named.expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visit_with(v, with_expr: WithExpr) {
|
||||
v.visit_expr(r!(with_expr.callee));
|
||||
v.visit_args(r!(with_expr.args));
|
||||
|
@ -83,3 +83,17 @@
|
||||
// Error: 8-13 unexpected argument
|
||||
f(1, "two", () => x)
|
||||
}
|
||||
|
||||
---
|
||||
// Named arguments.
|
||||
{
|
||||
let greet(name, birthday: false) = {
|
||||
if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!"
|
||||
}
|
||||
|
||||
test(greet("Typst"), "Hey, Typst!")
|
||||
test(greet("Typst", birthday: true), "Happy Birthday, Typst!")
|
||||
|
||||
// Error: 23-35 unexpected argument
|
||||
test(greet("Typst", whatever: 10))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user