Callee expressions 🍅

This commit is contained in:
Laurenz 2021-03-03 22:33:00 +01:00
parent 1cfc3c72b5
commit 193734f453
5 changed files with 124 additions and 81 deletions

View File

@ -75,7 +75,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
let group = if stmt { Group::Stmt } else { Group::Expr };
p.start_group(group, TokenMode::Code);
let expr = primary(p);
let expr = expr_with(p, true, 0);
if stmt && expr.is_some() && !p.eof() {
p.expected_at("semicolon or line break", p.end());
}
@ -166,8 +166,73 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
text
}
/// Parse an expression.
fn expr(p: &mut Parser) -> Option<Expr> {
expr_with(p, false, 0)
}
/// Parse an expression with operators having at least the minimum precedence.
///
/// If `atomic` is true, this does not parse binary operations and arrow
/// functions, which is exactly what we want in a shorthand expression directly
/// in markup.
///
/// Stops parsing at operations with lower precedence than `min_prec`,
fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option<Expr> {
let start = p.start();
let mut lhs = match p.eat_map(UnOp::from_token) {
Some(op) => {
let prec = op.precedence();
let expr = Box::new(expr_with(p, atomic, prec)?);
Expr::Unary(ExprUnary { span: p.span(start), op, expr })
}
None => primary(p, atomic)?,
};
loop {
// Parenthesis or bracket means this is a function call.
if matches!(
p.peek_direct(),
Some(Token::LeftParen) | Some(Token::LeftBracket),
) {
lhs = call(p, lhs);
continue;
}
if atomic {
break;
}
let op = match p.peek().and_then(BinOp::from_token) {
Some(binop) => binop,
None => break,
};
let mut prec = op.precedence();
if prec < min_prec {
break;
}
p.eat();
match op.associativity() {
Associativity::Left => prec += 1,
Associativity::Right => {}
}
let rhs = match expr_with(p, atomic, prec) {
Some(rhs) => Box::new(rhs),
None => break,
};
let span = lhs.span().join(rhs.span());
lhs = Expr::Binary(ExprBinary { span, lhs: Box::new(lhs), op, rhs });
}
Some(lhs)
}
/// Parse a primary expression.
fn primary(p: &mut Parser) -> Option<Expr> {
fn primary(p: &mut Parser, atomic: bool) -> Option<Expr> {
if let Some(expr) = literal(p) {
return Some(expr);
}
@ -175,31 +240,23 @@ fn primary(p: &mut Parser) -> Option<Expr> {
match p.peek() {
// Things that start with an identifier.
Some(Token::Ident(string)) => {
let ident = Ident {
let id = Ident {
span: p.eat_span(),
string: string.into(),
};
// Parenthesis or bracket means this is a function call.
if matches!(
p.peek_direct(),
Some(Token::LeftParen) | Some(Token::LeftBracket),
) {
return Some(call(p, ident));
}
// Arrow means this is closure's lone parameter.
if p.eat_if(Token::Arrow) {
// Arrow means this is a closure's lone parameter.
Some(if !atomic && p.eat_if(Token::Arrow) {
let body = expr(p)?;
return Some(Expr::Closure(ExprClosure {
span: ident.span.join(body.span()),
Expr::Closure(ExprClosure {
span: id.span.join(body.span()),
name: None,
params: Rc::new(vec![ident]),
params: Rc::new(vec![id]),
body: Rc::new(body),
}));
}
Some(Expr::Ident(ident))
})
} else {
Expr::Ident(id)
})
}
// Structures.
@ -260,7 +317,7 @@ pub fn parenthesized(p: &mut Parser) -> Option<Expr> {
return Some(dict(p, items, span));
}
// Arrow means this is closure's parameter list.
// Arrow means this is a closure's parameter list.
if p.eat_if(Token::Arrow) {
let params = params(p, items);
let body = expr(p)?;
@ -400,54 +457,8 @@ fn block(p: &mut Parser, scoping: bool) -> Expr {
Expr::Block(ExprBlock { span, exprs, scoping })
}
/// Parse an expression.
fn expr(p: &mut Parser) -> Option<Expr> {
expr_with(p, 0)
}
/// Parse an expression with operators having at least the minimum precedence.
fn expr_with(p: &mut Parser, min_prec: usize) -> Option<Expr> {
let start = p.start();
let mut lhs = match p.eat_map(UnOp::from_token) {
Some(op) => {
let prec = op.precedence();
let expr = Box::new(expr_with(p, prec)?);
Expr::Unary(ExprUnary { span: p.span(start), op, expr })
}
None => primary(p)?,
};
loop {
let op = match p.peek().and_then(BinOp::from_token) {
Some(binop) => binop,
None => break,
};
let mut prec = op.precedence();
if prec < min_prec {
break;
}
p.eat();
match op.associativity() {
Associativity::Left => prec += 1,
Associativity::Right => {}
}
let rhs = match expr_with(p, prec) {
Some(rhs) => Box::new(rhs),
None => break,
};
let span = lhs.span().join(rhs.span());
lhs = Expr::Binary(ExprBinary { span, lhs: Box::new(lhs), op, rhs });
}
Some(lhs)
}
/// Parse a function call.
fn call(p: &mut Parser, name: Ident) -> Expr {
fn call(p: &mut Parser, callee: Expr) -> Expr {
let mut args = match p.peek_direct() {
Some(Token::LeftParen) => {
p.start_group(Group::Paren, TokenMode::Code);
@ -456,7 +467,7 @@ fn call(p: &mut Parser, name: Ident) -> Expr {
args
}
_ => ExprArgs {
span: Span::at(name.span.end),
span: Span::at(callee.span().end),
items: vec![],
},
};
@ -467,8 +478,8 @@ fn call(p: &mut Parser, name: Ident) -> Expr {
}
Expr::Call(ExprCall {
span: p.span(name.span.start),
callee: Box::new(Expr::Ident(name)),
span: p.span(callee.span().start),
callee: Box::new(callee),
args,
})
}

View File

@ -726,6 +726,7 @@ mod tests {
// Function calls.
roundtrip("{v()}");
roundtrip("{v()()}");
roundtrip("{v(1)}");
roundtrip("{v(a: 1, b)}");
roundtrip("#v()");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -13,6 +13,45 @@
// Mixed arguments.
{args(1, b: "2", 3)}
// Should output `() + 2`.
#args() + 2
---
// Ref: false
// Call function assigned to variable.
#let alias = type
#test(alias(alias), "function")
// Library function `font` returns template.
#test(type(font(12pt)), "template")
---
// Callee expressions.
{
// Error: 5-9 expected function, found boolean
true()
// Wrapped in parens.
test((type)("hi"), "string")
// Call the return value of a function.
let adder(dx) = x => x + dx
test(adder(2)(5), 7)
}
#let f(x, body) = (y) => {
[{x}] + body + [{y}]
}
// Call return value of function with body.
#f(1)[2](3)
// Don't allow this to be a closure.
// Should output `x => "hi"`.
#let x = "x"
#x => "hi"
---
// Different forms of template arguments.
// Ref: true
@ -30,11 +69,3 @@
// Should output `<function args> (Okay.)`.
#args (Okay.)
---
// Call function assigned to variable.
#let alias = type
#test(alias(alias), "function")
// Library function `font` returns template.
#test(type(font(12pt)), "template")

View File

@ -12,8 +12,8 @@ A#let x = 3; B #test(x, 3) \
---
// Spacing around if-else.
A#if true[B]C \
A#if true[B] C \
A#if true [B]C \
A#if true [B] C \
A #if true{"B"}C \
A #if true{"B"} C \
A#if false [] #else [B]C \