1use ratex_lexer::token::SourceLocation;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum Mode {
8 Math,
9 Text,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum StyleStr {
16 Display,
17 Text,
18 Script,
19 Scriptscript,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "lowercase")]
25pub enum AtomFamily {
26 Bin,
27 Close,
28 Inner,
29 Open,
30 Punct,
31 Rel,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Measurement {
37 pub number: f64,
38 pub unit: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct AlignSpec {
44 #[serde(rename = "type")]
45 pub align_type: AlignType,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub align: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub pregap: Option<f64>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub postgap: Option<f64>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum AlignType {
57 Align,
58 Separator,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub enum ProofLineStyle {
64 Solid,
65 Dashed,
66 None,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ProofBranch {
71 pub conclusion: Vec<ParseNode>,
72 pub premises: Vec<ProofBranch>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 #[serde(rename = "leftLabel")]
75 pub left_label: Option<Vec<ParseNode>>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 #[serde(rename = "rightLabel")]
78 pub right_label: Option<Vec<ParseNode>>,
79 #[serde(rename = "lineStyle")]
80 pub line_style: ProofLineStyle,
81 #[serde(rename = "rootAtTop", skip_serializing_if = "is_false")]
82 pub root_at_top: bool,
83}
84
85fn is_false(b: &bool) -> bool {
86 !*b
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(tag = "type")]
95pub enum ParseNode {
96 #[serde(rename = "atom")]
100 Atom {
101 mode: Mode,
102 family: AtomFamily,
103 text: String,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 loc: Option<SourceLocation>,
106 },
107
108 #[serde(rename = "mathord")]
109 MathOrd {
110 mode: Mode,
111 text: String,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 loc: Option<SourceLocation>,
114 },
115
116 #[serde(rename = "textord")]
117 TextOrd {
118 mode: Mode,
119 text: String,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 loc: Option<SourceLocation>,
122 },
123
124 #[serde(rename = "op-token")]
125 OpToken {
126 mode: Mode,
127 text: String,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 loc: Option<SourceLocation>,
130 },
131
132 #[serde(rename = "accent-token")]
133 AccentToken {
134 mode: Mode,
135 text: String,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 loc: Option<SourceLocation>,
138 },
139
140 #[serde(rename = "spacing")]
141 SpacingNode {
142 mode: Mode,
143 text: String,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 loc: Option<SourceLocation>,
146 },
147
148 #[serde(rename = "ordgroup")]
152 OrdGroup {
153 mode: Mode,
154 body: Vec<ParseNode>,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 semisimple: Option<bool>,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 loc: Option<SourceLocation>,
159 },
160
161 #[serde(rename = "supsub")]
162 SupSub {
163 mode: Mode,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 base: Option<Box<ParseNode>>,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 sup: Option<Box<ParseNode>>,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 sub: Option<Box<ParseNode>>,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 loc: Option<SourceLocation>,
172 },
173
174 #[serde(rename = "genfrac")]
178 GenFrac {
179 mode: Mode,
180 continued: bool,
181 numer: Box<ParseNode>,
182 denom: Box<ParseNode>,
183 #[serde(rename = "hasBarLine")]
184 has_bar_line: bool,
185 #[serde(rename = "leftDelim")]
186 left_delim: Option<String>,
187 #[serde(rename = "rightDelim")]
188 right_delim: Option<String>,
189 #[serde(rename = "barSize")]
190 bar_size: Option<Measurement>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 loc: Option<SourceLocation>,
193 },
194
195 #[serde(rename = "sqrt")]
196 Sqrt {
197 mode: Mode,
198 body: Box<ParseNode>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 index: Option<Box<ParseNode>>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 loc: Option<SourceLocation>,
203 },
204
205 #[serde(rename = "accent")]
206 Accent {
207 mode: Mode,
208 label: String,
209 #[serde(rename = "isStretchy")]
210 #[serde(skip_serializing_if = "Option::is_none")]
211 is_stretchy: Option<bool>,
212 #[serde(rename = "isShifty")]
213 #[serde(skip_serializing_if = "Option::is_none")]
214 is_shifty: Option<bool>,
215 base: Box<ParseNode>,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 loc: Option<SourceLocation>,
218 },
219
220 #[serde(rename = "accentUnder")]
221 AccentUnder {
222 mode: Mode,
223 label: String,
224 #[serde(rename = "isStretchy")]
225 #[serde(skip_serializing_if = "Option::is_none")]
226 is_stretchy: Option<bool>,
227 #[serde(rename = "isShifty")]
228 #[serde(skip_serializing_if = "Option::is_none")]
229 is_shifty: Option<bool>,
230 base: Box<ParseNode>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 loc: Option<SourceLocation>,
233 },
234
235 #[serde(rename = "op")]
236 Op {
237 mode: Mode,
238 limits: bool,
239 #[serde(rename = "alwaysHandleSupSub")]
240 #[serde(skip_serializing_if = "Option::is_none")]
241 always_handle_sup_sub: Option<bool>,
242 #[serde(rename = "suppressBaseShift")]
243 #[serde(skip_serializing_if = "Option::is_none")]
244 suppress_base_shift: Option<bool>,
245 #[serde(rename = "parentIsSupSub")]
246 parent_is_sup_sub: bool,
247 symbol: bool,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 name: Option<String>,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 body: Option<Vec<ParseNode>>,
252 #[serde(skip_serializing_if = "Option::is_none")]
253 loc: Option<SourceLocation>,
254 },
255
256 #[serde(rename = "operatorname")]
257 OperatorName {
258 mode: Mode,
259 body: Vec<ParseNode>,
260 #[serde(rename = "alwaysHandleSupSub")]
261 always_handle_sup_sub: bool,
262 limits: bool,
263 #[serde(rename = "parentIsSupSub")]
264 parent_is_sup_sub: bool,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 loc: Option<SourceLocation>,
267 },
268
269 #[serde(rename = "font")]
270 Font {
271 mode: Mode,
272 font: String,
273 body: Box<ParseNode>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 loc: Option<SourceLocation>,
276 },
277
278 #[serde(rename = "text")]
279 Text {
280 mode: Mode,
281 body: Vec<ParseNode>,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 font: Option<String>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 loc: Option<SourceLocation>,
286 },
287
288 #[serde(rename = "color")]
289 Color {
290 mode: Mode,
291 color: String,
292 body: Vec<ParseNode>,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 loc: Option<SourceLocation>,
295 },
296
297 #[serde(rename = "color-token")]
298 ColorToken {
299 mode: Mode,
300 color: String,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 loc: Option<SourceLocation>,
303 },
304
305 #[serde(rename = "size")]
306 Size {
307 mode: Mode,
308 value: Measurement,
309 #[serde(rename = "isBlank")]
310 is_blank: bool,
311 #[serde(skip_serializing_if = "Option::is_none")]
312 loc: Option<SourceLocation>,
313 },
314
315 #[serde(rename = "styling")]
316 Styling {
317 mode: Mode,
318 style: StyleStr,
319 body: Vec<ParseNode>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 loc: Option<SourceLocation>,
322 },
323
324 #[serde(rename = "sizing")]
325 Sizing {
326 mode: Mode,
327 size: u8,
328 body: Vec<ParseNode>,
329 #[serde(skip_serializing_if = "Option::is_none")]
330 loc: Option<SourceLocation>,
331 },
332
333 #[serde(rename = "delimsizing")]
334 DelimSizing {
335 mode: Mode,
336 size: u8,
337 mclass: String,
338 delim: String,
339 #[serde(skip_serializing_if = "Option::is_none")]
340 loc: Option<SourceLocation>,
341 },
342
343 #[serde(rename = "leftright")]
344 LeftRight {
345 mode: Mode,
346 body: Vec<ParseNode>,
347 left: String,
348 right: String,
349 #[serde(rename = "rightColor")]
350 #[serde(skip_serializing_if = "Option::is_none")]
351 right_color: Option<String>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 loc: Option<SourceLocation>,
354 },
355
356 #[serde(rename = "leftright-right")]
357 LeftRightRight {
358 mode: Mode,
359 delim: String,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 color: Option<String>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 loc: Option<SourceLocation>,
364 },
365
366 #[serde(rename = "middle")]
367 Middle {
368 mode: Mode,
369 delim: String,
370 #[serde(skip_serializing_if = "Option::is_none")]
371 loc: Option<SourceLocation>,
372 },
373
374 #[serde(rename = "overline")]
375 Overline {
376 mode: Mode,
377 body: Box<ParseNode>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 loc: Option<SourceLocation>,
380 },
381
382 #[serde(rename = "underline")]
383 Underline {
384 mode: Mode,
385 body: Box<ParseNode>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 loc: Option<SourceLocation>,
388 },
389
390 #[serde(rename = "rule")]
391 Rule {
392 mode: Mode,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 shift: Option<Measurement>,
395 width: Measurement,
396 height: Measurement,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 loc: Option<SourceLocation>,
399 },
400
401 #[serde(rename = "kern")]
402 Kern {
403 mode: Mode,
404 dimension: Measurement,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 loc: Option<SourceLocation>,
407 },
408
409 #[serde(rename = "phantom")]
410 Phantom {
411 mode: Mode,
412 body: Vec<ParseNode>,
413 #[serde(skip_serializing_if = "Option::is_none")]
414 loc: Option<SourceLocation>,
415 },
416
417 #[serde(rename = "vphantom")]
418 VPhantom {
419 mode: Mode,
420 body: Box<ParseNode>,
421 #[serde(skip_serializing_if = "Option::is_none")]
422 loc: Option<SourceLocation>,
423 },
424
425 #[serde(rename = "smash")]
426 Smash {
427 mode: Mode,
428 body: Box<ParseNode>,
429 #[serde(rename = "smashHeight")]
430 smash_height: bool,
431 #[serde(rename = "smashDepth")]
432 smash_depth: bool,
433 #[serde(skip_serializing_if = "Option::is_none")]
434 loc: Option<SourceLocation>,
435 },
436
437 #[serde(rename = "mclass")]
438 MClass {
439 mode: Mode,
440 mclass: String,
441 body: Vec<ParseNode>,
442 #[serde(rename = "isCharacterBox")]
443 is_character_box: bool,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 loc: Option<SourceLocation>,
446 },
447
448 #[serde(rename = "array")]
449 Array {
450 mode: Mode,
451 body: Vec<Vec<ParseNode>>,
452 #[serde(rename = "rowGaps")]
453 row_gaps: Vec<Option<Measurement>>,
454 #[serde(rename = "hLinesBeforeRow")]
455 hlines_before_row: Vec<Vec<bool>>,
456 #[serde(skip_serializing_if = "Option::is_none")]
457 cols: Option<Vec<AlignSpec>>,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 #[serde(rename = "colSeparationType")]
460 col_separation_type: Option<String>,
461 #[serde(skip_serializing_if = "Option::is_none")]
462 #[serde(rename = "hskipBeforeAndAfter")]
463 hskip_before_and_after: Option<bool>,
464 #[serde(skip_serializing_if = "Option::is_none")]
465 #[serde(rename = "addJot")]
466 add_jot: Option<bool>,
467 #[serde(default = "default_arraystretch")]
468 arraystretch: f64,
469 #[serde(skip_serializing_if = "Option::is_none")]
470 tags: Option<Vec<ArrayTag>>,
471 #[serde(skip_serializing_if = "Option::is_none")]
472 leqno: Option<bool>,
473 #[serde(skip_serializing_if = "Option::is_none")]
474 #[serde(rename = "isCD")]
475 is_cd: Option<bool>,
476 #[serde(skip_serializing_if = "Option::is_none")]
477 loc: Option<SourceLocation>,
478 },
479
480 #[serde(rename = "environment")]
481 Environment {
482 mode: Mode,
483 name: String,
484 #[serde(rename = "nameGroup")]
485 name_group: Box<ParseNode>,
486 #[serde(skip_serializing_if = "Option::is_none")]
487 loc: Option<SourceLocation>,
488 },
489
490 #[serde(rename = "cr")]
491 Cr {
492 mode: Mode,
493 #[serde(rename = "newLine")]
494 new_line: bool,
495 #[serde(skip_serializing_if = "Option::is_none")]
496 size: Option<Measurement>,
497 #[serde(skip_serializing_if = "Option::is_none")]
498 loc: Option<SourceLocation>,
499 },
500
501 #[serde(rename = "infix")]
502 Infix {
503 mode: Mode,
504 #[serde(rename = "replaceWith")]
505 replace_with: String,
506 #[serde(skip_serializing_if = "Option::is_none")]
507 size: Option<Measurement>,
508 #[serde(skip_serializing_if = "Option::is_none")]
509 loc: Option<SourceLocation>,
510 },
511
512 #[serde(rename = "internal")]
513 Internal {
514 mode: Mode,
515 #[serde(skip_serializing_if = "Option::is_none")]
516 loc: Option<SourceLocation>,
517 },
518
519 #[serde(rename = "verb")]
520 Verb {
521 mode: Mode,
522 body: String,
523 star: bool,
524 #[serde(skip_serializing_if = "Option::is_none")]
525 loc: Option<SourceLocation>,
526 },
527
528 #[serde(rename = "href")]
529 Href {
530 mode: Mode,
531 href: String,
532 body: Vec<ParseNode>,
533 #[serde(skip_serializing_if = "Option::is_none")]
534 loc: Option<SourceLocation>,
535 },
536
537 #[serde(rename = "url")]
538 Url {
539 mode: Mode,
540 url: String,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 loc: Option<SourceLocation>,
543 },
544
545 #[serde(rename = "raw")]
546 Raw {
547 mode: Mode,
548 string: String,
549 #[serde(skip_serializing_if = "Option::is_none")]
550 loc: Option<SourceLocation>,
551 },
552
553 #[serde(rename = "hbox")]
554 HBox {
555 mode: Mode,
556 body: Vec<ParseNode>,
557 #[serde(skip_serializing_if = "Option::is_none")]
558 loc: Option<SourceLocation>,
559 },
560
561 #[serde(rename = "horizBrace")]
562 HorizBrace {
563 mode: Mode,
564 label: String,
565 #[serde(rename = "isOver")]
566 is_over: bool,
567 base: Box<ParseNode>,
568 #[serde(skip_serializing_if = "Option::is_none")]
569 loc: Option<SourceLocation>,
570 },
571
572 #[serde(rename = "enclose")]
573 Enclose {
574 mode: Mode,
575 label: String,
576 #[serde(rename = "backgroundColor")]
577 #[serde(skip_serializing_if = "Option::is_none")]
578 background_color: Option<String>,
579 #[serde(rename = "borderColor")]
580 #[serde(skip_serializing_if = "Option::is_none")]
581 border_color: Option<String>,
582 body: Box<ParseNode>,
583 #[serde(skip_serializing_if = "Option::is_none")]
584 loc: Option<SourceLocation>,
585 },
586
587 #[serde(rename = "lap")]
588 Lap {
589 mode: Mode,
590 alignment: String,
591 body: Box<ParseNode>,
592 #[serde(skip_serializing_if = "Option::is_none")]
593 loc: Option<SourceLocation>,
594 },
595
596 #[serde(rename = "mathchoice")]
597 MathChoice {
598 mode: Mode,
599 display: Vec<ParseNode>,
600 text: Vec<ParseNode>,
601 script: Vec<ParseNode>,
602 scriptscript: Vec<ParseNode>,
603 #[serde(skip_serializing_if = "Option::is_none")]
604 loc: Option<SourceLocation>,
605 },
606
607 #[serde(rename = "raisebox")]
608 RaiseBox {
609 mode: Mode,
610 dy: Measurement,
611 body: Box<ParseNode>,
612 #[serde(skip_serializing_if = "Option::is_none")]
613 loc: Option<SourceLocation>,
614 },
615
616 #[serde(rename = "vcenter")]
617 VCenter {
618 mode: Mode,
619 body: Box<ParseNode>,
620 #[serde(skip_serializing_if = "Option::is_none")]
621 loc: Option<SourceLocation>,
622 },
623
624 #[serde(rename = "xArrow")]
625 XArrow {
626 mode: Mode,
627 label: String,
628 body: Box<ParseNode>,
629 #[serde(skip_serializing_if = "Option::is_none")]
630 below: Option<Box<ParseNode>>,
631 #[serde(skip_serializing_if = "Option::is_none")]
632 loc: Option<SourceLocation>,
633 },
634
635 #[serde(rename = "pmb")]
636 Pmb {
637 mode: Mode,
638 mclass: String,
639 body: Vec<ParseNode>,
640 #[serde(skip_serializing_if = "Option::is_none")]
641 loc: Option<SourceLocation>,
642 },
643
644 #[serde(rename = "tag")]
645 Tag {
646 mode: Mode,
647 body: Vec<ParseNode>,
648 tag: Vec<ParseNode>,
649 #[serde(skip_serializing_if = "Option::is_none")]
650 loc: Option<SourceLocation>,
651 },
652
653 #[serde(rename = "nonumber")]
654 NoNumber {
655 mode: Mode,
656 #[serde(skip_serializing_if = "Option::is_none")]
657 loc: Option<SourceLocation>,
658 },
659
660 #[serde(rename = "html")]
661 Html {
662 mode: Mode,
663 attributes: std::collections::HashMap<String, String>,
664 body: Vec<ParseNode>,
665 #[serde(skip_serializing_if = "Option::is_none")]
666 loc: Option<SourceLocation>,
667 },
668
669 #[serde(rename = "htmlmathml")]
670 HtmlMathMl {
671 mode: Mode,
672 html: Vec<ParseNode>,
673 mathml: Vec<ParseNode>,
674 #[serde(skip_serializing_if = "Option::is_none")]
675 loc: Option<SourceLocation>,
676 },
677
678 #[serde(rename = "includegraphics")]
679 IncludeGraphics {
680 mode: Mode,
681 alt: String,
682 width: Measurement,
683 height: Measurement,
684 totalheight: Measurement,
685 src: String,
686 #[serde(skip_serializing_if = "Option::is_none")]
687 loc: Option<SourceLocation>,
688 },
689
690 #[serde(rename = "cdlabel")]
691 CdLabel {
692 mode: Mode,
693 side: String,
694 label: Box<ParseNode>,
695 #[serde(skip_serializing_if = "Option::is_none")]
696 loc: Option<SourceLocation>,
697 },
698
699 #[serde(rename = "cdlabelparent")]
700 CdLabelParent {
701 mode: Mode,
702 fragment: Box<ParseNode>,
703 #[serde(skip_serializing_if = "Option::is_none")]
704 loc: Option<SourceLocation>,
705 },
706
707 #[serde(rename = "cdArrow")]
708 CdArrow {
709 mode: Mode,
710 direction: String,
712 #[serde(skip_serializing_if = "Option::is_none")]
715 label_above: Option<Box<ParseNode>>,
716 #[serde(skip_serializing_if = "Option::is_none")]
719 label_below: Option<Box<ParseNode>>,
720 #[serde(skip_serializing_if = "Option::is_none")]
721 loc: Option<SourceLocation>,
722 },
723
724 #[serde(rename = "proofTree")]
725 ProofTree {
726 mode: Mode,
727 tree: ProofBranch,
728 #[serde(skip_serializing_if = "Option::is_none")]
729 loc: Option<SourceLocation>,
730 },
731}
732
733fn default_arraystretch() -> f64 {
734 1.0
735}
736
737#[derive(Debug, Clone, Serialize, Deserialize)]
739#[serde(untagged)]
740pub enum ArrayTag {
741 Auto(bool),
742 Explicit(Vec<ParseNode>),
743}
744
745impl ParseNode {
748 pub fn mode(&self) -> Mode {
749 match self {
750 Self::Atom { mode, .. }
751 | Self::MathOrd { mode, .. }
752 | Self::TextOrd { mode, .. }
753 | Self::OpToken { mode, .. }
754 | Self::AccentToken { mode, .. }
755 | Self::SpacingNode { mode, .. }
756 | Self::OrdGroup { mode, .. }
757 | Self::SupSub { mode, .. }
758 | Self::GenFrac { mode, .. }
759 | Self::Sqrt { mode, .. }
760 | Self::Accent { mode, .. }
761 | Self::AccentUnder { mode, .. }
762 | Self::Op { mode, .. }
763 | Self::OperatorName { mode, .. }
764 | Self::Font { mode, .. }
765 | Self::Text { mode, .. }
766 | Self::Color { mode, .. }
767 | Self::ColorToken { mode, .. }
768 | Self::Size { mode, .. }
769 | Self::Styling { mode, .. }
770 | Self::Sizing { mode, .. }
771 | Self::DelimSizing { mode, .. }
772 | Self::LeftRight { mode, .. }
773 | Self::LeftRightRight { mode, .. }
774 | Self::Middle { mode, .. }
775 | Self::Overline { mode, .. }
776 | Self::Underline { mode, .. }
777 | Self::Rule { mode, .. }
778 | Self::Kern { mode, .. }
779 | Self::Phantom { mode, .. }
780 | Self::VPhantom { mode, .. }
781 | Self::Smash { mode, .. }
782 | Self::MClass { mode, .. }
783 | Self::Array { mode, .. }
784 | Self::Environment { mode, .. }
785 | Self::Cr { mode, .. }
786 | Self::Infix { mode, .. }
787 | Self::Internal { mode, .. }
788 | Self::Verb { mode, .. }
789 | Self::Href { mode, .. }
790 | Self::Url { mode, .. }
791 | Self::Raw { mode, .. }
792 | Self::HBox { mode, .. }
793 | Self::HorizBrace { mode, .. }
794 | Self::Enclose { mode, .. }
795 | Self::Lap { mode, .. }
796 | Self::MathChoice { mode, .. }
797 | Self::RaiseBox { mode, .. }
798 | Self::VCenter { mode, .. }
799 | Self::XArrow { mode, .. }
800 | Self::Pmb { mode, .. }
801 | Self::Tag { mode, .. }
802 | Self::NoNumber { mode, .. }
803 | Self::Html { mode, .. }
804 | Self::HtmlMathMl { mode, .. }
805 | Self::IncludeGraphics { mode, .. }
806 | Self::CdLabel { mode, .. }
807 | Self::CdLabelParent { mode, .. }
808 | Self::CdArrow { mode, .. }
809 | Self::ProofTree { mode, .. } => *mode,
810 }
811 }
812
813 pub fn type_name(&self) -> &'static str {
814 match self {
815 Self::Atom { .. } => "atom",
816 Self::MathOrd { .. } => "mathord",
817 Self::TextOrd { .. } => "textord",
818 Self::OpToken { .. } => "op-token",
819 Self::AccentToken { .. } => "accent-token",
820 Self::SpacingNode { .. } => "spacing",
821 Self::OrdGroup { .. } => "ordgroup",
822 Self::SupSub { .. } => "supsub",
823 Self::GenFrac { .. } => "genfrac",
824 Self::Sqrt { .. } => "sqrt",
825 Self::Accent { .. } => "accent",
826 Self::AccentUnder { .. } => "accentUnder",
827 Self::Op { .. } => "op",
828 Self::OperatorName { .. } => "operatorname",
829 Self::Font { .. } => "font",
830 Self::Text { .. } => "text",
831 Self::Color { .. } => "color",
832 Self::ColorToken { .. } => "color-token",
833 Self::Size { .. } => "size",
834 Self::Styling { .. } => "styling",
835 Self::Sizing { .. } => "sizing",
836 Self::DelimSizing { .. } => "delimsizing",
837 Self::LeftRight { .. } => "leftright",
838 Self::LeftRightRight { .. } => "leftright-right",
839 Self::Middle { .. } => "middle",
840 Self::Overline { .. } => "overline",
841 Self::Underline { .. } => "underline",
842 Self::Rule { .. } => "rule",
843 Self::Kern { .. } => "kern",
844 Self::Phantom { .. } => "phantom",
845 Self::VPhantom { .. } => "vphantom",
846 Self::Smash { .. } => "smash",
847 Self::MClass { .. } => "mclass",
848 Self::Array { .. } => "array",
849 Self::Environment { .. } => "environment",
850 Self::Cr { .. } => "cr",
851 Self::Infix { .. } => "infix",
852 Self::Internal { .. } => "internal",
853 Self::Verb { .. } => "verb",
854 Self::Href { .. } => "href",
855 Self::Url { .. } => "url",
856 Self::Raw { .. } => "raw",
857 Self::HBox { .. } => "hbox",
858 Self::HorizBrace { .. } => "horizBrace",
859 Self::Enclose { .. } => "enclose",
860 Self::Lap { .. } => "lap",
861 Self::MathChoice { .. } => "mathchoice",
862 Self::RaiseBox { .. } => "raisebox",
863 Self::VCenter { .. } => "vcenter",
864 Self::XArrow { .. } => "xArrow",
865 Self::Pmb { .. } => "pmb",
866 Self::Tag { .. } => "tag",
867 Self::NoNumber { .. } => "nonumber",
868 Self::Html { .. } => "html",
869 Self::HtmlMathMl { .. } => "htmlmathml",
870 Self::IncludeGraphics { .. } => "includegraphics",
871 Self::CdLabel { .. } => "cdlabel",
872 Self::CdLabelParent { .. } => "cdlabelparent",
873 Self::CdArrow { .. } => "cdArrow",
874 Self::ProofTree { .. } => "proofTree",
875 }
876 }
877
878 pub fn is_symbol_node(&self) -> bool {
880 matches!(
881 self,
882 Self::Atom { .. }
883 | Self::MathOrd { .. }
884 | Self::TextOrd { .. }
885 | Self::OpToken { .. }
886 | Self::AccentToken { .. }
887 | Self::SpacingNode { .. }
888 )
889 }
890
891 pub fn symbol_text(&self) -> Option<&str> {
893 match self {
894 Self::Atom { text, .. }
895 | Self::MathOrd { text, .. }
896 | Self::TextOrd { text, .. }
897 | Self::OpToken { text, .. }
898 | Self::AccentToken { text, .. }
899 | Self::SpacingNode { text, .. } => Some(text),
900 _ => None,
901 }
902 }
903
904 pub fn normalize_argument(arg: ParseNode) -> ParseNode {
906 if let ParseNode::OrdGroup { body, .. } = &arg {
907 if body.len() == 1 {
908 return body[0].clone();
909 }
910 }
911 arg
912 }
913
914 pub fn ord_argument(arg: ParseNode) -> Vec<ParseNode> {
916 if let ParseNode::OrdGroup { body, .. } = arg {
917 body
918 } else {
919 vec![arg]
920 }
921 }
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927
928 #[test]
929 fn test_serialize_mathord() {
930 let node = ParseNode::MathOrd {
931 mode: Mode::Math,
932 text: "x".to_string(),
933 loc: None,
934 };
935 let json = serde_json::to_string(&node).unwrap();
936 assert!(json.contains(r#""type":"mathord""#));
937 assert!(json.contains(r#""mode":"math""#));
938 assert!(json.contains(r#""text":"x""#));
939 }
940
941 #[test]
942 fn test_serialize_ordgroup() {
943 let node = ParseNode::OrdGroup {
944 mode: Mode::Math,
945 body: vec![
946 ParseNode::MathOrd {
947 mode: Mode::Math,
948 text: "a".to_string(),
949 loc: None,
950 },
951 ],
952 semisimple: None,
953 loc: None,
954 };
955 let json = serde_json::to_string(&node).unwrap();
956 assert!(json.contains(r#""type":"ordgroup""#));
957 }
958
959 #[test]
960 fn test_serialize_supsub() {
961 let node = ParseNode::SupSub {
962 mode: Mode::Math,
963 base: Some(Box::new(ParseNode::MathOrd {
964 mode: Mode::Math,
965 text: "x".to_string(),
966 loc: None,
967 })),
968 sup: Some(Box::new(ParseNode::TextOrd {
969 mode: Mode::Math,
970 text: "2".to_string(),
971 loc: None,
972 })),
973 sub: None,
974 loc: None,
975 };
976 let json = serde_json::to_string(&node).unwrap();
977 assert!(json.contains(r#""type":"supsub""#));
978 }
979
980 #[test]
981 fn test_serialize_genfrac() {
982 let node = ParseNode::GenFrac {
983 mode: Mode::Math,
984 continued: false,
985 numer: Box::new(ParseNode::MathOrd {
986 mode: Mode::Math,
987 text: "a".to_string(),
988 loc: None,
989 }),
990 denom: Box::new(ParseNode::MathOrd {
991 mode: Mode::Math,
992 text: "b".to_string(),
993 loc: None,
994 }),
995 has_bar_line: true,
996 left_delim: None,
997 right_delim: None,
998 bar_size: None,
999 loc: None,
1000 };
1001 let json = serde_json::to_string(&node).unwrap();
1002 assert!(json.contains(r#""type":"genfrac""#));
1003 assert!(json.contains(r#""hasBarLine":true"#));
1004 }
1005
1006 #[test]
1007 fn test_serialize_atom() {
1008 let node = ParseNode::Atom {
1009 mode: Mode::Math,
1010 family: AtomFamily::Bin,
1011 text: "+".to_string(),
1012 loc: None,
1013 };
1014 let json = serde_json::to_string(&node).unwrap();
1015 assert!(json.contains(r#""type":"atom""#));
1016 assert!(json.contains(r#""family":"bin""#));
1017 }
1018
1019 #[test]
1020 fn test_roundtrip() {
1021 let node = ParseNode::MathOrd {
1022 mode: Mode::Math,
1023 text: "x".to_string(),
1024 loc: Some(SourceLocation { start: 0, end: 1 }),
1025 };
1026 let json = serde_json::to_string(&node).unwrap();
1027 let parsed: ParseNode = serde_json::from_str(&json).unwrap();
1028 assert_eq!(parsed.type_name(), "mathord");
1029 assert_eq!(parsed.symbol_text(), Some("x"));
1030 }
1031
1032 #[test]
1033 fn test_mode_accessor() {
1034 let node = ParseNode::Atom {
1035 mode: Mode::Math,
1036 family: AtomFamily::Rel,
1037 text: "=".to_string(),
1038 loc: None,
1039 };
1040 assert_eq!(node.mode(), Mode::Math);
1041 }
1042
1043 #[test]
1044 fn test_normalize_argument() {
1045 let group = ParseNode::OrdGroup {
1046 mode: Mode::Math,
1047 body: vec![ParseNode::MathOrd {
1048 mode: Mode::Math,
1049 text: "x".to_string(),
1050 loc: None,
1051 }],
1052 semisimple: None,
1053 loc: None,
1054 };
1055 let normalized = ParseNode::normalize_argument(group);
1056 assert_eq!(normalized.type_name(), "mathord");
1057 }
1058}