From dfe361ec6a4adfab4140bf480aae3a39b2683165 Mon Sep 17 00:00:00 2001 From: sitandr <60141933+sitandr@users.noreply.github.com> Date: Mon, 10 Jul 2023 13:09:09 +0300 Subject: [PATCH] Better primes in math (#1614) --- crates/typst-library/src/lib.rs | 1 + crates/typst-library/src/math/attach.rs | 55 ++++++++++++++++++++++ crates/typst/src/eval/library.rs | 2 + crates/typst/src/eval/mod.rs | 19 +++++++- crates/typst/src/ide/highlight.rs | 2 + crates/typst/src/syntax/ast.rs | 23 +++++++++ crates/typst/src/syntax/kind.rs | 6 +++ crates/typst/src/syntax/lexer.rs | 3 +- crates/typst/src/syntax/parser.rs | 59 +++++++++++++++++++++++- tests/ref/math/opticalsize.png | Bin 6897 -> 11318 bytes tests/ref/math/syntax.png | Bin 4977 -> 4882 bytes tests/typ/math/opticalsize.typ | 15 ++++++ 12 files changed, 182 insertions(+), 3 deletions(-) diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs index 0bd88501c..f9b95615a 100644 --- a/crates/typst-library/src/lib.rs +++ b/crates/typst-library/src/lib.rs @@ -120,6 +120,7 @@ fn items() -> LangItems { } elem.pack() }, + math_primes: |count| math::PrimesElem::new(count).pack(), math_accent: |base, accent| { math::AccentElem::new(base, math::Accent::new(accent)).pack() }, diff --git a/crates/typst-library/src/math/attach.rs b/crates/typst-library/src/math/attach.rs index fedeb9088..ee65b657c 100644 --- a/crates/typst-library/src/math/attach.rs +++ b/crates/typst-library/src/math/attach.rs @@ -84,6 +84,61 @@ impl LayoutMath for AttachElem { } } +/// Grouped primes. +/// +/// ## Example { #example } +/// ```example +/// $ a'''_b = a^'''_b $ +/// ``` +/// +/// ## Syntax +/// This function has dedicated syntax: use apostrophes instead of primes. They +/// will automatically attach to the previous element, moving superscripts to +/// the next level. +/// +/// Display: Attachment +/// Category: math +#[element(LayoutMath)] +pub struct PrimesElem { + /// The number of grouped primes. + #[required] + pub count: usize, +} + +impl LayoutMath for PrimesElem { + #[tracing::instrument(skip(ctx))] + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + match self.count() { + count @ 1..=4 => { + let f = ctx.layout_fragment(&TextElem::packed(match count { + 1 => '′', + 2 => '″', + 3 => '‴', + 4 => '⁗', + _ => unreachable!(), + }))?; + ctx.push(f); + } + count => { + // Custom amount of primes + let prime = ctx.layout_fragment(&TextElem::packed('′'))?.into_frame(); + let width = prime.width() * (count + 1) as f64 / 2.0; + let mut frame = Frame::new(Size::new(width, prime.height())); + frame.set_baseline(prime.ascent()); + + for i in 0..count { + frame.push_frame( + Point::new(prime.width() * (i as f64 / 2.0), Abs::zero()), + prime.clone(), + ) + } + ctx.push(FrameFragment::new(ctx, frame)); + } + } + Ok(()) + } +} + /// Forces a base to display attachments as scripts. /// /// ## Example { #example } diff --git a/crates/typst/src/eval/library.rs b/crates/typst/src/eval/library.rs index 1b05de838..dcd78b89f 100644 --- a/crates/typst/src/eval/library.rs +++ b/crates/typst/src/eval/library.rs @@ -96,6 +96,8 @@ pub struct LangItems { tr: Option, br: Option, ) -> Content, + /// Grouped primes: `a'''`. + pub math_primes: fn(count: usize) -> Content, /// A base with an accent: `arrow(x)`. pub math_accent: fn(base: Content, accent: char) -> Content, /// A fraction in math: `x/2`. diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs index 97cad97db..b76765e6e 100644 --- a/crates/typst/src/eval/mod.rs +++ b/crates/typst/src/eval/mod.rs @@ -460,6 +460,7 @@ impl Eval for ast::Expr { Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content), Self::MathDelimited(v) => v.eval(vm).map(Value::Content), Self::MathAttach(v) => v.eval(vm).map(Value::Content), + Self::MathPrimes(v) => v.eval(vm).map(Value::Content), Self::MathFrac(v) => v.eval(vm).map(Value::Content), Self::MathRoot(v) => v.eval(vm).map(Value::Content), Self::Ident(v) => v.eval(vm), @@ -733,12 +734,28 @@ impl Eval for ast::MathAttach { #[tracing::instrument(name = "MathAttach::eval", skip_all)] fn eval(&self, vm: &mut Vm) -> SourceResult { let base = self.base().eval_display(vm)?; - let top = self.top().map(|expr| expr.eval_display(vm)).transpose()?; + + let mut top = self.top().map(|expr| expr.eval_display(vm)).transpose()?; + if top.is_none() { + if let Some(primes) = self.primes() { + top = Some(primes.eval(vm)?); + } + } + let bottom = self.bottom().map(|expr| expr.eval_display(vm)).transpose()?; Ok((vm.items.math_attach)(base, top, bottom, None, None, None, None)) } } +impl Eval for ast::MathPrimes { + type Output = Content; + + #[tracing::instrument(name = "MathPrimes::eval", skip_all)] + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.math_primes)(self.count())) + } +} + impl Eval for ast::MathFrac { type Output = Content; diff --git a/crates/typst/src/ide/highlight.rs b/crates/typst/src/ide/highlight.rs index 2db636e32..c9748e922 100644 --- a/crates/typst/src/ide/highlight.rs +++ b/crates/typst/src/ide/highlight.rs @@ -147,6 +147,7 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::MathAttach => None, SyntaxKind::MathFrac => None, SyntaxKind::MathRoot => None, + SyntaxKind::MathPrimes => None, SyntaxKind::Hashtag => highlight_hashtag(node), SyntaxKind::LeftBrace => Some(Tag::Punctuation), @@ -174,6 +175,7 @@ pub fn highlight(node: &LinkedNode) -> Option { _ => Tag::Operator, }), SyntaxKind::Hat => Some(Tag::MathOperator), + SyntaxKind::Prime => Some(Tag::MathOperator), SyntaxKind::Dot => Some(Tag::Punctuation), SyntaxKind::Eq => match node.parent_kind() { Some(SyntaxKind::Heading) => None, diff --git a/crates/typst/src/syntax/ast.rs b/crates/typst/src/syntax/ast.rs index 7d5e2989c..4a0de424d 100644 --- a/crates/typst/src/syntax/ast.rs +++ b/crates/typst/src/syntax/ast.rs @@ -124,6 +124,8 @@ pub enum Expr { MathDelimited(MathDelimited), /// A base with optional attachments in math: `a_1^2`. MathAttach(MathAttach), + /// Grouped math primes + MathPrimes(MathPrimes), /// A fraction in math: `x/2`. MathFrac(MathFrac), /// A root in math: `√x`, `∛x` or `∜x`. @@ -224,6 +226,7 @@ impl AstNode for Expr { SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint), SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited), SyntaxKind::MathAttach => node.cast().map(Self::MathAttach), + SyntaxKind::MathPrimes => node.cast().map(Self::MathPrimes), SyntaxKind::MathFrac => node.cast().map(Self::MathFrac), SyntaxKind::MathRoot => node.cast().map(Self::MathRoot), SyntaxKind::Ident => node.cast().map(Self::Ident), @@ -285,6 +288,7 @@ impl AstNode for Expr { Self::MathAlignPoint(v) => v.as_untyped(), Self::MathDelimited(v) => v.as_untyped(), Self::MathAttach(v) => v.as_untyped(), + Self::MathPrimes(v) => v.as_untyped(), Self::MathFrac(v) => v.as_untyped(), Self::MathRoot(v) => v.as_untyped(), Self::Ident(v) => v.as_untyped(), @@ -841,6 +845,25 @@ impl MathAttach { .skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat)) .find_map(SyntaxNode::cast) } + + /// Extract primes if present. + pub fn primes(&self) -> Option { + self.0.cast_first_match() + } +} + +node! { + /// Grouped primes in math: `a'''`. + MathPrimes +} + +impl MathPrimes { + pub fn count(&self) -> usize { + self.0 + .children() + .filter(|node| matches!(node.kind(), SyntaxKind::Prime)) + .count() + } } node! { diff --git a/crates/typst/src/syntax/kind.rs b/crates/typst/src/syntax/kind.rs index 0c24c6674..491197208 100644 --- a/crates/typst/src/syntax/kind.rs +++ b/crates/typst/src/syntax/kind.rs @@ -65,6 +65,8 @@ pub enum SyntaxKind { MathDelimited, /// A base with optional attachments in math: `a_1^2`. MathAttach, + /// Grouped primes in math: `a'''`. + MathPrimes, /// A fraction in math: `x/2`. MathFrac, /// A root in math: `√x`, `∛x` or `∜x`. @@ -108,6 +110,8 @@ pub enum SyntaxKind { Slash, /// The superscript operator in math: `^`. Hat, + /// The prime in math: `'`. + Prime, /// The field access and method call operator: `.`. Dot, /// The assignment operator: `=`. @@ -378,6 +382,7 @@ impl SyntaxKind { Self::MathAttach => "math attachments", Self::MathFrac => "math fraction", Self::MathRoot => "math root", + Self::MathPrimes => "math primes", Self::Hashtag => "hashtag", Self::LeftBrace => "opening brace", Self::RightBrace => "closing brace", @@ -395,6 +400,7 @@ impl SyntaxKind { Self::Minus => "minus", Self::Slash => "slash", Self::Hat => "hat", + Self::Prime => "prime", Self::Dot => "dot", Self::Eq => "equals sign", Self::EqEq => "equality operator", diff --git a/crates/typst/src/syntax/lexer.rs b/crates/typst/src/syntax/lexer.rs index d95b5b7b8..8fe08f4cc 100644 --- a/crates/typst/src/syntax/lexer.rs +++ b/crates/typst/src/syntax/lexer.rs @@ -422,13 +422,14 @@ impl Lexer<'_> { '|' if self.s.eat_if('|') => SyntaxKind::Shorthand, '~' if self.s.eat_if("~>") => SyntaxKind::Shorthand, '~' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '*' | '\'' | '-' => SyntaxKind::Shorthand, + '*' | '-' => SyntaxKind::Shorthand, '#' => SyntaxKind::Hashtag, '_' => SyntaxKind::Underscore, '$' => SyntaxKind::Dollar, '/' => SyntaxKind::Slash, '^' => SyntaxKind::Hat, + '\'' => SyntaxKind::Prime, '&' => SyntaxKind::MathAlignPoint, '√' | '∛' | '∜' => SyntaxKind::Root, diff --git a/crates/typst/src/syntax/parser.rs b/crates/typst/src/syntax/parser.rs index bd78df20f..5cb31e5ed 100644 --- a/crates/typst/src/syntax/parser.rs +++ b/crates/typst/src/syntax/parser.rs @@ -295,6 +295,18 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { } } + SyntaxKind::Prime => { + // Means that there is nothing to attach the prime to. + continuable = true; + while p.at(SyntaxKind::Prime) { + let m2 = p.marker(); + p.eat(); + // Eat the group until the space. + while p.eat_if_direct(SyntaxKind::Prime) {} + p.wrap(m2, SyntaxKind::MathPrimes); + } + } + _ => p.expected("expression"), } @@ -306,6 +318,9 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { p.wrap(m, SyntaxKind::Math); } + // Whether there were _any_ primes in the loop. + let mut primed = false; + while !p.eof() && !p.at(stop) { if p.directly_at(SyntaxKind::Text) && p.current_text() == "!" { p.eat(); @@ -313,10 +328,39 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { continue; } + let prime_marker = p.marker(); + if p.eat_if_direct(SyntaxKind::Prime) { + // Eat as many primes as possible. + while p.eat_if_direct(SyntaxKind::Prime) {} + p.wrap(prime_marker, SyntaxKind::MathPrimes); + + // Will not be continued, so need to wrap the prime as attachment. + if p.at(stop) { + p.wrap(m, SyntaxKind::MathAttach); + } + + primed = true; + continue; + } + + // Separate primes and superscripts to different attachments. + if primed && p.current() == SyntaxKind::Hat { + p.wrap(m, SyntaxKind::MathAttach); + } + let Some((kind, stop, assoc, mut prec)) = math_op(p.current()) else { + // No attachments, so we need to wrap primes as attachment. + if primed { + p.wrap(m, SyntaxKind::MathAttach); + } + break; }; + if primed && kind == SyntaxKind::MathFrac { + p.wrap(m, SyntaxKind::MathAttach); + } + if prec < min_prec { break; } @@ -335,7 +379,7 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { math_expr_prec(p, prec, stop); math_unparen(p, m2); - if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { + if p.eat_if(SyntaxKind::Underscore) || (!primed && p.eat_if(SyntaxKind::Hat)) { let m3 = p.marker(); math_expr_prec(p, prec, SyntaxKind::Eof); math_unparen(p, m3); @@ -1451,6 +1495,10 @@ impl<'s> Parser<'s> { self.current == kind && self.prev_end == self.current_start } + /// Eats if at `kind`. + /// + /// Note: In math and code mode, this will ignore trivia in front of the + /// `kind`, To forbid skipping trivia, consider using `eat_if_direct`. fn eat_if(&mut self, kind: SyntaxKind) -> bool { let at = self.at(kind); if at { @@ -1459,6 +1507,15 @@ impl<'s> Parser<'s> { at } + /// Eats only if currently at the start of `kind`. + fn eat_if_direct(&mut self, kind: SyntaxKind) -> bool { + let at = self.directly_at(kind); + if at { + self.eat(); + } + at + } + fn convert(&mut self, kind: SyntaxKind) { self.current = kind; self.eat(); diff --git a/tests/ref/math/opticalsize.png b/tests/ref/math/opticalsize.png index 9fec5520bb586d2c130c7df219fcc4db019e4bf4..5c338ab6ea27b4c91e59d2c9e97c8dd9d4d133aa 100644 GIT binary patch delta 8709 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 V`qskt^snO{N^&n`%cM;L{|{%#`q}^h delta 4200 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 diff --git a/tests/ref/math/syntax.png b/tests/ref/math/syntax.png index 028e21d6b7908e2afe459dde5df237560ee44203..d828a478d13b8f82f2d6407dae6d51923ed29248 100644 GIT binary patch literal 4882 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