Skip to main content

pulldown_latex/
parser.rs

1//! Contains the [`Parser`], which Transforms input `LaTeX` into a stream of `Result<Event, ParserError>`.
2//!
3//! The parser is used as an iterator, and the events it generates can be rendered by a renderer.
4//! The `mahtml` renderer provided by this crate is available through [`push_mathml`] and [`write_mathml`].
5//!
6//! [`push_mathml`]: crate::mathml::push_mathml
7//! [`write_mathml`]: crate::mathml::write_mathml
8pub mod error;
9mod lex;
10mod macros;
11mod primitives;
12mod state;
13pub mod storage;
14mod tables;
15
16use std::ops::Range;
17
18use macros::MacroContext;
19
20use crate::event::{Event, Grouping, ScriptPosition, ScriptType};
21
22use self::{state::ParserState, storage::Storage};
23
24pub(crate) use error::{ErrorKind, InnerResult, ParserError};
25
26// Guard against infinite macro recursion and excessive
27// expansion. Each expansion allocates in the bump arena
28// which is never freed, so we limit both depth and total
29// expansion bytes to prevent OOM.
30const MAX_EXPANSION_DEPTH: usize = 64;
31const MAX_EXPANSION_BYTES: usize = 1024 * 1024; // 1 MB
32
33/// The parser completes the task of transforming the input `LaTeX` into a symbolic representation,
34/// namely a stream of [`Event`]s.
35///
36/// Transforming the events into rendered math is a task for the
37/// [`mahtml`](crate::mathml) renderer.
38///
39/// The algorithm of the [`Parser`] is driven by the [`Parser::next`] method.
40/// This method is provided through the [`Iterator`] trait implementation, thus an end user should
41/// only need to use the [`Parser`] as an iterator of `Result<Event, ParserError>`.
42#[derive(Debug)]
43pub struct Parser<'store> {
44    /// The next thing that should be parsed or outputed.
45    ///
46    /// When this is a string/substring, we should parse it. Some commands output
47    /// multiple events, so we need to keep track of them and ouput them in the next
48    /// iteration before continuing parsing.
49    ///
50    /// Instructions are stored backward in this stack, in the sense that the next event to be popped
51    /// is the next event to be outputed.
52    instruction_stack: Vec<Instruction<'store>>,
53
54    /// This buffer serves as a staging area when parsing a command.
55    ///
56    /// When a token is parsed, it is first pushed to this buffer, then scripts are checked
57    /// (superscript, and subscript), and then the events are moved from the buffer to the instruction stack.
58    buffer: Vec<Instruction<'store>>,
59
60    /// Macro definitions.
61    macro_context: MacroContext<'store>,
62
63    /// Where Macros are expanded if ever needed.
64    storage: &'store bumpalo::Bump,
65
66    /// A stack that serves to provide context when an error occurs.
67    span_stack: SpanStack<'store>,
68}
69
70impl<'store> Parser<'store> {
71    /// Create a new parser from the given input string and storage.
72    pub fn new<'input>(input: &'input str, storage: &'store Storage) -> Self
73    where
74        'input: 'store,
75    {
76        let mut instruction_stack = Vec::with_capacity(32);
77        instruction_stack.push(Instruction::SubGroup {
78            content: input,
79            allowed_alignment_count: None,
80        });
81        let buffer = Vec::with_capacity(16);
82        Self {
83            instruction_stack,
84            buffer,
85            macro_context: MacroContext::new(),
86            storage: &storage.0,
87            span_stack: SpanStack::from_input(input),
88        }
89    }
90}
91
92impl<'store> Iterator for Parser<'store> {
93    type Item = Result<Event<'store>, ParserError>;
94
95    fn next(&mut self) -> Option<Self::Item> {
96        match self.instruction_stack.last_mut() {
97            Some(Instruction::Event(_)) => Some(Ok(self
98                .instruction_stack
99                .pop()
100                .and_then(|i| match i {
101                    Instruction::Event(e) => Some(e),
102                    _ => None,
103                })
104                .expect("there is something in the stack"))),
105            Some(Instruction::SubGroup { content, .. }) if content.trim_start().is_empty() => {
106                self.instruction_stack.pop();
107                self.next()
108            }
109            Some(Instruction::SubGroup {
110                content,
111                allowed_alignment_count,
112                ..
113            }) => {
114                let state = ParserState {
115                    allowed_alignment_count: allowed_alignment_count.as_mut(),
116                    ..Default::default()
117                };
118
119                let inner = InnerParser {
120                    content,
121                    buffer: &mut self.buffer,
122                    state,
123                    macro_context: &mut self.macro_context,
124                    storage: self.storage,
125                    span_stack: &mut self.span_stack,
126                };
127
128                let (desc, rest) = inner.parse_next();
129                *content = rest;
130
131                let script_event = match desc {
132                    Err(e) => {
133                        let content_str = *content;
134                        return Some(Err(ParserError::new(
135                            e,
136                            content_str.as_ptr(),
137                            &mut self.span_stack,
138                        )));
139                    }
140                    Ok(Some((e, desc))) => {
141                        if desc.subscript_start > desc.superscript_start {
142                            let content = self.buffer.drain(desc.superscript_start..).rev();
143                            let added_len = content.len();
144
145                            self.instruction_stack.reserve(added_len);
146                            let spare =
147                                &mut self.instruction_stack.spare_capacity_mut()[..added_len];
148                            let mut idx = desc.subscript_start - desc.superscript_start;
149
150                            for e in content {
151                                if idx == added_len {
152                                    idx = 0;
153                                }
154                                spare[idx].write(e);
155                                idx += 1;
156                            }
157
158                            // Safety: The new length is less than the vector's capacity because we
159                            // reserved `added_len` previously. Every element in the vector up to
160                            // that new length is also initialized by the loop.
161                            unsafe {
162                                self.instruction_stack
163                                    .set_len(self.instruction_stack.len() + added_len)
164                            };
165                        } else {
166                            self.instruction_stack
167                                .extend(self.buffer.drain(desc.subscript_start..).rev());
168                        }
169                        Some(e)
170                    }
171                    Ok(None) => None,
172                };
173
174                self.instruction_stack.extend(self.buffer.drain(..).rev());
175                if let Some(e) = script_event {
176                    self.instruction_stack.push(Instruction::Event(e));
177                }
178                self.next()
179            }
180            None => None,
181        }
182    }
183}
184
185#[derive(Debug)]
186struct InnerParser<'b, 'store> {
187    content: &'store str,
188    buffer: &'b mut Vec<Instruction<'store>>,
189    state: ParserState<'b>,
190    macro_context: &'b mut MacroContext<'store>,
191    storage: &'store bumpalo::Bump,
192    span_stack: &'b mut SpanStack<'store>,
193}
194
195impl<'b, 'store> InnerParser<'b, 'store> {
196    /// Parse an arugment and pushes the argument to the stack surrounded by a
197    /// group: [..., EndGroup, Argument, BeginGroup], when the argument is a subgroup.
198    /// Otherwise, it pushes the argument to the stack ungrouped.
199    fn handle_argument(&mut self, argument: Argument<'store>) -> InnerResult<()> {
200        match argument {
201            Argument::Token(token) => {
202                self.state.handling_argument = true;
203                match token {
204                    Token::ControlSequence(cs) => self.handle_primitive(cs)?,
205                    Token::Character(c) => self.handle_char_token(c)?,
206                };
207            }
208            Argument::Group(group) => {
209                self.buffer.extend([
210                    Instruction::Event(Event::Begin(Grouping::Normal)),
211                    Instruction::SubGroup {
212                        content: group,
213                        allowed_alignment_count: None,
214                    },
215                    Instruction::Event(Event::End),
216                ]);
217            }
218        };
219        Ok(())
220    }
221
222    /// ## Script parsing
223    ///
224    /// The script parser first checks for directives about script placement, i.e. `\limits` and `\nolimits`,
225    /// if the `allow_script_modifiers` flag is set on the parser state. If the flag is set, and if more than one directive is found,
226    /// the last one takes effect, as per the [`amsmath docs`][amsdocs] (section 7.3). If the flag is not set, and a limit modifying
227    /// directive is found, the parser emits an error.
228    ///
229    /// [amsdocs]: https://mirror.its.dal.ca/ctan/macros/latex/required/amsmath/amsldoc.pdf
230    fn parse(&mut self) -> InnerResult<Option<(Event<'store>, ScriptDescriptor)>> {
231        // 1. Parse the next token and output everything to the staging stack.
232        let original_content = self.content.trim_start();
233        let token = match lex::token(&mut self.content) {
234            Ok(token) => token,
235            Err(ErrorKind::Token) => return Ok(None),
236            Err(e) => return Err(e),
237        };
238        match token {
239            Token::ControlSequence(cs) => {
240                if let Some(result) =
241                    self.macro_context
242                        .try_expand_in(cs, self.content, self.storage)
243                {
244                    if self.span_stack.expansions.len() >= MAX_EXPANSION_DEPTH
245                        || self.span_stack.total_expansion_bytes >= MAX_EXPANSION_BYTES
246                    {
247                        return Err(ErrorKind::MacroRecursionLimit);
248                    }
249
250                    let (new_content, arguments_consumed_length) = result?;
251                    self.span_stack.total_expansion_bytes += new_content.len();
252
253                    let call_site_length = cs.len() + arguments_consumed_length + 1;
254                    self.span_stack
255                        .add(new_content, original_content, call_site_length);
256
257                    self.content = new_content;
258                    return self.parse();
259                }
260
261                self.handle_primitive(cs)?
262            }
263            Token::Character(c) => self.handle_char_token(c)?,
264        };
265
266        // 2. Check for scripts, to complete the atom.
267        if self.state.skip_scripts {
268            return Ok(None);
269        }
270
271        if self.state.allow_script_modifiers {
272            if let Some(limits) = lex::limit_modifiers(&mut self.content) {
273                if limits {
274                    self.state.script_position = ScriptPosition::AboveBelow;
275                } else {
276                    self.state.script_position = ScriptPosition::Right;
277                }
278            }
279        }
280
281        self.content = self.content.trim_start();
282        let subscript_first = match self.content.chars().next() {
283            Some('^') => false,
284            Some('_') => true,
285            _ => return Ok(None),
286        };
287        self.content = &self.content[1..];
288
289        let first_script_start = self.buffer.len();
290        let arg = lex::argument(&mut self.content)?;
291        self.handle_argument(arg)?;
292        let second_script_start = self.buffer.len();
293        let next_char = self.content.chars().next();
294        if (next_char == Some('_') && !subscript_first)
295            || (next_char == Some('^') && subscript_first)
296        {
297            self.content = &self.content[1..];
298            let arg = lex::argument(&mut self.content)?;
299            self.handle_argument(arg)?;
300
301            match self.content.chars().next() {
302                Some('_') => return Err(ErrorKind::DoubleSubscript),
303                Some('^') => return Err(ErrorKind::DoubleSuperscript),
304                _ => {}
305            }
306        } else if next_char == Some('_') || next_char == Some('^') {
307            return Err(if subscript_first {
308                ErrorKind::DoubleSubscript
309            } else {
310                ErrorKind::DoubleSuperscript
311            });
312        }
313        let second_script_end = self.buffer.len();
314
315        Ok(Some(if second_script_start == second_script_end {
316            if subscript_first {
317                (
318                    Event::Script {
319                        ty: ScriptType::Subscript,
320                        position: self.state.script_position,
321                    },
322                    ScriptDescriptor {
323                        subscript_start: first_script_start,
324                        superscript_start: second_script_start,
325                    },
326                )
327            } else {
328                (
329                    Event::Script {
330                        ty: ScriptType::Superscript,
331                        position: self.state.script_position,
332                    },
333                    ScriptDescriptor {
334                        subscript_start: second_script_start,
335                        superscript_start: first_script_start,
336                    },
337                )
338            }
339        } else {
340            (
341                Event::Script {
342                    ty: ScriptType::SubSuperscript,
343                    position: self.state.script_position,
344                },
345                if subscript_first {
346                    ScriptDescriptor {
347                        subscript_start: first_script_start,
348                        superscript_start: second_script_start,
349                    }
350                } else {
351                    ScriptDescriptor {
352                        subscript_start: second_script_start,
353                        superscript_start: first_script_start,
354                    }
355                },
356            )
357        }))
358    }
359
360    fn parse_next(
361        mut self,
362    ) -> (
363        InnerResult<Option<(Event<'store>, ScriptDescriptor)>>,
364        &'store str,
365    ) {
366        (self.parse(), self.content)
367    }
368}
369
370struct ScriptDescriptor {
371    subscript_start: usize,
372    superscript_start: usize,
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
376pub(crate) enum Token<'a> {
377    ControlSequence(&'a str),
378    Character(CharToken<'a>),
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
382pub(crate) struct CharToken<'a> {
383    char: &'a str,
384}
385
386/// A verified character that retains the string context.
387impl<'a> CharToken<'a> {
388    fn from_str(s: &'a str) -> Self {
389        debug_assert!(
390            s.chars().next().is_some(),
391            "CharToken must be constructed from a non-empty string"
392        );
393        Self { char: s }
394    }
395
396    fn as_str(&self) -> &'a str {
397        self.char
398    }
399}
400
401impl From<CharToken<'_>> for char {
402    fn from(token: CharToken) -> char {
403        token.char.chars().next().unwrap()
404    }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
408enum Argument<'a> {
409    Token(Token<'a>),
410    Group(&'a str),
411}
412
413#[derive(Debug, Clone)]
414enum Instruction<'a> {
415    /// Send the event
416    Event(Event<'a>),
417    /// Parse the substring
418    SubGroup {
419        content: &'a str,
420        allowed_alignment_count: Option<AlignmentCount>,
421    },
422}
423
424#[derive(Debug, Clone)]
425struct AlignmentCount {
426    count: u16,
427    max: u16,
428}
429
430impl AlignmentCount {
431    fn new(max: u16) -> Self {
432        Self { count: 0, max }
433    }
434
435    fn reset(&mut self) {
436        self.count = 0;
437    }
438
439    fn increment(&mut self) {
440        self.count += 1;
441    }
442
443    fn can_increment(&self) -> bool {
444        self.count < self.max
445    }
446}
447
448/// For error reporting purposes.
449///
450/// Stores the context in which the parser is currently if an error were to arise.
451#[derive(Debug, Clone)]
452struct SpanStack<'store> {
453    /// The original input given to the parser.
454    input: &'store str,
455    /// Expansions of macros.
456    expansions: Vec<ExpansionSpan<'store>>,
457    /// Total bytes allocated by macro expansions (never decremented).
458    total_expansion_bytes: usize,
459}
460
461impl<'store> SpanStack<'store> {
462    fn from_input(input: &'store str) -> Self {
463        Self {
464            input,
465            expansions: Vec::new(),
466            total_expansion_bytes: 0,
467        }
468    }
469
470    fn add(&mut self, full_expansion: &'store str, call_site: &str, call_site_length: usize) {
471        let call_site_start = self.reach_original_call_site(call_site.as_ptr());
472        let expansion_length = (call_site_length as isize
473            - (call_site.len() as isize - full_expansion.len() as isize))
474            as usize;
475
476        self.expansions.push(ExpansionSpan {
477            full_expansion,
478            expansion_length,
479            call_site_in_origin: call_site_start..call_site_start + call_site_length,
480        });
481    }
482
483    /// Navigate down the stack until we reach the original span for the given substring. Returns
484    /// the index of the beginning of the call-site in the top-most span in the stack.
485    fn reach_original_call_site(&mut self, substr_start: *const u8) -> usize {
486        let ptr_val = substr_start as isize;
487
488        while let Some(expansion) = self.expansions.last() {
489            let expansion_ptr = expansion.full_expansion.as_ptr() as isize;
490
491            if ptr_val >= expansion_ptr
492                && ptr_val <= expansion_ptr + expansion.full_expansion.len() as isize
493            {
494                return (ptr_val - expansion_ptr) as usize;
495            }
496            self.expansions.pop();
497        }
498        let input_start = self.input.as_ptr() as isize;
499
500        assert!(ptr_val >= input_start && ptr_val <= input_start + self.input.len() as isize);
501        (ptr_val - input_start) as usize
502    }
503}
504
505/// A span of the input string. Used for error reporting.
506/// ```text
507///         full_expansion: [ -- Expanded --- | -- Rest -- ]
508///                        /                   \ < effective_expansion_stop
509///        [ -- Before -- | ---- Call Site ---- | -- Rest -- ]
510///                       ^---------------------^
511///                        declaration_in_origin
512/// ```
513#[derive(Debug, Clone)]
514struct ExpansionSpan<'a> {
515    /// The fully expaned string which is allocated in storage.
516    ///
517    /// This includes the expanded part and the included remaining.
518    full_expansion: &'a str,
519    /// The index where the expanded part ends and where the rest is equivalent to the rest of the
520    /// original string.
521    expansion_length: usize,
522    /// What the expansion replaces in the original string (where the macro invocation is in the
523    /// original string).
524    ///
525    /// The original string is the string coming before itself in the expansion stack.
526    call_site_in_origin: Range<usize>,
527}
528
529#[cfg(test)]
530mod tests {
531    use crate::event::{Content, DelimiterType, Dimension, DimensionUnit, RelationContent, Visual};
532
533    use super::*;
534
535    #[test]
536    fn substr_instructions() {
537        let store = Storage::new();
538        let parser = Parser::new("\\bar{y}", &store);
539
540        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
541
542        assert_eq!(
543            events,
544            vec![
545                Event::Script {
546                    ty: ScriptType::Superscript,
547                    position: ScriptPosition::AboveBelow
548                },
549                Event::Begin(Grouping::Normal),
550                Event::Content(Content::Ordinary {
551                    content: 'y',
552                    stretchy: false
553                }),
554                Event::End,
555                Event::Content(Content::Ordinary {
556                    content: '‾',
557                    stretchy: false,
558                }),
559            ]
560        );
561    }
562
563    #[test]
564    fn subsuperscript() {
565        let store = Storage::new();
566        let parser = Parser::new(r"a^{1+3}_2", &store);
567        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
568
569        assert_eq!(
570            events,
571            vec![
572                Event::Script {
573                    ty: ScriptType::SubSuperscript,
574                    position: ScriptPosition::Right
575                },
576                Event::Content(Content::Ordinary {
577                    content: 'a',
578                    stretchy: false,
579                }),
580                Event::Content(Content::Number("2")),
581                Event::Begin(Grouping::Normal),
582                Event::Content(Content::Number("1")),
583                Event::Content(Content::BinaryOp {
584                    content: '+',
585                    small: false
586                }),
587                Event::Content(Content::Number("3")),
588                Event::End,
589            ]
590        );
591    }
592    #[test]
593    fn subscript_torture() {
594        let store = Storage::new();
595        let parser = Parser::new(r"a_{5_{5_{5_{5_{5_{5_{5_{5_{5_{5_{5_5}}}}}}}}}}}", &store);
596        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
597
598        assert_eq!(
599            events,
600            vec![
601                Event::Script {
602                    ty: ScriptType::Subscript,
603                    position: ScriptPosition::Right
604                },
605                Event::Content(Content::Ordinary {
606                    content: 'a',
607                    stretchy: false,
608                }),
609                Event::Begin(Grouping::Normal),
610                Event::Script {
611                    ty: ScriptType::Subscript,
612                    position: ScriptPosition::Right
613                },
614                Event::Content(Content::Number("5")),
615                Event::Begin(Grouping::Normal),
616                Event::Script {
617                    ty: ScriptType::Subscript,
618                    position: ScriptPosition::Right
619                },
620                Event::Content(Content::Number("5")),
621                Event::Begin(Grouping::Normal),
622                Event::Script {
623                    ty: ScriptType::Subscript,
624                    position: ScriptPosition::Right
625                },
626                Event::Content(Content::Number("5")),
627                Event::Begin(Grouping::Normal),
628                Event::Script {
629                    ty: ScriptType::Subscript,
630                    position: ScriptPosition::Right
631                },
632                Event::Content(Content::Number("5")),
633                Event::Begin(Grouping::Normal),
634                Event::Script {
635                    ty: ScriptType::Subscript,
636                    position: ScriptPosition::Right
637                },
638                Event::Content(Content::Number("5")),
639                Event::Begin(Grouping::Normal),
640                Event::Script {
641                    ty: ScriptType::Subscript,
642                    position: ScriptPosition::Right
643                },
644                Event::Content(Content::Number("5")),
645                Event::Begin(Grouping::Normal),
646                Event::Script {
647                    ty: ScriptType::Subscript,
648                    position: ScriptPosition::Right
649                },
650                Event::Content(Content::Number("5")),
651                Event::Begin(Grouping::Normal),
652                Event::Script {
653                    ty: ScriptType::Subscript,
654                    position: ScriptPosition::Right
655                },
656                Event::Content(Content::Number("5")),
657                Event::Begin(Grouping::Normal),
658                Event::Script {
659                    ty: ScriptType::Subscript,
660                    position: ScriptPosition::Right
661                },
662                Event::Content(Content::Number("5")),
663                Event::Begin(Grouping::Normal),
664                Event::Script {
665                    ty: ScriptType::Subscript,
666                    position: ScriptPosition::Right
667                },
668                Event::Content(Content::Number("5")),
669                Event::Begin(Grouping::Normal),
670                Event::Script {
671                    ty: ScriptType::Subscript,
672                    position: ScriptPosition::Right
673                },
674                Event::Content(Content::Number("5")),
675                Event::Content(Content::Number("5")),
676                Event::End,
677                Event::End,
678                Event::End,
679                Event::End,
680                Event::End,
681                Event::End,
682                Event::End,
683                Event::End,
684                Event::End,
685                Event::End,
686                Event::End,
687            ]
688        )
689    }
690
691    #[test]
692    fn fraction() {
693        let store = Storage::new();
694        let parser = Parser::new(r"\frac{1}{2}_2^4", &store);
695        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
696
697        assert_eq!(
698            events,
699            vec![
700                Event::Script {
701                    ty: ScriptType::SubSuperscript,
702                    position: ScriptPosition::Right
703                },
704                Event::Visual(Visual::Fraction(None)),
705                Event::Begin(Grouping::Normal),
706                Event::Content(Content::Number("1")),
707                Event::End,
708                Event::Begin(Grouping::Normal),
709                Event::Content(Content::Number("2")),
710                Event::End,
711                Event::Content(Content::Number("2")),
712                Event::Content(Content::Number("4")),
713            ]
714        );
715    }
716
717    #[test]
718    fn multidigit_number() {
719        let store = Storage::new();
720        let parser = Parser::new("123", &store);
721        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
722
723        assert_eq!(events, vec![Event::Content(Content::Number("123"))]);
724    }
725
726    #[test]
727    fn non_greedy_number_argument() {
728        // arguments to commands without braces should only consume
729        // a single token, not greedily consume multiple digits. `\frac12`
730        // must parse as `\frac{1}{2}`, not `\frac{12}{}`.
731        let store = Storage::new();
732        let parser = Parser::new(r"\frac12", &store);
733        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
734
735        assert_eq!(
736            events,
737            vec![
738                Event::Visual(Visual::Fraction(None)),
739                Event::Content(Content::Number("1")),
740                Event::Content(Content::Number("2")),
741            ]
742        );
743    }
744
745    #[test]
746    fn non_greedy_number_argument_sqrt() {
747        // `\sqrt12` must parse as `\sqrt{1}` followed by `2`, not `\sqrt{12}`.
748        let store = Storage::new();
749        let parser = Parser::new(r"\sqrt12", &store);
750        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
751
752        assert_eq!(
753            events,
754            vec![
755                Event::Visual(Visual::SquareRoot),
756                Event::Content(Content::Number("1")),
757                Event::Content(Content::Number("2")),
758            ]
759        );
760    }
761
762    #[test]
763    fn non_greedy_decimal_argument() {
764        // The greedy-digit path also consumed `.` and `,`. As an argument,
765        // only the first digit should be taken; the `.` and following digits
766        // remain in the outer stream.
767        let store = Storage::new();
768        let parser = Parser::new(r"\frac1.5{x}", &store);
769        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
770
771        assert_eq!(
772            events,
773            vec![
774                Event::Visual(Visual::Fraction(None)),
775                Event::Content(Content::Number("1")),
776                Event::Content(Content::Punctuation('.')),
777                Event::Content(Content::Number("5")),
778                Event::Begin(Grouping::Normal),
779                Event::Content(Content::Ordinary {
780                    content: 'x',
781                    stretchy: false
782                }),
783                Event::End,
784            ]
785        );
786    }
787
788    #[test]
789    fn error() {
790        let store = Storage::new();
791        let parser = Parser::new(
792            r"\def\blah#1#2{\fra#1#2} \def\abc#1{\blah{a}#1} \abc{b}",
793            &store,
794        );
795        let events = parser.collect::<Vec<_>>();
796
797        assert!(events[0].is_err());
798    }
799
800    #[test]
801    fn no_limits() {
802        let store = Storage::new();
803        let parser = Parser::new(r#"\lim \nolimits _{x \to 0} f(x)"#, &store);
804        let events = parser.collect::<Result<Vec<_>, ParserError>>().unwrap();
805        assert_eq!(
806            events,
807            vec![
808                Event::Script {
809                    ty: ScriptType::Subscript,
810                    position: ScriptPosition::Right
811                },
812                Event::Content(Content::Function("lim")),
813                Event::Begin(Grouping::Normal),
814                Event::Content(Content::Ordinary {
815                    content: 'x',
816                    stretchy: false
817                }),
818                Event::Content(Content::Relation {
819                    content: RelationContent::single_char('→'),
820                    small: false
821                }),
822                Event::Content(Content::Number("0")),
823                Event::End,
824                Event::Content(Content::Ordinary {
825                    content: 'f',
826                    stretchy: false
827                }),
828                Event::Content(Content::Delimiter {
829                    content: '(',
830                    size: None,
831                    ty: DelimiterType::Open
832                }),
833                Event::Content(Content::Ordinary {
834                    content: 'x',
835                    stretchy: false
836                }),
837                Event::Content(Content::Delimiter {
838                    content: ')',
839                    size: None,
840                    ty: DelimiterType::Close
841                }),
842            ]
843        );
844    }
845
846    #[test]
847    fn struts() {
848        let store = Storage::new();
849
850        for (input, height) in [(r"\mathstrut", 0.7), (r"\strut", 1.0)] {
851            let events = Parser::new(input, &store)
852                .collect::<Result<Vec<_>, ParserError>>()
853                .unwrap();
854
855            assert_eq!(
856                events,
857                vec![Event::Space {
858                    width: None,
859                    height: Some(Dimension::new(height, DimensionUnit::Em)),
860                    depth: None,
861                }]
862            );
863        }
864    }
865
866    #[test]
867    fn expansions_in_groups() {
868        let store = Storage::new();
869        let mut parser = Parser::new(
870            r"\def\abc#1{#1} {\abc{a} + \abc{b}} = c \shoulderror",
871            &store,
872        );
873        assert!(parser.by_ref().collect::<Result<Vec<_>, _>>().is_err());
874        assert!(parser.span_stack.expansions.is_empty());
875    }
876}
877
878// Token parsing procedure, as per TeXbook p. 46-47.
879//
880// This is roughly what the lexer implementation will look like for text mode.
881//
882// 1. Trim any trailing whitespace from a line.
883//
884// 2. If '\' (escape character) is encountered, parse the next token.
885//  'is_ascii_alphabetic' => parse until an non ASCII alphabetic, and the name is the token
886//  '\n' => _The name is empty_???
887//  'otherwise' => parse next character, and the name is the symbol.
888//
889//  Go to SkipBlanks mode if the token is a word or a space symbol.
890//  Otherwise, go to MidLine mode.
891//
892// 3. If `^^` is found:
893//  - If the following are two characters of type ASCII lowercase letter or digit,
894//  then `^^__` is converted to the correspoding ascii value.
895//  - If the following is a single ASCII character, then `^^_` is converted to the corresponding ASCII
896//  value with the formula: if `c` is the character, then `c + 64` if `c` if the character has code
897//  between 0 and 63, and `c - 64` if the character has code between 64 and 127.
898//
899//  __Note__: This rule takes precedence over escape character parsing. If such a sequence is found
900//  in an escape sequence, it is converted to the corresponding ASCII value.
901//
902// 4. If the token is a single character, go to MidLine mode.
903//
904// 5. If the token is an end of line, go to the next line. If nothing was on the line (were in NewLine state), then the
905//  `par` token is emitted, meaning that a new paragraph should be started.
906//  If the state was MidLine, then the newline is transformed into a space.
907//  If the state was SkipBlanks, then the newline is ignored.
908//
909// 6. Ignore characters from the `Ignore` category.
910//
911// 7. If the token is a space and the mode is MidLine, the space is transformed into a space token.
912//
913// 8. If the token is a comment, ignore the rest of the line, and go to the next line.
914//
915// 9. Go to newlines on the next line.