Wide calls

This commit is contained in:
Laurenz 2021-06-29 13:08:16 +02:00
parent 6d0911d7a8
commit b89cd128ae
7 changed files with 129 additions and 28 deletions

View File

@ -15,6 +15,7 @@ pub use tokens::*;
use std::rc::Rc; use std::rc::Rc;
use crate::diag::Pass; use crate::diag::Pass;
use crate::syntax::visit::{mutable::visit_expr, VisitMut};
use crate::syntax::*; use crate::syntax::*;
/// Parse a string of source code. /// Parse a string of source code.
@ -25,7 +26,7 @@ pub fn parse(src: &str) -> Pass<Tree> {
/// Parse a syntax tree. /// Parse a syntax tree.
fn tree(p: &mut Parser) -> 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 /// 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()); 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, Some(Token::Space(n)) if n >= 1 => p.column(p.next_end()) >= column,
_ => true, _ => true,
}) })
} }
/// Parse a syntax tree. /// Parse a syntax tree.
fn tree_while( fn tree_while<F>(p: &mut Parser, mut at_start: bool, f: &mut F) -> Tree
p: &mut Parser, where
mut at_start: bool, F: FnMut(&mut Parser) -> bool,
mut f: impl FnMut(&mut Parser) -> bool, {
) -> Tree { /// 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 // 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. // or template to know whether things like headings are allowed.
let mut tree = vec![]; let mut tree = vec![];
while !p.eof() && f(p) { while !p.eof() && f(p) {
if let Some(node) = node(p, &mut at_start) { if let Some(mut node) = node(p, &mut at_start) {
match node { at_start &= matches!(node, Node::Space | Node::Parbreak(_));
Node::Space => {} if let Node::Expr(expr) = &mut node {
Node::Parbreak(_) => {} let mut visitor = WideVisitor { p, f, found: false };
_ => at_start = false, visitor.visit_expr(expr);
} }
tree.push(node); tree.push(node);
} }
} }
tree tree
} }
@ -236,12 +276,13 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option<Expr> {
}; };
loop { loop {
// Parenthesis or bracket means this is a function call. // Exclamation mark, parenthesis or bracket means this is a function
// call.
if matches!( if matches!(
p.peek_direct(), 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; continue;
} }
@ -516,7 +557,9 @@ fn block(p: &mut Parser, scoping: bool) -> Expr {
} }
/// Parse a function call. /// Parse a function call.
fn call(p: &mut Parser, callee: Expr) -> Expr { fn call(p: &mut Parser, callee: Expr) -> Option<Expr> {
let wide = p.eat_if(Token::Excl);
let mut args = match p.peek_direct() { let mut args = match p.peek_direct() {
Some(Token::LeftParen) => { Some(Token::LeftParen) => {
p.start_group(Group::Paren, TokenMode::Code); p.start_group(Group::Paren, TokenMode::Code);
@ -524,10 +567,14 @@ fn call(p: &mut Parser, callee: Expr) -> Expr {
p.end_group(); p.end_group();
args args
} }
_ => CallArgs { Some(Token::LeftBracket) => CallArgs {
span: Span::at(callee.span().end), span: Span::at(callee.span().end),
items: vec![], items: vec![],
}, },
_ => {
p.expected_at("argument list", p.prev_end());
return None;
}
}; };
if p.peek_direct() == Some(Token::LeftBracket) { 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)); args.items.push(CallArg::Pos(body));
} }
Expr::Call(CallExpr { Some(Expr::Call(CallExpr {
span: p.span(callee.span().start), span: p.span(callee.span().start),
callee: Box::new(callee), callee: Box::new(callee),
wide,
args, args,
}) }))
} }
/// Parse the arguments to a function call. /// Parse the arguments to a function call.

View File

@ -117,7 +117,7 @@ impl<'s> Tokens<'s> {
// Length two. // Length two.
'=' if self.s.eat_if('=') => Token::EqEq, '=' 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::LtEq,
'>' if self.s.eat_if('=') => Token::GtEq, '>' if self.s.eat_if('=') => Token::GtEq,
'+' if self.s.eat_if('=') => Token::PlusEq, '+' if self.s.eat_if('=') => Token::PlusEq,
@ -135,6 +135,7 @@ impl<'s> Tokens<'s> {
'-' => Token::Hyph, '-' => Token::Hyph,
'*' => Token::Star, '*' => Token::Star,
'/' => Token::Slash, '/' => Token::Slash,
'!' => Token::Excl,
'=' => Token::Eq, '=' => Token::Eq,
'<' => Token::Lt, '<' => Token::Lt,
'>' => Token::Gt, '>' => Token::Gt,
@ -750,7 +751,7 @@ mod tests {
t!(Code[" a1"]: "/" => Slash); t!(Code[" a1"]: "/" => Slash);
t!(Code: "=" => Eq); t!(Code: "=" => Eq);
t!(Code: "==" => EqEq); t!(Code: "==" => EqEq);
t!(Code: "!=" => BangEq); t!(Code: "!=" => ExclEq);
t!(Code: "<" => Lt); t!(Code: "<" => Lt);
t!(Code: "<=" => LtEq); t!(Code: "<=" => LtEq);
t!(Code: ">" => Gt); t!(Code: ">" => Gt);

View File

@ -295,7 +295,7 @@ impl BinOp {
Token::And => Self::And, Token::And => Self::And,
Token::Or => Self::Or, Token::Or => Self::Or,
Token::EqEq => Self::Eq, Token::EqEq => Self::Eq,
Token::BangEq => Self::Neq, Token::ExclEq => Self::Neq,
Token::Lt => Self::Lt, Token::Lt => Self::Lt,
Token::LtEq => Self::Leq, Token::LtEq => Self::Leq,
Token::Gt => Self::Gt, Token::Gt => Self::Gt,
@ -388,6 +388,8 @@ pub struct CallExpr {
pub span: Span, pub span: Span,
/// The function to call. /// The function to call.
pub callee: Box<Expr>, pub callee: Box<Expr>,
/// Whether the call is wide, that is, capturing the template behind it.
pub wide: bool,
/// The arguments to the function. /// The arguments to the function.
pub args: CallArgs, pub args: CallArgs,
} }

View File

@ -42,12 +42,14 @@ pub enum Token<'s> {
Hyph, Hyph,
/// A slash: `/`. /// A slash: `/`.
Slash, Slash,
/// An exlamation mark.
Excl,
/// A single equals sign: `=`. /// A single equals sign: `=`.
Eq, Eq,
/// Two equals signs: `==`. /// Two equals signs: `==`.
EqEq, EqEq,
/// An exclamation mark followed by an equals sign: `!=`. /// An exclamation mark followed by an equals sign: `!=`.
BangEq, ExclEq,
/// A less-than sign: `<`. /// A less-than sign: `<`.
Lt, Lt,
/// A less-than sign followed by an equals sign: `<=`. /// A less-than sign followed by an equals sign: `<=`.
@ -227,9 +229,10 @@ impl<'s> Token<'s> {
Self::Plus => "plus", Self::Plus => "plus",
Self::Hyph => "minus", Self::Hyph => "minus",
Self::Slash => "slash", Self::Slash => "slash",
Self::Excl => "exclamation mark",
Self::Eq => "assignment operator", Self::Eq => "assignment operator",
Self::EqEq => "equality operator", Self::EqEq => "equality operator",
Self::BangEq => "inequality operator", Self::ExclEq => "inequality operator",
Self::Lt => "less-than operator", Self::Lt => "less-than operator",
Self::LtEq => "less-than or equal operator", Self::LtEq => "less-than or equal operator",
Self::Gt => "greater-than operator", Self::Gt => "greater-than operator",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -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, <template>)")
---
// Test multiple wide calls in one expression.
// Ref: false
#let id(x) = x
#let add(x, y) = x + y
// Error: 11-13 duplicate wide call
[{id!() + id!()}]
// Test nested wide calls.
// Error: 2-6 duplicate wide call
[#add!(id!())]
---
// Test missing parentheses.
// Ref: false
// Error: 4 expected argument list
#f!

View File

@ -171,12 +171,12 @@
{ {
"comment": "Function name", "comment": "Function name",
"name": "entity.name.function.typst", "name": "entity.name.function.typst",
"match": "((#)[[:alpha:]_][[:alnum:]_-]*)(?=\\[|\\()", "match": "((#)[[:alpha:]_][[:alnum:]_-]*!?)(?=\\[|\\()",
"captures": { "2": { "name": "punctuation.definition.function.typst" } } "captures": { "2": { "name": "punctuation.definition.function.typst" } }
}, },
{ {
"comment": "Function arguments", "comment": "Function arguments",
"begin": "(?<=#[[:alpha:]_][[:alnum:]_-]*)\\(", "begin": "(?<=#[[:alpha:]_][[:alnum:]_-]*!?)\\(",
"end": "\\)", "end": "\\)",
"captures": { "0": { "name": "punctuation.definition.group.typst" } }, "captures": { "0": { "name": "punctuation.definition.group.typst" } },
"patterns": [{ "include": "#arguments" }] "patterns": [{ "include": "#arguments" }]
@ -231,11 +231,11 @@
{ {
"comment": "Function name", "comment": "Function name",
"name": "entity.name.function.typst", "name": "entity.name.function.typst",
"match": "\\b[[:alpha:]_][[:alnum:]_-]*(?=\\[|\\(|\\s+\\bwith\\b)" "match": "\\b[[:alpha:]_][[:alnum:]_-]*!?(?=(\\[|\\()|\\s+\\bwith\\b)"
}, },
{ {
"comment": "Function arguments", "comment": "Function arguments",
"begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*|\\bwith\\b\\s+)\\(", "begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*!?|\\bwith\\b\\s+)\\(",
"end": "\\)", "end": "\\)",
"captures": { "0": { "name": "punctuation.definition.group.typst" } }, "captures": { "0": { "name": "punctuation.definition.group.typst" } },
"patterns": [{ "include": "#arguments" }] "patterns": [{ "include": "#arguments" }]