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 std::rc::Rc;

use crate::error::ParseError;
use crate::macro_expander::{token_expansion, MacroExpander, MacroHandler, MacroReplacement};
use crate::token::Token;

use crate::builtin_macros::MacroLogEvent;

fn tokens_to_text(tokens: &[Token]) -> String {
    tokens.iter().rev().map(|token| token.text.clone()).collect()
}

pub(crate) fn message_macro(context: &mut MacroExpander) -> Result<MacroReplacement, ParseError> {
    let args = context.consume_args(1, None)?;
    (context.macro_reporter)(MacroLogEvent::MacroMessage(tokens_to_text(&args[0])));
    Ok(MacroReplacement::ReplacementText(String::new()))
}

pub(crate) fn error_message_macro(
    context: &mut MacroExpander,
) -> Result<MacroReplacement, ParseError> {
    let args = context.consume_args(1, None)?;
    (context.macro_reporter)(MacroLogEvent::MacroErrorMessage(tokens_to_text(&args[0])));
    Ok(MacroReplacement::ReplacementText(String::new()))
}

pub(crate) fn show_macro(context: &mut MacroExpander) -> Result<MacroReplacement, ParseError> {
    let token = context.pop_token()?;
    let status = if context.is_defined(&token.text) {
        "defined"
    } else {
        "undefined"
    };
    (context.macro_reporter)(MacroLogEvent::MacroShow(format!("{}: {}", token.text, status)));
    Ok(MacroReplacement::ReplacementText(String::new()))
}

pub(crate) fn tag_literal_macro(
    context: &mut MacroExpander,
) -> Result<MacroReplacement, ParseError> {
    if context.macros.has("\\df@tag") {
        return Err(ParseError::InvalidArgument {
            message: "Multiple \\tag".to_string(),
            loc: None,
        });
    }
    Ok(MacroReplacement::ReplacementText(
        "\\gdef\\df@tag{\\text{#1}}".to_string(),
    ))
}

fn restore_dynamic_macro(context: &mut MacroExpander, name: &str, old_value: Option<MacroHandler>) {
    context.dynamic_macros.set(name.to_string(), old_value, false);
}

fn braket_separator_handler(
    one: bool,
    doubled: bool,
    middle: Vec<Token>,
    middle_double: Vec<Token>,
    old_middle: Option<MacroHandler>,
    old_middle_double: Option<MacroHandler>,
) -> MacroHandler {
    Rc::new(move |context| {
        if one {
            restore_dynamic_macro(context, "|", old_middle.clone());
            if !middle_double.is_empty() {
                restore_dynamic_macro(context, "\\|", old_middle_double.clone());
            }
        }
        let mut use_double = doubled;
        if !doubled && !middle_double.is_empty() {
            let next = context.future()?;
            if next.text == "|" {
                let _ = context.pop_token()?;
                use_double = true;
            }
        }
        if use_double {
            Ok(token_expansion(middle_double.clone()))
        } else {
            Ok(token_expansion(middle.clone()))
        }
    })
}

fn braket_helper(context: &mut MacroExpander, one: bool) -> Result<MacroReplacement, ParseError> {
    let parts = context.consume_args(4, None)?;
    let left = parts[0].clone();
    let middle = parts[1].clone();
    let middle_double = parts[2].clone();
    let right = parts[3].clone();
    let old_middle = context.dynamic_macros.get_current("|").cloned();
    let old_middle_double = context.dynamic_macros.get_current("\\|").cloned();
    context.begin_group();
    context.dynamic_macros.set(
        "|".to_string(),
        Some(braket_separator_handler(
            one,
            false,
            middle.clone(),
            middle_double.clone(),
            old_middle.clone(),
            old_middle_double.clone(),
        )),
        false,
    );
    if !middle_double.is_empty() {
        context.dynamic_macros.set(
            "\\|".to_string(),
            Some(braket_separator_handler(
                one,
                true,
                middle,
                middle_double,
                old_middle,
                old_middle_double,
            )),
            false,
        );
    }
    let expanded: Result<Vec<Token>, ParseError> = (|| {
        let argument = context.consume_arg(None)?.tokens;
        let mut input = right.clone();
        input.extend(argument);
        input.extend(left.clone());
        context.expand_tokens(input)
    })();
    let close_result = context.end_group();
    match (expanded, close_result) {
        (Err(err), _) => Err(err),
        (Ok(_), Err(err)) => Err(err),
        (Ok(mut tokens), Ok(())) => {
            tokens.reverse();
            Ok(token_expansion(tokens))
        }
    }
}

pub(crate) fn braket_macro(context: &mut MacroExpander) -> Result<MacroReplacement, ParseError> {
    braket_helper(context, false)
}

pub(crate) fn set_macro(context: &mut MacroExpander) -> Result<MacroReplacement, ParseError> {
    braket_helper(context, true)
}