diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-06-30 11:04:53 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-06-30 11:04:53 +0200 |
| commit | 45812b700114a51f0ee21e31f4454cde3729eaf5 (patch) | |
| tree | 435e5a0ade7dc2cbf9199418c618846bb655e957 | |
| parent | 1c43d8af12a8d318fb681713d0e66eb754485589 (diff) | |
| parent | b2fb42cc7019b0269e892c4e39c52557252fecf9 (diff) | |
Merge pull request #35 from typst/wide-calls
Wide calls
32 files changed, 368 insertions, 245 deletions
diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 64275e93..10f7ec83 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use super::{Scope, Scopes, Value}; -use crate::syntax::visit::{visit_expr, Visit}; +use crate::syntax::visit::{immutable::visit_expr, Visit}; use crate::syntax::{Expr, Ident}; /// A visitor that captures variable slots. diff --git a/src/exec/mod.rs b/src/exec/mod.rs index fea6de33..882f1d0b 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -72,10 +72,10 @@ impl Exec for syntax::RawNode { ctx.parbreak(); } - let snapshot = ctx.state.clone(); + let snapshot = Rc::clone(&ctx.state.font); ctx.set_monospace(); ctx.push_text(&self.text); - ctx.state = snapshot; + ctx.state.font = snapshot; if self.block { ctx.parbreak(); @@ -85,16 +85,17 @@ impl Exec for syntax::RawNode { impl ExecWithMap for syntax::HeadingNode { fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { + ctx.parbreak(); + let snapshot = ctx.state.clone(); let font = ctx.state.font_mut(); - let upscale = 1.6 - 0.1 * self.level as f64; font.size *= upscale; font.strong = true; self.body.exec_with_map(ctx, map); - ctx.state = snapshot; + ctx.parbreak(); } } @@ -113,8 +114,6 @@ impl ExecWithMap for syntax::EnumItem { } fn exec_item(ctx: &mut ExecContext, label: String, body: &syntax::Tree, map: &ExprMap) { - ctx.parbreak(); - let label = ctx.exec_stack(|ctx| ctx.push_text(label)); let body = ctx.exec_tree_stack(body, map); let stack = StackNode { @@ -128,7 +127,6 @@ fn exec_item(ctx: &mut ExecContext, label: String, body: &syntax::Tree, map: &Ex }; ctx.push_into_stack(stack); - ctx.parbreak(); } impl Exec for Value { @@ -172,6 +170,8 @@ impl Exec for TemplateNode { impl Exec for TemplateFunc { fn exec(&self, ctx: &mut ExecContext) { + let snapshot = ctx.state.clone(); self(ctx); + ctx.state = snapshot; } } diff --git a/src/library/layout.rs b/src/library/layout.rs index 08924bad..4903077b 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -20,7 +20,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let right = args.named(ctx, "right"); let bottom = args.named(ctx, "bottom"); let flip = args.named(ctx, "flip"); - let body = args.eat::<TemplateValue>(ctx); + let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { let snapshot = ctx.state.clone(); @@ -66,13 +66,10 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } ctx.pagebreak(false, true, span); + body.exec(ctx); - if let Some(body) = &body { - // TODO: Restrict body to a single page? - body.exec(ctx); - ctx.state = snapshot; - ctx.pagebreak(true, false, span); - } + ctx.state = snapshot; + ctx.pagebreak(true, false, span); }) } @@ -111,7 +108,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let second = args.eat::<AlignValue>(ctx); let mut horizontal = args.named::<AlignValue>(ctx, "horizontal"); let mut vertical = args.named::<AlignValue>(ctx, "vertical"); - let body = args.eat::<TemplateValue>(ctx); + let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); for value in first.into_iter().chain(second) { match value.axis() { @@ -126,23 +123,19 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } Value::template(move |ctx| { - let snapshot = ctx.state.clone(); - if let Some(horizontal) = horizontal { ctx.state.aligns.cross = horizontal.to_align(ctx.state.lang.dir); } if let Some(vertical) = vertical { - ctx.state.aligns.main = vertical.to_align(Dir::TTB); - if ctx.state.aligns.main != snapshot.aligns.main { + let new = vertical.to_align(Dir::TTB); + if ctx.state.aligns.main != new { + ctx.state.aligns.main = new; ctx.parbreak(); } } - if let Some(body) = &body { - body.exec(ctx); - ctx.state = snapshot; - } + body.exec(ctx); }) } diff --git a/src/library/text.rs b/src/library/text.rs index 6a2fe9bb..28ca2bd8 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -17,10 +17,9 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let serif = args.named(ctx, "serif"); let sans_serif = args.named(ctx, "sans-serif"); let monospace = args.named(ctx, "monospace"); - let body = args.eat::<TemplateValue>(ctx); + let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { - let snapshot = ctx.state.clone(); let font = ctx.state.font_mut(); if let Some(linear) = size { @@ -67,10 +66,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { font.families_mut().monospace = monospace.clone(); } - if let Some(body) = &body { - body.exec(ctx); - ctx.state = snapshot; - } + body.exec(ctx); }) } @@ -161,6 +157,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let spacing = args.named(ctx, "spacing"); let leading = args.named(ctx, "leading"); let word_spacing = args.named(ctx, "word-spacing"); + let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { if let Some(spacing) = spacing { @@ -176,6 +173,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } ctx.parbreak(); + body.exec(ctx); }) } @@ -190,6 +188,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } None => None, }; + let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { if let Some(dir) = dir.or(iso) { @@ -197,6 +196,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } ctx.parbreak(); + body.exec(ctx); }) } @@ -232,7 +232,7 @@ fn line_impl( let position = args.named(ctx, "position"); let strength = args.named::<Linear>(ctx, "strength"); let extent = args.named(ctx, "extent").unwrap_or_default(); - let body = args.eat::<TemplateValue>(ctx); + let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); // Suppress any existing strikethrough if strength is explicitly zero. let state = strength.map_or(true, |s| !s.is_zero()).then(|| { @@ -245,13 +245,7 @@ fn line_impl( }); Value::template(move |ctx| { - let snapshot = ctx.state.clone(); - *substate(ctx.state.font_mut()) = state.clone(); - - if let Some(body) = &body { - body.exec(ctx); - ctx.state = snapshot; - } + body.exec(ctx); }) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 381d44e2..e8e16803 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<Tree> { /// 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<F>(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<Expr> { }; 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<Expr> { + 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 abc3d6a6..4d90dded 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 62f02399..aabff1ea 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<Expr>, + /// 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 3f07bb33..25062264 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/src/syntax/visit.rs b/src/syntax/visit.rs index 52418361..657b379a 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -1,54 +1,91 @@ -//! Syntax tree traversal. +//! Mutable and immutable syntax tree traversal. -use super::*; +use crate::syntax::*; -macro_rules! visit { - ($(fn $name:ident($v:ident $(, $node:ident: &$ty:ty)?) $body:block)*) => { - /// Traverses the syntax tree. - pub trait Visit<'ast> { - $(fn $name(&mut self $(, $node: &'ast $ty)?) { - $name(self, $($node)?); - })* +/// Implement the immutable and the mutable visitor version. +macro_rules! impl_visitors { + ($($name:ident($($tts:tt)*) $body:block)*) => { + macro_rules! r { + (rc: $x:expr) => { $x.as_ref() }; + ($x:expr) => { &$x }; + } + impl_visitor! { + /// Walk syntax trees immutably. + Visit, + /// Immutable visitor functions. + immutable, + [$(($name($($tts)*) $body))*] + } + + macro_rules! r { + (rc: $x:expr) => { std::rc::Rc::make_mut(&mut $x) }; + ($x:expr) => { &mut $x }; + } + + impl_visitor! { + /// Walk syntax trees mutably. + VisitMut, + /// Mutable visitor functions. + mutable, + [$(($name($($tts)*) $body mut))*] mut + } + }; +} + +/// Implement an immutable or mutable visitor. +macro_rules! impl_visitor { + ( + #[doc = $visit_doc:expr] $visit:ident, + #[doc = $module_doc:expr] $module:ident, + [$(( + $name:ident($v:ident, $node:ident: $ty:ty) + $body:block + $($fmut:tt)? + ))*] + $($mut:tt)? + ) => { + #[doc = $visit_doc] + pub trait $visit<'ast> { /// Visit a definition of a binding. /// /// Bindings are, for example, left-hand side of let expressions, /// and key/value patterns in for loops. - fn visit_binding(&mut self, _: &'ast Ident) {} + fn visit_binding(&mut self, _: &'ast $($mut)? Ident) {} /// Visit the entry into a scope. fn visit_enter(&mut self) {} /// Visit the exit from a scope. fn visit_exit(&mut self) {} - } - $(visit! { - @$(concat!("Walk a node of type [`", stringify!($ty), "`]."), )? - pub fn $name<'ast, V>( - #[allow(unused)] $v: &mut V - $(, #[allow(unused)] $node: &'ast $ty)? - ) - where - V: Visit<'ast> + ?Sized - $body - })* - }; + $(fn $name(&mut self, $node: &'ast $($fmut)? $ty) { + $module::$name(self, $node); + })* + } - (@$doc:expr, $($tts:tt)*) => { - #[doc = $doc] - $($tts)* + #[doc = $module_doc] + pub mod $module { + use super::*; + $( + #[allow(unused_variables)] + pub fn $name<'ast, V>($v: &mut V, $node: &'ast $($fmut)? $ty) + where + V: $visit<'ast> + ?Sized + $body + )* + } }; } -visit! { - fn visit_tree(v, node: &Tree) { - for node in node { - v.visit_node(&node); +impl_visitors! { + visit_tree(v, tree: Tree) { + for node in tree { + v.visit_node(node); } } - fn visit_node(v, node: &Node) { + visit_node(v, node: Node) { match node { Node::Text(_) => {} Node::Space => {} @@ -64,20 +101,20 @@ visit! { } } - fn visit_heading(v, node: &HeadingNode) { - v.visit_tree(&node.body); + visit_heading(v, heading: HeadingNode) { + v.visit_tree(r!(rc: heading.body)); } - fn visit_list(v, node: &ListItem) { - v.visit_tree(&node.body); + visit_list(v, item: ListItem) { + v.visit_tree(r!(item.body)); } - fn visit_enum(v, node: &EnumItem) { - v.visit_tree(&node.body); + visit_enum(v, item: EnumItem) { + v.visit_tree(r!(item.body)); } - fn visit_expr(v, node: &Expr) { - match node { + visit_expr(v, expr: Expr) { + match expr { Expr::None(_) => {} Expr::Auto(_) => {} Expr::Bool(_, _) => {} @@ -109,121 +146,121 @@ visit! { } } - fn visit_array(v, node: &ArrayExpr) { - for expr in &node.items { - v.visit_expr(&expr); + visit_array(v, array: ArrayExpr) { + for expr in r!(array.items) { + v.visit_expr(expr); } } - fn visit_dict(v, node: &DictExpr) { - for named in &node.items { - v.visit_expr(&named.expr); + visit_dict(v, dict: DictExpr) { + for named in r!(dict.items) { + v.visit_expr(r!(named.expr)); } } - fn visit_template(v, node: &TemplateExpr) { + visit_template(v, template: TemplateExpr) { v.visit_enter(); - v.visit_tree(&node.tree); + v.visit_tree(r!(rc: template.tree)); v.visit_exit(); } - fn visit_group(v, node: &GroupExpr) { - v.visit_expr(&node.expr); + visit_group(v, group: GroupExpr) { + v.visit_expr(r!(group.expr)); } - fn visit_block(v, node: &BlockExpr) { - if node.scoping { + visit_block(v, block: BlockExpr) { + if block.scoping { v.visit_enter(); } - for expr in &node.exprs { - v.visit_expr(&expr); + for expr in r!(block.exprs) { + v.visit_expr(expr); } - if node.scoping { + if block.scoping { v.visit_exit(); } } - fn visit_binary(v, node: &BinaryExpr) { - v.visit_expr(&node.lhs); - v.visit_expr(&node.rhs); + visit_binary(v, binary: BinaryExpr) { + v.visit_expr(r!(binary.lhs)); + v.visit_expr(r!(binary.rhs)); } - fn visit_unary(v, node: &UnaryExpr) { - v.visit_expr(&node.expr); + visit_unary(v, unary: UnaryExpr) { + v.visit_expr(r!(unary.expr)); } - fn visit_call(v, node: &CallExpr) { - v.visit_expr(&node.callee); - v.visit_args(&node.args); + visit_call(v, call: CallExpr) { + v.visit_expr(r!(call.callee)); + v.visit_args(r!(call.args)); } - fn visit_closure(v, node: &ClosureExpr) { - for param in node.params.iter() { + visit_closure(v, closure: ClosureExpr) { + for param in r!(rc: closure.params) { v.visit_binding(param); } - v.visit_expr(&node.body); + v.visit_expr(r!(rc: closure.body)); } - fn visit_args(v, node: &CallArgs) { - for arg in &node.items { + visit_args(v, args: CallArgs) { + for arg in r!(args.items) { v.visit_arg(arg); } } - fn visit_arg(v, node: &CallArg) { - match node { - CallArg::Pos(expr) => v.visit_expr(&expr), - CallArg::Named(named) => v.visit_expr(&named.expr), + visit_arg(v, arg: CallArg) { + match arg { + CallArg::Pos(expr) => v.visit_expr(expr), + CallArg::Named(named) => v.visit_expr(r!(named.expr)), } } - fn visit_with(v, node: &WithExpr) { - v.visit_expr(&node.callee); - v.visit_args(&node.args); + visit_with(v, with_expr: WithExpr) { + v.visit_expr(r!(with_expr.callee)); + v.visit_args(r!(with_expr.args)); } - fn visit_let(v, node: &LetExpr) { - if let Some(init) = &node.init { - v.visit_expr(&init); + visit_let(v, let_expr: LetExpr) { + if let Some(init) = r!(let_expr.init) { + v.visit_expr(init); } - v.visit_binding(&node.binding); + v.visit_binding(r!(let_expr.binding)); } - fn visit_if(v, node: &IfExpr) { - v.visit_expr(&node.condition); - v.visit_expr(&node.if_body); - if let Some(body) = &node.else_body { - v.visit_expr(&body); + visit_if(v, if_expr: IfExpr) { + v.visit_expr(r!(if_expr.condition)); + v.visit_expr(r!(if_expr.if_body)); + if let Some(body) = r!(if_expr.else_body) { + v.visit_expr(body); } } - fn visit_while(v, node: &WhileExpr) { - v.visit_expr(&node.condition); - v.visit_expr(&node.body); + visit_while(v, while_expr: WhileExpr) { + v.visit_expr(r!(while_expr.condition)); + v.visit_expr(r!(while_expr.body)); } - fn visit_for(v, node: &ForExpr) { - v.visit_expr(&node.iter); - match &node.pattern { + visit_for(v, for_expr: ForExpr) { + v.visit_expr(r!(for_expr.iter)); + match r!(for_expr.pattern) { ForPattern::Value(value) => v.visit_binding(value), ForPattern::KeyValue(key, value) => { v.visit_binding(key); v.visit_binding(value); } } - v.visit_expr(&node.body); + v.visit_expr(r!(for_expr.body)); } - fn visit_import(v, node: &ImportExpr) { - v.visit_expr(&node.path); - if let Imports::Idents(idents) = &node.imports { + visit_import(v, import_expr: ImportExpr) { + v.visit_expr(r!(import_expr.path)); + if let Imports::Idents(idents) = r!(import_expr.imports) { for ident in idents { v.visit_binding(ident); } } } - fn visit_include(v, node: &IncludeExpr) { - v.visit_expr(&node.path); + visit_include(v, include_expr: IncludeExpr) { + v.visit_expr(r!(include_expr.path)); } } diff --git a/tests/ref/code/call-wide.png b/tests/ref/code/call-wide.png Binary files differnew file mode 100644 index 00000000..028d7aa4 --- /dev/null +++ b/tests/ref/code/call-wide.png diff --git a/tests/typ/code/call-wide.typ b/tests/typ/code/call-wide.typ new file mode 100644 index 00000000..2bb6d82e --- /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, <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! diff --git a/tests/typ/code/call.typ b/tests/typ/code/call.typ index 5351eb29..eb5c6732 100644 --- a/tests/typ/code/call.typ +++ b/tests/typ/code/call.typ @@ -23,9 +23,6 @@ #let alias = type #test(alias(alias), "function") -// Library function `font` returns template. -#test(type(font(size: 12pt)), "template") - --- // Callee expressions. { diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ index b1eefc9f..5074bd9e 100644 --- a/tests/typ/coma.typ +++ b/tests/typ/coma.typ @@ -1,5 +1,5 @@ // Configuration with `page` and `font` functions. -#page(width: 450pt, margins: 1cm) +#page!(width: 450pt, margins: 1cm) // There are variables and they can take normal values like strings, ... #let city = "Berlin" diff --git a/tests/typ/insert/circle.typ b/tests/typ/insert/circle.typ index d2fa928e..1c8808a4 100644 --- a/tests/typ/insert/circle.typ +++ b/tests/typ/insert/circle.typ @@ -4,18 +4,24 @@ // Test auto sizing. Auto-sized circle. \ -#circle(fill: #eb5278, align(center, center, [But, soft!])) +#circle(fill: #eb5278)[ + #align!(center, center) + But, soft! +] Center-aligned rect in auto-sized circle. #circle(fill: forest)[ - #align(center, center) - #rect(fill: conifer, pad(5pt)[But, soft!]) + #align!(center, center) + #rect!(fill: conifer) + #pad!(5pt) + But, soft! ] 100%-width rect in auto-sized circle. \ -#circle(fill: forest, rect(width: 100%, fill: conifer)[ +#circle(fill: forest)[ + #rect!(width: 100%, fill: conifer) But, soft! what light through yonder window breaks? -]) +] Expanded by height. #circle(fill: conifer)[A \ B \ C] @@ -23,8 +29,8 @@ Expanded by height. --- // Test relative sizing. #rect(width: 100%, height: 50pt, fill: #aaa)[ - #align(center, center) - #font(color: #fff) + #align!(center, center) + #font!(color: #fff) #circle(radius: 10pt, fill: eastern)[A] #circle(height: 60%, fill: eastern)[B] #circle(width: 20% + 20pt, fill: eastern)[C] diff --git a/tests/typ/insert/ellipse.typ b/tests/typ/insert/ellipse.typ index 9b10eded..a086ab74 100644 --- a/tests/typ/insert/ellipse.typ +++ b/tests/typ/insert/ellipse.typ @@ -3,11 +3,10 @@ --- 100% rect in 100% ellipse in fixed rect. \ #rect(width: 3cm, height: 2cm, fill: #2a631a)[ - #ellipse(width: 100%, height: 100%, fill: forest)[ - #rect(width: 100%, height: 100%, fill: conifer)[ - #align(center, center)[Stuff inside an ellipse!] - ] - ] + #ellipse!(width: 100%, height: 100%, fill: forest) + #rect!(width: 100%, height: 100%, fill: conifer) + #align!(center, center) + Stuff inside an ellipse! ] Auto-sized ellipse. \ diff --git a/tests/typ/insert/rect.typ b/tests/typ/insert/rect.typ index f450064a..93df071b 100644 --- a/tests/typ/insert/rect.typ +++ b/tests/typ/insert/rect.typ @@ -3,7 +3,7 @@ --- // Test the `rect` function. -#page(width: 150pt) +#page!(width: 150pt) // Fit to text. #rect(fill: conifer)[Textbox] diff --git a/tests/typ/insert/square.typ b/tests/typ/insert/square.typ index 649d31c0..4dd8964f 100644 --- a/tests/typ/insert/square.typ +++ b/tests/typ/insert/square.typ @@ -3,11 +3,10 @@ --- Auto-sized square. \ #square(fill: eastern)[ - #align(center) - #pad(5pt)[ - #font(color: #fff, weight: bold) - Typst - ] + #align!(center) + #pad!(5pt) + #font!(color: #fff, weight: bold) + Typst ] --- @@ -18,14 +17,14 @@ Auto-sized square. \ --- // Test height overflow. -#page(width: 75pt, height: 100pt) +#page!(width: 75pt, height: 100pt) #square(fill: conifer)[ But, soft! what light through yonder window breaks? ] --- // Test width overflow. -#page(width: 100pt, height: 75pt) +#page!(width: 100pt, height: 75pt) #square(fill: conifer)[ But, soft! what light through yonder window breaks? ] diff --git a/tests/typ/layout/containers.typ b/tests/typ/layout/containers.typ index b2835b5e..75e2617b 100644 --- a/tests/typ/layout/containers.typ +++ b/tests/typ/layout/containers.typ @@ -12,7 +12,7 @@ Apart --- // Test block over multiple pages. -#page(height: 60pt) +#page!(height: 60pt) First! #block[ diff --git a/tests/typ/layout/grid-1.typ b/tests/typ/layout/grid-1.typ index 827a98a7..2938a7f2 100644 --- a/tests/typ/layout/grid-1.typ +++ b/tests/typ/layout/grid-1.typ @@ -3,7 +3,7 @@ --- #let rect(width, color) = rect(width: width, height: 2cm, fill: color) -#page(width: 100pt, height: 140pt) +#page!(width: 100pt, height: 140pt) #grid( columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%), rect(0.5cm, #2a631a), @@ -33,7 +33,7 @@ ) --- -#page(height: 3cm, width: 2cm) +#page!(height: 3cm, width: 2cm) #grid( columns: (1fr, 1cm, 1fr, 1fr), column-dir: ttb, @@ -46,8 +46,8 @@ ) --- -#page(height: 3cm, margins: 0pt) -#align(center) +#page!(height: 3cm, margins: 0pt) +#align!(center) #grid( columns: (1fr,), rows: (1fr, auto, 2fr), diff --git a/tests/typ/layout/grid-2.typ b/tests/typ/layout/grid-2.typ index 277e695c..e28cfaf2 100644 --- a/tests/typ/layout/grid-2.typ +++ b/tests/typ/layout/grid-2.typ @@ -1,7 +1,7 @@ // Test using the `grid` function to create a finance table. --- -#page(width: 12cm, height: 2.5cm) +#page!(width: 12cm, height: 2.5cm) #grid( columns: 5, gutter-columns: (2fr, 1fr, 1fr), diff --git a/tests/typ/layout/grid-3.typ b/tests/typ/layout/grid-3.typ index 6ef0ab83..62af4072 100644 --- a/tests/typ/layout/grid-3.typ +++ b/tests/typ/layout/grid-3.typ @@ -1,7 +1,7 @@ // Test grid cells that overflow to the next region. --- -#page(width: 5cm, height: 3cm) +#page!(width: 5cm, height: 3cm) #grid( columns: 2, gutter-rows: 3 * (8pt,), @@ -18,7 +18,7 @@ --- // Test a column that starts overflowing right after another row/column did // that. -#page(width: 5cm, height: 2cm) +#page!(width: 5cm, height: 2cm) #grid( columns: 4 * (1fr,), gutter-rows: (10pt,), @@ -32,7 +32,7 @@ --- // Test two columns in the same row overflowing by a different amount. -#page(width: 5cm, height: 2cm) +#page!(width: 5cm, height: 2cm) #grid( columns: 3 * (1fr,), gutter-rows: (8pt,), @@ -48,7 +48,7 @@ --- // Test grid within a grid, overflowing. -#page(width: 5cm, height: 2.25cm) +#page!(width: 5cm, height: 2.25cm) #grid( columns: 4 * (1fr,), gutter-rows: (10pt,), @@ -62,7 +62,7 @@ --- // Test partition of `fr` units before and after multi-region layout. -#page(width: 5cm, height: 4cm) +#page!(width: 5cm, height: 4cm) #grid( columns: 2 * (1fr,), rows: (1fr, 2fr, auto, 1fr, 1cm), diff --git a/tests/typ/layout/pad.typ b/tests/typ/layout/pad.typ index 142ae4a9..05f9b359 100644 --- a/tests/typ/layout/pad.typ +++ b/tests/typ/layout/pad.typ @@ -6,9 +6,8 @@ // All sides together. #rect(fill: conifer)[ - #pad(10pt, right: 20pt)[ - #rect(width: 20pt, height: 20pt, fill: #eb5278) - ] + #pad!(10pt, right: 20pt) + #rect(width: 20pt, height: 20pt, fill: #eb5278) ] // Error: 13-23 missing argument: body @@ -29,7 +28,7 @@ Hi #box(pad(left: 10pt)) there --- // Test that the pad node doesn't consume the whole region. -#page(height: 6cm) +#page!(height: 6cm) #align(left)[Before] #pad(10pt, image("../../res/tiger.jpg")) diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ index cf787e5d..d154d5f4 100644 --- a/tests/typ/layout/page.typ +++ b/tests/typ/layout/page.typ @@ -2,7 +2,7 @@ --- // Set width and height. -#page(width: 120pt, height: 120pt) +#page!(width: 120pt, height: 120pt) #page(width: 40pt)[High] #page(height: 40pt)[Wide] @@ -13,7 +13,7 @@ ] // Set individual margins. -#page(height: 40pt) +#page!(height: 40pt) #page(left: 0pt, align(left)[Left]) #page(right: 0pt, align(right)[Right]) #page(top: 0pt, align(top)[Top]) @@ -22,21 +22,21 @@ // Ensure that specific margins override general margins. #page(margins: 0pt, left: 20pt)[Overriden] -// Error: 7-18 unknown variable -#page(nonexistant) +// Error: 8-19 unknown variable +#page!(nonexistant) // Flipped predefined paper. #page("a11", flip: true)[Flipped A11] // Flipped custom page size. -#page(width: 40pt, height: 120pt) -#page(flip: true) +#page!(width: 40pt, height: 120pt) +#page!(flip: true) Wide --- // Test a combination of pages with bodies and normal content. -#page(height: 50pt) +#page!(height: 50pt) #page[First] #page[Second] diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ index dedf1ce6..b13a1bfd 100644 --- a/tests/typ/layout/pagebreak.typ +++ b/tests/typ/layout/pagebreak.typ @@ -3,7 +3,7 @@ --- First of two #pagebreak() -#page(height: 40pt) +#page!(height: 40pt) --- // Make sure that you can't do page related stuff in a container. @@ -14,7 +14,7 @@ A #pagebreak() // Error: 11-15 cannot modify page from here - #page("a4") + #page("a4")[] ] C diff --git a/tests/typ/text/basic.typ b/tests/typ/text/basic.typ index d5f0de05..6e36a210 100644 --- a/tests/typ/text/basic.typ +++ b/tests/typ/text/basic.typ @@ -1,7 +1,7 @@ // Test simple text. --- -#page(width: 250pt, height: 110pt) +#page!(width: 250pt, height: 110pt) But, soft! what light through yonder window breaks? It is the east, and Juliet is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and diff --git a/tests/typ/text/bidi.typ b/tests/typ/text/bidi.typ index 2e7236ca..26099b91 100644 --- a/tests/typ/text/bidi.typ +++ b/tests/typ/text/bidi.typ @@ -3,54 +3,54 @@ --- // Test reordering with different top-level paragraph directions. #let text = [Text טֶקסט] -#font(family: ("EB Garamond", "Noto Serif Hebrew")) -#lang("he") {text} -#lang("de") {text} +#font!(family: ("EB Garamond", "Noto Serif Hebrew")) +#lang!("he") {text} +#lang!("de") {text} --- // Test that consecutive, embedded LTR runs stay LTR. // Here, we have two runs: "A" and italic "B". #let text = [أنت A_B_مطرC] -#font(family: ("EB Garamond", "Noto Sans Arabic")) -#lang("ar") {text} -#lang("de") {text} +#font!(family: ("EB Garamond", "Noto Sans Arabic")) +#lang!("ar") {text} +#lang!("de") {text} --- // Test that consecutive, embedded RTL runs stay RTL. // Here, we have three runs: "גֶ", bold "שֶׁ", and "ם". #let text = [Aגֶ*שֶׁ*םB] -#font(family: ("EB Garamond", "Noto Serif Hebrew")) -#lang("he") {text} -#lang("de") {text} +#font!(family: ("EB Garamond", "Noto Serif Hebrew")) +#lang!("he") {text} +#lang!("de") {text} --- // Test embedding up to level 4 with isolates. -#font(family: ("EB Garamond", "Noto Serif Hebrew", "Twitter Color Emoji")) -#lang(dir: rtl) +#font!(family: ("EB Garamond", "Noto Serif Hebrew", "Twitter Color Emoji")) +#lang!(dir: rtl) א\u{2066}A\u{2067}Bב\u{2069}? --- // Test hard line break (leads to two paragraphs in unicode-bidi). -#font(family: ("Noto Sans Arabic", "EB Garamond")) -#lang("ar") +#font!(family: ("Noto Sans Arabic", "EB Garamond")) +#lang!("ar") Life المطر هو الحياة \ الحياة تمطر is rain. --- // Test spacing. -#font(family: ("EB Garamond", "Noto Serif Hebrew")) +#font!(family: ("EB Garamond", "Noto Serif Hebrew")) L #h(1cm) ריווחR \ Lריווח #h(1cm) R --- // Test inline object. -#font(family: ("Noto Serif Hebrew", "EB Garamond")) -#lang("he") +#font!(family: ("Noto Serif Hebrew", "EB Garamond")) +#lang!("he") קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים --- // Test the `lang` function. // Ref: false -// Error: 12-15 must be horizontal -#lang(dir: ttb) +// Error: 13-16 must be horizontal +#lang!(dir: ttb) diff --git a/tests/typ/text/chinese.typ b/tests/typ/text/chinese.typ index 3fe790ea..2da3f1a1 100644 --- a/tests/typ/text/chinese.typ +++ b/tests/typ/text/chinese.typ @@ -1,7 +1,7 @@ // Test chinese text from Wikipedia. --- -#font(family: "Noto Serif CJK SC") +#font!(family: "Noto Serif CJK SC") 是美国广播公司电视剧《迷失》第3季的第22和23集,也是全剧的第71集和72集 由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德 diff --git a/tests/typ/text/font.typ b/tests/typ/text/font.typ index f3a9495b..d6161c81 100644 --- a/tests/typ/text/font.typ +++ b/tests/typ/text/font.typ @@ -35,9 +35,9 @@ Emoji: 🐪, 🌋, 🏞 --- // Test top and bottom edge. -#page(width: 170pt) +#page!(width: 170pt) #let try(top, bottom) = rect(fill: conifer)[ - #font(top-edge: top, bottom-edge: bottom) + #font!(top-edge: top, bottom-edge: bottom) `From `#top` to `#bottom ] @@ -48,7 +48,7 @@ Emoji: 🐪, 🌋, 🏞 --- // Test class definitions. -#font(sans-serif: "PT Sans") +#font!(sans-serif: "PT Sans") #font(family: sans-serif)[Sans-serif.] \ #font(family: monospace)[Monospace.] \ #font(family: monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] @@ -57,18 +57,18 @@ Emoji: 🐪, 🌋, 🏞 // Ref: false // Error: 7-12 unexpected argument -#font(false) +#font(false)[] // Error: 3:14-3:18 expected font style, found font weight // Error: 2:28-2:34 expected font weight, found string // Error: 1:43-1:44 expected string or array of strings, found integer -#font(style: bold, weight: "thin", serif: 0) +#font(style: bold, weight: "thin", serif: 0)[] // Warning: 15-19 should be between 100 and 900 -#font(weight: 2700) +#font(weight: 2700)[] // Warning: 16-21 should be between 50% and 200% -#font(stretch: 1000%) +#font(stretch: 1000%)[] // Error: 7-27 unexpected argument -#font(something: "invalid") +#font(something: "invalid")[] diff --git a/tests/typ/text/par.typ b/tests/typ/text/par.typ index 4ce2f6f1..8aa1147d 100644 --- a/tests/typ/text/par.typ +++ b/tests/typ/text/par.typ @@ -2,11 +2,11 @@ --- // FIXME: Word spacing doesn't work due to new shaping process. -#par(spacing: 10pt, leading: 25%, word-spacing: 1pt) +#par!(spacing: 10pt, leading: 25%, word-spacing: 1pt) But, soft! what light through yonder window breaks? It is the east, and Juliet is the sun. --- // Test that it finishes an existing paragraph. -Hello #par(word-spacing: 0pt) t h e r e ! +Hello #par!(word-spacing: 0pt) t h e r e ! diff --git a/tests/typ/text/shaping.typ b/tests/typ/text/shaping.typ index 9c6f2966..98630ea9 100644 --- a/tests/typ/text/shaping.typ +++ b/tests/typ/text/shaping.typ @@ -7,11 +7,11 @@ Le fira // This should just shape nicely. -#font(family: "Noto Sans Arabic") +#font!(family: "Noto Sans Arabic") دع النص يمطر عليك // This should form a three-member family. -#font(family: "Twitter Color Emoji") +#font!(family: "Twitter Color Emoji") 👩👩👦 🤚🏿 // These two shouldn't be affected by a zero-width joiner. @@ -20,7 +20,7 @@ Le fira --- // Test font fallback. -#font(family: ("EB Garamond", "Noto Sans Arabic", "Twitter Color Emoji")) +#font!(family: ("EB Garamond", "Noto Sans Arabic", "Twitter Color Emoji")) // Font fallback for emoji. A😀B @@ -40,6 +40,6 @@ A🐈中文B --- // Test reshaping. -#font(family: "Noto Serif Hebrew") -#lang("he") +#font!(family: "Noto Serif Hebrew") +#lang!("he") ס \ טֶ diff --git a/tests/typeset.rs b/tests/typeset.rs index f95c066d..d14d6a77 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -372,10 +372,8 @@ fn register_helpers(scope: &mut Scope, panics: Rc<RefCell<Vec<Panic>>>) { let repr = typst::pretty::pretty(args); args.items.clear(); Value::template(move |ctx| { - let snapshot = ctx.state.clone(); ctx.set_monospace(); ctx.push_text(&repr); - ctx.state = snapshot; }) } diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index c45b8988..aaad9ba8 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -171,12 +171,12 @@ { "comment": "Function name", "name": "entity.name.function.typst", - "match": "((#)[[:alpha:]_][[:alnum:]_-]*)(?=\\[|\\()", + "match": "((#)[[:alpha:]_][[:alnum:]_-]*!?)(?=\\[|\\()", "captures": { "2": { "name": "punctuation.definition.function.typst" } } }, { "comment": "Function arguments", - "begin": "(?<=#[[:alpha:]_][[:alnum:]_-]*)\\(", + "begin": "(?<=#[[:alpha:]_][[:alnum:]_-]*!?)\\(", "end": "\\)", "captures": { "0": { "name": "punctuation.definition.group.typst" } }, "patterns": [{ "include": "#arguments" }] @@ -231,11 +231,11 @@ { "comment": "Function name", "name": "entity.name.function.typst", - "match": "\\b[[:alpha:]_][[:alnum:]_-]*(?=\\[|\\(|\\s+\\bwith\\b)" + "match": "\\b[[:alpha:]_][[:alnum:]_-]*!?(?=(\\[|\\()|\\s+\\bwith\\b)" }, { "comment": "Function arguments", - "begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*|\\bwith\\b\\s+)\\(", + "begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*!?|\\bwith\\b\\s+)\\(", "end": "\\)", "captures": { "0": { "name": "punctuation.definition.group.typst" } }, "patterns": [{ "include": "#arguments" }] |
