diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 116d3c122..a9fff56be 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -263,7 +263,8 @@ impl Eval for Expr { Self::Group(ref v) => v.eval(ctx), Self::Block(ref v) => v.eval(ctx), Self::Call(ref v) => v.eval(ctx), - Self::Closure(ref v) => Value::Func(v.eval(ctx)), + Self::Closure(ref v) => v.eval(ctx), + Self::With(ref v) => v.eval(ctx), Self::Unary(ref v) => v.eval(ctx), Self::Binary(ref v) => v.eval(ctx), Self::Let(ref v) => v.eval(ctx), @@ -498,11 +499,13 @@ impl Eval for CallArg { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { 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 { - name: Some(Spanned::new(name.string.clone(), name.span)), + span: self.span(), + name: Some(name.string.clone()), value: Spanned::new(expr.eval(ctx), expr.span()), }, } @@ -510,7 +513,7 @@ impl Eval for CallArg { } impl Eval for ClosureExpr { - type Output = FuncValue; + type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let params = Rc::clone(&self.params); @@ -524,7 +527,7 @@ impl Eval for ClosureExpr { }; let name = self.name.as_ref().map(|id| id.to_string()); - FuncValue::new(name, move |ctx, args| { + Value::Func(FuncValue::new(name, move |ctx, args| { // Don't leak the scopes from the call site. Instead, we use the // scope of captured variables we collected earlier. let prev = mem::take(&mut ctx.scopes); @@ -539,7 +542,40 @@ impl Eval for ClosureExpr { let value = body.eval(ctx); ctx.scopes = prev; value - }) + })) + } +} + +impl Eval for WithExpr { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let callee = self.callee.eval(ctx); + if let Some(func) = ctx.cast::(callee, self.callee.span()) { + let applied = self.args.eval(ctx); + let name = func.name().map(|s| s.to_string()); + Value::Func(FuncValue::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. + func(ctx, args) + })) + } else { + Value::Error + } } } diff --git a/src/eval/value.rs b/src/eval/value.rs index e8ecffa2e..28c7d5889 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -274,7 +274,7 @@ impl Debug for FuncValue { pub struct FuncArgs { /// The span of the whole argument list. pub span: Span, - /// The arguments. + /// The positional arguments. pub items: Vec, } @@ -346,7 +346,7 @@ impl FuncArgs { let index = self .items .iter() - .position(|arg| arg.name.as_ref().map(|s| s.v.as_str()) == Some(name))?; + .position(|arg| arg.name.as_ref().map_or(false, |other| name == other))?; let value = self.items.remove(index).value; let span = value.span; @@ -373,7 +373,7 @@ impl FuncArgs { pub fn finish(self, ctx: &mut EvalContext) { for arg in &self.items { if arg.value.v != Value::Error { - ctx.diag(error!(arg.span(), "unexpected argument")); + ctx.diag(error!(arg.span, "unexpected argument")); } } } @@ -382,22 +382,14 @@ impl FuncArgs { /// An argument to a function call: `12` or `draw: false`. #[derive(Debug, Clone, PartialEq)] pub struct FuncArg { + /// The span of the whole argument. + pub span: Span, /// The name of the argument (`None` for positional arguments). - pub name: Option>, + pub name: Option, /// The value of the argument. pub value: Spanned, } -impl FuncArg { - /// The source code location. - pub fn span(&self) -> Span { - match &self.name { - Some(name) => name.span.join(self.value.span), - None => self.value.span, - } - } -} - /// A wrapper around a dynamic value. pub struct AnyValue(Box); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index d8000d8cc..381d44e2f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -245,6 +245,10 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option { continue; } + if p.eat_if(Token::With) { + lhs = with_expr(p, lhs); + } + if atomic { break; } @@ -545,6 +549,19 @@ fn args(p: &mut Parser) -> CallArgs { CallArgs { span: p.span(start), items } } +/// Parse a with expression. +fn with_expr(p: &mut Parser, callee: Expr) -> Expr { + p.start_group(Group::Paren, TokenMode::Code); + let args = args(p); + p.end_group(); + + Expr::With(WithExpr { + span: p.span(callee.span().start), + callee: Box::new(callee), + args, + }) +} + /// Parse a let expression. fn let_expr(p: &mut Parser) -> Option { let start = p.next_start(); @@ -552,32 +569,37 @@ fn let_expr(p: &mut Parser) -> Option { let mut let_expr = None; if let Some(binding) = ident(p) { - // If a parenthesis follows, this is a function definition. - let mut 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)); - p.end_group(); - } - let mut init = None; - if p.eat_if(Token::Eq) { - init = expr(p); - } else if params.is_some() { - // Function definitions must have a body. - p.expected_at("body", p.prev_end()); - } - // Rewrite into a closure expression if it's a function definition. - if let Some(params) = params { - let body = init?; - init = Some(Expr::Closure(ClosureExpr { - span: binding.span.join(body.span()), - name: Some(binding.clone()), - params: Rc::new(params), - body: Rc::new(body), - })); + if p.eat_if(Token::With) { + init = Some(with_expr(p, Expr::Ident(binding.clone()))); + } else { + // If a parenthesis follows, this is a function definition. + let mut 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)); + p.end_group(); + } + + if p.eat_if(Token::Eq) { + init = expr(p); + } else if params.is_some() { + // Function definitions must have a body. + p.expected_at("body", p.prev_end()); + } + + // Rewrite into a closure expression if it's a function definition. + if let Some(params) = params { + let body = init?; + init = Some(Expr::Closure(ClosureExpr { + span: binding.span.join(body.span()), + name: Some(binding.clone()), + params: Rc::new(params), + body: Rc::new(body), + })); + } } let_expr = Some(Expr::Let(LetExpr { diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index a496010e3..abc3d6a6c 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -366,9 +366,6 @@ impl<'s> Tokens<'s> { fn ident(&mut self, start: usize) -> Token<'s> { self.s.eat_while(is_id_continue); match self.s.eaten_from(start) { - "not" => Token::Not, - "and" => Token::And, - "or" => Token::Or, "none" => Token::None, "auto" => Token::Auto, "true" => Token::Bool(true), @@ -489,6 +486,10 @@ impl Debug for Tokens<'_> { fn keyword(id: &str) -> Option> { Some(match id { + "not" => Token::Not, + "and" => Token::And, + "or" => Token::Or, + "with" => Token::With, "let" => Token::Let, "if" => Token::If, "else" => Token::Else, @@ -777,6 +778,7 @@ mod tests { fn test_tokenize_keywords() { // A list of a few (not all) keywords. let list = [ + ("not", Not), ("let", Let), ("if", If), ("else", Else), diff --git a/src/pretty.rs b/src/pretty.rs index e2942f6fd..216a63702 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -217,6 +217,7 @@ impl Pretty for Expr { Self::Binary(v) => v.pretty(p), Self::Call(v) => v.pretty(p), Self::Closure(v) => v.pretty(p), + Self::With(v) => v.pretty(p), Self::Let(v) => v.pretty(p), Self::If(v) => v.pretty(p), Self::While(v) => v.pretty(p), @@ -370,6 +371,15 @@ impl Pretty for ClosureExpr { } } +impl Pretty for WithExpr { + fn pretty(&self, p: &mut Printer) { + self.callee.pretty(p); + p.push_str(" with ("); + self.args.pretty(p); + p.push(')'); + } +} + impl Pretty for LetExpr { fn pretty(&self, p: &mut Printer) { p.push_str("let "); @@ -539,7 +549,7 @@ impl Pretty for FuncArgs { impl Pretty for FuncArg { fn pretty(&self, p: &mut Printer) { if let Some(name) = &self.name { - p.push_str(&name.v); + p.push_str(&name); p.push_str(": "); } self.value.v.pretty(p); diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 24850d651..62f023990 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -52,6 +52,8 @@ pub enum Expr { Call(CallExpr), /// A closure expression: `(x, y) => z`. Closure(ClosureExpr), + /// A with expression: `f with (x, y: 1)`. + With(WithExpr), /// A let expression: `let x = 1`. Let(LetExpr), /// An if-else expression: `if x { y } else { z }`. @@ -91,6 +93,7 @@ impl Expr { Self::Binary(ref v) => v.span, Self::Call(ref v) => v.span, Self::Closure(ref v) => v.span, + Self::With(ref v) => v.span, Self::Let(ref v) => v.span, Self::If(ref v) => v.span, Self::While(ref v) => v.span, @@ -383,7 +386,7 @@ pub enum Associativity { pub struct CallExpr { /// The source code location. pub span: Span, - /// The callee of the function. + /// The function to call. pub callee: Box, /// The arguments to the function. pub args: CallArgs, @@ -435,6 +438,19 @@ pub struct ClosureExpr { pub body: Rc, } +/// A with expression: `f with (x, y: 1)`. +/// +/// Applies arguments to a function. +#[derive(Debug, Clone, PartialEq)] +pub struct WithExpr { + /// The source code location. + pub span: Span, + /// The function to apply the arguments to. + pub callee: Box, + /// The arguments to apply to the function. + pub args: CallArgs, +} + /// A let expression: `let x = 1`. #[derive(Debug, Clone, PartialEq)] pub struct LetExpr { diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 254a56a24..3f07bb338 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -74,6 +74,8 @@ pub enum Token<'s> { And, /// The `or` operator. Or, + /// The `with` operator. + With, /// The none literal: `none`. None, /// The auto literal: `auto`. @@ -241,6 +243,7 @@ impl<'s> Token<'s> { Self::Not => "operator `not`", Self::And => "operator `and`", Self::Or => "operator `or`", + Self::With => "operator `with`", Self::None => "`none`", Self::Auto => "`auto`", Self::Let => "keyword `let`", diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index a1a848ef9..cf819ef06 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -99,6 +99,7 @@ visit! { Expr::Binary(e) => v.visit_binary(e), Expr::Call(e) => v.visit_call(e), Expr::Closure(e) => v.visit_closure(e), + Expr::With(e) => v.visit_with(e), Expr::Let(e) => v.visit_let(e), Expr::If(e) => v.visit_if(e), Expr::While(e) => v.visit_while(e), @@ -176,6 +177,11 @@ visit! { } } + fn visit_with(v, node: &WithExpr) { + v.visit_expr(&node.callee); + v.visit_args(&node.args); + } + fn visit_let(v, node: &LetExpr) { v.visit_binding(&node.binding); if let Some(init) = &node.init { diff --git a/tests/ref/code/ops.png b/tests/ref/code/ops.png index b566b5b75..4421edb43 100644 Binary files a/tests/ref/code/ops.png and b/tests/ref/code/ops.png differ diff --git a/tests/typ/code/ops.typ b/tests/typ/code/ops.typ index e2ecd4ff0..a7d5474e8 100644 --- a/tests/typ/code/ops.typ +++ b/tests/typ/code/ops.typ @@ -145,3 +145,21 @@ { x /= 2.0 } #test(x, 18.0) { x = "some" } #test(x, "some") { x += "thing" } #test(x, "something") + +--- +// Test with operator. +// Ref: true + +// Apply positional arguments. +#let add(x, y) = x + y +#test((add with (2))(4), 6) + +// Let .. with .. syntax. +#let f = add +#let f with (2) +#test(f(4), 6) + +// Make sure that named arguments are overridable. +#let align with (horizontal: right) +#align[Right] \ +#align(horizontal: left)[Left]