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
333pub const NOEXPAND_PREFIX: &str = "\\special_relax";
336pub const NOEXPAND_SEP: u8 = 1;
341
342pub fn noexpand_family(shadowed: &Token) -> Token {
348 let text = shadowed.with_str(|s| format!("{NOEXPAND_PREFIX}{}{s}", NOEXPAND_SEP as char));
349 Token {
350 text: arena::pin(text),
351 code: Catcode::CS,
352 #[cfg(feature = "token-locators")]
353 loc: 0,
354 }
355}
356
357#[thread_local]
364pub static TOKEN_BEGIN: Lazy<Token> = Lazy::new(|| Token {
365 text: arena::pin_static("{"),
366 code: Catcode::BEGIN,
367 #[cfg(feature = "token-locators")]
368 loc: 0,
369});
370#[thread_local]
372pub static TOKEN_END: Lazy<Token> = Lazy::new(|| Token {
373 text: arena::pin_static("}"),
374 code: Catcode::END,
375 #[cfg(feature = "token-locators")]
376 loc: 0,
377});
378#[thread_local]
380pub static TOKEN_MATH: Lazy<Token> = Lazy::new(|| Token {
381 text: arena::pin_static("$"),
382 code: Catcode::MATH,
383 #[cfg(feature = "token-locators")]
384 loc: 0,
385});
386#[thread_local]
388pub static TOKEN_ALIGN: Lazy<Token> = Lazy::new(|| Token {
389 text: arena::pin_static("&"),
390 code: Catcode::ALIGN,
391 #[cfg(feature = "token-locators")]
392 loc: 0,
393});
394#[thread_local]
396pub static TOKEN_PARAM: Lazy<Token> = Lazy::new(|| Token {
397 text: arena::pin_static("#"),
398 code: Catcode::PARAM,
399 #[cfg(feature = "token-locators")]
400 loc: 0,
401});
402#[thread_local]
404pub static TOKEN_SUPER: Lazy<Token> = Lazy::new(|| Token {
405 text: arena::pin_static("^"),
406 code: Catcode::SUPER,
407 #[cfg(feature = "token-locators")]
408 loc: 0,
409});
410#[thread_local]
412pub static TOKEN_SUB: Lazy<Token> = Lazy::new(|| Token {
413 text: arena::pin_static("_"),
414 code: Catcode::SUB,
415 #[cfg(feature = "token-locators")]
416 loc: 0,
417});
418#[thread_local]
420pub static TOKEN_SPACE: Lazy<Token> = Lazy::new(|| Token {
421 text: arena::pin_static(" "),
422 code: Catcode::SPACE,
423 #[cfg(feature = "token-locators")]
424 loc: 0,
425});
426#[thread_local]
428pub static TOKEN_CR: Lazy<Token> = Lazy::new(|| Token {
429 text: arena::pin_static("\n"),
430 code: Catcode::SPACE,
431 #[cfg(feature = "token-locators")]
432 loc: 0,
433});
434#[thread_local]
436pub static TOKEN_RELAX: Lazy<Token> = Lazy::new(|| Token {
437 text: arena::pin_static("\\relax"),
438 code: Catcode::CS,
439 #[cfg(feature = "token-locators")]
440 loc: 0,
441});
442#[thread_local]
444pub static TOKEN_EXPANDAFTER: Lazy<Token> = Lazy::new(|| Token {
445 text: arena::pin_static("\\expandafter"),
446 code: Catcode::CS,
447 #[cfg(feature = "token-locators")]
448 loc: 0,
449});
450#[thread_local]
452pub static TOKEN_ENDCSNAME: Lazy<Token> = Lazy::new(|| Token {
453 text: arena::pin_static("\\endcsname"),
454 code: Catcode::CS,
455 #[cfg(feature = "token-locators")]
456 loc: 0,
457});
458
459pub(crate) fn force_init() {
468 Lazy::force(&TOKEN_BEGIN);
469 Lazy::force(&TOKEN_END);
470 Lazy::force(&TOKEN_MATH);
471 Lazy::force(&TOKEN_ALIGN);
472 Lazy::force(&TOKEN_PARAM);
473 Lazy::force(&TOKEN_SUPER);
474 Lazy::force(&TOKEN_SUB);
475 Lazy::force(&TOKEN_SPACE);
476 Lazy::force(&TOKEN_CR);
477 Lazy::force(&TOKEN_RELAX);
478 Lazy::force(&TOKEN_EXPANDAFTER);
479 Lazy::force(&TOKEN_ENDCSNAME);
480}
481
482#[macro_export]
483macro_rules! T_BEGIN(() => { *$crate::token::TOKEN_BEGIN });
485#[macro_export]
486macro_rules! T_END(() => { *$crate::token::TOKEN_END });
488#[macro_export]
490macro_rules! T_MATH(() => { *$crate::token::TOKEN_MATH });
491#[macro_export]
493macro_rules! T_ALIGN(() => { *$crate::token::TOKEN_ALIGN });
494#[macro_export]
496macro_rules! T_PARAM(() => { *$crate::token::TOKEN_PARAM });
497#[macro_export]
499macro_rules! T_SUPER(() => { *$crate::token::TOKEN_SUPER });
500#[macro_export]
502macro_rules! T_SUB(() => { *$crate::token::TOKEN_SUB });
503#[macro_export]
505macro_rules! T_SPACE(() => { *$crate::token::TOKEN_SPACE };
506($text:literal) => {
507 Token { text: $crate::pin!($text), code: Catcode::SPACE,
508 #[cfg(feature = "token-locators")] loc: 0
509 }
510});
511#[macro_export]
513macro_rules! T_CR(() => { *$crate::token::TOKEN_CR });
514#[macro_export]
516macro_rules! T_LETTER {
517 ($text:literal) => {
518 Token {
519 text: $crate::pin!($text),
520 code: Catcode::LETTER,
521 #[cfg(feature = "token-locators")]
522 loc: 0,
523 }
524 };
525 ($text:expr_2021) => {
526 Token {
527 text: $crate::common::arena::pin($text),
528 code: Catcode::LETTER,
529 #[cfg(feature = "token-locators")]
530 loc: 0,
531 }
532 };
533}
534#[macro_export]
536macro_rules! T_OTHER {
537 ($text:literal) => {
538 Token {
539 text: $crate::pin!($text),
540 code: Catcode::OTHER,
541 #[cfg(feature = "token-locators")]
542 loc: 0,
543 }
544 };
545 ($text:expr_2021) => {
546 Token {
547 text: $crate::common::arena::pin($text),
548 code: Catcode::OTHER,
549 #[cfg(feature = "token-locators")]
550 loc: 0,
551 }
552 };
553}
554#[macro_export]
556macro_rules! T_OTHER_CHAR {
557 ($text:literal) => {
558 Token {
559 text: $crate::common::arena::pin_char($text),
560 code: Catcode::OTHER,
561 #[cfg(feature = "token-locators")]
562 loc: 0,
563 }
564 };
565}
566#[macro_export]
568macro_rules! T_ACTIVE {
569 ($c:expr_2021) => {{
570 let mut tmp = [0u8; 4];
571 let s = $c.encode_utf8(&mut tmp);
572 Token {
573 text: $crate::common::arena::pin(s),
574 code: Catcode::ACTIVE,
575 #[cfg(feature = "token-locators")]
576 loc: 0,
577 }
578 }};
579}
580#[macro_export]
582macro_rules! T_COMMENT {
583 ($text:expr_2021) => {
584 Token {
585 text: $crate::common::arena::pin($text),
586 code: Catcode::COMMENT,
587 #[cfg(feature = "token-locators")]
588 loc: 0,
589 }
590 };
591}
592#[macro_export]
594macro_rules! T_CS {
595 ($text:literal) => {
596 $crate::token::Token {
597 text: $crate::pin!($text),
598 code: $crate::token::Catcode::CS,
599 #[cfg(feature = "token-locators")]
600 loc: 0,
601 }
602 };
603 ($text:expr_2021) => {
604 $crate::token::Token {
605 text: $crate::common::arena::pin($text),
606 code: $crate::token::Catcode::CS,
607 #[cfg(feature = "token-locators")]
608 loc: 0,
609 }
610 };
611}
612
613#[macro_export]
615macro_rules! T_RELAX(() => { $crate::token::TOKEN_RELAX.clone() });
616
617#[macro_export]
619macro_rules! T_MARKER {
620 ($text:expr_2021) => {
621 Token {
622 text: $crate::common::arena::pin($text),
623 code: Catcode::MARKER,
624 #[cfg(feature = "token-locators")]
625 loc: 0,
626 }
627 };
628}
629
630#[macro_export]
632macro_rules! T_ARG {
633 ($text:expr_2021) => {
634 Token {
635 text: $crate::common::arena::pin($text.to_string()),
636 code: Catcode::ARG,
637 #[cfg(feature = "token-locators")]
638 loc: 0,
639 }
640 };
641}
642
643#[macro_export]
645macro_rules! Token {
646 ($text:expr_2021) => {
647 Token!($text, Catcode::OTHER)
648 };
649 ($text:literal, $cc:expr_2021) => {
650 Token {
651 text: $crate::pin!($text),
652 code: $cc,
653 #[cfg(feature = "token-locators")]
654 loc: 0,
655 }
656 };
657 ($text:expr_2021, $cc:expr_2021) => {
658 Token {
659 text: $crate::common::arena::pin($text),
660 code: $cc,
661 #[cfg(feature = "token-locators")]
662 loc: 0,
663 }
664 };
665}
666
667#[macro_export]
669macro_rules! CharToken {
670 ($c:expr_2021) => {
671 CharToken!($c, Catcode::OTHER)
672 };
673 ($c:expr_2021, $cc:expr_2021) => {{
674 let mut tmp = [0u8; 4];
675 let s = $c.encode_utf8(&mut tmp);
676 Token!(s, $cc)
677 }};
678}
679
680#[macro_export]
685macro_rules! Explode(($text:expr_2021) => (
686 $text.to_string().chars().map(|c|
687 if c==' ' { T_SPACE!() }
688 else {
689 CharToken!(c)
690 }
691 ).collect::<Vec<Token>>()
692));
693
694#[macro_export]
695macro_rules! ExplodeChars(($text:expr_2021) => (
696 $text.as_str().chars().map(|c|
697 if c==' ' { T_SPACE!() }
698 else {
699 CharToken!(c)
700 }
701 ).collect::<Vec<Token>>()
702));
703
704#[macro_export]
708macro_rules! ExplodeText(
709 ($text:expr_2021) => ({
710 use $crate::token::{Catcode,Token};
711 $text.to_string().chars().map(|c|
712 if c==' ' { T_SPACE!() }
713 else {
714 let mut tmp = [0u8; 4];
715 let s = c.encode_utf8(&mut tmp);
716 if c.is_alphabetic() {
717 T_LETTER!(s) }
718 else { T_OTHER!(s) }}
719 ).collect::<Vec<Token>>()
720}));
721
722#[macro_export]
723macro_rules! SymExplodeText(
724 ($sym:expr_2021) => ({
725 use $crate::token::{Catcode,Token};
726 let chars : Vec<char> = arena::with($sym, |text| text.chars().collect());
727 chars.into_iter().map(|c|
728 if c==' ' { T_SPACE!() }
729 else {
730 let mut tmp = [0u8; 4];
731 let s = c.encode_utf8(&mut tmp);
732 if c.is_alphabetic() {
733 T_LETTER!(s) }
734 else { T_OTHER!(s) }}
735 ).collect::<Vec<Token>>()
736}));
737
738impl Default for Token {
741 fn default() -> Self {
742 Token {
743 text: arena::pin_static("EXPECTED_TOKEN"),
744 code: Catcode::OTHER,
745 #[cfg(feature = "token-locators")]
746 loc: 0,
747 }
748 }
749}
750
751impl Token {
754 pub fn new<T: AsRef<str>>(text: T, code: Catcode) -> Self {
756 Token {
757 text: arena::pin(text),
758 code,
759 #[cfg(feature = "token-locators")]
760 loc: 0,
761 }
762 }
763
764 #[inline]
769 pub fn cycle_fingerprint(&self) -> u64 {
770 use std::hash::{Hash, Hasher};
771 let mut h = rustc_hash::FxHasher::default();
772 self.code.hash(&mut h);
773 if self.code != Catcode::SPACE {
774 self.text.hash(&mut h);
775 }
776 h.finish()
777 }
778
779 pub fn get_cs_name(&self) -> SymStr {
782 if self.code.is_primitive() {
783 self.code.name_sym()
784 } else {
785 self.get_sym()
786 }
787 }
788
789 pub fn with_cs_name<R, FnR>(&self, caller: FnR) -> R
793 where FnR: FnOnce(&str) -> R {
794 if self.code.is_primitive() {
795 caller(self.code.name())
796 } else {
797 self.with_str(caller)
798 }
799 }
800
801 pub fn pin_cs_name(&self) -> SymStr {
803 if self.code.is_primitive() {
804 self.code.name_sym()
805 } else {
806 self.get_sym()
807 }
808 }
809
810 pub fn get_primitive_name(&self) -> Option<&'static str> {
812 if self.code.is_primitive() {
813 Some(self.code.name())
814 } else {
815 None
816 }
817 }
818
819 pub fn get_executable_name(&self) -> String {
821 let cc = self.code;
822 if cc.is_executable() {
823 self
824 .get_primitive_name()
825 .map(ToString::to_string)
826 .unwrap_or_else(|| self.with_str(|text| text.to_string()))
827 } else {
828 String::new()
829 }
830 }
831
832 pub fn get_executable_primitive_name(&self) -> Option<&'static str> {
834 let cc = self.code;
835 if cc.is_executable() && cc.is_primitive() {
836 Some(self.code.name())
837 } else {
838 None
839 }
840 }
841
842 pub fn get_sym(&self) -> SymStr { self.text }
844 pub fn with_str<R, FnR>(&self, caller: FnR) -> R
847 where FnR: FnOnce(&str) -> R {
848 arena::with(self.text, caller)
849 }
850
851 pub fn is_noexpand_family(&self) -> bool {
861 self.code == Catcode::CS
862 && self.with_str(|s| {
863 s.starts_with(NOEXPAND_PREFIX)
864 && (s.len() == NOEXPAND_PREFIX.len()
865 || s.as_bytes()[NOEXPAND_PREFIX.len()] == NOEXPAND_SEP)
866 })
867 }
868
869 pub fn noexpand_shadowed(&self) -> Option<Token> {
873 if self.code != Catcode::CS {
874 return None;
875 }
876 let (name, code) = self.with_str(|s| {
883 let rest = s.strip_prefix(NOEXPAND_PREFIX)?;
884 let rest = rest.strip_prefix(NOEXPAND_SEP as char)?;
885 let code = if rest.starts_with('\\') {
886 Catcode::CS
887 } else {
888 Catcode::ACTIVE
889 };
890 Some((rest.to_string(), code))
891 })?;
892 Some(Token {
893 text: arena::pin(name),
894 code,
895 #[cfg(feature = "token-locators")]
896 loc: 0,
897 })
898 }
899
900 pub fn get_charcode(&self) -> u32 {
903 if self.code == Catcode::CS {
904 256
905 } else {
906 self.with_str(|text| {
907 if let Some(c) = text.chars().next() {
908 c as u32
909 } else {
910 0
911 }
912 })
913 }
914 }
915
916 pub fn get_catcode(&self) -> Catcode { self.code }
918 pub fn is_executable(&self) -> bool { self.code.is_executable() }
920
921 pub fn neutralize(self, extraspecials: &[char]) -> Token {
928 let first_c: Option<char> = self.with_str(|text| text.chars().next());
929 let ch = match first_c {
930 Some(ch) => ch,
931 None => return self,
932 };
933 let cc = self.code;
934 if cc.is_neutralizable() {
935 for extra in extraspecials {
936 if extra == &ch {
937 let mut tmp = [0u8; 4];
938 let s = ch.encode_utf8(&mut tmp);
939 return T_OTHER!(s);
940 }
941 }
942 let maybe_return = state::with_value("SPECIALS", |specials_opt| {
943 if let Some(Stored::Chars(specials_list)) = specials_opt {
944 for special in specials_list.iter() {
945 if *special == ch {
946 let mut tmp = [0u8; 4];
947 let s = ch.encode_utf8(&mut tmp);
948 return Some(T_OTHER!(s));
949 }
950 }
951 }
952 None
953 });
954 if let Some(token) = maybe_return {
955 return token;
956 }
957 }
958 self
959 }
960
961 pub fn as_other(&self) -> Token {
962 Token {
963 text: self.text,
964 code: Catcode::OTHER,
965 #[cfg(feature = "token-locators")]
966 loc: 0,
967 }
968 }
969 pub fn as_cs(&self) -> Token {
970 Token {
971 text: self.text,
972 code: Catcode::CS,
973 #[cfg(feature = "token-locators")]
974 loc: 0,
975 }
976 }
977
978 pub fn substitute_parameters(self, args: &[&Token]) -> Self {
979 if self.code == Catcode::ARG {
980 self.with_str(|text| {
981 let arg_idx = text
982 .parse::<usize>()
983 .expect("ARG catcode tokens should always contain numeric literals as text");
984 *args[arg_idx - 1]
985 })
986 } else {
987 self
988 }
989 }
990
991 pub fn revert(self) -> Token { self }
993
994 pub fn stringify(&self) -> String {
996 self.with_str(|text| {
997 let display_text = if text.len() == 1 {
999 let c = text.chars().next().unwrap() as u16;
1000 if c < 0x020 {
1001 Cow::Owned(s!("U+{:04x}/{}", c, CONTROLNAME[c as usize]))
1002 } else {
1003 Cow::Borrowed(text)
1004 }
1005 } else {
1006 Cow::Borrowed(text)
1007 };
1008 s!("{}[{}]", self.code.short_name(), display_text)
1009 })
1010 }
1011
1012 pub fn to_register(&self) -> Option<Rc<Register>> { state::lookup_register_definition(self) }
1013
1014 pub fn to_number(&self) -> Number {
1015 Number::new(self.with_str(|text| text.parse::<i64>()).unwrap_or(0))
1016 }
1017
1018 pub fn to_dimension(&self) -> Dimension {
1019 Dimension::new_f64(self.with_str(|text| text.parse::<f64>().unwrap_or(0.0)))
1020 }
1021
1022 pub fn to_mu_dimension(&self) -> MuDimension {
1023 MuDimension::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1024 }
1025
1026 pub fn to_glue(&self) -> Glue {
1027 Glue::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1028 }
1029
1030 pub fn to_mu_glue(&self) -> MuGlue {
1031 MuGlue::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1032 }
1033
1034 pub fn to_float(&self) -> Float {
1035 Float::new_f64(self.with_str(|s| s.parse::<f64>()).unwrap_or(0.0))
1036 }
1037
1038 pub fn be_digested(self) -> Result<Digested> { crate::stomach::digest(Tokens::new(vec![self])) }
1039
1040 pub fn defined_as(&self, other: &Token) -> bool {
1045 let cc = self.code;
1046 let occ = other.get_catcode();
1047 if (cc == occ) && ((occ == Catcode::SPACE) || (self.text == other.get_sym())) {
1048 return true;
1049 }
1050 if matches!(cc, Catcode::CS | Catcode::ACTIVE) {
1051 let letto_opt: Option<Token> = state::with_meaning(self, |defn_opt| {
1057 defn_opt.and_then(|defn| match defn {
1058 Stored::Token(t) => Some(*t),
1059 Stored::Expandable(inner) => Some(*inner.get_cs()),
1060 Stored::Primitive(inner) => Some(*inner.get_cs()),
1061 Stored::MathPrimitive(inner) => Some(*inner.get_cs()),
1062 Stored::Register(inner) => Some(*inner.get_cs()),
1063 Stored::Conditional(inner) => Some(*inner.get_cs()),
1064 Stored::Constructor(inner) => Some(*inner.get_cs()),
1065 _ => None,
1066 })
1067 });
1068 if let Some(letto) = letto_opt
1069 && (letto.get_catcode() == occ)
1070 && ((occ == Catcode::SPACE) || letto.get_sym() == other.get_sym())
1071 {
1072 return true;
1073 }
1074 }
1075 false
1076 }
1077}
1078
1079impl From<&str> for Token {
1082 fn from(text: &str) -> Token {
1083 match text.chars().next() {
1084 Some('{') => T_BEGIN!(),
1085 Some('}') => T_END!(),
1086 Some('$') => T_MATH!(),
1087 Some('#') => T_PARAM!(),
1088 Some('&') => T_ALIGN!(),
1089 Some('^') => T_SUPER!(),
1090 Some('_') => T_SUB!(),
1091 Some('\\') => T_CS!(text),
1092 Some('%') => T_COMMENT!(text),
1093 _ => {
1094 if text.chars().all(|c| c.is_alphabetic()) {
1095 T_LETTER!(text)
1096 } else if text.chars().all(|c| c.is_whitespace()) {
1097 T_SPACE!()
1098 } else {
1099 T_OTHER!(text)
1100 }
1101 },
1102 }
1103 }
1104}
1105
1106#[cfg(feature = "token-locators")]
1113#[derive(Clone, Copy, Debug)]
1114pub struct TokenStart {
1115 pub source: SymStr,
1116 pub line: u32,
1117 pub col: u32,
1118 pub inherited: bool,
1127}
1128
1129#[cfg(feature = "token-locators")]
1130thread_local! {
1131 static TOKEN_ORIGINS: std::cell::RefCell<Vec<TokenStart>> =
1132 const { std::cell::RefCell::new(Vec::new()) };
1133}
1134
1135#[cfg(feature = "token-locators")]
1138pub fn push_token_origin(source: SymStr, line: u32, col: u32) -> u32 {
1139 TOKEN_ORIGINS.with(|o| {
1140 let mut v = o.borrow_mut();
1141 v.push(TokenStart {
1142 source,
1143 line,
1144 col,
1145 inherited: false,
1146 });
1147 v.len() as u32 })
1149}
1150
1151#[cfg(feature = "token-locators")]
1157pub fn push_inherited_origin(handle: u32) -> u32 {
1158 if handle == 0 {
1159 return 0;
1160 }
1161 TOKEN_ORIGINS.with(|o| {
1162 let mut v = o.borrow_mut();
1163 let Some(mut start) = v.get((handle - 1) as usize).copied() else {
1164 return 0;
1165 };
1166 start.inherited = true;
1167 v.push(start);
1168 v.len() as u32
1169 })
1170}
1171
1172#[cfg(feature = "token-locators")]
1175pub fn get_token_origin(handle: u32) -> Option<TokenStart> {
1176 if handle == 0 {
1177 return None;
1178 }
1179 TOKEN_ORIGINS.with(|o| o.borrow().get((handle - 1) as usize).copied())
1180}
1181
1182#[cfg(feature = "token-locators")]
1184pub fn clear_token_origins() { TOKEN_ORIGINS.with(|o| o.borrow_mut().clear()); }
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189
1190 #[test]
1195 fn token_size_invariant() {
1196 #[cfg(not(feature = "token-locators"))]
1197 assert_eq!(
1198 size_of::<Token>(),
1199 8,
1200 "default Token must stay 8 bytes (SymStr + Catcode)"
1201 );
1202 #[cfg(feature = "token-locators")]
1203 assert_eq!(
1204 std::mem::size_of::<Token>(),
1205 12,
1206 "token-locators Token is 8 + a u32 origin handle"
1207 );
1208 }
1209
1210 #[cfg(feature = "token-locators")]
1216 #[test]
1217 fn token_origin_capture() {
1218 super::clear_token_origins();
1219 let toks = crate::mouth::tokenize("Hello");
1221 let got: Vec<(u32, u32)> = toks
1222 .unlist_ref()
1223 .iter()
1224 .map(|t| {
1225 let o = super::get_token_origin(t.loc).expect("token carries an origin handle");
1226 (o.line, o.col)
1227 })
1228 .collect();
1229 assert_eq!(
1230 got,
1231 vec![(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)],
1232 "each letter's captured (line, col) must be exact"
1233 );
1234 }
1235
1236 #[test]
1237 fn catcode_name_covers_all_variants() {
1238 use Catcode::*;
1240 for cc in [
1241 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE, IGNORE, LETTER, OTHER,
1242 ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1243 ] {
1244 assert!(!cc.name().is_empty(), "{cc:?}.name() is empty");
1245 }
1246 }
1247
1248 #[test]
1249 fn catcode_name_specific_values() {
1250 assert_eq!(Catcode::ESCAPE.name(), "Escape");
1251 assert_eq!(Catcode::BEGIN.name(), "Begin");
1252 assert_eq!(Catcode::CS.name(), "ControlSequence");
1253 assert_eq!(Catcode::LETTER.name(), "Letter");
1254 }
1255
1256 #[test]
1257 fn catcode_short_name_starts_with_t_prefix() {
1258 use Catcode::*;
1259 for cc in [
1260 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE, IGNORE, LETTER, OTHER,
1261 ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1262 ] {
1263 assert!(
1264 cc.short_name().starts_with("T_"),
1265 "{cc:?}.short_name() = {} lacks T_ prefix",
1266 cc.short_name()
1267 );
1268 }
1269 }
1270
1271 #[test]
1272 fn catcode_meaning_mostly_nonempty() {
1273 assert!(!Catcode::ESCAPE.meaning().is_empty());
1275 assert!(!Catcode::LETTER.meaning().is_empty());
1276 assert!(!Catcode::OTHER.meaning().is_empty());
1277 assert_eq!(Catcode::CS.meaning(), "");
1279 }
1280
1281 #[test]
1282 fn is_primitive_checks() {
1283 use Catcode::*;
1284 for cc in [
1286 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE,
1287 ] {
1288 assert!(cc.is_primitive(), "{cc:?} should be primitive");
1289 }
1290 for cc in [
1292 IGNORE, LETTER, OTHER, ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1293 ] {
1294 assert!(!cc.is_primitive(), "{cc:?} should not be primitive");
1295 }
1296 }
1297
1298 #[test]
1299 fn is_executable_checks() {
1300 use Catcode::*;
1301 for cc in [BEGIN, END, MATH, ALIGN, SUPER, SUB, ACTIVE, CS] {
1302 assert!(cc.is_executable(), "{cc:?} should be executable");
1303 }
1304 for cc in [
1305 EOL, ESCAPE, PARAM, SPACE, IGNORE, LETTER, OTHER, COMMENT, INVALID, MARKER, ARG,
1306 ] {
1307 assert!(!cc.is_executable(), "{cc:?} should not be executable");
1308 }
1309 }
1310
1311 #[test]
1312 fn is_neutralizable_set() {
1313 use Catcode::*;
1314 for cc in [MATH, ALIGN, PARAM, SUPER, SUB, ACTIVE] {
1315 assert!(cc.is_neutralizable(), "{cc:?}");
1316 }
1317 assert!(!CS.is_neutralizable());
1318 assert!(!LETTER.is_neutralizable());
1319 }
1320
1321 #[test]
1322 fn is_active_or_cs_narrow_set() {
1323 assert!(Catcode::ACTIVE.is_active_or_cs());
1324 assert!(Catcode::CS.is_active_or_cs());
1325 assert!(!Catcode::LETTER.is_active_or_cs());
1326 assert!(!Catcode::ESCAPE.is_active_or_cs());
1327 }
1328
1329 #[test]
1330 fn is_absorbable_space_letter_other_comment() {
1331 use Catcode::*;
1332 assert!(SPACE.is_absorbable());
1333 assert!(LETTER.is_absorbable());
1334 assert!(OTHER.is_absorbable());
1335 assert!(COMMENT.is_absorbable());
1336 assert!(!CS.is_absorbable());
1338 assert!(!BEGIN.is_absorbable());
1339 }
1340
1341 #[test]
1342 fn is_gullet_holdable_comment_marker_only() {
1343 assert!(Catcode::COMMENT.is_gullet_holdable());
1344 assert!(Catcode::MARKER.is_gullet_holdable());
1345 assert!(!Catcode::SPACE.is_gullet_holdable());
1346 assert!(!Catcode::LETTER.is_gullet_holdable());
1347 }
1348
1349 #[test]
1350 fn is_balanced_interesting_begin_end_marker() {
1351 assert!(Catcode::BEGIN.is_balanced_interesting());
1352 assert!(Catcode::END.is_balanced_interesting());
1353 assert!(Catcode::MARKER.is_balanced_interesting());
1354 assert!(!Catcode::LETTER.is_balanced_interesting());
1355 assert!(!Catcode::MATH.is_balanced_interesting());
1356 }
1357
1358 #[test]
1359 fn catcode_u8_roundtrip() {
1360 use Catcode::*;
1363 for cc in [
1364 ESCAPE, BEGIN, END, MATH, ALIGN, EOL, PARAM, SUPER, SUB, SPACE, IGNORE, LETTER, OTHER,
1365 ACTIVE, COMMENT, INVALID, CS, MARKER, ARG,
1366 ] {
1367 let b: u8 = cc.into();
1368 let cc2: Catcode = b.into();
1369 assert_eq!(cc, cc2, "roundtrip broke for {cc:?} (u8={b})");
1370 }
1371 }
1372
1373 #[test]
1374 fn token_new_and_display() {
1375 let t = Token::new("foo", Catcode::LETTER);
1376 assert_eq!(format!("{t}"), "foo");
1377 assert_eq!(t.code, Catcode::LETTER);
1378 }
1379
1380 #[test]
1381 fn token_arg_display_prepends_hash() {
1382 let t = Token::new("1", Catcode::ARG);
1384 assert_eq!(format!("{t}"), "#1");
1385 }
1386}