1use std::{borrow::Cow, fmt, fmt::Display, rc::Rc};
2
3use once_cell::sync::Lazy;
4
5use crate::{
6 Digested,
7 common::{
8 arena::{self, SymStr},
9 dimension::Dimension,
10 error::*,
11 float::Float,
12 glue::Glue,
13 mudimension::MuDimension,
14 muglue::MuGlue,
15 number::Number,
16 numeric_ops::NumericOps,
17 store::Stored,
18 },
19 definition::{Definition, register::Register},
20 state,
21 tokens::Tokens,
22};
23
24static CONTROLNAME: &[&str] = &[
25 "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO",
26 "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS",
27 "GS", "RS", "US",
28];
29
30#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
32pub enum Catcode {
33 ESCAPE,
34 BEGIN,
35 END,
36 MATH,
37 ALIGN,
38 EOL,
39 PARAM,
40 SUPER,
41 SUB,
42 IGNORE,
43 SPACE,
44 LETTER,
45 OTHER,
46 ACTIVE,
47 COMMENT,
48 INVALID,
49 CS,
50 MARKER,
51 ARG,
52}
53
54impl From<u8> for Catcode {
55 fn from(num: u8) -> Catcode {
56 use crate::token::Catcode::*;
57 match num {
58 0 => ESCAPE,
59 1 => BEGIN,
60 2 => END,
61 3 => MATH,
62 4 => ALIGN,
63 5 => EOL,
64 6 => PARAM,
65 7 => SUPER,
66 8 => SUB,
67 9 => IGNORE,
68 10 => SPACE,
69 11 => LETTER,
70 12 => OTHER,
71 13 => ACTIVE,
72 14 => COMMENT,
73 15 => INVALID,
74 16 => CS,
75 17 => MARKER,
76 18 => ARG,
77 _ => {
78 IGNORE
81 },
82 }
83 }
84}
85
86impl From<Catcode> for u8 {
87 fn from(cc: Catcode) -> u8 {
88 use crate::token::Catcode::*;
89 match cc {
90 ESCAPE => 0,
91 BEGIN => 1,
92 END => 2,
93 MATH => 3,
94 ALIGN => 4,
95 EOL => 5,
96 PARAM => 6,
97 SUPER => 7,
98 SUB => 8,
99 IGNORE => 9,
100 SPACE => 10,
101 LETTER => 11,
102 OTHER => 12,
103 ACTIVE => 13,
104 COMMENT => 14,
105 INVALID => 15,
106 CS => 16,
107 MARKER => 17,
108 ARG => 18,
109 }
110 }
111}
112
113impl Catcode {
114 pub fn name(self) -> &'static str {
116 use crate::token::Catcode::*;
117 match self {
118 ESCAPE => "Escape",
120 BEGIN => "Begin",
121 END => "End",
122 MATH => "Math",
123 ALIGN => "Align",
124 EOL => "EOL",
125 PARAM => "Parameter",
126 SUPER => "Superscript",
127 SUB => "Subscript",
128 SPACE => "Space",
129 IGNORE => "Ignore",
131 LETTER => "Letter",
132 OTHER => "Other",
133 ACTIVE => "Active",
134 COMMENT => "Comment",
135 INVALID => "Invalid",
136 CS => "ControlSequence",
137 MARKER => "Marker",
138 ARG => "Arg",
139 }
140 }
141
142 pub fn name_sym(self) -> SymStr {
147 use crate::token::Catcode::*;
148 match self {
149 ESCAPE => crate::pin!("Escape"),
150 BEGIN => crate::pin!("Begin"),
151 END => crate::pin!("End"),
152 MATH => crate::pin!("Math"),
153 ALIGN => crate::pin!("Align"),
154 EOL => crate::pin!("EOL"),
155 PARAM => crate::pin!("Parameter"),
156 SUPER => crate::pin!("Superscript"),
157 SUB => crate::pin!("Subscript"),
158 SPACE => crate::pin!("Space"),
159 IGNORE => crate::pin!("Ignore"),
160 LETTER => crate::pin!("Letter"),
161 OTHER => crate::pin!("Other"),
162 ACTIVE => crate::pin!("Active"),
163 COMMENT => crate::pin!("Comment"),
164 INVALID => crate::pin!("Invalid"),
165 CS => crate::pin!("ControlSequence"),
166 MARKER => crate::pin!("Marker"),
167 ARG => crate::pin!("Arg"),
168 }
169 }
170 pub fn meaning(self) -> &'static str {
172 use crate::token::Catcode::*;
173 match self {
174 ESCAPE => "the escape character",
175 BEGIN => "begin-group character",
176 END => "end-group character",
177 MATH => "math shift character",
178 ALIGN => "alignment tab character",
179 EOL => "end-of-line character",
180 PARAM => "macro parameter character",
181 SUPER => "superscript character",
182 SUB => "subscript character",
183 IGNORE => "ignored character",
184 SPACE => "blank space",
185 LETTER => "the letter",
186 OTHER => "the character",
187 ACTIVE => "active character",
188 COMMENT => "comment character",
189 INVALID => "invalid character",
190 _ => "",
191 }
192 }
193 pub fn short_name(self) -> &'static str {
195 use crate::token::Catcode::*;
196 match self {
197 ESCAPE => "T_ESCAPE",
198 BEGIN => "T_BEGIN",
199 END => "T_END",
200 MATH => "T_MATH",
201 ALIGN => "T_ALIGN",
202 EOL => "T_EOL",
203 PARAM => "T_PARAM",
204 SUPER => "T_SUPER",
205 SUB => "T_SUB",
206 IGNORE => "T_IGNORE",
207 SPACE => "T_SPACE",
208 LETTER => "T_LETTER",
209 OTHER => "T_OTHER",
210 ACTIVE => "T_ACTIVE",
211 COMMENT => "T_COMMENT",
212 INVALID => "T_INVALID",
213 CS => "T_CS",
214 MARKER => "T_MARKER",
215 ARG => "T_ARG",
216 }
217 }
218
219 pub fn is_primitive(self) -> bool {
225 use crate::token::Catcode::*;
226 match self {
227 ESCAPE | BEGIN | END | MATH | ALIGN | EOL | PARAM | SUPER | SUB | SPACE => true,
229 IGNORE | LETTER | OTHER | ACTIVE | COMMENT | INVALID | CS | MARKER | ARG => false,
231 }
232 }
233 pub fn is_executable(self) -> bool {
235 use crate::token::Catcode::*;
236 match self {
237 BEGIN | END | MATH | ALIGN | SUPER | SUB | ACTIVE | CS => true,
239 EOL | ESCAPE | PARAM | SPACE | IGNORE | LETTER | OTHER | COMMENT | INVALID | MARKER | ARG => {
241 false
242 },
243 }
244 }
245 pub fn is_neutralizable(self) -> bool {
247 use crate::token::Catcode::*;
248 match self {
249 MATH | ALIGN | PARAM | SUPER | SUB | ACTIVE => true,
251 ESCAPE | BEGIN | END | EOL | IGNORE | SPACE | LETTER | OTHER | COMMENT | INVALID | CS
253 | MARKER | ARG => false,
254 }
255 }
256 pub fn is_active_or_cs(self) -> bool {
258 use crate::token::Catcode::*;
259 matches!(self, ACTIVE | CS)
260 }
261 pub fn is_absorbable(self) -> bool {
263 use crate::token::Catcode::*;
264 matches!(self, SPACE | LETTER | OTHER | COMMENT)
266 }
267 pub fn is_gullet_holdable(self) -> bool {
269 use crate::token::Catcode::*;
270 matches!(self, COMMENT | MARKER)
271 }
272 pub fn is_balanced_interesting(self) -> bool {
274 use crate::token::Catcode::*;
275 matches!(self, BEGIN | END | MARKER)
277 }
278}
279
280#[derive(Copy, Clone)]
288pub struct Token {
289 pub text: SymStr,
291 pub code: Catcode,
293 #[cfg(feature = "token-locators")]
301 pub loc: u32,
302}
303
304impl fmt::Debug for Token {
305 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306 if self.code == Catcode::ARG {
307 self.with_str(|text| write!(f, "\"#{}\"", text))
308 } else {
309 self.with_str(|text| write!(f, "{:?}", text))
310 }
311 }
312}
313
314impl Display for Token {
315 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
316 if self.code == Catcode::ARG {
317 write!(f, "#")?;
318 }
319 self.with_str(|text| write!(f, "{}", text))
320 }
321}
322
323impl PartialEq for Token {
328 fn eq(&self, other: &Token) -> bool {
329 self.code == other.code && (self.code == Catcode::SPACE || (self.text == other.text))
330 }
331}
332
333thread_local! {
340 static NOEXPAND_FAMILY_MEMO: std::cell::RefCell<Vec<u8>> =
341 const { std::cell::RefCell::new(Vec::new()) };
342}
343
344pub fn reset_noexpand_family_memo() { NOEXPAND_FAMILY_MEMO.with(|m| m.borrow_mut().clear()); }
349
350pub const NOEXPAND_PREFIX: &str = "\\special_relax";
353pub const NOEXPAND_SEP: u8 = 1;
358
359pub fn noexpand_family(shadowed: &Token) -> Token {
365 let text = shadowed.with_str(|s| format!("{NOEXPAND_PREFIX}{}{s}", NOEXPAND_SEP as char));
366 Token {
367 text: arena::pin(text),
368 code: Catcode::CS,
369 #[cfg(feature = "token-locators")]
370 loc: 0,
371 }
372}
373
374#[thread_local]
381pub static TOKEN_BEGIN: Lazy<Token> = Lazy::new(|| Token {
382 text: arena::pin_static("{"),
383 code: Catcode::BEGIN,
384 #[cfg(feature = "token-locators")]
385 loc: 0,
386});
387#[thread_local]
389pub static TOKEN_END: Lazy<Token> = Lazy::new(|| Token {
390 text: arena::pin_static("}"),
391 code: Catcode::END,
392 #[cfg(feature = "token-locators")]
393 loc: 0,
394});
395#[thread_local]
397pub static TOKEN_MATH: Lazy<Token> = Lazy::new(|| Token {
398 text: arena::pin_static("$"),
399 code: Catcode::MATH,
400 #[cfg(feature = "token-locators")]
401 loc: 0,
402});
403#[thread_local]
405pub static TOKEN_ALIGN: Lazy<Token> = Lazy::new(|| Token {
406 text: arena::pin_static("&"),
407 code: Catcode::ALIGN,
408 #[cfg(feature = "token-locators")]
409 loc: 0,
410});
411#[thread_local]
413pub static TOKEN_PARAM: Lazy<Token> = Lazy::new(|| Token {
414 text: arena::pin_static("#"),
415 code: Catcode::PARAM,
416 #[cfg(feature = "token-locators")]
417 loc: 0,
418});
419#[thread_local]
421pub static TOKEN_SUPER: Lazy<Token> = Lazy::new(|| Token {
422 text: arena::pin_static("^"),
423 code: Catcode::SUPER,
424 #[cfg(feature = "token-locators")]
425 loc: 0,
426});
427#[thread_local]
429pub static TOKEN_SUB: Lazy<Token> = Lazy::new(|| Token {
430 text: arena::pin_static("_"),
431 code: Catcode::SUB,
432 #[cfg(feature = "token-locators")]
433 loc: 0,
434});
435#[thread_local]
437pub static TOKEN_SPACE: Lazy<Token> = Lazy::new(|| Token {
438 text: arena::pin_static(" "),
439 code: Catcode::SPACE,
440 #[cfg(feature = "token-locators")]
441 loc: 0,
442});
443#[thread_local]
445pub static TOKEN_CR: Lazy<Token> = Lazy::new(|| Token {
446 text: arena::pin_static("\n"),
447 code: Catcode::SPACE,
448 #[cfg(feature = "token-locators")]
449 loc: 0,
450});
451#[thread_local]
453pub static TOKEN_RELAX: Lazy<Token> = Lazy::new(|| Token {
454 text: arena::pin_static("\\relax"),
455 code: Catcode::CS,
456 #[cfg(feature = "token-locators")]
457 loc: 0,
458});
459#[thread_local]
461pub static TOKEN_EXPANDAFTER: Lazy<Token> = Lazy::new(|| Token {
462 text: arena::pin_static("\\expandafter"),
463 code: Catcode::CS,
464 #[cfg(feature = "token-locators")]
465 loc: 0,
466});
467#[thread_local]
469pub static TOKEN_ENDCSNAME: Lazy<Token> = Lazy::new(|| Token {
470 text: arena::pin_static("\\endcsname"),
471 code: Catcode::CS,
472 #[cfg(feature = "token-locators")]
473 loc: 0,
474});
475
476pub(crate) fn force_init() {
485 Lazy::force(&TOKEN_BEGIN);
486 Lazy::force(&TOKEN_END);
487 Lazy::force(&TOKEN_MATH);
488 Lazy::force(&TOKEN_ALIGN);
489 Lazy::force(&TOKEN_PARAM);
490 Lazy::force(&TOKEN_SUPER);
491 Lazy::force(&TOKEN_SUB);
492 Lazy::force(&TOKEN_SPACE);
493 Lazy::force(&TOKEN_CR);
494 Lazy::force(&TOKEN_RELAX);
495 Lazy::force(&TOKEN_EXPANDAFTER);
496 Lazy::force(&TOKEN_ENDCSNAME);
497}
498
499#[macro_export]
500macro_rules! T_BEGIN(() => { *$crate::token::TOKEN_BEGIN });
502#[macro_export]
503macro_rules! T_END(() => { *$crate::token::TOKEN_END });
505#[macro_export]
507macro_rules! T_MATH(() => { *$crate::token::TOKEN_MATH });
508#[macro_export]
510macro_rules! T_ALIGN(() => { *$crate::token::TOKEN_ALIGN });
511#[macro_export]
513macro_rules! T_PARAM(() => { *$crate::token::TOKEN_PARAM });
514#[macro_export]
516macro_rules! T_SUPER(() => { *$crate::token::TOKEN_SUPER });
517#[macro_export]
519macro_rules! T_SUB(() => { *$crate::token::TOKEN_SUB });
520#[macro_export]
522macro_rules! T_SPACE(() => { *$crate::token::TOKEN_SPACE };
523($text:literal) => {
524 Token { text: $crate::pin!($text), code: Catcode::SPACE,
525 #[cfg(feature = "token-locators")] loc: 0
526 }
527});
528#[macro_export]
530macro_rules! T_CR(() => { *$crate::token::TOKEN_CR });
531#[macro_export]
533macro_rules! T_LETTER {
534 ($text:literal) => {
535 Token {
536 text: $crate::pin!($text),
537 code: Catcode::LETTER,
538 #[cfg(feature = "token-locators")]
539 loc: 0,
540 }
541 };
542 ($text:expr_2021) => {
543 Token {
544 text: $crate::common::arena::pin($text),
545 code: Catcode::LETTER,
546 #[cfg(feature = "token-locators")]
547 loc: 0,
548 }
549 };
550}
551#[macro_export]
553macro_rules! T_OTHER {
554 ($text:literal) => {
555 Token {
556 text: $crate::pin!($text),
557 code: Catcode::OTHER,
558 #[cfg(feature = "token-locators")]
559 loc: 0,
560 }
561 };
562 ($text:expr_2021) => {
563 Token {
564 text: $crate::common::arena::pin($text),
565 code: Catcode::OTHER,
566 #[cfg(feature = "token-locators")]
567 loc: 0,
568 }
569 };
570}
571#[macro_export]
573macro_rules! T_OTHER_CHAR {
574 ($text:literal) => {
575 Token {
576 text: $crate::common::arena::pin_char($text),
577 code: Catcode::OTHER,
578 #[cfg(feature = "token-locators")]
579 loc: 0,
580 }
581 };
582}
583#[macro_export]
585macro_rules! T_ACTIVE {
586 ($c:expr_2021) => {{
587 let mut tmp = [0u8; 4];
588 let s = $c.encode_utf8(&mut tmp);
589 Token {
590 text: $crate::common::arena::pin(s),
591 code: Catcode::ACTIVE,
592 #[cfg(feature = "token-locators")]
593 loc: 0,
594 }
595 }};
596}
597#[macro_export]
599macro_rules! T_COMMENT {
600 ($text:expr_2021) => {
601 Token {
602 text: $crate::common::arena::pin($text),
603 code: Catcode::COMMENT,
604 #[cfg(feature = "token-locators")]
605 loc: 0,
606 }
607 };
608}
609#[macro_export]
611macro_rules! T_CS {
612 ($text:literal) => {
613 $crate::token::Token {
614 text: $crate::pin!($text),
615 code: $crate::token::Catcode::CS,
616 #[cfg(feature = "token-locators")]
617 loc: 0,
618 }
619 };
620 ($text:expr_2021) => {
621 $crate::token::Token {
622 text: $crate::common::arena::pin($text),
623 code: $crate::token::Catcode::CS,
624 #[cfg(feature = "token-locators")]
625 loc: 0,
626 }
627 };
628}
629
630#[macro_export]
632macro_rules! T_RELAX(() => { $crate::token::TOKEN_RELAX.clone() });
633
634#[macro_export]
636macro_rules! T_MARKER {
637 ($text:expr_2021) => {
638 Token {
639 text: $crate::common::arena::pin($text),
640 code: Catcode::MARKER,
641 #[cfg(feature = "token-locators")]
642 loc: 0,
643 }
644 };
645}
646
647#[macro_export]
649macro_rules! T_ARG {
650 ($text:expr_2021) => {
651 Token {
652 text: $crate::common::arena::pin($text.to_string()),
653 code: Catcode::ARG,
654 #[cfg(feature = "token-locators")]
655 loc: 0,
656 }
657 };
658}
659
660#[macro_export]
662macro_rules! Token {
663 ($text:expr_2021) => {
664 Token!($text, Catcode::OTHER)
665 };
666 ($text:literal, $cc:expr_2021) => {
667 Token {
668 text: $crate::pin!($text),
669 code: $cc,
670 #[cfg(feature = "token-locators")]
671 loc: 0,
672 }
673 };
674 ($text:expr_2021, $cc:expr_2021) => {
675 Token {
676 text: $crate::common::arena::pin($text),
677 code: $cc,
678 #[cfg(feature = "token-locators")]
679 loc: 0,
680 }
681 };
682}
683
684#[macro_export]
686macro_rules! CharToken {
687 ($c:expr_2021) => {
688 CharToken!($c, Catcode::OTHER)
689 };
690 ($c:expr_2021, $cc:expr_2021) => {{
691 let mut tmp = [0u8; 4];
692 let s = $c.encode_utf8(&mut tmp);
693 Token!(s, $cc)
694 }};
695}
696
697#[macro_export]
702macro_rules! Explode(($text:expr_2021) => (
703 $text.to_string().chars().map(|c|
704 if c==' ' { T_SPACE!() }
705 else {
706 CharToken!(c)
707 }
708 ).collect::<Vec<Token>>()
709));
710
711#[macro_export]
712macro_rules! ExplodeChars(($text:expr_2021) => (
713 $text.as_str().chars().map(|c|
714 if c==' ' { T_SPACE!() }
715 else {
716 CharToken!(c)
717 }
718 ).collect::<Vec<Token>>()
719));
720
721#[macro_export]
725macro_rules! ExplodeText(
726 ($text:expr_2021) => ({
727 use $crate::token::{Catcode,Token};
728 $text.to_string().chars().map(|c|
729 if c==' ' { T_SPACE!() }
730 else {
731 let mut tmp = [0u8; 4];
732 let s = c.encode_utf8(&mut tmp);
733 if c.is_alphabetic() {
734 T_LETTER!(s) }
735 else { T_OTHER!(s) }}
736 ).collect::<Vec<Token>>()
737}));
738
739#[macro_export]
740macro_rules! SymExplodeText(
741 ($sym:expr_2021) => ({
742 use $crate::token::{Catcode,Token};
743 let chars : Vec<char> = arena::with($sym, |text| text.chars().collect());
744 chars.into_iter().map(|c|
745 if c==' ' { T_SPACE!() }
746 else {
747 let mut tmp = [0u8; 4];
748 let s = c.encode_utf8(&mut tmp);
749 if c.is_alphabetic() {
750 T_LETTER!(s) }
751 else { T_OTHER!(s) }}
752 ).collect::<Vec<Token>>()
753}));
754
755impl Default for Token {
758 fn default() -> Self {
759 Token {
760 text: arena::pin_static("EXPECTED_TOKEN"),
761 code: Catcode::OTHER,
762 #[cfg(feature = "token-locators")]
763 loc: 0,
764 }
765 }
766}
767
768impl Token {
771 pub fn new<T: AsRef<str>>(text: T, code: Catcode) -> Self {
773 Token {
774 text: arena::pin(text),
775 code,
776 #[cfg(feature = "token-locators")]
777 loc: 0,
778 }
779 }
780
781 #[inline]
786 pub fn cycle_fingerprint(&self) -> u64 {
787 use std::hash::{Hash, Hasher};
788 let mut h = rustc_hash::FxHasher::default();
789 self.code.hash(&mut h);
790 if self.code != Catcode::SPACE {
791 self.text.hash(&mut h);
792 }
793 h.finish()
794 }
795
796 pub fn get_cs_name(&self) -> SymStr {
799 if self.code.is_primitive() {
800 self.code.name_sym()
801 } else {
802 self.get_sym()
803 }
804 }
805
806 pub fn with_cs_name<R, FnR>(&self, caller: FnR) -> R
810 where FnR: FnOnce(&str) -> R {
811 if self.code.is_primitive() {
812 caller(self.code.name())
813 } else {
814 self.with_str(caller)
815 }
816 }
817
818 pub fn pin_cs_name(&self) -> SymStr {
820 if self.code.is_primitive() {
821 self.code.name_sym()
822 } else {
823 self.get_sym()
824 }
825 }
826
827 pub fn get_primitive_name(&self) -> Option<&'static str> {
829 if self.code.is_primitive() {
830 Some(self.code.name())
831 } else {
832 None
833 }
834 }
835
836 pub fn get_executable_name(&self) -> String {
838 let cc = self.code;
839 if cc.is_executable() {
840 self
841 .get_primitive_name()
842 .map(ToString::to_string)
843 .unwrap_or_else(|| self.with_str(|text| text.to_string()))
844 } else {
845 String::new()
846 }
847 }
848
849 pub fn get_executable_primitive_name(&self) -> Option<&'static str> {
851 let cc = self.code;
852 if cc.is_executable() && cc.is_primitive() {
853 Some(self.code.name())
854 } else {
855 None
856 }
857 }
858
859 pub fn get_sym(&self) -> SymStr { self.text }
861 pub fn with_str<R, FnR>(&self, caller: FnR) -> R
864 where FnR: FnOnce(&str) -> R {
865 arena::with(self.text, caller)
866 }
867
868 pub fn is_noexpand_family(&self) -> bool {
878 if self.code != Catcode::CS {
879 return false;
880 }
881 use string_interner::Symbol;
889 let idx = self.text.to_usize();
890 let cached = NOEXPAND_FAMILY_MEMO.with(|m| m.borrow().get(idx).copied().unwrap_or(0));
891 if cached != 0 {
892 return cached == 2;
893 }
894 let is_family = self.with_str(|s| {
895 s.starts_with(NOEXPAND_PREFIX)
896 && (s.len() == NOEXPAND_PREFIX.len() || s.as_bytes()[NOEXPAND_PREFIX.len()] == NOEXPAND_SEP)
897 });
898 NOEXPAND_FAMILY_MEMO.with(|m| {
899 let mut memo = m.borrow_mut();
900 if memo.len() <= idx {
901 memo.resize(idx + 1, 0);
902 }
903 memo[idx] = if is_family { 2 } else { 1 };
904 });
905 is_family
906 }
907
908 pub fn noexpand_shadowed(&self) -> Option<Token> {
912 if self.code != Catcode::CS {
913 return None;
914 }
915 let (name, code) = self.with_str(|s| {
922 let rest = s.strip_prefix(NOEXPAND_PREFIX)?;
923 let rest = rest.strip_prefix(NOEXPAND_SEP as char)?;
924 let code = if rest.starts_with('\\') {
925 Catcode::CS
926 } else {
927 Catcode::ACTIVE
928 };
929 Some((rest.to_string(), code))
930 })?;
931 Some(Token {
932 text: arena::pin(name),
933 code,
934 #[cfg(feature = "token-locators")]
935 loc: 0,
936 })
937 }
938
939 pub fn get_charcode(&self) -> u32 {
942 if self.code == Catcode::CS {
943 256
944 } else {
945 self.with_str(|text| {
946 if let Some(c) = text.chars().next() {
947 c as u32
948 } else {
949 0
950 }
951 })
952 }
953 }
954
955 pub fn get_catcode(&self) -> Catcode { self.code }
957 pub fn is_executable(&self) -> bool { self.code.is_executable() }
959
960 pub fn neutralize(self, extraspecials: &[char]) -> Token {
967 let first_c: Option<char> = self.with_str(|text| text.chars().next());
968 let ch = match first_c {
969 Some(ch) => ch,
970 None => return self,
971 };
972 let cc = self.code;
973 if cc.is_neutralizable() {
974 for extra in extraspecials {
975 if extra == &ch {
976 let mut tmp = [0u8; 4];
977 let s = ch.encode_utf8(&mut tmp);
978 return T_OTHER!(s);
979 }
980 }
981 let maybe_return = state::with_value("SPECIALS", |specials_opt| {
982 if let Some(Stored::Chars(specials_list)) = specials_opt {
983 for special in specials_list.iter() {
984 if *special == ch {
985 let mut tmp = [0u8; 4];
986 let s = ch.encode_utf8(&mut tmp);
987 return Some(T_OTHER!(s));
988 }
989 }
990 }
991 None
992 });
993 if let Some(token) = maybe_return {
994 return token;
995 }
996 }
997 self
998 }
999
1000 pub fn as_other(&self) -> Token {
1001 Token {
1002 text: self.text,
1003 code: Catcode::OTHER,
1004 #[cfg(feature = "token-locators")]
1005 loc: 0,
1006 }
1007 }
1008 pub fn as_cs(&self) -> Token {
1009 Token {
1010 text: self.text,
1011 code: Catcode::CS,
1012 #[cfg(feature = "token-locators")]
1013 loc: 0,
1014 }
1015 }
1016
1017 pub fn substitute_parameters(self, args: &[&Token]) -> Self {
1018 if self.code == Catcode::ARG {
1019 self.with_str(|text| {
1020 let arg_idx = text
1021 .parse::<usize>()
1022 .expect("ARG catcode tokens should always contain numeric literals as text");
1023 *args[arg_idx - 1]
1024 })
1025 } else {
1026 self
1027 }
1028 }
1029
1030 pub fn revert(self) -> Token { self }
1032
1033 pub fn stringify(&self) -> String {
1035 self.with_str(|text| {
1036 let display_text = if text.len() == 1 {
1038 let c = text.chars().next().unwrap() as u16;
1039 if c < 0x020 {
1040 Cow::Owned(s!("U+{:04x}/{}", c, CONTROLNAME[c as usize]))
1041 } else {
1042 Cow::Borrowed(text)
1043 }
1044 } else {
1045 Cow::Borrowed(text)
1046 };
1047 s!("{}[{}]", self.code.short_name(), display_text)
1048 })
1049 }
1050
1051 pub fn to_register(&self) -> Option<Rc<Register>> { state::lookup_register_definition(self) }
1052
1053 pub fn to_number(&self) -> Number {
1054 Number::new(self.with_str(|text| text.parse::<i64>()).unwrap_or(0))
1055 }
1056
1057 pub fn to_dimension(&self) -> Dimension {
1058 Dimension::new_f64(self.with_str(|text| text.parse::<f64>().unwrap_or(0.0)))
1059 }
1060
1061 pub fn to_mu_dimension(&self) -> MuDimension {
1062 MuDimension::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1063 }
1064
1065 pub fn to_glue(&self) -> Glue {
1066 Glue::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1067 }
1068
1069 pub fn to_mu_glue(&self) -> MuGlue {
1070 MuGlue::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1071 }
1072
1073 pub fn to_float(&self) -> Float {
1074 Float::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1075 }
1076
1077 pub fn be_digested(self) -> Result<Digested> { crate::stomach::digest(Tokens::new(vec![self])) }
1078
1079 pub fn defined_as(&self, other: &Token) -> bool {
1084 let cc = self.code;
1085 let occ = other.get_catcode();
1086 if (cc == occ) && ((occ == Catcode::SPACE) || (self.text == other.get_sym())) {
1087 return true;
1088 }
1089 if matches!(cc, Catcode::CS | Catcode::ACTIVE) {
1090 let letto_opt: Option<Token> = state::with_meaning(self, |defn_opt| {
1096 defn_opt.and_then(|defn| match defn {
1097 Stored::Token(t) => Some(*t),
1098 Stored::Expandable(inner) => Some(*inner.get_cs()),
1099 Stored::Primitive(inner) => Some(*inner.get_cs()),
1100 Stored::MathPrimitive(inner) => Some(*inner.get_cs()),
1101 Stored::Register(inner) => Some(*inner.get_cs()),
1102 Stored::Conditional(inner) => Some(*inner.get_cs()),
1103 Stored::Constructor(inner) => Some(*inner.get_cs()),
1104 _ => None,
1105 })
1106 });
1107 if let Some(letto) = letto_opt
1108 && (letto.get_catcode() == occ)
1109 && ((occ == Catcode::SPACE) || letto.get_sym() == other.get_sym())
1110 {
1111 return true;
1112 }
1113 }
1114 false
1115 }
1116}
1117
1118impl From<&str> for Token {
1121 fn from(text: &str) -> Token {
1122 match text.chars().next() {
1123 Some('{') => T_BEGIN!(),
1124 Some('}') => T_END!(),
1125 Some('$') => T_MATH!(),
1126 Some('#') => T_PARAM!(),
1127 Some('&') => T_ALIGN!(),
1128 Some('^') => T_SUPER!(),
1129 Some('_') => T_SUB!(),
1130 Some('\\') => T_CS!(text),
1131 Some('%') => T_COMMENT!(text),
1132 _ => {
1133 if text.chars().all(|c| c.is_alphabetic()) {
1134 T_LETTER!(text)
1135 } else if text.chars().all(|c| c.is_whitespace()) {
1136 T_SPACE!()
1137 } else {
1138 T_OTHER!(text)
1139 }
1140 },
1141 }
1142 }
1143}
1144
1145#[cfg(feature = "token-locators")]
1152#[derive(Clone, Copy, Debug)]
1153pub struct TokenStart {
1154 pub source: SymStr,
1155 pub line: u32,
1156 pub col: u32,
1157 pub inherited: bool,
1166}
1167
1168#[cfg(feature = "token-locators")]
1169thread_local! {
1170 static TOKEN_ORIGINS: std::cell::RefCell<Vec<TokenStart>> =
1171 const { std::cell::RefCell::new(Vec::new()) };
1172}
1173
1174#[cfg(feature = "token-locators")]
1177pub fn push_token_origin(source: SymStr, line: u32, col: u32) -> u32 {
1178 TOKEN_ORIGINS.with(|o| {
1179 let mut v = o.borrow_mut();
1180 v.push(TokenStart {
1181 source,
1182 line,
1183 col,
1184 inherited: false,
1185 });
1186 v.len() as u32 })
1188}
1189
1190#[cfg(feature = "token-locators")]
1196pub fn push_inherited_origin(handle: u32) -> u32 {
1197 if handle == 0 {
1198 return 0;
1199 }
1200 TOKEN_ORIGINS.with(|o| {
1201 let mut v = o.borrow_mut();
1202 let Some(mut start) = v.get((handle - 1) as usize).copied() else {
1203 return 0;
1204 };
1205 start.inherited = true;
1206 v.push(start);
1207 v.len() as u32
1208 })
1209}
1210
1211#[cfg(feature = "token-locators")]
1214pub fn get_token_origin(handle: u32) -> Option<TokenStart> {
1215 if handle == 0 {
1216 return None;
1217 }
1218 TOKEN_ORIGINS.with(|o| o.borrow().get((handle - 1) as usize).copied())
1219}
1220
1221#[cfg(feature = "token-locators")]
1223pub fn clear_token_origins() { TOKEN_ORIGINS.with(|o| o.borrow_mut().clear()); }
1224
1225#[cfg(test)]
1226mod tests {
1227 use super::*;
1228
1229 #[test]
1234 fn token_size_invariant() {
1235 #[cfg(not(feature = "token-locators"))]
1236 assert_eq!(
1237 size_of::<Token>(),
1238 8,
1239 "default Token must stay 8 bytes (SymStr + Catcode)"
1240 );
1241 #[cfg(feature = "token-locators")]
1242 assert_eq!(
1243 std::mem::size_of::<Token>(),
1244 12,
1245 "token-locators Token is 8 + a u32 origin handle"
1246 );
1247 }
1248
1249 #[cfg(feature = "token-locators")]
1255 #[test]
1256 fn token_origin_capture() {
1257 super::clear_token_origins();
1258 let toks = crate::mouth::tokenize("Hello");
1260 let got: Vec<(u32, u32)> = toks
1261 .unlist_ref()
1262 .iter()
1263 .map(|t| {
1264 let o = super::get_token_origin(t.loc).expect("token carries an origin handle");
1265 (o.line, o.col)
1266 })
1267 .collect();
1268 assert_eq!(
1269 got,
1270 vec![(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)],
1271 "each letter's captured (line, col) must be exact"
1272 );
1273 }
1274
1275 #[test]
1276 fn catcode_name_covers_all_variants() {
1277 use Catcode::*;
1279 for cc in [
1280 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE, IGNORE, LETTER, OTHER,
1281 ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1282 ] {
1283 assert!(!cc.name().is_empty(), "{cc:?}.name() is empty");
1284 }
1285 }
1286
1287 #[test]
1288 fn catcode_name_specific_values() {
1289 assert_eq!(Catcode::ESCAPE.name(), "Escape");
1290 assert_eq!(Catcode::BEGIN.name(), "Begin");
1291 assert_eq!(Catcode::CS.name(), "ControlSequence");
1292 assert_eq!(Catcode::LETTER.name(), "Letter");
1293 }
1294
1295 #[test]
1296 fn catcode_short_name_starts_with_t_prefix() {
1297 use Catcode::*;
1298 for cc in [
1299 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE, IGNORE, LETTER, OTHER,
1300 ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1301 ] {
1302 assert!(
1303 cc.short_name().starts_with("T_"),
1304 "{cc:?}.short_name() = {} lacks T_ prefix",
1305 cc.short_name()
1306 );
1307 }
1308 }
1309
1310 #[test]
1311 fn catcode_meaning_mostly_nonempty() {
1312 assert!(!Catcode::ESCAPE.meaning().is_empty());
1314 assert!(!Catcode::LETTER.meaning().is_empty());
1315 assert!(!Catcode::OTHER.meaning().is_empty());
1316 assert_eq!(Catcode::CS.meaning(), "");
1318 }
1319
1320 #[test]
1321 fn is_primitive_checks() {
1322 use Catcode::*;
1323 for cc in [
1325 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE,
1326 ] {
1327 assert!(cc.is_primitive(), "{cc:?} should be primitive");
1328 }
1329 for cc in [
1331 IGNORE, LETTER, OTHER, ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1332 ] {
1333 assert!(!cc.is_primitive(), "{cc:?} should not be primitive");
1334 }
1335 }
1336
1337 #[test]
1338 fn is_executable_checks() {
1339 use Catcode::*;
1340 for cc in [BEGIN, END, MATH, ALIGN, SUPER, SUB, ACTIVE, CS] {
1341 assert!(cc.is_executable(), "{cc:?} should be executable");
1342 }
1343 for cc in [
1344 EOL, ESCAPE, PARAM, SPACE, IGNORE, LETTER, OTHER, COMMENT, INVALID, MARKER, ARG,
1345 ] {
1346 assert!(!cc.is_executable(), "{cc:?} should not be executable");
1347 }
1348 }
1349
1350 #[test]
1351 fn is_neutralizable_set() {
1352 use Catcode::*;
1353 for cc in [MATH, ALIGN, PARAM, SUPER, SUB, ACTIVE] {
1354 assert!(cc.is_neutralizable(), "{cc:?}");
1355 }
1356 assert!(!CS.is_neutralizable());
1357 assert!(!LETTER.is_neutralizable());
1358 }
1359
1360 #[test]
1361 fn is_active_or_cs_narrow_set() {
1362 assert!(Catcode::ACTIVE.is_active_or_cs());
1363 assert!(Catcode::CS.is_active_or_cs());
1364 assert!(!Catcode::LETTER.is_active_or_cs());
1365 assert!(!Catcode::ESCAPE.is_active_or_cs());
1366 }
1367
1368 #[test]
1369 fn is_absorbable_space_letter_other_comment() {
1370 use Catcode::*;
1371 assert!(SPACE.is_absorbable());
1372 assert!(LETTER.is_absorbable());
1373 assert!(OTHER.is_absorbable());
1374 assert!(COMMENT.is_absorbable());
1375 assert!(!CS.is_absorbable());
1377 assert!(!BEGIN.is_absorbable());
1378 }
1379
1380 #[test]
1381 fn is_gullet_holdable_comment_marker_only() {
1382 assert!(Catcode::COMMENT.is_gullet_holdable());
1383 assert!(Catcode::MARKER.is_gullet_holdable());
1384 assert!(!Catcode::SPACE.is_gullet_holdable());
1385 assert!(!Catcode::LETTER.is_gullet_holdable());
1386 }
1387
1388 #[test]
1389 fn is_balanced_interesting_begin_end_marker() {
1390 assert!(Catcode::BEGIN.is_balanced_interesting());
1391 assert!(Catcode::END.is_balanced_interesting());
1392 assert!(Catcode::MARKER.is_balanced_interesting());
1393 assert!(!Catcode::LETTER.is_balanced_interesting());
1394 assert!(!Catcode::MATH.is_balanced_interesting());
1395 }
1396
1397 #[test]
1398 fn catcode_u8_roundtrip() {
1399 use Catcode::*;
1402 for cc in [
1403 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE, IGNORE, LETTER, OTHER,
1404 ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1405 ] {
1406 let b: u8 = cc.into();
1407 let cc2: Catcode = b.into();
1408 assert_eq!(cc, cc2, "roundtrip broke for {cc:?} (u8={b})");
1409 }
1410 }
1411
1412 #[test]
1413 fn token_new_and_display() {
1414 let t = Token::new("foo", Catcode::LETTER);
1415 assert_eq!(format!("{t}"), "foo");
1416 assert_eq!(t.code, Catcode::LETTER);
1417 }
1418
1419 #[test]
1420 fn token_arg_display_prepends_hash() {
1421 let t = Token::new("1", Catcode::ARG);
1423 assert_eq!(format!("{t}"), "#1");
1424 }
1425}