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 pub sync: (u32, i32),
167}
168
169#[derive(Clone, Debug)]
171pub struct TokLevel {
172 pub list: TokenList,
174 pub loc: usize,
176 pub kind: TokKind,
178 pub param_start: usize,
180 pub name: CsId,
182}
183
184#[derive(Clone, Debug)]
186pub enum Level {
187 Text(Box<TextLevel>),
189 Tokens(TokLevel),
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
195pub enum ScannerStatus {
196 Normal,
198 Skipping,
200 Defining,
202 Matching,
204 Aligning,
206 Absorbing,
208}
209
210const INPUT_STACK_SIZE: usize = 10_000;
213
214pub const MAX_IN_OPEN: usize = 15;
217
218fn counts_as_open(source: &LineSource) -> bool {
223 match source {
224 LineSource::File { .. } | LineSource::Read(_) | LineSource::Lua(_) => true,
225 LineSource::Lines { pseudo, .. } => *pseudo,
226 _ => false,
227 }
228}
229
230const SPARE_TOKENS: usize = 16;
232
233#[derive(Debug)]
235pub struct InputState {
236 pub stack: Vec<Level>,
238 pub param_stack: Vec<TokenList>,
240 pub align_state: i32,
242 pub scanner_status: ScannerStatus,
244 pub warning_index: CsId,
246 pub open_parens: usize,
248 pub force_eof: bool,
250 pub last_line: i32,
252 pub in_open: usize,
255 pub spare_tokens: Vec<TokenList>,
258}
259
260impl Default for InputState {
261 fn default() -> Self {
262 InputState {
263 stack: Vec::new(),
264 param_stack: Vec::new(),
265 align_state: 1_000_000,
266 scanner_status: ScannerStatus::Normal,
267 warning_index: NO_CS,
268 open_parens: 0,
269 force_eof: false,
270 last_line: 0,
271 in_open: 0,
272 spare_tokens: Vec::new(),
273 }
274 }
275}
276
277pub const NO_EXPAND_FLAG: u32 = 0x11_0001;
280
281impl InputState {
282 pub fn in_open(&self) -> usize {
284 self.stack
285 .iter()
286 .filter(|l| matches!(l, Level::Text(t) if t.is_input_file))
287 .count()
288 }
289
290 pub fn cur_text(&self) -> Option<&TextLevel> {
292 self.stack.iter().rev().find_map(|l| match l {
293 Level::Text(t) => Some(&**t),
294 Level::Tokens(_) => None,
295 })
296 }
297
298 pub fn line(&self) -> i32 {
300 self.stack
301 .iter()
302 .rev()
303 .find_map(|l| match l {
304 Level::Text(t) if !matches!(t.source, LineSource::Read(_)) => Some(t.line),
305 _ => None,
306 })
307 .unwrap_or(0)
308 }
309
310 pub fn in_token_list(&self) -> bool {
312 matches!(self.stack.last(), Some(Level::Tokens(_)))
313 }
314
315 pub fn terminal_input(&self) -> bool {
317 matches!(self.stack.last(), Some(Level::Text(t)) if matches!(t.source, LineSource::Terminal))
318 }
319}
320
321pub fn next_line(data: &[u8], pos: usize) -> Option<(&[u8], usize)> {
324 if pos >= data.len() {
325 return None;
326 }
327 let rest = &data[pos..];
328 match rest.iter().position(|&b| b == b'\n' || b == b'\r') {
329 Some(i) => {
330 let mut next = pos + i + 1;
331 if rest[i] == b'\r' && rest.get(i + 1) == Some(&b'\n') {
332 next += 1;
333 }
334 Some((&rest[..i], next))
335 }
336 None => Some((rest, data.len())),
337 }
338}
339
340pub fn decode_line(bytes: &[u8]) -> Vec<u32> {
343 let mut end = bytes.len();
344 while end > 0 && bytes[end - 1] == b' ' {
345 end -= 1;
346 }
347 utf8x::chars_marking_invalid(&bytes[..end])
348}
349
350fn is_hex(c: u32) -> bool {
351 matches!(c, 0x30..=0x39 | 0x61..=0x66)
352}
353
354fn hex_val(c: u32) -> u32 {
355 if c <= 0x39 { c - 0x30 } else { c - 0x61 + 10 }
356}
357
358enum Expanded {
360 None,
362 Char { c: u32, len: usize },
364 Bad { six: bool, short: bool },
367}
368
369fn expanded_at(buf: &[u32], k: usize) -> Expanded {
372 let sup = buf[k];
373 if buf.get(k + 1) != Some(&sup) {
374 return Expanded::None;
375 }
376 let run = buf[k..].iter().take(6).take_while(|&&c| c == sup).count();
378 if run >= 4 {
379 let six = run == 6;
380 let n = if six { 6 } else { 4 };
381 let start = k + run;
382 let digits = buf.get(start..start + n);
383 return match digits {
384 Some(d) if d.iter().all(|&c| is_hex(c)) => {
385 let c = d.iter().fold(0u32, |acc, &c| acc * 16 + hex_val(c));
386 if c > 0x10_FFFF {
387 Expanded::Bad { six, short: false }
388 } else {
389 Expanded::Char { c, len: run + n }
390 }
391 }
392 _ => Expanded::Bad {
393 six,
394 short: start + n > buf.len(),
395 },
396 };
397 }
398 let Some(&c) = buf.get(k + 2) else {
400 return Expanded::None;
401 };
402 if c >= 0o200 {
403 return Expanded::None;
404 }
405 if is_hex(c)
406 && let Some(&cc) = buf.get(k + 3)
407 && is_hex(cc)
408 {
409 return Expanded::Char {
410 c: hex_val(c) * 16 + hex_val(cc),
411 len: 4,
412 };
413 }
414 Expanded::Char {
415 c: if c < 0o100 { c + 0o100 } else { c - 0o100 },
416 len: 3,
417 }
418}
419
420impl Engine {
421 pub fn begin_token_list(&mut self, list: TokenList, kind: TokKind) {
425 let name = match kind {
428 TokKind::OutputText => Some("output"),
429 TokKind::EveryParText => Some("everypar"),
430 TokKind::EveryMathText => Some("everymath"),
431 TokKind::EveryDisplayText => Some("everydisplay"),
432 TokKind::EveryHboxText => Some("everyhbox"),
433 TokKind::EveryVboxText => Some("everyvbox"),
434 TokKind::EveryJobText => Some("everyjob"),
435 TokKind::EveryCrText => Some("everycr"),
436 TokKind::EveryEofText => Some("everyeof"),
437 TokKind::MarkText => Some("mark"),
438 TokKind::WriteText => Some("write"),
439 _ => None,
440 };
441 if let Some(name) = name
442 && self
443 .eqtb
444 .int(crate::primitives_gen::assign_int::TRACINGMACROS)
445 > 1
446 {
447 self.begin_diagnostic();
448 self.printer.print_nl("");
449 self.print_esc_str(name);
450 self.printer.print_str("->");
451 self.token_show_list(&list, 0, list.len(), 10_000_000);
452 self.end_diagnostic(false);
453 }
454 self.push_input(Level::Tokens(TokLevel {
455 list,
456 loc: 0,
457 kind,
458 param_start: self.input.param_stack.len(),
459 name: NO_CS,
460 }));
461 }
462
463 fn push_input(&mut self, level: Level) {
466 if self.input.stack.len() > INPUT_STACK_SIZE {
467 self.overflow("input stack size", INPUT_STACK_SIZE as i64);
468 }
469 self.input.stack.push(level);
470 }
471
472 pub fn begin_macro(&mut self, list: TokenList, start: usize, name: CsId, param_start: usize) {
475 self.push_input(Level::Tokens(TokLevel {
476 list,
477 loc: start,
478 kind: TokKind::Macro,
479 param_start,
480 name,
481 }));
482 }
483
484 pub fn ins_list(&mut self, list: TokenList) {
486 self.begin_token_list(list, TokKind::Inserted);
487 }
488
489 pub fn end_token_list(&mut self) {
491 if let Some(Level::Tokens(t)) = self.input.stack.pop() {
492 if matches!(t.kind, TokKind::BackedUp | TokKind::Inserted)
493 && t.list.len() == 1
494 && Rc::strong_count(&t.list) == 1
495 && self.input.spare_tokens.len() < SPARE_TOKENS
496 {
497 self.input.spare_tokens.push(t.list);
498 self.check_interrupt();
499 return;
500 }
501 match t.kind {
502 TokKind::Macro => {
503 self.input.param_stack.truncate(t.param_start);
504 }
505 TokKind::UTemplate => {
506 if self.input.align_state > 500_000 {
507 self.input.align_state = 0;
508 } else {
509 self.fatal_error("(interwoven alignment preambles are not allowed)");
510 }
511 }
512 _ => {}
513 }
514 } else {
515 debug_assert!(false, "end_token_list on a text level");
516 }
517 self.check_interrupt();
518 }
519
520 pub fn back_input(&mut self) {
522 while let Some(Level::Tokens(t)) = self.input.stack.last() {
524 if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
525 break;
526 }
527 self.end_token_list();
528 }
529 let tok = self.cur_tok;
530 if tok.0 < crate::token::CS_TOKEN_FLAG {
531 let cmd = tok.cmd_code();
532 if cmd == Cmd::RightBrace as u32 {
533 self.input.align_state += 1;
534 } else if cmd == Cmd::LeftBrace as u32 {
535 self.input.align_state -= 1;
536 }
537 }
538 let list = match self.input.spare_tokens.pop() {
539 Some(mut list) => {
540 if let Some(slot) = Rc::get_mut(&mut list) {
542 slot[0] = tok;
543 list
544 } else {
545 Rc::from([tok])
546 }
547 }
548 None => Rc::from([tok]),
549 };
550 self.push_input(Level::Tokens(TokLevel {
551 list,
552 loc: 0,
553 kind: TokKind::BackedUp,
554 param_start: self.input.param_stack.len(),
555 name: NO_CS,
556 }));
557 }
558
559 pub fn back_input_inserted(&mut self) {
562 self.back_input();
563 if let Some(Level::Tokens(t)) = self.input.stack.last_mut() {
564 t.kind = TokKind::Inserted;
565 }
566 }
567
568 pub fn begin_file_reading(&mut self, source: LineSource, name: Vec<u8>, is_input_file: bool) {
571 if counts_as_open(&source) {
572 self.check_in_open();
573 self.input.in_open += 1;
574 }
575 self.push_input(Level::Text(Box::new(TextLevel {
576 state: LexState::NewLine,
577 buf: Vec::new(),
578 loc: 0,
579 source,
580 line: 0,
581 name,
582 is_input_file,
583 eof_seen: false,
584 grp_boundary: self.save.cur_boundary,
585 if_depth: self.cond.stack.len(),
586 catmode: CatMode::Current,
587 sync: (0, 0),
588 })));
589 self.sync_input_changed();
590 }
591
592 pub(crate) fn check_in_open(&mut self) {
595 if self.input.in_open >= MAX_IN_OPEN {
596 self.overflow("text input levels", MAX_IN_OPEN as i64);
597 }
598 }
599
600 pub fn end_file_reading(&mut self) {
602 let lvl = self.input.stack.pop();
603 if let Some(Level::Text(t)) = lvl
604 && counts_as_open(&t.source)
605 {
606 self.input.in_open = self.input.in_open.saturating_sub(1);
607 }
608 self.sync_input_changed();
609 }
610
611 fn read_next_line(&mut self) -> bool {
614 self.check_begun();
615 let end_line_char = self
616 .eqtb
617 .int(crate::primitives_gen::assign_int::ENDLINECHAR);
618 let Some(Level::Text(t)) = self.input.stack.last_mut() else {
619 return false;
620 };
621 let mut eol = true;
623 let line: Vec<u32> = match &mut t.source {
624 LineSource::File { data, pos } => match next_line(data, *pos) {
625 Some((bytes, next)) => {
626 let l = decode_line(bytes);
627 *pos = next;
628 l
629 }
630 None => return false,
631 },
632 LineSource::Lines { lines, pseudo, .. } => match lines.pop_front() {
633 Some(bytes) => {
634 eol = *pseudo || !lines.is_empty();
635 decode_line(&bytes)
636 }
637 None => return false,
638 },
639 LineSource::Lua(items) => {
640 let last = items.len() == 1;
641 match items.pop_front() {
642 Some(LuaItem::Text {
643 text,
644 kind,
645 catmode,
646 }) => {
647 t.catmode = catmode;
648 t.loc = 0;
649 t.sync.1 += 1;
650 t.line += 1;
654 if kind == LuaLineKind::Sprint {
655 t.buf = utf8x::chars(&text);
656 t.state = LexState::MidLine;
657 self.sync_input_changed();
658 return true;
659 }
660 t.buf = decode_line(&text);
661 t.state = LexState::NewLine;
662 if !last && (0..=0x10_FFFF).contains(&end_line_char) {
663 t.buf.push(end_line_char as u32);
664 }
665 self.sync_input_changed();
666 return true;
667 }
668 Some(LuaItem::Tokens(toks)) => {
669 t.buf.clear();
670 t.loc = 0;
671 t.line += 1;
672 self.pending_lua_tokens = Some(toks);
673 return true;
674 }
675 None => return false,
676 }
677 }
678 LineSource::Terminal | LineSource::Read(_) => return false,
679 };
680 t.buf = line;
681 if eol && (0..=0x10_FFFF).contains(&end_line_char) {
682 t.buf.push(end_line_char as u32);
683 }
684 t.loc = 0;
685 t.line += 1;
686 t.sync.1 = t.line;
687 self.sync_input_changed();
688 true
689 }
690
691 #[inline]
696 pub fn get_next(&mut self) {
697 if let Some(Level::Tokens(t)) = self.input.stack.last_mut()
701 && let Some(&tok) = t.list.get(t.loc)
702 {
703 if tok.is_cs() {
704 let cs = tok.cs_id();
705 let m = self.eqtb.meaning(cs);
706 if m.cmd < Cmd::TabMark || (m.cmd > Cmd::CarRet && !m.cmd.is_outer_or_special()) {
707 t.loc += 1;
708 self.cur_cs = cs;
709 self.cur_cmd = m.cmd;
710 self.cur_chr = m.chr;
711 return;
712 }
713 } else {
714 let cmd = tok.cmd();
715 if cmd > Cmd::CarRet || cmd < Cmd::MathShift {
716 t.loc += 1;
717 match cmd {
718 Cmd::LeftBrace => self.input.align_state += 1,
719 Cmd::RightBrace => self.input.align_state -= 1,
720 _ => {}
721 }
722 self.cur_cs = NO_CS;
723 self.cur_cmd = cmd;
724 self.cur_chr = tok.chr();
725 return;
726 }
727 }
728 }
729 self.get_next_slow();
730 }
731
732 #[inline(never)]
734 fn get_next_slow(&mut self) {
735 'restart: loop {
736 self.cur_cs = NO_CS;
737 let Some(top) = self.input.stack.last_mut() else {
738 self.cur_cmd = Cmd::Relax;
740 self.cur_chr = 0;
741 self.fatal_error("*** (job aborted, no legal \\end found)");
742 };
743 match top {
744 Level::Tokens(t) => {
745 if t.loc < t.list.len() {
746 let tok = t.list[t.loc];
747 t.loc += 1;
748 if tok.is_cs() {
749 let cs = tok.cs_id();
750 self.cur_cs = cs;
751 let m = self.eqtb.meaning(cs);
752 self.cur_cmd = m.cmd;
753 self.cur_chr = m.chr;
754 if self.cur_cmd.is_outer_or_special() {
755 if self.cur_cmd == Cmd::DontExpand {
756 let next = t.list[t.loc];
759 t.loc = t.list.len();
760 let cs = next.cs_id();
761 self.cur_cs = cs;
762 let m = self.eqtb.meaning(cs);
763 self.cur_cmd = m.cmd;
764 self.cur_chr = m.chr;
765 if self.cur_cmd > Cmd::MAX_COMMAND {
766 self.cur_cmd = Cmd::Relax;
767 self.cur_chr = NO_EXPAND_FLAG;
768 }
769 } else {
770 self.check_outer_validity();
771 }
772 }
773 } else {
774 self.cur_cmd = tok.cmd();
775 self.cur_chr = tok.chr();
776 match self.cur_cmd {
777 Cmd::LeftBrace => self.input.align_state += 1,
778 Cmd::RightBrace => self.input.align_state -= 1,
779 Cmd::CarRet => {
780 let p = (self.cur_chr as usize)
786 .checked_sub(1)
787 .and_then(|i| self.input.param_stack.get(t.param_start + i))
788 .cloned();
789 if let Some(p) = p {
790 self.begin_token_list(p, TokKind::Parameter);
791 }
792 continue 'restart;
793 }
794 _ => {}
795 }
796 }
797 } else {
798 self.end_token_list();
799 continue 'restart;
800 }
801 }
802 Level::Text(_) => {
803 if !self.get_next_from_text() {
804 continue 'restart;
805 }
806 }
807 }
808 if self.cur_cmd <= Cmd::CarRet
810 && self.cur_cmd >= Cmd::TabMark
811 && self.input.align_state == 0
812 {
813 self.insert_v_template();
814 continue 'restart;
815 }
816 return;
817 }
818 }
819
820 fn get_next_from_text(&mut self) -> bool {
823 loop {
824 let (c, catmode, state) = {
826 let Some(Level::Text(t)) = self.input.stack.last_mut() else {
827 return false;
828 };
829 if t.loc < t.buf.len() {
830 let c = t.buf[t.loc];
831 t.loc += 1;
832 (c, t.catmode, t.state)
833 } else {
834 if matches!(t.source, LineSource::Read(_)) {
835 self.cur_cmd = Cmd::Relax;
838 self.cur_chr = 0;
839 return true;
840 }
841 if !self.next_line_or_pop() {
842 return false;
843 }
844 continue;
845 }
846 };
847 let c = if utf8x::is_raw(c) {
848 self.invalid_utf8_sequence()
849 } else {
850 c
851 };
852 self.cur_chr = c;
853 let mut c = c;
855 loop {
856 let cat = self.catcode_in_mode(c, catmode);
857 self.cur_cmd = Cmd::from_u8(cat);
858 match (cat, state) {
859 (9, _) | (10, LexState::SkipBlanks | LexState::NewLine) => break,
862 (0, _) => {
863 self.scan_control_sequence();
864 return true;
865 }
866 (13, _) => {
867 let cs = self.hash.active(c);
868 self.cur_cs = cs;
869 let m = self.eqtb.meaning(cs);
870 self.cur_cmd = m.cmd;
871 self.cur_chr = m.chr;
872 self.set_state(LexState::MidLine);
873 if self.cur_cmd.is_outer_or_special() {
874 self.check_outer_validity();
875 }
876 return true;
877 }
878 (7, _) => {
879 match self.expanded_in_line() {
880 Expanded::Char { c: nc, len } => {
881 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
882 t.loc += len - 1;
883 }
884 c = nc;
885 self.cur_chr = c;
886 continue; }
888 Expanded::Bad { six, short } => {
889 self.print_err(match (six, short) {
890 (true, false) => "^^^^^^ needs six hex digits",
891 (true, true) => "^^^^^^ needs six hex digits, end of input",
892 (false, false) => "^^^^ needs four hex digits",
893 (false, true) => "^^^^ needs four hex digits, end of input",
894 });
895 self.help(&[]);
896 self.error();
897 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
901 t.loc += 2;
902 }
903 c = if c < 0o100 { c + 0o100 } else { c - 0o100 };
904 self.cur_chr = c;
905 continue; }
907 Expanded::None => {
908 self.set_state(LexState::MidLine);
909 return true;
910 }
911 }
912 }
913 (15, _) => {
914 self.print_err("Text line contains an invalid character");
915 self.help(&[
916 "A funny symbol that I can't read has just been input.",
917 "Continue, and I'll forget that it ever happened.",
918 ]);
919 self.err.deletions_allowed = false;
920 self.error();
921 self.err.deletions_allowed = true;
922 return false;
923 }
924 (10, LexState::MidLine) => {
925 self.set_state(LexState::SkipBlanks);
926 self.cur_chr = ' ' as u32;
927 return true;
928 }
929 (5, LexState::MidLine) => {
930 self.finish_line();
931 self.cur_cmd = Cmd::Spacer;
932 self.cur_chr = ' ' as u32;
933 return true;
934 }
935 (5, LexState::SkipBlanks) | (14, _) => {
936 self.finish_line();
937 break;
938 }
939 (5, LexState::NewLine) => {
940 self.finish_line();
941 let par = self.cs.par;
942 self.cur_cs = par;
943 let m = self.eqtb.meaning(par);
944 self.cur_cmd = m.cmd;
945 self.cur_chr = m.chr;
946 if self.cur_cmd.is_outer_or_special() {
947 self.check_outer_validity();
948 }
949 return true;
950 }
951 (1, s) => {
952 if s != LexState::MidLine {
953 self.set_state(LexState::MidLine);
954 }
955 self.input.align_state += 1;
956 return true;
957 }
958 (2, s) => {
959 if s != LexState::MidLine {
960 self.set_state(LexState::MidLine);
961 }
962 self.input.align_state -= 1;
963 return true;
964 }
965 (_, s) => {
966 if s != LexState::MidLine {
967 self.set_state(LexState::MidLine);
968 }
969 return true;
970 }
971 }
972 }
973 }
974 }
975
976 #[cold]
981 fn invalid_utf8_sequence(&mut self) -> u32 {
982 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
984 t.loc -= 1;
985 }
986 self.print_err("String contains an invalid utf-8 sequence");
987 self.help(&[
988 "A funny symbol that I can't read has just been (re)read.",
989 "Just continue, I'll change it to 0xFFFD.",
990 ]);
991 self.error();
992 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
993 let mut bytes = 0;
994 while bytes < 3 && t.loc < t.buf.len() {
995 let c = t.buf[t.loc];
996 bytes += if utf8x::is_raw(c) { 1 } else { utf8x::len(c) };
997 t.loc += 1;
998 }
999 }
1000 0xFFFD
1001 }
1002
1003 #[inline]
1005 pub fn catcode_in_mode(&self, c: u32, mode: CatMode) -> u8 {
1006 match mode {
1007 CatMode::Current => self.eqtb.catcode(c),
1008 CatMode::Table(t) => self.eqtb.catcode_in(t, c),
1009 CatMode::String => {
1010 if c == ' ' as u32 {
1011 10
1012 } else {
1013 12
1014 }
1015 }
1016 CatMode::Single(k) => k,
1017 }
1018 }
1019
1020 pub fn push_lua_output(&mut self, items: Vec<LuaItem>) {
1022 if items.is_empty() {
1023 return;
1024 }
1025 self.begin_file_reading(
1026 LineSource::Lua(items.into_iter().collect()),
1027 Vec::new(),
1028 false,
1029 );
1030 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1031 t.line = self.input_line_for_lua;
1032 }
1033 }
1034
1035 fn set_state(&mut self, s: LexState) {
1036 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1037 t.state = s;
1038 }
1039 }
1040
1041 fn finish_line(&mut self) {
1042 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1043 t.loc = t.buf.len();
1044 }
1045 }
1046
1047 fn expanded_in_line(&self) -> Expanded {
1049 let Some(Level::Text(t)) = self.input.stack.last() else {
1050 return Expanded::None;
1051 };
1052 expanded_at(&t.buf, t.loc - 1)
1053 }
1054
1055 fn next_line_or_pop(&mut self) -> bool {
1059 let (is_terminal, is_read, is_file) = match self.input.stack.last() {
1060 Some(Level::Text(t)) => (
1061 matches!(t.source, LineSource::Terminal),
1062 matches!(t.source, LineSource::Read(_)),
1063 t.is_input_file,
1064 ),
1065 _ => return false,
1066 };
1067 if is_read {
1068 return false;
1069 }
1070 if is_terminal {
1071 if self.input.stack.len() > 1 {
1073 self.end_file_reading();
1075 return false;
1076 }
1077 self.terminal_exhausted();
1078 return false;
1079 }
1080 let is_lua = matches!(
1081 self.input.stack.last(),
1082 Some(Level::Text(t)) if matches!(t.source, LineSource::Lua(_))
1083 );
1084 if !self.input.force_eof && self.read_next_line() {
1085 if let Some(toks) = self.pending_lua_tokens.take() {
1086 self.begin_token_list(std::rc::Rc::from(toks), TokKind::Inserted);
1087 return false;
1088 }
1089 if !is_lua {
1090 self.set_state(LexState::NewLine);
1091 }
1092 return true;
1093 }
1094 let is_pseudo = matches!(
1096 self.input.stack.last(),
1097 Some(Level::Text(t)) if matches!(t.source, LineSource::Lines { pseudo: true, .. })
1098 );
1099 if !self.input.force_eof && (is_file || is_pseudo) {
1100 let every_eof = self
1103 .eqtb
1104 .toks_param(crate::primitives_gen::assign_toks::EVERYEOF)
1105 .clone();
1106 let seen = match self.input.stack.last_mut() {
1107 Some(Level::Text(t)) => std::mem::replace(&mut t.eof_seen, true),
1108 _ => true,
1109 };
1110 if !seen && !every_eof.is_empty() {
1111 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1112 t.buf.clear();
1113 t.loc = 0;
1114 }
1115 self.begin_token_list(every_eof, TokKind::EveryEofText);
1116 return false;
1117 }
1118 }
1119 if is_file {
1120 self.printer.print_char(')' as u32);
1121 if is_pseudo {
1122 self.printer.print_char(')' as u32);
1125 }
1126 self.input.open_parens -= 1;
1127 self.printer.update_terminal();
1128 self.file_closed_checks();
1129 }
1130 self.input.force_eof = false;
1131 let check = match self.input.stack.last() {
1134 Some(Level::Text(t)) => match &t.source {
1135 LineSource::Lua(_) => false,
1136 LineSource::Lines { pseudo, .. } => *pseudo,
1137 _ => true,
1138 },
1139 _ => true,
1140 };
1141 self.end_file_reading();
1142 if check {
1143 self.check_outer_validity();
1144 }
1145 false
1146 }
1147
1148 fn scan_control_sequence(&mut self) {
1150 'start_cs: loop {
1151 let Some(Level::Text(t)) = self.input.stack.last() else {
1152 return;
1153 };
1154 let len = t.buf.len();
1155 let loc = t.loc;
1156 let catmode = t.catmode;
1157 if loc >= len {
1158 self.cur_cs = NULL_CS;
1159 break;
1160 }
1161 let mut k = loc;
1162 let mut ch = t.buf[k];
1163 let mut cat = self.catcode_in_mode(ch, catmode);
1164 k += 1;
1165 let state = if cat == 11 || cat == 10 {
1166 LexState::SkipBlanks
1167 } else {
1168 LexState::MidLine
1169 };
1170 if cat == 11 && k < len {
1171 loop {
1173 ch = t.buf[k];
1174 cat = self.catcode_in_mode(ch, catmode);
1175 k += 1;
1176 if cat != 11 || k >= len {
1177 break;
1178 }
1179 }
1180 if self.reduce_expanded(k, ch, cat) {
1181 continue 'start_cs;
1182 }
1183 if cat != 11 {
1184 k -= 1;
1185 }
1186 if k > loc + 1 {
1187 let mut name = Vec::with_capacity(k - loc);
1188 if let Some(Level::Text(t)) = self.input.stack.last() {
1189 for &c in &t.buf[loc..k] {
1190 utf8x::push(&mut name, c);
1191 }
1192 }
1193 self.cur_cs = self.hash.lookup_or_insert(&name);
1194 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1195 t.loc = k;
1196 t.state = state;
1197 }
1198 break;
1199 }
1200 } else if self.reduce_expanded(k, ch, cat) {
1201 continue 'start_cs;
1202 }
1203 let c = match self.input.stack.last() {
1204 Some(Level::Text(t)) => t.buf[loc],
1205 _ => return,
1206 };
1207 self.cur_cs = self.hash.single(c);
1208 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1209 t.loc = loc + 1;
1210 t.state = state;
1211 }
1212 break;
1213 }
1214 let m = self.eqtb.meaning(self.cur_cs);
1215 self.cur_cmd = m.cmd;
1216 self.cur_chr = m.chr;
1217 if self.cur_cmd.is_outer_or_special() {
1218 self.check_outer_validity();
1219 }
1220 }
1221
1222 fn reduce_expanded(&mut self, k: usize, ch: u32, cat: u8) -> bool {
1226 if cat != 7 {
1227 return false;
1228 }
1229 let Some(Level::Text(t)) = self.input.stack.last_mut() else {
1230 return false;
1231 };
1232 if t.buf.get(k) != Some(&ch) {
1233 return false;
1234 }
1235 match expanded_at(&t.buf, k - 1) {
1236 Expanded::Char { c, len } => {
1237 t.buf[k - 1] = c;
1238 t.buf.drain(k..k - 1 + len);
1239 true
1240 }
1241 _ => false,
1242 }
1243 }
1244
1245 pub fn get_token(&mut self) {
1247 self.err.no_new_control_sequence = false;
1248 self.get_next();
1249 self.err.no_new_control_sequence = true;
1250 self.cur_tok = self.make_cur_tok();
1251 }
1252
1253 #[inline]
1255 pub fn make_cur_tok(&self) -> Token {
1256 if self.cur_cs == NO_CS {
1257 Token::char(self.cur_cmd, self.cur_chr)
1258 } else {
1259 Token::cs(self.cur_cs)
1260 }
1261 }
1262}
1263
1264#[cfg(test)]
1265mod tests {
1266 use super::*;
1267
1268 #[test]
1269 fn line_splitting() {
1270 let data = b"ab \r\ncd\rx\n\nlast";
1271 let mut pos = 0;
1272 let mut lines = Vec::new();
1273 while let Some((l, next)) = next_line(data, pos) {
1274 lines.push(decode_line(l));
1275 pos = next;
1276 }
1277 let strs: Vec<String> = lines
1278 .iter()
1279 .map(|l| l.iter().map(|&c| char::from_u32(c).unwrap()).collect())
1280 .collect();
1281 assert_eq!(strs, vec!["ab", "cd", "x", "", "last"]);
1282 assert_eq!(decode_line(b"x\t "), vec!['x' as u32, 9]);
1283 }
1284
1285 #[test]
1286 fn expanded_chars() {
1287 let s = |x: &str| x.chars().map(|c| c as u32).collect::<Vec<_>>();
1288 assert!(matches!(
1289 expanded_at(&s("^^A"), 0),
1290 Expanded::Char { c: 1, len: 3 }
1291 ));
1292 assert!(matches!(
1293 expanded_at(&s("^^e9"), 0),
1294 Expanded::Char { c: 0xE9, len: 4 }
1295 ));
1296 assert!(matches!(
1297 expanded_at(&s("^^?"), 0),
1298 Expanded::Char { c: 127, len: 3 }
1299 ));
1300 assert!(matches!(
1301 expanded_at(&s("^^^^00e9"), 0),
1302 Expanded::Char { c: 0xE9, len: 8 }
1303 ));
1304 assert!(matches!(
1305 expanded_at(&s("^^^^^^01f600"), 0),
1306 Expanded::Char {
1307 c: 0x1F600,
1308 len: 12
1309 }
1310 ));
1311 assert!(matches!(
1312 expanded_at(&s("^^^^zz"), 0),
1313 Expanded::Bad {
1314 six: false,
1315 short: true
1316 }
1317 ));
1318 assert!(matches!(expanded_at(&s("^x"), 0), Expanded::None));
1319 }
1320}