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
1347pub fn parse(input: &str, settings: &mut Settings) -> Result<Vec<ParseNode>, ParseError> {
1352 parse_with_specs(input, settings, &[], &[])
1353}
1354
1355pub 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}