Skip to main content

ratex_parser/functions/
mclass.rs

1use std::collections::HashMap;
2
3use crate::error::ParseResult;
4use crate::functions::{define_function_full, FunctionContext, FunctionSpec};
5use crate::parse_node::ParseNode;
6
7pub fn register(map: &mut HashMap<&'static str, FunctionSpec>) {
8    define_function_full(
9        map,
10        &[
11            "\\mathord",
12            "\\mathbin",
13            "\\mathrel",
14            "\\mathopen",
15            "\\mathclose",
16            "\\mathpunct",
17            "\\mathinner",
18        ],
19        "mclass",
20        1,
21        0,
22        None,
23        false,
24        false,
25        true,
26        false,
27        true,
28        handle_mclass,
29    );
30
31    define_function_full(
32        map,
33        &["\\stackrel", "\\overset", "\\underset"],
34        "mclass",
35        2,
36        0,
37        None,
38        false,
39        false,
40        true,
41        false,
42        false,
43        handle_stackrel,
44    );
45
46    // \@binrel{x}{body} — infer mbin/mrel/mord from x, wrap body
47    define_function_full(
48        map,
49        &["\\@binrel"],
50        "mclass",
51        2,
52        0,
53        None,
54        false,
55        false,
56        true,
57        false,
58        false,
59        handle_binrel,
60    );
61}
62
63fn handle_mclass(
64    ctx: &mut FunctionContext,
65    args: Vec<ParseNode>,
66    _opt_args: Vec<Option<ParseNode>>,
67) -> ParseResult<ParseNode> {
68    let mclass = format!("m{}", &ctx.func_name[5..]);
69    let body = ParseNode::ord_argument(args.into_iter().next().unwrap());
70    let is_char_box = body.len() == 1 && body[0].is_symbol_node();
71
72    Ok(ParseNode::MClass {
73        mode: ctx.parser.mode,
74        mclass,
75        body,
76        is_character_box: is_char_box,
77        loc: None,
78    })
79}
80
81fn binrel_class(arg: &ParseNode) -> String {
82    let atom = if let ParseNode::OrdGroup { body, .. } = arg {
83        if !body.is_empty() {
84            &body[0]
85        } else {
86            arg
87        }
88    } else {
89        arg
90    };
91
92    if let ParseNode::Atom { family, .. } = atom {
93        match family {
94            crate::parse_node::AtomFamily::Bin => return "mbin".to_string(),
95            crate::parse_node::AtomFamily::Rel => return "mrel".to_string(),
96            _ => {}
97        }
98    }
99    "mord".to_string()
100}
101
102fn handle_binrel(
103    ctx: &mut FunctionContext,
104    args: Vec<ParseNode>,
105    _opt_args: Vec<Option<ParseNode>>,
106) -> ParseResult<ParseNode> {
107    let mut args = args.into_iter();
108    let atom = args.next().unwrap();
109    let body = args.next().unwrap();
110    let mclass = binrel_class(&atom);
111
112    Ok(ParseNode::MClass {
113        mode: ctx.parser.mode,
114        mclass,
115        body: ParseNode::ord_argument(body),
116        is_character_box: false,
117        loc: None,
118    })
119}
120
121fn handle_stackrel(
122    ctx: &mut FunctionContext,
123    args: Vec<ParseNode>,
124    _opt_args: Vec<Option<ParseNode>>,
125) -> ParseResult<ParseNode> {
126    let mut args = args.into_iter();
127    let shifted_arg = args.next().unwrap();
128    let base_arg = args.next().unwrap();
129
130    let mclass = if ctx.func_name == "\\stackrel" {
131        "mrel".to_string()
132    } else {
133        binrel_class(&base_arg)
134    };
135
136    let op_node = ParseNode::Op {
137        mode: ctx.parser.mode,
138        limits: true,
139        always_handle_sup_sub: Some(true),
140        suppress_base_shift: Some(ctx.func_name != "\\stackrel"),
141        parent_is_sup_sub: false,
142        symbol: false,
143        name: None,
144        body: Some(ParseNode::ord_argument(base_arg)),
145        loc: None,
146    };
147
148    let (sup, sub) = if ctx.func_name == "\\underset" {
149        (None, Some(Box::new(shifted_arg)))
150    } else {
151        (Some(Box::new(shifted_arg)), None)
152    };
153
154    let supsub = ParseNode::SupSub {
155        mode: ctx.parser.mode,
156        base: Some(Box::new(op_node)),
157        sup,
158        sub,
159        loc: None,
160    };
161
162    Ok(ParseNode::MClass {
163        mode: ctx.parser.mode,
164        mclass,
165        body: vec![supsub],
166        is_character_box: false,
167        loc: None,
168    })
169}