Skip to main content

asciimath_parser/
parse.rs

1use crate::tree::{
2    Expression, Frac, Func, Group, Intermediate, Matrix, Script, ScriptFunc, Simple, SimpleBinary,
3    SimpleFunc, SimpleScript, SimpleUnary,
4};
5use crate::{Token, Tokenizer};
6
7/// The maximum recursion depth before deeper structure is treated as [missing][Simple::Missing].
8///
9/// Recursion depth is otherwise linear in the input length, so deeply-nested input like
10/// `"sqrt ".repeat(100_000)` would overflow the stack and abort the process.
11const MAX_DEPTH: usize = 256;
12
13/// A token paired with the bracket-matching info precomputed for its position.
14struct Entry<'a> {
15    text: &'a str,
16    token: Token,
17    /// Matching close-bracket index for an open bracket, or `usize::MAX` if unmatched.
18    close: usize,
19    /// Whether this open bracket has a top-level separator (more than one column).
20    has_sep: bool,
21}
22
23/// A recursive-descent parser over a materialized token slice.
24///
25/// The index cursor makes backtracking a `pos` assignment, and a one-pass precompute of matching
26/// brackets makes matrix detection O(1), keeping the parse linear.
27struct Parser<'a> {
28    entries: Box<[Entry<'a>]>,
29    pos: usize,
30    /// Current recursion depth, bounded by [`MAX_DEPTH`].
31    depth: usize,
32}
33
34impl<'a> Parser<'a> {
35    fn new(tokens: impl IntoIterator<Item = (&'a str, Token)>) -> Self {
36        // collect straight into entries; unmatched open brackets keep close == usize::MAX
37        let mut entries: Vec<Entry<'a>> = tokens
38            .into_iter()
39            .map(|(text, token)| Entry {
40                text,
41                token,
42                close: usize::MAX,
43                has_sep: false,
44            })
45            .collect();
46        // one linear pass matches brackets and records top-level separators
47        let mut open_stack: Vec<usize> = Vec::new();
48        for index in 0..entries.len() {
49            match entries[index].token {
50                Token::OpenBracket => open_stack.push(index),
51                Token::CloseBracket => {
52                    if let Some(open) = open_stack.pop() {
53                        entries[open].close = index;
54                    }
55                }
56                Token::Sep => {
57                    if let Some(&open) = open_stack.last() {
58                        entries[open].has_sep = true;
59                    }
60                }
61                _ => {}
62            }
63        }
64        Parser {
65            entries: entries.into(),
66            pos: 0,
67            depth: 0,
68        }
69    }
70
71    /// Consume and return the next token, advancing the cursor.
72    fn advance(&mut self) -> Option<(&'a str, Token)> {
73        let item = self
74            .entries
75            .get(self.pos)
76            .map(|entry| (entry.text, entry.token));
77        if item.is_some() {
78            self.pos += 1;
79        }
80        item
81    }
82
83    fn next_simple(&mut self, stop: Option<Token>) -> Option<Simple<'a>> {
84        if self.depth >= MAX_DEPTH {
85            return None;
86        }
87        self.depth += 1;
88        let mark = self.pos;
89        let result = match self.advance() {
90            Some((_, token)) if Some(token) == stop => {
91                self.pos = mark; // rewind
92                None
93            }
94            Some((num, Token::Number)) => Some(Simple::Number(num)),
95            Some((text, Token::Text)) => Some(Simple::Text(text)),
96            Some((ident, Token::Ident)) => Some(Simple::Ident(ident)),
97            Some((symb, Token::Symbol)) => Some(Simple::Symbol(symb)),
98            Some((unary, Token::Unary)) => {
99                Some(SimpleUnary::new(unary, self.next_simple(None).unwrap_or_default()).into())
100            }
101            Some((func, Token::Function)) => {
102                Some(SimpleFunc::new(func, self.next_simple(None).unwrap_or_default()).into())
103            }
104            Some((binary, Token::Binary)) => Some(
105                SimpleBinary::new(
106                    binary,
107                    self.next_simple(None).unwrap_or_default(),
108                    self.next_simple(None).unwrap_or_default(),
109                )
110                .into(),
111            ),
112            Some((_, Token::CloseBracket)) => {
113                self.pos = mark; // rewind; always stop on close bracket
114                None
115            }
116            Some((open, Token::OpenBracket)) => Some({
117                // gate the matrix parse on the precompute; done unconditionally it's exponential
118                let matrix = self.could_be_matrix().then(|| {
119                    let mark = self.pos;
120                    self.next_matrix(open).or_else(|| {
121                        self.pos = mark; // rewind before the failed matrix attempt
122                        None
123                    })
124                });
125                match matrix {
126                    Some(Some(matrix)) => matrix.into(),
127                    _ => self.next_open_group(open).into(),
128                }
129            }),
130            Some((open, Token::OpenCloseBracket)) => Some(self.next_open_close_group(open)),
131            Some((raw, Token::Frac | Token::Super | Token::Sub | Token::Sep)) => {
132                Some(Simple::Symbol(raw))
133            }
134            None => None,
135        };
136        self.depth -= 1;
137        result
138    }
139
140    /// Whether the just-consumed open bracket (at `self.pos - 1`) begins a matrix.
141    ///
142    /// O(1) via the precomputed tables; only returns `false` when [`next_matrix`][Self::next_matrix]
143    /// would certainly fail, so results are unchanged.
144    fn could_be_matrix(&self) -> bool {
145        let outer_open = self.pos - 1;
146        let row_open = self.pos;
147        // the first row must itself open with a bracket
148        if !self
149            .entries
150            .get(row_open)
151            .is_some_and(|entry| entry.token == Token::OpenBracket)
152        {
153            return false;
154        }
155        let row_close = self.entries[row_open].close;
156        if row_close >= self.entries.len() {
157            return false; // the first row never closes
158        }
159        let after = row_close + 1;
160        if self
161            .entries
162            .get(after)
163            .is_some_and(|entry| entry.token == Token::Sep)
164        {
165            true // a separator implies a second row
166        } else if after == self.entries[outer_open].close {
167            self.entries[row_open].has_sep // single row: a matrix only with more than one column
168        } else {
169            false
170        }
171    }
172
173    fn next_open_group(&mut self, open: &'a str) -> Group<'a> {
174        let expr = self.next_expression(None);
175        let mark = self.pos;
176        let close = if let Some((bracket, Token::CloseBracket)) = self.advance() {
177            bracket
178        } else {
179            // unterminated (EOF or depth-capped): rewind and close with an empty bracket
180            self.pos = mark; // rewind
181            ""
182        };
183        Group::new(open, expr, close)
184    }
185
186    fn next_open_close_group(&mut self, open: &'a str) -> Simple<'a> {
187        let mark = self.pos;
188        if let Some(first) = self.next_intermediate(None) {
189            // take the first intermediate, even if it's another OpenCloseBracket
190            let mut inters = vec![first];
191            while let Some(inter) = self.next_intermediate(Some(Token::OpenCloseBracket)) {
192                inters.push(inter);
193            }
194            if let Some((close, Token::OpenCloseBracket)) = self.advance() {
195                Simple::Group(Group::new(open, inters, close))
196            } else {
197                // couldn't match the left-right bracket, so rewind and treat it as a symbol
198                self.pos = mark; // rewind
199                Simple::Symbol(open)
200            }
201        } else {
202            // empty so must return symbol
203            Simple::Symbol(open)
204        }
205    }
206
207    fn next_expression(&mut self, stop: Option<Token>) -> Expression<'a> {
208        let mut inters = Vec::new();
209        while let Some(inter) = self.next_intermediate(stop) {
210            inters.push(inter);
211        }
212        inters.into()
213    }
214
215    fn next_matrix_row(
216        &mut self,
217        exprs: &mut impl Extend<Expression<'a>>,
218    ) -> Option<(&'a str, usize, &'a str)> {
219        let open = match self.advance() {
220            Some((open, Token::OpenBracket)) => Some(open),
221            _ => None,
222        }?;
223        let mut len = 1;
224        exprs.extend([self.next_expression(Some(Token::Sep))]);
225        loop {
226            match self.advance() {
227                Some((_, Token::Sep)) => {
228                    exprs.extend([self.next_expression(Some(Token::Sep))]);
229                    len += 1;
230                }
231                Some((close, Token::CloseBracket)) => return Some((open, len, close)),
232                _ => return None,
233            }
234        }
235    }
236
237    fn next_matrix(&mut self, left: &'a str) -> Option<Matrix<'a>> {
238        let mut data = Vec::new();
239        let (open, num_cols, close) = self.next_matrix_row(&mut data)?;
240        loop {
241            match self.advance() {
242                Some((_, Token::Sep)) => {
243                    let (no, ncols, nc) = self.next_matrix_row(&mut data)?;
244                    if no != open || ncols != num_cols || nc != close {
245                        return None;
246                    }
247                }
248                Some((right, Token::CloseBracket))
249                    if data.len() > 1 && open == left && close == right =>
250                {
251                    return Some(Matrix::new(left, data, num_cols, right));
252                }
253                _ => return None,
254            }
255        }
256    }
257
258    fn next_script(&mut self) -> Script<'a> {
259        let mark = self.pos;
260        match self.advance() {
261            Some((_, Token::Super)) => Script::Super(self.next_simple(None).unwrap_or_default()),
262            Some((_, Token::Sub)) => {
263                let sub = self.next_simple(None).unwrap_or_default();
264                let mark = self.pos;
265                if let Some((_, Token::Super)) = self.advance() {
266                    Script::Subsuper(sub, self.next_simple(None).unwrap_or_default())
267                } else {
268                    self.pos = mark; // rewind
269                    Script::Sub(sub)
270                }
271            }
272            _ => {
273                self.pos = mark; // rewind
274                Script::None
275            }
276        }
277    }
278
279    fn next_script_func(&mut self, stop: Option<Token>) -> Option<ScriptFunc<'a>> {
280        if self.depth >= MAX_DEPTH {
281            return None;
282        }
283        self.depth += 1;
284        let mark = self.pos;
285        let result = if let Some((func, Token::Function)) = self.advance() {
286            Some(
287                Func::new(
288                    func,
289                    self.next_script(),
290                    self.next_script_func(None).unwrap_or_default(),
291                )
292                .into(),
293            )
294        } else {
295            self.pos = mark; // rewind
296            self.next_simple(stop)
297                .map(|simp| SimpleScript::new(simp, self.next_script()).into())
298        };
299        self.depth -= 1;
300        result
301    }
302
303    fn next_intermediate(&mut self, stop: Option<Token>) -> Option<Intermediate<'a>> {
304        let base = self.next_script_func(stop)?;
305        let mark = self.pos;
306        if let Some((_, Token::Frac)) = self.advance() {
307            Some(Intermediate::Frac(Frac::new(
308                base,
309                self.next_script_func(None).unwrap_or_default(),
310            )))
311        } else {
312            self.pos = mark; // rewind
313            Some(Intermediate::ScriptFunc(base))
314        }
315    }
316
317    fn parse(&mut self) -> Expression<'a> {
318        let mut inters = Vec::new();
319        let mut wraps = 0;
320        loop {
321            while let Some(inter) = self.next_intermediate(None) {
322                inters.push(inter);
323            }
324            match self.advance() {
325                Some((close, Token::CloseBracket)) => {
326                    // cap the invisible-group nesting so the tree stays bounded; drop excess closes
327                    if wraps < MAX_DEPTH {
328                        let group = Simple::Group(Group::new("", inters, close));
329                        inters = vec![group.into()];
330                        wraps += 1;
331                    }
332                }
333                other => {
334                    // NOTE this can still hide errors if the last token is unexpected
335                    debug_assert!(other.is_none(), "didn't exhaust tokens");
336                    break;
337                }
338            }
339        }
340        Expression::from(inters)
341    }
342}
343
344/// Parse a tokenized expression
345pub fn parse_tokens<'a, T>(tokens: T) -> Expression<'a>
346where
347    T: IntoIterator<Item = (&'a str, Token)>,
348{
349    Parser::new(tokens).parse()
350}
351
352/// Parse a string returning an asciimath expression
353///
354/// This uses an extended set of asciimath tokens that are accessible in [`crate::ASCIIMATH_TOKENS`].
355#[must_use]
356pub fn parse(inp: &str) -> Expression<'_> {
357    parse_tokens(Tokenizer::new(inp))
358}
359
360#[cfg(test)]
361mod tests {
362    use crate::tree::{
363        Expression, Frac, Func, Group, Intermediate, Matrix, Simple, SimpleBinary, SimpleFunc,
364        SimpleScript, SimpleUnary,
365    };
366
367    #[test]
368    fn complex_precedence() {
369        let expr = super::parse("sin_a^b c_d / (abs h)_i^j");
370        let expected = [Frac::new(
371            Func::with_subsuper(
372                "sin",
373                Simple::Ident("a"),
374                Simple::Ident("b"),
375                SimpleScript::with_sub(Simple::Ident("c"), Simple::Ident("d")),
376            ),
377            SimpleScript::with_subsuper(
378                Group::from_iter("(", [SimpleUnary::new("abs", Simple::Ident("h"))], ")"),
379                Simple::Ident("i"),
380                Simple::Ident("j"),
381            ),
382        )]
383        .into_iter()
384        .collect();
385        assert_eq!(expr, expected);
386    }
387
388    #[test]
389    fn missing_sub() {
390        let expr = super::parse("a_");
391        let expected =
392            Expression::from_iter([SimpleScript::with_sub(Simple::Ident("a"), Simple::Missing)]);
393        assert_eq!(expr, expected);
394    }
395
396    #[test]
397    fn missing_super() {
398        let expr = super::parse("a^");
399        let expected = [SimpleScript::with_super(
400            Simple::Ident("a"),
401            Simple::Missing,
402        )]
403        .into_iter()
404        .collect();
405        assert_eq!(expr, expected);
406    }
407
408    #[test]
409    fn missing_group_subsuper() {
410        // NOTE crashes asciimath
411        let expr = super::parse("(a_b^)");
412        let expected = [Group::from_iter(
413            "(",
414            [SimpleScript::with_subsuper(
415                Simple::Ident("a"),
416                Simple::Ident("b"),
417                Simple::Missing,
418            )],
419            ")",
420        )]
421        .into_iter()
422        .collect();
423        assert_eq!(expr, expected);
424    }
425
426    #[test]
427    fn missing_group_unary() {
428        // NOTE crashes asciimath
429        let expr = super::parse("(sqrt)");
430        let expected = [Group::from_iter(
431            "(",
432            [SimpleUnary::new("sqrt", Simple::Missing)],
433            ")",
434        )]
435        .into_iter()
436        .collect();
437        assert_eq!(expr, expected);
438    }
439
440    #[test]
441    fn unmatched_close() {
442        let expr = super::parse(")");
443        let expected = [Group::new("", Expression::default(), ")")]
444            .into_iter()
445            .collect();
446        assert_eq!(expr, expected);
447    }
448
449    #[test]
450    fn simple_bracket_matching() {
451        let expr = super::parse("|a|");
452        let expected = [Group::from_iter("|", [Simple::Ident("a")], "|")]
453            .into_iter()
454            .collect();
455        assert_eq!(expr, expected);
456    }
457
458    #[test]
459    fn eager_bracket_matching() {
460        let expr = super::parse("|a|b|c|"); // "|:a:|b|:c:|"
461        let expected = [
462            Group::from_iter("|", [Simple::Ident("a")], "|").into(),
463            Simple::Ident("b"),
464            Group::from_iter("|", [Simple::Ident("c")], "|").into(),
465        ]
466        .into_iter()
467        .collect();
468        assert_eq!(expr, expected);
469    }
470
471    #[test]
472    fn close_bracket_matching() {
473        let expr = super::parse("(a|b)c|d"); // "(:a|b:)c|d" not "(a|:b)c:|d"
474        let expected = [
475            Group::from_iter(
476                "(",
477                [Simple::Ident("a"), Simple::Symbol("|"), Simple::Ident("b")],
478                ")",
479            )
480            .into(),
481            Simple::Ident("c"),
482            Simple::Symbol("|"),
483            Simple::Ident("d"),
484        ]
485        .into_iter()
486        .collect();
487        assert_eq!(expr, expected);
488    }
489
490    #[test]
491    fn open_close_nonempty() {
492        let expr = super::parse("| |");
493        let expected = [Simple::Symbol("|"), Simple::Symbol("|")]
494            .into_iter()
495            .collect();
496        assert_eq!(expr, expected);
497    }
498
499    #[test]
500    fn double_open_close() {
501        let expr = super::parse("||x||");
502        let expected = Expression::from_iter([Group::from_iter("||", [Simple::Ident("x")], "||")]);
503        assert_eq!(expr, expected);
504    }
505
506    #[test]
507    fn simple_function() {
508        let expr = super::parse("sin x");
509        let expected = [Func::without_scripts("sin", Simple::Ident("x"))]
510            .into_iter()
511            .collect();
512        assert_eq!(expr, expected);
513    }
514
515    #[test]
516    fn complex_function() {
517        let expr = super::parse("sin_cos a cos^b c");
518        let expected = [Func::with_sub(
519            "sin",
520            SimpleFunc::new("cos", Simple::Ident("a")),
521            Func::with_super("cos", Simple::Ident("b"), Simple::Ident("c")),
522        )]
523        .into_iter()
524        .collect();
525        assert_eq!(expr, expected);
526    }
527
528    #[test]
529    fn unary_power_precidence() {
530        let expr = super::parse("sin_a b^c / d");
531        let expected = [Intermediate::Frac(Frac::new(
532            Func::with_sub(
533                "sin",
534                Simple::Ident("a"),
535                SimpleScript::with_super(Simple::Ident("b"), Simple::Ident("c")),
536            ),
537            Simple::Ident("d"),
538        ))]
539        .into();
540        assert_eq!(expr, expected);
541    }
542
543    #[test]
544    fn matrix_parsing() {
545        let expr = super::parse("[[a, b], [c, d]]");
546        let expected = [Matrix::new(
547            "[",
548            [
549                [Simple::Ident("a")].into_iter().collect(),
550                [Simple::Ident("b")].into_iter().collect(),
551                [Simple::Ident("c")].into_iter().collect(),
552                [Simple::Ident("d")].into_iter().collect(),
553            ],
554            2,
555            "]",
556        )]
557        .into_iter()
558        .collect();
559        assert_eq!(expr, expected);
560    }
561
562    #[test]
563    fn no_singleton_matrix() {
564        let expr = super::parse("[[a]]");
565        let expected = [Group::from_iter(
566            "[",
567            [Group::from_iter("[", [Simple::Ident("a")], "]")],
568            "]",
569        )]
570        .into_iter()
571        .collect();
572        assert_eq!(expr, expected);
573    }
574
575    #[test]
576    fn sets_as_groups() {
577        // asciimath treats sets special, here we opt to make matrix parsing a little more strict
578        // to avoid the possibility
579        let expr = super::parse("{(x, y), (a, b)}");
580        let expected = [Group::from_iter(
581            "{",
582            [
583                Group::from_iter(
584                    "(",
585                    [Simple::Ident("x"), Simple::Symbol(","), Simple::Ident("y")],
586                    ")",
587                )
588                .into(),
589                Simple::Symbol(","),
590                Group::from_iter(
591                    "(",
592                    [Simple::Ident("a"), Simple::Symbol(","), Simple::Ident("b")],
593                    ")",
594                )
595                .into(),
596            ],
597            "}",
598        )]
599        .into_iter()
600        .collect();
601        assert_eq!(expr, expected);
602    }
603
604    #[test]
605    fn simple_binary() {
606        let expr = super::parse("root 3");
607        let expected = [SimpleBinary::new(
608            "root",
609            Simple::Number("3"),
610            Simple::Missing,
611        )]
612        .into_iter()
613        .collect();
614        assert_eq!(expr, expected);
615    }
616
617    #[test]
618    fn raw_text() {
619        let expr = super::parse(r#""raw text""#);
620        let expected = Expression::from_iter([Simple::Text("raw text")]);
621        assert_eq!(expr, expected);
622    }
623
624    #[test]
625    fn bare_symbol() {
626        let expr = super::parse("alpha");
627        let expected = Expression::from_iter([Simple::Symbol("alpha")]);
628        assert_eq!(expr, expected);
629    }
630
631    #[test]
632    fn open_close_multiple_intermediates() {
633        // a left-right bracket group with more than one intermediate inside
634        let expr = super::parse("|a b|");
635        let expected = [Group::from_iter(
636            "|",
637            [Simple::Ident("a"), Simple::Ident("b")],
638            "|",
639        )]
640        .into_iter()
641        .collect();
642        assert_eq!(expr, expected);
643    }
644
645    #[test]
646    fn unclosed_groups() {
647        // brackets that never close fall back to groups with an empty closing bracket
648        let expr = super::parse("[[a");
649        let expected = [Group::from_iter(
650            "[",
651            [Group::from_iter("[", [Simple::Ident("a")], "")],
652            "",
653        )]
654        .into_iter()
655        .collect();
656        assert_eq!(expr, expected);
657    }
658
659    #[test]
660    fn deep_nested_brackets_are_not_exponential() {
661        // nested brackets used to be exponential (this would hang); it must be linear now
662        let depth = 150;
663        let input = format!("{}a{}", "(".repeat(depth), ")".repeat(depth));
664        let expr = super::parse(&input);
665        assert_eq!(expr.len(), 1);
666    }
667
668    #[test]
669    fn deep_unary_chain_does_not_overflow() {
670        // a deep unary chain must not overflow the stack (recurses via next_simple)
671        let input = "sqrt ".repeat(100_000);
672        let expr = super::parse(&input);
673        assert!(!expr.is_empty());
674    }
675
676    #[test]
677    fn deep_function_chain_does_not_overflow() {
678        // a deep function chain must not overflow the stack (recurses via next_script_func)
679        let input = "sin ".repeat(100_000);
680        let expr = super::parse(&input);
681        assert!(!expr.is_empty());
682    }
683
684    #[test]
685    fn many_unmatched_closes_are_capped() {
686        // capped to a bounded tree, so clone/compare/drop are all safe
687        let input = ")".repeat(200_000);
688        let expr = super::parse(&input);
689        assert_eq!(expr.len(), 1);
690        let cloned = expr.clone();
691        assert_eq!(expr, cloned);
692    }
693
694    #[test]
695    fn ragged_matrix_is_group() {
696        // mismatched column counts mean the second row doesn't match, so it isn't a matrix
697        let expr = super::parse("[[a, b], [c]]");
698        let expected = [Group::from_iter(
699            "[",
700            [
701                Group::from_iter(
702                    "[",
703                    [Simple::Ident("a"), Simple::Symbol(","), Simple::Ident("b")],
704                    "]",
705                )
706                .into(),
707                Simple::Symbol(","),
708                Group::from_iter("[", [Simple::Ident("c")], "]").into(),
709            ],
710            "]",
711        )]
712        .into_iter()
713        .collect();
714        assert_eq!(expr, expected);
715    }
716
717    #[test]
718    fn single_row_matrix() {
719        // a single bracketed row with more than one column is still a matrix (total cells > 1)
720        let expr = super::parse("[[a, b]]");
721        let expected = [Matrix::new(
722            "[",
723            [
724                [Simple::Ident("a")].into_iter().collect(),
725                [Simple::Ident("b")].into_iter().collect(),
726            ],
727            2,
728            "]",
729        )]
730        .into_iter()
731        .collect();
732        assert_eq!(expr, expected);
733    }
734
735    #[test]
736    fn matrix_candidate_with_trailing_tokens_is_group() {
737        // a row followed by a non-separator token can't be a matrix, so it stays a group
738        let expr = super::parse("[[a] b]");
739        let expected = [Group::from_iter(
740            "[",
741            [
742                Group::from_iter("[", [Simple::Ident("a")], "]").into(),
743                Simple::Ident("b"),
744            ],
745            "]",
746        )]
747        .into_iter()
748        .collect();
749        assert_eq!(expr, expected);
750    }
751
752    #[test]
753    fn matrix_row_with_bar() {
754        // a "|" inside a matrix row is just a symbol in that cell; the rows still parse as a matrix
755        let expr = super::parse("[[a|b],[c|d]]");
756        let expected = [Matrix::new(
757            "[",
758            [
759                [Simple::Ident("a"), Simple::Symbol("|"), Simple::Ident("b")]
760                    .into_iter()
761                    .collect(),
762                [Simple::Ident("c"), Simple::Symbol("|"), Simple::Ident("d")]
763                    .into_iter()
764                    .collect(),
765            ],
766            1,
767            "]",
768        )]
769        .into_iter()
770        .collect();
771        assert_eq!(expr, expected);
772    }
773}