1use crate::error::{ParseError, Result, Span};
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct Token {
8 pub kind: TokenKind,
10 pub span: Span,
12}
13
14#[derive(Debug, Clone, PartialEq)]
16pub enum TokenKind {
17 LBracket,
20 RBracket,
22 LParen,
24 RParen,
26 Colon,
28 Dot,
30 Comma,
32 Star,
34 Equal,
37 NotEqual,
39 LessThan,
41 LessThanOrEqual,
43 GreaterThan,
45 GreaterThanOrEqual,
47 And,
50 Or,
52 Not,
54 FollowedBy,
56 Like,
58 Matches,
60 In,
62 IsSubset,
64 IsSuperset,
66 Exists,
68 Within,
70 Repeats,
72 Seconds,
74 Times,
76 Start,
78 Stop,
80 Identifier(String),
83 String(String),
85 Integer(i64),
87 Float(f64),
89 Boolean(bool),
91 Timestamp(String),
93 Binary(String),
95 Hex(String),
97}
98
99pub fn tokenize(src: &str) -> Result<Vec<Token>> {
101 Lexer::new(src).run()
102}
103
104struct Lexer<'a> {
105 src: &'a [u8],
106 pos: usize,
107 tokens: Vec<Token>,
108}
109
110impl<'a> Lexer<'a> {
111 fn new(src: &'a str) -> Self {
112 Lexer {
113 src: src.as_bytes(),
114 pos: 0,
115 tokens: Vec::new(),
116 }
117 }
118
119 fn run(mut self) -> Result<Vec<Token>> {
120 while let Some(&c) = self.src.get(self.pos) {
121 match c {
122 b' ' | b'\t' | b'\r' | b'\n' => self.pos += 1,
123 b'[' => self.punct(TokenKind::LBracket),
124 b']' => self.punct(TokenKind::RBracket),
125 b'(' => self.punct(TokenKind::LParen),
126 b')' => self.punct(TokenKind::RParen),
127 b':' => self.punct(TokenKind::Colon),
128 b'.' if !self.peek_is_digit(1) => self.punct(TokenKind::Dot),
129 b',' => self.punct(TokenKind::Comma),
130 b'*' => self.punct(TokenKind::Star),
131 b'=' => self.punct(TokenKind::Equal),
132 b'!' => self.lex_bang()?,
133 b'<' => self.lex_lt(),
134 b'>' => self.lex_gt(),
135 b'\'' => self.lex_string()?,
136 b'-' | b'0'..=b'9' => self.lex_number()?,
137 _ if is_ident_start(c) => self.lex_word_or_typed_literal()?,
138 _ => {
139 return Err(ParseError::new(
140 format!("unexpected character '{}'", c as char),
141 Span::new(self.pos, self.pos + 1),
142 ))
143 }
144 }
145 }
146 Ok(self.tokens)
147 }
148
149 fn peek_is_digit(&self, ahead: usize) -> bool {
150 matches!(self.src.get(self.pos + ahead), Some(b'0'..=b'9'))
151 }
152
153 fn push(&mut self, kind: TokenKind, start: usize) {
154 self.tokens.push(Token {
155 kind,
156 span: Span::new(start, self.pos),
157 });
158 }
159
160 fn punct(&mut self, kind: TokenKind) {
161 let start = self.pos;
162 self.pos += 1;
163 self.push(kind, start);
164 }
165
166 fn lex_bang(&mut self) -> Result<()> {
167 let start = self.pos;
168 if self.src.get(self.pos + 1) == Some(&b'=') {
169 self.pos += 2;
170 self.push(TokenKind::NotEqual, start);
171 Ok(())
172 } else {
173 Err(ParseError::new(
174 "expected '=' after '!'",
175 Span::new(start, start + 1),
176 ))
177 }
178 }
179
180 fn lex_lt(&mut self) {
181 let start = self.pos;
182 if self.src.get(self.pos + 1) == Some(&b'=') {
183 self.pos += 2;
184 self.push(TokenKind::LessThanOrEqual, start);
185 } else {
186 self.pos += 1;
187 self.push(TokenKind::LessThan, start);
188 }
189 }
190
191 fn lex_gt(&mut self) {
192 let start = self.pos;
193 if self.src.get(self.pos + 1) == Some(&b'=') {
194 self.pos += 2;
195 self.push(TokenKind::GreaterThanOrEqual, start);
196 } else {
197 self.pos += 1;
198 self.push(TokenKind::GreaterThan, start);
199 }
200 }
201
202 fn read_quoted(&mut self) -> Result<String> {
205 let start = self.pos;
206 debug_assert_eq!(self.src.get(self.pos), Some(&b'\''));
207 self.pos += 1; let mut out = String::new();
209 loop {
210 match self.src.get(self.pos) {
211 None => {
212 return Err(ParseError::new(
213 "unterminated string literal",
214 Span::new(start, self.pos),
215 ))
216 }
217 Some(b'\\') => match self.src.get(self.pos + 1) {
218 Some(b'\'') => {
219 out.push('\'');
220 self.pos += 2;
221 }
222 Some(b'\\') => {
223 out.push('\\');
224 self.pos += 2;
225 }
226 _ => {
227 out.push('\\');
228 self.pos += 1;
229 }
230 },
231 Some(b'\'') => {
232 self.pos += 1; return Ok(out);
234 }
235 Some(&b) => {
236 out.push(b as char);
237 self.pos += 1;
238 }
239 }
240 }
241 }
242
243 fn lex_string(&mut self) -> Result<()> {
244 let start = self.pos;
245 let s = self.read_quoted()?;
246 self.push(TokenKind::String(s), start);
247 Ok(())
248 }
249
250 fn lex_number(&mut self) -> Result<()> {
251 let start = self.pos;
252 if self.src.get(self.pos) == Some(&b'-') {
253 self.pos += 1;
254 }
255 let mut is_float = false;
256 while let Some(&c) = self.src.get(self.pos) {
257 match c {
258 b'0'..=b'9' => self.pos += 1,
259 b'.' => {
260 is_float = true;
261 self.pos += 1;
262 }
263 _ => break,
264 }
265 }
266 let text = std::str::from_utf8(&self.src[start..self.pos]).unwrap();
267 if is_float {
268 let v: f64 = text.parse().map_err(|_| {
269 ParseError::new("invalid float literal", Span::new(start, self.pos))
270 })?;
271 self.push(TokenKind::Float(v), start);
272 } else {
273 let v: i64 = text.parse().map_err(|_| {
274 ParseError::new("invalid integer literal", Span::new(start, self.pos))
275 })?;
276 self.push(TokenKind::Integer(v), start);
277 }
278 Ok(())
279 }
280
281 fn lex_word_or_typed_literal(&mut self) -> Result<()> {
284 let start = self.pos;
285 if matches!(self.src.get(self.pos), Some(b't' | b'b' | b'h'))
287 && self.src.get(self.pos + 1) == Some(&b'\'')
288 {
289 let prefix = self.src[self.pos];
290 self.pos += 1; let body = self.read_quoted()?;
292 let kind = match prefix {
293 b't' => TokenKind::Timestamp(body),
294 b'b' => TokenKind::Binary(body),
295 _ => TokenKind::Hex(body),
296 };
297 self.push(kind, start);
298 return Ok(());
299 }
300
301 while let Some(&c) = self.src.get(self.pos) {
302 if is_ident_continue(c) {
303 self.pos += 1;
304 } else {
305 break;
306 }
307 }
308 let word = std::str::from_utf8(&self.src[start..self.pos]).unwrap();
309 let kind = keyword(word).unwrap_or_else(|| TokenKind::Identifier(word.to_string()));
310 self.push(kind, start);
311 Ok(())
312 }
313}
314
315fn is_ident_start(c: u8) -> bool {
316 c.is_ascii_alphabetic() || c == b'_'
317}
318
319fn is_ident_continue(c: u8) -> bool {
321 c.is_ascii_alphanumeric() || c == b'_' || c == b'-'
322}
323
324fn keyword(word: &str) -> Option<TokenKind> {
326 match word.to_ascii_uppercase().as_str() {
327 "AND" => Some(TokenKind::And),
328 "OR" => Some(TokenKind::Or),
329 "NOT" => Some(TokenKind::Not),
330 "FOLLOWEDBY" => Some(TokenKind::FollowedBy),
331 "LIKE" => Some(TokenKind::Like),
332 "MATCHES" => Some(TokenKind::Matches),
333 "IN" => Some(TokenKind::In),
334 "ISSUBSET" => Some(TokenKind::IsSubset),
335 "ISSUPERSET" => Some(TokenKind::IsSuperset),
336 "EXISTS" => Some(TokenKind::Exists),
337 "WITHIN" => Some(TokenKind::Within),
338 "REPEATS" => Some(TokenKind::Repeats),
339 "SECONDS" => Some(TokenKind::Seconds),
340 "TIMES" => Some(TokenKind::Times),
341 "START" => Some(TokenKind::Start),
342 "STOP" => Some(TokenKind::Stop),
343 "TRUE" => Some(TokenKind::Boolean(true)),
344 "FALSE" => Some(TokenKind::Boolean(false)),
345 _ => None,
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 fn kinds(src: &str) -> Vec<TokenKind> {
354 tokenize(src).unwrap().into_iter().map(|t| t.kind).collect()
355 }
356
357 #[test]
358 fn lexes_simple_comparison() {
359 let k = kinds("[ipv4-addr:value = '1.2.3.4']");
360 assert_eq!(
361 k,
362 vec![
363 TokenKind::LBracket,
364 TokenKind::Identifier("ipv4-addr".to_string()),
365 TokenKind::Colon,
366 TokenKind::Identifier("value".to_string()),
367 TokenKind::Equal,
368 TokenKind::String("1.2.3.4".to_string()),
369 TokenKind::RBracket,
370 ]
371 );
372 }
373
374 #[test]
375 fn lexes_keywords_case_insensitively() {
376 assert_eq!(kinds("AND and"), vec![TokenKind::And, TokenKind::And]);
377 assert_eq!(kinds("FOLLOWEDBY"), vec![TokenKind::FollowedBy]);
378 }
379
380 #[test]
381 fn lexes_operators() {
382 assert_eq!(
383 kinds("= != < <= > >="),
384 vec![
385 TokenKind::Equal,
386 TokenKind::NotEqual,
387 TokenKind::LessThan,
388 TokenKind::LessThanOrEqual,
389 TokenKind::GreaterThan,
390 TokenKind::GreaterThanOrEqual,
391 ]
392 );
393 }
394
395 #[test]
396 fn lexes_literals() {
397 assert_eq!(kinds("42"), vec![TokenKind::Integer(42)]);
398 assert_eq!(kinds("-7"), vec![TokenKind::Integer(-7)]);
399 assert_eq!(kinds("2.5"), vec![TokenKind::Float(2.5)]);
400 assert_eq!(
401 kinds("true false"),
402 vec![TokenKind::Boolean(true), TokenKind::Boolean(false)]
403 );
404 assert_eq!(
405 kinds("t'2014-01-13T07:03:17Z'"),
406 vec![TokenKind::Timestamp("2014-01-13T07:03:17Z".to_string())]
407 );
408 assert_eq!(
409 kinds("b'aGVsbG8='"),
410 vec![TokenKind::Binary("aGVsbG8=".to_string())]
411 );
412 assert_eq!(
413 kinds("h'1234abcd'"),
414 vec![TokenKind::Hex("1234abcd".to_string())]
415 );
416 }
417
418 #[test]
419 fn lexes_string_escapes() {
420 assert_eq!(kinds(r"'a\'b'"), vec![TokenKind::String("a'b".to_string())]);
421 assert_eq!(
422 kinds(r"'a\\b'"),
423 vec![TokenKind::String(r"a\b".to_string())]
424 );
425 }
426
427 #[test]
428 fn unterminated_string_errors() {
429 let err = tokenize("'oops").unwrap_err();
430 assert!(err.message.contains("unterminated"), "got: {}", err.message);
431 }
432
433 #[test]
434 fn tracks_spans() {
435 let toks = tokenize("[a:b]").unwrap();
436 assert_eq!(toks[0].span, crate::error::Span::new(0, 1)); }
438}