diff --git a/crates/typstyle-core/src/attr.rs b/crates/typstyle-core/src/attr.rs index b965f1dc..033df221 100644 --- a/crates/typstyle-core/src/attr.rs +++ b/crates/typstyle-core/src/attr.rs @@ -1,17 +1,12 @@ use rustc_hash::FxHashMap; use typst_syntax::{ - ast::{Args, AstNode, Math, Raw}, + ast::{Args, AstNode}, Span, SyntaxKind, SyntaxNode, }; use crate::ext::StrExt; -#[derive(Clone, Copy)] -struct State { - is_math: bool, -} - #[derive(Debug, Clone, Default)] pub struct Attributes { /// Indicates whether formatting is explicitly disabled (`@typstyle off`) or always ignored. @@ -118,30 +113,10 @@ impl AttrStore { } fn compute_no_format(&mut self, root: &SyntaxNode) { - self.compute_no_format_impl(root, State { is_math: false }); + self.compute_no_format_impl(root); } - fn compute_no_format_impl(&mut self, node: &SyntaxNode, state: State) { - let state = if node.is::() { - State { is_math: true } - } else { - state - }; - - // no format multiline single backtick raw block - if node - .cast::() - .is_some_and(|raw| !raw.block() && raw.lines().count() > 1) - { - self.set_format_disabled(node); - return; - } - // no format args in math blocks - if node.kind() == SyntaxKind::Args && state.is_math { - self.set_format_disabled(node); - return; - } - + fn compute_no_format_impl(&mut self, node: &SyntaxNode) { let mut disable_next = false; let mut commented = false; for child in node.children() { @@ -161,12 +136,7 @@ impl AttrStore { disable_next = false; continue; } - // no format hash related nodes in math blocks - if child_kind == SyntaxKind::Hash && state.is_math { - self.set_format_disabled(node); - break; - } - self.compute_no_format_impl(child, state); + self.compute_no_format_impl(child); } if commented { self.set_commented(node); diff --git a/crates/typstyle-core/src/pretty/code_flow.rs b/crates/typstyle-core/src/pretty/code_flow.rs index 54bebeb2..bd08eff7 100644 --- a/crates/typstyle-core/src/pretty/code_flow.rs +++ b/crates/typstyle-core/src/pretty/code_flow.rs @@ -11,9 +11,6 @@ impl<'a> PrettyPrinter<'a> { self.convert_flow_like(named.to_untyped(), |child| { if child.kind() == SyntaxKind::Colon { FlowItem::tight_spaced(self.arena.text(":")) - } else if child.kind() == SyntaxKind::Hash { - // name - FlowItem::spaced_tight(self.arena.text("#")) } else if let Some(expr) = child.cast() { // expr FlowItem::spaced_before(self.convert_expr(expr), seen_name.replace(true)) diff --git a/crates/typstyle-core/src/pretty/code_list.rs b/crates/typstyle-core/src/pretty/code_list.rs index d90808ed..25a175f7 100644 --- a/crates/typstyle-core/src/pretty/code_list.rs +++ b/crates/typstyle-core/src/pretty/code_list.rs @@ -77,14 +77,32 @@ impl<'a> PrettyPrinter<'a> { }) } + /// In math mode, we have `$fun(1, 2; 3, 4)$ == $fun(#(1, 2), #(3, 4))$`. pub(super) fn convert_array(&'a self, array: Array<'a>) -> ArenaDoc<'a> { let _g = self.with_mode(Mode::CodeCont); + // Whether the array has parens. + // This is also used to determine whether we need to add a trailing comma. + // Note that we should not strip trailing commas in math. + let is_explicit = array + .to_untyped() + .children() + .next() + .is_some_and(|child| child.kind() == SyntaxKind::LeftParen); + let ends_with_comma = !is_explicit + && array + .to_untyped() + .children() + .last() + .is_some_and(|child| child.kind() == SyntaxKind::Comma); + ListStylist::new(self) .with_fold_style(self.get_fold_style(array)) .process_list(array.to_untyped(), |node| self.convert_array_item(node)) .print_doc(ListStyle { - add_trailing_sep_single: true, + add_trailing_sep_single: is_explicit, + add_trailing_sep_always: ends_with_comma, + delim: if is_explicit { ("(", ")") } else { ("", "") }, ..Default::default() }) } diff --git a/crates/typstyle-core/src/pretty/flow.rs b/crates/typstyle-core/src/pretty/flow.rs index d9a61d52..0777c82f 100644 --- a/crates/typstyle-core/src/pretty/flow.rs +++ b/crates/typstyle-core/src/pretty/flow.rs @@ -3,7 +3,7 @@ use typst_syntax::{SyntaxKind, SyntaxNode}; use crate::ext::StrExt; -use super::{util::is_comment_node, ArenaDoc, PrettyPrinter}; +use super::{util::is_comment_node, ArenaDoc, Mode, PrettyPrinter}; /// An item in the flow. A space is added only when the item before and the item after both allow it. pub struct FlowItem<'a>(Option>); @@ -100,34 +100,41 @@ impl<'a> PrettyPrinter<'a> { node: &'a SyntaxNode, producer: impl FnMut(&'a SyntaxNode) -> FlowItem<'a>, ) -> ArenaDoc<'a> { - self.convert_flow_like_sliced(node.children(), producer) + self.convert_flow_like_iter(node.children(), producer) } - pub(super) fn convert_flow_like_sliced( + pub(super) fn convert_flow_like_iter( &'a self, - children: std::slice::Iter<'a, SyntaxNode>, + children: impl Iterator, mut producer: impl FnMut(&'a SyntaxNode) -> FlowItem<'a>, ) -> ArenaDoc<'a> { let mut flow = FlowStylist::new(self); - let mut seen_line_comment = false; + let mut peek_line_comment = false; + let mut peek_hash = false; for child in children { - let peek_line_comment = seen_line_comment; - seen_line_comment = false; + let at_line_comment = peek_line_comment; + peek_line_comment = false; + let at_hash = peek_hash; + peek_hash = false; if child.kind().is_keyword() && !matches!(child.kind(), SyntaxKind::None | SyntaxKind::Auto) { flow.push_doc(self.arena.text(child.text().as_str()), true, true); } else if is_comment_node(child) { if child.kind() == SyntaxKind::LineComment { - seen_line_comment = true; // defers the linebreak + peek_line_comment = true; // defers the linebreak } flow.push_comment(child); - } else if peek_line_comment + } else if at_line_comment && child.kind() == SyntaxKind::Space && child.text().has_linebreak() { flow.push_doc(self.arena.hardline(), false, false); + } else if child.kind() == SyntaxKind::Hash { + flow.push_doc(self.arena.text("#"), true, false); + peek_hash = true; } else { + let _g = self.with_mode_if(Mode::Code, at_hash); let item = producer(child); if let Some(repr) = item.0 { flow.push_doc(repr.doc, repr.space_before, repr.space_after); diff --git a/crates/typstyle-core/src/pretty/func_call.rs b/crates/typstyle-core/src/pretty/func_call.rs index 07f4d744..e5d976ef 100644 --- a/crates/typstyle-core/src/pretty/func_call.rs +++ b/crates/typstyle-core/src/pretty/func_call.rs @@ -1,6 +1,9 @@ use pretty::DocAllocator; -use typst_syntax::{ast::*, SyntaxKind}; +use typst_syntax::{ast::*, SyntaxKind, SyntaxNode}; +use crate::ext::StrExt; + +use super::flow::FlowItem; use super::list::{ListStyle, ListStylist}; use super::mode::Mode; use super::plain::PlainStylist; @@ -31,9 +34,8 @@ impl<'a> PrettyPrinter<'a> { fn convert_func_call_args(&'a self, func_call: FuncCall<'a>, args: Args<'a>) -> ArenaDoc<'a> { if self.current_mode().is_math() { - return self.format_disabled(args.to_untyped()); - } - let _g = self.with_mode(Mode::CodeCont); + return self.convert_args_in_math(args); + }; let mut doc = self.arena.nil(); let has_parenthesized_args = has_parenthesized_args(args); @@ -41,7 +43,7 @@ impl<'a> PrettyPrinter<'a> { if let Some(cols) = table::is_formatable_table(func_call) { doc += self.convert_table(func_call, cols); } else if has_parenthesized_args { - doc += self.convert_parenthesized_args_as_is(args); + doc += self.convert_parenthesized_args_as_list(args); } } else if has_parenthesized_args { doc += self.convert_parenthesized_args(args); @@ -105,7 +107,7 @@ impl<'a> PrettyPrinter<'a> { }) } - fn convert_parenthesized_args_as_is(&'a self, args: Args<'a>) -> ArenaDoc<'a> { + fn convert_parenthesized_args_as_list(&'a self, args: Args<'a>) -> ArenaDoc<'a> { let _g = self.with_mode(Mode::CodeCont); let inner = PlainStylist::new(self) @@ -116,6 +118,65 @@ impl<'a> PrettyPrinter<'a> { inner.nest(self.config.tab_spaces as isize).parens() } + fn convert_args_in_math(&'a self, args: Args<'a>) -> ArenaDoc<'a> { + // strip spaces + let children = { + let children = args.to_untyped().children().as_slice(); + let i = children + .iter() + .position(|child| { + !matches!(child.kind(), SyntaxKind::LeftParen | SyntaxKind::Space) + }) + .unwrap_or(0); + let j = children + .iter() + .rposition(|child| { + !matches!(child.kind(), SyntaxKind::RightParen | SyntaxKind::Space) + }) + .unwrap_or(children.len().saturating_sub(1)); + children[i..=j].iter() + }; + + let mut peek_hashed_arg = false; + let inner = self.convert_flow_like_iter(children, |child| { + let at_hashed_arg = peek_hashed_arg; + peek_hashed_arg = false; + match child.kind() { + SyntaxKind::Comma => FlowItem::tight_spaced(self.arena.text(",")), + SyntaxKind::Semicolon => { + // We should avoid the semicolon counted the terminator of the previous hashed arg. + FlowItem::new(self.arena.text(";"), at_hashed_arg, true) + } + SyntaxKind::Space => { + peek_hashed_arg = at_hashed_arg; + if child.text().has_linebreak() { + FlowItem::tight(self.arena.hardline()) + } else { + FlowItem::none() + } + } + _ => { + if let Some(arg) = child.cast::() { + if is_ends_with_hashed_expr(arg.to_untyped().children()) { + peek_hashed_arg = true; + } + FlowItem::spaced(self.convert_arg(arg)) + } else { + FlowItem::none() + } + } + } + }); + if self.attr_store.is_multiline(args.to_untyped()) { + ((self.arena.line_() + inner).nest(self.config.tab_spaces as isize) + + self.arena.line_()) + .group() + .parens() + } else { + inner.parens() + } + } + /// Handle additional content blocks fn convert_additional_args(&'a self, args: Args<'a>, has_paren: bool) -> ArenaDoc<'a> { let args = args @@ -141,3 +202,10 @@ impl<'a> PrettyPrinter<'a> { } } } + +fn is_ends_with_hashed_expr(mut children: std::slice::Iter<'_, SyntaxNode>) -> bool { + children.next_back().is_some_and(|it| it.is::()) + && children + .next_back() + .is_some_and(|it| it.kind() == SyntaxKind::Hash) +} diff --git a/crates/typstyle-core/src/pretty/import.rs b/crates/typstyle-core/src/pretty/import.rs index 839a6d37..789679a5 100644 --- a/crates/typstyle-core/src/pretty/import.rs +++ b/crates/typstyle-core/src/pretty/import.rs @@ -27,7 +27,7 @@ impl<'a> PrettyPrinter<'a> { }; // Convert the prefix section. - let prefix_doc = self.convert_flow_like_sliced(prefix_part.iter(), |child| { + let prefix_doc = self.convert_flow_like_iter(prefix_part.iter(), |child| { match child.kind() { SyntaxKind::Colon => FlowItem::tight_spaced(self.arena.text(":")), SyntaxKind::Star => FlowItem::spaced(self.arena.text("*")), // wildcard import diff --git a/crates/typstyle-core/src/pretty/list.rs b/crates/typstyle-core/src/pretty/list.rs index 4fcb3917..c8e28178 100644 --- a/crates/typstyle-core/src/pretty/list.rs +++ b/crates/typstyle-core/src/pretty/list.rs @@ -3,7 +3,7 @@ use typst_syntax::{ast::*, SyntaxKind, SyntaxNode}; use crate::ext::StrExt; -use super::{doc_ext::DocExt, style::FoldStyle, util::is_comment_node, ArenaDoc, PrettyPrinter}; +use super::{doc_ext::DocExt, style::FoldStyle, ArenaDoc, Mode, PrettyPrinter}; enum Item<'a> { /// Detached comments that can be put on a line. @@ -23,6 +23,7 @@ pub struct ListStylist<'a> { printer: &'a PrettyPrinter<'a>, can_attach: bool, free_comments: Vec>, + peek_hash: bool, items: Vec>, item_count: usize, has_comment: bool, @@ -40,8 +41,10 @@ pub struct ListStyle { pub delim: (&'static str, &'static str), /// Whether to add an addition space inside the delimiters if the list is flat. pub add_delim_space: bool, - /// Whether a trailing single-line separator is need if the list contains only one item. + /// Whether a trailing separator is need if the list contains only one item. pub add_trailing_sep_single: bool, + /// Whether a trailing separator is always needed. + pub add_trailing_sep_always: bool, /// Whether can omit the delimiter if the list contains only one item. pub omit_delim_single: bool, /// Whether can omit the delimiter if the list is flat. @@ -57,6 +60,7 @@ impl Default for ListStyle { delim: ("(", ")"), add_delim_space: false, add_trailing_sep_single: false, + add_trailing_sep_always: false, omit_delim_single: false, omit_delim_flat: false, omit_delim_empty: false, @@ -70,6 +74,7 @@ impl<'a> ListStylist<'a> { printer, can_attach: false, free_comments: Default::default(), + peek_hash: false, items: Default::default(), item_count: 0, has_comment: false, @@ -142,9 +147,12 @@ impl<'a> ListStylist<'a> { // If the back attachment appears before the comma, the comma is move to its front if multiline. for node in iterable { + let _g = self.printer.with_mode_if(Mode::Code, self.peek_hash); if let Some(item_body) = item_checker(node) { self.add_item(item_body); + self.peek_hash = false; } else { + self.peek_hash = false; self.process_trivia(node); } } @@ -166,8 +174,13 @@ impl<'a> ListStylist<'a> { } else { arena.intersperse(self.free_comments.drain(..), arena.line()) + arena.line() }; + let hash = if self.peek_hash { + arena.text("#") + } else { + arena.nil() + }; self.items.push(Item::Commented { - body: (before + item_body).group(), + body: (before + hash + item_body).group(), after: None, }); self.can_attach = true; @@ -175,27 +188,35 @@ impl<'a> ListStylist<'a> { /// Handle non-items. fn process_trivia(&mut self, node: &'a SyntaxNode) { - if is_comment_node(node) { - self.has_comment = true; - // Line comment cannot appear in single line block - if node.kind() == SyntaxKind::LineComment { - self.has_line_comment = true; - self.fold_style = FoldStyle::Never; + match node.kind() { + SyntaxKind::LineComment | SyntaxKind::BlockComment => { + self.has_comment = true; + // Line comment cannot appear in single line block + if node.kind() == SyntaxKind::LineComment { + self.has_line_comment = true; + self.fold_style = FoldStyle::Never; + } + self.free_comments.push(self.printer.convert_comment(node)); + } + SyntaxKind::Comma => { + self.try_attach_comments(); } - self.free_comments.push(self.printer.convert_comment(node)); - } else if node.kind() == SyntaxKind::Comma { - self.try_attach_comments(); - } else if node.kind() == SyntaxKind::Space { - let newline_cnt = node.text().count_linebreaks(); - if newline_cnt > 0 { - self.attach_or_detach_comments(); - self.can_attach = false; - if let Some(nl) = self.keep_linebreak { - if newline_cnt >= 2 && !self.items.is_empty() { - self.items.push(Item::Linebreak((newline_cnt - 1).min(nl))); + SyntaxKind::Space => { + let newline_cnt = node.text().count_linebreaks(); + if newline_cnt > 0 { + self.attach_or_detach_comments(); + self.can_attach = false; + if let Some(nl) = self.keep_linebreak { + if newline_cnt >= 2 && !self.items.is_empty() { + self.items.push(Item::Linebreak((newline_cnt - 1).min(nl))); + } } } } + SyntaxKind::Hash => { + self.peek_hash = true; + } + _ => {} } } @@ -291,7 +312,9 @@ impl<'a> ListStylist<'a> { inner += body + after; if i + 1 != self.item_count { inner += sep.clone() + arena.space(); - } else if is_single && sty.add_trailing_sep_single { + } else if sty.add_trailing_sep_always + || is_single && sty.add_trailing_sep_single + { // trailing comma for one-size array inner += sep.clone(); } @@ -319,7 +342,10 @@ impl<'a> ListStylist<'a> { body, after: Option::None, } => { - let follow = if is_single && sty.add_trailing_sep_single || !is_last { + let follow = if !is_last + || sty.add_trailing_sep_always + || is_single && sty.add_trailing_sep_single + { sep.clone() } else { sep.clone().flat_alt(arena.nil()) @@ -332,12 +358,14 @@ impl<'a> ListStylist<'a> { after: Some(after), } => { let follow_break = sep.clone() + after.clone(); - let follow_flat = - if !is_last || is_single && sty.add_trailing_sep_single { - after + sep.clone() - } else { - after - }; + let follow_flat = if !is_last + || sty.add_trailing_sep_always + || is_single && sty.add_trailing_sep_single + { + after + sep.clone() + } else { + after + }; let ln = if is_last { arena.line_() } else { arena.line() }; inner += body + follow_break.flat_alt(follow_flat) + ln; } diff --git a/crates/typstyle-core/src/pretty/markup.rs b/crates/typstyle-core/src/pretty/markup.rs index f2f56c65..057f7021 100644 --- a/crates/typstyle-core/src/pretty/markup.rs +++ b/crates/typstyle-core/src/pretty/markup.rs @@ -117,10 +117,8 @@ impl<'a> PrettyPrinter<'a> { } doc += if let Some(expr) = node.cast::() { if mixed_text { - let break_suppressed = self.break_suppressed.get(); - self.break_suppressed.set(true); + let _g = self.suppress_breaks(); let doc = self.convert_expr(expr); - self.break_suppressed.set(break_suppressed); doc } else { self.convert_expr(expr) diff --git a/crates/typstyle-core/src/pretty/math.rs b/crates/typstyle-core/src/pretty/math.rs new file mode 100644 index 00000000..cb539da4 --- /dev/null +++ b/crates/typstyle-core/src/pretty/math.rs @@ -0,0 +1,110 @@ +use pretty::DocAllocator; +use typst_syntax::{ast::*, SyntaxKind}; + +use super::{flow::FlowItem, ArenaDoc, Mode, PrettyPrinter}; + +impl<'a> PrettyPrinter<'a> { + pub(super) fn convert_math(&'a self, math: Math<'a>) -> ArenaDoc<'a> { + if let Some(res) = self.check_disabled(math.to_untyped()) { + return res; + } + let _g = self.suppress_breaks(); + let mut doc = self.arena.nil(); + let mut peek_hash = false; + for node in math.to_untyped().children() { + let at_hash = peek_hash; + peek_hash = false; + if let Some(expr) = node.cast::() { + let _g = self.with_mode_if(Mode::Code, at_hash); + let expr_doc = self.convert_expr(expr); + doc += expr_doc; + } else if let Some(space) = node.cast::() { + doc += self.convert_space(space); + } else if node.kind() == SyntaxKind::Hash { + doc += self.arena.text("#"); + peek_hash = true; + } else { + doc += self.convert_trivia_untyped(node); + } + } + doc + } + + pub(super) fn convert_math_delimited( + &'a self, + math_delimited: MathDelimited<'a>, + ) -> ArenaDoc<'a> { + fn has_spaces(math_delimited: MathDelimited<'_>) -> (bool, bool) { + let mut has_space_before_math = false; + let mut has_space_after_math = false; + let mut is_before_math = true; + for child in math_delimited.to_untyped().children() { + if child.kind() == SyntaxKind::Math { + is_before_math = false; + } else if child.kind() == SyntaxKind::Space { + if is_before_math { + has_space_before_math = true; + } else { + has_space_after_math = true; + } + } + } + (has_space_before_math, has_space_after_math) + } + let open = self.convert_expr(math_delimited.open()); + let close = self.convert_expr(math_delimited.close()); + let body = self.convert_math(math_delimited.body()); + let (has_space_before_math, has_space_after_math) = has_spaces(math_delimited); + + body.enclose( + if has_space_before_math { + self.arena.space() + } else { + self.arena.nil() + }, + if has_space_after_math { + self.arena.space() + } else { + self.arena.nil() + }, + ) + .nest(self.config.tab_spaces as isize) + .enclose(open, close) + } + + pub(super) fn convert_math_attach(&'a self, math_attach: MathAttach<'a>) -> ArenaDoc<'a> { + self.convert_flow_like(math_attach.to_untyped(), |node| { + if let Some(expr) = node.cast::() { + FlowItem::tight(self.convert_expr(expr)) + } else { + FlowItem::tight(self.convert_verbatim_untyped(node)) + } + }) + } + + pub(super) fn convert_math_primes(&'a self, math_primes: MathPrimes<'a>) -> ArenaDoc<'a> { + self.arena.text("'".repeat(math_primes.count())) + } + + pub(super) fn convert_math_frac(&'a self, math_frac: MathFrac<'a>) -> ArenaDoc<'a> { + self.convert_flow_like(math_frac.to_untyped(), |node| { + if let Some(expr) = node.cast::() { + FlowItem::spaced(self.convert_expr(expr)) + } else if node.kind() != SyntaxKind::Space { + FlowItem::spaced(self.convert_verbatim_untyped(node)) + } else { + FlowItem::none() + } + }) + } + + pub(super) fn convert_math_root(&'a self, math_root: MathRoot<'a>) -> ArenaDoc<'a> { + self.convert_flow_like(math_root.to_untyped(), |node| { + if let Some(expr) = node.cast::() { + FlowItem::tight(self.convert_expr(expr)) + } else { + FlowItem::tight(self.convert_verbatim_untyped(node)) + } + }) + } +} diff --git a/crates/typstyle-core/src/pretty/mod.rs b/crates/typstyle-core/src/pretty/mod.rs index fb5b3cdd..5734a4f9 100644 --- a/crates/typstyle-core/src/pretty/mod.rs +++ b/crates/typstyle-core/src/pretty/mod.rs @@ -11,6 +11,7 @@ mod func_call; mod import; mod list; mod markup; +mod math; mod mode; mod parened_expr; mod plain; @@ -50,10 +51,6 @@ impl<'a> PrettyPrinter<'a> { } } - pub fn is_break_suppressed(&self) -> bool { - self.break_suppressed.get() - } - fn get_fold_style(&self, node: impl AstNode<'a>) -> FoldStyle { self.get_fold_style_untyped(node.to_untyped()) } @@ -115,6 +112,10 @@ impl<'a> PrettyPrinter<'a> { return res; } } + self.convert_expr_impl(expr) + } + + fn convert_expr_impl(&'a self, expr: Expr<'a>) -> ArenaDoc<'a> { match expr { Expr::Text(t) => self.convert_text(t), Expr::Space(s) => self.convert_space(s), @@ -135,6 +136,7 @@ impl<'a> PrettyPrinter<'a> { Expr::Term(t) => self.convert_term_item(t), Expr::Equation(e) => self.convert_equation(e), Expr::Math(m) => self.convert_math(m), + Expr::MathText(math_text) => self.convert_trivia(math_text), Expr::MathIdent(mi) => self.convert_verbatim(mi), Expr::MathAlignPoint(map) => self.convert_verbatim(map), Expr::MathDelimited(md) => self.convert_math_delimited(md), @@ -144,8 +146,8 @@ impl<'a> PrettyPrinter<'a> { Expr::MathRoot(mr) => self.convert_math_root(mr), Expr::MathShorthand(ms) => self.convert_verbatim(ms), Expr::Ident(i) => self.convert_ident(i), - Expr::None(n) => self.convert_verbatim(n), - Expr::Auto(a) => self.convert_verbatim(a), + Expr::None(_) => self.convert_literal("none"), + Expr::Auto(_) => self.convert_literal("auto"), Expr::Bool(b) => self.convert_verbatim(b), Expr::Int(i) => self.convert_verbatim(i), Expr::Float(f) => self.convert_verbatim(f), @@ -165,19 +167,22 @@ impl<'a> PrettyPrinter<'a> { Expr::DestructAssign(da) => self.convert_destruct_assignment(da), Expr::Set(s) => self.convert_set_rule(s), Expr::Show(s) => self.convert_show_rule(s), + Expr::Contextual(c) => self.convert_contextual(c), Expr::Conditional(c) => self.convert_conditional(c), Expr::While(w) => self.convert_while_loop(w), Expr::For(f) => self.convert_for_loop(f), Expr::Import(i) => self.convert_import(i), Expr::Include(i) => self.convert_include(i), - Expr::Break(b) => self.convert_break(b), - Expr::Continue(c) => self.convert_continue(c), + Expr::Break(_) => self.convert_literal("break"), + Expr::Continue(_) => self.convert_literal("continue"), Expr::Return(r) => self.convert_return(r), - Expr::Contextual(c) => self.convert_contextual(c), - Expr::MathText(math_text) => self.convert_trivia(math_text), } } + fn convert_literal(&'a self, literal: &'a str) -> ArenaDoc<'a> { + self.arena.text(literal) + } + fn convert_trivia(&'a self, node: impl AstNode<'a>) -> ArenaDoc<'a> { trivia(&self.arena, node.to_untyped()) } @@ -205,6 +210,11 @@ impl<'a> PrettyPrinter<'a> { } fn convert_raw(&'a self, raw: Raw<'a>) -> ArenaDoc<'a> { + // no format multiline single backtick raw block + if !raw.block() && raw.lines().count() > 1 { + return self.format_disabled(raw.to_untyped()); + } + let mut doc = self.arena.nil(); for child in raw.to_untyped().children() { if let Some(delim) = child.cast::() { @@ -267,24 +277,6 @@ impl<'a> PrettyPrinter<'a> { doc.enclose("$", "$") } - fn convert_math(&'a self, math: Math<'a>) -> ArenaDoc<'a> { - if let Some(res) = self.check_disabled(math.to_untyped()) { - return res; - } - let mut doc = self.arena.nil(); - for node in math.to_untyped().children() { - if let Some(expr) = node.cast::() { - let expr_doc = self.convert_expr(expr); - doc += expr_doc; - } else if let Some(space) = node.cast::() { - doc += self.convert_space(space); - } else { - doc += self.convert_trivia_untyped(node); - } - } - doc - } - fn convert_ident(&'a self, ident: Ident<'a>) -> ArenaDoc<'a> { self.convert_verbatim(ident) } @@ -334,146 +326,6 @@ impl<'a> PrettyPrinter<'a> { DestructuringItem::Pattern(p) => self.convert_pattern(p), } } - - fn convert_break(&'a self, _break: LoopBreak<'a>) -> ArenaDoc<'a> { - self.arena.text("break") - } - - fn convert_continue(&'a self, _continue: LoopContinue<'a>) -> ArenaDoc<'a> { - self.arena.text("continue") - } - - fn convert_math_delimited(&'a self, math_delimited: MathDelimited<'a>) -> ArenaDoc<'a> { - fn has_spaces(math_delimited: MathDelimited<'_>) -> (bool, bool) { - let mut has_space_before_math = false; - let mut has_space_after_math = false; - let mut is_before_math = true; - for child in math_delimited.to_untyped().children() { - if child.kind() == SyntaxKind::Math { - is_before_math = false; - } else if child.kind() == SyntaxKind::Space { - if is_before_math { - has_space_before_math = true; - } else { - has_space_after_math = true; - } - } - } - (has_space_before_math, has_space_after_math) - } - let open = self.convert_expr(math_delimited.open()); - let close = self.convert_expr(math_delimited.close()); - let body = self.convert_math(math_delimited.body()); - let (has_space_before_math, has_space_after_math) = has_spaces(math_delimited); - - body.enclose( - if has_space_before_math { - self.arena.space() - } else { - self.arena.nil() - }, - if has_space_after_math { - self.arena.space() - } else { - self.arena.nil() - }, - ) - .nest(self.config.tab_spaces as isize) - .enclose(open, close) - } - - fn convert_math_attach(&'a self, math_attach: MathAttach<'a>) -> ArenaDoc<'a> { - let mut doc = self.convert_expr(math_attach.base()); - let prime_index = math_attach - .to_untyped() - .children() - .enumerate() - .skip_while(|(_i, node)| node.cast::>().is_none()) - .nth(1) - .filter(|(_i, n)| n.cast::().is_some()) - .map(|(i, _n)| i); - - let bottom_index = math_attach - .to_untyped() - .children() - .enumerate() - .skip_while(|(_i, node)| !matches!(node.kind(), SyntaxKind::Underscore)) - .find_map(|(i, n)| SyntaxNode::cast::>(n).map(|n| (i, n))) - .map(|(i, _n)| i); - - let top_index = math_attach - .to_untyped() - .children() - .enumerate() - .skip_while(|(_i, node)| !matches!(node.kind(), SyntaxKind::Hat)) - .find_map(|(i, n)| SyntaxNode::cast::>(n).map(|n| (i, n))) - .map(|(i, _n)| i); - - #[derive(Debug)] - enum IndexType { - Prime, - Bottom, - Top, - } - - let mut index_types = [IndexType::Prime, IndexType::Bottom, IndexType::Top]; - index_types.sort_by_key(|index_type| match index_type { - IndexType::Prime => prime_index, - IndexType::Bottom => bottom_index, - IndexType::Top => top_index, - }); - - for index in index_types { - match index { - IndexType::Prime => { - if let Some(primes) = math_attach.primes() { - doc += self.convert_math_primes(primes); - } - } - IndexType::Bottom => { - if let Some(bottom) = math_attach.bottom() { - doc += self.arena.text("_") + self.convert_expr(bottom); - } - } - IndexType::Top => { - if let Some(top) = math_attach.top() { - doc += self.arena.text("^") + self.convert_expr(top); - } - } - } - } - doc - } - - fn convert_math_primes(&'a self, math_primes: MathPrimes<'a>) -> ArenaDoc<'a> { - self.arena.text("'".repeat(math_primes.count())) - } - - fn convert_math_frac(&'a self, math_frac: MathFrac<'a>) -> ArenaDoc<'a> { - let singleline = self.convert_expr(math_frac.num()) - + self.arena.space() - + self.arena.text("/") - + self.arena.space() - + self.convert_expr(math_frac.denom()); - // TODO: add multiline version - singleline - } - - fn convert_math_root(&'a self, math_root: MathRoot<'a>) -> ArenaDoc<'a> { - let sqrt_sym = if let Some(index) = math_root.index() { - if index == 3 { - "∛" - } else if index == 4 { - "∜" - } else { - // TODO: actually unreachable - "√" - } - } else { - "√" - }; - self.arena.text(sqrt_sym) + self.convert_expr(math_root.radicand()) - } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/typstyle-core/src/pretty/mode.rs b/crates/typstyle-core/src/pretty/mode.rs index 46dd22ae..bfafcc49 100644 --- a/crates/typstyle-core/src/pretty/mode.rs +++ b/crates/typstyle-core/src/pretty/mode.rs @@ -45,17 +45,52 @@ impl PrettyPrinter<'_> { } } -pub(super) struct ModeGuard<'a>(&'a PrettyPrinter<'a>); - impl<'a> PrettyPrinter<'a> { - pub(super) fn with_mode(&'a self, mode: Mode) -> ModeGuard<'a> { + pub fn with_mode(&'a self, mode: Mode) -> ModeGuard<'a> { self.push_mode(mode); ModeGuard(self) } + + pub fn with_mode_if(&'a self, mode: Mode, cond: bool) -> ConditionalModeGuard<'a> { + if cond { + self.push_mode(mode); + } + ConditionalModeGuard(self, cond) + } } +pub struct ModeGuard<'a>(&'a PrettyPrinter<'a>); + impl Drop for ModeGuard<'_> { fn drop(&mut self) { self.0.pop_mode(); } } + +pub struct ConditionalModeGuard<'a>(&'a PrettyPrinter<'a>, bool); + +impl Drop for ConditionalModeGuard<'_> { + fn drop(&mut self) { + if self.1 { + self.0.pop_mode(); + } + } +} + +impl<'a> PrettyPrinter<'a> { + pub fn is_break_suppressed(&self) -> bool { + self.break_suppressed.get() + } + + pub fn suppress_breaks(&'a self) -> BreakSuppressGuard<'a> { + BreakSuppressGuard(self, self.break_suppressed.replace(true)) + } +} + +pub struct BreakSuppressGuard<'a>(&'a PrettyPrinter<'a>, bool); + +impl Drop for BreakSuppressGuard<'_> { + fn drop(&mut self) { + self.0.break_suppressed.set(self.1); + } +} diff --git a/crates/typstyle-core/src/pretty/parened_expr.rs b/crates/typstyle-core/src/pretty/parened_expr.rs index 297d5468..49166151 100644 --- a/crates/typstyle-core/src/pretty/parened_expr.rs +++ b/crates/typstyle-core/src/pretty/parened_expr.rs @@ -34,7 +34,7 @@ impl<'a> PrettyPrinter<'a> { expr: Expr<'a>, use_braces: bool, ) -> ArenaDoc<'a> { - if !is_paren_needed(expr) { + if self.is_break_suppressed() || !is_paren_needed(expr) { return self.convert_expr(expr); } let (mode, delims) = if use_braces { diff --git a/crates/typstyle-core/src/pretty/table.rs b/crates/typstyle-core/src/pretty/table.rs index cd430fa6..9b4df34e 100644 --- a/crates/typstyle-core/src/pretty/table.rs +++ b/crates/typstyle-core/src/pretty/table.rs @@ -2,7 +2,10 @@ use itertools::Itertools; use pretty::DocAllocator; use typst_syntax::{ast::*, SyntaxKind}; -use crate::{pretty::util::get_parenthesized_args, PrettyPrinter}; +use crate::{ + pretty::{util::get_parenthesized_args, Mode}, + PrettyPrinter, +}; use super::{ util::{func_name, indent_func_name}, @@ -22,6 +25,8 @@ const HEADER_FOOTER: [&str; 4] = ["table.header", "table.footer", "grid.header", impl<'a> PrettyPrinter<'a> { pub(super) fn convert_table(&'a self, table: FuncCall<'a>, columns: usize) -> ArenaDoc<'a> { + let _g = self.with_mode(Mode::CodeCont); + let mut doc = self.arena.hardline(); for named in table.args().items().filter_map(|node| match node { Arg::Named(named) => Some(named), diff --git a/crates/typstyle-core/src/pretty/util.rs b/crates/typstyle-core/src/pretty/util.rs index 4fd80a46..9f1f7437 100644 --- a/crates/typstyle-core/src/pretty/util.rs +++ b/crates/typstyle-core/src/pretty/util.rs @@ -36,35 +36,17 @@ pub(super) fn func_name(node: FuncCall<'_>) -> EcoString { pub(super) fn has_parenthesized_args(node: Args<'_>) -> bool { node.to_untyped() .children() - .any(|node| matches!(node.kind(), SyntaxKind::LeftParen | SyntaxKind::RightParen)) + .next() + .is_some_and(|child| child.kind() == SyntaxKind::LeftParen) } pub(super) fn get_parenthesized_args_untyped(node: Args<'_>) -> impl Iterator { node.to_untyped() .children() .skip_while(|node| node.kind() != SyntaxKind::LeftParen) - .skip(1) .take_while(|node| node.kind() != SyntaxKind::RightParen) } -#[allow(unused)] pub(super) fn get_parenthesized_args(node: Args<'_>) -> impl Iterator> { get_parenthesized_args_untyped(node).filter_map(|node| node.cast::()) } - -#[allow(unused)] -pub(super) fn has_additional_args(node: Args<'_>) -> bool { - let has_paren_args = has_parenthesized_args(node); - let args = node - .to_untyped() - .children() - .skip_while(|node| { - if has_paren_args { - node.kind() != SyntaxKind::RightParen - } else { - node.kind() != SyntaxKind::ContentBlock - } - }) - .filter_map(|node| node.cast::<'_, Arg>()); - args.count() > 1 -} diff --git a/tests/fixtures/articles/snap/undergraduate-math.typ-0.snap b/tests/fixtures/articles/snap/undergraduate-math.typ-0.snap index bdfd549b..b454658a 100644 --- a/tests/fixtures/articles/snap/undergraduate-math.typ-0.snap +++ b/tests/fixtures/articles/snap/undergraduate-math.typ-0.snap @@ -1,7 +1,6 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/articles/undergraduate-math.typ -snapshot_kind: text --- // Meta data #set document( @@ -1077,9 +1076,9 @@ Definition by cases can be easily obtained with the `cases` function. [ $ f_n = cases( - a &"if" n = 0, - r dot f_(n - 1) &"else" - ) + a &"if" n = 0, + r dot f_(n - 1) &"else" + ) $ ], [ diff --git a/tests/fixtures/articles/snap/undergraduate-math.typ-120.snap b/tests/fixtures/articles/snap/undergraduate-math.typ-120.snap index 22ed8532..c736f5bb 100644 --- a/tests/fixtures/articles/snap/undergraduate-math.typ-120.snap +++ b/tests/fixtures/articles/snap/undergraduate-math.typ-120.snap @@ -1,7 +1,6 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/articles/undergraduate-math.typ -snapshot_kind: text --- // Meta data #set document(title: "Typst Math for Undergrads", author: "johanvx") @@ -494,9 +493,9 @@ Definition by cases can be easily obtained with the `cases` function. [ $ f_n = cases( - a &"if" n = 0, - r dot f_(n - 1) &"else" - ) + a &"if" n = 0, + r dot f_(n - 1) &"else" + ) $ ], [ diff --git a/tests/fixtures/articles/snap/undergraduate-math.typ-40.snap b/tests/fixtures/articles/snap/undergraduate-math.typ-40.snap index ee36eaee..dc737eca 100644 --- a/tests/fixtures/articles/snap/undergraduate-math.typ-40.snap +++ b/tests/fixtures/articles/snap/undergraduate-math.typ-40.snap @@ -1,7 +1,6 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/articles/undergraduate-math.typ -snapshot_kind: text --- // Meta data #set document( @@ -964,9 +963,9 @@ Definition by cases can be easily obtained with the `cases` function. [ $ f_n = cases( - a &"if" n = 0, - r dot f_(n - 1) &"else" - ) + a &"if" n = 0, + r dot f_(n - 1) &"else" + ) $ ], [ diff --git a/tests/fixtures/articles/snap/undergraduate-math.typ-80.snap b/tests/fixtures/articles/snap/undergraduate-math.typ-80.snap index bc95a7ef..f1ae38e9 100644 --- a/tests/fixtures/articles/snap/undergraduate-math.typ-80.snap +++ b/tests/fixtures/articles/snap/undergraduate-math.typ-80.snap @@ -1,7 +1,6 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/articles/undergraduate-math.typ -snapshot_kind: text --- // Meta data #set document(title: "Typst Math for Undergrads", author: "johanvx") @@ -594,9 +593,9 @@ Definition by cases can be easily obtained with the `cases` function. [ $ f_n = cases( - a &"if" n = 0, - r dot f_(n - 1) &"else" - ) + a &"if" n = 0, + r dot f_(n - 1) &"else" + ) $ ], [ diff --git a/tests/fixtures/unit/math/args.typ b/tests/fixtures/unit/math/args.typ new file mode 100644 index 00000000..f3adbd2f --- /dev/null +++ b/tests/fixtures/unit/math/args.typ @@ -0,0 +1,29 @@ +#let fun(..args) = raw(lang: "typc", repr(args)) +$fun(1,#2 ;3, 4 )$ +$fun(1,named: #2 ;3, 4 )$ +$fun(1,named: #2;3, 4 )$ + +#let aaa = $x$ +$bold(#aaa)$ +$sin(2, named: #3,)$ + +#($mat(1, #2 ;3 ,4)$ == $ mat( #(1,2),#(3,4))$) +$mat(1 ,#2 , gap: #1em , ..#() , .. #(), )$ +$mat(1 ,#2; 3,4 ; gap: #1em , ..#() , .. #(), )$ +$vec( 1,2, gap: #1em , ..#() , ..#(:))$ +$#text( red, size: 2em , )[baka]$ +$#text( red , size : 2em )[baka]$ + +// Comma and semicolon as terminator +$mat(#1,#2)$ $mat(#1, #2)$ $mat(#1 ,#2)$ +#assert($mat(#1,#2)$ == $mat(#1, #2)$) +#assert($mat(#1 ,#2)$ == $mat(#1,#2)$) + +$mat(#1;#2)$ $mat(#1; #2)$ $mat(#1 ;#2)$ $mat(#1 ; #2,)$ $mat(#1 ; #2; #3)$ +#assert($mat(#1;#2)$ != $mat(#1; #2)$) +#assert($mat(#1;#2)$ != $mat(#1 ;#2)$) +#assert($mat(#1, ;#2)$ != $mat(#1 ;#2)$) +#assert($mat(#1 ;#2)$ == $mat(#1 ; #2,)$) +#assert($mat(#1 ;#2)$ != $mat(#1 ; #2,,)$) +#assert($mat(#1 ; #2,,)$ != $mat(#1 ; #2,)$) +#assert($mat(#1 ; #2,,)$ == $mat(#1 ; #2 , ,)$) diff --git a/tests/fixtures/unit/math/fn-comma.typ b/tests/fixtures/unit/math/fn-comma.typ index f25264a6..c354a5db 100644 --- a/tests/fixtures/unit/math/fn-comma.typ +++ b/tests/fixtures/unit/math/fn-comma.typ @@ -1,5 +1,8 @@ $ sin(x) $ $ sin(x,) $ $ sin(x,,,) $ +$ sin( x , ,, ) $ $ mat(1,;,1) $ $ mat(1,;,;,1;1) $ +$ mat(;,;,1;1,,) $ +$ mat( 1, ; , ;,1 ; 1 ) $ diff --git a/tests/fixtures/unit/math/hashes.typ b/tests/fixtures/unit/math/hashes.typ new file mode 100644 index 00000000..c3b450f1 --- /dev/null +++ b/tests/fixtures/unit/math/hashes.typ @@ -0,0 +1,65 @@ +// Test hash (#) usage in math expressions +#let x = 5 +#let y = 10 +#let f(x) = x*x + 2*x + 1 + +// 1. Basic arithmetic with hashes +$#x + #y$ +$#x - #y $ +$#x * #y$ +$#x/#y$ +$#x^#y$ + +// 2. Functions and roots +$abs( #x - #y)$ +$floor( #x/2 )$ +$ceil(#y / #x )$ +$√#x$ +$∛#x$ +$∜#y$ + +// 3. Subscripts and superscripts +$x_#x^#y$ +$sum_(i=#x)^#y i$ +$α_#x + β_#y$ + +// 4. Complex expressions +$((#x + 1)/(#y - 2))^2$ +$sqrt(#x^2 + #y^2)$ +$#x/ 2 + #y /4$ + +// 5. Function calls and evaluations +$f(x) = #f(x)$ +$log_#x (y) = #calc.log(y, base: x)$ +$2^8 = #calc.pow(2, 8)$ + +// 6. Code blocks in math +$ sum_(i=1)^#x i^2 = #{ + let sum = 0 + for i in range(1, x + 1) { + sum += i * i } + sum +} $ + +// 7. Arrays and matrices +$mat(#(1, 2, 3), #(4, 5, x))$ +$mat(#(1, 2, 3), + #(4, 5, x))$ + $ mat(#(1, 2, 3), + #(4, 5, x)) $ + +// 8. Conditional expressions +$ cases( + #x/2 "if" x < 0, +#y/4 "if" x >= 0 +) $ + +// 9. Text and formatting in math +$"x = " #str(x)" cm"$ +$#text(red, $x + y$)$ + +// 10. Code blocks +$#for i in range( 10) { [#i,];[ -#i,]}$ +$ #for i in range(10 ) {[#i,] ;[-#i, ] } $ +$#(i=>i*2)(5 )$ +$ #(i=>i*2)( 5) $ diff --git a/tests/fixtures/unit/math/snap/args.typ-0.snap b/tests/fixtures/unit/math/snap/args.typ-0.snap new file mode 100644 index 00000000..b15da8e6 --- /dev/null +++ b/tests/fixtures/unit/math/snap/args.typ-0.snap @@ -0,0 +1,68 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/args.typ +--- +#let fun( + ..args, +) = raw( + lang: "typc", + repr(args), +) +$fun(1, #2 ; 3, 4)$ +$fun(1, named: #2 ; 3, 4)$ +$fun(1, named: #2;3, 4)$ + +#let aaa = $x$ +$bold(#aaa)$ +$sin(2, named: #3,)$ + +#( + $mat(1, #2 ; 3, 4)$ + == $mat(#(1, 2), #(3, 4))$ +) +$mat(1, #2, gap: #1em, ..#(), .. #(),)$ +$mat(1, #2; 3, 4; gap: #1em, ..#(), .. #(),)$ +$vec(1, 2, gap: #1em, ..#(), ..#(:))$ +$#text(red, size: 2em)[baka]$ +$#text(red, size: 2em)[baka]$ + +// Comma and semicolon as terminator +$mat(#1, #2)$ $mat(#1, #2)$ $mat(#1, #2)$ +#assert( + $mat(#1, #2)$ + == $mat(#1, #2)$, +) +#assert( + $mat(#1, #2)$ + == $mat(#1, #2)$, +) + +$mat(#1;#2)$ $mat(#1; #2)$ $mat(#1 ; #2)$ $mat(#1 ; #2,)$ $mat(#1 ; #2; #3)$ +#assert( + $mat(#1;#2)$ + != $mat(#1; #2)$, +) +#assert( + $mat(#1;#2)$ + != $mat(#1 ; #2)$, +) +#assert( + $mat(#1, ; #2)$ + != $mat(#1 ; #2)$, +) +#assert( + $mat(#1 ; #2)$ + == $mat(#1 ; #2,)$, +) +#assert( + $mat(#1 ; #2)$ + != $mat(#1 ; #2, ,)$, +) +#assert( + $mat(#1 ; #2, ,)$ + != $mat(#1 ; #2,)$, +) +#assert( + $mat(#1 ; #2, ,)$ + == $mat(#1 ; #2, ,)$, +) diff --git a/tests/fixtures/unit/math/snap/args.typ-120.snap b/tests/fixtures/unit/math/snap/args.typ-120.snap new file mode 100644 index 00000000..85b09113 --- /dev/null +++ b/tests/fixtures/unit/math/snap/args.typ-120.snap @@ -0,0 +1,33 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/args.typ +--- +#let fun(..args) = raw(lang: "typc", repr(args)) +$fun(1, #2 ; 3, 4)$ +$fun(1, named: #2 ; 3, 4)$ +$fun(1, named: #2;3, 4)$ + +#let aaa = $x$ +$bold(#aaa)$ +$sin(2, named: #3,)$ + +#($mat(1, #2 ; 3, 4)$ == $mat(#(1, 2), #(3, 4))$) +$mat(1, #2, gap: #1em, ..#(), .. #(),)$ +$mat(1, #2; 3, 4; gap: #1em, ..#(), .. #(),)$ +$vec(1, 2, gap: #1em, ..#(), ..#(:))$ +$#text(red, size: 2em)[baka]$ +$#text(red, size: 2em)[baka]$ + +// Comma and semicolon as terminator +$mat(#1, #2)$ $mat(#1, #2)$ $mat(#1, #2)$ +#assert($mat(#1, #2)$ == $mat(#1, #2)$) +#assert($mat(#1, #2)$ == $mat(#1, #2)$) + +$mat(#1;#2)$ $mat(#1; #2)$ $mat(#1 ; #2)$ $mat(#1 ; #2,)$ $mat(#1 ; #2; #3)$ +#assert($mat(#1;#2)$ != $mat(#1; #2)$) +#assert($mat(#1;#2)$ != $mat(#1 ; #2)$) +#assert($mat(#1, ; #2)$ != $mat(#1 ; #2)$) +#assert($mat(#1 ; #2)$ == $mat(#1 ; #2,)$) +#assert($mat(#1 ; #2)$ != $mat(#1 ; #2, ,)$) +#assert($mat(#1 ; #2, ,)$ != $mat(#1 ; #2,)$) +#assert($mat(#1 ; #2, ,)$ == $mat(#1 ; #2, ,)$) diff --git a/tests/fixtures/unit/math/snap/args.typ-40.snap b/tests/fixtures/unit/math/snap/args.typ-40.snap new file mode 100644 index 00000000..39e76e24 --- /dev/null +++ b/tests/fixtures/unit/math/snap/args.typ-40.snap @@ -0,0 +1,50 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/args.typ +--- +#let fun(..args) = raw( + lang: "typc", + repr(args), +) +$fun(1, #2 ; 3, 4)$ +$fun(1, named: #2 ; 3, 4)$ +$fun(1, named: #2;3, 4)$ + +#let aaa = $x$ +$bold(#aaa)$ +$sin(2, named: #3,)$ + +#( + $mat(1, #2 ; 3, 4)$ + == $mat(#(1, 2), #(3, 4))$ +) +$mat(1, #2, gap: #1em, ..#(), .. #(),)$ +$mat(1, #2; 3, 4; gap: #1em, ..#(), .. #(),)$ +$vec(1, 2, gap: #1em, ..#(), ..#(:))$ +$#text(red, size: 2em)[baka]$ +$#text(red, size: 2em)[baka]$ + +// Comma and semicolon as terminator +$mat(#1, #2)$ $mat(#1, #2)$ $mat(#1, #2)$ +#assert($mat(#1, #2)$ == $mat(#1, #2)$) +#assert($mat(#1, #2)$ == $mat(#1, #2)$) + +$mat(#1;#2)$ $mat(#1; #2)$ $mat(#1 ; #2)$ $mat(#1 ; #2,)$ $mat(#1 ; #2; #3)$ +#assert($mat(#1;#2)$ != $mat(#1; #2)$) +#assert($mat(#1;#2)$ != $mat(#1 ; #2)$) +#assert( + $mat(#1, ; #2)$ != $mat(#1 ; #2)$, +) +#assert( + $mat(#1 ; #2)$ == $mat(#1 ; #2,)$, +) +#assert( + $mat(#1 ; #2)$ != $mat(#1 ; #2, ,)$, +) +#assert( + $mat(#1 ; #2, ,)$ != $mat(#1 ; #2,)$, +) +#assert( + $mat(#1 ; #2, ,)$ + == $mat(#1 ; #2, ,)$, +) diff --git a/tests/fixtures/unit/math/snap/args.typ-80.snap b/tests/fixtures/unit/math/snap/args.typ-80.snap new file mode 100644 index 00000000..85b09113 --- /dev/null +++ b/tests/fixtures/unit/math/snap/args.typ-80.snap @@ -0,0 +1,33 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/args.typ +--- +#let fun(..args) = raw(lang: "typc", repr(args)) +$fun(1, #2 ; 3, 4)$ +$fun(1, named: #2 ; 3, 4)$ +$fun(1, named: #2;3, 4)$ + +#let aaa = $x$ +$bold(#aaa)$ +$sin(2, named: #3,)$ + +#($mat(1, #2 ; 3, 4)$ == $mat(#(1, 2), #(3, 4))$) +$mat(1, #2, gap: #1em, ..#(), .. #(),)$ +$mat(1, #2; 3, 4; gap: #1em, ..#(), .. #(),)$ +$vec(1, 2, gap: #1em, ..#(), ..#(:))$ +$#text(red, size: 2em)[baka]$ +$#text(red, size: 2em)[baka]$ + +// Comma and semicolon as terminator +$mat(#1, #2)$ $mat(#1, #2)$ $mat(#1, #2)$ +#assert($mat(#1, #2)$ == $mat(#1, #2)$) +#assert($mat(#1, #2)$ == $mat(#1, #2)$) + +$mat(#1;#2)$ $mat(#1; #2)$ $mat(#1 ; #2)$ $mat(#1 ; #2,)$ $mat(#1 ; #2; #3)$ +#assert($mat(#1;#2)$ != $mat(#1; #2)$) +#assert($mat(#1;#2)$ != $mat(#1 ; #2)$) +#assert($mat(#1, ; #2)$ != $mat(#1 ; #2)$) +#assert($mat(#1 ; #2)$ == $mat(#1 ; #2,)$) +#assert($mat(#1 ; #2)$ != $mat(#1 ; #2, ,)$) +#assert($mat(#1 ; #2, ,)$ != $mat(#1 ; #2,)$) +#assert($mat(#1 ; #2, ,)$ == $mat(#1 ; #2, ,)$) diff --git a/tests/fixtures/unit/math/snap/delimited.typ-0.snap b/tests/fixtures/unit/math/snap/delimited.typ-0.snap index bb677214..74b2d20b 100644 --- a/tests/fixtures/unit/math/snap/delimited.typ-0.snap +++ b/tests/fixtures/unit/math/snap/delimited.typ-0.snap @@ -1,12 +1,13 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/delimited.typ -snapshot_kind: text --- -$f(x) = sin( cos(tan ( - x) ) arctan - (y + 6 * (3 - 78 / 64) - ) )$ +$f(x) = sin( + cos( + tan ( x) + ) arctan + (y + 6 * (3 - 78 / 64) ) + )$ $f(x) = a + (b + c) + d +d diff --git a/tests/fixtures/unit/math/snap/delimited.typ-120.snap b/tests/fixtures/unit/math/snap/delimited.typ-120.snap index bb677214..e8885a5f 100644 --- a/tests/fixtures/unit/math/snap/delimited.typ-120.snap +++ b/tests/fixtures/unit/math/snap/delimited.typ-120.snap @@ -1,12 +1,11 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/delimited.typ -snapshot_kind: text --- -$f(x) = sin( cos(tan ( - x) ) arctan - (y + 6 * (3 - 78 / 64) - ) )$ +$f(x) = sin( + cos(tan ( x)) arctan + (y + 6 * (3 - 78 / 64) ) + )$ $f(x) = a + (b + c) + d +d diff --git a/tests/fixtures/unit/math/snap/delimited.typ-40.snap b/tests/fixtures/unit/math/snap/delimited.typ-40.snap index bb677214..e8885a5f 100644 --- a/tests/fixtures/unit/math/snap/delimited.typ-40.snap +++ b/tests/fixtures/unit/math/snap/delimited.typ-40.snap @@ -1,12 +1,11 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/delimited.typ -snapshot_kind: text --- -$f(x) = sin( cos(tan ( - x) ) arctan - (y + 6 * (3 - 78 / 64) - ) )$ +$f(x) = sin( + cos(tan ( x)) arctan + (y + 6 * (3 - 78 / 64) ) + )$ $f(x) = a + (b + c) + d +d diff --git a/tests/fixtures/unit/math/snap/delimited.typ-80.snap b/tests/fixtures/unit/math/snap/delimited.typ-80.snap index bb677214..e8885a5f 100644 --- a/tests/fixtures/unit/math/snap/delimited.typ-80.snap +++ b/tests/fixtures/unit/math/snap/delimited.typ-80.snap @@ -1,12 +1,11 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/delimited.typ -snapshot_kind: text --- -$f(x) = sin( cos(tan ( - x) ) arctan - (y + 6 * (3 - 78 / 64) - ) )$ +$f(x) = sin( + cos(tan ( x)) arctan + (y + 6 * (3 - 78 / 64) ) + )$ $f(x) = a + (b + c) + d +d diff --git a/tests/fixtures/unit/math/snap/fn-comma.typ-0.snap b/tests/fixtures/unit/math/snap/fn-comma.typ-0.snap index 32ebf9b8..9cac8b13 100644 --- a/tests/fixtures/unit/math/snap/fn-comma.typ-0.snap +++ b/tests/fixtures/unit/math/snap/fn-comma.typ-0.snap @@ -1,7 +1,6 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/fn-comma.typ -snapshot_kind: text --- $ sin(x) @@ -10,11 +9,20 @@ $ sin(x,) $ $ - sin(x,,,) + sin(x, , ,) $ $ - mat(1,;,1) + sin(x, , ,) $ $ - mat(1,;,;,1;1) + mat(1, ; , 1) +$ +$ + mat(1, ; , ; , 1; 1) +$ +$ + mat(; , ; , 1; 1, ,) +$ +$ + mat(1, ; , ; , 1; 1) $ diff --git a/tests/fixtures/unit/math/snap/fn-comma.typ-120.snap b/tests/fixtures/unit/math/snap/fn-comma.typ-120.snap index 5567ba09..933a74df 100644 --- a/tests/fixtures/unit/math/snap/fn-comma.typ-120.snap +++ b/tests/fixtures/unit/math/snap/fn-comma.typ-120.snap @@ -1,10 +1,12 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/fn-comma.typ -snapshot_kind: text --- $ sin(x) $ $ sin(x,) $ -$ sin(x,,,) $ -$ mat(1,;,1) $ -$ mat(1,;,;,1;1) $ +$ sin(x, , ,) $ +$ sin(x, , ,) $ +$ mat(1, ; , 1) $ +$ mat(1, ; , ; , 1; 1) $ +$ mat(; , ; , 1; 1, ,) $ +$ mat(1, ; , ; , 1; 1) $ diff --git a/tests/fixtures/unit/math/snap/fn-comma.typ-40.snap b/tests/fixtures/unit/math/snap/fn-comma.typ-40.snap index 5567ba09..933a74df 100644 --- a/tests/fixtures/unit/math/snap/fn-comma.typ-40.snap +++ b/tests/fixtures/unit/math/snap/fn-comma.typ-40.snap @@ -1,10 +1,12 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/fn-comma.typ -snapshot_kind: text --- $ sin(x) $ $ sin(x,) $ -$ sin(x,,,) $ -$ mat(1,;,1) $ -$ mat(1,;,;,1;1) $ +$ sin(x, , ,) $ +$ sin(x, , ,) $ +$ mat(1, ; , 1) $ +$ mat(1, ; , ; , 1; 1) $ +$ mat(; , ; , 1; 1, ,) $ +$ mat(1, ; , ; , 1; 1) $ diff --git a/tests/fixtures/unit/math/snap/fn-comma.typ-80.snap b/tests/fixtures/unit/math/snap/fn-comma.typ-80.snap index 5567ba09..933a74df 100644 --- a/tests/fixtures/unit/math/snap/fn-comma.typ-80.snap +++ b/tests/fixtures/unit/math/snap/fn-comma.typ-80.snap @@ -1,10 +1,12 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/fn-comma.typ -snapshot_kind: text --- $ sin(x) $ $ sin(x,) $ -$ sin(x,,,) $ -$ mat(1,;,1) $ -$ mat(1,;,;,1;1) $ +$ sin(x, , ,) $ +$ sin(x, , ,) $ +$ mat(1, ; , 1) $ +$ mat(1, ; , ; , 1; 1) $ +$ mat(; , ; , 1; 1, ,) $ +$ mat(1, ; , ; , 1; 1) $ diff --git a/tests/fixtures/unit/math/snap/hashes.typ-0.snap b/tests/fixtures/unit/math/snap/hashes.typ-0.snap new file mode 100644 index 00000000..88da579a --- /dev/null +++ b/tests/fixtures/unit/math/snap/hashes.typ-0.snap @@ -0,0 +1,98 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/hashes.typ +--- +// Test hash (#) usage in math expressions +#let x = 5 +#let y = 10 +#let f( + x, +) = ( + x + * x + + 2 + * x + + 1 +) + +// 1. Basic arithmetic with hashes +$#x + #y$ +$#x - #y$ +$#x * #y$ +$#x / #y$ +$#x^#y$ + +// 2. Functions and roots +$abs(#x - #y)$ +$floor(#x / 2)$ +$ceil(#y / #x)$ +$√#x$ +$∛#x$ +$∜#y$ + +// 3. Subscripts and superscripts +$x_#x^#y$ +$sum_(i=#x)^#y i$ +$α_#x + β_#y$ + +// 4. Complex expressions +$((#x + 1) / (#y - 2))^2$ +$sqrt(#x^2 + #y^2)$ +$#x / 2 + #y / 4$ + +// 5. Function calls and evaluations +$f(x) = #f(x)$ +$log_#x (y) = #calc.log(y, base: x)$ +$2^8 = #calc.pow(2, 8)$ + +// 6. Code blocks in math +$ + sum_(i=1)^#x i^2 = #{ + let sum = 0 + for i in range(1, x + 1) { + sum += i * i + } + sum + } +$ + +// 7. Arrays and matrices +$mat(#(1, 2, 3), #(4, 5, x))$ +$mat( + #(1, 2, 3), + #(4, 5, x) + )$ +$ + mat( + #(1, 2, 3), + #(4, 5, x) + ) +$ + +// 8. Conditional expressions +$ + cases( + #x / 2 "if" x < 0, + #y / 4 "if" x >= 0 + ) +$ + +// 9. Text and formatting in math +$"x = " #str(x)" cm"$ +$#text(red, $x + y$)$ + +// 10. Code blocks +$#for i in range(10) { + [#i,] + [ -#i,] + }$ +$ + #for i in range(10) { + [#i,] + [-#i, ] + } +$ +$#(i => i * 2)(5)$ +$ + #(i => i * 2)(5) +$ diff --git a/tests/fixtures/unit/math/snap/hashes.typ-120.snap b/tests/fixtures/unit/math/snap/hashes.typ-120.snap new file mode 100644 index 00000000..34a93345 --- /dev/null +++ b/tests/fixtures/unit/math/snap/hashes.typ-120.snap @@ -0,0 +1,86 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/hashes.typ +--- +// Test hash (#) usage in math expressions +#let x = 5 +#let y = 10 +#let f(x) = x * x + 2 * x + 1 + +// 1. Basic arithmetic with hashes +$#x + #y$ +$#x - #y$ +$#x * #y$ +$#x / #y$ +$#x^#y$ + +// 2. Functions and roots +$abs(#x - #y)$ +$floor(#x / 2)$ +$ceil(#y / #x)$ +$√#x$ +$∛#x$ +$∜#y$ + +// 3. Subscripts and superscripts +$x_#x^#y$ +$sum_(i=#x)^#y i$ +$α_#x + β_#y$ + +// 4. Complex expressions +$((#x + 1) / (#y - 2))^2$ +$sqrt(#x^2 + #y^2)$ +$#x / 2 + #y / 4$ + +// 5. Function calls and evaluations +$f(x) = #f(x)$ +$log_#x (y) = #calc.log(y, base: x)$ +$2^8 = #calc.pow(2, 8)$ + +// 6. Code blocks in math +$ + sum_(i=1)^#x i^2 = #{ + let sum = 0 + for i in range(1, x + 1) { sum += i * i } + sum + } +$ + +// 7. Arrays and matrices +$mat(#(1, 2, 3), #(4, 5, x))$ +$mat( + #(1, 2, 3), + #(4, 5, x) + )$ +$ + mat( + #(1, 2, 3), + #(4, 5, x) + ) +$ + +// 8. Conditional expressions +$ + cases( + #x / 2 "if" x < 0, + #y / 4 "if" x >= 0 + ) +$ + +// 9. Text and formatting in math +$"x = " #str(x)" cm"$ +$#text(red, $x + y$)$ + +// 10. Code blocks +$#for i in range(10) { + [#i,] + [ -#i,] + }$ +$ + #for i in range(10) { + [#i,] + [-#i, ] + } +$ +$#(i => i * 2)(5)$ +$ #(i => i * 2)(5) $ diff --git a/tests/fixtures/unit/math/snap/hashes.typ-40.snap b/tests/fixtures/unit/math/snap/hashes.typ-40.snap new file mode 100644 index 00000000..faccbe61 --- /dev/null +++ b/tests/fixtures/unit/math/snap/hashes.typ-40.snap @@ -0,0 +1,88 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/hashes.typ +--- +// Test hash (#) usage in math expressions +#let x = 5 +#let y = 10 +#let f(x) = x * x + 2 * x + 1 + +// 1. Basic arithmetic with hashes +$#x + #y$ +$#x - #y$ +$#x * #y$ +$#x / #y$ +$#x^#y$ + +// 2. Functions and roots +$abs(#x - #y)$ +$floor(#x / 2)$ +$ceil(#y / #x)$ +$√#x$ +$∛#x$ +$∜#y$ + +// 3. Subscripts and superscripts +$x_#x^#y$ +$sum_(i=#x)^#y i$ +$α_#x + β_#y$ + +// 4. Complex expressions +$((#x + 1) / (#y - 2))^2$ +$sqrt(#x^2 + #y^2)$ +$#x / 2 + #y / 4$ + +// 5. Function calls and evaluations +$f(x) = #f(x)$ +$log_#x (y) = #calc.log(y, base: x)$ +$2^8 = #calc.pow(2, 8)$ + +// 6. Code blocks in math +$ + sum_(i=1)^#x i^2 = #{ + let sum = 0 + for i in range(1, x + 1) { + sum += i * i + } + sum + } +$ + +// 7. Arrays and matrices +$mat(#(1, 2, 3), #(4, 5, x))$ +$mat( + #(1, 2, 3), + #(4, 5, x) + )$ +$ + mat( + #(1, 2, 3), + #(4, 5, x) + ) +$ + +// 8. Conditional expressions +$ + cases( + #x / 2 "if" x < 0, + #y / 4 "if" x >= 0 + ) +$ + +// 9. Text and formatting in math +$"x = " #str(x)" cm"$ +$#text(red, $x + y$)$ + +// 10. Code blocks +$#for i in range(10) { + [#i,] + [ -#i,] + }$ +$ + #for i in range(10) { + [#i,] + [-#i, ] + } +$ +$#(i => i * 2)(5)$ +$ #(i => i * 2)(5) $ diff --git a/tests/fixtures/unit/math/snap/hashes.typ-80.snap b/tests/fixtures/unit/math/snap/hashes.typ-80.snap new file mode 100644 index 00000000..34a93345 --- /dev/null +++ b/tests/fixtures/unit/math/snap/hashes.typ-80.snap @@ -0,0 +1,86 @@ +--- +source: tests/src/unit.rs +input_file: tests/fixtures/unit/math/hashes.typ +--- +// Test hash (#) usage in math expressions +#let x = 5 +#let y = 10 +#let f(x) = x * x + 2 * x + 1 + +// 1. Basic arithmetic with hashes +$#x + #y$ +$#x - #y$ +$#x * #y$ +$#x / #y$ +$#x^#y$ + +// 2. Functions and roots +$abs(#x - #y)$ +$floor(#x / 2)$ +$ceil(#y / #x)$ +$√#x$ +$∛#x$ +$∜#y$ + +// 3. Subscripts and superscripts +$x_#x^#y$ +$sum_(i=#x)^#y i$ +$α_#x + β_#y$ + +// 4. Complex expressions +$((#x + 1) / (#y - 2))^2$ +$sqrt(#x^2 + #y^2)$ +$#x / 2 + #y / 4$ + +// 5. Function calls and evaluations +$f(x) = #f(x)$ +$log_#x (y) = #calc.log(y, base: x)$ +$2^8 = #calc.pow(2, 8)$ + +// 6. Code blocks in math +$ + sum_(i=1)^#x i^2 = #{ + let sum = 0 + for i in range(1, x + 1) { sum += i * i } + sum + } +$ + +// 7. Arrays and matrices +$mat(#(1, 2, 3), #(4, 5, x))$ +$mat( + #(1, 2, 3), + #(4, 5, x) + )$ +$ + mat( + #(1, 2, 3), + #(4, 5, x) + ) +$ + +// 8. Conditional expressions +$ + cases( + #x / 2 "if" x < 0, + #y / 4 "if" x >= 0 + ) +$ + +// 9. Text and formatting in math +$"x = " #str(x)" cm"$ +$#text(red, $x + y$)$ + +// 10. Code blocks +$#for i in range(10) { + [#i,] + [ -#i,] + }$ +$ + #for i in range(10) { + [#i,] + [-#i, ] + } +$ +$#(i => i * 2)(5)$ +$ #(i => i * 2)(5) $ diff --git a/tests/fixtures/unit/math/snap/let.typ-0.snap b/tests/fixtures/unit/math/snap/let.typ-0.snap index c7875516..1712d270 100644 --- a/tests/fixtures/unit/math/snap/let.typ-0.snap +++ b/tests/fixtures/unit/math/snap/let.typ-0.snap @@ -1,12 +1,11 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/let.typ -snapshot_kind: text --- #let xmat( ..arg, ) = [..#arg] $ - #let g = (i) => $g^#i$ - xmat(2, 2, #g) + #let g = i => $g^#i$ + xmat(2, 2, #g) $ diff --git a/tests/fixtures/unit/math/snap/let.typ-120.snap b/tests/fixtures/unit/math/snap/let.typ-120.snap index e43b8a58..521f025c 100644 --- a/tests/fixtures/unit/math/snap/let.typ-120.snap +++ b/tests/fixtures/unit/math/snap/let.typ-120.snap @@ -1,10 +1,9 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/let.typ -snapshot_kind: text --- #let xmat(..arg) = [..#arg] $ - #let g = (i) => $g^#i$ - xmat(2, 2, #g) + #let g = i => $g^#i$ + xmat(2, 2, #g) $ diff --git a/tests/fixtures/unit/math/snap/let.typ-40.snap b/tests/fixtures/unit/math/snap/let.typ-40.snap index e43b8a58..521f025c 100644 --- a/tests/fixtures/unit/math/snap/let.typ-40.snap +++ b/tests/fixtures/unit/math/snap/let.typ-40.snap @@ -1,10 +1,9 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/let.typ -snapshot_kind: text --- #let xmat(..arg) = [..#arg] $ - #let g = (i) => $g^#i$ - xmat(2, 2, #g) + #let g = i => $g^#i$ + xmat(2, 2, #g) $ diff --git a/tests/fixtures/unit/math/snap/let.typ-80.snap b/tests/fixtures/unit/math/snap/let.typ-80.snap index e43b8a58..521f025c 100644 --- a/tests/fixtures/unit/math/snap/let.typ-80.snap +++ b/tests/fixtures/unit/math/snap/let.typ-80.snap @@ -1,10 +1,9 @@ --- source: tests/src/unit.rs input_file: tests/fixtures/unit/math/let.typ -snapshot_kind: text --- #let xmat(..arg) = [..#arg] $ - #let g = (i) => $g^#i$ - xmat(2, 2, #g) + #let g = i => $g^#i$ + xmat(2, 2, #g) $