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::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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>);
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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),
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
@ -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`",
|
||||||
|
@ -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 |
@ -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]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user