mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
With expressions
This commit is contained in:
parent
d53c933e4d
commit
422b8e640f
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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`",
|
||||
|
@ -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 |
@ -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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user