katex-parser 0.1.0

A port of the KaTeX parser: lexing and parsing LaTeX math expressions with macro expansion into a typed AST, plus a Unicode rendering backend
Documentation
use crate::builtin_macros::MathSymbolGroup;
use crate::error::ParseError;
use crate::lexer::starts_with_at;
use crate::macro_expander::{MacroExpander, MacroReplacement};

fn dots_by_token(name: &str) -> Option<String> {
    let result = match name {
        "," => Some("\\dotsc"),
        "\\not" => Some("\\dotsb"),
        "+" | "=" | "<" | ">" | "-" | "*" | ":" | "\\DOTSB" | "\\coprod" | "\\bigvee"
        | "\\bigwedge" | "\\biguplus" | "\\bigcap" | "\\bigcup" | "\\prod" | "\\sum"
        | "\\bigotimes" | "\\bigoplus" | "\\bigodot" | "\\bigsqcup" | "\\And"
        | "\\longrightarrow" | "\\Longrightarrow" | "\\longleftarrow" | "\\Longleftarrow"
        | "\\longleftrightarrow" | "\\Longleftrightarrow" | "\\mapsto" | "\\longmapsto"
        | "\\hookrightarrow" | "\\doteq" | "\\mathbin" | "\\mathrel" | "\\relbar"
        | "\\Relbar" | "\\xrightarrow" | "\\xleftarrow" => Some("\\dotsb"),
        "\\DOTSI" | "\\int" | "\\oint" | "\\iint" | "\\iiint" | "\\iiiint" | "\\idotsint" => {
            Some("\\dotsi")
        }
        "\\DOTSX" => Some("\\dotsx"),
        _ => None,
    };
    result.map(|s| s.to_string())
}

fn space_after_dots(name: &str) -> bool {
    matches!(
        name,
        ")"
            | "]"
            | "\\rbrack"
            | "\\}"
            | "\\rbrace"
            | "\\rangle"
            | "\\rceil"
            | "\\rfloor"
            | "\\rgroup"
            | "\\rmoustache"
            | "\\right"
            | "\\bigr"
            | "\\biggr"
            | "\\Bigr"
            | "\\Biggr"
            | "$"
            | ";"
            | "."
            | ","
    )
}

pub(crate) fn dots_macro(context: &mut MacroExpander) -> Result<MacroReplacement, ParseError> {
    let next = context.expand_after_future()?.text;
    let result = if let Some(value) = dots_by_token(&next) {
        value
    } else if starts_with_at(&next.chars().collect::<Vec<char>>(), 0, "\\not") {
        "\\dotsb".to_string()
    } else {
        match (context.math_symbol_group)(&next) {
            Some(MathSymbolGroup::BinarySymbol) | Some(MathSymbolGroup::RelationSymbol) => {
                "\\dotsb".to_string()
            }
            None => "\\dotso".to_string(),
        }
    };
    Ok(MacroReplacement::ReplacementText(result))
}

pub(crate) fn dots_other_macro(context: &mut MacroExpander) -> Result<MacroReplacement, ParseError> {
    if space_after_dots(&context.future()?.text) {
        Ok(MacroReplacement::ReplacementText("\\ldots\\,".to_string()))
    } else {
        Ok(MacroReplacement::ReplacementText("\\ldots".to_string()))
    }
}

pub(crate) fn dots_comma_macro(context: &mut MacroExpander) -> Result<MacroReplacement, ParseError> {
    let next = context.future()?;
    if next.text != "," && space_after_dots(&next.text) {
        Ok(MacroReplacement::ReplacementText("\\ldots\\,".to_string()))
    } else {
        Ok(MacroReplacement::ReplacementText("\\ldots".to_string()))
    }
}

pub(crate) fn centered_dots_macro(
    context: &mut MacroExpander,
) -> Result<MacroReplacement, ParseError> {
    if space_after_dots(&context.future()?.text) {
        Ok(MacroReplacement::ReplacementText("\\@cdots\\,".to_string()))
    } else {
        Ok(MacroReplacement::ReplacementText("\\@cdots".to_string()))
    }
}