1use std::sync::Arc;
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum StringPart {
11 Literal(Arc<str>),
13 Variable(Arc<str>),
15 Expression(Arc<str>),
17 MethodCall(Arc<str>),
19 ArraySlice(Arc<str>),
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub enum TokenType {
26 Division,
29 RegexMatch,
31 Substitution,
33 Transliteration,
35 QuoteRegex,
37
38 StringLiteral,
41 QuoteSingle,
43 QuoteDouble,
45 QuoteWords,
47 QuoteCommand,
49
50 InterpolatedString(Vec<StringPart>),
53
54 HeredocStart,
57 HeredocBody(Arc<str>),
59
60 FormatBody(Arc<str>),
63
64 Version(Arc<str>),
67
68 Pod,
71
72 DataMarker(Arc<str>),
75 DataBody(Arc<str>),
77
78 UnknownRest,
81
82 Identifier(Arc<str>),
85 Number(Arc<str>),
87 Operator(Arc<str>),
89 Keyword(Arc<str>),
91
92 LeftParen,
95 RightParen,
97 LeftBracket,
99 RightBracket,
101 LeftBrace,
103 RightBrace,
105
106 Semicolon,
109 Comma,
111 Colon,
113 Arrow,
115 FatComma,
117
118 Whitespace,
121 Newline,
123 Comment(Arc<str>),
125
126 EOF,
129 Error(Arc<str>),
131}
132
133impl TokenType {
134 pub fn is_trivia(&self) -> bool {
136 matches!(self, Self::Whitespace | Self::Newline | Self::Comment(_))
137 }
138
139 pub fn is_recovery_token(&self) -> bool {
141 matches!(self, Self::UnknownRest | Self::Error(_))
142 }
143}
144
145#[derive(Debug, Clone)]
150pub struct Token {
151 pub token_type: TokenType,
153 pub text: Arc<str>,
155 pub start: usize,
157 pub end: usize,
159}
160
161impl Token {
162 pub fn new(token_type: TokenType, text: impl Into<Arc<str>>, start: usize, end: usize) -> Self {
164 Self { token_type, text: text.into(), start, end }
165 }
166
167 pub fn len(&self) -> usize {
169 self.end - self.start
170 }
171
172 pub fn is_empty(&self) -> bool {
174 self.start == self.end
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::{Token, TokenType};
181 use std::sync::Arc;
182
183 #[test]
184 fn token_type_trivia_classifier_matches_expected_variants()
185 -> Result<(), Box<dyn std::error::Error>> {
186 assert!(TokenType::Whitespace.is_trivia());
187 assert!(TokenType::Newline.is_trivia());
188 assert!(TokenType::Comment(Arc::from("# note")).is_trivia());
189 assert!(!TokenType::Identifier(Arc::from("foo")).is_trivia());
190 Ok(())
191 }
192
193 #[test]
194 fn token_type_recovery_classifier_matches_expected_variants()
195 -> Result<(), Box<dyn std::error::Error>> {
196 assert!(TokenType::UnknownRest.is_recovery_token());
197 assert!(TokenType::Error(Arc::from("oops")).is_recovery_token());
198 assert!(!TokenType::EOF.is_recovery_token());
199 Ok(())
200 }
201
202 #[test]
205 fn token_new_stores_type_text_and_span() -> Result<(), Box<dyn std::error::Error>> {
206 let tok = Token::new(TokenType::Semicolon, ";", 3, 4);
207 assert_eq!(tok.token_type, TokenType::Semicolon);
208 assert_eq!(tok.text.as_ref(), ";");
209 assert_eq!(tok.start, 3);
210 assert_eq!(tok.end, 4);
211 Ok(())
212 }
213
214 #[test]
215 fn token_new_accepts_arc_str_directly() -> Result<(), Box<dyn std::error::Error>> {
216 let text: Arc<str> = Arc::from("hello");
217 let tok = Token::new(TokenType::Identifier(Arc::from("hello")), text, 0, 5);
218 assert_eq!(tok.start, 0);
219 assert_eq!(tok.end, 5);
220 Ok(())
221 }
222
223 #[test]
226 fn token_len_returns_end_minus_start() -> Result<(), Box<dyn std::error::Error>> {
227 let tok = Token::new(TokenType::Semicolon, ";", 10, 17);
228 assert_eq!(tok.len(), 7);
229 Ok(())
230 }
231
232 #[test]
233 fn token_len_zero_when_span_is_empty() -> Result<(), Box<dyn std::error::Error>> {
234 let tok = Token::new(TokenType::EOF, "", 5, 5);
235 assert_eq!(tok.len(), 0);
236 Ok(())
237 }
238
239 #[test]
240 fn token_len_single_byte_span() -> Result<(), Box<dyn std::error::Error>> {
241 let tok = Token::new(TokenType::Comma, ",", 0, 1);
242 assert_eq!(tok.len(), 1);
243 Ok(())
244 }
245
246 #[test]
249 fn token_is_empty_true_for_zero_length_span() -> Result<(), Box<dyn std::error::Error>> {
250 let tok = Token::new(TokenType::EOF, "", 0, 0);
251 assert!(tok.is_empty());
252 Ok(())
253 }
254
255 #[test]
256 fn token_is_empty_true_for_non_zero_start_matching_end()
257 -> Result<(), Box<dyn std::error::Error>> {
258 let tok = Token::new(TokenType::EOF, "", 42, 42);
259 assert!(tok.is_empty());
260 Ok(())
261 }
262
263 #[test]
264 fn token_is_empty_false_for_non_zero_length_span() -> Result<(), Box<dyn std::error::Error>> {
265 let tok = Token::new(TokenType::Semicolon, ";", 0, 1);
266 assert!(!tok.is_empty());
267 Ok(())
268 }
269}