With expressions

This commit is contained in:
Laurenz 2021-06-26 18:07:05 +02:00
parent d53c933e4d
commit 422b8e640f
10 changed files with 153 additions and 48 deletions

View File

@ -263,7 +263,8 @@ impl Eval for Expr {
Self::Group(ref v) => v.eval(ctx), Self::Group(ref v) => v.eval(ctx),
Self::Block(ref v) => v.eval(ctx), Self::Block(ref v) => v.eval(ctx),
Self::Call(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::Unary(ref v) => v.eval(ctx),
Self::Binary(ref v) => v.eval(ctx), Self::Binary(ref v) => v.eval(ctx),
Self::Let(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 { fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
match self { match self {
Self::Pos(expr) => FuncArg { Self::Pos(expr) => FuncArg {
span: self.span(),
name: None, name: None,
value: Spanned::new(expr.eval(ctx), expr.span()), value: Spanned::new(expr.eval(ctx), expr.span()),
}, },
Self::Named(Named { name, expr }) => FuncArg { 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()), value: Spanned::new(expr.eval(ctx), expr.span()),
}, },
} }
@ -510,7 +513,7 @@ impl Eval for CallArg {
} }
impl Eval for ClosureExpr { impl Eval for ClosureExpr {
type Output = FuncValue; type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> Self::Output { fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
let params = Rc::clone(&self.params); 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()); 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 // Don't leak the scopes from the call site. Instead, we use the
// scope of captured variables we collected earlier. // scope of captured variables we collected earlier.
let prev = mem::take(&mut ctx.scopes); let prev = mem::take(&mut ctx.scopes);
@ -539,7 +542,40 @@ impl Eval for ClosureExpr {
let value = body.eval(ctx); let value = body.eval(ctx);
ctx.scopes = prev; ctx.scopes = prev;
value 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::<FuncValue>(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
}
} }
} }

View File

@ -274,7 +274,7 @@ impl Debug for FuncValue {
pub struct FuncArgs { pub struct FuncArgs {
/// The span of the whole argument list. /// The span of the whole argument list.
pub span: Span, pub span: Span,
/// The arguments. /// The positional arguments.
pub items: Vec<FuncArg>, pub items: Vec<FuncArg>,
} }
@ -346,7 +346,7 @@ impl FuncArgs {
let index = self let index = self
.items .items
.iter() .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 value = self.items.remove(index).value;
let span = value.span; let span = value.span;
@ -373,7 +373,7 @@ impl FuncArgs {
pub fn finish(self, ctx: &mut EvalContext) { pub fn finish(self, ctx: &mut EvalContext) {
for arg in &self.items { for arg in &self.items {
if arg.value.v != Value::Error { 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`. /// An argument to a function call: `12` or `draw: false`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FuncArg { pub struct FuncArg {
/// The span of the whole argument.
pub span: Span,
/// The name of the argument (`None` for positional arguments). /// The name of the argument (`None` for positional arguments).
pub name: Option<Spanned<String>>, pub name: Option<String>,
/// The value of the argument. /// The value of the argument.
pub value: Spanned<Value>, pub value: Spanned<Value>,
} }
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. /// A wrapper around a dynamic value.
pub struct AnyValue(Box<dyn Bounds>); pub struct AnyValue(Box<dyn Bounds>);

View File

@ -245,6 +245,10 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option<Expr> {
continue; continue;
} }
if p.eat_if(Token::With) {
lhs = with_expr(p, lhs);
}
if atomic { if atomic {
break; break;
} }
@ -545,6 +549,19 @@ fn args(p: &mut Parser) -> CallArgs {
CallArgs { span: p.span(start), items } 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. /// Parse a let expression.
fn let_expr(p: &mut Parser) -> Option<Expr> { fn let_expr(p: &mut Parser) -> Option<Expr> {
let start = p.next_start(); let start = p.next_start();
@ -552,32 +569,37 @@ fn let_expr(p: &mut Parser) -> Option<Expr> {
let mut let_expr = None; let mut let_expr = None;
if let Some(binding) = ident(p) { 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; 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 p.eat_if(Token::With) {
if let Some(params) = params { init = Some(with_expr(p, Expr::Ident(binding.clone())));
let body = init?; } else {
init = Some(Expr::Closure(ClosureExpr { // If a parenthesis follows, this is a function definition.
span: binding.span.join(body.span()), let mut params = None;
name: Some(binding.clone()), if p.peek_direct() == Some(Token::LeftParen) {
params: Rc::new(params), p.start_group(Group::Paren, TokenMode::Code);
body: Rc::new(body), 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 { let_expr = Some(Expr::Let(LetExpr {

View File

@ -366,9 +366,6 @@ impl<'s> Tokens<'s> {
fn ident(&mut self, start: usize) -> Token<'s> { fn ident(&mut self, start: usize) -> Token<'s> {
self.s.eat_while(is_id_continue); self.s.eat_while(is_id_continue);
match self.s.eaten_from(start) { match self.s.eaten_from(start) {
"not" => Token::Not,
"and" => Token::And,
"or" => Token::Or,
"none" => Token::None, "none" => Token::None,
"auto" => Token::Auto, "auto" => Token::Auto,
"true" => Token::Bool(true), "true" => Token::Bool(true),
@ -489,6 +486,10 @@ impl Debug for Tokens<'_> {
fn keyword(id: &str) -> Option<Token<'static>> { fn keyword(id: &str) -> Option<Token<'static>> {
Some(match id { Some(match id {
"not" => Token::Not,
"and" => Token::And,
"or" => Token::Or,
"with" => Token::With,
"let" => Token::Let, "let" => Token::Let,
"if" => Token::If, "if" => Token::If,
"else" => Token::Else, "else" => Token::Else,
@ -777,6 +778,7 @@ mod tests {
fn test_tokenize_keywords() { fn test_tokenize_keywords() {
// A list of a few (not all) keywords. // A list of a few (not all) keywords.
let list = [ let list = [
("not", Not),
("let", Let), ("let", Let),
("if", If), ("if", If),
("else", Else), ("else", Else),

View File

@ -217,6 +217,7 @@ impl Pretty for Expr {
Self::Binary(v) => v.pretty(p), Self::Binary(v) => v.pretty(p),
Self::Call(v) => v.pretty(p), Self::Call(v) => v.pretty(p),
Self::Closure(v) => v.pretty(p), Self::Closure(v) => v.pretty(p),
Self::With(v) => v.pretty(p),
Self::Let(v) => v.pretty(p), Self::Let(v) => v.pretty(p),
Self::If(v) => v.pretty(p), Self::If(v) => v.pretty(p),
Self::While(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 { impl Pretty for LetExpr {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
p.push_str("let "); p.push_str("let ");
@ -539,7 +549,7 @@ impl Pretty for FuncArgs {
impl Pretty for FuncArg { impl Pretty for FuncArg {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
if let Some(name) = &self.name { if let Some(name) = &self.name {
p.push_str(&name.v); p.push_str(&name);
p.push_str(": "); p.push_str(": ");
} }
self.value.v.pretty(p); self.value.v.pretty(p);

View File

@ -52,6 +52,8 @@ pub enum Expr {
Call(CallExpr), Call(CallExpr),
/// A closure expression: `(x, y) => z`. /// A closure expression: `(x, y) => z`.
Closure(ClosureExpr), Closure(ClosureExpr),
/// A with expression: `f with (x, y: 1)`.
With(WithExpr),
/// A let expression: `let x = 1`. /// A let expression: `let x = 1`.
Let(LetExpr), Let(LetExpr),
/// An if-else expression: `if x { y } else { z }`. /// An if-else expression: `if x { y } else { z }`.
@ -91,6 +93,7 @@ impl Expr {
Self::Binary(ref v) => v.span, Self::Binary(ref v) => v.span,
Self::Call(ref v) => v.span, Self::Call(ref v) => v.span,
Self::Closure(ref v) => v.span, Self::Closure(ref v) => v.span,
Self::With(ref v) => v.span,
Self::Let(ref v) => v.span, Self::Let(ref v) => v.span,
Self::If(ref v) => v.span, Self::If(ref v) => v.span,
Self::While(ref v) => v.span, Self::While(ref v) => v.span,
@ -383,7 +386,7 @@ pub enum Associativity {
pub struct CallExpr { pub struct CallExpr {
/// The source code location. /// The source code location.
pub span: Span, pub span: Span,
/// The callee of the function. /// The function to call.
pub callee: Box<Expr>, pub callee: Box<Expr>,
/// The arguments to the function. /// The arguments to the function.
pub args: CallArgs, pub args: CallArgs,
@ -435,6 +438,19 @@ pub struct ClosureExpr {
pub body: Rc<Expr>, pub body: Rc<Expr>,
} }
/// 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<Expr>,
/// The arguments to apply to the function.
pub args: CallArgs,
}
/// A let expression: `let x = 1`. /// A let expression: `let x = 1`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LetExpr { pub struct LetExpr {

View File

@ -74,6 +74,8 @@ pub enum Token<'s> {
And, And,
/// The `or` operator. /// The `or` operator.
Or, Or,
/// The `with` operator.
With,
/// The none literal: `none`. /// The none literal: `none`.
None, None,
/// The auto literal: `auto`. /// The auto literal: `auto`.
@ -241,6 +243,7 @@ impl<'s> Token<'s> {
Self::Not => "operator `not`", Self::Not => "operator `not`",
Self::And => "operator `and`", Self::And => "operator `and`",
Self::Or => "operator `or`", Self::Or => "operator `or`",
Self::With => "operator `with`",
Self::None => "`none`", Self::None => "`none`",
Self::Auto => "`auto`", Self::Auto => "`auto`",
Self::Let => "keyword `let`", Self::Let => "keyword `let`",

View File

@ -99,6 +99,7 @@ visit! {
Expr::Binary(e) => v.visit_binary(e), Expr::Binary(e) => v.visit_binary(e),
Expr::Call(e) => v.visit_call(e), Expr::Call(e) => v.visit_call(e),
Expr::Closure(e) => v.visit_closure(e), Expr::Closure(e) => v.visit_closure(e),
Expr::With(e) => v.visit_with(e),
Expr::Let(e) => v.visit_let(e), Expr::Let(e) => v.visit_let(e),
Expr::If(e) => v.visit_if(e), Expr::If(e) => v.visit_if(e),
Expr::While(e) => v.visit_while(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) { fn visit_let(v, node: &LetExpr) {
v.visit_binding(&node.binding); v.visit_binding(&node.binding);
if let Some(init) = &node.init { if let Some(init) = &node.init {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -145,3 +145,21 @@
{ x /= 2.0 } #test(x, 18.0) { x /= 2.0 } #test(x, 18.0)
{ x = "some" } #test(x, "some") { x = "some" } #test(x, "some")
{ x += "thing" } #test(x, "something") { 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]