diff --git a/crates/math-core/src/character_class.rs b/crates/math-core/src/character_class.rs index a28c5b6a..1cc556d3 100644 --- a/crates/math-core/src/character_class.rs +++ b/crates/math-core/src/character_class.rs @@ -17,4 +17,6 @@ pub enum Class { Punctuation, /// `mathinner` Inner, + /// A class indicating the end of the current formula. + End, } diff --git a/crates/math-core/src/parser.rs b/crates/math-core/src/parser.rs index f0d763cd..0bae4397 100644 --- a/crates/math-core/src/parser.rs +++ b/crates/math-core/src/parser.rs @@ -223,10 +223,12 @@ where ) -> ParseResult<(Class, &'arena Node<'arena>)> { let (cur_token, span) = cur_tokloc?.into_parts(); let mut class = Class::default(); - let next_class = self - .tokens - .peek_class_token()? - .class(parse_as.in_sequence(), self.state.right_boundary_hack); + let next_class = self.tokens.peek_class_token(parse_as.in_sequence())?; + let next_class = if self.state.right_boundary_hack && matches!(next_class, Class::End) { + Class::Default + } else { + next_class + }; let node: Result = match cur_token { Token::Digit(number) => 'digit: { if let Some(MathVariant::Transform(tf)) = self.state.transform { @@ -334,7 +336,7 @@ where } Token::Punctuation(punc) => { class = Class::Punctuation; - let right = if matches!(next_class, Class::Close) || self.state.script_style { + let right = if matches!(next_class, Class::End) || self.state.script_style { Some(MathSpacing::Zero) } else { None @@ -346,6 +348,20 @@ where right, }) } + Token::ForcePunctuation(op) => { + class = Class::Punctuation; + let right = if matches!(next_class, Class::End) || self.state.script_style { + Some(MathSpacing::Zero) + } else { + Some(MathSpacing::ThreeMu) + }; + Ok(Node::Operator { + op, + attrs: OpAttrs::empty(), + left: Some(MathSpacing::Zero), + right, + }) + } Token::Ord(ord) => { let attr = if matches!(ord.category(), OrdCategory::FGandForceDefault) { // Category F+G operators will stretch in pre- and postfix positions, @@ -430,10 +446,7 @@ where }; class = Class::BinaryOp; // Recompute the next class: - let next_class = self - .tokens - .peek_class_token()? - .class(parse_as.in_sequence(), self.state.right_boundary_hack); + let next_class = self.tokens.peek_class_token(parse_as.in_sequence())?; let spacing = self.state .bin_op_spacing(parse_as.in_sequence(), prev_class, next_class, true); @@ -459,14 +472,16 @@ where } else { None }; - let right = - if matches!(next_class, Class::Relation | Class::BinaryOp | Class::Close) - || (self.state.script_style && !matches!(next_class, Class::Operator)) - { - Some(MathSpacing::Zero) - } else { - None - }; + let right = if matches!( + next_class, + Class::Relation | Class::BinaryOp | Class::Close | Class::End + ) || (self.state.script_style + && !matches!(next_class, Class::Operator)) + { + Some(MathSpacing::Zero) + } else { + None + }; Ok(Node::Operator { op: op.as_op(), attrs: OpAttrs::empty(), @@ -476,6 +491,7 @@ where } Token::OpGreaterThan => { let (left, right) = self.state.relation_spacing(prev_class, next_class); + class = Class::Relation; Ok(Node::PseudoOp { name: ">", attrs: OpAttrs::empty(), @@ -485,6 +501,7 @@ where } Token::OpLessThan => { let (left, right) = self.state.relation_spacing(prev_class, next_class); + class = Class::Relation; Ok(Node::PseudoOp { name: "<", attrs: OpAttrs::empty(), @@ -802,10 +819,7 @@ where // `\not` has to be followed by something: let (tok, new_span) = self.next_token()?.into_parts(); // Recompute the next class: - let next_class = self - .tokens - .peek_class_token()? - .class(parse_as.in_sequence(), self.state.right_boundary_hack); + let next_class = self.tokens.peek_class_token(parse_as.in_sequence())?; match tok { Token::Relation(op) => { let (left, right) = self.state.relation_spacing(prev_class, next_class); @@ -1509,11 +1523,8 @@ where explicit: bool, ) -> ParseResult<(Option, Option)> { // We re-determine the next class here, because the next token may have changed - // because we discarded bounds or limits tokens. - let next_class = self - .tokens - .peek_class_token()? - .class(parse_as.in_sequence(), self.state.right_boundary_hack); + // because we discarded bounds tokens. + let next_class = self.tokens.peek_class_token(parse_as.in_sequence())?; Ok(( if matches!( prev_class, @@ -1532,8 +1543,9 @@ where }, if matches!( next_class, - Class::Relation | Class::Punctuation | Class::Open | Class::Close - ) { + Class::Relation | Class::Punctuation | Class::Open | Class::Close | Class::End + ) || (self.state.script_style && matches!(next_class, Class::Inner)) + { Some(MathSpacing::Zero) } else if explicit { Some(MathSpacing::ThreeMu) @@ -1690,7 +1702,7 @@ impl ParserState<'_> { }, if matches!( next_class, - Class::Relation | Class::Punctuation | Class::Close + Class::Relation | Class::Punctuation | Class::Close | Class::End ) || self.script_style { Some(MathSpacing::Zero) @@ -1715,7 +1727,7 @@ impl ParserState<'_> { Class::Relation | Class::Punctuation | Class::BinaryOp | Class::Operator | Class::Open ) || matches!( next_class, - Class::Relation | Class::Punctuation | Class::Close + Class::Relation | Class::Punctuation | Class::Close | Class::End ) || self.script_style { Some(MathSpacing::Zero) diff --git a/crates/math-core/src/predefined.rs b/crates/math-core/src/predefined.rs index 4b1506ec..119e89df 100644 --- a/crates/math-core/src/predefined.rs +++ b/crates/math-core/src/predefined.rs @@ -50,13 +50,13 @@ pub static XLEFTARROW: [Token<'static>; 7] = [ pub static DOTS: [Token<'static>; 3] = [ Inner(symbol::FULL_STOP), - Op(symbol::FULL_STOP), + ForcePunctuation(symbol::FULL_STOP.as_op()), Inner(symbol::FULL_STOP), ]; pub static CDOTS: [Token<'static>; 3] = [ Inner(symbol::MIDDLE_DOT), - Op(symbol::MIDDLE_DOT), + ForcePunctuation(symbol::MIDDLE_DOT.as_op()), Inner(symbol::MIDDLE_DOT), ]; diff --git a/crates/math-core/src/token.rs b/crates/math-core/src/token.rs index c70a3b6e..3a69a912 100644 --- a/crates/math-core/src/token.rs +++ b/crates/math-core/src/token.rs @@ -110,6 +110,10 @@ pub enum Token<'source> { /// The character `&`. /// It has its own token because we need to escape it for the HTML output. OpAmpersand, + /// A token to force an operator to behave like a binary operator (mathbin). + /// This is, for example, needed for `×`, which in LaTeX is a binary operator, + /// but in MathML Core is a "big operator" (mathop). + ForceBinaryOp(MathMLOperator), /// A token to force an operator to behave like a relation (mathrel). /// This is, for example, needed for `:`, which in LaTeX is a relation, /// but in MathML Core is a separator (punctuation). @@ -118,10 +122,8 @@ pub enum Token<'source> { /// This is, for example, needed for `!`, which in LaTeX is a closing symbol, /// but in MathML Core is an ordinary operator. ForceClose(MathMLOperator), - /// A token to force an operator to behave like a binary operator (mathbin). - /// This is, for example, needed for `×`, which in LaTeX is a binary operator, - /// but in MathML Core is a "big operator" (mathop). - ForceBinaryOp(MathMLOperator), + /// A token to force an operator to behave like punctuation (mathpunct). + ForcePunctuation(MathMLOperator), /// `\mathbin` Mathbin, /// A token to allow a relation to stretch. @@ -192,35 +194,61 @@ static_assertions::assert_eq_size!(Result, &'static i32>, [usize; 3]); impl Token<'_> { /// Returns the character class of this token. - pub(super) fn class(&self, in_sequence: bool, ignore_end_tokens: bool) -> Class { - if !in_sequence { - return Class::Default; - } + pub(super) fn class(&self) -> Option { + use Token::*; match self.unwrap_math_ref() { - Token::Relation(_) | Token::ForceRelation(_) => Class::Relation, - Token::Punctuation(_) => Class::Punctuation, - Token::Open(_) | Token::Left | Token::SquareBracketOpen => Class::Open, - Token::Close(_) - | Token::SquareBracketClose - | Token::NewColumn - | Token::ForceClose(_) => Class::Close, - Token::BinaryOp(_) | Token::ForceBinaryOp(_) | Token::Mathbin => Class::BinaryOp, - Token::Op(_) => Class::Operator, - Token::End(_) | Token::Right | Token::GroupEnd | Token::Eoi if !ignore_end_tokens => { - Class::Close + Relation(_) | ForceRelation(_) | OpGreaterThan | OpLessThan | StretchyRel(_) => { + Some(Class::Relation) } - Token::Inner(_) => Class::Inner, - // `\big` commands without the "l" or "r" really produce `Class::Default`. - Token::Big(_, Some(paren_type)) => { - if matches!(paren_type, ParenType::Open) { - Class::Open - } else { - Class::Close - } + Punctuation(_) | ForcePunctuation(_) => Some(Class::Punctuation), + Open(_) | Left | SquareBracketOpen | Begin(_) | GroupBegin => Some(Class::Open), + Close(_) | SquareBracketClose | ForceClose(_) | Right => Some(Class::Close), + BinaryOp(_) | ForceBinaryOp(_) | Mathbin => Some(Class::BinaryOp), + Op(_) | PseudoOperator(_) | PseudoOperatorLimits(_) | OperatorName(_) => { + Some(Class::Operator) } - // TODO: This needs to skip spaces and other non-class tokens in the token sequence. - Token::CustomCmd(_, [head, ..]) => head.class(in_sequence, ignore_end_tokens), - _ => Class::Default, + End(_)| NewColumn | GroupEnd | Eoi => Some(Class::End), + Inner(_) => Some(Class::Inner), + Big(_, Some(paren_type)) => Some(if matches!(paren_type, ParenType::Open) { + Class::Open + } else { + Class::Close + }), + CustomCmd(_, toks) => toks.iter().find_map(|tok| tok.class()), + Whitespace | Space(_) | Not | TransformSwitch(_) | NoNumber | Tag | CustomSpace + | Limits | NonBreakingSpace => None, + Letter(_, _) + | UprightLetter(_) + | Digit(_) + // `\big` commands without the "l" or "r" really produce `Class::Default`. + | Big(_, None) + | NewLine + | Middle + | Frac(_) + | Genfrac + | Underscore + | Circumflex + | Binom(_) + | Overset + | Underset + | OverUnderBrace(_, _) + | Sqrt + | Transform(_) + | Ord(_) + | Prime + | Enclose(_) + | OpAmpersand + | Slashed + | Text(_) + | Style(_) + | Color + | CustomCmdArg(_) + | HardcodedMathML(_) + | TextMode(_) + | MathOrTextMode(_, _) + | UnknownCommand(_) + | InternalStringLiteral(_) + | Accent(_, _, _) => Some(Class::Default), } } @@ -332,11 +360,6 @@ impl<'config> TokSpan<'config> { pub fn span(&self) -> Span { self.1 } - - #[inline] - pub(super) fn class(&self, in_sequence: bool, ignore_end_tokens: bool) -> Class { - self.0.class(in_sequence, ignore_end_tokens) - } } impl<'config> From> for TokSpan<'config> { diff --git a/crates/math-core/src/token_queue.rs b/crates/math-core/src/token_queue.rs index 85600ef6..c75bdffe 100644 --- a/crates/math-core/src/token_queue.rs +++ b/crates/math-core/src/token_queue.rs @@ -1,6 +1,7 @@ use std::{collections::VecDeque, ops::Range}; use crate::{ + character_class::Class, error::{LatexErrKind, LatexError}, lexer::Lexer, token::{EndToken, Span, TokSpan, Token}, @@ -25,48 +26,54 @@ impl<'source, 'config> TokenQueue<'source, 'config> { next_non_whitespace: 0, }; // Ensure that we have at least one non-whitespace token in the buffer for peeking. - let offset = tm.load_token(SkipMode::Whitespace)?; - tm.next_non_whitespace = offset; + let idx = tm.load_token_skip_whitespace()?; + tm.next_non_whitespace = idx; Ok(tm) } + /// Load the next non-whitespace token from the lexer into the buffer, and return its index. + fn load_token_skip_whitespace(&mut self) -> Result> { + Ok(self + .load_token(is_not_whitespace)? + .unwrap_or_else(|| self.queue.len())) + } + /// Load the next not-skipped token from the lexer into the buffer. /// If the end of the input is reached, this will return early. - fn load_token(&mut self, skip_mode: SkipMode) -> Result> { + fn load_token( + &mut self, + predicate: fn(usize, &Token) -> Option, + ) -> Result, Box> { if self.lexer_is_eoi { - // Returning here with offset 0 is the right thing to do, - // because it will result in an index that is one past the end of the buffer. - return Ok(0); + return Ok(None); } + let starting_len = self.queue.len(); let mut non_skipped_offset = 0usize; - let predicate = match skip_mode { - SkipMode::Whitespace => is_not_whitespace, - SkipMode::NoClass => has_class, - }; loop { let tok = self.lexer.next_token()?; - let not_skipped = predicate(&tok); + let result = predicate(starting_len + non_skipped_offset, tok.token()); let is_eoi = matches!(tok.token(), Token::Eoi); self.queue.push_back(tok); - if not_skipped { - break; + if let Some(result) = result { + return Ok(Some(result)); } non_skipped_offset += 1; if is_eoi { self.lexer_is_eoi = true; - // We return with the offset for one past the EOI token. - // This is needed to ensure that peek() works correctly. - break; + return Ok(None); } } - Ok(non_skipped_offset) } /// Perform a linear search to find the next non-whitespace token in the buffer. fn find_next_non_whitespace(&self) -> Option { - self.queue.iter().position(is_not_whitespace) + self.queue + .iter() + .position(|tokspan| !matches!(tokspan.token(), Token::Whitespace)) } + /// Ensure that `next_non_whitespace` points to the next non-whitespace token in the buffer, + /// or to one past the end if there is none. fn ensure_next_non_whitespace(&mut self) -> Result<(), Box> { let pos = 'pos_calc: { // First, try to find the next non-whitespace token in the existing buffer. @@ -76,8 +83,7 @@ impl<'source, 'config> TokenQueue<'source, 'config> { break 'pos_calc pos; } // Then, try to load more tokens until we find one or reach EOI. - let starting_len = self.queue.len(); - starting_len + self.load_token(SkipMode::Whitespace)? + self.load_token_skip_whitespace()? }; self.next_non_whitespace = pos; Ok(()) @@ -101,72 +107,75 @@ impl<'source, 'config> TokenQueue<'source, 'config> { } } - /// Find or load a token which is not skipped according to `skip_mode`. + /// Find or load a token which is not skipped according to `predicate`. /// /// This function starts its search after `next_non_whitespace` (i.e., it skips /// the first non-whitespace token). The idea is that the caller has already /// checked `next_non_whitespace` or is not interested in it. - /// - /// This function returns an index instead of a reference to the token - /// in order to avoid issues with the borrow checker. - fn find_or_load_after_next( + fn find_or_load_after_next( &mut self, - skip_mode: SkipMode, - ) -> Result<&TokSpan<'source>, Box> { + predicate: fn(usize, &Token) -> Option, + ) -> Result, Box> { // We use a block here which returns an index to avoid borrow checker issues. - let tok_idx = { + let result = { // Ensure that the compiler can tell that `self.queue.range(start..)` // cannot panic due to being out of bounds. let start = self.next_non_whitespace; if start < self.queue.len() { let mut range = self.queue.range(start..); range.next(); // Skip `next_non_whitespace`. - let predicate = match skip_mode { - SkipMode::Whitespace => is_not_whitespace, - SkipMode::NoClass => has_class, - }; - range.position(predicate).map(|pos| start + 1 + pos) + range + .enumerate() + .find_map(|(idx, ts)| predicate(start + 1 + idx, ts.token())) } else { debug_assert!( self.lexer_is_eoi, "find_or_load_after_next called without ensure" ); - Some(self.queue.len()) + return Ok(None); } }; - if let Some(tok_idx) = tok_idx { + if let Some(result) = result { // If we found a token in the existing buffer, return it. - Ok(self.queue.get(tok_idx).unwrap_or(&EOI_TOK)) + Ok(Some(result)) } else { // Otherwise, load more tokens until we find one or reach EOI. - let starting_len = self.queue.len(); - let offset = self.load_token(skip_mode)?; - if let Some(tok) = self.queue.get(starting_len + offset) { - Ok(tok) - } else { - debug_assert!( - self.lexer_is_eoi, - "find_or_load_after_next called without ensure" - ); - Ok(&EOI_TOK) - } + self.load_token(predicate) } } + /// Peek at the second non-whitespace token without consuming it. pub(super) fn peek_second(&mut self) -> Result<&TokSpan<'source>, Box> { - self.find_or_load_after_next(SkipMode::Whitespace) + if let Some(tok) = self + .find_or_load_after_next(is_not_whitespace)? + .and_then(|idx| self.queue.get(idx)) + { + Ok(tok) + } else { + debug_assert!(self.lexer_is_eoi, "peek_second called without ensure"); + Ok(&EOI_TOK) + } } /// Peek at the first token which has a character class. /// /// This excludes, for example, `Space` tokens. - pub(super) fn peek_class_token(&mut self) -> Result<&TokSpan<'source>, Box> { + pub(super) fn peek_class_token(&mut self, in_sequence: bool) -> Result> { + if !in_sequence { + return Ok(Class::Default); + } // First check the common case where the next token is already a token with class. - if has_class(self.peek()) { - return Ok(self.peek()); + if let Some(class) = self.peek().token().class() { + return Ok(class); + } + if let Some(class) = self.find_or_load_after_next(has_class)? { + Ok(class) + } else { + debug_assert!(self.lexer_is_eoi, "peek_class_token called without ensure"); + // EOI is treated as having class `Close`. + Ok(Class::Close) } - self.find_or_load_after_next(SkipMode::NoClass) } /// Get the next math-mode token. @@ -220,6 +229,9 @@ impl<'source, 'config> TokenQueue<'source, 'config> { } } + /// Queue a stream of tokens in the front of the buffer. + /// + /// We use a ring buffer, so this is efficient as long as the number of tokens is not too large. pub(super) fn queue_in_front(&mut self, tokens: &[impl Into> + Copy]) { self.queue.reserve(tokens.len()); // Queue the token stream in the front in reverse order. @@ -311,21 +323,12 @@ impl<'source, 'config> TokenQueue<'source, 'config> { } } -fn is_not_whitespace(tok: &TokSpan) -> bool { - !matches!(tok.token(), Token::Whitespace) -} - -fn has_class(tok: &TokSpan) -> bool { - !matches!( - tok.token().unwrap_math_ref(), - Token::Whitespace | Token::Space(_) | Token::Not | Token::TransformSwitch(_) - ) +fn is_not_whitespace(idx: usize, tok: &Token) -> Option { + (!matches!(tok, Token::Whitespace)).then_some(idx) } -#[derive(Debug, Clone, Copy)] -enum SkipMode { - Whitespace, - NoClass, +fn has_class(_idx: usize, tok: &Token) -> Option { + tok.class() } /// A macro argument, which is either a single token or a group of tokens. @@ -396,8 +399,8 @@ mod tests { let lexer = Lexer::new(problem, false, None); let mut manager = TokenQueue::new(lexer).expect("Failed to create TokenManager"); // Load up some tokens to ensure the code can deal with that. - manager.load_token(SkipMode::Whitespace).unwrap(); - manager.load_token(SkipMode::Whitespace).unwrap(); + manager.load_token_skip_whitespace().unwrap(); + manager.load_token_skip_whitespace().unwrap(); // Check that the first token is `GroupBegin`. assert!(matches!(manager.next().unwrap().token(), Token::GroupBegin)); let mut tokens = Vec::new(); @@ -456,18 +459,20 @@ mod tests { assert!(matches!(queue.peek().token(), Token::Letter('y', _))); // Test the branch that needs to load more tokens. - let tok = queue.find_or_load_after_next(SkipMode::Whitespace).unwrap(); - assert!(matches!(tok.token(), Token::Letter('z', _))); + let tok_idx = queue.find_or_load_after_next(is_not_whitespace).unwrap(); + assert!(matches!(tok_idx, Some(3))); assert_eq!(queue.queue.len(), 4); assert!(matches!(queue.queue[0].token(), Token::Whitespace)); assert!(matches!(queue.queue[2].token(), Token::Whitespace)); + assert!(matches!(queue.queue[3].token(), Token::Letter('z', _))); // Test the branch that finds the token in the existing buffer. - let tok = queue.find_or_load_after_next(SkipMode::Whitespace).unwrap(); - assert!(matches!(tok.token(), Token::Letter('z', _))); + let tok_idx = queue.find_or_load_after_next(is_not_whitespace).unwrap(); + assert!(matches!(tok_idx, Some(3))); assert_eq!(queue.queue.len(), 4); assert!(matches!(queue.queue[0].token(), Token::Whitespace)); assert!(matches!(queue.queue[2].token(), Token::Whitespace)); + assert!(matches!(queue.queue[3].token(), Token::Letter('z', _))); } #[test] diff --git a/crates/math-core/tests/conversion_test.rs b/crates/math-core/tests/conversion_test.rs index 397c2ec5..61169cfd 100644 --- a/crates/math-core/tests/conversion_test.rs +++ b/crates/math-core/tests/conversion_test.rs @@ -257,6 +257,7 @@ fn main() { ("overset_real_prime", r"\overset{\prime}{=}x"), ("overset_plus", r"\overset{!}{+}"), ("overset_implies", r"\overset{\implies}{xxxxxxx}"), + ("overset_with_left_right", r"\overset{!}{\left(x+\right)}"), ("exclamation_mark_spacing", r"x=!=x"), ("int_limit_prime", r"\int\limits'"), ("prime_command", r"f^\prime"), @@ -347,6 +348,7 @@ fn main() { ("idotsint_with_limits", r"\idotsint\limits_0^1 xy"), ("cdots_before_open", r"4 + \cdots ()"), ("cdots_before_close", r"{\sum \cdots}"), + ("dots_in_subscript", r"x_{\ldots\log}"), ("infix_double_bar", r"x \| y"), ("underset_tilde", r"\underset{z\sim Z}{\mathbb{E}}"), ("bra_and_ket", r"x\bra{\uparrow} + \ket{\downarrow}y"), @@ -354,6 +356,7 @@ fn main() { ("mathbin_no_braces", r"x\mathbin|y"), ("mathbin_paren", r"{\frac12\mathbin)y}"), ("mathbin_after_dots", r"\dots\mathbin+, \dots+"), + ("comma_before_close_paren", r"4,)"), ]; let config = MathCoreConfig { diff --git a/crates/math-core/tests/snapshots/conversion_test__cdots_before_close.snap b/crates/math-core/tests/snapshots/conversion_test__cdots_before_close.snap index c93c88af..afc50190 100644 --- a/crates/math-core/tests/snapshots/conversion_test__cdots_before_close.snap +++ b/crates/math-core/tests/snapshots/conversion_test__cdots_before_close.snap @@ -6,7 +6,7 @@ expression: "{\\sum \\cdots}" · - · + · · diff --git a/crates/math-core/tests/snapshots/conversion_test__cdots_before_open.snap b/crates/math-core/tests/snapshots/conversion_test__cdots_before_open.snap index 2a4881c1..fb82b7cf 100644 --- a/crates/math-core/tests/snapshots/conversion_test__cdots_before_open.snap +++ b/crates/math-core/tests/snapshots/conversion_test__cdots_before_open.snap @@ -6,7 +6,7 @@ expression: "4 + \\cdots ()" 4 + · - · + · · ( ) diff --git a/crates/math-core/tests/snapshots/conversion_test__comma_before_close_paren.snap b/crates/math-core/tests/snapshots/conversion_test__comma_before_close_paren.snap new file mode 100644 index 00000000..a63c46d4 --- /dev/null +++ b/crates/math-core/tests/snapshots/conversion_test__comma_before_close_paren.snap @@ -0,0 +1,9 @@ +--- +source: crates/math-core/tests/conversion_test.rs +expression: "4,)" +--- + + 4 + , + ) + diff --git a/crates/math-core/tests/snapshots/conversion_test__dots_in_subscript.snap b/crates/math-core/tests/snapshots/conversion_test__dots_in_subscript.snap new file mode 100644 index 00000000..ab3aefed --- /dev/null +++ b/crates/math-core/tests/snapshots/conversion_test__dots_in_subscript.snap @@ -0,0 +1,15 @@ +--- +source: crates/math-core/tests/conversion_test.rs +expression: "x_{\\ldots\\log}" +--- + + + x + + . + . + . + log + + + diff --git a/crates/math-core/tests/snapshots/conversion_test__idotsint.snap b/crates/math-core/tests/snapshots/conversion_test__idotsint.snap index baa36af1..b8906f5b 100644 --- a/crates/math-core/tests/snapshots/conversion_test__idotsint.snap +++ b/crates/math-core/tests/snapshots/conversion_test__idotsint.snap @@ -6,7 +6,7 @@ expression: "x \\idotsint =" x · - · + · · = diff --git a/crates/math-core/tests/snapshots/conversion_test__idotsint_with_limits.snap b/crates/math-core/tests/snapshots/conversion_test__idotsint_with_limits.snap index 137adf02..121bc7dd 100644 --- a/crates/math-core/tests/snapshots/conversion_test__idotsint_with_limits.snap +++ b/crates/math-core/tests/snapshots/conversion_test__idotsint_with_limits.snap @@ -5,7 +5,7 @@ expression: "\\idotsint\\limits_0^1 xy" · - · + · · diff --git a/crates/math-core/tests/snapshots/conversion_test__mathbin_after_dots.snap b/crates/math-core/tests/snapshots/conversion_test__mathbin_after_dots.snap index fdf20a1e..6990e2ef 100644 --- a/crates/math-core/tests/snapshots/conversion_test__mathbin_after_dots.snap +++ b/crates/math-core/tests/snapshots/conversion_test__mathbin_after_dots.snap @@ -4,12 +4,12 @@ expression: "\\dots\\mathbin+, \\dots+" --- . - . + . . + , . - . + . . + diff --git a/crates/math-core/tests/snapshots/conversion_test__overset_with_left_right.snap b/crates/math-core/tests/snapshots/conversion_test__overset_with_left_right.snap new file mode 100644 index 00000000..1328a62a --- /dev/null +++ b/crates/math-core/tests/snapshots/conversion_test__overset_with_left_right.snap @@ -0,0 +1,17 @@ +--- +source: crates/math-core/tests/conversion_test.rs +expression: "\\overset{!}{\\left(x+\\right)}" +--- + + + + ( + + x + + + + ) + + ! + + diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki096.snap b/crates/math-core/tests/snapshots/wiki_test__wiki096.snap index 56bdac9a..43eae92f 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki096.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki096.snap @@ -10,9 +10,9 @@ expression: "\\amalg \\P \\S \\% \\dagger\\ddagger\\ldots\\cdots" . - . + . . · - · + · · diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki115.snap b/crates/math-core/tests/snapshots/wiki_test__wiki115.snap index f29a6ff6..25e3fbd5 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki115.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki115.snap @@ -11,7 +11,7 @@ expression: "\\overbrace{ 1+2+\\cdots+100 }^{5050}" 2 + · - · + · · + 100 diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki116.snap b/crates/math-core/tests/snapshots/wiki_test__wiki116.snap index 092ba201..283251df 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki116.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki116.snap @@ -11,7 +11,7 @@ expression: "\\underbrace{ a+b+\\cdots+z }_{26}" b + · - · + · · + z diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki125.snap b/crates/math-core/tests/snapshots/wiki_test__wiki125.snap index f75e3e79..233ea150 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki125.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki125.snap @@ -12,7 +12,7 @@ expression: "\\begin{bmatrix} 0 & \\cdots & 0 \\\\ \\vdots & \\ddots & \\vdots \ · - · + · · @@ -36,7 +36,7 @@ expression: "\\begin{bmatrix} 0 & \\cdots & 0 \\\\ \\vdots & \\ddots & \\vdots \ · - · + · · diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki145.snap b/crates/math-core/tests/snapshots/wiki_test__wiki145.snap index 6bbfe15c..4d0b3a8d 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki145.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki145.snap @@ -9,7 +9,7 @@ expression: "( \\bigl( \\Bigl( \\biggl( \\Biggl( \\dots \\Biggr] \\biggr] \\Bigr ( ( . - . + . . ] ] diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki146.snap b/crates/math-core/tests/snapshots/wiki_test__wiki146.snap index facaac10..0293a4fb 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki146.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki146.snap @@ -9,7 +9,7 @@ expression: "\\{ \\bigl\\{ \\Bigl\\{ \\biggl\\{ \\Biggl\\{ \\dots \\Biggr\\rangl { { . - . + . . diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki147.snap b/crates/math-core/tests/snapshots/wiki_test__wiki147.snap index b5b152e1..cf9e7ab8 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki147.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki147.snap @@ -9,7 +9,7 @@ expression: "\\| \\big\\| \\Big\\| \\bigg\\| \\Bigg\\| \\dots \\Bigg| \\bigg| \\ . - . + . . | | diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki148.snap b/crates/math-core/tests/snapshots/wiki_test__wiki148.snap index f0ed12c6..9f18b8ab 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki148.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki148.snap @@ -9,7 +9,7 @@ expression: "\\lfloor \\bigl\\lfloor \\Bigl\\lfloor \\biggl\\lfloor \\Biggl\\lfl . - . + . . diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki149.snap b/crates/math-core/tests/snapshots/wiki_test__wiki149.snap index 3ebcd21e..67106814 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki149.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki149.snap @@ -9,7 +9,7 @@ expression: "\\uparrow \\big\\uparrow \\Big\\uparrow \\bigg\\uparrow \\Bigg\\upa . - . + . . diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki150.snap b/crates/math-core/tests/snapshots/wiki_test__wiki150.snap index ca612351..31de41c2 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki150.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki150.snap @@ -9,7 +9,7 @@ expression: "\\updownarrow\\big\\updownarrow\\Big\\updownarrow \\bigg\\updownarr . - . + . . diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki151.snap b/crates/math-core/tests/snapshots/wiki_test__wiki151.snap index d52b8166..238a5f1a 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki151.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki151.snap @@ -9,7 +9,7 @@ expression: "/ \\big/ \\Big/ \\bigg/ \\Bigg/ \\dots \\Bigg\\backslash \\bigg\\ba / / . - . + . . \ \ diff --git a/crates/math-core/tests/snapshots/wiki_test__wiki215.snap b/crates/math-core/tests/snapshots/wiki_test__wiki215.snap index 69006de7..79572623 100644 --- a/crates/math-core/tests/snapshots/wiki_test__wiki215.snap +++ b/crates/math-core/tests/snapshots/wiki_test__wiki215.snap @@ -19,7 +19,7 @@ expression: "{}_pF_q(a_1,\\dots,a_p;c_1,\\dots,c_q;z) = \\sum_{n=0}^\\infty \\fr , . - . + . . , @@ -33,7 +33,7 @@ expression: "{}_pF_q(a_1,\\dots,a_p;c_1,\\dots,c_q;z) = \\sum_{n=0}^\\infty \\fr , . - . + . . , @@ -65,7 +65,7 @@ expression: "{}_pF_q(a_1,\\dots,a_p;c_1,\\dots,c_q;z) = \\sum_{n=0}^\\infty \\fr n · - · + · · ( @@ -88,7 +88,7 @@ expression: "{}_pF_q(a_1,\\dots,a_p;c_1,\\dots,c_q;z) = \\sum_{n=0}^\\infty \\fr n · - · + · · (