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::{ColumnSeparationType, ParseNode, StyleLevel};
use crate::error::ParseError;
use crate::environments::registry::{ArrayEnvironmentOptions, EnvironmentContext, EnvironmentParser};
use crate::functions::require_function_arg;

pub(crate) fn environment_argument_text(arg: &ParseNode) -> Result<String, ParseError> {
    let nodes = match arg {
        ParseNode::OrdGroup { body, .. } => body,
        _ => std::slice::from_ref(arg),
    };
    let mut text = String::new();
    for node in nodes {
        let part = match node {
            ParseNode::MathOrd { text, .. }
            | ParseNode::TextOrd { text, .. }
            | ParseNode::Atom { text, .. } => text.as_str(),
            _ => {
                return Err(ParseError::InvalidArgument {
                    message: "Invalid alignment column count".to_string(),
                    loc: None,
                })
            }
        };
        text.push_str(part);
    }
    Ok(text)
}

fn alignat_pair_count(arg: &ParseNode) -> Result<usize, ParseError> {
    let text = environment_argument_text(arg)?;
    let Some(count) = crate::builtin_macros_commands::parse_argument_count(&text) else {
        return Err(ParseError::InvalidArgument {
            message: "Invalid alignment column count".to_string(),
            loc: None,
        });
    };
    Ok(count)
}

pub(crate) fn alignat_environment_handler(
    parser: &mut dyn EnvironmentParser,
    context: &EnvironmentContext,
    args: &[ParseNode],
    _opt_args: &[Option<ParseNode>],
) -> Result<ParseNode, ParseError> {
    if context.env_name != "alignedat" && !context.display_mode {
        return Err(ParseError::InvalidArgument {
            message: format!("{{{}}} can be used only in display mode.", context.env_name),
            loc: None,
        });
    }
    let pairs = alignat_pair_count(&require_function_arg(
        args,
        0,
        &format!("\\begin{{{}}}", context.env_name),
    )?)?;
    let auto_tag = match context.env_name.as_str() {
        "alignat" => Some(true),
        "alignat*" => Some(false),
        _ => None,
    };
    let array = parser.parse_array(ArrayEnvironmentOptions {
        columns: None,
        array_stretch: 1.0,
        hskip_before_and_after: false,
        cell_style: StyleLevel::DisplayStyle,
        max_columns: Some(pairs * 2),
        single_row: false,
        auto_tag,
        leqno: context.leqno,
        add_jot: true,
        column_separation_type: Some(ColumnSeparationType::AlignAtSeparation),
    })?;
    let array = super::alignment::insert_alignment_empty_groups(array)?;
    let count = super::alignment::array_body(&array)?.iter().map(|r| r.len()).max().unwrap_or(0);
    super::alignment::replace_alignment_columns(array, super::alignment::alignment_columns(count, false))
}