diff --git a/src/parse/mod.rs b/src/parse/mod.rs index afd851930..f23f19785 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -75,7 +75,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { 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_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 { + 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 { +fn primary(p: &mut Parser, atomic: bool) -> Option { if let Some(expr) = literal(p) { return Some(expr); } @@ -175,31 +240,23 @@ fn primary(p: &mut Parser) -> Option { 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 { 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_with(p, 0) -} - -/// Parse an expression with operators having at least the minimum precedence. -fn expr_with(p: &mut Parser, min_prec: usize) -> Option { - 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, }) } diff --git a/src/pretty.rs b/src/pretty.rs index 3f4205486..963157c02 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -726,6 +726,7 @@ mod tests { // Function calls. roundtrip("{v()}"); + roundtrip("{v()()}"); roundtrip("{v(1)}"); roundtrip("{v(a: 1, b)}"); roundtrip("#v()"); diff --git a/tests/ref/expr/call.png b/tests/ref/expr/call.png index b80869003..39941778b 100644 Binary files a/tests/ref/expr/call.png and b/tests/ref/expr/call.png differ diff --git a/tests/typ/expr/call.typ b/tests/typ/expr/call.typ index 213d55541..358322d12 100644 --- a/tests/typ/expr/call.typ +++ b/tests/typ/expr/call.typ @@ -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 ` (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") diff --git a/tests/typ/spacing.typ b/tests/typ/spacing.typ index 77dac53cc..ccaf084a2 100644 --- a/tests/typ/spacing.typ +++ b/tests/typ/spacing.typ @@ -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 \