1use radixdb_core::SmartString;
20use rustc_hash::FxHashSet;
21use std::fmt;
22use std::sync::LazyLock;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub struct Position {
27 pub offset: usize,
29 pub line: usize,
31 pub column: usize,
33}
34
35impl Position {
36 pub fn new(offset: usize, line: usize, column: usize) -> Self {
38 Self {
39 offset,
40 line,
41 column,
42 }
43 }
44}
45
46impl fmt::Display for Position {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "line {}, column {}", self.line, self.column)
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum TokenType {
55 Error,
57 Eof,
59 Identifier,
61 Keyword,
63 String,
65 Integer,
67 Float,
69 Operator,
71 Punctuator,
73 Comment,
75 Parameter,
77}
78
79impl fmt::Display for TokenType {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 TokenType::Error => write!(f, "ERROR"),
83 TokenType::Eof => write!(f, "EOF"),
84 TokenType::Identifier => write!(f, "IDENTIFIER"),
85 TokenType::Keyword => write!(f, "KEYWORD"),
86 TokenType::String => write!(f, "STRING"),
87 TokenType::Integer => write!(f, "INTEGER"),
88 TokenType::Float => write!(f, "FLOAT"),
89 TokenType::Operator => write!(f, "OPERATOR"),
90 TokenType::Punctuator => write!(f, "PUNCTUATOR"),
91 TokenType::Comment => write!(f, "COMMENT"),
92 TokenType::Parameter => write!(f, "PARAMETER"),
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq)]
103pub struct Token {
104 pub token_type: TokenType,
106 pub literal: SmartString,
109 pub position: Position,
111 pub quoted: bool,
113}
114
115impl Token {
116 #[inline]
118 pub fn new(token_type: TokenType, literal: impl AsRef<str>, position: Position) -> Self {
119 Self {
120 token_type,
121 literal: SmartString::from(literal.as_ref()),
122 position,
123 quoted: false,
124 }
125 }
126
127 #[inline]
129 pub fn new_quoted(token_type: TokenType, literal: impl AsRef<str>, position: Position) -> Self {
130 Self {
131 token_type,
132 literal: SmartString::from(literal.as_ref()),
133 position,
134 quoted: true,
135 }
136 }
137
138 pub fn error(message: impl AsRef<str>, _literal: impl AsRef<str>, position: Position) -> Self {
140 Self {
141 token_type: TokenType::Error,
142 literal: SmartString::from(message.as_ref()),
143 position,
144 quoted: false,
145 }
146 }
147
148 #[inline]
150 pub fn eof(position: Position) -> Self {
151 Self {
152 token_type: TokenType::Eof,
153 literal: SmartString::const_new(""),
154 position,
155 quoted: false,
156 }
157 }
158
159 pub fn is_eof(&self) -> bool {
161 self.token_type == TokenType::Eof
162 }
163
164 pub fn is_error(&self) -> bool {
166 self.token_type == TokenType::Error
167 }
168
169 pub fn is_keyword(&self, keyword: &str) -> bool {
171 self.token_type == TokenType::Keyword && self.literal.eq_ignore_ascii_case(keyword)
172 }
173
174 pub fn is_operator(&self, op: &str) -> bool {
176 self.token_type == TokenType::Operator && self.literal == op
177 }
178
179 pub fn is_punctuator(&self, punct: &str) -> bool {
181 self.token_type == TokenType::Punctuator && self.literal == punct
182 }
183}
184
185impl fmt::Display for Token {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 if self.token_type == TokenType::Error {
188 write!(
190 f,
191 "{}: {} at {}",
192 self.token_type, self.literal, self.position
193 )
194 } else if self.token_type == TokenType::Keyword {
195 write!(
196 f,
197 "{}: {} at {}",
198 self.token_type, self.literal, self.position
199 )
200 } else {
201 write!(
202 f,
203 "{}: '{}' at {}",
204 self.token_type, self.literal, self.position
205 )
206 }
207 }
208}
209
210pub static KEYWORDS: &[&str] = &[
212 "SELECT",
213 "FROM",
214 "WHERE",
215 "INSERT",
216 "INTO",
217 "VALUES",
218 "UPDATE",
219 "SET",
220 "DELETE",
221 "CREATE",
222 "REPLACE",
223 "TABLE",
224 "DROP",
225 "ALTER",
226 "ADD",
227 "COLUMN",
228 "AND",
229 "OR",
230 "XOR",
231 "NOT",
232 "NULL",
233 "PRIMARY",
234 "PRAGMA",
235 "KEY",
236 "AUTO_INCREMENT",
237 "AUTOINCREMENT",
238 "DEFAULT",
239 "AS",
240 "OF",
241 "DISTINCT",
242 "ORDER",
243 "BY",
244 "ASC",
245 "DESC",
246 "LIMIT",
247 "OFFSET",
248 "GROUP",
249 "HAVING",
250 "JOIN",
251 "INNER",
252 "OUTER",
253 "LEFT",
254 "RIGHT",
255 "FULL",
256 "ON",
257 "DUPLICATE",
258 "CONFLICT",
259 "DO",
260 "NOTHING",
261 "USING",
262 "CROSS",
263 "NATURAL",
264 "TRUE",
265 "FALSE",
266 "INTEGER",
267 "FLOAT",
268 "TEXT",
269 "BOOLEAN",
270 "BOOL",
271 "TIMESTAMP",
272 "TIMESTAMPTZ",
273 "DATETIME",
274 "DATE",
275 "TIME",
276 "JSON",
277 "UUID",
278 "VECTOR",
279 "CASE",
280 "CAST",
281 "EXTRACT",
282 "WHEN",
283 "THEN",
284 "ELSE",
285 "END",
286 "BETWEEN",
287 "IN",
288 "IS",
289 "LIKE",
290 "ILIKE",
291 "ESCAPE",
292 "GLOB",
293 "REGEXP",
294 "RLIKE",
295 "EXISTS",
296 "ALL",
297 "ANY",
298 "SOME",
299 "IF",
300 "UNION",
301 "INTERSECT",
302 "EXCEPT",
303 "WITH",
304 "UNIQUE",
305 "CHECK",
306 "CONSTRAINT",
307 "FOREIGN",
308 "REFERENCES",
309 "SHOW",
310 "DESCRIBE",
311 "DESC",
312 "TABLES",
313 "VIEWS",
314 "INDEXES",
315 "CASCADE",
316 "RESTRICT",
317 "INDEX",
318 "VIEW",
319 "EXTENSION",
320 "TYPE",
321 "VERSION",
322 "TRIGGER",
323 "PROCEDURE",
324 "FUNCTION",
325 "RETURNING",
326 "OVER",
327 "PARTITION",
328 "RANGE",
329 "ROWS",
330 "WINDOW",
331 "UNBOUNDED",
332 "BEGIN",
333 "TRANSACTION",
334 "COMMIT",
335 "ROLLBACK",
336 "SAVEPOINT",
337 "RELEASE",
338 "PRECEDING",
339 "FOLLOWING",
340 "CURRENT",
341 "ROW",
342 "MODIFY",
343 "RENAME",
344 "TO",
345 "VARCHAR",
346 "CHAR",
347 "STRING",
348 "BIGINT",
349 "TINYINT",
350 "SMALLINT",
351 "REAL",
352 "DOUBLE",
353 "DECIMAL",
354 "NUMERIC",
355 "INT",
356 "ISOLATION",
357 "LEVEL",
358 "READ",
359 "COMMITTED",
360 "UNCOMMITTED",
361 "INTERVAL",
362 "RECURSIVE",
363 "UNION",
364 "INTERSECT",
365 "EXCEPT",
366 "NULLS",
367 "FIRST",
368 "LAST",
369 "TRUNCATE",
370 "SOME",
371 "FILTER",
372 "RETURNING",
373 "EXPLAIN",
374 "ANALYZE",
375 "FETCH",
376 "NEXT",
377 "ONLY",
378 "VACUUM",
379 "COPY",
380 "FORMAT",
381 "HEADER",
382 "DELIMITER",
383 "DECLARE",
384 "CURSOR",
385 "CONSTANT",
386 "ELSIF",
387 "THEN",
388 "LOOP",
389 "WHILE",
390 "FOR",
391 "REVERSE",
392 "EXIT",
393 "CONTINUE",
394 "RETURN",
395 "QUERY",
396 "EXCEPTION",
397 "RAISE",
398 "OTHERS",
399 "OPEN",
400 "CLOSE",
401 "CALL",
402 "PERFORM",
403 "EXECUTE",
404 "FUNCTION",
405 "PROCEDURE",
406 "RETURNS",
407 "LANGUAGE",
408 "RADIX",
409 "NATIVE",
410 "OUT",
411 "INOUT",
412 "ARRAY",
413 "ROWTYPE",
414 "IMMUTABLE",
415 "STABLE",
416 "VOLATILE",
417 "SECURITY",
418 "INVOKER",
419 "DEFINER",
420 "SEARCH",
421 "PATH",
422 "RESOURCE",
423 "POLICY",
424 "STRICT",
425 "PRIORITY",
426 "BEFORE",
427 "AFTER",
428 "EACH",
429 "STATEMENT",
430 "OLD",
431 "NEW",
432 "JOB",
433 "SCHEDULE",
434 "EVERY",
435 "AT",
436 "ENABLE",
437 "DISABLE",
438 "RUN",
439 "PRINCIPAL",
440 "PASSWORD",
441 "ROLE",
442 "SCHEMA",
443 "DATABASE",
444 "GRANT",
445 "REVOKE",
446 "CONNECT",
447 "USAGE",
448 "OWNER",
449 "ADMIN",
450 "OPTION",
451 "FOUND",
452 "NOTFOUND",
453 "ROWCOUNT",
454 "ISOPEN",
455 "OPERATOR",
456 "CLASS",
457 "PLANNER",
458 "SUPPORT",
459 "LEFTARG",
460 "RIGHTARG",
461];
462
463static KEYWORD_SET: LazyLock<FxHashSet<&'static str>> = LazyLock::new(|| {
466 let mut set = FxHashSet::with_capacity_and_hasher(KEYWORDS.len(), Default::default());
467 for kw in KEYWORDS {
468 set.insert(*kw);
469 }
470 set
471});
472
473#[inline]
476pub fn is_keyword(s: &str) -> bool {
477 if KEYWORD_SET.contains(s) {
479 return true;
480 }
481 if s.len() <= 32 {
484 let mut buf = [0u8; 32];
485 let bytes = s.as_bytes();
486 for (i, &b) in bytes.iter().enumerate() {
487 buf[i] = b.to_ascii_uppercase();
488 }
489 let upper = unsafe { std::str::from_utf8_unchecked(&buf[..s.len()]) };
491 KEYWORD_SET.contains(upper)
492 } else {
493 let upper = s.to_uppercase();
495 KEYWORD_SET.contains(upper.as_str())
496 }
497}
498
499pub static OPERATORS: &[&str] = &[
501 "=", ">", "<", ">=", "<=", "<>", "!=", "+", "-", "*", "/", "%",
502 "||", "->", "->>", "&", "|", "^", "~", "<<", ">>", "<=>", "&&", "@>", "<@", ":=", "=>",
508];
509
510static OPERATOR_SET: LazyLock<FxHashSet<&'static str>> = LazyLock::new(|| {
512 let mut set = FxHashSet::with_capacity_and_hasher(OPERATORS.len(), Default::default());
513 for op in OPERATORS {
514 set.insert(*op);
515 }
516 set
517});
518
519#[inline]
521pub fn is_operator(s: &str) -> bool {
522 OPERATOR_SET.contains(s)
523}
524
525pub static PUNCTUATORS: &[char] = &[',', ';', '(', ')', '.', ':', '[', ']'];
527
528pub fn is_punctuator(c: char) -> bool {
530 PUNCTUATORS.contains(&c)
531}
532
533#[inline]
536pub fn punctuator_str(c: char) -> Option<&'static str> {
537 match c {
538 ',' => Some(","),
539 ';' => Some(";"),
540 '(' => Some("("),
541 ')' => Some(")"),
542 '.' => Some("."),
543 ':' => Some(":"),
544 '[' => Some("["),
545 ']' => Some("]"),
546 _ => None,
547 }
548}
549
550pub fn is_operator_char(c: char) -> bool {
552 matches!(
554 c,
555 '=' | '<' | '>' | '!' | '+' | '-' | '*' | '/' | '%' | '|' | '&' | '^' | '~' | ':' | '@'
556 )
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
564 fn test_position_display() {
565 let pos = Position::new(10, 2, 5);
566 assert_eq!(pos.to_string(), "line 2, column 5");
567 }
568
569 #[test]
570 fn test_token_type_display() {
571 assert_eq!(TokenType::Keyword.to_string(), "KEYWORD");
572 assert_eq!(TokenType::Identifier.to_string(), "IDENTIFIER");
573 assert_eq!(TokenType::String.to_string(), "STRING");
574 assert_eq!(TokenType::Eof.to_string(), "EOF");
575 }
576
577 #[test]
578 fn test_token_creation() {
579 let token = Token::new(TokenType::Keyword, "SELECT", Position::new(0, 1, 1));
580 assert_eq!(token.token_type, TokenType::Keyword);
581 assert_eq!(token.literal, "SELECT");
582 assert!(token.is_keyword("SELECT"));
583 assert!(token.is_keyword("select"));
584 assert!(!token.is_keyword("FROM"));
585 }
586
587 #[test]
588 fn test_error_token() {
589 let token = Token::error("unexpected character", "x", Position::new(5, 1, 6));
590 assert!(token.is_error());
591 assert_eq!(token.literal.as_str(), "unexpected character");
593 }
594
595 #[test]
596 fn test_eof_token() {
597 let token = Token::eof(Position::new(100, 5, 10));
598 assert!(token.is_eof());
599 assert_eq!(token.literal, "");
600 }
601
602 #[test]
603 fn test_is_keyword() {
604 assert!(is_keyword("SELECT"));
605 assert!(is_keyword("select"));
606 assert!(is_keyword("Select"));
607 assert!(!is_keyword("SELEC"));
608 assert!(!is_keyword("mycolumn"));
609 }
610
611 #[test]
612 fn test_is_operator() {
613 assert!(is_operator("="));
614 assert!(is_operator(">="));
615 assert!(is_operator("->"));
616 assert!(is_operator("->>"));
617 assert!(!is_operator("==="));
618 }
619
620 #[test]
621 fn test_is_punctuator() {
622 assert!(is_punctuator(','));
623 assert!(is_punctuator(';'));
624 assert!(is_punctuator('('));
625 assert!(is_punctuator(')'));
626 assert!(!is_punctuator('x'));
627 }
628
629 #[test]
630 fn test_is_operator_char() {
631 assert!(is_operator_char('='));
632 assert!(is_operator_char('+'));
633 assert!(is_operator_char('-'));
634 assert!(is_operator_char('|'));
635 assert!(!is_operator_char('a'));
636 assert!(!is_operator_char('#')); }
638
639 #[test]
640 fn test_token_display() {
641 let keyword = Token::new(TokenType::Keyword, "SELECT", Position::new(0, 1, 1));
642 assert!(keyword.to_string().contains("KEYWORD: SELECT"));
643
644 let string = Token::new(TokenType::String, "hello", Position::new(7, 1, 8));
645 assert!(string.to_string().contains("STRING: 'hello'"));
646
647 let error = Token::error("bad token", "x", Position::new(0, 1, 1));
648 assert!(error.to_string().contains("ERROR: bad token"));
649 }
650}