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