diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 381d44e2f..e8e168033 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -15,6 +15,7 @@ pub use tokens::*; use std::rc::Rc; use crate::diag::Pass; +use crate::syntax::visit::{mutable::visit_expr, VisitMut}; use crate::syntax::*; /// Parse a string of source code. @@ -25,7 +26,7 @@ pub fn parse(src: &str) -> Pass { /// Parse a syntax tree. fn tree(p: &mut Parser) -> Tree { - tree_while(p, true, |_| true) + tree_while(p, true, &mut |_| true) } /// Parse a syntax tree that stays right of the column at the start of the next @@ -38,31 +39,70 @@ fn tree_indented(p: &mut Parser) -> Tree { }); let column = p.column(p.next_start()); - tree_while(p, false, |p| match p.peek() { + tree_while(p, false, &mut |p| match p.peek() { Some(Token::Space(n)) if n >= 1 => p.column(p.next_end()) >= column, _ => true, }) } /// Parse a syntax tree. -fn tree_while( - p: &mut Parser, - mut at_start: bool, - mut f: impl FnMut(&mut Parser) -> bool, -) -> Tree { +fn tree_while(p: &mut Parser, mut at_start: bool, f: &mut F) -> Tree +where + F: FnMut(&mut Parser) -> bool, +{ + /// Visitor that adds a recursively parsed rest template to the first wide + /// call's argument list and diagnoses all following wide calls. + struct WideVisitor<'a, 's, F> { + p: &'a mut Parser<'s>, + f: &'a mut F, + found: bool, + } + + impl<'ast, 'a, 's, F> VisitMut<'ast> for WideVisitor<'a, 's, F> + where + F: FnMut(&mut Parser) -> bool, + { + fn visit_expr(&mut self, node: &'ast mut Expr) { + visit_expr(self, node); + + if let Expr::Call(call) = node { + if call.wide { + let start = self.p.next_start(); + let tree = if !self.found { + tree_while(self.p, true, self.f) + } else { + self.p.diag(error!(call.callee.span(), "duplicate wide call")); + Tree::default() + }; + + call.args.items.push(CallArg::Pos(Expr::Template(TemplateExpr { + span: self.p.span(start), + tree: Rc::new(tree), + }))); + + self.found = true; + } + } + } + + // Don't recurse into templates. + fn visit_template(&mut self, _: &'ast mut TemplateExpr) {} + } + // We use `at_start` to keep track of whether we are at the start of a line // or template to know whether things like headings are allowed. let mut tree = vec![]; while !p.eof() && f(p) { - if let Some(node) = node(p, &mut at_start) { - match node { - Node::Space => {} - Node::Parbreak(_) => {} - _ => at_start = false, + if let Some(mut node) = node(p, &mut at_start) { + at_start &= matches!(node, Node::Space | Node::Parbreak(_)); + if let Node::Expr(expr) = &mut node { + let mut visitor = WideVisitor { p, f, found: false }; + visitor.visit_expr(expr); } tree.push(node); } } + tree } @@ -236,12 +276,13 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option { }; loop { - // Parenthesis or bracket means this is a function call. + // Exclamation mark, parenthesis or bracket means this is a function + // call. if matches!( p.peek_direct(), - Some(Token::LeftParen) | Some(Token::LeftBracket), + Some(Token::Excl) | Some(Token::LeftParen) | Some(Token::LeftBracket), ) { - lhs = call(p, lhs); + lhs = call(p, lhs)?; continue; } @@ -516,7 +557,9 @@ fn block(p: &mut Parser, scoping: bool) -> Expr { } /// Parse a function call. -fn call(p: &mut Parser, callee: Expr) -> Expr { +fn call(p: &mut Parser, callee: Expr) -> Option { + let wide = p.eat_if(Token::Excl); + let mut args = match p.peek_direct() { Some(Token::LeftParen) => { p.start_group(Group::Paren, TokenMode::Code); @@ -524,10 +567,14 @@ fn call(p: &mut Parser, callee: Expr) -> Expr { p.end_group(); args } - _ => CallArgs { + Some(Token::LeftBracket) => CallArgs { span: Span::at(callee.span().end), items: vec![], }, + _ => { + p.expected_at("argument list", p.prev_end()); + return None; + } }; if p.peek_direct() == Some(Token::LeftBracket) { @@ -535,11 +582,12 @@ fn call(p: &mut Parser, callee: Expr) -> Expr { args.items.push(CallArg::Pos(body)); } - Expr::Call(CallExpr { + Some(Expr::Call(CallExpr { span: p.span(callee.span().start), callee: Box::new(callee), + wide, args, - }) + })) } /// Parse the arguments to a function call. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index abc3d6a6c..4d90dded6 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -117,7 +117,7 @@ impl<'s> Tokens<'s> { // Length two. '=' if self.s.eat_if('=') => Token::EqEq, - '!' if self.s.eat_if('=') => Token::BangEq, + '!' if self.s.eat_if('=') => Token::ExclEq, '<' if self.s.eat_if('=') => Token::LtEq, '>' if self.s.eat_if('=') => Token::GtEq, '+' if self.s.eat_if('=') => Token::PlusEq, @@ -135,6 +135,7 @@ impl<'s> Tokens<'s> { '-' => Token::Hyph, '*' => Token::Star, '/' => Token::Slash, + '!' => Token::Excl, '=' => Token::Eq, '<' => Token::Lt, '>' => Token::Gt, @@ -750,7 +751,7 @@ mod tests { t!(Code[" a1"]: "/" => Slash); t!(Code: "=" => Eq); t!(Code: "==" => EqEq); - t!(Code: "!=" => BangEq); + t!(Code: "!=" => ExclEq); t!(Code: "<" => Lt); t!(Code: "<=" => LtEq); t!(Code: ">" => Gt); diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 62f023990..aabff1eaf 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -295,7 +295,7 @@ impl BinOp { Token::And => Self::And, Token::Or => Self::Or, Token::EqEq => Self::Eq, - Token::BangEq => Self::Neq, + Token::ExclEq => Self::Neq, Token::Lt => Self::Lt, Token::LtEq => Self::Leq, Token::Gt => Self::Gt, @@ -388,6 +388,8 @@ pub struct CallExpr { pub span: Span, /// The function to call. pub callee: Box, + /// Whether the call is wide, that is, capturing the template behind it. + pub wide: bool, /// The arguments to the function. pub args: CallArgs, } diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 3f07bb338..250622643 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -42,12 +42,14 @@ pub enum Token<'s> { Hyph, /// A slash: `/`. Slash, + /// An exlamation mark. + Excl, /// A single equals sign: `=`. Eq, /// Two equals signs: `==`. EqEq, /// An exclamation mark followed by an equals sign: `!=`. - BangEq, + ExclEq, /// A less-than sign: `<`. Lt, /// A less-than sign followed by an equals sign: `<=`. @@ -227,9 +229,10 @@ impl<'s> Token<'s> { Self::Plus => "plus", Self::Hyph => "minus", Self::Slash => "slash", + Self::Excl => "exclamation mark", Self::Eq => "assignment operator", Self::EqEq => "equality operator", - Self::BangEq => "inequality operator", + Self::ExclEq => "inequality operator", Self::Lt => "less-than operator", Self::LtEq => "less-than or equal operator", Self::Gt => "greater-than operator", diff --git a/tests/ref/code/call-wide.png b/tests/ref/code/call-wide.png new file mode 100644 index 000000000..028d7aa4e Binary files /dev/null and b/tests/ref/code/call-wide.png differ diff --git a/tests/typ/code/call-wide.typ b/tests/typ/code/call-wide.typ new file mode 100644 index 000000000..2bb6d82e4 --- /dev/null +++ b/tests/typ/code/call-wide.typ @@ -0,0 +1,47 @@ +// Test wide calls. + +--- +// Test multiple wide calls in separate expressions. +#font!(color: eastern) - First +#font!(color: forest) - Second + +--- +// Test in heading. +# A #align!(right) B +C + +--- +// Test evaluation semantics. +// Ref: false + +#let r +#let x = 1 +#let f(x, body) = (x, body) + +[ + { r = f!(x) } + { x = 2 } +] + +#test(repr(r), "(1,