1use std::rc::Rc;
11
12use crate::Engine;
13use crate::cmd::Cmd;
14use crate::hash::{CsId, NO_CS, NULL_CS};
15use crate::token::{Token, TokenList};
16use crate::utf8x;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum LexState {
21 MidLine,
23 SkipBlanks,
25 NewLine,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum TokKind {
32 Parameter,
34 UTemplate,
36 VTemplate,
38 BackedUp,
40 BackedUpChar,
42 Inserted,
44 Macro,
46 OutputText,
48 EveryParText,
50 EveryMathText,
52 EveryDisplayText,
54 EveryHboxText,
56 EveryVboxText,
58 EveryJobText,
60 EveryCrText,
62 MarkText,
64 EveryEofText,
66 WriteText,
68 LocalText,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum CatMode {
75 Current,
77 Table(u32),
79 String,
81 Single(u8),
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum LuaLineKind {
89 Print,
91 Sprint,
93}
94
95#[derive(Clone, Debug)]
97pub enum LuaItem {
98 Text {
100 text: Vec<u8>,
102 kind: LuaLineKind,
104 catmode: CatMode,
106 },
107 Tokens(Vec<Token>),
109}
110
111#[derive(Clone, Debug)]
113pub enum LineSource {
114 File {
116 data: Rc<[u8]>,
118 pos: usize,
120 },
121 Terminal,
123 Read(u32),
125 Lines {
127 lines: std::collections::VecDeque<Vec<u8>>,
129 catcodes: Option<u32>,
131 pseudo: bool,
133 },
134 Lua(std::collections::VecDeque<LuaItem>),
136}
137
138#[derive(Clone, Debug)]
140pub struct TextLevel {
141 pub state: LexState,
143 pub buf: Vec<u32>,
146 pub loc: usize,
148 pub source: LineSource,
150 pub line: i32,
152 pub name: Vec<u8>,
154 pub is_input_file: bool,
156 pub eof_seen: bool,
158 pub grp_boundary: usize,
160 pub if_depth: usize,
162 pub catmode: CatMode,
164}
165
166#[derive(Clone, Debug)]
168pub struct TokLevel {
169 pub list: TokenList,
171 pub loc: usize,
173 pub kind: TokKind,
175 pub param_start: usize,
177 pub name: CsId,
179}
180
181#[derive(Clone, Debug)]
183pub enum Level {
184 Text(Box<TextLevel>),
186 Tokens(TokLevel),
188}
189
190#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
192pub enum ScannerStatus {
193 Normal,
195 Skipping,
197 Defining,
199 Matching,
201 Aligning,
203 Absorbing,
205}
206
207#[derive(Debug)]
209pub struct InputState {
210 pub stack: Vec<Level>,
212 pub param_stack: Vec<TokenList>,
214 pub align_state: i32,
216 pub scanner_status: ScannerStatus,
218 pub warning_index: CsId,
220 pub open_parens: usize,
222 pub force_eof: bool,
224 pub last_line: i32,
226}
227
228impl Default for InputState {
229 fn default() -> Self {
230 InputState {
231 stack: Vec::new(),
232 param_stack: Vec::new(),
233 align_state: 1_000_000,
234 scanner_status: ScannerStatus::Normal,
235 warning_index: NO_CS,
236 open_parens: 0,
237 force_eof: false,
238 last_line: 0,
239 }
240 }
241}
242
243pub const NO_EXPAND_FLAG: u32 = 0x11_0001;
246
247impl InputState {
248 pub fn in_open(&self) -> usize {
250 self.stack
251 .iter()
252 .filter(|l| matches!(l, Level::Text(t) if t.is_input_file))
253 .count()
254 }
255
256 pub fn cur_text(&self) -> Option<&TextLevel> {
258 self.stack.iter().rev().find_map(|l| match l {
259 Level::Text(t) => Some(&**t),
260 Level::Tokens(_) => None,
261 })
262 }
263
264 pub fn line(&self) -> i32 {
266 self.stack
267 .iter()
268 .rev()
269 .find_map(|l| match l {
270 Level::Text(t) if !matches!(t.source, LineSource::Read(_)) => Some(t.line),
271 _ => None,
272 })
273 .unwrap_or(0)
274 }
275
276 pub fn in_token_list(&self) -> bool {
278 matches!(self.stack.last(), Some(Level::Tokens(_)))
279 }
280
281 pub fn terminal_input(&self) -> bool {
283 matches!(self.stack.last(), Some(Level::Text(t)) if matches!(t.source, LineSource::Terminal))
284 }
285}
286
287pub fn next_line(data: &[u8], pos: usize) -> Option<(&[u8], usize)> {
290 if pos >= data.len() {
291 return None;
292 }
293 let rest = &data[pos..];
294 match rest.iter().position(|&b| b == b'\n' || b == b'\r') {
295 Some(i) => {
296 let mut next = pos + i + 1;
297 if rest[i] == b'\r' && rest.get(i + 1) == Some(&b'\n') {
298 next += 1;
299 }
300 Some((&rest[..i], next))
301 }
302 None => Some((rest, data.len())),
303 }
304}
305
306pub fn decode_line(bytes: &[u8]) -> Vec<u32> {
309 let mut end = bytes.len();
310 while end > 0 && bytes[end - 1] == b' ' {
311 end -= 1;
312 }
313 utf8x::chars(&bytes[..end])
314}
315
316fn is_hex(c: u32) -> bool {
317 matches!(c, 0x30..=0x39 | 0x61..=0x66)
318}
319
320fn hex_val(c: u32) -> u32 {
321 if c <= 0x39 { c - 0x30 } else { c - 0x61 + 10 }
322}
323
324enum Expanded {
326 None,
328 Char { c: u32, len: usize },
330 Bad { six: bool },
332}
333
334fn expanded_at(buf: &[u32], k: usize) -> Expanded {
337 let sup = buf[k];
338 if buf.get(k + 1) != Some(&sup) {
339 return Expanded::None;
340 }
341 let run = buf[k..].iter().take(6).take_while(|&&c| c == sup).count();
343 if run >= 4 {
344 let six = run == 6;
345 let n = if six { 6 } else { 4 };
346 let start = k + run;
347 let digits = buf.get(start..start + n);
348 return match digits {
349 Some(d) if d.iter().all(|&c| is_hex(c)) => {
350 let c = d.iter().fold(0u32, |acc, &c| acc * 16 + hex_val(c));
351 if c > 0x10_FFFF {
352 Expanded::Bad { six }
353 } else {
354 Expanded::Char { c, len: run + n }
355 }
356 }
357 _ => Expanded::Bad { six },
358 };
359 }
360 let Some(&c) = buf.get(k + 2) else {
362 return Expanded::None;
363 };
364 if c >= 0o200 {
365 return Expanded::None;
366 }
367 if is_hex(c)
368 && let Some(&cc) = buf.get(k + 3)
369 && is_hex(cc)
370 {
371 return Expanded::Char {
372 c: hex_val(c) * 16 + hex_val(cc),
373 len: 4,
374 };
375 }
376 Expanded::Char {
377 c: if c < 0o100 { c + 0o100 } else { c - 0o100 },
378 len: 3,
379 }
380}
381
382impl Engine {
383 pub fn begin_token_list(&mut self, list: TokenList, kind: TokKind) {
387 self.input.stack.push(Level::Tokens(TokLevel {
388 list,
389 loc: 0,
390 kind,
391 param_start: self.input.param_stack.len(),
392 name: NO_CS,
393 }));
394 }
395
396 pub fn begin_macro(&mut self, list: TokenList, start: usize, name: CsId, param_start: usize) {
399 self.input.stack.push(Level::Tokens(TokLevel {
400 list,
401 loc: start,
402 kind: TokKind::Macro,
403 param_start,
404 name,
405 }));
406 }
407
408 pub fn ins_list(&mut self, list: TokenList) {
410 self.begin_token_list(list, TokKind::Inserted);
411 }
412
413 pub fn end_token_list(&mut self) {
415 if let Some(Level::Tokens(t)) = self.input.stack.pop() {
416 match t.kind {
417 TokKind::Macro => {
418 self.input.param_stack.truncate(t.param_start);
419 }
420 TokKind::UTemplate => {
421 if self.input.align_state > 500_000 {
422 self.input.align_state = 0;
423 } else {
424 self.fatal_error("(interwoven alignment preambles are not allowed)");
425 }
426 }
427 _ => {}
428 }
429 } else {
430 debug_assert!(false, "end_token_list on a text level");
431 }
432 self.check_interrupt();
433 }
434
435 pub fn back_input(&mut self) {
437 while let Some(Level::Tokens(t)) = self.input.stack.last() {
439 if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
440 break;
441 }
442 self.end_token_list();
443 }
444 let tok = self.cur_tok;
445 if tok.0 < crate::token::CS_TOKEN_FLAG {
446 let cmd = tok.cmd_code();
447 if cmd == Cmd::RightBrace as u32 {
448 self.input.align_state += 1;
449 } else if cmd == Cmd::LeftBrace as u32 {
450 self.input.align_state -= 1;
451 }
452 }
453 let list: TokenList = Rc::from(vec![tok]);
454 self.input.stack.push(Level::Tokens(TokLevel {
455 list,
456 loc: 0,
457 kind: TokKind::BackedUp,
458 param_start: self.input.param_stack.len(),
459 name: NO_CS,
460 }));
461 }
462
463 pub fn back_input_inserted(&mut self) {
466 self.back_input();
467 if let Some(Level::Tokens(t)) = self.input.stack.last_mut() {
468 t.kind = TokKind::Inserted;
469 }
470 }
471
472 pub fn begin_file_reading(&mut self, source: LineSource, name: Vec<u8>, is_input_file: bool) {
474 self.input.stack.push(Level::Text(Box::new(TextLevel {
475 state: LexState::NewLine,
476 buf: Vec::new(),
477 loc: 0,
478 source,
479 line: 0,
480 name,
481 is_input_file,
482 eof_seen: false,
483 grp_boundary: self.save.cur_boundary,
484 if_depth: self.cond.stack.len(),
485 catmode: CatMode::Current,
486 })));
487 }
488
489 pub fn end_file_reading(&mut self) {
491 let lvl = self.input.stack.pop();
492 if let Some(Level::Text(t)) = lvl
493 && let LineSource::Read(n) = t.source
494 {
495 let _ = n;
496 }
497 }
498
499 fn read_next_line(&mut self) -> bool {
502 let end_line_char = self
503 .eqtb
504 .int(crate::primitives_gen::assign_int::ENDLINECHAR);
505 let Some(Level::Text(t)) = self.input.stack.last_mut() else {
506 return false;
507 };
508 let mut eol = true;
510 let line: Vec<u32> = match &mut t.source {
511 LineSource::File { data, pos } => match next_line(data, *pos) {
512 Some((bytes, next)) => {
513 let l = decode_line(bytes);
514 *pos = next;
515 l
516 }
517 None => return false,
518 },
519 LineSource::Lines { lines, pseudo, .. } => match lines.pop_front() {
520 Some(bytes) => {
521 eol = *pseudo || !lines.is_empty();
522 decode_line(&bytes)
523 }
524 None => return false,
525 },
526 LineSource::Lua(items) => {
527 let last = items.len() == 1;
528 match items.pop_front() {
529 Some(LuaItem::Text {
530 text,
531 kind,
532 catmode,
533 }) => {
534 t.catmode = catmode;
535 t.loc = 0;
536 if kind == LuaLineKind::Sprint {
537 t.buf = utf8x::chars(&text);
538 t.state = LexState::MidLine;
539 return true;
540 }
541 t.buf = decode_line(&text);
542 t.state = LexState::NewLine;
543 if !last && (0..=0x10_FFFF).contains(&end_line_char) {
544 t.buf.push(end_line_char as u32);
545 }
546 return true;
547 }
548 Some(LuaItem::Tokens(toks)) => {
549 t.buf.clear();
550 t.loc = 0;
551 self.pending_lua_tokens = Some(toks);
552 return true;
553 }
554 None => return false,
555 }
556 }
557 LineSource::Terminal | LineSource::Read(_) => return false,
558 };
559 t.buf = line;
560 if eol && (0..=0x10_FFFF).contains(&end_line_char) {
561 t.buf.push(end_line_char as u32);
562 }
563 t.loc = 0;
564 t.line += 1;
565 true
566 }
567
568 #[inline]
573 pub fn get_next(&mut self) {
574 if let Some(Level::Tokens(t)) = self.input.stack.last_mut()
578 && let Some(&tok) = t.list.get(t.loc)
579 {
580 if tok.is_cs() {
581 let cs = tok.cs_id();
582 let m = self.eqtb.meaning(cs);
583 if m.cmd < Cmd::TabMark || (m.cmd > Cmd::CarRet && !m.cmd.is_outer_or_special()) {
584 t.loc += 1;
585 self.cur_cs = cs;
586 self.cur_cmd = m.cmd;
587 self.cur_chr = m.chr;
588 return;
589 }
590 } else {
591 let cmd = tok.cmd();
592 if cmd > Cmd::CarRet || cmd < Cmd::MathShift {
593 t.loc += 1;
594 match cmd {
595 Cmd::LeftBrace => self.input.align_state += 1,
596 Cmd::RightBrace => self.input.align_state -= 1,
597 _ => {}
598 }
599 self.cur_cs = NO_CS;
600 self.cur_cmd = cmd;
601 self.cur_chr = tok.chr();
602 return;
603 }
604 }
605 }
606 self.get_next_slow();
607 }
608
609 #[inline(never)]
611 fn get_next_slow(&mut self) {
612 'restart: loop {
613 self.cur_cs = NO_CS;
614 let Some(top) = self.input.stack.last_mut() else {
615 self.cur_cmd = Cmd::Relax;
617 self.cur_chr = 0;
618 self.fatal_error("*** (job aborted, no legal \\end found)");
619 };
620 match top {
621 Level::Tokens(t) => {
622 if t.loc < t.list.len() {
623 let tok = t.list[t.loc];
624 t.loc += 1;
625 if tok.is_cs() {
626 let cs = tok.cs_id();
627 self.cur_cs = cs;
628 let m = self.eqtb.meaning(cs);
629 self.cur_cmd = m.cmd;
630 self.cur_chr = m.chr;
631 if self.cur_cmd.is_outer_or_special() {
632 if self.cur_cmd == Cmd::DontExpand {
633 let next = t.list[t.loc];
636 t.loc = t.list.len();
637 let cs = next.cs_id();
638 self.cur_cs = cs;
639 let m = self.eqtb.meaning(cs);
640 self.cur_cmd = m.cmd;
641 self.cur_chr = m.chr;
642 if self.cur_cmd > Cmd::MAX_COMMAND {
643 self.cur_cmd = Cmd::Relax;
644 self.cur_chr = NO_EXPAND_FLAG;
645 }
646 } else {
647 self.check_outer_validity();
648 }
649 }
650 } else {
651 self.cur_cmd = tok.cmd();
652 self.cur_chr = tok.chr();
653 match self.cur_cmd {
654 Cmd::LeftBrace => self.input.align_state += 1,
655 Cmd::RightBrace => self.input.align_state -= 1,
656 Cmd::CarRet => {
657 let p = self.input.param_stack
659 [t.param_start + self.cur_chr as usize - 1]
660 .clone();
661 self.begin_token_list(p, TokKind::Parameter);
662 continue 'restart;
663 }
664 _ => {}
665 }
666 }
667 } else {
668 self.end_token_list();
669 continue 'restart;
670 }
671 }
672 Level::Text(_) => {
673 if !self.get_next_from_text() {
674 continue 'restart;
675 }
676 }
677 }
678 if self.cur_cmd <= Cmd::CarRet
680 && self.cur_cmd >= Cmd::TabMark
681 && self.input.align_state == 0
682 {
683 self.insert_v_template();
684 continue 'restart;
685 }
686 return;
687 }
688 }
689
690 fn get_next_from_text(&mut self) -> bool {
693 loop {
694 let (c, catmode, state) = {
696 let Some(Level::Text(t)) = self.input.stack.last_mut() else {
697 return false;
698 };
699 if t.loc < t.buf.len() {
700 let c = t.buf[t.loc];
701 t.loc += 1;
702 (c, t.catmode, t.state)
703 } else {
704 if matches!(t.source, LineSource::Read(_)) {
705 self.cur_cmd = Cmd::Relax;
708 self.cur_chr = 0;
709 return true;
710 }
711 if !self.next_line_or_pop() {
712 return false;
713 }
714 continue;
715 }
716 };
717 self.cur_chr = c;
718 let mut c = c;
720 loop {
721 let cat = self.catcode_in_mode(c, catmode);
722 self.cur_cmd = Cmd::from_u8(cat);
723 match (cat, state) {
724 (9, _) | (10, LexState::SkipBlanks | LexState::NewLine) => break,
727 (0, _) => {
728 self.scan_control_sequence();
729 return true;
730 }
731 (13, _) => {
732 let cs = self.hash.active(c);
733 self.cur_cs = cs;
734 let m = self.eqtb.meaning(cs);
735 self.cur_cmd = m.cmd;
736 self.cur_chr = m.chr;
737 self.set_state(LexState::MidLine);
738 if self.cur_cmd.is_outer_or_special() {
739 self.check_outer_validity();
740 }
741 return true;
742 }
743 (7, _) => {
744 match self.expanded_in_line() {
745 Expanded::Char { c: nc, len } => {
746 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
747 t.loc += len - 1;
748 }
749 c = nc;
750 self.cur_chr = c;
751 continue; }
753 Expanded::Bad { six } => {
754 self.print_err(if six {
755 "^^^^^^ needs six hex digits"
756 } else {
757 "^^^^ needs four hex digits"
758 });
759 self.help(&[]);
760 self.error();
761 self.set_state(LexState::MidLine);
762 return true;
763 }
764 Expanded::None => {
765 self.set_state(LexState::MidLine);
766 return true;
767 }
768 }
769 }
770 (15, _) => {
771 self.print_err("Text line contains an invalid character");
772 self.help(&[
773 "A funny symbol that I can't read has just been input.",
774 "Continue, and I'll forget that it ever happened.",
775 ]);
776 self.err.deletions_allowed = false;
777 self.error();
778 self.err.deletions_allowed = true;
779 return false;
780 }
781 (10, LexState::MidLine) => {
782 self.set_state(LexState::SkipBlanks);
783 self.cur_chr = ' ' as u32;
784 return true;
785 }
786 (5, LexState::MidLine) => {
787 self.finish_line();
788 self.cur_cmd = Cmd::Spacer;
789 self.cur_chr = ' ' as u32;
790 return true;
791 }
792 (5, LexState::SkipBlanks) | (14, _) => {
793 self.finish_line();
794 break;
795 }
796 (5, LexState::NewLine) => {
797 self.finish_line();
798 let par = self.cs.par;
799 self.cur_cs = par;
800 let m = self.eqtb.meaning(par);
801 self.cur_cmd = m.cmd;
802 self.cur_chr = m.chr;
803 if self.cur_cmd.is_outer_or_special() {
804 self.check_outer_validity();
805 }
806 return true;
807 }
808 (1, s) => {
809 if s != LexState::MidLine {
810 self.set_state(LexState::MidLine);
811 }
812 self.input.align_state += 1;
813 return true;
814 }
815 (2, s) => {
816 if s != LexState::MidLine {
817 self.set_state(LexState::MidLine);
818 }
819 self.input.align_state -= 1;
820 return true;
821 }
822 (_, s) => {
823 if s != LexState::MidLine {
824 self.set_state(LexState::MidLine);
825 }
826 return true;
827 }
828 }
829 }
830 }
831 }
832
833 #[inline]
835 pub fn catcode_in_mode(&self, c: u32, mode: CatMode) -> u8 {
836 match mode {
837 CatMode::Current => self.eqtb.catcode(c),
838 CatMode::Table(t) => self.eqtb.catcode_in(t, c),
839 CatMode::String => {
840 if c == ' ' as u32 {
841 10
842 } else {
843 12
844 }
845 }
846 CatMode::Single(k) => k,
847 }
848 }
849
850 pub fn push_lua_output(&mut self, items: Vec<LuaItem>) {
852 if items.is_empty() {
853 return;
854 }
855 self.begin_file_reading(
856 LineSource::Lua(items.into_iter().collect()),
857 Vec::new(),
858 false,
859 );
860 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
861 t.line = self.input_line_for_lua;
862 }
863 }
864
865 fn set_state(&mut self, s: LexState) {
866 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
867 t.state = s;
868 }
869 }
870
871 fn finish_line(&mut self) {
872 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
873 t.loc = t.buf.len();
874 }
875 }
876
877 fn expanded_in_line(&self) -> Expanded {
879 let Some(Level::Text(t)) = self.input.stack.last() else {
880 return Expanded::None;
881 };
882 expanded_at(&t.buf, t.loc - 1)
883 }
884
885 fn next_line_or_pop(&mut self) -> bool {
889 let (is_terminal, is_read, is_file) = match self.input.stack.last() {
890 Some(Level::Text(t)) => (
891 matches!(t.source, LineSource::Terminal),
892 matches!(t.source, LineSource::Read(_)),
893 t.is_input_file,
894 ),
895 _ => return false,
896 };
897 if is_read {
898 return false;
899 }
900 if is_terminal {
901 if self.input.stack.len() > 1 {
903 self.end_file_reading();
905 return false;
906 }
907 self.terminal_exhausted();
908 return false;
909 }
910 let is_lua = matches!(
911 self.input.stack.last(),
912 Some(Level::Text(t)) if matches!(t.source, LineSource::Lua(_))
913 );
914 if !self.input.force_eof && self.read_next_line() {
915 if let Some(toks) = self.pending_lua_tokens.take() {
916 self.begin_token_list(std::rc::Rc::from(toks), TokKind::Inserted);
917 return false;
918 }
919 if !is_lua {
920 self.set_state(LexState::NewLine);
921 }
922 return true;
923 }
924 let is_pseudo = matches!(
926 self.input.stack.last(),
927 Some(Level::Text(t)) if matches!(t.source, LineSource::Lines { pseudo: true, .. })
928 );
929 if !self.input.force_eof && (is_file || is_pseudo) {
930 let every_eof = self
933 .eqtb
934 .toks_param(crate::primitives_gen::assign_toks::EVERYEOF)
935 .clone();
936 let seen = match self.input.stack.last_mut() {
937 Some(Level::Text(t)) => std::mem::replace(&mut t.eof_seen, true),
938 _ => true,
939 };
940 if !seen && !every_eof.is_empty() {
941 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
942 t.buf.clear();
943 t.loc = 0;
944 }
945 self.begin_token_list(every_eof, TokKind::EveryEofText);
946 return false;
947 }
948 }
949 if is_file {
950 self.printer.print_char(')' as u32);
951 self.input.open_parens -= 1;
952 self.printer.update_terminal();
953 self.file_closed_checks();
954 }
955 self.input.force_eof = false;
956 let check = match self.input.stack.last() {
959 Some(Level::Text(t)) => match &t.source {
960 LineSource::Lua(_) => false,
961 LineSource::Lines { pseudo, .. } => *pseudo,
962 _ => true,
963 },
964 _ => true,
965 };
966 self.end_file_reading();
967 if check {
968 self.check_outer_validity();
969 }
970 false
971 }
972
973 fn scan_control_sequence(&mut self) {
975 'start_cs: loop {
976 let Some(Level::Text(t)) = self.input.stack.last() else {
977 return;
978 };
979 let len = t.buf.len();
980 let loc = t.loc;
981 let catmode = t.catmode;
982 if loc >= len {
983 self.cur_cs = NULL_CS;
984 break;
985 }
986 let mut k = loc;
987 let mut ch = t.buf[k];
988 let mut cat = self.catcode_in_mode(ch, catmode);
989 k += 1;
990 let state = if cat == 11 || cat == 10 {
991 LexState::SkipBlanks
992 } else {
993 LexState::MidLine
994 };
995 if cat == 11 && k < len {
996 loop {
998 ch = t.buf[k];
999 cat = self.catcode_in_mode(ch, catmode);
1000 k += 1;
1001 if cat != 11 || k >= len {
1002 break;
1003 }
1004 }
1005 if self.reduce_expanded(k, ch, cat) {
1006 continue 'start_cs;
1007 }
1008 if cat != 11 {
1009 k -= 1;
1010 }
1011 if k > loc + 1 {
1012 let mut name = Vec::with_capacity(k - loc);
1013 if let Some(Level::Text(t)) = self.input.stack.last() {
1014 for &c in &t.buf[loc..k] {
1015 utf8x::push(&mut name, c);
1016 }
1017 }
1018 self.cur_cs = self.hash.lookup_or_insert(&name);
1019 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1020 t.loc = k;
1021 t.state = state;
1022 }
1023 break;
1024 }
1025 } else if self.reduce_expanded(k, ch, cat) {
1026 continue 'start_cs;
1027 }
1028 let c = match self.input.stack.last() {
1029 Some(Level::Text(t)) => t.buf[loc],
1030 _ => return,
1031 };
1032 self.cur_cs = self.hash.single(c);
1033 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1034 t.loc = loc + 1;
1035 t.state = state;
1036 }
1037 break;
1038 }
1039 let m = self.eqtb.meaning(self.cur_cs);
1040 self.cur_cmd = m.cmd;
1041 self.cur_chr = m.chr;
1042 if self.cur_cmd.is_outer_or_special() {
1043 self.check_outer_validity();
1044 }
1045 }
1046
1047 fn reduce_expanded(&mut self, k: usize, ch: u32, cat: u8) -> bool {
1051 if cat != 7 {
1052 return false;
1053 }
1054 let Some(Level::Text(t)) = self.input.stack.last_mut() else {
1055 return false;
1056 };
1057 if t.buf.get(k) != Some(&ch) {
1058 return false;
1059 }
1060 match expanded_at(&t.buf, k - 1) {
1061 Expanded::Char { c, len } => {
1062 t.buf[k - 1] = c;
1063 t.buf.drain(k..k - 1 + len);
1064 true
1065 }
1066 _ => false,
1067 }
1068 }
1069
1070 pub fn get_token(&mut self) {
1072 self.err.no_new_control_sequence = false;
1073 self.get_next();
1074 self.err.no_new_control_sequence = true;
1075 self.cur_tok = self.make_cur_tok();
1076 }
1077
1078 #[inline]
1080 pub fn make_cur_tok(&self) -> Token {
1081 if self.cur_cs == NO_CS {
1082 Token::char(self.cur_cmd, self.cur_chr)
1083 } else {
1084 Token::cs(self.cur_cs)
1085 }
1086 }
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091 use super::*;
1092
1093 #[test]
1094 fn line_splitting() {
1095 let data = b"ab \r\ncd\rx\n\nlast";
1096 let mut pos = 0;
1097 let mut lines = Vec::new();
1098 while let Some((l, next)) = next_line(data, pos) {
1099 lines.push(decode_line(l));
1100 pos = next;
1101 }
1102 let strs: Vec<String> = lines
1103 .iter()
1104 .map(|l| l.iter().map(|&c| char::from_u32(c).unwrap()).collect())
1105 .collect();
1106 assert_eq!(strs, vec!["ab", "cd", "x", "", "last"]);
1107 assert_eq!(decode_line(b"x\t "), vec!['x' as u32, 9]);
1108 }
1109
1110 #[test]
1111 fn expanded_chars() {
1112 let s = |x: &str| x.chars().map(|c| c as u32).collect::<Vec<_>>();
1113 assert!(matches!(
1114 expanded_at(&s("^^A"), 0),
1115 Expanded::Char { c: 1, len: 3 }
1116 ));
1117 assert!(matches!(
1118 expanded_at(&s("^^e9"), 0),
1119 Expanded::Char { c: 0xE9, len: 4 }
1120 ));
1121 assert!(matches!(
1122 expanded_at(&s("^^?"), 0),
1123 Expanded::Char { c: 127, len: 3 }
1124 ));
1125 assert!(matches!(
1126 expanded_at(&s("^^^^00e9"), 0),
1127 Expanded::Char { c: 0xE9, len: 8 }
1128 ));
1129 assert!(matches!(
1130 expanded_at(&s("^^^^^^01f600"), 0),
1131 Expanded::Char {
1132 c: 0x1F600,
1133 len: 12
1134 }
1135 ));
1136 assert!(matches!(
1137 expanded_at(&s("^^^^zz"), 0),
1138 Expanded::Bad { six: false }
1139 ));
1140 assert!(matches!(expanded_at(&s("^x"), 0), Expanded::None));
1141 }
1142}