Merge pull request #35 from typst/wide-calls

Wide calls
This commit is contained in:
Laurenz 2021-06-30 11:04:53 +02:00 committed by GitHub
commit 45812b7001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 368 additions and 245 deletions

View File

@ -1,7 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use super::{Scope, Scopes, Value}; 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}; use crate::syntax::{Expr, Ident};
/// A visitor that captures variable slots. /// A visitor that captures variable slots.

View File

@ -72,10 +72,10 @@ impl Exec for syntax::RawNode {
ctx.parbreak(); ctx.parbreak();
} }
let snapshot = ctx.state.clone(); let snapshot = Rc::clone(&ctx.state.font);
ctx.set_monospace(); ctx.set_monospace();
ctx.push_text(&self.text); ctx.push_text(&self.text);
ctx.state = snapshot; ctx.state.font = snapshot;
if self.block { if self.block {
ctx.parbreak(); ctx.parbreak();
@ -85,16 +85,17 @@ impl Exec for syntax::RawNode {
impl ExecWithMap for syntax::HeadingNode { impl ExecWithMap for syntax::HeadingNode {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
ctx.parbreak();
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let font = ctx.state.font_mut(); let font = ctx.state.font_mut();
let upscale = 1.6 - 0.1 * self.level as f64; let upscale = 1.6 - 0.1 * self.level as f64;
font.size *= upscale; font.size *= upscale;
font.strong = true; font.strong = true;
self.body.exec_with_map(ctx, map); self.body.exec_with_map(ctx, map);
ctx.state = snapshot; ctx.state = snapshot;
ctx.parbreak(); ctx.parbreak();
} }
} }
@ -113,8 +114,6 @@ impl ExecWithMap for syntax::EnumItem {
} }
fn exec_item(ctx: &mut ExecContext, label: String, body: &syntax::Tree, map: &ExprMap) { 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 label = ctx.exec_stack(|ctx| ctx.push_text(label));
let body = ctx.exec_tree_stack(body, map); let body = ctx.exec_tree_stack(body, map);
let stack = StackNode { 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.push_into_stack(stack);
ctx.parbreak();
} }
impl Exec for Value { impl Exec for Value {
@ -172,6 +170,8 @@ impl Exec for TemplateNode {
impl Exec for TemplateFunc { impl Exec for TemplateFunc {
fn exec(&self, ctx: &mut ExecContext) { fn exec(&self, ctx: &mut ExecContext) {
let snapshot = ctx.state.clone();
self(ctx); self(ctx);
ctx.state = snapshot;
} }
} }

View File

@ -20,7 +20,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let right = args.named(ctx, "right"); let right = args.named(ctx, "right");
let bottom = args.named(ctx, "bottom"); let bottom = args.named(ctx, "bottom");
let flip = args.named(ctx, "flip"); 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| { Value::template(move |ctx| {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
@ -66,13 +66,10 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
} }
ctx.pagebreak(false, true, span); ctx.pagebreak(false, true, span);
body.exec(ctx);
if let Some(body) = &body { ctx.state = snapshot;
// TODO: Restrict body to a single page? ctx.pagebreak(true, false, span);
body.exec(ctx);
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 second = args.eat::<AlignValue>(ctx);
let mut horizontal = args.named::<AlignValue>(ctx, "horizontal"); let mut horizontal = args.named::<AlignValue>(ctx, "horizontal");
let mut vertical = args.named::<AlignValue>(ctx, "vertical"); 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) { for value in first.into_iter().chain(second) {
match value.axis() { match value.axis() {
@ -126,23 +123,19 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
} }
Value::template(move |ctx| { Value::template(move |ctx| {
let snapshot = ctx.state.clone();
if let Some(horizontal) = horizontal { if let Some(horizontal) = horizontal {
ctx.state.aligns.cross = horizontal.to_align(ctx.state.lang.dir); ctx.state.aligns.cross = horizontal.to_align(ctx.state.lang.dir);
} }
if let Some(vertical) = vertical { if let Some(vertical) = vertical {
ctx.state.aligns.main = vertical.to_align(Dir::TTB); let new = vertical.to_align(Dir::TTB);
if ctx.state.aligns.main != snapshot.aligns.main { if ctx.state.aligns.main != new {
ctx.state.aligns.main = new;
ctx.parbreak(); ctx.parbreak();
} }
} }
if let Some(body) = &body { body.exec(ctx);
body.exec(ctx);
ctx.state = snapshot;
}
}) })
} }

View File

@ -17,10 +17,9 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let serif = args.named(ctx, "serif"); let serif = args.named(ctx, "serif");
let sans_serif = args.named(ctx, "sans-serif"); let sans_serif = args.named(ctx, "sans-serif");
let monospace = args.named(ctx, "monospace"); 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| { Value::template(move |ctx| {
let snapshot = ctx.state.clone();
let font = ctx.state.font_mut(); let font = ctx.state.font_mut();
if let Some(linear) = size { 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(); font.families_mut().monospace = monospace.clone();
} }
if let Some(body) = &body { body.exec(ctx);
body.exec(ctx);
ctx.state = snapshot;
}
}) })
} }
@ -161,6 +157,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let spacing = args.named(ctx, "spacing"); let spacing = args.named(ctx, "spacing");
let leading = args.named(ctx, "leading"); let leading = args.named(ctx, "leading");
let word_spacing = args.named(ctx, "word-spacing"); let word_spacing = args.named(ctx, "word-spacing");
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
Value::template(move |ctx| { Value::template(move |ctx| {
if let Some(spacing) = spacing { if let Some(spacing) = spacing {
@ -176,6 +173,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
} }
ctx.parbreak(); ctx.parbreak();
body.exec(ctx);
}) })
} }
@ -190,6 +188,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
} }
None => None, None => None,
}; };
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
Value::template(move |ctx| { Value::template(move |ctx| {
if let Some(dir) = dir.or(iso) { if let Some(dir) = dir.or(iso) {
@ -197,6 +196,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
} }
ctx.parbreak(); ctx.parbreak();
body.exec(ctx);
}) })
} }
@ -232,7 +232,7 @@ fn line_impl(
let position = args.named(ctx, "position"); let position = args.named(ctx, "position");
let strength = args.named::<Linear>(ctx, "strength"); let strength = args.named::<Linear>(ctx, "strength");
let extent = args.named(ctx, "extent").unwrap_or_default(); 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. // Suppress any existing strikethrough if strength is explicitly zero.
let state = strength.map_or(true, |s| !s.is_zero()).then(|| { let state = strength.map_or(true, |s| !s.is_zero()).then(|| {
@ -245,13 +245,7 @@ fn line_impl(
}); });
Value::template(move |ctx| { Value::template(move |ctx| {
let snapshot = ctx.state.clone();
*substate(ctx.state.font_mut()) = state.clone(); *substate(ctx.state.font_mut()) = state.clone();
body.exec(ctx);
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
}) })
} }

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",

View File

@ -1,54 +1,91 @@
//! Syntax tree traversal. //! Mutable and immutable syntax tree traversal.
use super::*; use crate::syntax::*;
macro_rules! visit { /// Implement the immutable and the mutable visitor version.
($(fn $name:ident($v:ident $(, $node:ident: &$ty:ty)?) $body:block)*) => { macro_rules! impl_visitors {
/// Traverses the syntax tree. ($($name:ident($($tts:tt)*) $body:block)*) => {
pub trait Visit<'ast> { macro_rules! r {
$(fn $name(&mut self $(, $node: &'ast $ty)?) { (rc: $x:expr) => { $x.as_ref() };
$name(self, $($node)?); ($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. /// Visit a definition of a binding.
/// ///
/// Bindings are, for example, left-hand side of let expressions, /// Bindings are, for example, left-hand side of let expressions,
/// and key/value patterns in for loops. /// 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. /// Visit the entry into a scope.
fn visit_enter(&mut self) {} fn visit_enter(&mut self) {}
/// Visit the exit from a scope. /// Visit the exit from a scope.
fn visit_exit(&mut self) {} fn visit_exit(&mut self) {}
$(fn $name(&mut self, $node: &'ast $($fmut)? $ty) {
$module::$name(self, $node);
})*
} }
$(visit! { #[doc = $module_doc]
@$(concat!("Walk a node of type [`", stringify!($ty), "`]."), )? pub mod $module {
pub fn $name<'ast, V>( use super::*;
#[allow(unused)] $v: &mut V $(
$(, #[allow(unused)] $node: &'ast $ty)? #[allow(unused_variables)]
) pub fn $name<'ast, V>($v: &mut V, $node: &'ast $($fmut)? $ty)
where where
V: Visit<'ast> + ?Sized V: $visit<'ast> + ?Sized
$body $body
})* )*
}; }
(@$doc:expr, $($tts:tt)*) => {
#[doc = $doc]
$($tts)*
}; };
} }
visit! { impl_visitors! {
fn visit_tree(v, node: &Tree) { visit_tree(v, tree: Tree) {
for node in node { for node in tree {
v.visit_node(&node); v.visit_node(node);
} }
} }
fn visit_node(v, node: &Node) { visit_node(v, node: Node) {
match node { match node {
Node::Text(_) => {} Node::Text(_) => {}
Node::Space => {} Node::Space => {}
@ -64,20 +101,20 @@ visit! {
} }
} }
fn visit_heading(v, node: &HeadingNode) { visit_heading(v, heading: HeadingNode) {
v.visit_tree(&node.body); v.visit_tree(r!(rc: heading.body));
} }
fn visit_list(v, node: &ListItem) { visit_list(v, item: ListItem) {
v.visit_tree(&node.body); v.visit_tree(r!(item.body));
} }
fn visit_enum(v, node: &EnumItem) { visit_enum(v, item: EnumItem) {
v.visit_tree(&node.body); v.visit_tree(r!(item.body));
} }
fn visit_expr(v, node: &Expr) { visit_expr(v, expr: Expr) {
match node { match expr {
Expr::None(_) => {} Expr::None(_) => {}
Expr::Auto(_) => {} Expr::Auto(_) => {}
Expr::Bool(_, _) => {} Expr::Bool(_, _) => {}
@ -109,121 +146,121 @@ visit! {
} }
} }
fn visit_array(v, node: &ArrayExpr) { visit_array(v, array: ArrayExpr) {
for expr in &node.items { for expr in r!(array.items) {
v.visit_expr(&expr); v.visit_expr(expr);
} }
} }
fn visit_dict(v, node: &DictExpr) { visit_dict(v, dict: DictExpr) {
for named in &node.items { for named in r!(dict.items) {
v.visit_expr(&named.expr); v.visit_expr(r!(named.expr));
} }
} }
fn visit_template(v, node: &TemplateExpr) { visit_template(v, template: TemplateExpr) {
v.visit_enter(); v.visit_enter();
v.visit_tree(&node.tree); v.visit_tree(r!(rc: template.tree));
v.visit_exit(); v.visit_exit();
} }
fn visit_group(v, node: &GroupExpr) { visit_group(v, group: GroupExpr) {
v.visit_expr(&node.expr); v.visit_expr(r!(group.expr));
} }
fn visit_block(v, node: &BlockExpr) { visit_block(v, block: BlockExpr) {
if node.scoping { if block.scoping {
v.visit_enter(); v.visit_enter();
} }
for expr in &node.exprs { for expr in r!(block.exprs) {
v.visit_expr(&expr); v.visit_expr(expr);
} }
if node.scoping { if block.scoping {
v.visit_exit(); v.visit_exit();
} }
} }
fn visit_binary(v, node: &BinaryExpr) { visit_binary(v, binary: BinaryExpr) {
v.visit_expr(&node.lhs); v.visit_expr(r!(binary.lhs));
v.visit_expr(&node.rhs); v.visit_expr(r!(binary.rhs));
} }
fn visit_unary(v, node: &UnaryExpr) { visit_unary(v, unary: UnaryExpr) {
v.visit_expr(&node.expr); v.visit_expr(r!(unary.expr));
} }
fn visit_call(v, node: &CallExpr) { visit_call(v, call: CallExpr) {
v.visit_expr(&node.callee); v.visit_expr(r!(call.callee));
v.visit_args(&node.args); v.visit_args(r!(call.args));
} }
fn visit_closure(v, node: &ClosureExpr) { visit_closure(v, closure: ClosureExpr) {
for param in node.params.iter() { for param in r!(rc: closure.params) {
v.visit_binding(param); v.visit_binding(param);
} }
v.visit_expr(&node.body); v.visit_expr(r!(rc: closure.body));
} }
fn visit_args(v, node: &CallArgs) { visit_args(v, args: CallArgs) {
for arg in &node.items { for arg in r!(args.items) {
v.visit_arg(arg); v.visit_arg(arg);
} }
} }
fn visit_arg(v, node: &CallArg) { visit_arg(v, arg: CallArg) {
match node { match arg {
CallArg::Pos(expr) => v.visit_expr(&expr), CallArg::Pos(expr) => v.visit_expr(expr),
CallArg::Named(named) => v.visit_expr(&named.expr), CallArg::Named(named) => v.visit_expr(r!(named.expr)),
} }
} }
fn visit_with(v, node: &WithExpr) { visit_with(v, with_expr: WithExpr) {
v.visit_expr(&node.callee); v.visit_expr(r!(with_expr.callee));
v.visit_args(&node.args); v.visit_args(r!(with_expr.args));
} }
fn visit_let(v, node: &LetExpr) { visit_let(v, let_expr: LetExpr) {
if let Some(init) = &node.init { if let Some(init) = r!(let_expr.init) {
v.visit_expr(&init); v.visit_expr(init);
} }
v.visit_binding(&node.binding); v.visit_binding(r!(let_expr.binding));
} }
fn visit_if(v, node: &IfExpr) { visit_if(v, if_expr: IfExpr) {
v.visit_expr(&node.condition); v.visit_expr(r!(if_expr.condition));
v.visit_expr(&node.if_body); v.visit_expr(r!(if_expr.if_body));
if let Some(body) = &node.else_body { if let Some(body) = r!(if_expr.else_body) {
v.visit_expr(&body); v.visit_expr(body);
} }
} }
fn visit_while(v, node: &WhileExpr) { visit_while(v, while_expr: WhileExpr) {
v.visit_expr(&node.condition); v.visit_expr(r!(while_expr.condition));
v.visit_expr(&node.body); v.visit_expr(r!(while_expr.body));
} }
fn visit_for(v, node: &ForExpr) { visit_for(v, for_expr: ForExpr) {
v.visit_expr(&node.iter); v.visit_expr(r!(for_expr.iter));
match &node.pattern { match r!(for_expr.pattern) {
ForPattern::Value(value) => v.visit_binding(value), ForPattern::Value(value) => v.visit_binding(value),
ForPattern::KeyValue(key, value) => { ForPattern::KeyValue(key, value) => {
v.visit_binding(key); v.visit_binding(key);
v.visit_binding(value); v.visit_binding(value);
} }
} }
v.visit_expr(&node.body); v.visit_expr(r!(for_expr.body));
} }
fn visit_import(v, node: &ImportExpr) { visit_import(v, import_expr: ImportExpr) {
v.visit_expr(&node.path); v.visit_expr(r!(import_expr.path));
if let Imports::Idents(idents) = &node.imports { if let Imports::Idents(idents) = r!(import_expr.imports) {
for ident in idents { for ident in idents {
v.visit_binding(ident); v.visit_binding(ident);
} }
} }
} }
fn visit_include(v, node: &IncludeExpr) { visit_include(v, include_expr: IncludeExpr) {
v.visit_expr(&node.path); v.visit_expr(r!(include_expr.path));
} }
} }

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

@ -23,9 +23,6 @@
#let alias = type #let alias = type
#test(alias(alias), "function") #test(alias(alias), "function")
// Library function `font` returns template.
#test(type(font(size: 12pt)), "template")
--- ---
// Callee expressions. // Callee expressions.
{ {

View File

@ -1,5 +1,5 @@
// Configuration with `page` and `font` functions. // 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, ... // There are variables and they can take normal values like strings, ...
#let city = "Berlin" #let city = "Berlin"

View File

@ -4,18 +4,24 @@
// Test auto sizing. // Test auto sizing.
Auto-sized circle. \ 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. Center-aligned rect in auto-sized circle.
#circle(fill: forest)[ #circle(fill: forest)[
#align(center, center) #align!(center, center)
#rect(fill: conifer, pad(5pt)[But, soft!]) #rect!(fill: conifer)
#pad!(5pt)
But, soft!
] ]
100%-width rect in auto-sized circle. \ 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? But, soft! what light through yonder window breaks?
]) ]
Expanded by height. Expanded by height.
#circle(fill: conifer)[A \ B \ C] #circle(fill: conifer)[A \ B \ C]
@ -23,8 +29,8 @@ Expanded by height.
--- ---
// Test relative sizing. // Test relative sizing.
#rect(width: 100%, height: 50pt, fill: #aaa)[ #rect(width: 100%, height: 50pt, fill: #aaa)[
#align(center, center) #align!(center, center)
#font(color: #fff) #font!(color: #fff)
#circle(radius: 10pt, fill: eastern)[A] #circle(radius: 10pt, fill: eastern)[A]
#circle(height: 60%, fill: eastern)[B] #circle(height: 60%, fill: eastern)[B]
#circle(width: 20% + 20pt, fill: eastern)[C] #circle(width: 20% + 20pt, fill: eastern)[C]

View File

@ -3,11 +3,10 @@
--- ---
100% rect in 100% ellipse in fixed rect. \ 100% rect in 100% ellipse in fixed rect. \
#rect(width: 3cm, height: 2cm, fill: #2a631a)[ #rect(width: 3cm, height: 2cm, fill: #2a631a)[
#ellipse(width: 100%, height: 100%, fill: forest)[ #ellipse!(width: 100%, height: 100%, fill: forest)
#rect(width: 100%, height: 100%, fill: conifer)[ #rect!(width: 100%, height: 100%, fill: conifer)
#align(center, center)[Stuff inside an ellipse!] #align!(center, center)
] Stuff inside an ellipse!
]
] ]
Auto-sized ellipse. \ Auto-sized ellipse. \

View File

@ -3,7 +3,7 @@
--- ---
// Test the `rect` function. // Test the `rect` function.
#page(width: 150pt) #page!(width: 150pt)
// Fit to text. // Fit to text.
#rect(fill: conifer)[Textbox] #rect(fill: conifer)[Textbox]

View File

@ -3,11 +3,10 @@
--- ---
Auto-sized square. \ Auto-sized square. \
#square(fill: eastern)[ #square(fill: eastern)[
#align(center) #align!(center)
#pad(5pt)[ #pad!(5pt)
#font(color: #fff, weight: bold) #font!(color: #fff, weight: bold)
Typst Typst
]
] ]
--- ---
@ -18,14 +17,14 @@ Auto-sized square. \
--- ---
// Test height overflow. // Test height overflow.
#page(width: 75pt, height: 100pt) #page!(width: 75pt, height: 100pt)
#square(fill: conifer)[ #square(fill: conifer)[
But, soft! what light through yonder window breaks? But, soft! what light through yonder window breaks?
] ]
--- ---
// Test width overflow. // Test width overflow.
#page(width: 100pt, height: 75pt) #page!(width: 100pt, height: 75pt)
#square(fill: conifer)[ #square(fill: conifer)[
But, soft! what light through yonder window breaks? But, soft! what light through yonder window breaks?
] ]

View File

@ -12,7 +12,7 @@ Apart
--- ---
// Test block over multiple pages. // Test block over multiple pages.
#page(height: 60pt) #page!(height: 60pt)
First! First!
#block[ #block[

View File

@ -3,7 +3,7 @@
--- ---
#let rect(width, color) = rect(width: width, height: 2cm, fill: color) #let rect(width, color) = rect(width: width, height: 2cm, fill: color)
#page(width: 100pt, height: 140pt) #page!(width: 100pt, height: 140pt)
#grid( #grid(
columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%), columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
rect(0.5cm, #2a631a), rect(0.5cm, #2a631a),
@ -33,7 +33,7 @@
) )
--- ---
#page(height: 3cm, width: 2cm) #page!(height: 3cm, width: 2cm)
#grid( #grid(
columns: (1fr, 1cm, 1fr, 1fr), columns: (1fr, 1cm, 1fr, 1fr),
column-dir: ttb, column-dir: ttb,
@ -46,8 +46,8 @@
) )
--- ---
#page(height: 3cm, margins: 0pt) #page!(height: 3cm, margins: 0pt)
#align(center) #align!(center)
#grid( #grid(
columns: (1fr,), columns: (1fr,),
rows: (1fr, auto, 2fr), rows: (1fr, auto, 2fr),

View File

@ -1,7 +1,7 @@
// Test using the `grid` function to create a finance table. // Test using the `grid` function to create a finance table.
--- ---
#page(width: 12cm, height: 2.5cm) #page!(width: 12cm, height: 2.5cm)
#grid( #grid(
columns: 5, columns: 5,
gutter-columns: (2fr, 1fr, 1fr), gutter-columns: (2fr, 1fr, 1fr),

View File

@ -1,7 +1,7 @@
// Test grid cells that overflow to the next region. // Test grid cells that overflow to the next region.
--- ---
#page(width: 5cm, height: 3cm) #page!(width: 5cm, height: 3cm)
#grid( #grid(
columns: 2, columns: 2,
gutter-rows: 3 * (8pt,), gutter-rows: 3 * (8pt,),
@ -18,7 +18,7 @@
--- ---
// Test a column that starts overflowing right after another row/column did // Test a column that starts overflowing right after another row/column did
// that. // that.
#page(width: 5cm, height: 2cm) #page!(width: 5cm, height: 2cm)
#grid( #grid(
columns: 4 * (1fr,), columns: 4 * (1fr,),
gutter-rows: (10pt,), gutter-rows: (10pt,),
@ -32,7 +32,7 @@
--- ---
// Test two columns in the same row overflowing by a different amount. // Test two columns in the same row overflowing by a different amount.
#page(width: 5cm, height: 2cm) #page!(width: 5cm, height: 2cm)
#grid( #grid(
columns: 3 * (1fr,), columns: 3 * (1fr,),
gutter-rows: (8pt,), gutter-rows: (8pt,),
@ -48,7 +48,7 @@
--- ---
// Test grid within a grid, overflowing. // Test grid within a grid, overflowing.
#page(width: 5cm, height: 2.25cm) #page!(width: 5cm, height: 2.25cm)
#grid( #grid(
columns: 4 * (1fr,), columns: 4 * (1fr,),
gutter-rows: (10pt,), gutter-rows: (10pt,),
@ -62,7 +62,7 @@
--- ---
// Test partition of `fr` units before and after multi-region layout. // Test partition of `fr` units before and after multi-region layout.
#page(width: 5cm, height: 4cm) #page!(width: 5cm, height: 4cm)
#grid( #grid(
columns: 2 * (1fr,), columns: 2 * (1fr,),
rows: (1fr, 2fr, auto, 1fr, 1cm), rows: (1fr, 2fr, auto, 1fr, 1cm),

View File

@ -6,9 +6,8 @@
// All sides together. // All sides together.
#rect(fill: conifer)[ #rect(fill: conifer)[
#pad(10pt, right: 20pt)[ #pad!(10pt, right: 20pt)
#rect(width: 20pt, height: 20pt, fill: #eb5278) #rect(width: 20pt, height: 20pt, fill: #eb5278)
]
] ]
// Error: 13-23 missing argument: body // 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. // Test that the pad node doesn't consume the whole region.
#page(height: 6cm) #page!(height: 6cm)
#align(left)[Before] #align(left)[Before]
#pad(10pt, image("../../res/tiger.jpg")) #pad(10pt, image("../../res/tiger.jpg"))

View File

@ -2,7 +2,7 @@
--- ---
// Set width and height. // Set width and height.
#page(width: 120pt, height: 120pt) #page!(width: 120pt, height: 120pt)
#page(width: 40pt)[High] #page(width: 40pt)[High]
#page(height: 40pt)[Wide] #page(height: 40pt)[Wide]
@ -13,7 +13,7 @@
] ]
// Set individual margins. // Set individual margins.
#page(height: 40pt) #page!(height: 40pt)
#page(left: 0pt, align(left)[Left]) #page(left: 0pt, align(left)[Left])
#page(right: 0pt, align(right)[Right]) #page(right: 0pt, align(right)[Right])
#page(top: 0pt, align(top)[Top]) #page(top: 0pt, align(top)[Top])
@ -22,21 +22,21 @@
// Ensure that specific margins override general margins. // Ensure that specific margins override general margins.
#page(margins: 0pt, left: 20pt)[Overriden] #page(margins: 0pt, left: 20pt)[Overriden]
// Error: 7-18 unknown variable // Error: 8-19 unknown variable
#page(nonexistant) #page!(nonexistant)
// Flipped predefined paper. // Flipped predefined paper.
#page("a11", flip: true)[Flipped A11] #page("a11", flip: true)[Flipped A11]
// Flipped custom page size. // Flipped custom page size.
#page(width: 40pt, height: 120pt) #page!(width: 40pt, height: 120pt)
#page(flip: true) #page!(flip: true)
Wide Wide
--- ---
// Test a combination of pages with bodies and normal content. // Test a combination of pages with bodies and normal content.
#page(height: 50pt) #page!(height: 50pt)
#page[First] #page[First]
#page[Second] #page[Second]

View File

@ -3,7 +3,7 @@
--- ---
First of two First of two
#pagebreak() #pagebreak()
#page(height: 40pt) #page!(height: 40pt)
--- ---
// Make sure that you can't do page related stuff in a container. // Make sure that you can't do page related stuff in a container.
@ -14,7 +14,7 @@ A
#pagebreak() #pagebreak()
// Error: 11-15 cannot modify page from here // Error: 11-15 cannot modify page from here
#page("a4") #page("a4")[]
] ]
C C

View File

@ -1,7 +1,7 @@
// Test simple text. // 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 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 is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and

View File

@ -3,54 +3,54 @@
--- ---
// Test reordering with different top-level paragraph directions. // Test reordering with different top-level paragraph directions.
#let text = [Text טֶקסט] #let text = [Text טֶקסט]
#font(family: ("EB Garamond", "Noto Serif Hebrew")) #font!(family: ("EB Garamond", "Noto Serif Hebrew"))
#lang("he") {text} #lang!("he") {text}
#lang("de") {text} #lang!("de") {text}
--- ---
// Test that consecutive, embedded LTR runs stay LTR. // Test that consecutive, embedded LTR runs stay LTR.
// Here, we have two runs: "A" and italic "B". // Here, we have two runs: "A" and italic "B".
#let text = [أنت A_B_مطرC] #let text = [أنت A_B_مطرC]
#font(family: ("EB Garamond", "Noto Sans Arabic")) #font!(family: ("EB Garamond", "Noto Sans Arabic"))
#lang("ar") {text} #lang!("ar") {text}
#lang("de") {text} #lang!("de") {text}
--- ---
// Test that consecutive, embedded RTL runs stay RTL. // Test that consecutive, embedded RTL runs stay RTL.
// Here, we have three runs: "גֶ", bold "שֶׁ", and "ם". // Here, we have three runs: "גֶ", bold "שֶׁ", and "ם".
#let text = [Aגֶ*שֶׁ*םB] #let text = [Aגֶ*שֶׁ*םB]
#font(family: ("EB Garamond", "Noto Serif Hebrew")) #font!(family: ("EB Garamond", "Noto Serif Hebrew"))
#lang("he") {text} #lang!("he") {text}
#lang("de") {text} #lang!("de") {text}
--- ---
// Test embedding up to level 4 with isolates. // Test embedding up to level 4 with isolates.
#font(family: ("EB Garamond", "Noto Serif Hebrew", "Twitter Color Emoji")) #font!(family: ("EB Garamond", "Noto Serif Hebrew", "Twitter Color Emoji"))
#lang(dir: rtl) #lang!(dir: rtl)
א\u{2066}A\u{2067}Bב\u{2069}? א\u{2066}A\u{2067}Bב\u{2069}?
--- ---
// Test hard line break (leads to two paragraphs in unicode-bidi). // Test hard line break (leads to two paragraphs in unicode-bidi).
#font(family: ("Noto Sans Arabic", "EB Garamond")) #font!(family: ("Noto Sans Arabic", "EB Garamond"))
#lang("ar") #lang!("ar")
Life المطر هو الحياة \ Life المطر هو الحياة \
الحياة تمطر is rain. الحياة تمطر is rain.
--- ---
// Test spacing. // Test spacing.
#font(family: ("EB Garamond", "Noto Serif Hebrew")) #font!(family: ("EB Garamond", "Noto Serif Hebrew"))
L #h(1cm) ריווחR \ L #h(1cm) ריווחR \
יווח #h(1cm) R יווח #h(1cm) R
--- ---
// Test inline object. // Test inline object.
#font(family: ("Noto Serif Hebrew", "EB Garamond")) #font!(family: ("Noto Serif Hebrew", "EB Garamond"))
#lang("he") #lang!("he")
קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים
--- ---
// Test the `lang` function. // Test the `lang` function.
// Ref: false // Ref: false
// Error: 12-15 must be horizontal // Error: 13-16 must be horizontal
#lang(dir: ttb) #lang!(dir: ttb)

View File

@ -1,7 +1,7 @@
// Test chinese text from Wikipedia. // Test chinese text from Wikipedia.
--- ---
#font(family: "Noto Serif CJK SC") #font!(family: "Noto Serif CJK SC")
是美国广播公司电视剧《迷失》第3季的第22和23集也是全剧的第71集和72集 是美国广播公司电视剧《迷失》第3季的第22和23集也是全剧的第71集和72集
由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德 由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德

View File

@ -35,9 +35,9 @@ Emoji: 🐪, 🌋, 🏞
--- ---
// Test top and bottom edge. // Test top and bottom edge.
#page(width: 170pt) #page!(width: 170pt)
#let try(top, bottom) = rect(fill: conifer)[ #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 `From `#top` to `#bottom
] ]
@ -48,7 +48,7 @@ Emoji: 🐪, 🌋, 🏞
--- ---
// Test class definitions. // Test class definitions.
#font(sans-serif: "PT Sans") #font!(sans-serif: "PT Sans")
#font(family: sans-serif)[Sans-serif.] \ #font(family: sans-serif)[Sans-serif.] \
#font(family: monospace)[Monospace.] \ #font(family: monospace)[Monospace.] \
#font(family: monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] #font(family: monospace, monospace: ("Nope", "Latin Modern Math"))[Math.]
@ -57,18 +57,18 @@ Emoji: 🐪, 🌋, 🏞
// Ref: false // Ref: false
// Error: 7-12 unexpected argument // Error: 7-12 unexpected argument
#font(false) #font(false)[]
// Error: 3:14-3:18 expected font style, found font weight // Error: 3:14-3:18 expected font style, found font weight
// Error: 2:28-2:34 expected font weight, found string // Error: 2:28-2:34 expected font weight, found string
// Error: 1:43-1:44 expected string or array of strings, found integer // 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 // Warning: 15-19 should be between 100 and 900
#font(weight: 2700) #font(weight: 2700)[]
// Warning: 16-21 should be between 50% and 200% // Warning: 16-21 should be between 50% and 200%
#font(stretch: 1000%) #font(stretch: 1000%)[]
// Error: 7-27 unexpected argument // Error: 7-27 unexpected argument
#font(something: "invalid") #font(something: "invalid")[]

View File

@ -2,11 +2,11 @@
--- ---
// FIXME: Word spacing doesn't work due to new shaping process. // 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 But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. is the sun.
--- ---
// Test that it finishes an existing paragraph. // 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 !

View File

@ -7,11 +7,11 @@
Le fira Le fira
// This should just shape nicely. // This should just shape nicely.
#font(family: "Noto Sans Arabic") #font!(family: "Noto Sans Arabic")
دع النص يمطر عليك دع النص يمطر عليك
// This should form a three-member family. // 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. // These two shouldn't be affected by a zero-width joiner.
@ -20,7 +20,7 @@ Le fira
--- ---
// Test font fallback. // 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. // Font fallback for emoji.
A😀B A😀B
@ -40,6 +40,6 @@ A🐈中文B
--- ---
// Test reshaping. // Test reshaping.
#font(family: "Noto Serif Hebrew") #font!(family: "Noto Serif Hebrew")
#lang("he") #lang!("he")
ס \ טֶ ס \ טֶ

View File

@ -372,10 +372,8 @@ fn register_helpers(scope: &mut Scope, panics: Rc<RefCell<Vec<Panic>>>) {
let repr = typst::pretty::pretty(args); let repr = typst::pretty::pretty(args);
args.items.clear(); args.items.clear();
Value::template(move |ctx| { Value::template(move |ctx| {
let snapshot = ctx.state.clone();
ctx.set_monospace(); ctx.set_monospace();
ctx.push_text(&repr); ctx.push_text(&repr);
ctx.state = snapshot;
}) })
} }

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" }]