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 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}