texform-core 0.1.0

Parser, document tree, and serializer for TeXForm (internal; use the texform crate)
Documentation
use texform_interface::syntax_node::{
    Argument, ArgumentKind, ArgumentValue, ContentMode, GroupKind, SyntaxNode,
};

fn content_argument(node: SyntaxNode) -> Argument {
    Argument::from_value(ArgumentKind::Mandatory, ArgumentValue::TextContent(node))
}

#[test]
fn leaf_and_group_helpers_report_meaningful_structure() {
    let char_node = SyntaxNode::Char('a');
    let group = SyntaxNode::Group {
        mode: ContentMode::Math,
        kind: GroupKind::Explicit,
        children: vec![SyntaxNode::Char('x')],
    };

    assert!(char_node.is_leaf());
    assert!(group.is_group());
    assert_eq!(group.group_mode(), Some(ContentMode::Math));
}

#[test]
fn root_preserves_group_helper_semantics() {
    let root = SyntaxNode::root(ContentMode::Text, vec![SyntaxNode::Text("x".to_string())]);

    assert!(root.is_group());
    assert_eq!(root.group_mode(), Some(ContentMode::Text));
}

#[test]
fn implicit_group_helpers_preserve_mode_and_kind() {
    let children = vec![SyntaxNode::Char('a'), SyntaxNode::Char('b')];
    let group = SyntaxNode::implicit_group(ContentMode::Math, children.clone());

    match group {
        SyntaxNode::Group {
            mode,
            kind,
            children: c,
        } => {
            assert_eq!(mode, ContentMode::Math);
            assert_eq!(kind, GroupKind::Implicit);
            assert_eq!(c.len(), 2);
        }
        _ => panic!("Expected Group"),
    }

    let empty = SyntaxNode::empty_group(ContentMode::Text);
    match empty {
        SyntaxNode::Group {
            mode,
            kind,
            children,
        } => {
            assert_eq!(mode, ContentMode::Text);
            assert_eq!(kind, GroupKind::Implicit);
            assert!(children.is_empty());
        }
        _ => panic!("Expected Group"),
    }
}

#[test]
fn argument_from_value_preserves_kind_and_variant() {
    let arg = Argument::from_value(
        ArgumentKind::Mandatory,
        ArgumentValue::TextContent(SyntaxNode::Char('x')),
    );

    assert_eq!(arg.kind, ArgumentKind::Mandatory);
    assert_eq!(arg.value, ArgumentValue::TextContent(SyntaxNode::Char('x')));
}

#[test]
fn content_mode_display_helpers_match_public_strings() {
    assert_eq!(ContentMode::Math.as_str(), "math");
    assert_eq!(ContentMode::Text.as_str(), "text");
    assert_eq!(ContentMode::Math.to_string(), "math");
    assert_eq!(ContentMode::Text.to_string(), "text");
}

#[test]
fn command_is_leaf_only_without_content_arguments() {
    let leaf = SyntaxNode::Command {
        name: "alpha".to_string(),
        args: vec![],
        known: true,
    };
    let non_leaf = SyntaxNode::Command {
        name: "text".to_string(),
        args: vec![Some(content_argument(SyntaxNode::Char('x')))],
        known: true,
    };

    assert!(leaf.is_leaf());
    assert!(!non_leaf.is_leaf());
}

#[test]
fn declarative_is_leaf_only_without_content_arguments() {
    let leaf = SyntaxNode::Declarative {
        name: "bfseries".to_string(),
        args: vec![],
    };
    let non_leaf = SyntaxNode::Declarative {
        name: "color".to_string(),
        args: vec![Some(content_argument(SyntaxNode::Text("red".to_string())))],
    };

    assert!(leaf.is_leaf());
    assert!(!non_leaf.is_leaf());
}

#[test]
fn display_merges_adjacent_chars_without_losing_node_boundaries() {
    let node = SyntaxNode::Char('a');
    let display = format!("{}", node);
    assert!(display.contains("Char"));
    assert!(display.contains('a'));

    let group = SyntaxNode::implicit_group(
        ContentMode::Math,
        vec![SyntaxNode::Char('x'), SyntaxNode::Char('y')],
    );
    let display = format!("{}", group);
    assert!(display.contains("Group"));
    assert!(display.contains("Chars(\"xy\")"));
    assert!(!display.contains("Char('x')"));
    assert!(!display.contains("Char('y')"));
}

#[test]
fn display_keeps_non_char_boundaries_visible() {
    let group = SyntaxNode::implicit_group(
        ContentMode::Math,
        vec![
            SyntaxNode::Char('a'),
            SyntaxNode::Char('b'),
            SyntaxNode::ActiveSpace,
            SyntaxNode::Char('c'),
        ],
    );
    let display = format!("{}", group);

    assert!(display.contains("Chars(\"ab\")"));
    assert!(display.contains("ActiveSpace"));
    assert!(display.contains("Char('c')"));
}

#[test]
fn default_display_marks_root_node() {
    let root = SyntaxNode::root(ContentMode::Math, vec![SyntaxNode::Char('x')]);
    let display = format!("{}", root);

    assert!(display.contains("Root(Math)"));
    assert!(!display.contains("Group(Math, root)"));
}

#[test]
fn display_standalone_implicit_group_is_not_marked_as_root() {
    let group = SyntaxNode::implicit_group(ContentMode::Math, vec![SyntaxNode::Char('x')]);
    let display = format!("{}", group);

    assert!(display.contains("Group(Math, Implicit)"));
    assert!(!display.contains("Root("));
}

#[test]
fn error_node_display_and_leaf_status() {
    let error = SyntaxNode::Error {
        message: "invalid \\left delimiter".to_string(),
        snippet: "\\left\\foo x \\right)".to_string(),
    };

    assert!(error.is_leaf());

    let display = error.to_string();
    assert!(display.contains("invalid \\left delimiter"));
    assert!(display.contains("\\left\\foo x \\right)"));
}