use crate::clex_language::clex_error_type::{ClexErrorType, ParentErrorType};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Span {
pub start: usize,
pub end: usize,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum TokenType {
LeftParens,
RightParens,
QuestionColon,
Backslash,
LeftSquareBracket,
RightSquareBracket,
LeftCurlyBrackets,
RightCurlyBrackets,
Comma,
Integer,
Float,
String,
CharacterSetAlpha,
CharacterSetAlnum,
CharacterSetNewline,
CharacterSetNumeric,
CharacterSetUpper,
CharacterSetLower,
CharacterSetAll,
LiteralNumber(i64),
LiteralString(String),
Eof,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Token {
pub token_type: TokenType,
pub lexeme: String,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct Tokens {
tokens: Vec<Token>,
start: usize,
current: usize,
source_language: String,
}
impl Tokens {
pub fn new(source_language: String) -> Self {
Self {
tokens: Vec::new(),
start: 0,
current: 0,
source_language,
}
}
pub fn get_tokens(&self) -> Vec<Token> {
self.tokens.clone()
}
pub fn scan_tokens(&mut self) -> Result<(), ClexErrorType> {
while !self.at_end() {
self.start = self.current;
self.scan_token()?;
}
self.tokens.push(Token {
token_type: TokenType::Eof,
lexeme: String::new(),
span: Span {
start: self.current,
end: self.current,
},
});
Ok(())
}
fn at_end(&self) -> bool {
self.source_language.len() <= self.current
}
fn scan_token(&mut self) -> Result<(), ClexErrorType> {
let c = self.advance().to_string(); match c.as_str() {
"(" => self.add_token(TokenType::LeftParens),
")" => self.add_token(TokenType::RightParens),
"[" => self.add_token(TokenType::LeftSquareBracket),
"]" => self.add_token(TokenType::RightSquareBracket),
"{" => self.add_token(TokenType::LeftCurlyBrackets),
"}" => self.add_token(TokenType::RightCurlyBrackets),
"," => self.add_token(TokenType::Comma),
"\\" => self.add_token(TokenType::Backslash),
"N" => self.add_token(TokenType::Integer),
"F" => self.add_token(TokenType::Float),
"S" => self.add_token(TokenType::String),
"@" => {
let start_pos = self.start; self.start += 1;
let mut literal = String::new();
while self.peek() != "@" && !self.at_end() {
literal.push_str(self.advance());
}
if self.at_end() {
return Err(ClexErrorType::UnclosedAtSymbol(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
let token_type = match literal.trim().to_uppercase().as_str() {
"CH_ALPHA" => TokenType::CharacterSetAlpha,
"CH_NUM" => TokenType::CharacterSetNumeric,
"CH_NEWLINE" => TokenType::CharacterSetNewline,
"CH_ALNUM" => TokenType::CharacterSetAlnum,
"CH_UPPER" => TokenType::CharacterSetUpper,
"CH_LOWER" => TokenType::CharacterSetLower,
"CH_ALL" => TokenType::CharacterSetAll,
_ => {
return Err(ClexErrorType::InvalidCharacterSet(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
};
self.add_token(token_type);
if !self.match_str("@") {
return Err(ClexErrorType::UnclosedAtSymbol(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
}
" " | "\r" | "\t" | "\n" => {
}
"'" => {
let start_pos = self.start; self.start += 1;
let mut literal = String::new();
while self.peek() != "'" && !self.at_end() {
let c = self.advance();
if c == "\\" {
let escaped = match self.peek() {
"n" => '\n', "t" => '\t', "r" => '\r', "\\" => '\\', "'" => '\'', "\"" => '\"', "0" => '\0', "a" => '\x07', "b" => '\x08', "f" => '\x0C', "v" => '\x0B', _ => '\\',
};
self.advance();
literal.push(escaped);
} else {
literal.push_str(c);
}
}
if self.at_end() {
return Err(ClexErrorType::UnclosedSingleQuotes(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
self.add_token(TokenType::LiteralString(literal));
if !self.match_str("'") {
return Err(ClexErrorType::UnclosedSingleQuotes(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
}
"?" => {
let start_pos = self.start;
if self.match_str(":") {
self.add_token(TokenType::QuestionColon);
} else {
return Err(ClexErrorType::MissingColonAfterQuestionMark(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
}
_ => {
if c.as_str() == "-" || Self::is_digit(c.as_str()) {
let start_pos = self.start;
if c.as_str() == "-" && !Self::is_digit(self.peek()) {
return Err(ClexErrorType::MissingNumberAfterNegativeSign(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
while Self::is_digit(self.peek()) {
self.current += 1;
}
let number = match self.source_language[self.start..self.current].parse::<i64>()
{
Ok(num) => num,
Err(_err) => {
return Err(ClexErrorType::NumericParsingError(
ParentErrorType::LexerError,
Span {
start: start_pos,
end: self.current,
},
));
}
};
self.add_token(TokenType::LiteralNumber(number));
} else {
let character: &'static str = Box::leak(c.into());
return Err(ClexErrorType::UnknownCharacter(
ParentErrorType::LexerError,
Span {
start: self.start,
end: self.current,
},
character,
));
}
}
}
Ok(())
}
fn add_token(&mut self, token_type: TokenType) {
self.tokens.push(Token {
token_type,
lexeme: self.source_language[self.start..self.current].to_string(),
span: Span {
start: self.start,
end: self.current,
},
});
}
fn advance(&mut self) -> &str {
self.current += 1;
self.char_at(self.current - 1)
}
fn char_at(&self, index: usize) -> &str {
self.source_language.graphemes(true).collect::<Vec<&str>>()[index]
}
fn match_str(&mut self, expected: &str) -> bool {
if self.at_end() || self.char_at(self.current) != expected {
false
} else {
self.current += 1;
true
}
}
fn is_digit(ch: &str) -> bool {
("0"..="9").contains(&ch)
}
fn peek(&self) -> &str {
if self.at_end() {
"\0"
} else {
self.char_at(self.current)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tokenization_works() {
let src = "12N3";
let mut tokens = Tokens::new(src.to_string());
tokens.scan_tokens().unwrap();
assert_eq!(
tokens.tokens,
vec![
Token {
token_type: TokenType::LiteralNumber(12),
lexeme: "12".to_string(),
span: Span { start: 0, end: 2 },
},
Token {
token_type: TokenType::Integer,
lexeme: "N".to_string(),
span: Span { start: 2, end: 3 },
},
Token {
token_type: TokenType::LiteralNumber(3),
lexeme: "3".to_string(),
span: Span { start: 3, end: 4 },
},
Token {
token_type: TokenType::Eof,
lexeme: String::new(),
span: Span { start: 4, end: 4 },
}
]
);
}
}