texform-transform 0.1.0

Profile-based AST transform engine for TeXForm (internal; use the texform crate)
Documentation
use crate::ast::{
    ArgumentKind, ArgumentSlot, ArgumentValue, ContentMode, Delimiter, GroupKind, Node, NodeId,
};
use crate::rewrite::RuleError;
use crate::rewrite::helpers::FenceToken;
use crate::rewrite::rule::RuleKey;
use crate::rewrite::rule_context::RuleContext;

pub(super) struct BinaryFencePair {
    pub(super) auto_left: Delimiter,
    pub(super) auto_right: Delimiter,
    pub(super) fixed_left: FenceToken,
    pub(super) fixed_right: FenceToken,
}

pub(super) fn required_braced_math_arg(
    rule: RuleKey,
    cx: &mut RuleContext<'_>,
    slot: &ArgumentSlot,
    subject: &str,
    label: &str,
) -> Result<NodeId, RuleError> {
    match slot {
        Some(arg) if arg.kind == ArgumentKind::Group => match arg.value {
            ArgumentValue::MathContent(node_id) => Ok(node_id),
            _ => Err(cx.for_rule(rule).invalid_shape(format!("{subject} {label} should be math content"))),
        },
        _ => Err(cx.for_rule(rule).invalid_shape(format!("{subject} {label} should be a required braced math group"))),
    }
}

pub(super) fn required_math_arg(
    rule: RuleKey,
    cx: &mut RuleContext<'_>,
    slot: &ArgumentSlot,
    subject: &str,
    label: &str,
) -> Result<NodeId, RuleError> {
    cx.for_rule(rule)
        .mandatory_or_group_math_content(slot, subject, label)
}

pub(super) fn replace_with_binary_bracket_fence(
    cx: &mut RuleContext<'_>,
    node_id: NodeId,
    starred: bool,
    left: NodeId,
    right: NodeId,
    fences: BinaryFencePair,
) {
    if starred {
        replace_with_fixed_fence(
            cx,
            node_id,
            left,
            right,
            fences.fixed_left,
            fences.fixed_right,
        );
    } else {
        replace_with_auto_fence(cx, node_id, left, right, fences.auto_left, fences.auto_right);
    }
}

fn replace_with_auto_fence(
    cx: &mut RuleContext<'_>,
    node_id: NodeId,
    left: NodeId,
    right: NodeId,
    open: Delimiter,
    close: Delimiter,
) {
    let mut children = Vec::new();
    append_binary_bracket_body(cx, &mut children, left, right);

    cx.ast.replace_node_drop_detached_children(node_id,
        Node::Group {
            children,
            kind: GroupKind::Delimited {
                left: open,
                right: close,
            },
            mode: ContentMode::Math,
        },
    );
}

fn replace_with_fixed_fence(
    cx: &mut RuleContext<'_>,
    node_id: NodeId,
    left: NodeId,
    right: NodeId,
    open: FenceToken,
    close: FenceToken,
) {
    let mut rest = Vec::new();
    append_binary_bracket_body(cx, &mut rest, left, right);
    rest.push(cx.ast.new_node(close.node()));

    let open = cx.ast.new_node(open.node());
    cx.ast
        .replace_with_math_sequence_preserving_scripts(node_id, Vec::new(), open, rest);
}

fn append_binary_bracket_body(
    cx: &mut RuleContext<'_>,
    out: &mut Vec<NodeId>,
    left: NodeId,
    right: NodeId,
) {
    cx.ast.append_cloned_math_content(out, left);
    out.push(cx.ast.new_node(Node::Char(',')));
    cx.ast.append_cloned_math_content(out, right);
}