diff --git a/Cargo.toml b/Cargo.toml index c56301d1..39c12116 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,14 @@ strum_macros = "0.28.0" static_assertions = "1.1.0" ariadne = "0.6.0" +[workspace.lints.clippy] +unnecessary_semicolon = "deny" +match_wildcard_for_single_variants = "deny" +explicit_iter_loop = "deny" +match_same_arms = "deny" +elidable_lifetime_names = "deny" +trivially_copy_pass_by_ref = "deny" + [profile.dev.package] insta.opt-level = 3 similar.opt-level = 3 diff --git a/crates/math-core-cli/src/main.rs b/crates/math-core-cli/src/main.rs index 6f666bad..acfba7e0 100644 --- a/crates/math-core-cli/src/main.rs +++ b/crates/math-core-cli/src/main.rs @@ -289,13 +289,11 @@ fn convert_html_recursive( for entry in dir.filter_map(Result::ok) { convert_html_recursive(args, entry.path().as_ref(), replacer, converter) } - } else if path.is_file() { - if let Some(ext) = path.extension() { - if ext == "html" { + } else if path.is_file() + && let Some(ext) = path.extension() + && ext == "html" { convert_html(args, path, replacer, converter); } - } - } } fn convert_html(args: &Args, fp: &Path, replacer: &mut Replacer, converter: &mut LatexToMathML) { diff --git a/crates/math-core/Cargo.toml b/crates/math-core/Cargo.toml index baa01081..d03ca232 100644 --- a/crates/math-core/Cargo.toml +++ b/crates/math-core/Cargo.toml @@ -40,3 +40,7 @@ minijinja = "2.16.0" [features] serde = ["dep:serde", "dep:serde-tuple-vec-map"] ariadne = ["dep:ariadne"] + +[lints] +workspace = true + diff --git a/crates/math-core/src/commands.rs b/crates/math-core/src/commands.rs index 347d9cb1..145cb1a7 100644 --- a/crates/math-core/src/commands.rs +++ b/crates/math-core/src/commands.rs @@ -722,14 +722,13 @@ static COMMANDS: phf::Map<&'static str, Token> = phf::phf_map! { }; pub fn get_command(command: &str) -> Option> { - match COMMANDS.get(command) { - Some(token) => Some(*token), - None => { - if let Some(function) = FUNCTIONS.get_key(command) { - return Some(PseudoOperator(function)); - } - None + if let Some(token) = COMMANDS.get(command) { + Some(*token) + } else { + if let Some(function) = FUNCTIONS.get_key(command) { + return Some(PseudoOperator(function)); } + None } } diff --git a/crates/math-core/src/environments.rs b/crates/math-core/src/environments.rs index 56bb5ee4..ea1a477b 100644 --- a/crates/math-core/src/environments.rs +++ b/crates/math-core/src/environments.rs @@ -56,15 +56,15 @@ impl Env { ENVIRONMENTS.get(s).copied() } - pub(super) fn as_str(&self) -> &'static str { + pub(super) fn as_str(self) -> &'static str { ENVIRONMENTS .entries() - .find_map(|(k, v)| if v == self { Some(*k) } else { None }) + .find_map(|(k, v)| if v == &self { Some(*k) } else { None }) .unwrap_or("unknown") } #[inline] - pub(super) fn allows_columns(&self) -> bool { + pub(super) fn allows_columns(self) -> bool { !matches!( self, Env::Equation @@ -77,12 +77,12 @@ impl Env { } #[inline] - pub(super) fn meaningful_newlines(&self) -> bool { + pub(super) fn meaningful_newlines(self) -> bool { !matches!(self, Env::Equation | Env::EquationStar) } #[inline] - pub(super) fn get_numbered_env_state(&self) -> Option { + pub(super) fn get_numbered_env_state(self) -> Option { if matches!( self, Env::Align @@ -112,7 +112,7 @@ impl Env { } pub(super) fn construct_node<'arena>( - &self, + self, content: &'arena [&'arena Node<'arena>], array_spec: Option<&'arena ArraySpec<'arena>>, arena: &'arena Arena, @@ -137,7 +137,7 @@ impl Env { content, } } - Env::Gathered => Node::Table { + Env::Gathered | Env::Matrix => Node::Table { align: Alignment::Centered, style: Some(Style::Display), content, @@ -166,11 +166,6 @@ impl Env { style: None, } } - Env::Matrix => Node::Table { - align: Alignment::Centered, - style: Some(Style::Display), - content, - }, array_variant @ (Env::Array | Env::Subarray) => { // SAFETY: `array_spec` is guaranteed to be Some because we checked for // `Env::Array` and `Env::Subarray` in the caller. diff --git a/crates/math-core/src/error.rs b/crates/math-core/src/error.rs index 11ff0083..2cea79ca 100644 --- a/crates/math-core/src/error.rs +++ b/crates/math-core/src/error.rs @@ -118,16 +118,16 @@ impl LatexErrKind { )?; } LatexErrKind::DisallowedChar(got) => { - write!(s, "Disallowed character in text group: '{}'.", got)?; + write!(s, "Disallowed character in text group: '{got}'.")?; } LatexErrKind::UnknownEnvironment(environment) => { - write!(s, "Unknown environment \"{}\".", environment)?; + write!(s, "Unknown environment \"{environment}\".")?; } LatexErrKind::UnknownCommand(cmd) => { - write!(s, "Unknown command \"\\{}\".", cmd)?; + write!(s, "Unknown command \"\\{cmd}\".")?; } LatexErrKind::UnknownColor(color) => { - write!(s, "Unknown color \"{}\".", color)?; + write!(s, "Unknown color \"{color}\".")?; } LatexErrKind::MismatchedEnvironment { expected, got } => { write!( @@ -164,16 +164,16 @@ impl LatexErrKind { write!(s, "Math variant switch command found outside of sequence.")?; } LatexErrKind::ExpectedText(place) => { - write!(s, "Expected text in {}.", place)?; + write!(s, "Expected text in {place}.")?; } LatexErrKind::ExpectedLength(got) => { - write!(s, "Expected length with units, got \"{}\".", got)?; + write!(s, "Expected length with units, got \"{got}\".")?; } LatexErrKind::ExpectedNumber(got) => { - write!(s, "Expected a number, got \"{}\".", got)?; + write!(s, "Expected a number, got \"{got}\".")?; } LatexErrKind::ExpectedColSpec(got) => { - write!(s, "Expected column specification, got \"{}\".", got)?; + write!(s, "Expected column specification, got \"{got}\".")?; } LatexErrKind::NotValidInTextMode => { write!(s, "Not valid in text mode.")?; @@ -185,7 +185,7 @@ impl LatexErrKind { write!(s, "Could not extract text from the given macro.")?; } LatexErrKind::InvalidMacroName(name) => { - write!(s, "Invalid macro name: \"\\{}\".", name)?; + write!(s, "Invalid macro name: \"\\{name}\".")?; } LatexErrKind::InvalidParameterNumber => { write!(s, "Invalid parameter number. Must be 1-9.")?; @@ -232,7 +232,7 @@ impl LatexError { "span" }; let css_class = css_class.unwrap_or("math-core-error"); - let _ = write!(output, r#"<{} class="{}" title=""#, tag, css_class); + let _ = write!(output, r#"<{tag} class="{css_class}" title=""#); let mut err_msg = String::new(); self.to_message(&mut err_msg, latex); escape_double_quoted_html_attribute(&mut output, &err_msg); @@ -253,11 +253,11 @@ impl LatexError { /// # Arguments /// - `s`: The string to write the message into. /// - `input`: The original LaTeX input that caused the error; used to - /// calculate the character offset for the error position. + /// calculate the character offset for the error position. pub fn to_message(&self, s: &mut String, input: &str) { let loc = input.floor_char_boundary(self.0.start); let codepoint_offset = input[..loc].chars().count(); - let _ = write!(s, "{}: ", codepoint_offset); + let _ = write!(s, "{codepoint_offset}: "); let _ = self.1.write_msg(s); } } @@ -281,8 +281,9 @@ impl LatexError { ) .into(), LatexErrKind::UnmatchedClose(_) => "no matching opening for this".into(), - LatexErrKind::ExpectedArgumentGotClose => "expected an argument here".into(), - LatexErrKind::ExpectedArgumentGotEOI => "expected an argument here".into(), + LatexErrKind::ExpectedArgumentGotClose | LatexErrKind::ExpectedArgumentGotEOI => { + "expected an argument here".into() + } LatexErrKind::ExpectedDelimiter(_) => "expected a delimiter here".into(), LatexErrKind::DisallowedChar(_) => "disallowed character".into(), LatexErrKind::UnknownEnvironment(_) => "unknown environment".into(), diff --git a/crates/math-core/src/lexer.rs b/crates/math-core/src/lexer.rs index 23597ca1..febf533a 100644 --- a/crates/math-core/src/lexer.rs +++ b/crates/math-core/src/lexer.rs @@ -62,8 +62,7 @@ impl<'config, 'source> Lexer<'config, 'source> { &mut self.peek, self.input .next() - .map(|(idx, ch)| (idx, Some(ch))) - .unwrap_or((self.input_length, None)), + .map_or((self.input_length, None), |(idx, ch)| (idx, Some(ch))), ) } @@ -120,7 +119,7 @@ impl<'config, 'source> Lexer<'config, 'source> { /// If the end of the input is reached before finding a `}`, the `Err` contains /// the span and `None`. #[inline] - fn read_env_name(&mut self) -> Result<(&'source str, usize), (Range, Option)> { + fn read_env_name(&mut self) -> Result<(&'source str, usize), CharSpan> { // If the first character is not `{`, we read a single character. let (loc, first) = self.read_char(); if first != Some('{') { @@ -128,7 +127,7 @@ impl<'config, 'source> Lexer<'config, 'source> { // SAFETY: we got `start` and `end` from `CharIndices`, so they are valid bounds. Ok((self.input_string.get_unwrap(loc..self.peek.0), self.peek.0)) } else { - Err((loc..(loc + first.map_or(0, |ch| ch.len_utf8())), first)) + Err((first, loc..(loc + first.map_or(0, char::len_utf8)))) }; } let start = self.peek.0; @@ -146,7 +145,7 @@ impl<'config, 'source> Lexer<'config, 'source> { // SAFETY: we got `start` and `end` from `CharIndices`, so they are valid bounds. Ok((self.input_string.get_unwrap(start..end), end + 1)) } else { - Err((loc..(loc + closing.map_or(0, |ch| ch.len_utf8())), closing)) + Err((closing, loc..(loc + closing.map_or(0, char::len_utf8)))) } } @@ -215,13 +214,16 @@ impl<'config, 'source> Lexer<'config, 'source> { { // In pre-defined commands, `#` is used to denote a parameter. let param_num = (next as u32).wrapping_sub('1' as u32); - if !(0..=8).contains(¶m_num) { + let param_num = if let Ok(param_num) = u8::try_from(param_num) + && (0..=8).contains(¶m_num) + { + param_num + } else { return LexerResult::Err(Box::new(LatexError( (loc + 1)..(loc + 2), LatexErrKind::InvalidParameterNumber, ))); - } - let param_num = param_num as u8; + }; if (param_num + 1) > *num { *num = param_num + 1; } @@ -236,12 +238,11 @@ impl<'config, 'source> Lexer<'config, 'source> { loc..(loc + ch.len_utf8()), LatexErrKind::InvalidParameterNumber, ))); - } else { - return LexerResult::Err(Box::new(LatexError( - loc..loc, - LatexErrKind::ExpectedParamNumberGotEOI, - ))); } + return LexerResult::Err(Box::new(LatexError( + loc..loc, + LatexErrKind::ExpectedParamNumberGotEOI, + ))); } } else { return LexerResult::Err(Box::new(LatexError( @@ -307,7 +308,7 @@ impl<'config, 'source> Lexer<'config, 'source> { // Read the environment name. let (name, end) = match self.read_env_name() { Ok(lit) => lit, - Err((span, ch)) => match ch { + Err((ch, span)) => match ch { None => { break 'env_name Err(LatexError( span, @@ -349,6 +350,8 @@ impl<'config, 'source> Lexer<'config, 'source> { } } +type CharSpan = (Option, Range); + fn nonalpha_nonspecial_ascii_to_token(ch: char) -> Option> { let tok_ref = match ch { '!' => &Token::ForceClose(symbol::EXCLAMATION_MARK), @@ -376,9 +379,8 @@ enum EnvMarker { pub(crate) fn recover_limited_ascii(tok: Token) -> Option { match tok { Token::Letter(ch, _) if ch.is_ascii_alphabetic() || ch == '.' => Some(ch), - Token::MathOrTextMode(_, ch) => Some(ch), + Token::Digit(ch) | Token::MathOrTextMode(_, ch) => Some(ch), Token::Whitespace => Some(' '), - Token::Digit(ch) => Some(ch), _ => None, } } diff --git a/crates/math-core/src/lib.rs b/crates/math-core/src/lib.rs index 7e989035..13df9a30 100644 --- a/crates/math-core/src/lib.rs +++ b/crates/math-core/src/lib.rs @@ -40,7 +40,7 @@ mod text_parser; mod token; mod token_queue; -use rustc_hash::FxHashMap; +use rustc_hash::{FxBuildHasher, FxHashMap}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -264,7 +264,7 @@ fn convert( } if matches!(display, MathDisplay::Block) { output.push_str(" display=\"block\""); - }; + } output.push('>'); let pretty_print = matches!(flags.pretty_print, PrettyPrint::Always) @@ -274,7 +274,7 @@ fn convert( if flags.annotation { new_line_and_indent(&mut output, base_indent); output.push_str(""); - let node = parser::node_vec_to_node(&arena, ast, false); + let node = parser::node_vec_to_node(&arena, &ast, false); let _ = node.emit(&mut output, base_indent + 1); new_line_and_indent(&mut output, base_indent + 1); output.push_str(""); @@ -318,7 +318,7 @@ fn parse_custom_commands( macros: Vec<(String, String)>, ignore_unknown_commands: bool, ) -> Result, usize, String)> { - let mut map = FxHashMap::with_capacity_and_hasher(macros.len(), Default::default()); + let mut map = FxHashMap::with_capacity_and_hasher(macros.len(), FxBuildHasher); let mut tokens = Vec::new(); for (idx, (name, definition)) in macros.into_iter().enumerate() { if !is_valid_macro_name(name.as_str()) { @@ -360,7 +360,7 @@ fn parse_custom_commands( Ok(v) => { map.insert(name, v); } - }; + } } Ok(CommandConfig { custom_cmd_tokens: tokens, diff --git a/crates/math-core/src/parser.rs b/crates/math-core/src/parser.rs index 62f642a1..ea1eb885 100644 --- a/crates/math-core/src/parser.rs +++ b/crates/math-core/src/parser.rs @@ -48,7 +48,7 @@ struct ParserState<'source> { script_style: bool, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] enum SequenceEnd { EndToken(EndToken), AnyEndToken, @@ -56,7 +56,7 @@ enum SequenceEnd { impl SequenceEnd { #[inline] - fn matches(&self, other: &Token<'_>) -> bool { + fn matches(self, other: &Token<'_>) -> bool { match self { SequenceEnd::EndToken(token) => token.matches(other), SequenceEnd::AnyEndToken => matches!( @@ -83,7 +83,7 @@ enum ParseAs { impl ParseAs { #[inline] - fn in_sequence(&self) -> bool { + fn in_sequence(self) -> bool { matches!(self, ParseAs::Sequence | ParseAs::ContinueSequence) } } @@ -120,11 +120,6 @@ where }) } - #[inline] - fn alloc_err(&mut self, err: LatexError) -> Box { - Box::new(err) - } - #[inline(never)] fn next_token(&mut self) -> ParseResult> { self.tokens.next() @@ -167,7 +162,7 @@ where Token::Eoi => { if let SequenceEnd::EndToken(end_token) = sequence_end { // The input has ended without the closing token. - return Err(self.alloc_err(LatexError( + return Err(Box::new(LatexError( span.into(), LatexErrKind::UnclosedGroup(end_token), ))); @@ -228,7 +223,7 @@ where prev_class: Class, ) -> ParseResult<(Class, &'arena Node<'arena>)> { let (cur_token, span) = cur_tokloc?.into_parts(); - let mut class: Class = Default::default(); + let mut class = Class::default(); let next_class = self .tokens .peek_class_token()? @@ -406,7 +401,7 @@ where }) } Token::Mathbin => 'mathbin: { - let tokspan = match self.tokens.read_argument(false)?.as_one_or_none()? { + let tokspan = match self.tokens.read_argument(false)?.into_one_or_none()? { OneOrNone::One(tokspan) => tokspan, OneOrNone::None(span) => { break 'mathbin Err(LatexError( @@ -422,9 +417,9 @@ where Token::BinaryOp(op) => op.as_op(), Token::Relation(op) => op.as_op(), Token::Punctuation(op) => op.as_op(), - Token::ForceRelation(op) => op, - Token::ForceClose(op) => op, - Token::ForceBinaryOp(op) => op, + Token::ForceRelation(op) | Token::ForceClose(op) | Token::ForceBinaryOp(op) => { + op + } Token::SquareBracketOpen => symbol::LEFT_SQUARE_BRACKET.as_op(), Token::SquareBracketClose => symbol::RIGHT_SQUARE_BRACKET.as_op(), _ => { @@ -528,7 +523,7 @@ where match parse_length_specification(length.trim()) { Some(space) => Ok(Node::Space(space)), None => Err(LatexError( - span.into(), + span, LatexErrKind::ExpectedLength(length.into()), )), } @@ -547,7 +542,7 @@ where )?; let content = self.parse_next(ParseAs::Arg)?; Ok(Node::Root( - node_vec_to_node(self.arena, degree, true), + node_vec_to_node(self.arena, °ree, true), content, )) } else { @@ -597,10 +592,10 @@ where 'source: 'arena, 'arena: 'cell, { - let tok = parser.tokens.read_argument(false)?.as_one_or_none()?; + let tok = parser.tokens.read_argument(false)?.into_one_or_none()?; Ok(match tok { OneOrNone::One(tok) => { - Some(parser.extract_delimiter(tok, DelimiterModifier::Genfrac)?) + Some(extract_delimiter(tok, DelimiterModifier::Genfrac)?) } OneOrNone::None(_) => None, }) @@ -611,14 +606,14 @@ where let lt = match length.trim() { "" => Length::none(), decimal => parse_length_specification(decimal).ok_or_else(|| { - self.alloc_err(LatexError( - span.into(), + Box::new(LatexError( + span, LatexErrKind::ExpectedLength(decimal.into()), )) })?, }; let style_token: Option = - self.tokens.read_argument(false)?.as_one_or_none()?.into(); + self.tokens.read_argument(false)?.into_one_or_none()?.into(); let style = if let Some(tokspan) = style_token { if let Token::Digit(num) = tokspan.token() { match num { @@ -871,16 +866,16 @@ where } Token::ForceRelation(op) => { class = Class::Relation; - let (left, right) = if !parse_as.in_sequence() { - // Don't add spacing if we are in an argument. - (None, None) - } else { + let (left, right) = if parse_as.in_sequence() { let (left, right) = self.state.relation_spacing(prev_class, next_class); // We have to turn `None` into explicit relation spacing. ( left.or(Some(MathSpacing::FiveMu)), right.or(Some(MathSpacing::FiveMu)), ) + } else { + // Don't add spacing if we are in an argument. + (None, None) }; Ok(Node::Operator { op, @@ -910,7 +905,7 @@ where )?; return Ok(( Class::Default, - node_vec_to_node(self.arena, content, matches!(parse_as, ParseAs::Arg)), + node_vec_to_node(self.arena, &content, matches!(parse_as, ParseAs::Arg)), )); } ref tok @ (Token::Open(paren) | Token::Close(paren)) => 'open_close: { @@ -963,7 +958,7 @@ where { None } else { - Some(self.extract_delimiter(tok_loc, DelimiterModifier::Left)?) + Some(extract_delimiter(tok_loc, DelimiterModifier::Left)?) }; let content = self.parse_sequence( SequenceEnd::EndToken(EndToken::Right), @@ -975,23 +970,23 @@ where { None } else { - Some(self.extract_delimiter(tok_loc, DelimiterModifier::Right)?) + Some(extract_delimiter(tok_loc, DelimiterModifier::Right)?) }; Ok(Node::Fenced { open: open_paren, close: close_paren, - content: node_vec_to_node(self.arena, content, false), + content: node_vec_to_node(self.arena, &content, false), style: None, }) } Token::Middle => { let tok_loc = self.next_token()?; - let op = self.extract_delimiter(tok_loc, DelimiterModifier::Middle)?; + let op = extract_delimiter(tok_loc, DelimiterModifier::Middle)?; Ok(Node::StretchableOp(op, StretchMode::Middle, None)) } Token::Big(size, paren_type) => { let tok_loc = self.next_token()?; - let paren = self.extract_delimiter(tok_loc, DelimiterModifier::Big)?; + let paren = extract_delimiter(tok_loc, DelimiterModifier::Big)?; // `\big` commands without the "l" or "r" really produce `Class::Default`. class = match paren_type { Some(ParenType::Open) => Class::Open, @@ -1012,7 +1007,7 @@ where }; if matches!(env, Env::Subarray) { spec.is_sub = true; - }; + } Some(self.arena.alloc_array_spec(spec)) } else { None @@ -1062,7 +1057,7 @@ where let (last_equation_num, num_rows) = if let Some(mut n) = numbered_state { match n.next_equation_number(self.equation_counter, true) { Ok(num) => (num, n.num_rows), - Err(_) => { + Err(()) => { break 'begin_env Err(LatexError( span.into(), LatexErrKind::HardLimitExceeded, @@ -1122,7 +1117,7 @@ where .into_iter() .map(|(style, text)| self.commit(Node::Text(style, text))) .collect::>(); - return Ok((Class::Close, node_vec_to_node(self.arena, nodes, false))); + return Ok((Class::Close, node_vec_to_node(self.arena, &nodes, false))); } Token::NewColumn => { if self.state.allow_columns { @@ -1157,7 +1152,7 @@ where } match numbered_state.next_equation_number(self.equation_counter, false) { Ok(num) => Ok(Node::RowSeparator(num)), - Err(_) => Err(LatexError(span.into(), LatexErrKind::HardLimitExceeded)), + Err(()) => Err(LatexError(span.into(), LatexErrKind::HardLimitExceeded)), } } else { Ok(Node::RowSeparator(None)) @@ -1202,7 +1197,7 @@ where let (color_name, span) = self.parse_string_literal()?; let Some(color) = get_color(color_name) else { break 'color Err(LatexError( - span.into(), + span, LatexErrKind::UnknownColor(color_name.into()), )); }; @@ -1239,18 +1234,11 @@ where } tok @ (Token::Underscore | Token::Circumflex) => { let symbol = self.parse_next(ParseAs::Arg)?; - if !matches!( + if matches!( self.tokens.peek().token(), Token::Eoi | Token::GroupEnd | Token::End(_) ) { - let base = self.parse_next(ParseAs::Sequence)?; - let (sub, sup) = if matches!(tok, Token::Underscore) { - (Some(symbol), None) - } else { - (None, Some(symbol)) - }; - Ok(Node::Multiscript { base, sub, sup }) - } else { + // If nothing follows the sub- or superscript, we use an empty row as the base. let empty_row = self.commit(Node::Row { nodes: &[], attr: None, @@ -1266,6 +1254,16 @@ where symbol, }) } + } else { + // If something follows the sub- or superscript, we parse it as a sequence and + // use it as the base. + let base = self.parse_next(ParseAs::Sequence)?; + let (sub, sup) = if matches!(tok, Token::Underscore) { + (Some(symbol), None) + } else { + (None, Some(symbol)) + }; + Ok(Node::Multiscript { base, sub, sup }) } } Token::Limits => Err(LatexError( @@ -1362,7 +1360,7 @@ where }; match node { Ok(n) => Ok((class, self.commit(n))), - Err(e) => Err(self.alloc_err(e)), + Err(e) => Err(Box::new(e)), } } @@ -1397,9 +1395,10 @@ where if (first_circumflex && second_circumflex) || (first_underscore && second_underscore) { let span = self.next_token()?.span(); - return Err( - self.alloc_err(LatexError(span.into(), LatexErrKind::DuplicateSubOrSup)) - ); + return Err(Box::new(LatexError( + span.into(), + LatexErrKind::DuplicateSubOrSup, + ))); } if (first_underscore && second_circumflex) || (first_circumflex && second_underscore) { @@ -1420,13 +1419,13 @@ where (None, None) }; - let sup = if !primes.is_empty() { + let sup = if primes.is_empty() { + sup + } else { if let Some(sup) = sup { primes.push(sup); } - Some(node_vec_to_node(self.arena, primes, false)) - } else { - sup + Some(node_vec_to_node(self.arena, &primes, false)) }; Ok(Bounds(sub, sup)) @@ -1477,7 +1476,10 @@ where && let (Token::Underscore | Token::Circumflex | Token::Prime, span) = tokloc.into_parts() { - return Err(self.alloc_err(LatexError(span.into(), LatexErrKind::BoundFollowedByBound))); + return Err(Box::new(LatexError( + span.into(), + LatexErrKind::BoundFollowedByBound, + ))); } let old_script_style = mem::replace(&mut self.state.script_style, true); let node = self.parse_token(next, ParseAs::Arg, Class::Default); @@ -1485,7 +1487,7 @@ where // If the bound was a superscript, it may *not* be followed by a prime. if is_sup && matches!(self.tokens.peek().token(), Token::Prime) { - return Err(self.alloc_err(LatexError( + return Err(Box::new(LatexError( self.tokens.peek().span().into(), LatexErrKind::DuplicateSubOrSup, ))); @@ -1535,34 +1537,6 @@ where )) } - fn extract_delimiter( - &mut self, - tok: TokSpan<'source>, - location: DelimiterModifier, - ) -> ParseResult { - let (tok, span) = tok.into_parts(); - const SQ_L_BRACKET: StretchableOp = - symbol::LEFT_SQUARE_BRACKET.as_stretchable_op().unwrap(); - const SQ_R_BRACKET: StretchableOp = - symbol::RIGHT_SQUARE_BRACKET.as_stretchable_op().unwrap(); - let delim = match tok { - Token::Open(paren) => paren.as_stretchable_op(), - Token::Close(paren) => paren.as_stretchable_op(), - Token::Ord(ord) => ord.as_stretchable_op(), - Token::Relation(rel) => rel.as_stretchable_op(), - Token::SquareBracketOpen => Some(SQ_L_BRACKET), - Token::SquareBracketClose => Some(SQ_R_BRACKET), - _ => None, - }; - let Some(delim) = delim else { - return Err(self.alloc_err(LatexError( - span.into(), - LatexErrKind::ExpectedDelimiter(location), - ))); - }; - Ok(delim) - } - #[inline] fn merge_and_transform_letters( &mut self, @@ -1681,7 +1655,7 @@ where continue; } let Some(ch) = recover_limited_ascii(tok) else { - return Err(self.alloc_err(LatexError( + return Err(Box::new(LatexError( span.into(), LatexErrKind::ExpectedText("string literal"), ))); @@ -1692,7 +1666,7 @@ where } } -impl<'source> ParserState<'source> { +impl ParserState<'_> { fn relation_spacing( &self, prev_class: Class, @@ -1727,7 +1701,7 @@ impl<'source> ParserState<'source> { next_class: Class, force: bool, ) -> Option { - let spacing = if !in_sequence { + if !in_sequence { // Don't add spacing if we are in an argument. None } else if matches!( @@ -1743,8 +1717,7 @@ impl<'source> ParserState<'source> { Some(MathSpacing::FourMu) // force binary op spacing } else { None - }; - spacing + } } } @@ -1754,10 +1727,10 @@ impl<'source> ParserState<'source> { // or by creating a row node if there are multiple nodes. pub(crate) fn node_vec_to_node<'arena>( arena: &'arena Arena, - nodes: Vec<&'arena Node<'arena>>, + nodes: &[&'arena Node<'arena>], reset_spacing: bool, ) -> &'arena Node<'arena> { - if let [single] = &nodes[..] { + if let [single] = nodes { if reset_spacing { if let Node::Operator { op, @@ -1779,11 +1752,32 @@ pub(crate) fn node_vec_to_node<'arena>( single } } else { - let nodes = arena.push_slice(&nodes); + let nodes = arena.push_slice(nodes); arena.push(Node::Row { nodes, attr: None }) } } +fn extract_delimiter(tok: TokSpan<'_>, location: DelimiterModifier) -> ParseResult { + let (tok, span) = tok.into_parts(); + const SQ_L_BRACKET: StretchableOp = symbol::LEFT_SQUARE_BRACKET.as_stretchable_op().unwrap(); + const SQ_R_BRACKET: StretchableOp = symbol::RIGHT_SQUARE_BRACKET.as_stretchable_op().unwrap(); + let delim = match tok { + Token::Open(paren) | Token::Close(paren) => paren.as_stretchable_op(), + Token::Ord(ord) => ord.as_stretchable_op(), + Token::Relation(rel) => rel.as_stretchable_op(), + Token::SquareBracketOpen => Some(SQ_L_BRACKET), + Token::SquareBracketClose => Some(SQ_R_BRACKET), + _ => None, + }; + let Some(delim) = delim else { + return Err(Box::new(LatexError( + span.into(), + LatexErrKind::ExpectedDelimiter(location), + ))); + }; + Ok(delim) +} + struct Bounds<'arena>(Option<&'arena Node<'arena>>, Option<&'arena Node<'arena>>); #[cfg(test)] diff --git a/crates/math-core/src/text_parser.rs b/crates/math-core/src/text_parser.rs index 840c04ad..a4355b0a 100644 --- a/crates/math-core/src/text_parser.rs +++ b/crates/math-core/src/text_parser.rs @@ -12,7 +12,7 @@ use crate::{ token::{EndToken, Mode, TextToken, Token}, }; -impl<'cell, 'arena, 'source, 'config> Parser<'cell, 'arena, 'source, 'config> { +impl<'arena> Parser<'_, 'arena, '_, '_> { pub(super) fn extract_text( &mut self, initial_style: Option, @@ -157,12 +157,10 @@ impl<'cell, 'arena, 'source, 'config> Parser<'cell, 'arena, 'source, 'config> { } else if !text_mode { // These tokens are only valid in math mode. match token { - Token::Letter(c, _) => Ok(c), - Token::UprightLetter(c) => Ok(c), - Token::Open(op) | Token::Close(op) => Ok(op.as_op().into()), + Token::Letter(c, _) | Token::UprightLetter(c) => Ok(c), + Token::Open(op) | Token::Close(op) | Token::Ord(op) => Ok(op.as_op().into()), Token::BinaryOp(op) => Ok(op.as_op().into()), Token::Relation(op) => Ok(op.as_op().into()), - Token::Ord(op) => Ok(op.as_op().into()), Token::ForceRelation(op) => Ok(op.as_char()), Token::Punctuation(op) => Ok(op.as_op().into()), @@ -170,16 +168,15 @@ impl<'cell, 'arena, 'source, 'config> Parser<'cell, 'arena, 'source, 'config> { if let Some(str_builder) = &mut str_builder { str_builder.push_str(output); continue; - } else { - snippets.push((current_style, output)); - style_stack.pop(); - brace_nesting = previous_nesting; - if !style_stack.is_empty() { - // If there are still styles to process, we must have been within a group. - str_builder = Some(self.buffer.get_builder()); - } - continue; } + snippets.push((current_style, output)); + style_stack.pop(); + brace_nesting = previous_nesting; + if !style_stack.is_empty() { + // If there are still styles to process, we must have been within a group. + str_builder = Some(self.buffer.get_builder()); + } + continue; } Token::Space(length) => { if length == Length::new(1.0, LengthUnit::Em) { diff --git a/crates/math-core/src/token.rs b/crates/math-core/src/token.rs index b3207eea..37f2ef51 100644 --- a/crates/math-core/src/token.rs +++ b/crates/math-core/src/token.rs @@ -223,9 +223,7 @@ impl Token<'_> { _ => Class::Default, } } -} -impl<'source> Token<'source> { /// If this token is `MathOrTextMode`, returns the inner token. Otherwise, returns `self`. #[inline] pub fn unwrap_math_ref(&self) -> &Self { @@ -363,7 +361,7 @@ pub enum EndToken { } impl EndToken { - pub fn matches(&self, other: &Token) -> bool { + pub fn matches(self, other: &Token) -> bool { matches!( (self, other), (EndToken::End, Token::End(_)) diff --git a/crates/math-core/src/token_queue.rs b/crates/math-core/src/token_queue.rs index ca670042..85600ef6 100644 --- a/crates/math-core/src/token_queue.rs +++ b/crates/math-core/src/token_queue.rs @@ -74,7 +74,7 @@ impl<'source, 'config> TokenQueue<'source, 'config> { && let Some(pos) = self.find_next_non_whitespace() { 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)? @@ -135,21 +135,21 @@ impl<'source, 'config> TokenQueue<'source, 'config> { } }; - match tok_idx { - Some(tok_idx) => Ok(self.queue.get(tok_idx).unwrap_or(&EOI_TOK)), - None => { - // 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) - } + if let Some(tok_idx) = tok_idx { + // If we found a token in the existing buffer, return it. + Ok(self.queue.get(tok_idx).unwrap_or(&EOI_TOK)) + } 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) } } } @@ -322,6 +322,7 @@ fn has_class(tok: &TokSpan) -> bool { ) } +#[derive(Debug, Clone, Copy)] enum SkipMode { Whitespace, NoClass, @@ -336,7 +337,7 @@ pub enum MacroArgument<'source> { impl<'source> MacroArgument<'source> { /// Try to interpret this macro argument as a single token. - pub fn as_one_or_none(self) -> Result, Box> { + pub fn into_one_or_none(self) -> Result, Box> { match self { MacroArgument::Token(tok) => Ok(OneOrNone::One(tok)), MacroArgument::Group(tokens, span) => { diff --git a/crates/mathml-renderer/Cargo.toml b/crates/mathml-renderer/Cargo.toml index c69951ee..57899e8a 100644 --- a/crates/mathml-renderer/Cargo.toml +++ b/crates/mathml-renderer/Cargo.toml @@ -27,3 +27,6 @@ bitflags = "2.11.0" [features] serde = ["dep:serde"] + +[lints] +workspace = true diff --git a/crates/mathml-renderer/src/ast.rs b/crates/mathml-renderer/src/ast.rs index 84bdbc9c..0c52804a 100644 --- a/crates/mathml-renderer/src/ast.rs +++ b/crates/mathml-renderer/src/ast.rs @@ -395,7 +395,7 @@ impl Node<'_> { write!(s, ";\">")?; } } - for node in nodes.iter() { + for node in *nodes { node.emit(s, child_indent)?; } writeln_indent!(s, base_indent, ""); @@ -409,7 +409,7 @@ impl Node<'_> { match style { Some(style) => write!(s, "", <&str>::from(style))?, None => write!(s, "")?, - }; + } new_line_and_indent(s, child_indent); emit_stretchy_op(s, StretchMode::Fence, *open, None)?; // TODO: if `content` is an `mrow`, we should flatten it before emitting. @@ -432,7 +432,7 @@ impl Node<'_> { Stretchy::AlwaysAsymmetric => { write!(s, " symmetric=\"true\"")?; } - _ => {} + Stretchy::Always => {} } if let Some(paren_type) = paren_type && matches!(paren.spacing, DelimiterSpacing::InfixNonZero) @@ -606,7 +606,7 @@ impl Node<'_> { Node::Dummy => { // Do nothing. } - }; + } Ok(()) } } @@ -620,7 +620,7 @@ fn emit_operator_attributes( match attr { Some(attributes) => write!(s, "::from(attributes))?, None => write!(s, " { write!( @@ -637,7 +637,7 @@ fn emit_operator_attributes( write!(s, " rspace=\"{}\"", <&str>::from(right))?; } _ => {} - }; + } Ok(()) } @@ -649,7 +649,7 @@ enum NumberColums { impl NumberColums { fn dummy_column_opening( - &self, + self, s: &mut String, child_indent2: usize, ) -> Result<(), std::fmt::Error> { @@ -667,7 +667,7 @@ impl NumberColums { /// Initial dummy column for equation numbering for keeping alignment. #[inline] fn initial_dummy_column( - &self, + self, s: &mut String, child_indent2: usize, ) -> Result<(), std::fmt::Error> { @@ -701,7 +701,7 @@ fn emit_table( numbering_cols.initial_dummy_column(s, child_indent2)?; } col_gen.write_next_mtd(s, child_indent2)?; - for node in content.iter() { + for node in content { match node { Node::ColumnSeparator => { writeln_indent!(s, child_indent2, ""); @@ -755,7 +755,7 @@ fn write_equation_num( ) -> Result<(), std::fmt::Error> { numbering_cols.dummy_column_opening(s, child_indent2)?; if let Some(equation_counter) = equation_counter { - write!(s, r#";{}">"#, RIGHT_ALIGN)?; + write!(s, r#";{RIGHT_ALIGN}">"#)?; writeln_indent!(s, child_indent3, "({})", equation_counter); writeln_indent!(s, child_indent2, ""); } else { diff --git a/crates/mathml-renderer/src/symbol.rs b/crates/mathml-renderer/src/symbol.rs index 03bc7fef..5f89d469 100644 --- a/crates/mathml-renderer/src/symbol.rs +++ b/crates/mathml-renderer/src/symbol.rs @@ -14,7 +14,7 @@ use serde::Serialize; pub struct MathMLOperator(char); impl MathMLOperator { - #[inline(always)] + #[inline] pub const fn as_char(&self) -> char { self.0 } @@ -49,8 +49,8 @@ impl BMPChar { BMPChar { char: ch as u16 } } - #[inline(always)] - pub const fn as_char(&self) -> char { + #[inline] + pub const fn as_char(self) -> char { debug_assert!(char::from_u32(self.char as u32).is_some(), "Invalid char."); unsafe { char::from_u32_unchecked(self.char as u32) } } @@ -80,7 +80,7 @@ macro_rules! make_character_class { } } - #[inline(always)] + #[inline] pub const fn as_op(&self) -> MathMLOperator { MathMLOperator(self.char.as_char()) } @@ -99,9 +99,9 @@ pub enum OrdCategory { D, /// Category E: Postfix, zero spacing (e.g. `′`). E, - /// Category F: Prefix or postfix, zero spacing, stretchy, symmetric + /// Category F: Prefix, zero spacing, stretchy, symmetric F, - /// Category G: Prefix or postfix, zero spacing, stretchy, symmetric + /// Category G: Postfix, zero spacing, stretchy, symmetric G, /// Category F and G: Prefix or postfix, zero spacing, stretchy, symmetric /// (e.g. `‖`). @@ -126,7 +126,7 @@ make_character_class!( ); impl OrdLike { - #[inline(always)] + #[inline] pub const fn as_stretchable_op(&self) -> Option { let (stretchy, spacing) = match self.cat { OrdCategory::F | OrdCategory::G => (Stretchy::Always, DelimiterSpacing::Zero), @@ -190,7 +190,7 @@ make_character_class!( ); impl Rel { - #[inline(always)] + #[inline] pub const fn as_stretchable_op(&self) -> Option { match self.cat { RelCategory::A => Some(StretchableOp { @@ -198,7 +198,7 @@ impl Rel { stretchy: Stretchy::AlwaysAsymmetric, spacing: DelimiterSpacing::NonZero, }), - _ => None, + RelCategory::Default => None, } } } @@ -209,7 +209,7 @@ impl Rel { pub struct Punct(char); impl Punct { - #[inline(always)] + #[inline] pub const fn as_op(&self) -> MathMLOperator { MathMLOperator(self.0) } diff --git a/crates/mathml-renderer/src/table.rs b/crates/mathml-renderer/src/table.rs index bfdb747a..49c4cd75 100644 --- a/crates/mathml-renderer/src/table.rs +++ b/crates/mathml-renderer/src/table.rs @@ -204,7 +204,7 @@ impl<'arena> ColumnGenerator<'arena> { write!(s, "{SIMPLE_CENTERED}")?; } } - }; + } Ok(()) } } diff --git a/playground/experiments.html b/playground/experiments.html index 0573272a..b4fe4447 100644 --- a/playground/experiments.html +++ b/playground/experiments.html @@ -288,6 +288,33 @@

Experiments

+ + + mpadded for pseudo operators + + + x + + cos + + θ + + + + + mpadded for pseudo operators with accent + + + x + + + cos + + ̂ + + θ + +