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::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::<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 {
/// The span of the whole argument list.
pub span: Span,
/// The arguments.
/// The positional arguments.
pub items: Vec<FuncArg>,
}
@ -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<Spanned<String>>,
pub name: Option<String>,
/// The value of the argument.
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.
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;
}
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<Expr> {
let start = p.next_start();
@ -552,32 +569,37 @@ fn let_expr(p: &mut Parser) -> Option<Expr> {
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 {

View File

@ -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<Token<'static>> {
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),

View File

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

View File

@ -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<Expr>,
/// The arguments to the function.
pub args: CallArgs,
@ -435,6 +438,19 @@ pub struct ClosureExpr {
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`.
#[derive(Debug, Clone, PartialEq)]
pub struct LetExpr {

View File

@ -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`",

View File

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

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 = "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]