mod cursor;
mod identifier;
mod literal;
mod trivia;
use super::token::{
is_keyword, is_operator, is_operator_char, is_punctuator, punctuator_str, Position, Token,
TokenType,
};
use radixdb_core::SmartString;
pub struct Lexer {
input: Box<[u8]>,
position: usize,
read_position: usize,
ch: char,
eof: bool,
pos: Position,
last_error: Option<String>,
}
impl Lexer {
pub fn new(input: &str) -> Self {
let bytes: Box<[u8]> = input.as_bytes().into();
let mut lexer = Self {
input: bytes,
position: 0,
read_position: 0,
ch: '\0',
eof: true,
pos: Position::new(0, 1, 1),
last_error: None,
};
lexer.read_char();
lexer
}
pub fn next_token(&mut self) -> Token {
self.skip_whitespace();
let pos = self.pos;
match self.ch {
'\0' if self.eof => Token::eof(pos),
'\0' => {
self.read_char();
Token::error("NULL byte (0x00) is not allowed in SQL input", "", pos)
}
'\'' => {
let literal = self.read_string_literal();
if let Some(err) = self.last_error.take() {
return Token::error(err, "", pos);
}
Token::new(TokenType::String, literal, pos)
}
'"' => {
let literal = self.read_quoted_identifier('"');
if let Some(err) = self.last_error.take() {
return Token::error(err, "", pos);
}
Token::new_quoted(TokenType::Identifier, literal, pos)
}
'`' => {
let literal = self.read_quoted_identifier('`');
if let Some(err) = self.last_error.take() {
return Token::error(err, "", pos);
}
Token::new_quoted(TokenType::Identifier, literal, pos)
}
c if c.is_ascii_digit() => {
let literal = self.read_number();
if literal.contains('.') || literal.contains('e') || literal.contains('E') {
Token::new(TokenType::Float, literal, pos)
} else {
Token::new(TokenType::Integer, literal, pos)
}
}
'#' => {
let literal = self.read_line_comment();
if let Some(err) = self.last_error.take() {
return Token::error(err, "", pos);
}
Token::new(TokenType::Comment, literal, pos)
}
'-' if self.peek_char() == '-' => {
let literal = self.read_line_comment();
if let Some(err) = self.last_error.take() {
return Token::error(err, "", pos);
}
Token::new(TokenType::Comment, literal, pos)
}
'/' if self.peek_char() == '*' => {
let literal = self.read_block_comment();
if let Some(err) = self.last_error.take() {
return Token::error(err, "", pos);
}
Token::new(TokenType::Comment, literal, pos)
}
'$' if self.peek_char().is_ascii_digit() => {
let literal = self.read_parameter();
Token::new(TokenType::Parameter, literal, pos)
}
'?' => {
self.read_char();
Token::new(TokenType::Parameter, "?", pos)
}
':' if self.peek_char().is_alphabetic() || self.peek_char() == '_' => {
let literal = self.read_named_parameter();
Token::new(TokenType::Parameter, literal, pos)
}
':' if self.peek_char() == '=' => {
let literal = self.read_operator();
Token::new(TokenType::Operator, literal, pos)
}
'*' => {
self.read_char();
Token::new(TokenType::Operator, "*", pos)
}
c if is_punctuator(c) => {
self.read_char();
Token::new(TokenType::Punctuator, punctuator_str(c).unwrap(), pos)
}
c if is_operator_char(c) => {
let literal = self.read_operator();
Token::new(TokenType::Operator, literal, pos)
}
c if c.is_alphabetic() || c == '_' => {
let literal = self.read_identifier();
if is_keyword(&literal) {
Token::new(TokenType::Keyword, literal.to_uppercase(), pos)
} else {
Token::new(TokenType::Identifier, literal, pos)
}
}
c => {
self.read_char();
Token::error(
format!("unrecognized character: {:?}", c),
c.to_string(),
pos,
)
}
}
}
fn read_operator(&mut self) -> SmartString {
let mut result = SmartString::new("");
let first_char = self.ch;
result.push(first_char);
self.read_char();
if !self.eof {
let two_chars: SmartString =
SmartString::from_iter([first_char, self.ch].iter().copied());
if is_operator(&two_chars) {
result.push(self.ch);
self.read_char();
if !self.eof {
let mut three_chars = two_chars.clone();
three_chars.push(self.ch);
if is_operator(&three_chars) {
result.push(self.ch);
self.read_char();
}
}
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_select() {
let mut lexer = Lexer::new("SELECT * FROM users");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
assert_eq!(token.literal, "SELECT");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Operator);
assert_eq!(token.literal, "*");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
assert_eq!(token.literal, "FROM");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Identifier);
assert_eq!(token.literal, "users");
let token = lexer.next_token();
assert!(token.is_eof());
}
#[test]
fn test_numbers() {
let mut lexer = Lexer::new("123 45.67 -89 3.14e10 1.5E-3");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Integer);
assert_eq!(token.literal, "123");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Float);
assert_eq!(token.literal, "45.67");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Operator);
assert_eq!(token.literal, "-");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Integer);
assert_eq!(token.literal, "89");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Float);
assert_eq!(token.literal, "3.14e10");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Float);
assert_eq!(token.literal, "1.5E-3");
}
#[test]
fn test_string_literals() {
let mut lexer = Lexer::new("'hello' 'world''s' 'escaped\\ntext'");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::String);
assert_eq!(token.literal, "'hello'");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::String);
assert_eq!(token.literal, "'world's'");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::String);
assert_eq!(token.literal, "'escaped\\ntext'");
}
#[test]
fn test_quoted_identifiers() {
let mut lexer = Lexer::new("\"table name\" `column`");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Identifier);
assert_eq!(token.literal, "table name");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Identifier);
assert_eq!(token.literal, "column");
}
#[test]
fn test_operators() {
let mut lexer = Lexer::new("= <> >= <= != + - * / || -> ->>");
let expected = vec![
"=", "<>", ">=", "<=", "!=", "+", "-", "*", "/", "||", "->", "->>",
];
for exp in expected {
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Operator);
assert_eq!(token.literal, exp);
}
}
#[test]
fn test_punctuators() {
let mut lexer = Lexer::new("( ) , ; . [ ]");
let expected = vec!["(", ")", ",", ";", ".", "[", "]"];
for exp in expected {
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Punctuator);
assert_eq!(token.literal, exp);
}
}
#[test]
fn test_comments() {
let mut lexer = Lexer::new("-- line comment\nSELECT /* block */ 1");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Comment);
assert!(token.literal.contains("line comment"));
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
assert_eq!(token.literal, "SELECT");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Comment);
assert!(token.literal.contains("block"));
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Integer);
assert_eq!(token.literal, "1");
}
#[test]
fn test_double_dash_is_always_comment() {
let mut lexer = Lexer::new("SELECT --5");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
assert_eq!(token.literal, "SELECT");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Comment);
let mut lexer = Lexer::new("SELECT --val");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Comment);
let mut lexer = Lexer::new("--comment\nSELECT 1");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Comment);
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
assert_eq!(token.literal, "SELECT");
let mut lexer = Lexer::new("SELECT -- comment");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Comment);
let mut lexer = Lexer::new("SELECT - -5");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Operator);
assert_eq!(token.literal, "-");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Operator);
assert_eq!(token.literal, "-");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Integer);
assert_eq!(token.literal, "5");
}
#[test]
fn test_parameters() {
let mut lexer = Lexer::new("$1 $23 ? :name :user_id :_private");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Parameter);
assert_eq!(token.literal, "$1");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Parameter);
assert_eq!(token.literal, "$23");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Parameter);
assert_eq!(token.literal, "?");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Parameter);
assert_eq!(token.literal, ":name");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Parameter);
assert_eq!(token.literal, ":user_id");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Parameter);
assert_eq!(token.literal, ":_private");
}
#[test]
fn test_keywords_case_insensitive() {
let mut lexer = Lexer::new("select SELECT Select");
for _ in 0..3 {
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
assert_eq!(token.literal, "SELECT");
}
}
#[test]
fn test_position_tracking() {
let mut lexer = Lexer::new("SELECT\nFROM");
let token = lexer.next_token();
assert_eq!(token.position.line, 1);
assert_eq!(token.position.column, 1);
let token = lexer.next_token();
assert_eq!(token.position.line, 2);
assert_eq!(token.position.column, 1);
}
#[test]
fn test_complex_query() {
let query = r#"
SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.active = TRUE AND o.amount >= 100.50
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 0
ORDER BY order_count DESC
LIMIT 10
"#;
let mut lexer = Lexer::new(query);
let mut tokens = Vec::new();
loop {
let token = lexer.next_token();
if token.is_eof() {
break;
}
tokens.push(token);
}
assert!(tokens.len() > 30);
assert!(tokens.iter().any(|t| t.is_keyword("SELECT")));
assert!(tokens.iter().any(|t| t.is_keyword("FROM")));
assert!(tokens.iter().any(|t| t.is_keyword("JOIN")));
assert!(tokens.iter().any(|t| t.is_keyword("WHERE")));
assert!(tokens.iter().any(|t| t.is_keyword("GROUP")));
assert!(tokens.iter().any(|t| t.is_keyword("HAVING")));
assert!(tokens.iter().any(|t| t.is_keyword("ORDER")));
assert!(tokens.iter().any(|t| t.is_keyword("LIMIT")));
}
#[test]
fn test_error_token() {
let mut lexer = Lexer::new("SELECT © FROM");
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Keyword);
let token = lexer.next_token();
assert_eq!(token.token_type, TokenType::Error);
assert!(!token.literal.is_empty());
}
}