1use std::borrow::Cow;
2
3use libxml::tree::Node;
4
5use super::SizingClosure;
6use crate::common::arena::SymHashMap as HashMap;
8use crate::{
9 Digested,
10 common::{error::*, object::Object, store::Stored},
11 definition::{
12 BeforeDigestClosure, ConstructionClosure, Definition, DigestionClosure, FontDirective,
13 PrimitiveClosure, Reversion,
14 },
15 document::Document,
16 parameter::Parameters,
17 state::Scope,
18 token::*,
19 tokens::Tokens,
20 whatsit::Whatsit,
21};
22
23#[derive(Clone)]
39pub struct MathPrimitiveOptions {
40 pub bounded: bool,
41 pub mode: Option<String>,
42 pub before_digest: Vec<BeforeDigestClosure>,
43 pub after_digest: Vec<DigestionClosure>,
44 pub before_construct: Vec<ConstructionClosure>,
45 pub after_construct: Vec<ConstructionClosure>,
46 pub is_prefix: bool,
47 pub scope: Option<Scope>,
48 pub font: Option<FontDirective>,
49 pub require_math: bool,
50 pub forbid_math: bool,
51 pub locked: bool,
52 pub alias: Option<String>,
53 pub decl_id: Option<String>,
54 pub replace: Option<String>,
55 pub protected: bool,
56 pub robust: bool,
57
58 pub name: Option<String>,
60 pub meaning: Option<String>,
61 pub omcd: Option<String>,
62 pub reversion: Option<Reversion>,
63 pub sizer: Option<SizingClosure>,
64 pub role: Option<String>,
65 pub operator_role: Option<String>,
66 pub reorder: bool,
67 pub dual: bool,
68 pub mathstyle: Option<String>,
69 pub dynamic_mathstyle: bool,
72 pub scriptpos: Option<String>,
73 pub dynamic_scriptpos: bool,
76 pub operator_scriptpos: Option<usize>,
77 pub stretchy: Option<bool>,
78 pub operator_stretchy: Option<bool>,
79 pub nogroup: bool,
80 pub hide_content_reversion: bool,
81 pub revert_as: Option<Cow<'static, str>>,
82 pub lpadding: Option<usize>,
83 pub rpadding: Option<usize>,
84}
85impl Default for MathPrimitiveOptions {
86 fn default() -> Self {
87 MathPrimitiveOptions {
88 bounded: false,
89 before_digest: Vec::new(),
90 after_digest: Vec::new(),
91 before_construct: Vec::new(),
92 after_construct: Vec::new(),
93 mode: None,
94 is_prefix: false,
95 scope: None,
96 require_math: false,
97 forbid_math: false,
98 locked: false,
99 alias: None,
100 font: None,
101 decl_id: None,
102 replace: None,
103 protected: false,
104 robust: false,
105
106 name: None,
108 meaning: None,
109 omcd: None,
110 reversion: None,
111 sizer: None,
112 role: None,
113 operator_role: None,
114 reorder: false,
115 dual: false,
116 mathstyle: None,
117 dynamic_mathstyle: false,
118 scriptpos: None,
119 dynamic_scriptpos: false,
120 operator_scriptpos: None,
121 stretchy: None,
122 operator_stretchy: None,
123 nogroup: true,
124 hide_content_reversion: false,
125 revert_as: None,
126 lpadding: None,
127 rpadding: None,
128 }
129 }
130}
131impl PartialEq for MathPrimitiveOptions {
132 fn eq(&self, other: &MathPrimitiveOptions) -> bool {
133 self.name == other.name && self.meaning == other.meaning && self.role == other.role
134 }
135}
136
137impl MathPrimitiveOptions {
138 pub fn to_hash_stored(&self) -> HashMap<Stored> {
139 let mut h = HashMap::default();
140 if let Some(ref meaning) = self.meaning {
141 h.insert("meaning", meaning.into());
142 }
143 if let Some(ref name) = self.name {
144 h.insert("name", name.into());
145 }
146 if let Some(ref omcd) = self.omcd {
147 h.insert("omcd", omcd.into());
148 }
149 if let Some(ref role) = self.role {
150 h.insert("role", role.into());
151 }
152 if let Some(ref decl_id) = self.decl_id {
153 h.insert("decl_id", decl_id.into());
154 }
155 if let Some(ref operator_role) = self.operator_role {
156 h.insert("operator_role", operator_role.into());
157 }
158 if let Some(ref mathstyle) = self.mathstyle {
159 h.insert("mathstyle", mathstyle.into());
160 }
161 if let Some(ref scriptpos) = self.scriptpos {
162 h.insert("scriptpos", scriptpos.into());
163 }
164 if let Some(ref operator_scriptpos) = self.operator_scriptpos {
165 h.insert(
166 "operator_scriptpos",
167 Stored::Int(*operator_scriptpos as i64),
168 );
169 }
170 if let Some(ref stretchy) = self.stretchy {
171 h.insert("stretchy", (*stretchy).into());
172 }
173 if let Some(ref stretchy) = self.operator_stretchy {
174 h.insert("operator_stretchy", (*stretchy).into());
175 }
176 if let Some(ref mode) = self.mode {
177 h.insert("mode", mode.into());
178 }
179 if let Some(ref font_directive) = self.font {
181 h.insert("font", Stored::FontDirective(font_directive.clone()));
182 }
183 if let Some(ref lpadding) = self.lpadding {
184 h.insert("lpadding", (*lpadding).into());
185 }
186 if let Some(ref rpadding) = self.rpadding {
187 h.insert("rpadding", (*rpadding).into());
188 }
189
190 h
191 }
192
193 pub fn to_hash_stored_with_overrides(
197 &self,
198 mode_override: Option<&'static str>,
199 mathstyle_override: Option<&'static str>,
200 scriptpos_override: Option<&'static str>,
201 ) -> HashMap<Stored> {
202 let mut h = self.to_hash_stored();
203 if let Some(m) = mode_override {
204 h.insert("mode", Stored::String(crate::common::arena::pin_static(m)));
205 }
206 if let Some(ms) = mathstyle_override {
207 h.insert(
208 "mathstyle",
209 Stored::String(crate::common::arena::pin_static(ms)),
210 );
211 }
212 if let Some(sp) = scriptpos_override {
213 h.insert(
214 "scriptpos",
215 Stored::String(crate::common::arena::pin_static(sp)),
216 );
217 }
218 h
219 }
220
221 pub fn has_complex_option(&self) -> bool {
225 self.bounded
228 || self.mode.is_some()
229 || !self.before_digest.is_empty()
230 || !self.after_digest.is_empty()
231 || self.is_prefix
232 || self.require_math
233 || self.forbid_math
234 || self.alias.is_some()
235 || self.decl_id.is_some()
236 || self.replace.is_some()
237 || self.reversion.is_some()
238 || self.sizer.is_some()
239 || self.operator_role.is_some()
240 || self.reorder
241 || self.dual
242 || self.operator_scriptpos.is_some()
243 || self.stretchy.is_some()
244 || self.operator_stretchy.is_some()
245 || self.hide_content_reversion
246 || self.revert_as.is_some()
247 }
248}
249
250#[derive(Clone)]
251pub struct MathPrimitive {
252 pub cs: Token,
253 pub paramlist: Option<Parameters>,
254 pub nargs: Option<usize>,
255 pub replacement: Option<PrimitiveClosure>,
256 pub options: MathPrimitiveOptions,
257 pub alias: Option<String>,
258}
259impl Default for MathPrimitive {
260 fn default() -> Self {
261 MathPrimitive {
262 cs: T_CS!("MathPrimitive"),
263 paramlist: None,
264 nargs: None,
265 replacement: None,
266 options: MathPrimitiveOptions::default(),
267 alias: None,
268 }
269 }
270}
271impl PartialEq for MathPrimitive {
272 fn eq(&self, other: &MathPrimitive) -> bool { self.cs == other.cs }
273}
274
275impl Object for MathPrimitive {
281 fn stringify(&self) -> String { <Self as Definition>::stringify_type(self, "MathPrimitive") }
282}
283impl Definition for MathPrimitive {
284 fn before_digest(&self) -> Option<&Vec<BeforeDigestClosure>> { Some(&self.options.before_digest) }
285 fn after_digest(&self) -> Option<&Vec<DigestionClosure>> { Some(&self.options.after_digest) }
286 fn invoke(&self, _once_only: bool) -> Result<Tokens> { Ok(Tokens!()) }
287 fn invoke_primitive(&self) -> Result<Vec<Digested>> {
288 let mut result: Vec<Digested> = self.execute_before_digest()?;
294 let args = self.read_arguments()?;
295 let replacement_result = match self.replacement {
297 None => Vec::new(),
298 Some(ref closure) => closure(args)?,
299 };
300 result.extend(replacement_result);
301 let mut w = Whatsit::default();
302 let after_result = self.execute_after_digest(&mut w)?;
303 result.extend(after_result);
304
305 Ok(result)
307 }
308
309 fn do_absorption(&self, _document: &mut Document, _whatsit: &Whatsit) -> Result<Vec<Node>> {
310 fatal!(
311 Definition,
312 Unexpected,
313 "do_absorption on MathPrimitive should never be called!"
314 );
315 }
316
317 fn get_cs(&self) -> Cow<'_, Token> { Cow::Borrowed(&self.cs) }
318 fn get_cs_name(&self) -> Cow<'_, str> { Cow::Owned(self.cs.with_cs_name(ToString::to_string)) }
319 fn get_alias(&self) -> Option<&String> { self.alias.as_ref() }
320 fn get_parameters(&self) -> Option<&Parameters> { self.paramlist.as_ref() }
321 fn get_num_args(&self) -> usize {
322 match self.nargs {
323 Some(n) => n,
324 None => match self.paramlist {
325 Some(ref params) => params.get_num_args(),
326 None => 0,
327 },
328 }
329 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn math_primitive_options_default_fields() {
340 let o = MathPrimitiveOptions::default();
341 assert!(!o.bounded);
343 assert!(!o.is_prefix);
344 assert!(!o.require_math);
345 assert!(!o.forbid_math);
346 assert!(!o.locked);
347 assert!(!o.robust);
348 assert!(!o.protected);
349 assert!(!o.reorder);
350 assert!(!o.dual);
351 assert!(!o.dynamic_mathstyle);
352 assert!(!o.dynamic_scriptpos);
353 assert!(o.nogroup, "nogroup defaults to true (Perl parity)");
354 assert!(!o.hide_content_reversion);
355 assert!(o.name.is_none());
356 assert!(o.meaning.is_none());
357 assert!(o.role.is_none());
358 assert!(o.operator_role.is_none());
359 assert!(o.mathstyle.is_none());
360 assert!(o.scriptpos.is_none());
361 assert!(o.operator_scriptpos.is_none());
362 assert!(o.stretchy.is_none());
363 assert!(o.operator_stretchy.is_none());
364 assert!(o.revert_as.is_none());
365 assert!(o.lpadding.is_none());
366 assert!(o.rpadding.is_none());
367 assert!(o.before_digest.is_empty());
368 assert!(o.after_digest.is_empty());
369 }
370
371 #[test]
372 fn math_primitive_options_partial_eq_by_subset() {
373 let mut a = MathPrimitiveOptions::default();
376 let mut b = MathPrimitiveOptions::default();
377 a.name = Some("plus".into());
378 b.name = Some("plus".into());
379 assert!(a == b, "same name equal");
380 a.locked = true;
382 assert!(a == b);
383 b.name = Some("times".into());
385 assert!(!(a == b));
386 }
387
388 #[test]
389 fn math_primitive_options_to_hash_stored_empty_default() {
390 let o = MathPrimitiveOptions::default();
393 let h = o.to_hash_stored();
394 assert_eq!(h.len(), 0);
395 }
396
397 #[test]
398 fn math_primitive_options_to_hash_stored_with_fields() {
399 let o = MathPrimitiveOptions {
400 meaning: Some("plus".into()),
401 name: Some("+".into()),
402 ..Default::default()
403 };
404 let h = o.to_hash_stored();
405 assert!(h.contains_key("meaning"));
406 assert!(h.contains_key("name"));
407 assert!(
408 !h.contains_key("role"),
409 "role=None shouldn't populate the hash"
410 );
411 }
412
413 #[test]
414 fn math_primitive_default_fields() {
415 let m = MathPrimitive::default();
416 assert!(m.paramlist.is_none());
417 assert!(m.replacement.is_none());
418 assert!(m.alias.is_none());
419 assert!(m.nargs.is_none());
420 assert!(m.options.reversion.is_none());
422 assert!(!m.options.is_prefix);
423 }
424
425 #[test]
426 fn math_primitive_partial_eq_by_cs() {
427 let a = MathPrimitive::default();
428 let b = MathPrimitive::default();
429 assert!(a == b);
431 }
432}