1use super::ast::{
7 AccentKind, AtomKind, ColSpec, DelimSize, Delimiter, EnvRow, EqNumber, IntegralKind, MathNode,
8 MatrixStyle, PhantomKind, SpaceKind, TextStyle,
9};
10use super::preproc::preprocess;
11use super::token::{tokenize, Token};
12use crate::color::{parse_color_spec, Color, ColorTable};
13use crate::dim::Dim;
14use crate::error::{Error, ParseError};
15use crate::symbols::{lookup, SymbolKind as CatalogKind};
16
17pub const DEFAULT_MAX_NESTING_DEPTH: usize = 32;
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54#[non_exhaustive]
55pub struct ParseOptions {
56 pub max_depth: usize,
58}
59
60impl Default for ParseOptions {
61 fn default() -> Self {
62 Self {
63 max_depth: DEFAULT_MAX_NESTING_DEPTH,
64 }
65 }
66}
67
68impl ParseOptions {
69 #[must_use]
71 pub fn new() -> Self {
72 Self::default()
73 }
74
75 #[must_use]
80 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
81 self.max_depth = max_depth;
82 self
83 }
84}
85
86pub fn parse(input: &str) -> Result<MathNode, ParseError> {
116 parse_with_colors(input).map(|(n, _)| n)
117}
118
119pub fn parse_with_colors(input: &str) -> Result<(MathNode, ColorTable), ParseError> {
143 parse_with_options(input, &ParseOptions::default())
144}
145
146pub fn parse_with_options(
163 input: &str,
164 options: &ParseOptions,
165) -> Result<(MathNode, ColorTable), ParseError> {
166 let sanitized = preprocess(input);
167 let tokens = tokenize(&sanitized)?;
168 let tokens = strip_fences(&tokens)?;
169 let mut p = Parser {
170 tokens,
171 pos: 0,
172 depth: 0,
173 max_depth: options.max_depth,
174 colors: ColorTable::new(),
175 };
176 let node = p.parse_list(Stop::eof())?;
177 p.skip_ws();
178 if p.pos < p.tokens.len() {
179 return Err(ParseError::Malformed(format!(
180 "unexpected leftover token {}",
181 p.tokens[p.pos]
182 )));
183 }
184 Ok((node, p.colors))
185}
186
187#[derive(Clone, Copy)]
188struct Stop {
189 end_group: bool,
190 amp: bool,
191 cr: bool,
192 right: bool,
193 end_env: bool,
194 rbracket: bool,
195}
196
197impl Stop {
198 fn eof() -> Self {
199 Self {
200 end_group: false,
201 amp: false,
202 cr: false,
203 right: false,
204 end_env: false,
205 rbracket: false,
206 }
207 }
208
209 fn group() -> Self {
210 Self {
211 end_group: true,
212 ..Self::eof()
213 }
214 }
215
216 fn cell() -> Self {
217 Self {
218 amp: true,
219 cr: true,
220 end_env: true,
221 ..Self::eof()
222 }
223 }
224
225 fn delim() -> Self {
226 Self {
227 right: true,
228 ..Self::eof()
229 }
230 }
231
232 fn index() -> Self {
233 Self {
234 rbracket: true,
235 ..Self::eof()
236 }
237 }
238
239 fn substack_line() -> Self {
240 Self {
241 end_group: true,
242 cr: true,
243 ..Self::eof()
244 }
245 }
246}
247
248struct Parser {
249 tokens: Vec<Token>,
250 pos: usize,
251 colors: ColorTable,
252 depth: usize,
254 max_depth: usize,
256}
257
258impl Parser {
259 fn skip_ws(&mut self) {
260 while matches!(self.tokens.get(self.pos), Some(Token::Space)) {
261 self.pos += 1;
262 }
263 }
264
265 fn peek(&self) -> Option<&Token> {
266 self.tokens.get(self.pos)
267 }
268
269 fn peek_ws(&mut self) -> Option<&Token> {
270 self.skip_ws();
271 self.peek()
272 }
273
274 fn bump(&mut self) -> Option<Token> {
275 self.skip_ws();
276 self.bump_raw()
277 }
278
279 fn bump_raw(&mut self) -> Option<Token> {
280 let t = self.tokens.get(self.pos).cloned()?;
281 self.pos += 1;
282 Some(t)
283 }
284
285 fn is_stop(&self, tok: &Token, stop: Stop) -> bool {
286 match tok {
287 Token::EndGroup if stop.end_group => true,
288 Token::AlignmentTab if stop.amp => true,
289 Token::Command(s) if s == "\\" && stop.cr => true,
290 Token::Command(s) if s == "cr" && stop.cr => true,
291 Token::Command(s) if s == "right" && stop.right => true,
292 Token::Command(s) if s == "end" && stop.end_env => true,
293 Token::Char(']') if stop.rbracket => true,
294 _ => false,
295 }
296 }
297
298 fn nested<T>(
300 &mut self,
301 f: impl FnOnce(&mut Self) -> Result<T, ParseError>,
302 ) -> Result<T, ParseError> {
303 if self.depth >= self.max_depth {
304 return Err(ParseError::Malformed(format!(
305 "input nests deeper than {} levels",
306 self.max_depth
307 )));
308 }
309 self.depth += 1;
310 let out = f(self);
311 self.depth -= 1;
312 out
313 }
314
315 fn parse_list(&mut self, stop: Stop) -> Result<MathNode, ParseError> {
316 self.nested(|p| p.parse_list_inner(stop))
317 }
318
319 fn parse_list_inner(&mut self, stop: Stop) -> Result<MathNode, ParseError> {
320 let mut items = Vec::new();
321 loop {
322 self.skip_ws();
323 let Some(tok) = self.peek().cloned() else {
324 break;
325 };
326 if self.is_stop(&tok, stop) {
327 break;
328 }
329 match &tok {
330 Token::MathShift | Token::DisplayShift => {
331 return Err(ParseError::Malformed("unexpected math shift".into()));
332 }
333 Token::Command(n) if n == "color" => {
334 self.bump();
335 let c = self.parse_color_from_cmd()?;
336 let rest = self.parse_list(stop)?;
337 items.push(MathNode::Color(c, Box::new(rest)));
338 break;
339 }
340 Token::Command(n) if n == "definecolor" => {
341 self.bump();
342 self.parse_definecolor()?;
343 continue;
344 }
345 _ => {}
346 }
347 items.push(self.parse_atom()?);
348 }
349 Ok(wrap_row(items))
350 }
351
352 fn parse_atom(&mut self) -> Result<MathNode, ParseError> {
353 let mut nucleus = self.parse_nucleus()?;
354 let mut limits = None;
355 loop {
356 match self.peek_ws() {
357 Some(Token::Command(n)) if n == "limits" => {
358 self.bump();
359 limits = Some(true);
360 }
361 Some(Token::Command(n)) if n == "nolimits" => {
362 self.bump();
363 limits = Some(false);
364 }
365 _ => break,
366 }
367 }
368 if let Some(flag) = limits {
369 if let MathNode::Operator(name, _) = nucleus {
370 nucleus = MathNode::Operator(name, flag);
371 }
372 }
373 self.bind_scripts(nucleus)
374 }
375
376 fn parse_nucleus(&mut self) -> Result<MathNode, ParseError> {
377 self.skip_ws();
378 match self.peek().cloned() {
379 None => Err(ParseError::Malformed("unexpected end of input".into())),
380 Some(Token::Superscript | Token::Subscript | Token::Char('\'')) => {
381 Ok(MathNode::Row(Vec::new()))
382 }
383 Some(Token::BeginGroup) => self.parse_group(),
384 Some(Token::Char(c)) => {
385 self.bump();
386 Ok(MathNode::Atom(c, atom_kind(c)))
387 }
388 Some(Token::Command(name)) => {
389 self.bump();
390 self.parse_command(&name)
391 }
392 Some(other) => Err(ParseError::Malformed(format!("unexpected token {other}"))),
393 }
394 }
395
396 fn parse_group(&mut self) -> Result<MathNode, ParseError> {
397 match self.bump() {
398 Some(Token::BeginGroup) => {}
399 _ => return Err(ParseError::Malformed("expected '{'".into())),
400 }
401 let inner = self.parse_list(Stop::group())?;
402 match self.bump() {
403 Some(Token::EndGroup) => Ok(inner),
404 _ => Err(ParseError::Malformed("unmatched '{'".into())),
405 }
406 }
407
408 fn parse_arg(&mut self) -> Result<MathNode, ParseError> {
409 self.nested(Self::parse_arg_inner)
410 }
411
412 fn parse_arg_inner(&mut self) -> Result<MathNode, ParseError> {
413 self.skip_ws();
414 match self.peek() {
415 Some(Token::BeginGroup) => self.parse_group(),
416 Some(_) => self.parse_nucleus(),
417 None => Err(ParseError::Malformed("missing argument".into())),
418 }
419 }
420
421 fn parse_script(&mut self) -> Result<MathNode, ParseError> {
422 self.parse_arg()
423 }
424
425 fn bind_scripts(&mut self, nucleus: MathNode) -> Result<MathNode, ParseError> {
426 let mut sub: Option<MathNode> = None;
427 let mut sup: Option<MathNode> = None;
428 let mut sup_from_prime = false;
429 loop {
430 match self.peek_ws() {
431 Some(Token::Subscript) => {
432 self.bump();
433 if sub.is_some() {
434 return Err(ParseError::Malformed("double subscript".into()));
435 }
436 sub = Some(self.parse_script()?);
437 }
438 Some(Token::Superscript) => {
439 self.bump();
440 let s = self.parse_script()?;
441 if let Some(prev) = sup.take() {
442 if !sup_from_prime {
443 return Err(ParseError::Malformed("double superscript".into()));
444 }
445 sup = Some(wrap_row(vec![prev, s]));
446 sup_from_prime = false;
447 } else {
448 sup = Some(s);
449 }
450 }
451 Some(Token::Char('\'')) => {
452 self.bump();
453 let prime = MathNode::Atom('′', AtomKind::Ord);
454 sup = Some(match sup.take() {
455 None => prime,
456 Some(prev) => wrap_row(vec![prev, prime]),
457 });
458 sup_from_prime = true;
459 }
460 _ => break,
461 }
462 }
463 Ok(apply_scripts(nucleus, sub, sup))
464 }
465
466 fn parse_command(&mut self, name: &str) -> Result<MathNode, ParseError> {
467 match name {
468 "frac" | "dfrac" | "tfrac" | "cfrac" => {
469 let n = self.parse_arg()?;
470 let d = self.parse_arg()?;
471 Ok(MathNode::Fraction(Box::new(n), Box::new(d)))
472 }
473 "binom" | "dbinom" | "tbinom" => {
474 let n = self.parse_arg()?;
475 let k = self.parse_arg()?;
476 Ok(MathNode::Delimited(
477 Delimiter::Char('('),
478 Box::new(MathNode::Fraction(Box::new(n), Box::new(k))),
479 Delimiter::Char(')'),
480 ))
481 }
482 "genfrac" => self.parse_genfrac(),
483 "sqrt" => {
484 let index = if matches!(self.peek_ws(), Some(Token::Char('['))) {
485 self.bump();
486 let idx = self.parse_list(Stop::index())?;
487 match self.bump() {
488 Some(Token::Char(']')) => {}
489 _ => {
490 return Err(ParseError::Malformed(
491 "expected ']' after \\sqrt index".into(),
492 ))
493 }
494 }
495 Some(Box::new(idx))
496 } else {
497 None
498 };
499 let rad = self.parse_arg()?;
500 Ok(MathNode::Radical(index, Box::new(rad)))
501 }
502 "left" => self.parse_delimited(),
503 "right" => Err(ParseError::UnmatchedDelimiter),
504 "begin" => self.parse_begin(),
505 "end" => Err(ParseError::Malformed("unexpected \\end".into())),
506 "over" => Err(ParseError::Malformed("\\over outside a group".into())),
507 "choose" => Err(ParseError::Malformed("\\choose outside a group".into())),
508 "hat" => self.accent(AccentKind::Hat),
509 "check" => self.accent(AccentKind::Check),
510 "breve" => self.accent(AccentKind::Breve),
511 "acute" => self.accent(AccentKind::Acute),
512 "grave" => self.accent(AccentKind::Grave),
513 "tilde" => self.accent(AccentKind::Tilde),
514 "bar" => self.accent(AccentKind::Bar),
515 "vec" => self.accent(AccentKind::Vec),
516 "dot" => self.accent(AccentKind::Dot),
517 "ddot" => self.accent(AccentKind::Ddot),
518 "dddot" => self.accent(AccentKind::Dddot),
519 "ddddot" => self.accent(AccentKind::Ddddot),
520 "widehat" => self.accent(AccentKind::WideHat),
521 "widetilde" => self.accent(AccentKind::WideTilde),
522 "overline" => self.accent(AccentKind::Overline),
523 "underline" => self.accent(AccentKind::Underline),
524 "overbrace" => self.accent(AccentKind::Overbrace),
525 "underbrace" => self.accent(AccentKind::Underbrace),
526 "overleftarrow" => self.accent(AccentKind::Overleftarrow),
527 "overrightarrow" => self.accent(AccentKind::Overrightarrow),
528 "overleftrightarrow" => self.accent(AccentKind::Overleftrightarrow),
529 "underleftarrow" => self.accent(AccentKind::Underleftarrow),
530 "underrightarrow" => self.accent(AccentKind::Underrightarrow),
531 "underleftrightarrow" => self.accent(AccentKind::Underleftrightarrow),
532 "cancel" => self.accent(AccentKind::Cancel),
533 "bcancel" => self.accent(AccentKind::BCancel),
534 "xcancel" => self.accent(AccentKind::XCancel),
535 "boxed" | "fbox" => self.accent(AccentKind::Boxed),
536 "mathring" => self.accent(AccentKind::Ring),
537 "cancelto" => {
538 let value = self.parse_arg()?;
539 let expr = self.parse_arg()?;
540 if is_empty_node(&expr) {
541 return Err(ParseError::Malformed("empty accent base".into()));
542 }
543 Ok(MathNode::CancelTo(Box::new(value), Box::new(expr)))
544 }
545 "not" => {
546 let body = self.parse_nucleus()?;
547 Ok(MathNode::Accent(Box::new(body), AccentKind::Not))
548 }
549 "overset" => {
550 let over = self.parse_arg()?;
551 let base = self.parse_arg()?;
552 Ok(MathNode::OverUnder(
553 Box::new(base),
554 Some(Box::new(over)),
555 None,
556 ))
557 }
558 "underset" => {
559 let under = self.parse_arg()?;
560 let base = self.parse_arg()?;
561 Ok(MathNode::OverUnder(
562 Box::new(base),
563 None,
564 Some(Box::new(under)),
565 ))
566 }
567 "stackrel" => {
568 let over = self.parse_arg()?;
569 let base = self.parse_arg()?;
570 Ok(MathNode::OverUnder(
571 Box::new(base),
572 Some(Box::new(over)),
573 None,
574 ))
575 }
576 "mathrm" | "textrm" => self.font(TextStyle::Rm),
577 "mathbf" | "textbf" => self.font(TextStyle::Bf),
578 "mathit" | "textit" => self.font(TextStyle::It),
579 "mathsf" | "textsf" => self.font(TextStyle::Sf),
580 "mathtt" | "texttt" => self.font(TextStyle::Tt),
581 "mathbb" => self.font(TextStyle::Bb),
582 "mathcal" => self.font(TextStyle::Cal),
583 "mathfrak" => self.font(TextStyle::Frak),
584 "mathscr" => self.font(TextStyle::Scr),
585 "boldsymbol" => self.font(TextStyle::Boldsymbol),
586 "pmb" => self.font(TextStyle::Pmb),
587 "xrightarrow" => self.parse_xarrow("longrightarrow"),
588 "xleftarrow" => self.parse_xarrow("longleftarrow"),
589 "text" | "mbox" => self.parse_text(TextStyle::Text),
590 "operatorname" => {
591 let name = self.collect_group_text()?;
592 Ok(MathNode::Operator(name, false))
593 }
594 "," => Ok(MathNode::Space(SpaceKind::Thin)),
595 ":" | ">" => Ok(MathNode::Space(SpaceKind::Medium)),
596 ";" => Ok(MathNode::Space(SpaceKind::Thick)),
597 "!" => Ok(MathNode::Space(SpaceKind::NegThin)),
598 "quad" => Ok(MathNode::Space(SpaceKind::Quad)),
599 "qquad" => Ok(MathNode::Space(SpaceKind::Qquad)),
600 " " => Ok(MathNode::Space(SpaceKind::ControlSpace)),
601 "hspace" => {
602 let spec = self.collect_group_text()?;
603 let d = parse_tex_dim(&spec)?;
604 Ok(MathNode::Space(SpaceKind::Hspace(d)))
605 }
606 "phantom" => {
607 let b = self.parse_arg()?;
608 Ok(MathNode::Phantom(PhantomKind::Full, Box::new(b)))
609 }
610 "vphantom" => {
611 let b = self.parse_arg()?;
612 Ok(MathNode::Phantom(PhantomKind::Vertical, Box::new(b)))
613 }
614 "hphantom" => {
615 let b = self.parse_arg()?;
616 Ok(MathNode::Phantom(PhantomKind::Horizontal, Box::new(b)))
617 }
618 "strut" => Ok(MathNode::Strut(Dim::ratio(7, 10), Dim::ratio(3, 10))),
619 "rule" => {
620 let _w = parse_tex_dim(&self.collect_group_text()?)?;
621 let h = parse_tex_dim(&self.collect_group_text()?)?;
622 Ok(MathNode::Strut(h, Dim::zero()))
623 }
624 "textcolor" => {
625 let c = self.parse_color_from_cmd()?;
626 let body = self.parse_arg()?;
627 Ok(MathNode::TextColor(c, Box::new(body)))
628 }
629 "colorbox" => {
630 let c = self.parse_color_from_cmd()?;
631 let body = self.parse_arg()?;
632 Ok(MathNode::ColorBox(c, Box::new(body)))
633 }
634 "fcolorbox" => {
635 let border = self.parse_color_from_cmd()?;
636 let fill = self.parse_color_from_cmd()?;
637 let body = self.parse_arg()?;
638 Ok(MathNode::FColorBox(border, fill, Box::new(body)))
639 }
640 "sum" => Ok(MathNode::Sum(None, None)),
641 "prod" => Ok(MathNode::Product(None, None)),
642 "int" => Ok(MathNode::Integral(IntegralKind::Int, None, None)),
643 "iint" => Ok(MathNode::Integral(IntegralKind::Iint, None, None)),
644 "iiint" => Ok(MathNode::Integral(IntegralKind::Iiint, None, None)),
645 "oint" => Ok(MathNode::Integral(IntegralKind::Oint, None, None)),
646 "oiint" => Ok(MathNode::Integral(IntegralKind::Oiint, None, None)),
647 "lim" => Ok(MathNode::Limit(None)),
648 "sin" | "cos" | "tan" | "cot" | "sec" | "csc" | "arcsin" | "arccos" | "arctan"
649 | "sinh" | "cosh" | "tanh" | "coth" | "log" | "ln" | "lg" | "exp" | "limsup"
650 | "liminf" | "sup" | "inf" | "max" | "min" | "det" | "dim" | "ker" | "deg" | "gcd"
651 | "lcm" | "Pr" | "arg" => Ok(MathNode::Operator(name.to_string(), false)),
652 "coprod" | "bigcup" | "bigcap" | "bigsqcup" | "bigvee" | "bigwedge" | "bigoplus"
653 | "bigotimes" | "biguplus" => Ok(MathNode::Operator(name.to_string(), true)),
654 "big" | "Big" | "bigg" | "Bigg" | "bigl" | "bigr" | "Bigl" | "Bigr" | "biggl"
655 | "biggr" | "Biggl" | "Biggr" | "bigm" | "Bigm" | "biggm" | "Biggm" => {
656 self.parse_sized_delim(name)
657 }
658 "tag" => {
659 let star = matches!(self.peek_ws(), Some(Token::Char('*')));
660 if star {
661 self.bump();
662 }
663 let body = self.parse_arg()?;
664 Ok(MathNode::Tag {
665 star,
666 body: Box::new(body),
667 })
668 }
669 "label" => {
670 let key = self.collect_group_text()?;
671 Ok(MathNode::Label(key))
672 }
673 "ref" => {
674 let key = self.collect_group_text()?;
675 Ok(MathNode::Ref(key))
676 }
677 "nonumber" | "notag" => Ok(MathNode::NoNumber),
678 "hline" => Ok(MathNode::Hline),
679 "intertext" => {
680 let s = self.collect_group_text()?;
681 Ok(MathNode::Intertext(Box::new(MathNode::Text(
682 s,
683 TextStyle::Text,
684 ))))
685 }
686 "substack" => self.parse_substack(),
687 "displaystyle" | "textstyle" | "scriptstyle" | "scriptscriptstyle" | "limits"
688 | "nolimits" => self.parse_nucleus(),
689 "{" | "}" => {
690 let c = name.chars().next().unwrap_or('{');
691 Ok(MathNode::Atom(
692 c,
693 if name == "{" {
694 AtomKind::Open
695 } else {
696 AtomKind::Close
697 },
698 ))
699 }
700 "|" => Ok(MathNode::Symbol("Vert".into())),
701 "backslash" => Ok(MathNode::Symbol("backslash".into())),
702 _ => {
703 if name.starts_with("math")
704 && name.len() > 4
705 && name.chars().all(|c| c.is_ascii_alphabetic())
706 {
707 return Err(ParseError::Unsupported(format!("font style {name}")));
708 }
709 if name.starts_with("wide") {
710 return Err(ParseError::Unsupported(format!("accent {name}")));
711 }
712 self.parse_symbol_or_unknown(name)
713 }
714 }
715 }
716
717 fn accent(&mut self, kind: AccentKind) -> Result<MathNode, ParseError> {
718 let body = self.parse_arg()?;
719 if is_empty_node(&body) {
720 return Err(ParseError::Malformed("empty accent base".into()));
721 }
722 Ok(MathNode::Accent(Box::new(body), kind))
723 }
724
725 fn parse_xarrow(&mut self, arrow: &str) -> Result<MathNode, ParseError> {
726 let under = if matches!(self.peek_ws(), Some(Token::Char('['))) {
727 self.bump();
728 let u = self.parse_list(Stop::index())?;
729 match self.bump() {
730 Some(Token::Char(']')) => Some(Box::new(u)),
731 _ => {
732 return Err(ParseError::Malformed(
733 "expected ']' after x-arrow optional argument".into(),
734 ))
735 }
736 }
737 } else {
738 None
739 };
740 let over = self.parse_arg()?;
741 Ok(MathNode::OverUnder(
742 Box::new(MathNode::Symbol(arrow.to_string())),
743 Some(Box::new(over)),
744 under,
745 ))
746 }
747
748 fn font(&mut self, style: TextStyle) -> Result<MathNode, ParseError> {
749 let inner = self.parse_arg()?;
750 Ok(collapse_text(apply_text_style(inner, style)))
751 }
752
753 fn parse_text(&mut self, style: TextStyle) -> Result<MathNode, ParseError> {
754 let s = self.collect_group_text()?;
755 Ok(MathNode::Text(s, style))
756 }
757
758 fn parse_delimited(&mut self) -> Result<MathNode, ParseError> {
759 let open = self.parse_delimiter()?;
760 let body = self.parse_list(Stop::delim())?;
761 match self.bump() {
762 Some(Token::Command(n)) if n == "right" => {}
763 _ => return Err(ParseError::UnmatchedDelimiter),
764 }
765 let close = self.parse_delimiter()?;
766 Ok(MathNode::Delimited(open, Box::new(body), close))
767 }
768
769 fn parse_sized_delim(&mut self, name: &str) -> Result<MathNode, ParseError> {
770 let size = DelimSize::from_command(name)
771 .ok_or_else(|| ParseError::Malformed(format!("unknown delimiter size \\{name}")))?;
772 let d = self.parse_delimiter()?;
773 let class = DelimSize::class_from_command(name).unwrap_or_else(|| match &d {
774 Delimiter::Char(c) => atom_kind(*c),
775 Delimiter::Named(n) if n == "{" => AtomKind::Open,
776 Delimiter::Named(n) if n == "}" => AtomKind::Close,
777 _ => AtomKind::Open,
778 });
779 Ok(MathNode::SizedDelim(d, size, class))
780 }
781
782 fn parse_delimiter(&mut self) -> Result<Delimiter, ParseError> {
783 self.skip_ws();
784 match self.bump() {
785 Some(Token::Char('.')) => Ok(Delimiter::Empty),
786 Some(Token::Char(c)) if matches!(c, '(' | ')' | '[' | ']' | '|' | '/' | '<' | '>') => {
787 Ok(Delimiter::Char(c))
788 }
789 Some(Token::Command(n)) => match n.as_str() {
790 "." => Ok(Delimiter::Empty),
791 "{" | "}" | "|" => Ok(Delimiter::Named(n)),
792 "langle" | "rangle" | "lfloor" | "rfloor" | "lceil" | "rceil" | "lvert"
793 | "rvert" | "lVert" | "rVert" | "vert" | "Vert" | "uparrow" | "downarrow"
794 | "Uparrow" | "Downarrow" | "updownarrow" | "Updownarrow" | "backslash"
795 | "lgroup" | "rgroup" | "lmoustache" | "rmoustache" => Ok(Delimiter::Named(n)),
796 other => Err(ParseError::Malformed(format!(
797 "unknown delimiter \\{other}"
798 ))),
799 },
800 Some(other) => Err(ParseError::Malformed(format!(
801 "expected delimiter, found {other}"
802 ))),
803 None => Err(ParseError::Malformed("expected delimiter".into())),
804 }
805 }
806
807 fn parse_begin(&mut self) -> Result<MathNode, ParseError> {
808 let name = self.collect_group_text()?;
809 let colspec = if name == "array" {
810 let preamble = self.collect_group_text()?;
811 parse_colspec(&preamble)?
812 } else {
813 Vec::new()
814 };
815 let style = match name.as_str() {
816 "matrix" => MatrixStyle::Matrix,
817 "pmatrix" => MatrixStyle::Pmatrix,
818 "bmatrix" => MatrixStyle::Bmatrix,
819 "vmatrix" => MatrixStyle::Vmatrix,
820 "Vmatrix" => MatrixStyle::VVmatrix,
821 "Bmatrix" => MatrixStyle::BBmatrix,
822 "cases" => MatrixStyle::Cases,
823 "array" => MatrixStyle::Array,
824 "aligned" => MatrixStyle::Aligned,
825 "align" => MatrixStyle::Align,
826 "gather" => MatrixStyle::Gather,
827 "multline" => MatrixStyle::Multline,
828 "equation" => MatrixStyle::Equation,
829 "split" => MatrixStyle::Split,
830 other => {
831 return Err(ParseError::Unsupported(format!("environment {other}")));
832 }
833 };
834 let rows = self.parse_rows()?;
835 self.expect_end(&name)?;
836 Ok(MathNode::Matrix(style, colspec, rows))
837 }
838
839 fn parse_substack(&mut self) -> Result<MathNode, ParseError> {
840 self.skip_ws();
841 match self.bump() {
842 Some(Token::BeginGroup) => {}
843 _ => {
844 return Err(ParseError::Malformed(
845 "expected '{' after \\substack".into(),
846 ))
847 }
848 }
849 let mut lines = Vec::new();
850 loop {
851 self.skip_ws();
852 if matches!(self.peek(), Some(Token::EndGroup)) {
853 self.bump();
854 break;
855 }
856 let line = self.parse_list(Stop::substack_line())?;
857 lines.push(line);
858 self.skip_ws();
859 match self.peek() {
860 Some(Token::Command(n)) if n == "\\" || n == "cr" => {
861 self.bump();
862 }
863 Some(Token::EndGroup) => {
864 self.bump();
865 break;
866 }
867 None => return Err(ParseError::Malformed("unmatched '{' in \\substack".into())),
868 Some(other) => {
869 return Err(ParseError::Malformed(format!(
870 "unexpected token {other} in \\substack"
871 )))
872 }
873 }
874 }
875 if lines.is_empty() {
876 return Err(ParseError::Malformed("empty \\substack".into()));
877 }
878 Ok(MathNode::Substack(lines))
879 }
880
881 fn parse_rows(&mut self) -> Result<Vec<EnvRow>, ParseError> {
882 self.skip_ws();
883 if matches!(self.peek(), Some(Token::Command(n)) if n == "end") {
884 return Ok(Vec::new());
885 }
886 let mut rows = Vec::new();
887 loop {
888 self.skip_ws();
889 if matches!(self.peek(), Some(Token::Command(n)) if n == "end") {
890 return Ok(rows);
891 }
892 if matches!(self.peek(), Some(Token::Command(n)) if n == "hline") {
893 self.bump();
894 rows.push(EnvRow::Hline);
895 continue;
896 }
897 if matches!(self.peek(), Some(Token::Command(n)) if n == "intertext") {
898 self.bump();
899 let s = self.collect_group_text()?;
900 rows.push(EnvRow::Intertext(Box::new(MathNode::Text(
901 s,
902 TextStyle::Text,
903 ))));
904 continue;
905 }
906 let mut cells = Vec::new();
907 let mut number = EqNumber::Default;
908 let mut labels = Vec::new();
909 loop {
910 let cell = self.parse_list(Stop::cell())?;
911 let cell = peel_row_meta(cell, &mut number, &mut labels);
912 cells.push(cell);
913 self.skip_ws();
914 match self.peek() {
915 Some(Token::AlignmentTab) => {
916 self.bump();
917 }
918 Some(Token::Command(n)) if n == "\\" || n == "cr" => {
919 self.bump();
920 rows.push(finish_env_row(cells, number, labels));
921 self.skip_ws();
922 if matches!(self.peek(), Some(Token::Command(e)) if e == "end") {
923 return Ok(rows);
924 }
925 break;
926 }
927 Some(Token::Command(n)) if n == "end" => {
928 rows.push(finish_env_row(cells, number, labels));
929 return Ok(rows);
930 }
931 None => {
932 return Err(ParseError::Malformed(
933 "unmatched \\begin (missing \\end)".into(),
934 ));
935 }
936 Some(other) => {
937 return Err(ParseError::Malformed(format!(
938 "unexpected token {other} in environment body"
939 )));
940 }
941 }
942 }
943 }
944 }
945
946 fn expect_end(&mut self, name: &str) -> Result<(), ParseError> {
947 match self.bump() {
948 Some(Token::Command(n)) if n == "end" => {}
949 _ => return Err(ParseError::Malformed(format!("expected \\end{{{name}}}"))),
950 }
951 let got = self.collect_group_text()?;
952 if got != name {
953 return Err(ParseError::Malformed(format!(
954 "\\begin{{{name}}} closed by \\end{{{got}}}"
955 )));
956 }
957 Ok(())
958 }
959
960 fn parse_genfrac(&mut self) -> Result<MathNode, ParseError> {
961 let ldel = self.collect_group_text()?;
962 let rdel = self.collect_group_text()?;
963 let _thickness = self.collect_group_text()?;
964 let _style = self.collect_group_text()?;
965 let num = self.parse_arg()?;
966 let den = self.parse_arg()?;
967 let frac = MathNode::Fraction(Box::new(num), Box::new(den));
968 if ldel.is_empty() && rdel.is_empty() {
969 return Ok(frac);
970 }
971 Ok(MathNode::Delimited(
972 delim_from_text(&ldel)?,
973 Box::new(frac),
974 delim_from_text(&rdel)?,
975 ))
976 }
977
978 fn parse_color_from_cmd(&mut self) -> Result<Color, ParseError> {
979 self.skip_ws();
980 let model = if matches!(self.peek(), Some(Token::Char('['))) {
981 self.bump();
982 let m = self.collect_until_char(']')?;
983 match self.bump() {
984 Some(Token::Char(']')) => {}
985 _ => {
986 return Err(ParseError::Malformed(
987 "expected ']' after color model".into(),
988 ))
989 }
990 }
991 m
992 } else {
993 "named".into()
994 };
995 let spec = self.collect_group_text()?;
996 parse_color_spec(&model, &spec, Some(&self.colors)).map_err(color_err)
997 }
998
999 fn parse_definecolor(&mut self) -> Result<(), ParseError> {
1000 let name = self.collect_group_text()?;
1001 let model = self.collect_group_text()?;
1002 let spec = self.collect_group_text()?;
1003 self.colors
1004 .define(&name, &model, &spec)
1005 .map_err(color_err)?;
1006 Ok(())
1007 }
1008
1009 fn collect_group_text(&mut self) -> Result<String, ParseError> {
1010 self.skip_ws();
1011 match self.bump_raw() {
1012 Some(Token::Space) => {
1013 self.pos -= 1;
1014 self.skip_ws();
1015 return self.collect_group_text();
1016 }
1017 Some(Token::BeginGroup) => {}
1018 _ => return Err(ParseError::Malformed("expected '{'".into())),
1019 }
1020 let mut s = String::new();
1021 let mut depth = 1;
1022 while depth > 0 {
1023 match self.bump_raw() {
1024 None => return Err(ParseError::Malformed("unmatched '{'".into())),
1025 Some(Token::BeginGroup) => {
1026 depth += 1;
1027 s.push('{');
1028 }
1029 Some(Token::EndGroup) => {
1030 depth -= 1;
1031 if depth > 0 {
1032 s.push('}');
1033 }
1034 }
1035 Some(Token::Space) => s.push(' '),
1036 Some(Token::Char(c)) => s.push(c),
1037 Some(Token::Command(n)) => {
1038 if n.len() == 1 {
1039 s.push(n.chars().next().unwrap_or('\\'));
1040 } else {
1041 s.push('\\');
1042 s.push_str(&n);
1043 }
1044 }
1045 Some(other) => {
1046 return Err(ParseError::Malformed(format!(
1047 "unexpected token {other} in group text"
1048 )))
1049 }
1050 }
1051 }
1052 Ok(s)
1053 }
1054
1055 fn collect_until_char(&mut self, end: char) -> Result<String, ParseError> {
1056 let mut s = String::new();
1057 loop {
1058 match self.peek() {
1059 None => return Err(ParseError::Malformed(format!("expected '{end}'"))),
1060 Some(Token::Char(c)) if *c == end => break,
1061 Some(Token::Char(c)) => {
1062 s.push(*c);
1063 self.bump_raw();
1064 }
1065 Some(Token::Space) => {
1066 s.push(' ');
1067 self.bump_raw();
1068 }
1069 Some(other) => {
1070 return Err(ParseError::Malformed(format!(
1071 "unexpected token {other} in optional argument"
1072 )))
1073 }
1074 }
1075 }
1076 Ok(s)
1077 }
1078
1079 fn parse_symbol_or_unknown(&self, name: &str) -> Result<MathNode, ParseError> {
1080 let canon = alias(name);
1081 if let Some(e) = lookup(canon).or_else(|| lookup(name)) {
1082 match e.kind {
1083 CatalogKind::Container | CatalogKind::Modifier => {
1084 return Err(ParseError::Unsupported(format!("\\{name}")));
1085 }
1086 CatalogKind::Symbol | CatalogKind::Operator => {
1087 return Ok(MathNode::Symbol(canon.to_string()));
1088 }
1089 }
1090 }
1091 if is_extra_symbol(canon) {
1092 return Ok(MathNode::Symbol(canon.to_string()));
1093 }
1094 Err(ParseError::Unknown(format!("\\{name}")))
1095 }
1096}
1097
1098fn strip_fences(tokens: &[Token]) -> Result<Vec<Token>, ParseError> {
1099 let t = trim_spaces(tokens);
1100 if t.len() >= 2 {
1101 let inner = match (&t[0], &t[t.len() - 1]) {
1102 (Token::MathShift, Token::MathShift) => Some(&t[1..t.len() - 1]),
1103 (Token::DisplayShift, Token::DisplayShift) => Some(&t[1..t.len() - 1]),
1104 (Token::Command(a), Token::Command(b)) if a == "[" && b == "]" => {
1105 Some(&t[1..t.len() - 1])
1106 }
1107 (Token::Command(a), Token::Command(b)) if a == "(" && b == ")" => {
1108 Some(&t[1..t.len() - 1])
1109 }
1110 _ => None,
1111 };
1112 if let Some(inner) = inner {
1113 return Ok(trim_spaces(inner).to_vec());
1114 }
1115 if matches!(t[0], Token::MathShift | Token::DisplayShift)
1116 || matches!(&t[0], Token::Command(s) if s == "[" || s == "(")
1117 {
1118 return Err(ParseError::Malformed("unmatched math delimiter".into()));
1119 }
1120 }
1121 Ok(t.to_vec())
1122}
1123
1124fn trim_spaces(tokens: &[Token]) -> &[Token] {
1125 let mut a = 0;
1126 let mut b = tokens.len();
1127 while a < b && tokens[a] == Token::Space {
1128 a += 1;
1129 }
1130 while b > a && tokens[b - 1] == Token::Space {
1131 b -= 1;
1132 }
1133 &tokens[a..b]
1134}
1135
1136fn wrap_row(mut items: Vec<MathNode>) -> MathNode {
1137 if items.len() == 1 {
1138 items.remove(0)
1139 } else {
1140 MathNode::Row(items)
1141 }
1142}
1143
1144fn parse_colspec(s: &str) -> Result<Vec<ColSpec>, ParseError> {
1145 let mut out = Vec::new();
1146 for c in s.chars() {
1147 match c {
1148 'l' => out.push(ColSpec::Left),
1149 'c' => out.push(ColSpec::Center),
1150 'r' => out.push(ColSpec::Right),
1151 '|' => out.push(ColSpec::VRule),
1152 ' ' | '\t' => {}
1153 '@' | '!' | '>' | '<' | 'p' | 'm' | 'b' | '*' => {
1154 return Err(ParseError::Unsupported(format!("array preamble `{c}`")))
1155 }
1156 other => return Err(ParseError::Malformed(format!("array preamble `{other}`"))),
1157 }
1158 }
1159 if out.is_empty() {
1160 return Err(ParseError::Malformed("empty array preamble".into()));
1161 }
1162 Ok(out)
1163}
1164
1165fn peel_row_meta(node: MathNode, number: &mut EqNumber, labels: &mut Vec<String>) -> MathNode {
1166 match node {
1167 MathNode::NoNumber => {
1168 *number = EqNumber::Suppress;
1169 MathNode::Row(Vec::new())
1170 }
1171 MathNode::Tag { star, body } => {
1172 *number = EqNumber::Tag { star, body };
1173 MathNode::Row(Vec::new())
1174 }
1175 MathNode::Label(k) => {
1176 labels.push(k);
1177 MathNode::Row(Vec::new())
1178 }
1179 MathNode::Hline => MathNode::Hline,
1180 MathNode::Intertext(n) => MathNode::Intertext(n),
1181 MathNode::Row(items) => {
1182 let mut kept = Vec::new();
1183 for it in items {
1184 let p = peel_row_meta(it, number, labels);
1185 if !is_empty_node(&p) {
1186 kept.push(p);
1187 }
1188 }
1189 wrap_row(kept)
1190 }
1191 other => other,
1192 }
1193}
1194
1195fn finish_env_row(cells: Vec<MathNode>, number: EqNumber, labels: Vec<String>) -> EnvRow {
1196 if cells.len() == 1 && matches!(cells[0], MathNode::Hline) {
1197 return EnvRow::Hline;
1198 }
1199 if cells.len() == 1 {
1200 if let MathNode::Intertext(n) = &cells[0] {
1201 return EnvRow::Intertext(n.clone());
1202 }
1203 }
1204 EnvRow::Cells {
1205 cells,
1206 number,
1207 labels,
1208 }
1209}
1210
1211fn is_empty_node(n: &MathNode) -> bool {
1212 match n {
1213 MathNode::Row(v) => v.is_empty() || v.iter().all(is_empty_node),
1214 MathNode::Space(_) | MathNode::NoNumber | MathNode::Label(_) => true,
1215 _ => false,
1216 }
1217}
1218
1219fn apply_scripts(nucleus: MathNode, sub: Option<MathNode>, sup: Option<MathNode>) -> MathNode {
1220 match nucleus {
1221 MathNode::Sum(None, None) => MathNode::Sum(sub.map(Box::new), sup.map(Box::new)),
1222 MathNode::Product(None, None) => MathNode::Product(sub.map(Box::new), sup.map(Box::new)),
1223 MathNode::Integral(k, None, None) => {
1224 MathNode::Integral(k, sub.map(Box::new), sup.map(Box::new))
1225 }
1226 MathNode::Limit(None) => {
1227 let lim = MathNode::Limit(sub.map(Box::new));
1228 match sup {
1229 Some(s) => MathNode::Superscript(Box::new(lim), Box::new(s)),
1230 None => lim,
1231 }
1232 }
1233 MathNode::Accent(b, k @ (AccentKind::Overbrace | AccentKind::Underbrace)) => {
1234 match (sub, sup) {
1235 (None, None) => MathNode::Accent(b, k),
1236 (s, e) => MathNode::OverUnder(
1237 Box::new(MathNode::Accent(b, k)),
1238 e.map(Box::new),
1239 s.map(Box::new),
1240 ),
1241 }
1242 }
1243 other => match (sub, sup) {
1244 (None, None) => other,
1245 (Some(s), None) => MathNode::Subscript(Box::new(other), Box::new(s)),
1246 (None, Some(e)) => MathNode::Superscript(Box::new(other), Box::new(e)),
1247 (Some(s), Some(e)) => MathNode::SubSup(Box::new(other), Box::new(s), Box::new(e)),
1248 },
1249 }
1250}
1251
1252fn apply_text_style(node: MathNode, style: TextStyle) -> MathNode {
1260 match node {
1261 MathNode::Atom(c, _) if crate::style_map::is_stylable(c) => {
1262 MathNode::Text(c.to_string(), style)
1263 }
1264 MathNode::Text(s, _) => MathNode::Text(s, style),
1265 MathNode::Symbol(name) => {
1266 if let Some(ch) = crate::symbols::glyph_char(&name) {
1267 if crate::style_map::is_stylable(ch) {
1268 MathNode::Text(ch.to_string(), style)
1269 } else {
1270 MathNode::Symbol(name)
1271 }
1272 } else {
1273 MathNode::Symbol(name)
1274 }
1275 }
1276 MathNode::Row(v) => collapse_text(MathNode::Row(
1277 v.into_iter().map(|n| apply_text_style(n, style)).collect(),
1278 )),
1279 MathNode::Substack(v) => {
1280 MathNode::Substack(v.into_iter().map(|n| apply_text_style(n, style)).collect())
1281 }
1282 MathNode::Superscript(b, sup) => MathNode::Superscript(
1283 Box::new(apply_text_style(*b, style)),
1284 Box::new(apply_text_style(*sup, style)),
1285 ),
1286 MathNode::Subscript(b, sub) => MathNode::Subscript(
1287 Box::new(apply_text_style(*b, style)),
1288 Box::new(apply_text_style(*sub, style)),
1289 ),
1290 MathNode::SubSup(b, sub, sup) => MathNode::SubSup(
1291 Box::new(apply_text_style(*b, style)),
1292 Box::new(apply_text_style(*sub, style)),
1293 Box::new(apply_text_style(*sup, style)),
1294 ),
1295 MathNode::Fraction(num, den) => MathNode::Fraction(
1296 Box::new(apply_text_style(*num, style)),
1297 Box::new(apply_text_style(*den, style)),
1298 ),
1299 MathNode::Radical(index, body) => MathNode::Radical(
1300 index.map(|i| Box::new(apply_text_style(*i, style))),
1301 Box::new(apply_text_style(*body, style)),
1302 ),
1303 MathNode::Accent(body, kind) => {
1304 MathNode::Accent(Box::new(apply_text_style(*body, style)), kind)
1305 }
1306 MathNode::Delimited(open, body, close) => {
1307 MathNode::Delimited(open, Box::new(apply_text_style(*body, style)), close)
1308 }
1309 other => other,
1310 }
1311}
1312
1313fn collapse_text(node: MathNode) -> MathNode {
1314 let MathNode::Row(v) = node else {
1315 return node;
1316 };
1317 let mut out: Vec<MathNode> = Vec::new();
1318 for n in v {
1319 match (out.last_mut(), &n) {
1320 (Some(MathNode::Text(a, sa)), MathNode::Text(b, sb)) if sa == sb => {
1321 a.push_str(b);
1322 }
1323 _ => out.push(n),
1324 }
1325 }
1326 wrap_row(out)
1327}
1328
1329fn atom_kind(c: char) -> AtomKind {
1330 match c {
1331 '+' | '-' | '*' | '±' | '∓' | '·' | '×' | '÷' => AtomKind::Bin,
1332 '=' | '<' | '>' | '≠' | '≤' | '≥' | '≈' | '≡' => AtomKind::Rel,
1333 '(' | '[' | '{' => AtomKind::Open,
1334 ')' | ']' | '}' => AtomKind::Close,
1335 ',' | ';' | '!' | '?' | ':' => AtomKind::Punct,
1336 _ => AtomKind::Ord,
1337 }
1338}
1339
1340fn delim_from_text(s: &str) -> Result<Delimiter, ParseError> {
1341 let s = s.trim();
1342 if s.is_empty() || s == "." {
1343 return Ok(Delimiter::Empty);
1344 }
1345 if s.chars().count() == 1 {
1346 let c = s.chars().next().unwrap();
1347 return Ok(Delimiter::Char(c));
1348 }
1349 let name = s.strip_prefix('\\').unwrap_or(s);
1350 Ok(Delimiter::Named(name.to_string()))
1351}
1352
1353fn parse_tex_dim(s: &str) -> Result<Dim, ParseError> {
1354 let s = s.trim();
1355 if s.is_empty() {
1356 return Err(ParseError::Malformed("empty dimension".into()));
1357 }
1358 let mut i = 0;
1359 let b = s.as_bytes();
1360 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
1361 i += 1;
1362 }
1363 while i < b.len() && (b[i].is_ascii_digit() || b[i] == b'.') {
1364 i += 1;
1365 }
1366 if i == 0 || (i == 1 && (b[0] == b'+' || b[0] == b'-')) {
1367 return Err(ParseError::Malformed(format!("invalid dimension `{s}`")));
1368 }
1369 let num = Dim::parse(&s[..i]);
1370 let unit = s[i..].trim();
1371 match unit {
1372 "" | "em" => Ok(num),
1373 "mu" => Ok(Dim::from_mu(&num)),
1374 "pt" | "bp" => Ok(num / Dim::from_i64(10)),
1375 other => Err(ParseError::Unsupported(format!("dimension unit {other}"))),
1376 }
1377}
1378
1379fn color_err(e: Error) -> ParseError {
1380 match e {
1381 Error::Unsupported { what } => ParseError::Unsupported(what),
1382 Error::Parse(p) => p,
1383 Error::Malformed { what } => ParseError::Malformed(what),
1384 Error::InvalidOption { what } => ParseError::Malformed(what),
1385 other => ParseError::Malformed(other.to_string()),
1386 }
1387}
1388
1389fn alias(name: &str) -> &str {
1390 match name {
1391 "le" => "leq",
1392 "ge" => "geq",
1393 "ne" => "neq",
1394 "dots" => "ldots",
1395 "lnot" => "neg",
1396 "dag" => "dagger",
1397 "ddag" => "ddagger",
1398 "owns" => "ni",
1399 _ => name,
1400 }
1401}
1402
1403fn is_extra_symbol(name: &str) -> bool {
1404 matches!(
1405 name,
1406 "Gamma"
1407 | "Delta"
1408 | "Theta"
1409 | "Lambda"
1410 | "Xi"
1411 | "Pi"
1412 | "Sigma"
1413 | "Upsilon"
1414 | "Phi"
1415 | "Psi"
1416 | "Omega"
1417 | "varepsilon"
1418 | "vartheta"
1419 | "varpi"
1420 | "varrho"
1421 | "varsigma"
1422 | "varphi"
1423 | "ldots"
1424 | "cdots"
1425 | "vdots"
1426 | "ddots"
1427 | "colon"
1428 | "mid"
1429 | "lvert"
1430 | "rvert"
1431 | "lVert"
1432 | "rVert"
1433 | "vert"
1434 | "Vert"
1435 | "implies"
1436 | "iff"
1437 | "to"
1438 | "gets"
1439 | "neq"
1440 | "leq"
1441 | "geq"
1442 )
1443}
1444
1445#[cfg(test)]
1446mod tests {
1447 use super::*;
1448
1449 #[test]
1450 fn frac_gold() {
1451 let n = parse(r"\frac{1}{2}").unwrap();
1452 assert_eq!(n.gold(), r#"(frac (atom Ord "1") (atom Ord "2"))"#);
1453 }
1454}