From 12699eb7f415bdba6797c84e3e7bf44dde75bdf9 Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Wed, 2 Apr 2025 05:30:04 -0400 Subject: [PATCH] Parse multi-character numbers consistently in math (#5996) Co-authored-by: Laurenz --- crates/typst-syntax/src/parser.rs | 8 +++----- .../ref/issue-4828-math-number-multi-char.png | Bin 0 -> 465 bytes tests/ref/math-frac-precedence.png | Bin 5504 -> 3586 bytes tests/suite/math/frac.typ | 4 ++-- tests/suite/math/syntax.typ | 4 ++++ 5 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 tests/ref/issue-4828-math-number-multi-char.png diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index c5d13c8b3..ecd0d78a5 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -271,11 +271,9 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { } SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::MathShorthand => { - continuable = !p.at(SyntaxKind::MathShorthand) - && matches!( - math_class(p.current_text()), - None | Some(MathClass::Alphabetic) - ); + // `a(b)/c` parses as `(a(b))/c` if `a` is continuable. + continuable = math_class(p.current_text()) == Some(MathClass::Alphabetic) + || p.current_text().chars().all(char::is_alphabetic); if !maybe_delimited(p) { p.eat(); } diff --git a/tests/ref/issue-4828-math-number-multi-char.png b/tests/ref/issue-4828-math-number-multi-char.png new file mode 100644 index 0000000000000000000000000000000000000000..ff0a9bab97de1957f3ea99de261346c14f8ccd9d GIT binary patch literal 465 zcmV;?0WSWDP)|ER(H<~| zY15fS>tQtA{}XtTVCdm*c-i;*JA9768pl*k6|TZn_hfVZBb`zLU zu}s@P^Y-#l!KFB0edr+gWfqVu9ueHV4bU7M05<^?+C#&I=3AT8;l;r86B6U-X$jKF za=L2`P;6~j!>cYf`lp^vQWqM|=kr@!WGr_t9x0mZz~X6-bI-fy7r^iQUO)9tl5%1- z#^oR^9F0bMsV8vm3s&yKnOscn)a^gub#$+3kwt>F^Kh*g7B-VXEpJLEO)(aS^x2H5 z;KpF747*FrtuipA^fu91SfEx|IQ_Y;k4A3B?xZqAmSN`-9qo4u4+mkcQ8L^4%lZSK z8oHWdWg0`nTS2m)v3YDC_`}G^hC`YiT`~W@dW%)K3RmbaR+3c8Kj6b400000NkvXX Hu0mjf!|2wg literal 0 HcmV?d00001 diff --git a/tests/ref/math-frac-precedence.png b/tests/ref/math-frac-precedence.png index 973c433e2c0d267aa57d746e586736c2b77b1a96..fd16f2e6bf3e043a81e4e6e3146a6b15c05fc559 100644 GIT binary patch literal 3586 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 IM6N<$f*h$L1poj5 literal 5504 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 diff --git a/tests/suite/math/frac.typ b/tests/suite/math/frac.typ index 7f513930a..3bd00eab2 100644 --- a/tests/suite/math/frac.typ +++ b/tests/suite/math/frac.typ @@ -37,8 +37,8 @@ $ 1/2/3 = (1/2)/3 = 1/(2/3) $ // Test precedence. $ a_1/b_2, 1/f(x), zeta(x)/2, "foo"[|x|]/2 \ 1.2/3.7, 2.3^3.4 \ - 🏳️‍🌈[x]/2, f [x]/2, phi [x]/2, 🏳️‍🌈 [x]/2 \ - +[x]/2, 1(x)/2, 2[x]/2 \ + f [x]/2, phi [x]/2 \ + +[x]/2, 1(x)/2, 2[x]/2, 🏳️‍🌈[x]/2 \ (a)b/2, b(a)[b]/2 \ n!/2, 5!/2, n !/2, 1/n!, 1/5! $ diff --git a/tests/suite/math/syntax.typ b/tests/suite/math/syntax.typ index 7091d908c..32b9c098c 100644 --- a/tests/suite/math/syntax.typ +++ b/tests/suite/math/syntax.typ @@ -28,6 +28,10 @@ $ dot \ dots \ ast \ tilde \ star $ $floor(phi.alt.)$ $floor(phi.alt. )$ +--- issue-4828-math-number-multi-char --- +// Numbers should parse the same regardless of number of characters. +$1/2(x)$ vs. $1/10(x)$ + --- math-unclosed --- // Error: 1-2 unclosed delimiter $a