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