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::{AtomFamily, ParseNode, StyleLevel};
use crate::error::ParseError;
use crate::function_registry::{ArgType, FunctionContext, FunctionParser, FunctionSpec};
use crate::token::token_location;

use super::{normalize_argument, require_function_arg};

struct GenFracConfig {
    continued: bool,
    has_bar_line: bool,
    left_delim: Option<String>,
    right_delim: Option<String>,
    style: Option<StyleLevel>,
}

pub(crate) fn wrap_genfrac_style(node: ParseNode, style: Option<StyleLevel>) -> ParseNode {
    if let Some(style) = style {
        ParseNode::Styling {
            mode: node.mode(),
            body: vec![node],
            style,
            reset_font: false,
        }
    } else {
        node
    }
}

fn standard_genfrac_config(func_name: &str) -> Result<GenFracConfig, ParseError> {
    let config = match func_name {
        "\\cfrac" => GenFracConfig {
            continued: true,
            has_bar_line: true,
            left_delim: None,
            right_delim: None,
            style: Some(StyleLevel::DisplayStyle),
        },
        "\\dfrac" => GenFracConfig {
            continued: false,
            has_bar_line: true,
            left_delim: None,
            right_delim: None,
            style: Some(StyleLevel::DisplayStyle),
        },
        "\\frac" => GenFracConfig {
            continued: false,
            has_bar_line: true,
            left_delim: None,
            right_delim: None,
            style: None,
        },
        "\\tfrac" => GenFracConfig {
            continued: false,
            has_bar_line: true,
            left_delim: None,
            right_delim: None,
            style: Some(StyleLevel::TextStyle),
        },
        "\\dbinom" => GenFracConfig {
            continued: false,
            has_bar_line: false,
            left_delim: Some("(".to_string()),
            right_delim: Some(")".to_string()),
            style: Some(StyleLevel::DisplayStyle),
        },
        "\\binom" => GenFracConfig {
            continued: false,
            has_bar_line: false,
            left_delim: Some("(".to_string()),
            right_delim: Some(")".to_string()),
            style: None,
        },
        "\\tbinom" => GenFracConfig {
            continued: false,
            has_bar_line: false,
            left_delim: Some("(".to_string()),
            right_delim: Some(")".to_string()),
            style: Some(StyleLevel::TextStyle),
        },
        "\\\\atopfrac" => GenFracConfig {
            continued: false,
            has_bar_line: false,
            left_delim: None,
            right_delim: None,
            style: None,
        },
        "\\\\bracefrac" => GenFracConfig {
            continued: false,
            has_bar_line: false,
            left_delim: Some("\\{".to_string()),
            right_delim: Some("\\}".to_string()),
            style: None,
        },
        "\\\\brackfrac" => GenFracConfig {
            continued: false,
            has_bar_line: false,
            left_delim: Some("[".to_string()),
            right_delim: Some("]".to_string()),
            style: None,
        },
        _ => {
            return Err(ParseError::InternalInvariant {
                message: format!("Unrecognized standard genfrac command: {func_name}"),
            })
        }
    };
    Ok(config)
}

pub(crate) fn standard_genfrac_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec![
            "\\cfrac".to_string(),
            "\\dfrac".to_string(),
            "\\frac".to_string(),
            "\\tfrac".to_string(),
            "\\dbinom".to_string(),
            "\\binom".to_string(),
            "\\tbinom".to_string(),
            "\\\\atopfrac".to_string(),
            "\\\\bracefrac".to_string(),
            "\\\\brackfrac".to_string(),
        ],
        num_args: 2,
        allowed_in_argument: true,
        handler: Some(standard_genfrac_handler),
        ..Default::default()
    }
}

fn standard_genfrac_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let config = standard_genfrac_config(&context.func_name)?;
    let node = ParseNode::GenFrac {
        mode: context.mode,
        numer: Box::new(require_function_arg(args, 0, &context.func_name)?),
        denom: Box::new(require_function_arg(args, 1, &context.func_name)?),
        continued: config.continued,
        has_bar_line: config.has_bar_line,
        bar_size: None,
        left_delim: config.left_delim,
        right_delim: config.right_delim,
    };
    Ok(wrap_genfrac_style(node, config.style))
}

fn infix_replacement(func_name: &str) -> Result<String, ParseError> {
    let replacement = match func_name {
        "\\over" => "\\frac",
        "\\choose" => "\\binom",
        "\\atop" => "\\\\atopfrac",
        "\\brace" => "\\\\bracefrac",
        "\\brack" => "\\\\brackfrac",
        _ => {
            return Err(ParseError::InternalInvariant {
                message: format!("Unrecognized infix genfrac command: {func_name}"),
            })
        }
    };
    Ok(replacement.to_string())
}

pub(crate) fn infix_genfrac_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec![
            "\\over".to_string(),
            "\\choose".to_string(),
            "\\atop".to_string(),
            "\\brace".to_string(),
            "\\brack".to_string(),
        ],
        infix: true,
        handler: Some(infix_genfrac_handler),
        ..Default::default()
    }
}

fn infix_genfrac_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    _args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    Ok(ParseNode::Infix {
        mode: context.mode,
        replace_with: infix_replacement(&context.func_name)?,
        size: None,
        loc: token_location(context.token.as_ref()),
    })
}

fn delimiter_from_argument(arg: ParseNode, family: AtomFamily) -> Option<String> {
    match normalize_argument(arg) {
        ParseNode::Atom {
            family: AtomFamily::Mopen,
            text,
            ..
        } if family == AtomFamily::Mopen => {
            if text == "." {
                None
            } else {
                Some(text)
            }
        }
        ParseNode::Atom {
            family: AtomFamily::Mclose,
            text,
            ..
        } if family == AtomFamily::Mclose => {
            if text == "." {
                None
            } else {
                Some(text)
            }
        }
        _ => None,
    }
}

fn genfrac_style(arg: &ParseNode) -> Result<Option<StyleLevel>, ParseError> {
    let first = match arg {
        ParseNode::OrdGroup { body, .. } if body.is_empty() => return Ok(None),
        ParseNode::OrdGroup { body, .. } => &body[0],
        node => node,
    };
    let text = match first {
        ParseNode::TextOrd { text, .. } => text,
        _ => {
            return Err(ParseError::InternalInvariant {
                message: "\\genfrac style argument did not contain a textord".to_string(),
            })
        }
    };
    Ok(match text.as_str() {
        "0" => Some(StyleLevel::DisplayStyle),
        "1" => Some(StyleLevel::TextStyle),
        "2" => Some(StyleLevel::ScriptStyle),
        "3" => Some(StyleLevel::ScriptScriptStyle),
        _ => None,
    })
}

pub(crate) fn general_genfrac_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec!["\\genfrac".to_string()],
        num_args: 6,
        arg_types: vec![
            ArgType::MathArg,
            ArgType::MathArg,
            ArgType::SizeArg,
            ArgType::TextArg,
            ArgType::MathArg,
            ArgType::MathArg,
        ],
        allowed_in_argument: true,
        handler: Some(general_genfrac_handler),
        ..Default::default()
    }
}

fn general_genfrac_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let left = require_function_arg(args, 0, &context.func_name)?;
    let right = require_function_arg(args, 1, &context.func_name)?;
    let bar = require_function_arg(args, 2, &context.func_name)?;
    let style_arg = require_function_arg(args, 3, &context.func_name)?;
    let numer = require_function_arg(args, 4, &context.func_name)?;
    let denom = require_function_arg(args, 5, &context.func_name)?;
    let (has_bar_line, bar_size) = match &bar {
        ParseNode::Size {
            value, is_blank, ..
        } => {
            let has_bar_line = *is_blank || value.number > 0.0;
            let bar_size = if *is_blank { None } else { Some(value.clone()) };
            (has_bar_line, bar_size)
        }
        _ => {
            return Err(ParseError::InternalInvariant {
                message: "\\genfrac bar argument was not a size".to_string(),
            })
        }
    };
    let node = ParseNode::GenFrac {
        mode: context.mode,
        numer: Box::new(numer),
        denom: Box::new(denom),
        continued: false,
        has_bar_line,
        bar_size,
        left_delim: delimiter_from_argument(left, AtomFamily::Mopen),
        right_delim: delimiter_from_argument(right, AtomFamily::Mclose),
    };
    Ok(wrap_genfrac_style(node, genfrac_style(&style_arg)?))
}

pub(crate) fn above_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec!["\\above".to_string()],
        num_args: 1,
        arg_types: vec![ArgType::SizeArg],
        infix: true,
        handler: Some(above_handler),
        ..Default::default()
    }
}

fn above_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let arg = require_function_arg(args, 0, &context.func_name)?;
    let value = match &arg {
        ParseNode::Size { value, .. } => value.clone(),
        _ => {
            return Err(ParseError::InternalInvariant {
                message: "\\above argument was not a size".to_string(),
            })
        }
    };
    Ok(ParseNode::Infix {
        mode: context.mode,
        replace_with: "\\\\abovefrac".to_string(),
        size: Some(value),
        loc: token_location(context.token.as_ref()),
    })
}

pub(crate) fn abovefrac_spec() -> FunctionSpec {
    FunctionSpec {
        names: vec!["\\\\abovefrac".to_string()],
        num_args: 3,
        arg_types: vec![ArgType::MathArg, ArgType::SizeArg, ArgType::MathArg],
        handler: Some(abovefrac_handler),
        ..Default::default()
    }
}

fn abovefrac_handler(
    _parser: &mut dyn FunctionParser,
    context: &FunctionContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    let numer = require_function_arg(args, 0, &context.func_name)?;
    let infix = require_function_arg(args, 1, &context.func_name)?;
    let denom = require_function_arg(args, 2, &context.func_name)?;
    let bar_size = match &infix {
        ParseNode::Infix { size: Some(size), .. } => size.clone(),
        _ => {
            return Err(ParseError::InternalInvariant {
                message: "\\\\abovefrac expected an infix size".to_string(),
            })
        }
    };
    Ok(ParseNode::GenFrac {
        mode: context.mode,
        numer: Box::new(numer),
        denom: Box::new(denom),
        continued: false,
        has_bar_line: bar_size.number > 0.0,
        bar_size: Some(bar_size),
        left_delim: None,
        right_delim: None,
    })
}