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::ast::ParseNode;
use crate::error::ParseError;
use crate::function_registry::{FunctionContext, FunctionParser, FunctionSpec};

use super::{binrel_class, is_character_box, normalize_argument, require_function_arg};

pub(crate) const FONT_COMMANDS: &[(&str, &str)] = &[
    ("\\mathrm", "mathrm"),
    ("\\mathit", "mathit"),
    ("\\mathbf", "mathbf"),
    ("\\mathnormal", "mathnormal"),
    ("\\mathsfit", "mathsfit"),
    ("\\mathbb", "mathbb"),
    ("\\mathcal", "mathcal"),
    ("\\mathfrak", "mathfrak"),
    ("\\mathscr", "mathscr"),
    ("\\mathsf", "mathsf"),
    ("\\mathtt", "mathtt"),
    ("\\Bbb", "mathbb"),
    ("\\bold", "mathbf"),
    ("\\frak", "mathfrak"),
];

pub(crate) const OLD_FONT_COMMANDS: &[(&str, &str)] = &[
    ("\\rm", "mathrm"),
    ("\\sf", "mathsf"),
    ("\\tt", "mathtt"),
    ("\\bf", "mathbf"),
    ("\\it", "mathit"),
    ("\\cal", "mathcal"),
];

fn command_font(commands: &[(&str, &str)], func_name: &str) -> Result<String, ParseError> {
    for (name, font) in commands {
        if *name == func_name {
            return Ok((*font).to_string());
        }
    }
    Err(ParseError::InternalInvariant {
        message: format!("Unknown font command: {func_name}"),
    })
}

pub(crate) fn font_spec() -> FunctionSpec {
    FunctionSpec {
        names: FONT_COMMANDS.iter().map(|(n, _)| n.to_string()).collect(),
        num_args: 1,
        allowed_in_argument: true,
        handler: Some(font_handler),
        ..Default::default()
    }
}

fn font_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    Ok(ParseNode::Font {
        mode: context.mode,
        font: command_font(FONT_COMMANDS, &context.func_name)?,
        body: Box::new(normalize_argument(require_function_arg(
            args,
            0,
            &context.func_name,
        )?)),
    })
}

pub(crate) fn boldsymbol_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec!["\\boldsymbol".to_string(), "\\bm".to_string()],
        num_args: 1,
        handler: Some(boldsymbol_handler),
        ..Default::default()
    }
}

fn boldsymbol_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let body = require_function_arg(args, 0, &context.func_name)?;
    Ok(ParseNode::MClass {
        mode: context.mode,
        mclass: binrel_class(&body),
        body: vec![ParseNode::Font {
            mode: context.mode,
            font: "boldsymbol".to_string(),
            body: Box::new(body.clone()),
        }],
        is_character_box: is_character_box(&body),
    })
}

pub(crate) fn old_font_spec() -> FunctionSpec {
    FunctionSpec {
        names: OLD_FONT_COMMANDS.iter().map(|(n, _)| n.to_string()).collect(),
        allowed_in_text: true,
        handler: Some(old_font_handler),
        ..Default::default()
    }
}

fn old_font_handler(
    parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    _args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let body = parser.parse_expression(true, context.break_on_token_text.as_deref())?;
    Ok(ParseNode::Font {
        mode: context.mode,
        font: command_font(OLD_FONT_COMMANDS, &context.func_name)?,
        body: Box::new(ParseNode::OrdGroup {
            mode: context.mode,
            loc: None,
            body,
            semisimple: false,
        }),
    })
}