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::{ArgType, FunctionContext, FunctionParser, FunctionSpec};

use super::{normalize_argument, require_function_arg};

pub(crate) fn accent_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec![
            "\\acute".to_string(),
            "\\grave".to_string(),
            "\\ddot".to_string(),
            "\\tilde".to_string(),
            "\\bar".to_string(),
            "\\breve".to_string(),
            "\\check".to_string(),
            "\\hat".to_string(),
            "\\vec".to_string(),
            "\\dot".to_string(),
            "\\mathring".to_string(),
            "\\widecheck".to_string(),
            "\\widehat".to_string(),
            "\\widetilde".to_string(),
            "\\overrightarrow".to_string(),
            "\\overleftarrow".to_string(),
            "\\Overrightarrow".to_string(),
            "\\overleftrightarrow".to_string(),
            "\\overgroup".to_string(),
            "\\overlinesegment".to_string(),
            "\\overleftharpoon".to_string(),
            "\\overrightharpoon".to_string(),
        ],
        num_args: 1,
        handler: Some(accent_handler),
        ..Default::default()
    }
}

fn is_non_stretchy_accent(func_name: &str) -> bool {
    matches!(
        func_name,
        "\\acute"
            | "\\grave"
            | "\\ddot"
            | "\\tilde"
            | "\\bar"
            | "\\breve"
            | "\\check"
            | "\\hat"
            | "\\vec"
            | "\\dot"
            | "\\mathring"
    )
}

fn accent_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let is_stretchy = !is_non_stretchy_accent(&context.func_name);
    Ok(ParseNode::Accent {
        mode: context.mode,
        loc: None,
        label: context.func_name.clone(),
        is_stretchy,
        is_shifty: !is_stretchy
            || context.func_name == "\\widehat"
            || context.func_name == "\\widetilde"
            || context.func_name == "\\widecheck",
        base: Box::new(normalize_argument(require_function_arg(
            args,
            0,
            &context.func_name,
        )?)),
    })
}

pub(crate) fn text_accent_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec![
            "\\'".to_string(),
            "\\`".to_string(),
            "\\^".to_string(),
            "\\~".to_string(),
            "\\=".to_string(),
            "\\u".to_string(),
            "\\.".to_string(),
            "\\\"".to_string(),
            "\\c".to_string(),
            "\\r".to_string(),
            "\\H".to_string(),
            "\\v".to_string(),
            "\\textcircled".to_string(),
        ],
        num_args: 1,
        arg_types: vec![ArgType::PrimitiveArg],
        allowed_in_text: true,
        handler: Some(text_accent_handler),
        ..Default::default()
    }
}

fn text_accent_handler(
    parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let mode = if context.mode == crate::ast::Mode::Math {
        parser.report_nonstrict(
            "mathVsTextAccents",
            &format!(
                "LaTeX's accent {} works only in text mode",
                context.func_name
            ),
            context.token.as_ref(),
        )?;
        crate::ast::Mode::Text
    } else {
        context.mode
    };
    Ok(ParseNode::Accent {
        mode,
        loc: None,
        label: context.func_name.clone(),
        is_stretchy: false,
        is_shifty: true,
        base: Box::new(require_function_arg(args, 0, &context.func_name)?),
    })
}