1use crate::event::{DelimiterType, Dimension, DimensionUnit, Glue, GroupingKind, Line};
2
3use super::{
4 tables::{dvipsnames_color, primitive_color, token_to_delim},
5 Argument, CharToken, ErrorKind, InnerResult, Token,
6};
7
8pub fn definition<'a>(input: &mut &'a str) -> InnerResult<(&'a str, &'a str, &'a str)> {
14 let control_sequence = control_sequence(input)?;
15 let (parameter_text, rest) = input.split_once('{').ok_or(ErrorKind::MissingExpansion)?;
16
17 if let Some(idx) = parameter_text.find(['%', '}']) {
18 return Err(if parameter_text.as_bytes()[idx] == b'%' {
19 ErrorKind::CommentInParamText
20 } else {
21 ErrorKind::BracesInParamText
22 });
23 }
24
25 *input = rest;
26 let replacement_text = group_content(input, GroupingKind::Normal)?;
27
28 Ok((control_sequence, parameter_text, replacement_text))
29}
30
31pub fn argument<'a>(input: &mut &'a str) -> InnerResult<Argument<'a>> {
33 if let Some(rest) = input.trim_start().strip_prefix('{') {
34 *input = rest;
35 let content = group_content(input, GroupingKind::Normal)?;
36 Ok(Argument::Group(content))
37 } else {
38 Ok(Argument::Token(token(input)?))
39 }
40}
41
42pub fn optional_argument<'a>(input: &mut &'a str) -> Option<&'a str> {
43 if let Some(rest) = input.trim_start().strip_prefix('[') {
44 *input = rest;
45 let content = group_content(input, GroupingKind::OptionalArgument).ok()?;
46 Some(content)
47 } else {
48 None
49 }
50}
51
52pub fn brace_argument<'a>(input: &mut &'a str) -> InnerResult<&'a str> {
53 if let Some(rest) = input.trim_start().strip_prefix('{') {
54 *input = rest;
55 group_content(input, GroupingKind::Normal)
56 } else {
57 Err(ErrorKind::GroupArgument)
58 }
59}
60
61pub fn group_content<'a>(input: &mut &'a str, grouping_kind: GroupingKind) -> InnerResult<&'a str> {
66 let start = grouping_kind.opening_str();
67 let end = grouping_kind.closing_str();
68 let mut escaped = false;
69 let mut index = 0;
70 let mut depth = 0u32;
71 let bytes = input.as_bytes();
72 while escaped || depth > 0 || !bytes[index..].starts_with(end.as_bytes()) {
73 if index + end.len() > input.len() {
74 *input = &input[input.len()..];
75 return Err(ErrorKind::UnbalancedGroup(Some(grouping_kind)));
76 }
77 if !escaped && bytes[index..].starts_with(start.as_bytes()) {
78 depth += 1;
79 index += start.len();
80 continue;
81 }
82 if !escaped && bytes[index..].starts_with(end.as_bytes()) {
83 if depth.checked_sub(1).is_none() {
84 break;
85 }
86 depth -= 1;
87 index += end.len();
88 continue;
89 }
90 match bytes[index] {
91 b'\\' => escaped = !escaped,
92 b'%' if !escaped => {
93 let Some(rest_pos) = bytes[index..].iter().position(|&c| c == b'\n') else {
94 return Err(ErrorKind::UnbalancedGroup(Some(grouping_kind)));
95 };
96 index += rest_pos;
97 }
98 _ => escaped = false,
99 }
100 index += 1;
101 }
102 let (argument, rest) = input.split_at(index);
103 *input = &rest[end.len()..];
104 Ok(argument)
105}
106
107pub fn content_with_suffix<'a>(input: &mut &'a str, suffix: &str) -> InnerResult<&'a str> {
109 let mut escaped = false;
110 let mut index = 0;
111 let bytes = input.as_bytes();
112 while escaped || {
113 if index + suffix.len() > input.len() {
114 *input = &input[input.len()..];
115 return Err(ErrorKind::MacroSuffixNotFound);
116 }
117 !bytes[index..].starts_with(suffix.as_bytes())
118 } {
119 match bytes[index] {
120 b'\\' => escaped = !escaped,
121 b'%' if !escaped => {
122 let rest_pos = bytes[index..]
123 .iter()
124 .position(|&c| c == b'\n')
125 .unwrap_or(bytes.len());
126 index += rest_pos;
127 }
128 b'{' if !escaped => {
129 let content = group_content(&mut &input[index + 1..], GroupingKind::Normal)?;
130 index += content.len() + 1;
131 }
132 _ => escaped = false,
133 }
134 index += 1;
135 }
136 let (argument, rest) = input.split_at(index);
137 *input = &rest[suffix.len()..];
138 Ok(argument)
139}
140
141pub fn delimiter(input: &mut &str) -> InnerResult<(char, DelimiterType)> {
146 let maybe_delim = token(input)?;
147 token_to_delim(maybe_delim).ok_or(ErrorKind::Delimiter)
148}
149
150pub fn futurelet_assignment<'a>(input: &mut &'a str) -> InnerResult<(&'a str, Token<'a>, &'a str)> {
155 let control_sequence = control_sequence(input)?;
156
157 let input_with_tokens = *input;
158
159 let _ = token(input)?;
160 let token = token(input)?;
161 Ok((control_sequence, token, input_with_tokens))
162}
163
164pub fn let_assignment<'a>(input: &mut &'a str) -> InnerResult<(&'a str, Token<'a>)> {
168 let control_sequence = control_sequence(input)?;
169 if let Some(s) = input.trim_start().strip_prefix('=') {
170 *input = s;
171 }
172 let token = token(input)?;
173 Ok((control_sequence, token))
174}
175
176pub fn control_sequence<'a>(input: &mut &'a str) -> InnerResult<&'a str> {
178 if let Some(rest) = input.strip_prefix('\\') {
179 *input = rest;
180 rhs_control_sequence(input)
181 } else {
182 input
183 .chars()
184 .next()
185 .map_or(Err(ErrorKind::EmptyControlSequence), |_| {
186 Err(ErrorKind::ControlSequence)
187 })
188 }
189}
190
191pub fn limit_modifiers(input: &mut &str) -> Option<bool> {
192 let mut output = None;
193 while let Some((rest, limits)) = input
194 .trim_start()
195 .strip_prefix(r"\limits")
196 .map(|rest| (rest, true))
197 .or_else(|| {
198 input
199 .trim_start()
200 .strip_prefix(r"\nolimits")
201 .map(|rest| (rest, false))
202 })
203 {
204 *input = rest;
205 output = Some(limits);
206 }
207 output
208}
209
210pub fn rhs_control_sequence<'a>(input: &mut &'a str) -> InnerResult<&'a str> {
214 let first_char_byte_count = input
215 .chars()
216 .next()
217 .ok_or(ErrorKind::EmptyControlSequence)?
218 .len_utf8();
219
220 let len = input
221 .chars()
222 .take_while(|c| c.is_ascii_alphabetic())
223 .count()
224 .max(first_char_byte_count);
225
226 let (control_sequence, rest) = input.split_at(len);
227 *input = rest.trim_start();
228 Ok(control_sequence)
229}
230
231pub fn glue(input: &mut &str) -> InnerResult<Glue> {
233 let mut dimen = (dimension(input)?, None, None);
234 if let Some(s) = input.trim_start().strip_prefix("plus") {
235 *input = s;
236 dimen.1 = Some(dimension(input)?);
237 }
238 if let Some(s) = input.trim_start().strip_prefix("minus") {
239 *input = s;
240 dimen.2 = Some(dimension(input)?);
241 }
242 Ok(dimen)
243}
244
245pub fn glue_or_braced(input: &mut &str) -> InnerResult<Glue> {
250 if input.trim_start().starts_with('{') {
251 let mut inner = brace_argument(input)?;
252 let result = glue(&mut inner)?;
253 if !inner.trim_start().is_empty() {
255 return Err(ErrorKind::DimensionArgument);
256 }
257 Ok(result)
258 } else {
259 glue(input)
260 }
261}
262
263pub fn dimension(input: &mut &str) -> InnerResult<Dimension> {
265 let number = floating_point(input)?;
266 let unit = dimension_unit(input)?;
267 Ok(Dimension::new(number, unit))
268}
269
270pub fn dimension_or_braced(input: &mut &str) -> InnerResult<Dimension> {
275 if input.trim_start().starts_with('{') {
276 let mut inner = brace_argument(input)?;
277 let result = dimension(&mut inner)?;
278 if !inner.trim_start().is_empty() {
279 return Err(ErrorKind::DimensionArgument);
280 }
281 Ok(result)
282 } else {
283 dimension(input)
284 }
285}
286
287pub fn dimension_unit(input: &mut &str) -> InnerResult<DimensionUnit> {
289 *input = input.trim_start();
290 let unit = input.get(0..2).ok_or(ErrorKind::DimensionUnit)?;
291 let unit = match unit {
292 "em" => DimensionUnit::Em,
293 "ex" => DimensionUnit::Ex,
294 "pt" => DimensionUnit::Pt,
295 "pc" => DimensionUnit::Pc,
296 "in" => DimensionUnit::In,
297 "bp" => DimensionUnit::Bp,
298 "cm" => DimensionUnit::Cm,
299 "mm" => DimensionUnit::Mm,
300 "dd" => DimensionUnit::Dd,
301 "cc" => DimensionUnit::Cc,
302 "sp" => DimensionUnit::Sp,
303 "mu" => DimensionUnit::Mu,
304 _ => return Err(ErrorKind::DimensionUnit),
305 };
306
307 *input = &input[2..];
308 one_optional_space(input);
309
310 Ok(unit)
311}
312
313#[allow(dead_code)]
317pub fn integer(input: &mut &str) -> InnerResult<isize> {
318 let signum = signs(input)?;
319
320 let unsigned_int = unsigned_integer(input)?;
321
322 Ok(unsigned_int as isize * signum)
323}
324
325pub fn unsigned_integer(input: &mut &str) -> InnerResult<usize> {
326 let next_char = input.chars().next().ok_or(ErrorKind::Number)?;
328 if next_char.is_ascii_digit() {
329 return Ok(decimal(input));
330 }
331 *input = &input[1..];
332 match next_char {
333 '`' => {
334 let mut next_byte = *input.as_bytes().first().ok_or(ErrorKind::Number)?;
335 if next_byte == b'\\' {
336 *input = &input[1..];
337 next_byte = *input.as_bytes().first().ok_or(ErrorKind::Number)?;
338 }
339 if next_byte.is_ascii() {
340 *input = &input[1..];
341 Ok(next_byte as usize)
342 } else {
343 Err(ErrorKind::CharacterNumber)
344 }
345 }
346 '\'' => Ok(octal(input)),
347 '"' => Ok(hexadecimal(input)),
348 _ => Err(ErrorKind::Number),
349 }
350}
351
352pub fn signs(input: &mut &str) -> InnerResult<isize> {
354 let mut minus_count = 0;
355 *input = input
356 .trim_start_matches(|c: char| {
357 if c == '-' {
358 minus_count += 1;
359 true
360 } else {
361 c == '+' || c.is_whitespace()
362 }
363 })
364 .trim_start();
365 Ok(if minus_count % 2 == 0 { 1 } else { -1 })
366}
367
368pub fn hexadecimal(input: &mut &str) -> usize {
370 let mut number = 0;
371 *input = input.trim_start_matches(|c: char| {
372 if c.is_ascii_alphanumeric() && c < 'G' {
373 number =
374 number * 16 + c.to_digit(16).expect("the character is a valid hex digit") as usize;
375 true
376 } else {
377 false
378 }
379 });
380 one_optional_space(input);
381
382 number
383}
384
385pub fn floating_point(input: &mut &str) -> InnerResult<f32> {
387 let signum = signs(input)?;
388
389 let mut number = 0.;
390 *input = input.trim_start_matches(|c: char| {
391 if c.is_ascii_digit() {
392 number = number * 10. + (c as u8 - b'0') as f32;
393 true
394 } else {
395 false
396 }
397 });
398
399 if let Some(stripped_decimal_point) = input.strip_prefix(|c| c == '.' || c == ',') {
400 let mut decimal = 0.;
401 let mut decimal_divisor = 1.;
402 *input = stripped_decimal_point.trim_start_matches(|c: char| {
403 if c.is_ascii_digit() {
404 decimal = decimal * 10. + (c as u8 - b'0') as f32;
405 decimal_divisor *= 10.;
406 true
407 } else {
408 false
409 }
410 });
411 number += decimal / decimal_divisor;
412 };
413
414 Ok(signum as f32 * number)
415}
416
417pub fn decimal(input: &mut &str) -> usize {
419 let mut number = 0;
420 *input = input.trim_start_matches(|c: char| {
421 if c.is_ascii_digit() {
422 number = number * 10 + (c as u8 - b'0') as usize;
423 true
424 } else {
425 false
426 }
427 });
428 one_optional_space(input);
429
430 number
431}
432
433pub fn octal(input: &mut &str) -> usize {
435 let mut number = 0;
436 *input = input.trim_start_matches(|c: char| {
437 if c.is_ascii_digit() {
438 number = number * 8 + (c as u8 - b'0') as usize;
439 true
440 } else {
441 false
442 }
443 });
444 one_optional_space(input);
445
446 number
447}
448
449pub fn one_optional_space(input: &mut &str) -> bool {
451 let mut chars = input.chars();
452 if chars.next().is_some_and(|c| c.is_whitespace()) {
453 *input = &input[1..];
454 true
455 } else {
456 false
457 }
458}
459
460pub fn token<'a>(input: &mut &'a str) -> InnerResult<Token<'a>> {
464 *input = input.trim_start();
465 match input.chars().next() {
466 Some('\\') => {
467 *input = &input[1..];
468 Ok(Token::ControlSequence(rhs_control_sequence(input)?))
469 }
470 Some('%') => {
471 let (_, rest) = input
472 .split_once('\n')
473 .unwrap_or(("", &input[input.len()..]));
474 *input = rest;
475 token(input)
476 }
477 Some(c) => {
478 let context = *input;
479 *input = input.split_at(c.len_utf8()).1;
480 Ok(Token::Character(CharToken::from_str(context)))
481 }
482 None => Err(ErrorKind::Token),
483 }
484}
485
486pub fn color(color: &str) -> Option<(u8, u8, u8)> {
487 match color.strip_prefix('#') {
488 Some(color) if color.len() == 6 => {
489 let r = u8::from_str_radix(&color[..2], 16).ok()?;
490 let g = u8::from_str_radix(&color[2..4], 16).ok()?;
491 let b = u8::from_str_radix(&color[4..], 16).ok()?;
492 Some((r, g, b))
493 }
494 None => dvipsnames_color(color).or_else(|| primitive_color(color)),
498 _ => None,
499 }
500}
501
502pub fn horizontal_lines(content: &mut &str) -> Box<[Line]> {
503 let mut horizontal_lines = Vec::new();
504 while let Some((rest, line)) = content
505 .trim_start()
506 .strip_prefix("\\hline")
507 .map(|rest| (rest, Line::Solid))
508 .or_else(|| {
509 content
510 .trim_start()
511 .strip_prefix("\\hdashline")
512 .map(|rest| (rest, Line::Dashed))
513 })
514 {
515 horizontal_lines.push(line);
516 *content = rest;
517 }
518
519 horizontal_lines.into()
520}
521
522#[cfg(test)]
523mod tests {
524 use crate::{
525 event::{Dimension, DimensionUnit, GroupingKind},
526 parser::{lex, Token},
527 };
528
529 #[test]
530 fn signs() {
531 let mut input = " + +- \\test";
532 assert_eq!(lex::signs(&mut input).unwrap(), -1);
533 assert_eq!(input, "\\test");
534 }
535
536 #[test]
537 fn no_signs() {
538 let mut input = "\\mycommand";
539 assert_eq!(lex::signs(&mut input).unwrap(), 1);
540 assert_eq!(input, "\\mycommand");
541 }
542
543 #[test]
546 fn definition_texbook() {
547 let mut input = "\\cs AB#1#2C$#3\\$ {#3{ab#1}#1 c##\\x #2}";
548
549 let (cs, param, repl) = lex::definition(&mut input).unwrap();
550 assert_eq!(cs, "cs");
551 assert_eq!(param, "AB#1#2C$#3\\$ ");
552 assert_eq!(repl, "#3{ab#1}#1 c##\\x #2");
553 assert_eq!(input, "");
554 }
555
556 #[test]
557 fn complex_definition() {
558 let mut input = r"\foo #1\test#2#{##\####2#2 \{{\}} \{\{\{} 5 + 5 = 10";
559 let (cs, param, repl) = lex::definition(&mut input).unwrap();
560
561 assert_eq!(cs, "foo");
562 assert_eq!(param, r"#1\test#2#");
563 assert_eq!(repl, r"##\####2#2 \{{\}} \{\{\{");
564 assert_eq!(input, " 5 + 5 = 10");
565 }
566
567 #[test]
568 fn let_assignment() {
569 let mut input = r"\foo = \bar";
570 let (cs, token) = lex::let_assignment(&mut input).unwrap();
571
572 assert_eq!(cs, "foo");
573 assert_eq!(token, Token::ControlSequence("bar"));
574 assert_eq!(input, "");
575 }
576
577 #[test]
578 fn futurelet_assignment() {
579 let mut input = r"\foo\bar\baz blah";
580 let (cs, token, rest) = lex::futurelet_assignment(&mut input).unwrap();
581
582 assert_eq!(cs, "foo");
583 assert_eq!(token, Token::ControlSequence("baz"));
584 assert_eq!(rest, r"\bar\baz blah");
585 }
586
587 #[test]
588 fn dimension() {
589 let mut input = "1.2pt";
590 let dim = lex::dimension(&mut input).unwrap();
591
592 assert_eq!(dim, Dimension::new(1.2, DimensionUnit::Pt));
593 assert_eq!(input, "");
594 }
595
596 #[test]
597 fn complex_glue() {
598 let mut input = "1.2 pt plus 3.4pt minus 5.6pt nope";
599 let glue = lex::glue(&mut input).unwrap();
600
601 assert_eq!(
602 glue,
603 (
604 Dimension::new(1.2, DimensionUnit::Pt),
605 Some(Dimension::new(3.4, DimensionUnit::Pt)),
606 Some(Dimension::new(5.6, DimensionUnit::Pt))
607 )
608 );
609 assert_eq!(input, "nope");
610 }
611
612 #[test]
613 fn dimension_or_braced_bare() {
614 let mut input = "1em rest";
615 let dim = lex::dimension_or_braced(&mut input).unwrap();
616 assert_eq!(dim, Dimension::new(1.0, DimensionUnit::Em));
617 assert_eq!(input, "rest");
618 }
619
620 #[test]
621 fn dimension_or_braced_with_braces() {
622 let mut input = "{1em} rest";
623 let dim = lex::dimension_or_braced(&mut input).unwrap();
624 assert_eq!(dim, Dimension::new(1.0, DimensionUnit::Em));
625 assert_eq!(input, " rest");
626 }
627
628 #[test]
629 fn glue_or_braced_with_braces() {
630 let mut input = "{1.2pt plus 3pt minus 1pt} rest";
631 let glue = lex::glue_or_braced(&mut input).unwrap();
632 assert_eq!(
633 glue,
634 (
635 Dimension::new(1.2, DimensionUnit::Pt),
636 Some(Dimension::new(3.0, DimensionUnit::Pt)),
637 Some(Dimension::new(1.0, DimensionUnit::Pt))
638 )
639 );
640 assert_eq!(input, " rest");
641 }
642
643 #[test]
644 fn numbers() {
645 let mut input = "123 -\"AEF24 --'3475 `\\a -.47";
646 assert_eq!(lex::integer(&mut input).unwrap(), 123);
647 assert_eq!(lex::integer(&mut input).unwrap(), -716580);
648 assert_eq!(lex::integer(&mut input).unwrap(), 1853);
649 assert_eq!(lex::integer(&mut input).unwrap(), 97);
650 assert_eq!(lex::floating_point(&mut input).unwrap(), -0.47);
651 assert_eq!(input, "");
652 }
653
654 #[test]
655 fn dvipsnames_colors() {
656 assert_eq!(lex::color("Apricot"), Some((0xFB, 0xB9, 0x82)));
658 assert_eq!(lex::color("Bittersweet"), Some((0xC0, 0x4F, 0x17)));
659 assert_eq!(lex::color("BlueGreen"), Some((0x00, 0xB3, 0xB8)));
660 assert_eq!(lex::color("WildStrawberry"), Some((0xEE, 0x29, 0x67)));
661 assert_eq!(lex::color("YellowOrange"), Some((0xFA, 0xA2, 0x1A)));
662
663 assert_eq!(lex::color("Blue"), Some((0x2D, 0x2F, 0x92)));
665 assert_eq!(lex::color("blue"), Some((0, 0, 255)));
666
667 assert_eq!(lex::color("#FBB982"), Some((0xFB, 0xB9, 0x82)));
669
670 assert_eq!(lex::color("NotARealColor"), None);
672 }
673
674 #[test]
675 fn group_content() {
676 let mut input =
677 "this { { is a test } to see if { the content parsing { of this } } } works }";
678 let content = lex::group_content(&mut input, GroupingKind::Normal).unwrap();
679 assert_eq!(
680 content,
681 "this { { is a test } to see if { the content parsing { of this } } } works "
682 );
683 }
684}