mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Wide calls
This commit is contained in:
parent
6d0911d7a8
commit
b89cd128ae
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
BIN
tests/ref/code/call-wide.png
Normal file
BIN
tests/ref/code/call-wide.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
47
tests/typ/code/call-wide.typ
Normal file
47
tests/typ/code/call-wide.typ
Normal 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!
|
@ -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" }]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user