1use crate::math::{
14 Fraction, FractionType, LimitLocation, MathElement, MathZone, Matrix, Nary, Radical,
15};
16
17pub trait MathZoneExt {
23 fn element_count(&self) -> usize;
25
26 fn is_empty(&self) -> bool;
28
29 fn has_fractions(&self) -> bool;
31
32 fn has_radicals(&self) -> bool;
34
35 fn has_matrices(&self) -> bool;
37
38 fn has_scripts(&self) -> bool;
40
41 fn has_nary(&self) -> bool;
43}
44
45impl MathZoneExt for MathZone {
46 fn element_count(&self) -> usize {
47 self.elements.len()
48 }
49
50 fn is_empty(&self) -> bool {
51 self.elements.is_empty()
52 }
53
54 fn has_fractions(&self) -> bool {
55 self.elements.iter().any(|e| e.is_fraction())
56 }
57
58 fn has_radicals(&self) -> bool {
59 self.elements.iter().any(|e| e.is_radical())
60 }
61
62 fn has_matrices(&self) -> bool {
63 self.elements.iter().any(|e| e.is_matrix())
64 }
65
66 fn has_scripts(&self) -> bool {
67 self.elements.iter().any(|e| e.is_script())
68 }
69
70 fn has_nary(&self) -> bool {
71 self.elements.iter().any(|e| e.is_nary())
72 }
73}
74
75pub trait MathElementExt {
81 fn is_fraction(&self) -> bool;
83 fn is_radical(&self) -> bool;
85 fn is_nary(&self) -> bool;
87 fn is_script(&self) -> bool;
89 fn is_matrix(&self) -> bool;
91 fn is_delimiter(&self) -> bool;
93 fn is_run(&self) -> bool;
95
96 fn as_fraction(&self) -> Option<&Fraction>;
98 fn as_radical(&self) -> Option<&Radical>;
100 fn as_nary(&self) -> Option<&Nary>;
102 fn as_matrix(&self) -> Option<&Matrix>;
104
105 fn operator_count(&self) -> usize;
108}
109
110impl MathElementExt for MathElement {
111 fn is_fraction(&self) -> bool {
112 matches!(self, MathElement::Fraction(_))
113 }
114
115 fn is_radical(&self) -> bool {
116 matches!(self, MathElement::Radical(_))
117 }
118
119 fn is_nary(&self) -> bool {
120 matches!(self, MathElement::Nary(_))
121 }
122
123 fn is_script(&self) -> bool {
124 matches!(
125 self,
126 MathElement::Subscript(_)
127 | MathElement::Superscript(_)
128 | MathElement::SubSuperscript(_)
129 | MathElement::PreScript(_)
130 )
131 }
132
133 fn is_matrix(&self) -> bool {
134 matches!(self, MathElement::Matrix(_))
135 }
136
137 fn is_delimiter(&self) -> bool {
138 matches!(self, MathElement::Delimiter(_))
139 }
140
141 fn is_run(&self) -> bool {
142 matches!(self, MathElement::Run(_))
143 }
144
145 fn as_fraction(&self) -> Option<&Fraction> {
146 if let MathElement::Fraction(f) = self {
147 Some(f)
148 } else {
149 None
150 }
151 }
152
153 fn as_radical(&self) -> Option<&Radical> {
154 if let MathElement::Radical(r) = self {
155 Some(r)
156 } else {
157 None
158 }
159 }
160
161 fn as_nary(&self) -> Option<&Nary> {
162 if let MathElement::Nary(n) = self {
163 Some(n)
164 } else {
165 None
166 }
167 }
168
169 fn as_matrix(&self) -> Option<&Matrix> {
170 if let MathElement::Matrix(m) = self {
171 Some(m)
172 } else {
173 None
174 }
175 }
176
177 fn operator_count(&self) -> usize {
178 count_operators_in_zone_list(std::slice::from_ref(self))
179 }
180}
181
182fn count_operators_in_zone_list(elements: &[MathElement]) -> usize {
185 elements.iter().map(count_operators).sum()
186}
187
188fn count_operators(e: &MathElement) -> usize {
189 match e {
190 MathElement::Fraction(f) => {
191 1 + count_operators_in_zone(&f.numerator) + count_operators_in_zone(&f.denominator)
192 }
193 MathElement::Radical(r) => {
194 1 + count_operators_in_zone(&r.base) + count_operators_in_zone(&r.degree)
195 }
196 MathElement::Nary(n) => {
197 1 + count_operators_in_zone(&n.subscript)
198 + count_operators_in_zone(&n.superscript)
199 + count_operators_in_zone(&n.base)
200 }
201 MathElement::Subscript(s) | MathElement::Superscript(s) => {
202 count_operators_in_zone(&s.base) + count_operators_in_zone(&s.script)
203 }
204 MathElement::SubSuperscript(s) => {
205 count_operators_in_zone(&s.base)
206 + count_operators_in_zone(&s.subscript)
207 + count_operators_in_zone(&s.superscript)
208 }
209 MathElement::PreScript(p) => {
210 count_operators_in_zone(&p.base)
211 + count_operators_in_zone(&p.subscript)
212 + count_operators_in_zone(&p.superscript)
213 }
214 MathElement::Delimiter(d) => d.elements.iter().map(count_operators_in_zone).sum(),
215 MathElement::Matrix(m) => m
216 .rows
217 .iter()
218 .flat_map(|row| row.iter())
219 .map(count_operators_in_zone)
220 .sum(),
221 MathElement::Function(f) => {
222 count_operators_in_zone(&f.name) + count_operators_in_zone(&f.argument)
223 }
224 MathElement::Accent(a) => count_operators_in_zone(&a.base),
225 MathElement::Bar(b) => count_operators_in_zone(&b.base),
226 MathElement::Box(b) => count_operators_in_zone(&b.content),
227 MathElement::BorderBox(b) => count_operators_in_zone(&b.content),
228 MathElement::EquationArray(e) => e.equations.iter().map(count_operators_in_zone).sum(),
229 MathElement::LowerLimit(l) | MathElement::UpperLimit(l) => {
230 count_operators_in_zone(&l.base) + count_operators_in_zone(&l.limit)
231 }
232 MathElement::GroupChar(g) => count_operators_in_zone(&g.base),
233 MathElement::Phantom(p) => count_operators_in_zone(&p.content),
234 MathElement::Run(_) => 0,
235 }
236}
237
238fn count_operators_in_zone(zone: &MathZone) -> usize {
239 count_operators_in_zone_list(&zone.elements)
240}
241
242pub trait FractionExt {
248 fn fraction_type(&self) -> Option<FractionType>;
250 fn numerator(&self) -> &MathZone;
252 fn denominator(&self) -> &MathZone;
254 fn is_skewed(&self) -> bool;
256}
257
258impl FractionExt for Fraction {
259 fn fraction_type(&self) -> Option<FractionType> {
260 self.fraction_type
261 }
262
263 fn numerator(&self) -> &MathZone {
264 &self.numerator
265 }
266
267 fn denominator(&self) -> &MathZone {
268 &self.denominator
269 }
270
271 fn is_skewed(&self) -> bool {
272 self.fraction_type == Some(FractionType::Skewed)
273 }
274}
275
276pub trait NaryExt {
282 fn lower_limit(&self) -> &MathZone;
284 fn upper_limit(&self) -> &MathZone;
286 fn limit_location(&self) -> Option<LimitLocation>;
288}
289
290impl NaryExt for Nary {
291 fn lower_limit(&self) -> &MathZone {
292 &self.subscript
293 }
294
295 fn upper_limit(&self) -> &MathZone {
296 &self.superscript
297 }
298
299 fn limit_location(&self) -> Option<LimitLocation> {
300 self.limit_location
301 }
302}
303
304pub trait RadicalExt {
310 fn radicand(&self) -> &MathZone;
312 fn degree(&self) -> &MathZone;
314 fn is_square_root(&self) -> bool;
316}
317
318impl RadicalExt for Radical {
319 fn radicand(&self) -> &MathZone {
320 &self.base
321 }
322
323 fn degree(&self) -> &MathZone {
324 &self.degree
325 }
326
327 fn is_square_root(&self) -> bool {
328 self.degree.elements.is_empty() || self.hide_degree
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::math::{Delimiter, Fraction, FractionType, MathRun, Nary, Radical, Script};
336
337 fn run(text: &str) -> MathElement {
338 MathElement::Run(MathRun {
339 text: text.to_string(),
340 properties: None,
341 })
342 }
343
344 fn zone(elements: Vec<MathElement>) -> MathZone {
345 MathZone { elements }
346 }
347
348 #[test]
353 fn test_math_zone_empty() {
354 let z = MathZone::default();
355 assert!(z.is_empty());
356 assert_eq!(z.element_count(), 0);
357 assert!(!z.has_fractions());
358 assert!(!z.has_radicals());
359 assert!(!z.has_nary());
360 }
361
362 #[test]
363 fn test_math_zone_has_fraction() {
364 let z = zone(vec![MathElement::Fraction(Fraction::default())]);
365 assert!(!z.is_empty());
366 assert_eq!(z.element_count(), 1);
367 assert!(z.has_fractions());
368 assert!(!z.has_radicals());
369 }
370
371 #[test]
372 fn test_math_zone_has_radical() {
373 let z = zone(vec![MathElement::Radical(Radical::default())]);
374 assert!(z.has_radicals());
375 assert!(!z.has_fractions());
376 }
377
378 #[test]
379 fn test_math_zone_has_nary() {
380 let z = zone(vec![MathElement::Nary(Nary::default())]);
381 assert!(z.has_nary());
382 assert!(!z.has_matrices());
383 }
384
385 #[test]
390 fn test_element_type_checks() {
391 let frac = MathElement::Fraction(Fraction::default());
392 assert!(frac.is_fraction());
393 assert!(!frac.is_radical());
394 assert!(!frac.is_run());
395 assert!(frac.as_fraction().is_some());
396 assert!(frac.as_radical().is_none());
397
398 let r = run("x");
399 assert!(r.is_run());
400 assert!(!r.is_fraction());
401 assert!(r.as_fraction().is_none());
402 }
403
404 #[test]
405 fn test_script_check() {
406 let sub = MathElement::Subscript(Script::default());
407 let sup = MathElement::Superscript(Script::default());
408 assert!(sub.is_script());
409 assert!(sup.is_script());
410 assert!(!sub.is_fraction());
411 }
412
413 #[test]
414 fn test_delimiter_check() {
415 let d = MathElement::Delimiter(Delimiter::default());
416 assert!(d.is_delimiter());
417 assert!(!d.is_run());
418 }
419
420 #[test]
421 fn test_operator_count_nested() {
422 let frac = MathElement::Fraction(Fraction {
424 numerator: zone(vec![MathElement::Radical(Radical::default())]),
425 denominator: zone(vec![run("2")]),
426 fraction_type: None,
427 });
428 assert_eq!(frac.operator_count(), 2);
430 }
431
432 #[test]
433 fn test_operator_count_run() {
434 assert_eq!(run("x").operator_count(), 0);
435 }
436
437 #[test]
442 fn test_fraction_ext() {
443 let f = Fraction {
444 numerator: zone(vec![run("1")]),
445 denominator: zone(vec![run("2")]),
446 fraction_type: Some(FractionType::Skewed),
447 };
448 assert!(f.is_skewed());
449 assert_eq!(f.fraction_type(), Some(FractionType::Skewed));
450 assert_eq!(f.numerator().elements.len(), 1);
451 assert_eq!(f.denominator().elements.len(), 1);
452 }
453
454 #[test]
455 fn test_fraction_not_skewed() {
456 let f = Fraction {
457 fraction_type: Some(FractionType::Bar),
458 ..Default::default()
459 };
460 assert!(!f.is_skewed());
461 }
462
463 #[test]
468 fn test_radical_square_root() {
469 let r = Radical::default(); assert!(r.is_square_root());
471 assert_eq!(r.degree().elements.len(), 0);
472 }
473
474 #[test]
475 fn test_radical_nth_root() {
476 let r = Radical {
477 degree: zone(vec![run("3")]),
478 base: zone(vec![run("x")]),
479 hide_degree: false,
480 };
481 assert!(!r.is_square_root());
482 assert_eq!(r.degree().elements.len(), 1);
483 assert_eq!(r.radicand().elements.len(), 1);
484 }
485
486 #[test]
487 fn test_radical_hide_degree_treated_as_square() {
488 let r = Radical {
489 degree: zone(vec![run("2")]),
490 base: zone(vec![run("x")]),
491 hide_degree: true,
492 };
493 assert!(r.is_square_root());
495 }
496
497 #[test]
502 fn test_nary_ext() {
503 let n = Nary {
504 operator: Some("∑".to_string()),
505 subscript: zone(vec![run("i=0")]),
506 superscript: zone(vec![run("n")]),
507 base: zone(vec![run("x")]),
508 limit_location: Some(LimitLocation::UnderOver),
509 grow: false,
510 };
511 assert_eq!(n.lower_limit().elements.len(), 1);
512 assert_eq!(n.upper_limit().elements.len(), 1);
513 assert_eq!(n.limit_location(), Some(LimitLocation::UnderOver));
514 }
515}