texform-core 0.1.0

Parser, document tree, and serializer for TeXForm (internal; use the texform crate)
Documentation
mod support;

use support::parser::parse;
use texform_interface::syntax_node::{ContentMode, GroupKind, SyntaxNode};

fn parse_math(src: &str) -> SyntaxNode {
    parse(src, true).expect("expected parse success").0
}

fn command(name: &str) -> SyntaxNode {
    SyntaxNode::Command {
        name: name.to_string(),
        args: Vec::new(),
        known: true,
    }
}

fn explicit_math_group(children: Vec<SyntaxNode>) -> SyntaxNode {
    SyntaxNode::Group {
        mode: ContentMode::Math,
        kind: GroupKind::Explicit,
        children,
    }
}

fn implicit_math_group(children: Vec<SyntaxNode>) -> SyntaxNode {
    SyntaxNode::Group {
        mode: ContentMode::Math,
        kind: GroupKind::Implicit,
        children,
    }
}

fn scripted(
    base: SyntaxNode,
    subscript: Option<SyntaxNode>,
    superscript: Option<SyntaxNode>,
) -> SyntaxNode {
    SyntaxNode::Scripted {
        base: Box::new(base),
        subscript: subscript.map(Box::new),
        superscript: superscript.map(Box::new),
    }
}

fn root(children: Vec<SyntaxNode>) -> SyntaxNode {
    SyntaxNode::Root {
        mode: ContentMode::Math,
        children,
    }
}

#[test]
fn ascii_prime_scripts_parse_as_prime_nodes() {
    assert_eq!(
        parse_math("f'"),
        root(vec![scripted(
            SyntaxNode::Char('f'),
            None,
            Some(SyntaxNode::Prime { count: 1 }),
        )])
    );

    assert_eq!(
        parse_math("f''"),
        root(vec![scripted(
            SyntaxNode::Char('f'),
            None,
            Some(SyntaxNode::Prime { count: 2 }),
        )])
    );
}

#[test]
fn prime_then_explicit_superscript_keeps_single_superscript_slot() {
    assert_eq!(
        parse_math("f'^2"),
        root(vec![scripted(
            SyntaxNode::Char('f'),
            None,
            Some(implicit_math_group(vec![
                SyntaxNode::Prime { count: 1 },
                SyntaxNode::Char('2'),
            ])),
        )])
    );
}

#[test]
fn braced_superscript_prime_content_stays_inside_the_group() {
    assert_eq!(
        parse_math("f^{'}"),
        root(vec![scripted(
            SyntaxNode::Char('f'),
            None,
            Some(explicit_math_group(vec![SyntaxNode::Prime { count: 1 }])),
        )])
    );

    assert_eq!(
        parse_math(r"A^{'\alpha}"),
        root(vec![scripted(
            SyntaxNode::Char('A'),
            None,
            Some(explicit_math_group(vec![
                SyntaxNode::Prime { count: 1 },
                command("alpha"),
            ])),
        )])
    );
}

#[test]
fn command_prime_is_not_collapsed_by_the_parser() {
    assert_eq!(
        parse_math(r"f^{\prime}"),
        root(vec![scripted(
            SyntaxNode::Char('f'),
            None,
            Some(explicit_math_group(vec![command("prime")])),
        )])
    );
}

#[test]
fn leading_prime_is_a_math_atom_not_an_empty_base_script() {
    assert_eq!(
        parse_math("'x"),
        root(vec![SyntaxNode::Prime { count: 1 }, SyntaxNode::Char('x')])
    );
}

#[test]
fn prime_atoms_inside_script_groups_can_receive_scripts() {
    assert_eq!(
        parse_math("x^{'_{a}}"),
        root(vec![scripted(
            SyntaxNode::Char('x'),
            None,
            Some(explicit_math_group(vec![scripted(
                SyntaxNode::Prime { count: 1 },
                Some(explicit_math_group(vec![SyntaxNode::Char('a')])),
                None,
            )])),
        )])
    );

    assert_eq!(
        parse_math("x^{'^{a}}"),
        root(vec![scripted(
            SyntaxNode::Char('x'),
            None,
            Some(explicit_math_group(vec![scripted(
                SyntaxNode::Prime { count: 1 },
                None,
                Some(explicit_math_group(vec![SyntaxNode::Char('a')])),
            )])),
        )])
    );
}