Skip to main content

katex_parser/
parser.rs

1use std::rc::Rc;
2
3use crate::ast::{AtomFamily, ColumnSeparationType, Measurement, Mode, ParseNode, StyleLevel};
4use crate::environments::{
5    build_environment_registry, cd_row, EnvironmentContext, EnvironmentParser,
6    EnvironmentRegistry, EnvironmentSpec, ArrayEnvironmentOptions,
7};
8use crate::error::{Diagnostic, ParseError};
9use crate::function_registry::{
10    build_function_registry, ArgType, FunctionContext, FunctionParser, FunctionRegistry,
11    FunctionSpec,
12};
13use crate::functions::{parse_size_measurement, size_scan_candidate, valid_size_unit};
14use crate::lexer::{is_ascii_alphabetic, starts_with_at};
15use crate::macro_definition::MacroDefinition;
16use crate::macro_expander::{is_implicit_command, ExternalCommandStatus, MacroExpander};
17use crate::settings::{Settings, TrustContext};
18use crate::source_location::SourceLocation;
19use crate::symbol_registry::{is_registered_symbol, lookup_symbol, SymbolGroup, SymbolSpec};
20use crate::text_ligature::form_text_ligatures;
21use crate::token::Token;
22use crate::unicode_scripts::{lookup_unicode_script, supported_codepoint, UnicodeScriptKind};
23use crate::unicode_symbols::{
24    normalize_unicode_symbol, trailing_combining_mark_start, unicode_accent_command,
25};
26
27pub struct Parser {
28    mode: Mode,
29    gullet: MacroExpander,
30    settings: Settings,
31    function_registry: FunctionRegistry,
32    environment_registry: EnvironmentRegistry,
33    next_token: Option<Token>,
34    leftright_depth: usize,
35}
36
37enum AtomResult {
38    EmitAtom(ParseNode),
39    SkipAtom,
40}
41
42impl Parser {
43    pub(crate) fn new(
44        input: &str,
45        settings: Settings,
46        extra_specs: &[FunctionSpec],
47        extra_env_specs: &[EnvironmentSpec],
48    ) -> Parser {
49        let registry = build_function_registry(extra_specs);
50        let command_registry = registry.clone();
51        let settings_for_reporter = settings.clone();
52        let gullet = MacroExpander::new(
53            input,
54            settings.clone(),
55            Rc::new(move |error_code, error_message| {
56                settings_for_reporter.report_nonstrict(error_code, error_message, None)
57            }),
58            Rc::new(move |name| match command_registry.get(name) {
59                Some(spec) => {
60                    if spec.is_expandable() {
61                        ExternalCommandStatus::ExternalExpandable
62                    } else {
63                        ExternalCommandStatus::ExternalUnexpandable
64                    }
65                }
66                None => {
67                    if is_registered_symbol(name) {
68                        ExternalCommandStatus::ExternalUnexpandable
69                    } else {
70                        ExternalCommandStatus::ExternalUndefined
71                    }
72                }
73            }),
74        );
75        Parser {
76            mode: Mode::Math,
77            gullet,
78            settings,
79            function_registry: registry,
80            environment_registry: build_environment_registry(extra_env_specs),
81            next_token: None,
82            leftright_depth: 0,
83        }
84    }
85
86    fn fetch(&mut self) -> Result<Token, ParseError> {
87        match &self.next_token {
88            Some(token) => Ok(token.clone()),
89            None => {
90                let token = self.gullet.expand_next_token()?;
91                self.next_token = Some(token.clone());
92                Ok(token)
93            }
94        }
95    }
96
97    fn consume(&mut self) {
98        self.next_token = None;
99    }
100
101    fn expect(&mut self, text: &str, consume: bool) -> Result<(), ParseError> {
102        let token = self.fetch()?;
103        if token.text != text {
104            return Err(ParseError::ExpectedToken {
105                expected: text.to_string(),
106                actual: Diagnostic::from_token(&token),
107            });
108        }
109        if consume {
110            self.consume();
111        }
112        Ok(())
113    }
114
115    fn parse(&mut self) -> Result<Vec<ParseNode>, ParseError> {
116        if !self.settings.global_group {
117            self.gullet.begin_group();
118        }
119        if self.settings.color_is_text_color {
120            self.gullet.macros.set(
121                "\\color".to_string(),
122                Some(MacroDefinition::text("\\textcolor")),
123                false,
124            );
125        }
126        let result: Result<Vec<ParseNode>, ParseError> = (|| {
127            let body = self.parse_expression(false, None)?;
128            self.expect("EOF", true)?;
129            Ok(body)
130        })();
131        let close_result = if self.settings.global_group {
132            Ok(())
133        } else {
134            self.gullet.end_group()
135        };
136        self.gullet.end_groups();
137        if self.settings.global_group {
138            self.persist_user_macros();
139        }
140        unwrap_captured(close_result, result)
141    }
142
143    fn persist_user_macros(&mut self) {
144        let user_entries = self.gullet.macros.get_user_entries();
145        if let Some(macros) = &mut self.settings.macro_store {
146            for (name, definition) in user_entries {
147                macros.0.insert(name, definition.clone());
148            }
149        }
150    }
151
152    fn subparse(&mut self, tokens: Vec<Token>) -> Result<Vec<ParseNode>, ParseError> {
153        let old_token = self.next_token.clone();
154        self.consume();
155        self.gullet.push_token(Token::new("}", None));
156        self.gullet.push_tokens(tokens);
157        let result = self.subparse_inner();
158        self.next_token = old_token;
159        result
160    }
161
162    fn subparse_inner(&mut self) -> Result<Vec<ParseNode>, ParseError> {
163        let body = self.parse_expression(false, Some("}"))?;
164        self.expect("}", true)?;
165        Ok(body)
166    }
167
168    fn parse_math_mode(&mut self, close: &str) -> Result<Vec<ParseNode>, ParseError> {
169        let outer_mode = self.mode;
170        self.switch_mode(Mode::Math);
171        let result = self.parse_math_mode_inner(close);
172        self.switch_mode(outer_mode);
173        result
174    }
175
176    fn parse_math_mode_inner(&mut self, close: &str) -> Result<Vec<ParseNode>, ParseError> {
177        let body = self.parse_expression(false, Some(close))?;
178        self.expect(close, true)?;
179        Ok(body)
180    }
181
182    fn current_color(&self) -> Result<Option<String>, ParseError> {
183        match self.gullet.macros.get("\\current@color") {
184            None => Ok(None),
185            Some(MacroDefinition::Text(color)) => Ok(Some(color.clone())),
186            Some(MacroDefinition::Expansion(_)) => Err(ParseError::InvalidArgument {
187                message: "\\current@color set to non-string in \\right".to_string(),
188                loc: None,
189            }),
190        }
191    }
192
193    fn parse_left_right(&mut self, left: &str) -> Result<ParseNode, ParseError> {
194        self.leftright_depth += 1;
195        let result = self.parse_left_right_inner(left);
196        self.leftright_depth -= 1;
197        result
198    }
199
200    fn parse_left_right_inner(&mut self, left: &str) -> Result<ParseNode, ParseError> {
201        let body = self.parse_expression(false, None)?;
202        self.expect("\\right", false)?;
203        let Some(AtomResult::EmitAtom(ParseNode::LeftRightRight {
204            delim: right,
205            color,
206            ..
207        })) = self.parse_function(None, None)?
208        else {
209            return Err(ParseError::InternalInvariant {
210                message: "\\right did not produce a closing delimiter".to_string(),
211            });
212        };
213        Ok(ParseNode::LeftRight {
214            mode: self.mode,
215            body,
216            left: left.to_string(),
217            right,
218            right_color: color,
219        })
220    }
221
222    fn parse_expression(
223        &mut self,
224        break_on_infix: bool,
225        break_on_token_text: Option<&str>,
226    ) -> Result<Vec<ParseNode>, ParseError> {
227        let mut body: Vec<ParseNode> = Vec::new();
228        loop {
229            if self.mode == Mode::Math {
230                self.consume_spaces()?;
231            }
232            let token = self.fetch()?;
233            if self.should_break_expression(&token.text, break_on_infix, break_on_token_text) {
234                return self.finish_expression(body);
235            }
236            match self.parse_atom(break_on_token_text)? {
237                None => return self.finish_expression(body),
238                Some(AtomResult::SkipAtom) => continue,
239                Some(AtomResult::EmitAtom(node)) => {
240                    body.push(node);
241                    continue;
242                }
243            }
244        }
245    }
246
247    fn should_break_expression(
248        &self,
249        text: &str,
250        break_on_infix: bool,
251        break_on_token_text: Option<&str>,
252    ) -> bool {
253        is_end_of_expression(text)
254            || (break_on_token_text.is_some_and(|stop| text == stop))
255            || (break_on_infix
256                && self
257                    .function_registry
258                    .get(text)
259                    .is_some_and(|spec| spec.infix))
260    }
261
262    fn finish_expression(&mut self, body: Vec<ParseNode>) -> Result<Vec<ParseNode>, ParseError> {
263        let normalized = if self.mode == Mode::Text {
264            form_text_ligatures(body)
265        } else {
266            body
267        };
268        self.handle_infix_nodes(normalized)
269    }
270
271    fn consume_spaces(&mut self) -> Result<(), ParseError> {
272        loop {
273            if self.fetch()?.text != " " {
274                break;
275            }
276            self.consume();
277        }
278        Ok(())
279    }
280
281    fn parse_atom(&mut self, break_on_token_text: Option<&str>) -> Result<Option<AtomResult>, ParseError> {
282        match self.parse_group("atom", break_on_token_text)? {
283            None => Ok(None),
284            Some(AtomResult::SkipAtom) => Ok(Some(AtomResult::SkipAtom)),
285            Some(AtomResult::EmitAtom(ParseNode::Internal { .. })) => Ok(Some(AtomResult::SkipAtom)),
286            Some(AtomResult::EmitAtom(base)) if self.mode == Mode::Text => {
287                Ok(Some(AtomResult::EmitAtom(base)))
288            }
289            Some(AtomResult::EmitAtom(base)) => {
290                Ok(Some(AtomResult::EmitAtom(self.parse_scripts(base)?)))
291            }
292        }
293    }
294
295    fn parse_scripts(&mut self, base: ParseNode) -> Result<ParseNode, ParseError> {
296        let mut base = base;
297        let mut sup: Option<ParseNode> = None;
298        let mut sub: Option<ParseNode> = None;
299        loop {
300            self.consume_spaces()?;
301            let token = self.fetch()?;
302            if token.text == "\\limits" || token.text == "\\nolimits" {
303                base = set_limits(base, token.text == "\\limits", token.loc.clone())?;
304                self.consume();
305                continue;
306            } else if token.text == "^" {
307                if sup.is_some() {
308                    return Err(ParseError::DoubleSuperscript { loc: token.loc.clone() });
309                }
310                sup = Some(self.handle_sup_subscript("superscript")?);
311                continue;
312            } else if token.text == "_" {
313                if sub.is_some() {
314                    return Err(ParseError::DoubleSubscript { loc: token.loc.clone() });
315                }
316                sub = Some(self.handle_sup_subscript("subscript")?);
317                continue;
318            } else if token.text == "'" {
319                if sup.is_some() {
320                    return Err(ParseError::DoubleSuperscript { loc: token.loc.clone() });
321                }
322                sup = Some(self.parse_prime_run()?);
323                continue;
324            } else {
325                match lookup_unicode_script(&token.text) {
326                    None => return Ok(make_supsub_or_base(self.mode, base, sup, sub)),
327                    Some(first_script) => {
328                        let (is_subscript, script_tokens) =
329                            self.consume_unicode_script_run(first_script)?;
330                        let body = self.subparse(script_tokens)?;
331                        let group = ParseNode::OrdGroup {
332                            mode: Mode::Math,
333                            loc: None,
334                            body,
335                            semisimple: false,
336                        };
337                        if is_subscript {
338                            sub = Some(group);
339                        } else {
340                            sup = Some(group);
341                        }
342                        continue;
343                    }
344                }
345            }
346        }
347    }
348
349    fn parse_prime_run(&mut self) -> Result<ParseNode, ParseError> {
350        let mut primes: Vec<ParseNode> = Vec::new();
351        while self.fetch()?.text == "'" {
352            let prime_token = self.fetch()?;
353            primes.push(ParseNode::TextOrd {
354                mode: self.mode,
355                loc: prime_token.loc.clone(),
356                text: "\\prime".to_string(),
357            });
358            self.consume();
359        }
360        if self.fetch()?.text == "^" {
361            primes.push(self.handle_sup_subscript("superscript")?);
362        }
363        Ok(ParseNode::OrdGroup {
364            mode: self.mode,
365            loc: None,
366            body: primes,
367            semisimple: false,
368        })
369    }
370
371    fn consume_unicode_script_run(
372        &mut self,
373        first: &crate::unicode_scripts::UnicodeScript,
374    ) -> Result<(bool, Vec<Token>), ParseError> {
375        let is_subscript = first.kind == UnicodeScriptKind::UnicodeSubscript;
376        let mut tokens: Vec<Token> = vec![Token::new(first.replacement.clone(), None)];
377        self.consume();
378        loop {
379            let next = self.fetch()?;
380            match lookup_unicode_script(&next.text) {
381                Some(script)
382                    if (script.kind == UnicodeScriptKind::UnicodeSubscript) == is_subscript =>
383                {
384                    tokens.push(Token::new(script.replacement.clone(), None));
385                    self.consume();
386                    continue;
387                }
388                _ => {
389                    tokens.reverse();
390                    return Ok((is_subscript, tokens));
391                }
392            }
393        }
394    }
395
396    fn handle_sup_subscript(&mut self, name: &str) -> Result<ParseNode, ParseError> {
397        let token = self.fetch()?;
398        self.consume();
399        self.consume_spaces()?;
400        loop {
401            match self.parse_group(name, None)? {
402                Some(AtomResult::EmitAtom(ParseNode::Internal { .. })) | Some(AtomResult::SkipAtom) => {
403                    continue;
404                }
405                Some(AtomResult::EmitAtom(group)) => return Ok(group),
406                None => {
407                    return Err(ParseError::ExpectedGroupAfter {
408                        symbol: token.text.clone(),
409                        loc: token.loc.clone(),
410                    })
411                }
412            }
413        }
414    }
415
416    fn parse_group(
417        &mut self,
418        name: &str,
419        break_on_token_text: Option<&str>,
420    ) -> Result<Option<AtomResult>, ParseError> {
421        let first_token = self.fetch()?;
422        let text = first_token.text.clone();
423        if text == "{" || text == "\\begingroup" {
424            Ok(Some(AtomResult::EmitAtom(
425                self.parse_group_body(&first_token, &text)?,
426            )))
427        } else {
428            match self.parse_function(break_on_token_text, Some(name))? {
429                Some(result) => Ok(Some(result)),
430                None => match self.parse_symbol()? {
431                    Some(node) => Ok(Some(AtomResult::EmitAtom(node))),
432                    None => self.handle_undefined_control(&first_token),
433                },
434            }
435        }
436    }
437
438    fn parse_group_body(&mut self, first_token: &Token, text: &str) -> Result<ParseNode, ParseError> {
439        self.consume();
440        let group_end = if text == "{" { "}" } else { "\\endgroup" };
441        self.gullet.begin_group();
442        let body = self.parse_expression(false, Some(group_end))?;
443        let last = self.fetch()?;
444        self.expect(group_end, true)?;
445        self.gullet.end_group()?;
446        let loc = match (&first_token.loc, &last.loc) {
447            (Some(start_loc), Some(end_loc)) => Some(SourceLocation::range(start_loc, end_loc)),
448            _ => None,
449        };
450        Ok(ParseNode::OrdGroup {
451            mode: self.mode,
452            loc,
453            body,
454            semisimple: text == "\\begingroup",
455        })
456    }
457
458    fn handle_undefined_control(
459        &mut self,
460        token: &Token,
461    ) -> Result<Option<AtomResult>, ParseError> {
462        let text = token.text.clone();
463        if !is_undefined_control_sequence(&text) {
464            return Ok(None);
465        }
466        if !self.settings.throw_on_error {
467            self.consume();
468            return Ok(Some(AtomResult::EmitAtom(format_unsupported_command(
469                self.mode,
470                &self.settings,
471                &text,
472            ))));
473        }
474        Err(ParseError::UndefinedControlSequence {
475            name: text,
476            loc: token.loc.clone(),
477        })
478    }
479
480    fn parse_function(
481        &mut self,
482        break_on_token_text: Option<&str>,
483        name: Option<&str>,
484    ) -> Result<Option<AtomResult>, ParseError> {
485        let token = self.fetch()?;
486        let func_data = match self.function_registry.get(&token.text) {
487            None => return Ok(None),
488            Some(fd) => fd.clone(),
489        };
490        self.consume();
491        if let Some(context_name) = name
492            && context_name != "atom" && !func_data.allowed_in_argument {
493                return Err(ParseError::FunctionNotAllowed {
494                    func_name: token.text.clone(),
495                    context: context_name.to_string(),
496                    loc: token.loc.clone(),
497                });
498            }
499        if self.mode == Mode::Text && !func_data.allowed_in_text {
500            return Err(ParseError::FunctionNotAllowed {
501                func_name: token.text.clone(),
502                context: "text mode".to_string(),
503                loc: token.loc.clone(),
504            });
505        }
506        if self.mode == Mode::Math && !func_data.allowed_in_math {
507            return Err(ParseError::FunctionNotAllowed {
508                func_name: token.text.clone(),
509                context: "math mode".to_string(),
510                loc: token.loc.clone(),
511            });
512        }
513        let (args, opt_args) = self.parse_arguments(&token.text, &func_data)?;
514        Ok(Some(AtomResult::EmitAtom(self.call_function(
515            &token.text,
516            args,
517            opt_args,
518            Some(token.clone()),
519            break_on_token_text.map(|s| s.to_string()),
520        )?)))
521    }
522
523    fn call_function(
524        &mut self,
525        func_name: &str,
526        args: Vec<ParseNode>,
527        opt_args: Vec<Option<ParseNode>>,
528        token: Option<Token>,
529        break_on_token_text: Option<String>,
530    ) -> Result<ParseNode, ParseError> {
531        let context = FunctionContext {
532            func_name: func_name.to_string(),
533            mode: self.mode,
534            token: token.clone(),
535            break_on_token_text: break_on_token_text.clone(),
536            display_mode: self.settings.display_mode,
537        };
538        let handler = {
539            let spec = self.function_registry.get(func_name).ok_or_else(|| {
540                ParseError::MissingFunctionHandler {
541                    func_name: func_name.to_string(),
542                    loc: None,
543                }
544            })?;
545            spec.handler.ok_or_else(|| ParseError::MissingFunctionHandler {
546                func_name: func_name.to_string(),
547                loc: None,
548            })?
549        };
550        handler(self, &context, &args, &opt_args)
551    }
552
553    fn parse_arguments(
554        &mut self,
555        func: &str,
556        func_data: &FunctionSpec,
557    ) -> Result<(Vec<ParseNode>, Vec<Option<ParseNode>>), ParseError> {
558        let mut args: Vec<ParseNode> = Vec::new();
559        let mut opt_args: Vec<Option<ParseNode>> = Vec::new();
560        let total_args = func_data.num_args + func_data.num_optional_args;
561        let mut index = 0;
562        while index < total_args {
563            let optional = index < func_data.num_optional_args;
564            let arg_type = match func_data.arg_types.get(index) {
565                Some(kind) => *kind,
566                None if func_data.primitive => ArgType::PrimitiveArg,
567                None
568                    if func_data
569                        .primitive_after_missing_optional
570                        .is_some_and(|optional_index| {
571                            index == func_data.num_optional_args
572                                && opt_args
573                                    .get(optional_index)
574                                    .is_some_and(|value| value.is_none())
575                        }) =>
576                {
577                    ArgType::PrimitiveArg
578                }
579                None => ArgType::OriginalArg,
580            };
581            match self.parse_group_of_type(&format!("argument to '{func}'"), arg_type, optional)? {
582                None if optional => {
583                    opt_args.push(None);
584                    index += 1;
585                    continue;
586                }
587                None => {
588                    return Err(ParseError::InternalInvariant {
589                        message: "Null mandatory function argument after parser validation"
590                            .to_string(),
591                    })
592                }
593                Some(arg) if optional => {
594                    opt_args.push(Some(arg));
595                    index += 1;
596                    continue;
597                }
598                Some(arg) => {
599                    args.push(arg);
600                    index += 1;
601                    continue;
602                }
603            }
604        }
605        Ok((args, opt_args))
606    }
607
608    fn parse_group_of_type(
609        &mut self,
610        name: &str,
611        arg_type: ArgType,
612        optional: bool,
613    ) -> Result<Option<ParseNode>, ParseError> {
614        match arg_type {
615            ArgType::ColorArg => self.parse_color_group(optional),
616            ArgType::SizeArg => self.parse_size_group(optional),
617            ArgType::UrlArg => self.parse_url_group(optional),
618            ArgType::RawArg => Ok(self
619                .parse_string_group(optional)?
620                .map(|token| ParseNode::Raw {
621                    mode: Mode::Text,
622                    string: token.text,
623                })),
624            ArgType::MathArg => self.parse_argument_group(optional, Some(Mode::Math)),
625            ArgType::TextArg => self.parse_argument_group(optional, Some(Mode::Text)),
626            ArgType::HboxArg => Ok(self.parse_argument_group(optional, Some(Mode::Text))?.map(
627                |group| ParseNode::Styling {
628                    mode: group.mode(),
629                    body: vec![group],
630                    style: StyleLevel::TextStyle,
631                    reset_font: true,
632                },
633            )),
634            ArgType::PrimitiveArg => self.parse_primitive_group(name, optional),
635            ArgType::OriginalArg => self.parse_argument_group(optional, None),
636        }
637    }
638
639    fn parse_primitive_group(&mut self, name: &str, optional: bool) -> Result<Option<ParseNode>, ParseError> {
640        if optional {
641            return Err(ParseError::InvalidArgument {
642                message: "A primitive argument cannot be optional".to_string(),
643                loc: None,
644            });
645        }
646        let Some(AtomResult::EmitAtom(group)) = self.parse_group(name, None)? else {
647            let token = self.fetch()?;
648            return Err(ParseError::InvalidArgument {
649                message: format!("Expected group as {name}"),
650                loc: token.loc,
651            });
652        };
653        Ok(Some(group))
654    }
655
656    fn parse_argument_group(
657        &mut self,
658        optional: bool,
659        mode: Option<Mode>,
660    ) -> Result<Option<ParseNode>, ParseError> {
661        match self.gullet.scan_argument(optional)? {
662            None => Ok(None),
663            Some(arg_token) => {
664                let outer_mode = self.mode;
665                if let Some(argument_mode) = mode {
666                    self.switch_mode(argument_mode);
667                }
668                self.gullet.begin_group();
669                let result: Result<ParseNode, ParseError> = (|| {
670                    let body = self.parse_expression(false, Some("EOF"))?;
671                    self.expect("EOF", true)?;
672                    Ok(ParseNode::OrdGroup {
673                        mode: self.mode,
674                        loc: arg_token.loc.clone(),
675                        body,
676                        semisimple: false,
677                    })
678                })();
679                let close_result = self.gullet.end_group();
680                self.switch_mode(outer_mode);
681                Ok(Some(unwrap_captured(close_result, result)?))
682            }
683        }
684    }
685
686    fn parse_string_group(&mut self, optional: bool) -> Result<Option<Token>, ParseError> {
687        match self.gullet.scan_argument(optional)? {
688            None => Ok(None),
689            Some(mut arg_token) => {
690                let mut builder = String::new();
691                loop {
692                    let token = self.fetch()?;
693                    if token.text == "EOF" {
694                        self.consume();
695                        arg_token.text = builder;
696                        break Ok(Some(arg_token));
697                    }
698                    builder.push_str(&token.text);
699                    self.consume();
700                }
701            }
702        }
703    }
704
705    fn parse_color_group(&mut self, optional: bool) -> Result<Option<ParseNode>, ParseError> {
706        match self.parse_string_group(optional)? {
707            None => Ok(None),
708            Some(token) => {
709                let color = normalized_color(&token.text).ok_or_else(|| {
710                    ParseError::InvalidArgument {
711                        message: format!("Invalid color: '{}'", token.text),
712                        loc: token.loc.clone(),
713                    }
714                })?;
715                Ok(Some(ParseNode::ColorToken {
716                    mode: self.mode,
717                    color,
718                }))
719            }
720        }
721    }
722
723    fn parse_url_group(&mut self, optional: bool) -> Result<Option<ParseNode>, ParseError> {
724        self.gullet.set_lexer_catcode("%", 13);
725        self.gullet.set_lexer_catcode("~", 12);
726        let parsed = self.parse_string_group(optional);
727        self.gullet.set_lexer_catcode("%", 14);
728        self.gullet.set_lexer_catcode("~", 13);
729        match parsed? {
730            None => Ok(None),
731            Some(token) => Ok(Some(ParseNode::Url {
732                mode: self.mode,
733                url: unescape_url(&token.text),
734            })),
735        }
736    }
737
738    fn parse_size_regex_group(&mut self) -> Result<Token, ParseError> {
739        let first_token = self.fetch()?;
740        let mut last_token = first_token.clone();
741        let mut builder = String::new();
742        loop {
743            let token = self.fetch()?;
744            if token.text == "EOF" {
745                break;
746            }
747            let candidate = format!("{builder}{}", token.text);
748            if !size_scan_candidate(&candidate) {
749                break;
750            }
751            builder.push_str(&token.text);
752            last_token = token;
753            self.consume();
754        }
755        if builder.is_empty() {
756            return Err(ParseError::InvalidArgument {
757                message: format!("Invalid size: '{}'", first_token.text),
758                loc: first_token.loc.clone(),
759            });
760        }
761        Ok(first_token.range(&last_token, builder))
762    }
763
764    fn parse_size_group(&mut self, optional: bool) -> Result<Option<ParseNode>, ParseError> {
765        self.gullet.consume_spaces()?;
766        let parsed = if !optional && self.gullet.future()?.text != "{" {
767            Some(self.parse_size_regex_group()?)
768        } else {
769            self.parse_string_group(optional)?
770        };
771        match parsed {
772            None => Ok(None),
773            Some(token) => {
774                let mut text = token.text;
775                let is_blank = !optional && text.is_empty();
776                if is_blank {
777                    text = "0pt".to_string();
778                }
779                let value = parse_size_measurement(&text)?.ok_or_else(|| {
780                    ParseError::InvalidArgument {
781                        message: format!("Invalid size: '{text}'"),
782                        loc: token.loc.clone(),
783                    }
784                })?;
785                if !valid_size_unit(&value.unit) {
786                    return Err(ParseError::InvalidArgument {
787                        message: format!("Invalid unit: '{}'", value.unit),
788                        loc: token.loc.clone(),
789                    });
790                }
791                Ok(Some(ParseNode::Size {
792                    mode: self.mode,
793                    value,
794                    is_blank,
795                }))
796            }
797        }
798    }
799
800    fn handle_infix_nodes(&mut self, body: Vec<ParseNode>) -> Result<Vec<ParseNode>, ParseError> {
801        let mut infix: Option<(usize, String)> = None;
802        for (index, node) in body.iter().enumerate() {
803            if let ParseNode::Infix { replace_with, loc, .. } = node {
804                if infix.is_some() {
805                    return Err(ParseError::InvalidArgument {
806                        message: "only one infix operator per group".to_string(),
807                        loc: loc.clone(),
808                    });
809                }
810                infix = Some((index, replace_with.clone()));
811            }
812        }
813        if let Some((index, func_name)) = infix {
814            let numer = infix_side_group(self.mode, body[..index].to_vec());
815            let denom = infix_side_group(self.mode, body[index + 1..].to_vec());
816            let node = if func_name == "\\\\abovefrac" {
817                self.call_function(
818                    &func_name,
819                    vec![numer, body[index].clone(), denom],
820                    Vec::new(),
821                    None,
822                    None,
823                )?
824            } else {
825                self.call_function(&func_name, vec![numer, denom], Vec::new(), None, None)?
826            };
827            Ok(vec![node])
828        } else {
829            Ok(body)
830        }
831    }
832
833    fn parse_environment(&mut self, name: &str) -> Result<ParseNode, ParseError> {
834        let spec = self
835            .environment_registry
836            .get(name)
837            .cloned()
838            .ok_or_else(|| ParseError::InvalidArgument {
839                message: format!("No such environment: {name}"),
840                loc: None,
841            })?;
842        let arguments = FunctionSpec {
843            names: Vec::new(),
844            num_args: spec.num_args,
845            num_optional_args: spec.num_optional_args,
846            arg_types: spec.arg_types.clone(),
847            ..Default::default()
848        };
849        let (args, opt_args) = self.parse_arguments(&format!("\\begin{{{name}}}"), &arguments)?;
850        let context = EnvironmentContext {
851            mode: self.mode,
852            display_mode: self.settings.display_mode,
853            leqno: self.settings.leqno,
854            env_name: name.to_string(),
855        };
856        let result = (spec.handler)(self, &context, &args, &opt_args)?;
857        self.expect("\\end", false)?;
858        match self.parse_function(None, None)? {
859            Some(AtomResult::EmitAtom(ParseNode::EnvironmentEnd { name: end_name, .. }))
860                if end_name == name =>
861            {
862                Ok(result)
863            }
864            Some(AtomResult::EmitAtom(ParseNode::EnvironmentEnd { name: end_name, .. })) => {
865                Err(ParseError::InvalidArgument {
866                    message: format!(
867                        "Mismatch: \\begin{{{name}}} matched by \\end{{{end_name}}}"
868                    ),
869                    loc: None,
870                })
871            }
872            _ => Err(ParseError::InternalInvariant {
873                message: "Expected environment end".to_string(),
874            }),
875        }
876    }
877
878    fn parse_matrix_alignment(&mut self) -> Result<Option<String>, ParseError> {
879        self.consume_spaces()?;
880        if self.fetch()?.text != "[" {
881            return Ok(None);
882        }
883        self.consume();
884        self.consume_spaces()?;
885        let token = self.fetch()?;
886        if token.text != "l" && token.text != "c" && token.text != "r" {
887            return Err(ParseError::InvalidArgument {
888                message: "Expected l or c or r".to_string(),
889                loc: token.loc.clone(),
890            });
891        }
892        self.consume();
893        self.consume_spaces()?;
894        self.expect("]", true)?;
895        Ok(Some(token.text))
896    }
897
898    fn parse_prefixed_function(&mut self, name: &str) -> Result<ParseNode, ParseError> {
899        self.gullet.push_token(Token::new(name, None));
900        let Some(AtomResult::EmitAtom(node)) = self.parse_function(None, None)? else {
901            return Err(ParseError::InternalInvariant {
902                message: "Expected function after macro prefix".to_string(),
903            });
904        };
905        Ok(node)
906    }
907
908    fn parse_symbol(&mut self) -> Result<Option<ParseNode>, ParseError> {
909        let token = self.fetch()?;
910        let original_text = token.text.clone();
911        if original_text == "EOF"
912            || original_text == "^"
913            || original_text == "_"
914            || original_text == "{"
915            || original_text == "}"
916            || original_text == "&"
917        {
918            Ok(None)
919        } else if is_verb_token(&original_text) {
920            self.consume();
921            Ok(Some(parse_verb_token(&original_text)?))
922        } else {
923            self.parse_symbol_text(
924                &token,
925                &original_text,
926                normalize_unicode_symbol(self.mode, &original_text),
927            )
928        }
929    }
930
931    fn parse_symbol_text(
932        &mut self,
933        token: &Token,
934        original_text: &str,
935        normalized: String,
936    ) -> Result<Option<ParseNode>, ParseError> {
937        if self.mode == Mode::Math && normalized != original_text {
938            let first = original_text.chars().next().unwrap();
939            self.settings.report_nonstrict(
940                "unicodeTextInMathMode",
941                &format!("Accented Unicode text character \"{first}\" used in math mode"),
942                Some(token),
943            )?;
944        }
945        let (text, marks) = split_combining_marks(&normalized);
946        match lookup_symbol(self.mode, &text) {
947            Some(spec) => {
948                if self.mode == Mode::Math && is_extra_latin(&text) {
949                    let first = text.chars().next().unwrap();
950                    self.settings.report_nonstrict(
951                        "unicodeTextInMathMode",
952                        &format!("Latin-1/Unicode text character \"{first}\" used in math mode"),
953                        Some(token),
954                    )?;
955                }
956                self.consume();
957                let base = make_symbol_node(self.mode, &text, token.loc.clone(), spec);
958                match marks {
959                    None => Ok(Some(base)),
960                    Some(accents) => Ok(Some(apply_unicode_accents(
961                        self.mode,
962                        token.loc.clone(),
963                        base,
964                        &accents,
965                    )?)),
966                }
967            }
968            None if is_non_ascii(&text) => {
969                let first = text.chars().next().unwrap();
970                if !supported_codepoint(first as u32) {
971                    self.settings.report_nonstrict(
972                        "unknownSymbol",
973                        &format!(
974                            "Unrecognized Unicode character \"{first}\" ({})",
975                            first as u32
976                        ),
977                        Some(token),
978                    )?;
979                } else if self.mode == Mode::Math {
980                    self.settings.report_nonstrict(
981                        "unicodeTextInMathMode",
982                        &format!("Unicode text character \"{first}\" used in math mode"),
983                        Some(token),
984                    )?;
985                }
986                self.consume();
987                Ok(Some(ParseNode::TextOrd {
988                    mode: Mode::Text,
989                    loc: token.loc.clone(),
990                    text,
991                }))
992            }
993            None => Ok(None),
994        }
995    }
996
997    fn switch_mode(&mut self, mode: Mode) {
998        if self.mode != mode {
999            self.mode = mode;
1000            self.gullet.switch_mode(mode);
1001        }
1002    }
1003
1004    fn consume_array_hlines(&mut self) -> Result<Vec<bool>, ParseError> {
1005        let mut lines: Vec<bool> = Vec::new();
1006        self.consume_spaces()?;
1007        while self.fetch()?.text == "\\hline" || self.fetch()?.text == "\\hdashline" {
1008            let dashed = self.fetch()?.text == "\\hdashline";
1009            self.consume();
1010            lines.push(dashed);
1011            self.consume_spaces()?;
1012        }
1013        Ok(lines)
1014    }
1015
1016    fn take_array_tag(
1017        &mut self,
1018        auto_tag: Option<bool>,
1019    ) -> Result<(Option<Vec<ParseNode>>, bool), ParseError> {
1020        let Some(automatic) = auto_tag else {
1021            return Ok((None, false));
1022        };
1023        if self.gullet.macros.get("\\df@tag").is_none() {
1024            return Ok((None, automatic));
1025        }
1026        let tag = self.subparse(vec![Token::new("\\df@tag", None)])?;
1027        self.gullet.macros.set("\\df@tag".to_string(), None, true);
1028        Ok((Some(tag), false))
1029    }
1030
1031    fn push_array_tag(
1032        &mut self,
1033        tags: &mut Vec<Option<Vec<ParseNode>>>,
1034        auto_tags: &mut Vec<bool>,
1035        auto_tag: Option<bool>,
1036    ) -> Result<(), ParseError> {
1037        let (tag, automatic) = self.take_array_tag(auto_tag)?;
1038        if auto_tag.is_some() {
1039            tags.push(tag);
1040            auto_tags.push(automatic);
1041        }
1042        Ok(())
1043    }
1044
1045    fn parse_array_row_gap(&mut self) -> Result<Option<Measurement>, ParseError> {
1046        if self.gullet.future()?.text == " " {
1047            Ok(None)
1048        } else {
1049            match self.parse_size_group(true)? {
1050                Some(ParseNode::Size { value, .. }) => Ok(Some(value)),
1051                Some(_) => Err(ParseError::InternalInvariant {
1052                    message: "Expected array row gap".to_string(),
1053                }),
1054                None => Ok(None),
1055            }
1056        }
1057    }
1058
1059    fn parse_array_environment(
1060        &mut self,
1061        options: ArrayEnvironmentOptions,
1062    ) -> Result<ParseNode, ParseError> {
1063        self.gullet.begin_group();
1064        self.gullet
1065            .macros
1066            .set("\\cr".to_string(), Some(MacroDefinition::text("\\\\\\relax")), false);
1067        self.gullet.begin_group();
1068        let result: Result<ParseNode, ParseError> = (|| {
1069            let mut body: Vec<Vec<ParseNode>> = vec![Vec::new()];
1070            let mut row_gaps: Vec<Option<Measurement>> = Vec::new();
1071            let mut hlines_before_row: Vec<Vec<bool>> = vec![self.consume_array_hlines()?];
1072            let mut tags: Vec<Option<Vec<ParseNode>>> = Vec::new();
1073            let mut auto_tags: Vec<bool> = Vec::new();
1074            loop {
1075                let cell_body = self.parse_expression(false, Some("\\\\"))?;
1076                let cell = ParseNode::Styling {
1077                    mode: self.mode,
1078                    body: vec![ParseNode::OrdGroup {
1079                        mode: self.mode,
1080                        loc: None,
1081                        body: cell_body,
1082                        semisimple: false,
1083                    }],
1084                    style: options.cell_style,
1085                    reset_font: true,
1086                };
1087                self.gullet.end_group()?;
1088                self.gullet.begin_group();
1089                let Some(row) = body.last_mut() else {
1090                    return Err(ParseError::InternalInvariant {
1091                        message: "Missing array row".to_string(),
1092                    });
1093                };
1094                row.push(cell);
1095                let text = self.fetch()?.text;
1096                match text.as_str() {
1097                    "&" => {
1098                        if array_row_at_max(&body, options.max_columns) {
1099                            return Err(ParseError::InvalidArgument {
1100                                message: "Too many tab characters: &".to_string(),
1101                                loc: None,
1102                            });
1103                        }
1104                        self.consume();
1105                    }
1106                    "\\end" => {
1107                        self.push_array_tag(&mut tags, &mut auto_tags, options.auto_tag)?;
1108                        break;
1109                    }
1110                    "\\\\" => {
1111                        if options.single_row {
1112                            return Err(ParseError::InvalidArgument {
1113                                message: "Expected \\end".to_string(),
1114                                loc: None,
1115                            });
1116                        }
1117                        self.consume();
1118                        row_gaps.push(self.parse_array_row_gap()?);
1119                        self.push_array_tag(&mut tags, &mut auto_tags, options.auto_tag)?;
1120                        hlines_before_row.push(self.consume_array_hlines()?);
1121                        body.push(Vec::new());
1122                    }
1123                    _ => {
1124                        return Err(ParseError::InvalidArgument {
1125                            message: format!("Expected & or \\\\ or \\end, got {text}"),
1126                            loc: None,
1127                        })
1128                    }
1129                }
1130            }
1131            if hlines_before_row.len() < body.len() + 1 {
1132                hlines_before_row.push(Vec::new());
1133            }
1134            Ok(ParseNode::Array {
1135                mode: self.mode,
1136                body,
1137                add_jot: options.add_jot,
1138                array_stretch: options.array_stretch,
1139                columns: options.columns.clone(),
1140                row_gaps,
1141                hskip_before_and_after: options.hskip_before_and_after,
1142                hlines_before_row,
1143                column_separation_type: options.column_separation_type,
1144                tags: if options.auto_tag.is_some() { Some(tags) } else { None },
1145                auto_tags: if options.auto_tag.is_some() { Some(auto_tags) } else { None },
1146                leqno: options.leqno,
1147            })
1148        })();
1149        let close_cell = self.gullet.end_group();
1150        let close_array = self.gullet.end_group();
1151        unwrap_array_parse_result(result, close_cell, close_array)
1152    }
1153
1154    fn parse_cd_environment(&mut self) -> Result<ParseNode, ParseError> {
1155        self.gullet.begin_group();
1156        self.gullet
1157            .macros
1158            .set("\\cr".to_string(), Some(MacroDefinition::text("\\\\\\relax")), false);
1159        self.gullet.begin_group();
1160        let result: Result<ParseNode, ParseError> = (|| {
1161            let mut parsed_rows: Vec<Vec<ParseNode>> = vec![Vec::new()];
1162            loop {
1163                let part = self.parse_expression(false, Some("\\\\"))?;
1164                let Some(row) = parsed_rows.last_mut() else {
1165                    return Err(ParseError::InternalInvariant {
1166                        message: "Missing CD row".to_string(),
1167                    });
1168                };
1169                row.extend(part);
1170                match self.fetch()?.text.as_str() {
1171                    "&" => self.consume(),
1172                    "\\\\" => {
1173                        self.consume();
1174                        parsed_rows.push(Vec::new());
1175                    }
1176                    "\\end" => break,
1177                    token => {
1178                        return Err(ParseError::InvalidArgument {
1179                            message: format!("Expected \\ or \\end, got {token}"),
1180                            loc: None,
1181                        })
1182                    }
1183                }
1184            }
1185            if parsed_rows.last().is_some_and(|row| row.is_empty()) {
1186                parsed_rows.pop();
1187            }
1188            let mut body: Vec<Vec<ParseNode>> = Vec::new();
1189            for (index, row) in parsed_rows.iter().enumerate() {
1190                body.push(cd_row(row.clone(), index % 2 == 0)?);
1191            }
1192            let count = body.first().map_or(0, |row| row.len());
1193            let columns: Vec<crate::ast::ArrayColumn> = (0..count)
1194                .map(|_| crate::ast::ArrayColumn::AlignColumn {
1195                    alignment: "c".to_string(),
1196                    pre_gap: 0.25,
1197                    post_gap: 0.25,
1198                })
1199                .collect();
1200            let row_gap_count = body.len() + 1;
1201            let hlines_before_row: Vec<Vec<bool>> =
1202                (0..row_gap_count).map(|_| Vec::new()).collect();
1203            Ok(ParseNode::Array {
1204                mode: Mode::Math,
1205                body,
1206                add_jot: true,
1207                array_stretch: 1.0,
1208                columns: Some(columns),
1209                row_gaps: vec![None],
1210                hskip_before_and_after: false,
1211                hlines_before_row,
1212                column_separation_type: Some(ColumnSeparationType::CdSeparation),
1213                tags: None,
1214                auto_tags: None,
1215                leqno: false,
1216            })
1217        })();
1218        let close_cell = self.gullet.end_group();
1219        let close_array = self.gullet.end_group();
1220        unwrap_array_parse_result(result, close_cell, close_array)
1221    }
1222}
1223
1224impl FunctionParser for Parser {
1225    fn report_nonstrict(
1226        &self,
1227        error_code: &str,
1228        error_message: &str,
1229        token: Option<&Token>,
1230    ) -> Result<(), ParseError> {
1231        self.settings.report_nonstrict(error_code, error_message, token)
1232    }
1233
1234    fn use_strict_behavior(
1235        &self,
1236        error_code: &str,
1237        error_message: &str,
1238        token: Option<&Token>,
1239    ) -> bool {
1240        self.settings.use_strict_behavior(error_code, error_message, token)
1241    }
1242
1243    fn is_trusted(&self, context: TrustContext) -> bool {
1244        self.settings.is_trusted(context)
1245    }
1246
1247    fn current_color(&self) -> Result<Option<String>, ParseError> {
1248        self.current_color()
1249    }
1250
1251    fn in_left_right(&self) -> bool {
1252        self.leftright_depth > 0
1253    }
1254
1255    fn is_expandable(&self, name: &str) -> bool {
1256        self.gullet.is_expandable(name)
1257    }
1258
1259    fn get_macro(&self, name: &str) -> Option<MacroDefinition> {
1260        self.gullet.macros.get(name).cloned()
1261    }
1262
1263    fn set_macro(&mut self, name: &str, definition: Option<MacroDefinition>) {
1264        self.gullet.macros.set(name.to_string(), definition, false);
1265    }
1266
1267    fn set_macro_definition(&mut self, name: &str, definition: MacroDefinition, global: bool) {
1268        self.gullet.macros.set(name.to_string(), Some(definition), global);
1269    }
1270
1271    fn parse_expression(
1272        &mut self,
1273        break_on_infix: bool,
1274        break_on_token_text: Option<&str>,
1275    ) -> Result<Vec<ParseNode>, ParseError> {
1276        self.parse_expression(break_on_infix, break_on_token_text)
1277    }
1278
1279    fn parse_math_mode(&mut self, closing: &str) -> Result<Vec<ParseNode>, ParseError> {
1280        self.parse_math_mode(closing)
1281    }
1282
1283    fn parse_left_right(&mut self, open: &str) -> Result<ParseNode, ParseError> {
1284        self.parse_left_right(open)
1285    }
1286
1287    fn parse_optional_size(&mut self) -> Result<Option<Measurement>, ParseError> {
1288        if self.gullet.future()?.text != "[" {
1289            Ok(None)
1290        } else {
1291            match self.parse_size_group(true)? {
1292                Some(ParseNode::Size { value, .. }) => Ok(Some(value)),
1293                _ => Err(ParseError::InternalInvariant {
1294                    message: "Expected optional size".to_string(),
1295                }),
1296            }
1297        }
1298    }
1299
1300    fn parse_prefixed_function(&mut self, name: &str) -> Result<ParseNode, ParseError> {
1301        self.parse_prefixed_function(name)
1302    }
1303
1304    fn parse_environment(&mut self, name: &str) -> Result<ParseNode, ParseError> {
1305        self.parse_environment(name)
1306    }
1307
1308    fn pop_token(&mut self) -> Result<Token, ParseError> {
1309        self.gullet.pop_token()
1310    }
1311
1312    fn future_token(&mut self) -> Result<Token, ParseError> {
1313        self.gullet.future()
1314    }
1315
1316    fn push_token(&mut self, token: Token) {
1317        self.gullet.push_token(token);
1318    }
1319
1320    fn consume_spaces(&mut self) -> Result<(), ParseError> {
1321        self.gullet.consume_spaces()
1322    }
1323
1324    fn consume_macro_arg(&mut self) -> Result<Vec<Token>, ParseError> {
1325        Ok(self.gullet.consume_arg(None)?.tokens)
1326    }
1327
1328    fn expand_tokens(&mut self, tokens: Vec<Token>) -> Result<Vec<Token>, ParseError> {
1329        self.gullet.expand_tokens(tokens)
1330    }
1331}
1332
1333impl EnvironmentParser for Parser {
1334    fn parse_array(&mut self, options: ArrayEnvironmentOptions) -> Result<ParseNode, ParseError> {
1335        self.parse_array_environment(options)
1336    }
1337
1338    fn parse_matrix_alignment(&mut self) -> Result<Option<String>, ParseError> {
1339        self.parse_matrix_alignment()
1340    }
1341
1342    fn parse_cd(&mut self) -> Result<ParseNode, ParseError> {
1343        self.parse_cd_environment()
1344    }
1345}
1346
1347/// Parses `input` into a `ParseNode` list using the given settings. When
1348/// `settings.global_group` is set and `settings.macro_store` is present,
1349/// macros defined during the parse (e.g. via `\newcommand`) are written back
1350/// into the caller's store.
1351pub fn parse(input: &str, settings: &mut Settings) -> Result<Vec<ParseNode>, ParseError> {
1352    parse_with_specs(input, settings, &[], &[])
1353}
1354
1355/// Parses `input` with caller-provided extension function/environment specs.
1356pub fn parse_with_specs(
1357    input: &str,
1358    settings: &mut Settings,
1359    extra_specs: &[FunctionSpec],
1360    extra_env_specs: &[EnvironmentSpec],
1361) -> Result<Vec<ParseNode>, ParseError> {
1362    let mut parser = Parser::new(input, settings.clone(), extra_specs, extra_env_specs);
1363    parser
1364        .gullet
1365        .macros
1366        .set("\\df@tag".to_string(), None, false);
1367    let parse_result = parser.parse();
1368    if settings.global_group {
1369        settings.macro_store = parser.settings.macro_store.clone();
1370    }
1371    let mut body = parse_result?;
1372    if parser.gullet.macros.get("\\df@tag").is_some() {
1373        if !settings.display_mode {
1374            return Err(ParseError::InvalidArgument {
1375                message: "\\tag works only in display equations".to_string(),
1376                loc: None,
1377            });
1378        }
1379        let tag = parser.subparse(vec![Token::new("\\df@tag", None)])?;
1380        body = vec![ParseNode::Tag {
1381            mode: Mode::Text,
1382            body,
1383            tag,
1384        }];
1385    }
1386    parser
1387        .gullet
1388        .macros
1389        .set("\\current@color".to_string(), None, false);
1390    parser
1391        .gullet
1392        .macros
1393        .set("\\color".to_string(), None, false);
1394    if settings.display_mode {
1395        Ok(vec![ParseNode::Styling {
1396            mode: Mode::Math,
1397            body,
1398            style: StyleLevel::DisplayStyle,
1399            reset_font: true,
1400        }])
1401    } else {
1402        Ok(body)
1403    }
1404}
1405
1406fn unwrap_captured<V>(close: Result<(), ParseError>, value: Result<V, ParseError>) -> Result<V, ParseError> {
1407    match (close, value) {
1408        (Err(err), _) => Err(err),
1409        (_, Err(err)) => Err(err),
1410        (Ok(()), Ok(value)) => Ok(value),
1411    }
1412}
1413
1414fn is_end_of_expression(text: &str) -> bool {
1415    text == "}" || text == "\\endgroup" || text == "\\end" || text == "\\right" || text == "&"
1416}
1417
1418fn set_limits(base: ParseNode, limits: bool, loc: Option<SourceLocation>) -> Result<ParseNode, ParseError> {
1419    match base {
1420        ParseNode::Op {
1421            mode,
1422            parent_is_sup_sub,
1423            suppress_base_shift,
1424            content,
1425            ..
1426        } => Ok(ParseNode::Op {
1427            mode,
1428            limits,
1429            always_handle_sup_sub: true,
1430            parent_is_sup_sub,
1431            suppress_base_shift,
1432            content,
1433        }),
1434        ParseNode::OperatorName {
1435            mode,
1436            body,
1437            always_handle_sup_sub: true,
1438            parent_is_sup_sub,
1439            ..
1440        } => Ok(ParseNode::OperatorName {
1441            mode,
1442            body,
1443            always_handle_sup_sub: true,
1444            limits,
1445            parent_is_sup_sub,
1446        }),
1447        _ => Err(ParseError::InvalidArgument {
1448            message: "Limit controls must follow a math operator".to_string(),
1449            loc,
1450        }),
1451    }
1452}
1453
1454fn make_supsub_or_base(mode: Mode, base: ParseNode, sup: Option<ParseNode>, sub: Option<ParseNode>) -> ParseNode {
1455    match (sup, sub) {
1456        (None, None) => base,
1457        (sup, sub) => ParseNode::SupSub {
1458            mode,
1459            base: Some(Box::new(base)),
1460            sup: sup.map(Box::new),
1461            sub: sub.map(Box::new),
1462        },
1463    }
1464}
1465
1466fn infix_side_group(mode: Mode, body: Vec<ParseNode>) -> ParseNode {
1467    if body.len() == 1 && matches!(body[0], ParseNode::OrdGroup { .. }) {
1468        body[0].clone()
1469    } else {
1470        ParseNode::OrdGroup {
1471            mode,
1472            loc: None,
1473            body,
1474            semisimple: false,
1475        }
1476    }
1477}
1478
1479fn is_undefined_control_sequence(text: &str) -> bool {
1480    !text.is_empty() && text.starts_with('\\') && !is_implicit_command(text)
1481}
1482
1483fn is_verb_token(text: &str) -> bool {
1484    let chars: Vec<char> = text.chars().collect();
1485    chars.len() > 5
1486        && starts_with_at(&chars, 0, "\\verb")
1487        && !is_ascii_alphabetic(chars[5])
1488}
1489
1490fn parse_verb_token(text: &str) -> Result<ParseNode, ParseError> {
1491    let chars: Vec<char> = text.chars().collect();
1492    let raw_argument = &chars[5..];
1493    let star = !raw_argument.is_empty() && raw_argument[0] == '*';
1494    let argument: &[char] = if star { &raw_argument[1..] } else { raw_argument };
1495    if argument.len() < 2 || argument[0] != argument[argument.len() - 1] {
1496        Err(ParseError::InternalInvariant {
1497            message: "\\verb assertion failed -- please report what input caused this bug"
1498                .to_string(),
1499        })
1500    } else {
1501        Ok(ParseNode::Verb {
1502            mode: Mode::Text,
1503            loc: None,
1504            body: argument[1..argument.len() - 1].iter().collect(),
1505            star,
1506        })
1507    }
1508}
1509
1510fn is_extra_latin(text: &str) -> bool {
1511    !text.is_empty() && matches!(text.chars().next(), Some('Ð' | 'Þ' | 'þ'))
1512}
1513
1514fn is_non_ascii(text: &str) -> bool {
1515    !text.is_empty() && text.chars().next().is_some_and(|c| c as u32 >= 0x80)
1516}
1517
1518fn split_combining_marks(normalized: &str) -> (String, Option<String>) {
1519    match trailing_combining_mark_start(normalized) {
1520        None => (normalized.to_string(), None),
1521        Some(start) => {
1522            let chars: Vec<char> = normalized.chars().collect();
1523            let base: String = chars[..start].iter().collect();
1524            let base = if base == "i" {
1525                "ı".to_string()
1526            } else if base == "j" {
1527                "ȷ".to_string()
1528            } else {
1529                base
1530            };
1531            let marks: String = chars[start..].iter().collect();
1532            (base, Some(marks))
1533        }
1534    }
1535}
1536
1537fn apply_unicode_accents(
1538    mode: Mode,
1539    loc: Option<SourceLocation>,
1540    base: ParseNode,
1541    accents: &str,
1542) -> Result<ParseNode, ParseError> {
1543    let mut result = base;
1544    for accent in accents.chars() {
1545        let accent_text = accent.to_string();
1546        let Some(label) = unicode_accent_command(mode, &accent_text) else {
1547            return Err(ParseError::InvalidArgument {
1548                message: format!("Unknown accent ' {accent_text}'"),
1549                loc: loc.clone(),
1550            });
1551        };
1552        result = ParseNode::Accent {
1553            mode,
1554            loc: loc.clone(),
1555            label,
1556            is_stretchy: false,
1557            is_shifty: true,
1558            base: Box::new(result),
1559        };
1560    }
1561    Ok(result)
1562}
1563
1564fn make_symbol_node(
1565    mode: Mode,
1566    text: &str,
1567    loc: Option<SourceLocation>,
1568    spec: &SymbolSpec,
1569) -> ParseNode {
1570    let text = text.to_string();
1571    match spec.group {
1572        SymbolGroup::AccentTokenGroup => ParseNode::AccentToken { mode, loc, text },
1573        SymbolGroup::BinaryGroup => ParseNode::Atom {
1574            mode,
1575            loc,
1576            family: AtomFamily::Mbin,
1577            text,
1578        },
1579        SymbolGroup::CloseGroup => ParseNode::Atom {
1580            mode,
1581            loc,
1582            family: AtomFamily::Mclose,
1583            text,
1584        },
1585        SymbolGroup::InnerGroup => ParseNode::Atom {
1586            mode,
1587            loc,
1588            family: AtomFamily::Minner,
1589            text,
1590        },
1591        SymbolGroup::MathOrdGroup => ParseNode::MathOrd { mode, loc, text },
1592        SymbolGroup::OperatorTokenGroup => ParseNode::OperatorToken { mode, loc, text },
1593        SymbolGroup::OpenGroup => ParseNode::Atom {
1594            mode,
1595            loc,
1596            family: AtomFamily::Mopen,
1597            text,
1598        },
1599        SymbolGroup::PunctuationGroup => ParseNode::Atom {
1600            mode,
1601            loc,
1602            family: AtomFamily::Mpunct,
1603            text,
1604        },
1605        SymbolGroup::RelationGroup => ParseNode::Atom {
1606            mode,
1607            loc,
1608            family: AtomFamily::Mrel,
1609            text,
1610        },
1611        SymbolGroup::SpacingGroup => ParseNode::Spacing { mode, loc, text },
1612        SymbolGroup::TextOrdGroup => ParseNode::TextOrd { mode, loc, text },
1613    }
1614}
1615
1616fn format_unsupported_command(mode: Mode, settings: &Settings, text: &str) -> ParseNode {
1617    let body: Vec<ParseNode> = text
1618        .chars()
1619        .map(|ch| ParseNode::TextOrd {
1620            mode: Mode::Text,
1621            loc: None,
1622            text: ch.to_string(),
1623        })
1624        .collect();
1625    ParseNode::Color {
1626        mode,
1627        color: settings.error_color.clone(),
1628        body,
1629    }
1630}
1631
1632fn array_row_at_max(body: &[Vec<ParseNode>], max_columns: Option<usize>) -> bool {
1633    max_columns.is_some_and(|maximum| {
1634        body.last().is_some_and(|row| row.len() >= maximum)
1635    })
1636}
1637
1638fn unwrap_array_parse_result(
1639    result: Result<ParseNode, ParseError>,
1640    close_cell: Result<(), ParseError>,
1641    close_array: Result<(), ParseError>,
1642) -> Result<ParseNode, ParseError> {
1643    match (result, close_cell, close_array) {
1644        (Err(err), _, _) => Err(err),
1645        (_, Err(err), _) => Err(err),
1646        (_, _, Err(err)) => Err(err),
1647        (Ok(node), Ok(()), Ok(())) => Ok(node),
1648    }
1649}
1650
1651fn is_ascii_hex_digit(c: char) -> bool {
1652    c.is_ascii_digit() || ('a'..='f').contains(&c) || ('A'..='F').contains(&c)
1653}
1654
1655fn all_code_units(text: &str, predicate: impl Fn(char) -> bool) -> bool {
1656    if text.is_empty() {
1657        return false;
1658    }
1659    text.chars().all(predicate)
1660}
1661
1662fn normalized_color(text: &str) -> Option<String> {
1663    if let Some(digits) = text.strip_prefix('#') {
1664        if (digits.len() == 3 || digits.len() == 4 || digits.len() == 6 || digits.len() == 8)
1665            && all_code_units(digits, is_ascii_hex_digit)
1666        {
1667            Some(text.to_string())
1668        } else {
1669            None
1670        }
1671    } else if text.chars().count() == 6 && all_code_units(text, is_ascii_hex_digit) {
1672        Some(format!("#{text}"))
1673    } else if all_code_units(text, is_ascii_alphabetic) {
1674        Some(text.to_string())
1675    } else {
1676        None
1677    }
1678}
1679
1680fn is_url_escape_target(c: char) -> bool {
1681    matches!(c, '#' | '$' | '%' | '&' | '~' | '_' | '^' | '{' | '}')
1682}
1683
1684fn unescape_url(text: &str) -> String {
1685    let chars: Vec<char> = text.chars().collect();
1686    let mut builder = String::new();
1687    let mut segment_start = 0;
1688    let mut index = 0;
1689    while index < chars.len() {
1690        if chars[index] == '\\'
1691            && index + 1 < chars.len()
1692            && is_url_escape_target(chars[index + 1])
1693        {
1694            builder.extend(&chars[segment_start..index]);
1695            builder.push(chars[index + 1]);
1696            segment_start = index + 2;
1697            index += 2;
1698            continue;
1699        }
1700        index += 1;
1701    }
1702    builder.extend(&chars[segment_start..]);
1703    builder
1704}