1use ratex_lexer::token::{SourceLocation, Token};
2use unicode_normalization::UnicodeNormalization;
3
4use crate::error::{ParseError, ParseResult};
5use crate::functions::{self, ArgType, FunctionContext, FUNCTIONS};
6use crate::macro_expander::{MacroExpander, IMPLICIT_COMMANDS};
7use crate::parse_node::{AtomFamily, Mode, ParseNode};
8
9static END_OF_EXPRESSION: &[&str] = &["}", "\\endgroup", "\\end", "\\right", "&"];
11
12const MAX_RECURSION_DEPTH: usize = 512;
13
14pub struct Parser<'a> {
24 pub mode: Mode,
25 pub gullet: MacroExpander<'a>,
26 pub leftright_depth: i32,
27 recursion_depth: usize,
28 next_token: Option<Token>,
29 pub equation_counter: usize,
30}
31
32impl<'a> Parser<'a> {
33 pub fn new(input: &'a str) -> Self {
34 Self {
35 mode: Mode::Math,
36 gullet: MacroExpander::new(input, Mode::Math),
37 leftright_depth: 0,
38 recursion_depth: 0,
39 next_token: None,
40 equation_counter: 0,
41 }
42 }
43
44 pub fn fetch(&mut self) -> ParseResult<Token> {
48 if self.next_token.is_none() {
49 self.next_token = Some(self.gullet.expand_next_token()?);
50 }
51 Ok(self.next_token.clone().unwrap())
52 }
53
54 pub fn consume(&mut self) {
56 self.next_token = None;
57 }
58
59 pub fn expect(&mut self, text: &str, do_consume: bool) -> ParseResult<()> {
61 let tok = self.fetch()?;
62 if tok.text != text {
63 return Err(ParseError::new(
64 format!("Expected '{}', got '{}'", text, tok.text),
65 Some(&tok),
66 ));
67 }
68 if do_consume {
69 self.consume();
70 }
71 Ok(())
72 }
73
74 pub fn consume_spaces(&mut self) -> ParseResult<()> {
76 loop {
77 let tok = self.fetch()?;
78 if tok.text == " " {
79 self.consume();
80 } else {
81 break;
82 }
83 }
84 Ok(())
85 }
86
87 pub fn switch_mode(&mut self, new_mode: Mode) {
89 self.mode = new_mode;
90 self.gullet.switch_mode(new_mode);
91 }
92
93 pub fn parse(&mut self) -> ParseResult<Vec<ParseNode>> {
97 self.gullet.begin_group();
98
99 let result = self.parse_expression(false, None);
100
101 match result {
102 Ok(parse) => {
103 self.expect("EOF", true)?;
104 self.gullet.end_group();
105 Ok(parse)
106 }
107 Err(e) => {
108 self.gullet.end_groups();
109 Err(e)
110 }
111 }
112 }
113
114 pub fn parse_expression(
118 &mut self,
119 break_on_infix: bool,
120 break_on_token_text: Option<&str>,
121 ) -> ParseResult<Vec<ParseNode>> {
122 self.recursion_depth += 1;
123 if self.recursion_depth > MAX_RECURSION_DEPTH {
124 self.recursion_depth -= 1;
125 return Err(ParseError::recursion_limit_exceeded());
126 }
127 let result = self.parse_expression_impl(break_on_infix, break_on_token_text);
128 self.recursion_depth -= 1;
129 result
130 }
131
132 fn parse_expression_impl(
133 &mut self,
134 break_on_infix: bool,
135 break_on_token_text: Option<&str>,
136 ) -> ParseResult<Vec<ParseNode>> {
137 let mut body = Vec::new();
138
139 loop {
140 if self.mode == Mode::Math {
141 self.consume_spaces()?;
142 }
143
144 let lex = self.fetch()?;
145
146 if END_OF_EXPRESSION.contains(&lex.text.as_str()) {
147 break;
148 }
149 if let Some(break_text) = break_on_token_text {
150 if lex.text == break_text {
151 break;
152 }
153 }
154 if break_on_infix {
155 if let Some(func) = FUNCTIONS.get(lex.text.as_str()) {
156 if func.infix {
157 break;
158 }
159 }
160 }
161
162 let atom = self.parse_atom(break_on_token_text)?;
163
164 match atom {
165 None => break,
166 Some(node) if node.type_name() == "internal" => continue,
167 Some(node) => body.push(node),
168 }
169 }
170
171 if self.mode == Mode::Text {
172 self.form_ligatures(&mut body);
173 }
174
175 self.handle_infix_nodes(body)
176 }
177
178 fn handle_infix_nodes(&mut self, body: Vec<ParseNode>) -> ParseResult<Vec<ParseNode>> {
180 let mut over_index: Option<usize> = None;
181 let mut func_name: Option<String> = None;
182
183 for (i, node) in body.iter().enumerate() {
184 if let ParseNode::Infix { replace_with, .. } = node {
185 if over_index.is_some() {
186 return Err(ParseError::msg("only one infix operator per group"));
187 }
188 over_index = Some(i);
189 func_name = Some(replace_with.clone());
190 }
191 }
192
193 if let (Some(idx), Some(fname)) = (over_index, func_name) {
194 let numer_body: Vec<ParseNode> = body[..idx].to_vec();
195 let denom_body: Vec<ParseNode> = body[idx + 1..].to_vec();
196
197 let numer = if numer_body.len() == 1 {
198 if let ParseNode::OrdGroup { .. } = &numer_body[0] {
199 numer_body.into_iter().next().unwrap()
200 } else {
201 ParseNode::OrdGroup {
202 mode: self.mode,
203 body: numer_body,
204 semisimple: None,
205 loc: None,
206 }
207 }
208 } else {
209 ParseNode::OrdGroup {
210 mode: self.mode,
211 body: numer_body,
212 semisimple: None,
213 loc: None,
214 }
215 };
216
217 let denom = if denom_body.len() == 1 {
218 if let ParseNode::OrdGroup { .. } = &denom_body[0] {
219 denom_body.into_iter().next().unwrap()
220 } else {
221 ParseNode::OrdGroup {
222 mode: self.mode,
223 body: denom_body,
224 semisimple: None,
225 loc: None,
226 }
227 }
228 } else {
229 ParseNode::OrdGroup {
230 mode: self.mode,
231 body: denom_body,
232 semisimple: None,
233 loc: None,
234 }
235 };
236
237 let node = if fname == "\\\\abovefrac" {
238 let infix_node = body[idx].clone();
240 self.call_function(&fname, vec![numer, infix_node, denom], vec![], None, None)?
241 } else {
242 self.call_function(&fname, vec![numer, denom], vec![], None, None)?
243 };
244 Ok(vec![node])
245 } else {
246 Ok(body)
247 }
248 }
249
250 fn form_ligatures(&self, group: &mut Vec<ParseNode>) {
252 let mut i = 0;
253 while i + 1 < group.len() {
254 let a_text = group[i].symbol_text().map(|s| s.to_string());
255 let b_text = group[i + 1].symbol_text().map(|s| s.to_string());
256
257 if let (Some(a), Some(b)) = (a_text, b_text) {
258 if group[i].type_name() == "textord" && group[i + 1].type_name() == "textord" {
259 if a == "-" && b == "-" {
260 if i + 2 < group.len() {
261 if let Some(c) = group[i + 2].symbol_text() {
262 if c == "-" && group[i + 2].type_name() == "textord" {
263 group[i] = ParseNode::TextOrd {
264 mode: Mode::Text,
265 text: "---".to_string(),
266 loc: None,
267 };
268 group.remove(i + 2);
269 group.remove(i + 1);
270 continue;
271 }
272 }
273 }
274 group[i] = ParseNode::TextOrd {
275 mode: Mode::Text,
276 text: "--".to_string(),
277 loc: None,
278 };
279 group.remove(i + 1);
280 continue;
281 }
282 if (a == "'" || a == "`") && b == a {
283 group[i] = ParseNode::TextOrd {
284 mode: Mode::Text,
285 text: format!("{}{}", a, a),
286 loc: None,
287 };
288 group.remove(i + 1);
289 continue;
290 }
291 }
292 }
293 i += 1;
294 }
295 }
296
297 pub fn parse_atom(
301 &mut self,
302 break_on_token_text: Option<&str>,
303 ) -> ParseResult<Option<ParseNode>> {
304 let mut base = self.parse_group("atom", break_on_token_text)?;
305
306 if let Some(ref b) = base {
307 if b.type_name() == "internal" {
308 return Ok(base);
309 }
310 }
311
312 if self.mode == Mode::Text {
313 return Ok(base);
314 }
315
316 let mut superscript: Option<ParseNode> = None;
317 let mut subscript: Option<ParseNode> = None;
318
319 loop {
320 self.consume_spaces()?;
321 let lex = self.fetch()?;
322
323 if lex.text == "\\limits" || lex.text == "\\nolimits" {
324 let is_limits = lex.text == "\\limits";
325 self.consume();
326 if let Some(base_node) = base.as_mut() {
327 match base_node {
328 ParseNode::Op {
329 limits,
330 always_handle_sup_sub,
331 ..
332 } => {
333 *limits = is_limits;
334 *always_handle_sup_sub = Some(is_limits);
335 }
336 ParseNode::OperatorName {
337 limits,
338 always_handle_sup_sub,
339 ..
340 } => {
341 *limits = is_limits;
342 *always_handle_sup_sub = is_limits;
343 }
344 _ => {}
345 }
346 }
347 } else if lex.text == "^" {
348 if superscript.is_some() {
349 return Err(ParseError::new("Double superscript", Some(&lex)));
350 }
351 superscript = Some(self.handle_sup_subscript("superscript")?);
352 } else if lex.text == "_" {
353 if subscript.is_some() {
354 return Err(ParseError::new("Double subscript", Some(&lex)));
355 }
356 subscript = Some(self.handle_sup_subscript("subscript")?);
357 } else if lex.text == "'" {
358 if superscript.is_some() {
359 return Err(ParseError::new("Double superscript", Some(&lex)));
360 }
361 let prime = ParseNode::TextOrd {
362 mode: self.mode,
363 text: "\\prime".to_string(),
364 loc: None,
365 };
366 let mut primes = vec![prime.clone()];
367 self.consume();
368 while self.fetch()?.text == "'" {
369 primes.push(prime.clone());
370 self.consume();
371 }
372 if self.fetch()?.text == "^" {
373 primes.push(self.handle_sup_subscript("superscript")?);
374 }
375 superscript = Some(ParseNode::OrdGroup {
376 mode: self.mode,
377 body: primes,
378 semisimple: None,
379 loc: None,
380 });
381 } else if let Some((mapped, is_sub)) = lex
382 .text
383 .chars()
384 .next()
385 .and_then(crate::unicode_sup_sub::unicode_sub_sup)
386 {
387 if is_sub && subscript.is_some() {
388 return Err(ParseError::new("Double subscript", Some(&lex)));
389 }
390 if !is_sub && superscript.is_some() {
391 return Err(ParseError::new("Double superscript", Some(&lex)));
392 }
393 let mut subsup_tokens = vec![Token::new(mapped, 0, 0)];
395 self.consume();
396 loop {
397 let tok = self.fetch()?;
398 match tok
399 .text
400 .chars()
401 .next()
402 .and_then(crate::unicode_sup_sub::unicode_sub_sup)
403 {
404 Some((m, sub)) if sub == is_sub => {
405 subsup_tokens.insert(0, Token::new(m, 0, 0));
406 self.consume();
407 }
408 _ => break,
409 }
410 }
411 let body = self.subparse(subsup_tokens)?;
412 let group = ParseNode::OrdGroup {
413 mode: Mode::Math,
414 body,
415 semisimple: None,
416 loc: None,
417 };
418 if is_sub {
419 subscript = Some(group);
420 } else {
421 superscript = Some(group);
422 }
423 } else {
424 break;
425 }
426 }
427
428 if superscript.is_some() || subscript.is_some() {
429 Ok(Some(ParseNode::SupSub {
430 mode: self.mode,
431 base: base.map(Box::new),
432 sup: superscript.map(Box::new),
433 sub: subscript.map(Box::new),
434 loc: None,
435 }))
436 } else {
437 Ok(base)
438 }
439 }
440
441 fn handle_sup_subscript(&mut self, name: &str) -> ParseResult<ParseNode> {
443 let symbol_token = self.fetch()?;
444 self.consume();
445 self.consume_spaces()?;
446
447 let group = self.parse_group(name, None)?;
448 match group {
449 Some(g) if g.type_name() != "internal" => Ok(g),
450 Some(_) => {
451 let g2 = self.parse_group(name, None)?;
453 g2.ok_or_else(|| {
454 ParseError::new(
455 format!("Expected group after '{}'", symbol_token.text),
456 Some(&symbol_token),
457 )
458 })
459 }
460 None => Err(ParseError::new(
461 format!("Expected group after '{}'", symbol_token.text),
462 Some(&symbol_token),
463 )),
464 }
465 }
466
467 pub fn parse_group(
471 &mut self,
472 name: &str,
473 break_on_token_text: Option<&str>,
474 ) -> ParseResult<Option<ParseNode>> {
475 let first_token = self.fetch()?;
476 let text = first_token.text.clone();
477
478 if text == "{" || text == "\\begingroup" {
479 self.consume();
480 let group_end = if text == "{" { "}" } else { "\\endgroup" };
481
482 self.gullet.begin_group();
483 let expression = self.parse_expression(false, Some(group_end))?;
484 let last_token = self.fetch()?;
485 self.expect(group_end, true)?;
486 self.gullet.end_group();
487
488 let loc = Some(SourceLocation::range(&first_token.loc, &last_token.loc));
489 let semisimple = if text == "\\begingroup" {
490 Some(true)
491 } else {
492 None
493 };
494
495 Ok(Some(ParseNode::OrdGroup {
496 mode: self.mode,
497 body: expression,
498 semisimple,
499 loc,
500 }))
501 } else {
502 let result = self
503 .parse_function(break_on_token_text, Some(name))?
504 .or_else(|| self.parse_symbol_inner().ok().flatten());
505
506 if result.is_none()
507 && text.starts_with('\\')
508 && !IMPLICIT_COMMANDS.contains(&text.as_str())
509 {
510 return Err(ParseError::new(
511 format!("Undefined control sequence: {}", text),
512 Some(&first_token),
513 ));
514 }
515
516 Ok(result)
517 }
518 }
519
520 pub fn parse_function(
524 &mut self,
525 break_on_token_text: Option<&str>,
526 name: Option<&str>,
527 ) -> ParseResult<Option<ParseNode>> {
528 let token = self.fetch()?;
529 let func = token.text.clone();
530
531 let func_data = match FUNCTIONS.get(func.as_str()) {
532 Some(f) => f,
533 None => return Ok(None),
534 };
535
536 self.consume();
537
538 if let Some(n) = name {
539 if n != "atom" && !func_data.allowed_in_argument {
540 return Err(ParseError::new(
541 format!("Got function '{}' with no arguments as {}", func, n),
542 Some(&token),
543 ));
544 }
545 }
546
547 functions::check_mode_compatibility(func_data, self.mode, &func, Some(&token))?;
548
549 if func == "\\hspace" {
553 self.gullet.consume_spaces();
554 if self.gullet.future().text == "*" {
555 self.gullet.pop_token();
556 }
557 }
558
559 let (args, opt_args) = self.parse_arguments(&func, func_data)?;
560
561 self.call_function(
562 &func,
563 args,
564 opt_args,
565 Some(token),
566 break_on_token_text.map(|s| s.to_string()).as_deref(),
567 )
568 .map(Some)
569 }
570
571 pub fn call_function(
573 &mut self,
574 name: &str,
575 args: Vec<ParseNode>,
576 opt_args: Vec<Option<ParseNode>>,
577 token: Option<Token>,
578 break_on_token_text: Option<&str>,
579 ) -> ParseResult<ParseNode> {
580 let func = FUNCTIONS
581 .get(name)
582 .ok_or_else(|| ParseError::msg(format!("No function handler for {}", name)))?;
583
584 let mut ctx = FunctionContext {
585 func_name: name.to_string(),
586 parser: self,
587 token: token.clone(),
588 break_on_token_text: break_on_token_text.map(|s| s.to_string()),
589 };
590
591 (func.handler)(&mut ctx, args, opt_args)
592 }
593
594 pub fn parse_arguments(
596 &mut self,
597 func: &str,
598 func_data: &functions::FunctionSpec,
599 ) -> ParseResult<(Vec<ParseNode>, Vec<Option<ParseNode>>)> {
600 let total_args = func_data.num_args + func_data.num_optional_args;
601 if total_args == 0 {
602 return Ok((Vec::new(), Vec::new()));
603 }
604
605 let mut args = Vec::new();
606 let mut opt_args = Vec::new();
607
608 for i in 0..total_args {
609 let arg_type = func_data
610 .arg_types
611 .as_ref()
612 .and_then(|types| types.get(i).copied());
613 let is_optional = i < func_data.num_optional_args;
614
615 let effective_type = if (func_data.primitive && arg_type.is_none())
616 || (func_data.node_type == "sqrt"
617 && i == 1
618 && opt_args
619 .first()
620 .is_some_and(|o: &Option<ParseNode>| o.is_none()))
621 {
622 Some(ArgType::Primitive)
623 } else {
624 arg_type
625 };
626
627 let arg = self.parse_group_of_type(
628 &format!("argument to '{}'", func),
629 effective_type,
630 is_optional,
631 )?;
632
633 if is_optional {
634 opt_args.push(arg);
635 } else if let Some(a) = arg {
636 args.push(a);
637 } else {
638 return Err(ParseError::msg(
639 "Null argument, please report this as a bug",
640 ));
641 }
642 }
643
644 Ok((args, opt_args))
645 }
646
647 fn parse_group_of_type(
649 &mut self,
650 name: &str,
651 arg_type: Option<ArgType>,
652 optional: bool,
653 ) -> ParseResult<Option<ParseNode>> {
654 match arg_type {
655 Some(ArgType::Color) => self.parse_color_group(optional),
656 Some(ArgType::Size) => self.parse_size_group(optional),
657 Some(ArgType::Primitive) => {
658 if optional {
659 return Err(ParseError::msg("A primitive argument cannot be optional"));
660 }
661 let group = self.parse_group(name, None)?;
662 match group {
663 Some(g) => Ok(Some(g)),
664 None => Err(ParseError::new(format!("Expected group as {}", name), None)),
665 }
666 }
667 Some(ArgType::Math) | Some(ArgType::Text) => {
668 let mode = match arg_type {
669 Some(ArgType::Math) => Some(Mode::Math),
670 Some(ArgType::Text) => Some(Mode::Text),
671 _ => None,
672 };
673 self.parse_argument_group(optional, mode)
674 }
675 Some(ArgType::HBox) => {
676 let group = self.parse_argument_group(optional, Some(Mode::Text))?;
677 match group {
678 Some(g) => Ok(Some(ParseNode::Styling {
679 mode: g.mode(),
680 style: crate::parse_node::StyleStr::Text,
681 body: vec![g],
682 loc: None,
683 })),
684 None => Ok(None),
685 }
686 }
687 Some(ArgType::Raw) => {
688 let token = self.parse_string_group("raw", optional)?;
689 match token {
690 Some(t) => Ok(Some(ParseNode::Raw {
691 mode: Mode::Text,
692 string: t.text,
693 loc: None,
694 })),
695 None => Ok(None),
696 }
697 }
698 Some(ArgType::Url) => self.parse_url_group(optional),
699 None | Some(ArgType::Original) => self.parse_argument_group(optional, None),
700 }
701 }
702
703 fn parse_color_group(&mut self, optional: bool) -> ParseResult<Option<ParseNode>> {
705 let res = self.parse_string_group("color", optional)?;
706 match res {
707 None => Ok(None),
708 Some(token) => {
709 let text = token.text.trim().to_string();
710 let re = regex_lite::Regex::new(
711 r"^(#[a-fA-F0-9]{3,4}|#[a-fA-F0-9]{6}|#[a-fA-F0-9]{8}|[a-fA-F0-9]{6}|[a-zA-Z]+|\d+(\.\d+)?(,\d+(\.\d+)?)*)$",
712 )
713 .unwrap();
714
715 if !re.is_match(&text) {
716 return Err(ParseError::new(
717 format!("Invalid color: '{}'", text),
718 Some(&token),
719 ));
720 }
721 let mut color = text;
722 if regex_lite::Regex::new(r"^[0-9a-fA-F]{6}$")
723 .unwrap()
724 .is_match(&color)
725 {
726 color = format!("#{}", color);
727 }
728
729 Ok(Some(ParseNode::ColorToken {
730 mode: self.mode,
731 color,
732 loc: None,
733 }))
734 }
735 }
736 }
737
738 pub fn parse_size_group(&mut self, optional: bool) -> ParseResult<Option<ParseNode>> {
740 let mut is_blank = false;
741
742 self.gullet.consume_spaces();
743 let res = if !optional && self.gullet.future().text != "{" {
744 Some(
745 self.parse_regex_group(
746 ®ex_lite::Regex::new(r"^[-+]? *(?:$|\d+|\d+\.\d*|\.\d*) *[a-z]{0,2} *$")
747 .unwrap(),
748 "size",
749 )?,
750 )
751 } else {
752 self.parse_string_group("size", optional)?
753 };
754
755 let res = match res {
756 Some(r) => r,
757 None => return Ok(None),
758 };
759
760 let mut text = res.text.clone();
761 if !optional && text.is_empty() {
762 text = "0pt".to_string();
763 is_blank = true;
764 }
765
766 let size_re =
767 regex_lite::Regex::new(r"([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})").unwrap();
768 let m = size_re
769 .captures(&text)
770 .ok_or_else(|| ParseError::new(format!("Invalid size: '{}'", text), Some(&res)))?;
771
772 let sign = m.get(1).map_or("", |m| m.as_str());
773 let magnitude = m.get(2).map_or("", |m| m.as_str());
774 let unit = m.get(3).map_or("", |m| m.as_str());
775
776 let number: f64 = format!("{}{}", sign, magnitude).parse().unwrap_or(0.0);
777
778 if !is_valid_unit(unit) {
779 return Err(ParseError::new(
780 format!("Invalid unit: '{}'", unit),
781 Some(&res),
782 ));
783 }
784
785 Ok(Some(ParseNode::Size {
786 mode: self.mode,
787 value: crate::parse_node::Measurement {
788 number,
789 unit: unit.to_string(),
790 },
791 is_blank,
792 loc: None,
793 }))
794 }
795
796 fn parse_url_group(&mut self, optional: bool) -> ParseResult<Option<ParseNode>> {
799 self.gullet.lexer.set_catcode('%', 13);
800 self.gullet.lexer.set_catcode('~', 12);
801 let res = self.parse_string_group("url", optional);
802 self.gullet.lexer.set_catcode('%', 14);
803 self.gullet.lexer.set_catcode('~', 13);
804 let res = res?;
805 match res {
806 None => Ok(None),
807 Some(token) => {
808 let url = token.text;
809 Ok(Some(ParseNode::Url {
810 mode: self.mode,
811 url,
812 loc: None,
813 }))
814 }
815 }
816 }
817
818 fn parse_string_group(
820 &mut self,
821 _mode_name: &str,
822 optional: bool,
823 ) -> ParseResult<Option<Token>> {
824 let arg_token = self.gullet.scan_argument(optional)?;
825 let arg_token = match arg_token {
826 Some(t) => t,
827 None => return Ok(None),
828 };
829
830 let mut s = String::new();
831 loop {
832 let next = self.fetch()?;
833 if next.text == "EOF" {
834 break;
835 }
836 s.push_str(&next.text);
837 self.consume();
838 }
839 self.consume(); let mut result = arg_token;
842 result.text = s;
843 Ok(Some(result))
844 }
845
846 fn parse_regex_group(
848 &mut self,
849 regex: ®ex_lite::Regex,
850 mode_name: &str,
851 ) -> ParseResult<Token> {
852 let first_token = self.fetch()?;
853 let mut last_token = first_token.clone();
854 let mut s = String::new();
855
856 loop {
857 let next = self.fetch()?;
858 if next.text == "EOF" {
859 break;
860 }
861 let candidate = format!("{}{}", s, next.text);
862 if regex.is_match(&candidate) {
863 last_token = next;
864 s = candidate;
865 self.consume();
866 } else {
867 break;
868 }
869 }
870
871 if s.is_empty() {
872 return Err(ParseError::new(
873 format!("Invalid {}: '{}'", mode_name, first_token.text),
874 Some(&first_token),
875 ));
876 }
877
878 Ok(first_token.range(&last_token, s))
879 }
880
881 pub fn parse_argument_group(
883 &mut self,
884 optional: bool,
885 mode: Option<Mode>,
886 ) -> ParseResult<Option<ParseNode>> {
887 let arg_token = self.gullet.scan_argument(optional)?;
888 let arg_token = match arg_token {
889 Some(t) => t,
890 None => return Ok(None),
891 };
892
893 let outer_mode = self.mode;
894 if let Some(m) = mode {
895 self.switch_mode(m);
896 }
897
898 self.gullet.begin_group();
899 let expression = self.parse_expression(false, Some("EOF"))?;
900 self.expect("EOF", true)?;
901 self.gullet.end_group();
902
903 let result = ParseNode::OrdGroup {
904 mode: self.mode,
905 loc: Some(arg_token.loc.clone()),
906 body: expression,
907 semisimple: None,
908 };
909
910 if mode.is_some() {
911 self.switch_mode(outer_mode);
912 }
913
914 Ok(Some(result))
915 }
916
917 fn parse_symbol_inner(&mut self) -> ParseResult<Option<ParseNode>> {
921 let nucleus = self.fetch()?;
922 let text = nucleus.text.clone();
923
924 if let Some(stripped) = text.strip_prefix("\\verb") {
925 self.consume();
926 let (star, rest) = if let Some(r) = stripped.strip_prefix('*') {
927 (true, r)
928 } else {
929 (false, stripped)
930 };
931 let chars: Vec<char> = rest.chars().collect();
932 if chars.len() < 2 {
933 return Err(ParseError::new("\\verb assertion failed", Some(&nucleus)));
934 }
935 let body: String = chars[1..chars.len() - 1].iter().collect();
936 return Ok(Some(ParseNode::Verb {
937 mode: Mode::Text,
938 body,
939 star,
940 loc: Some(nucleus.loc.clone()),
941 }));
942 }
943
944 let font_mode = match self.mode {
945 Mode::Math => ratex_font::symbols::Mode::Math,
946 Mode::Text => ratex_font::symbols::Mode::Text,
947 };
948
949 if text == "^" || text == "_" {
951 return Ok(None);
952 }
953
954 if text == "\\" {
956 return Ok(None);
957 }
958
959 if let Some(sym_info) = ratex_font::symbols::get_symbol(&text, font_mode) {
960 let loc = Some(SourceLocation::range(&nucleus.loc, &nucleus.loc));
961 let group = sym_info.group;
962
963 let node = if group.is_atom() {
964 let family = match group {
965 ratex_font::symbols::Group::Bin => AtomFamily::Bin,
966 ratex_font::symbols::Group::Close => AtomFamily::Close,
967 ratex_font::symbols::Group::Inner => AtomFamily::Inner,
968 ratex_font::symbols::Group::Open => AtomFamily::Open,
969 ratex_font::symbols::Group::Punct => AtomFamily::Punct,
970 ratex_font::symbols::Group::Rel => AtomFamily::Rel,
971 _ => unreachable!(),
972 };
973 ParseNode::Atom {
974 mode: self.mode,
975 family,
976 text: text.clone(),
977 loc,
978 }
979 } else {
980 match group {
981 ratex_font::symbols::Group::MathOrd => ParseNode::MathOrd {
982 mode: self.mode,
983 text: text.clone(),
984 loc,
985 },
986 ratex_font::symbols::Group::TextOrd => ParseNode::TextOrd {
987 mode: self.mode,
988 text: text.clone(),
989 loc,
990 },
991 ratex_font::symbols::Group::OpToken => ParseNode::OpToken {
992 mode: self.mode,
993 text: text.clone(),
994 loc,
995 },
996 ratex_font::symbols::Group::AccentToken => ParseNode::AccentToken {
997 mode: self.mode,
998 text: text.clone(),
999 loc,
1000 },
1001 ratex_font::symbols::Group::Spacing => ParseNode::SpacingNode {
1002 mode: self.mode,
1003 text: text.clone(),
1004 loc,
1005 },
1006 _ => ParseNode::MathOrd {
1007 mode: self.mode,
1008 text: text.clone(),
1009 loc,
1010 },
1011 }
1012 };
1013
1014 self.consume();
1015 return Ok(Some(node));
1016 }
1017
1018 if let Some(node) = self.try_parse_unicode_accent(&text, &nucleus)? {
1021 self.consume();
1022 return Ok(Some(node));
1023 }
1024
1025 let first_char = text.chars().next();
1028 if let Some(ch) = first_char {
1029 if ch as u32 >= 0x80 {
1030 let node = ParseNode::TextOrd {
1031 mode: Mode::Text,
1032 text: text.clone(),
1033 loc: Some(SourceLocation::range(&nucleus.loc, &nucleus.loc)),
1034 };
1035 self.consume();
1036 return Ok(Some(node));
1037 }
1038 }
1039
1040 Ok(None)
1041 }
1042
1043 fn try_parse_unicode_accent(
1047 &self,
1048 text: &str,
1049 nucleus: &Token,
1050 ) -> ParseResult<Option<ParseNode>> {
1051 let nfd: String = text.nfd().collect();
1052 let chars: Vec<char> = nfd.chars().collect();
1053
1054 if chars.len() < 2 {
1055 return Ok(None);
1056 }
1057
1058 let mut split_idx = chars.len() - 1;
1060 while split_idx > 0 && is_supported_combining_accent(chars[split_idx]) {
1061 split_idx -= 1;
1062 }
1063
1064 if split_idx == chars.len() - 1 {
1066 return Ok(None);
1067 }
1068
1069 let base_char = chars[0];
1071 if !is_latin_base_char(base_char) {
1072 return Ok(None);
1073 }
1074
1075 let loc = Some(SourceLocation::range(&nucleus.loc, &nucleus.loc));
1076
1077 let mut base_str: String = chars[..split_idx + 1].iter().collect();
1079
1080 if base_str.len() == 1 {
1082 match base_str.as_str() {
1083 "i" => base_str = "\u{0131}".to_string(), "j" => base_str = "\u{0237}".to_string(), _ => {}
1086 }
1087 }
1088
1089 let font_mode = match self.mode {
1090 Mode::Math => ratex_font::symbols::Mode::Math,
1091 Mode::Text => ratex_font::symbols::Mode::Text,
1092 };
1093
1094 let mut node = if base_str.chars().count() == 1 {
1095 let ch = base_str.chars().next().unwrap();
1096 if let Some(sym) = ratex_font::symbols::get_symbol(&base_str, font_mode) {
1097 match sym.group {
1098 ratex_font::symbols::Group::TextOrd => ParseNode::TextOrd {
1099 mode: self.mode,
1100 text: base_str.clone(),
1101 loc: loc.clone(),
1102 },
1103 _ => ParseNode::MathOrd {
1104 mode: self.mode,
1105 text: base_str.clone(),
1106 loc: loc.clone(),
1107 },
1108 }
1109 } else if (ch as u32) >= 0x80 {
1110 ParseNode::TextOrd {
1112 mode: Mode::Text,
1113 text: base_str.clone(),
1114 loc: loc.clone(),
1115 }
1116 } else {
1117 ParseNode::MathOrd {
1118 mode: self.mode,
1119 text: base_str.clone(),
1120 loc: loc.clone(),
1121 }
1122 }
1123 } else {
1124 return self
1125 .try_parse_unicode_accent(&base_str, nucleus)
1126 .map(|opt| {
1127 opt.or_else(|| {
1128 Some(ParseNode::TextOrd {
1129 mode: Mode::Text,
1130 text: base_str.clone(),
1131 loc: loc.clone(),
1132 })
1133 })
1134 });
1135 };
1136
1137 for &combining in &chars[split_idx + 1..] {
1139 let label = combining_to_accent_label(combining, self.mode);
1140 node = ParseNode::Accent {
1141 mode: self.mode,
1142 label,
1143 is_stretchy: Some(false),
1144 is_shifty: Some(true),
1145 base: Box::new(node),
1146 loc: loc.clone(),
1147 };
1148 }
1149
1150 Ok(Some(node))
1151 }
1152
1153 pub fn subparse(&mut self, tokens: Vec<Token>) -> ParseResult<Vec<ParseNode>> {
1155 let old_token = self.next_token.take();
1156
1157 self.gullet.push_token(Token::new("}", 0, 0));
1158 self.gullet.push_tokens(tokens);
1159 let parse = self.parse_expression(false, None)?;
1160 self.expect("}", true)?;
1161
1162 self.next_token = old_token;
1163 Ok(parse)
1164 }
1165}
1166
1167fn is_latin_base_char(ch: char) -> bool {
1168 matches!(ch,
1169 'A'..='Z' | 'a'..='z'
1170 | '\u{0131}' | '\u{0237}' | '\u{00C6}' | '\u{00D0}' | '\u{00D8}' | '\u{00DE}' | '\u{00DF}' | '\u{00E6}' | '\u{00F0}' | '\u{00F8}' | '\u{00FE}' )
1182}
1183
1184fn is_supported_combining_accent(ch: char) -> bool {
1185 matches!(
1186 ch,
1187 '\u{0300}'
1188 | '\u{0301}'
1189 | '\u{0302}'
1190 | '\u{0303}'
1191 | '\u{0304}'
1192 | '\u{0306}'
1193 | '\u{0307}'
1194 | '\u{0308}'
1195 | '\u{030A}'
1196 | '\u{030B}'
1197 | '\u{030C}'
1198 | '\u{0327}'
1199 )
1200}
1201
1202fn combining_to_accent_label(ch: char, mode: Mode) -> String {
1203 match mode {
1204 Mode::Math => match ch {
1205 '\u{0300}' => "\\grave".to_string(),
1206 '\u{0301}' => "\\acute".to_string(),
1207 '\u{0302}' => "\\hat".to_string(),
1208 '\u{0303}' => "\\tilde".to_string(),
1209 '\u{0304}' => "\\bar".to_string(),
1210 '\u{0306}' => "\\breve".to_string(),
1211 '\u{0307}' => "\\dot".to_string(),
1212 '\u{0308}' => "\\ddot".to_string(),
1213 '\u{030A}' => "\\mathring".to_string(),
1214 '\u{030B}' => "\\H".to_string(),
1215 '\u{030C}' => "\\check".to_string(),
1216 '\u{0327}' => "\\c".to_string(),
1217 _ => format!("\\char\"{:X}", ch as u32),
1218 },
1219 Mode::Text => match ch {
1220 '\u{0300}' => "\\`".to_string(),
1221 '\u{0301}' => "\\'".to_string(),
1222 '\u{0302}' => "\\^".to_string(),
1223 '\u{0303}' => "\\~".to_string(),
1224 '\u{0304}' => "\\=".to_string(),
1225 '\u{0306}' => "\\u".to_string(),
1226 '\u{0307}' => "\\.".to_string(),
1227 '\u{0308}' => "\\\"".to_string(),
1228 '\u{030A}' => "\\r".to_string(),
1229 '\u{030B}' => "\\H".to_string(),
1230 '\u{030C}' => "\\v".to_string(),
1231 '\u{0327}' => "\\c".to_string(),
1232 _ => format!("\\char\"{:X}", ch as u32),
1233 },
1234 }
1235}
1236
1237fn is_valid_unit(unit: &str) -> bool {
1238 matches!(
1239 unit,
1240 "pt" | "mm"
1241 | "cm"
1242 | "in"
1243 | "bp"
1244 | "pc"
1245 | "dd"
1246 | "cc"
1247 | "nd"
1248 | "nc"
1249 | "sp"
1250 | "px"
1251 | "ex"
1252 | "em"
1253 | "mu"
1254 )
1255}
1256
1257fn strip_outer_math_delimiters(input: &str) -> &str {
1261 let s = input.trim();
1262 if s.len() >= 4 && s.starts_with("$$") && s.ends_with("$$") {
1263 return s[2..s.len() - 2].trim();
1264 }
1265 if s.len() >= 2 && s.starts_with('$') && s.ends_with('$') {
1266 return s[1..s.len() - 1].trim();
1267 }
1268 s
1269}
1270
1271pub fn parse(input: &str) -> ParseResult<Vec<ParseNode>> {
1273 Parser::new(strip_outer_math_delimiters(input)).parse()
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278 use super::*;
1279
1280 #[test]
1281 fn test_parse_single_char() {
1282 let result = parse("x").unwrap();
1283 assert_eq!(result.len(), 1);
1284 assert_eq!(result[0].type_name(), "mathord");
1285 }
1286
1287 #[test]
1288 fn test_parse_strips_outer_dollar_inline_math() {
1289 let inner = r"C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}";
1290 let wrapped = format!("${inner}$");
1291 let a = parse(&wrapped).expect("wrapped");
1292 let b = parse(inner).expect("inner");
1293 assert_eq!(a.len(), b.len());
1294 for (x, y) in a.iter().zip(b.iter()) {
1295 assert_eq!(x.type_name(), y.type_name());
1296 }
1297 }
1298
1299 #[test]
1300 fn test_parse_addition() {
1301 let result = parse("a+b").unwrap();
1302 assert_eq!(result.len(), 3);
1303 assert_eq!(result[0].type_name(), "mathord"); assert_eq!(result[1].type_name(), "atom"); assert_eq!(result[2].type_name(), "mathord"); }
1307
1308 #[test]
1309 fn test_parse_superscript() {
1310 let result = parse("x^2").unwrap();
1311 assert_eq!(result.len(), 1);
1312 assert_eq!(result[0].type_name(), "supsub");
1313 }
1314
1315 #[test]
1316 fn test_parse_subscript() {
1317 let result = parse("a_i").unwrap();
1318 assert_eq!(result.len(), 1);
1319 assert_eq!(result[0].type_name(), "supsub");
1320 }
1321
1322 #[test]
1323 fn test_parse_supsub() {
1324 let result = parse("x^2_i").unwrap();
1325 assert_eq!(result.len(), 1);
1326 assert_eq!(result[0].type_name(), "supsub");
1327 if let ParseNode::SupSub { sup, sub, .. } = &result[0] {
1328 assert!(sup.is_some());
1329 assert!(sub.is_some());
1330 } else {
1331 panic!("Expected SupSub");
1332 }
1333 }
1334
1335 #[test]
1336 fn test_parse_group() {
1337 let result = parse("{a+b}").unwrap();
1338 assert_eq!(result.len(), 1);
1339 assert_eq!(result[0].type_name(), "ordgroup");
1340 }
1341
1342 #[test]
1343 fn test_parse_frac() {
1344 let result = parse("\\frac{a}{b}").unwrap();
1345 assert_eq!(result.len(), 1);
1346 assert_eq!(result[0].type_name(), "genfrac");
1347 }
1348
1349 #[test]
1350 fn test_parse_sqrt() {
1351 let result = parse("\\sqrt{x}").unwrap();
1352 assert_eq!(result.len(), 1);
1353 assert_eq!(result[0].type_name(), "sqrt");
1354 }
1355
1356 #[test]
1357 fn test_parse_sqrt_optional() {
1358 let result = parse("\\sqrt[3]{x}").unwrap();
1359 assert_eq!(result.len(), 1);
1360 if let ParseNode::Sqrt { index, .. } = &result[0] {
1361 assert!(index.is_some());
1362 } else {
1363 panic!("Expected Sqrt");
1364 }
1365 }
1366
1367 #[test]
1368 fn test_parse_nested() {
1369 let result = parse("\\frac{\\sqrt{a^2+b^2}}{c}").unwrap();
1370 assert_eq!(result.len(), 1);
1371 assert_eq!(result[0].type_name(), "genfrac");
1372 }
1373
1374 #[test]
1375 fn test_parse_empty() {
1376 let result = parse("").unwrap();
1377 assert_eq!(result.len(), 0);
1378 }
1379
1380 #[test]
1381 fn test_parse_double_superscript_error() {
1382 let result = parse("x^2^3");
1383 assert!(result.is_err());
1384 }
1385
1386 #[test]
1387 fn test_parse_unclosed_brace_error() {
1388 let result = parse("{x");
1389 assert!(result.is_err());
1390 }
1391
1392 #[test]
1393 fn test_parse_json_output() {
1394 let result = parse("x^2").unwrap();
1395 let json = serde_json::to_string_pretty(&result).unwrap();
1396 assert!(json.contains("supsub"));
1397 }
1398}