latex_rust/parser/
token.rs1use core::fmt;
4use core::iter::Peekable;
5use core::str::Chars;
6
7use crate::error::ParseError;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum Token {
12 Char(char),
14 Command(String),
16 BeginGroup,
18 EndGroup,
20 Superscript,
22 Subscript,
24 AlignmentTab,
26 MathShift,
28 DisplayShift,
30 Space,
32}
33
34impl fmt::Display for Token {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::Char(c) => write!(f, "char:{c}"),
38 Self::Command(name) => write!(f, "cmd:{name}"),
39 Self::BeginGroup => f.write_str("{"),
40 Self::EndGroup => f.write_str("}"),
41 Self::Superscript => f.write_str("^"),
42 Self::Subscript => f.write_str("_"),
43 Self::AlignmentTab => f.write_str("&"),
44 Self::MathShift => f.write_str("$"),
45 Self::DisplayShift => f.write_str("$$"),
46 Self::Space => f.write_str("space"),
47 }
48 }
49}
50
51#[must_use]
62pub fn format_tokens(tokens: &[Token]) -> String {
63 let mut out = String::new();
64 for (i, t) in tokens.iter().enumerate() {
65 if i > 0 {
66 out.push(' ');
67 }
68 out.push_str(&t.to_string());
69 }
70 out
71}
72
73pub fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
99 let mut chars = input.chars().peekable();
100 let mut out = Vec::new();
101 while let Some(c) = chars.next() {
102 match c {
103 '%' => skip_line(&mut chars),
104 ' ' => out.push(Token::Space),
105 '\t' | '\n' | '\r' => {}
106 '{' => out.push(Token::BeginGroup),
107 '}' => out.push(Token::EndGroup),
108 '^' => out.push(Token::Superscript),
109 '_' => out.push(Token::Subscript),
110 '&' => out.push(Token::AlignmentTab),
111 '$' => {
112 if chars.peek() == Some(&'$') {
113 chars.next();
114 out.push(Token::DisplayShift);
115 } else {
116 out.push(Token::MathShift);
117 }
118 }
119 '\\' => out.push(command(&mut chars)?),
120 other => out.push(Token::Char(other)),
121 }
122 }
123 Ok(out)
124}
125
126fn skip_line(chars: &mut Peekable<Chars<'_>>) {
127 for c in chars.by_ref() {
128 if c == '\n' {
129 break;
130 }
131 }
132}
133
134fn command(chars: &mut Peekable<Chars<'_>>) -> Result<Token, ParseError> {
135 let Some(&first) = chars.peek() else {
136 return Err(ParseError::TrailingBackslash);
137 };
138 if first.is_ascii_alphabetic() {
139 let mut name = String::new();
140 while let Some(&c) = chars.peek() {
141 if c.is_ascii_alphabetic() {
142 name.push(c);
143 chars.next();
144 } else {
145 break;
146 }
147 }
148 while matches!(chars.peek(), Some(' ' | '\t' | '\n' | '\r')) {
149 chars.next();
150 }
151 Ok(Token::Command(name))
152 } else {
153 chars.next();
154 Ok(Token::Command(first.to_string()))
155 }
156}