use std::{error::Error, fmt, iter::Peekable, num::ParseFloatError, str::Chars};
use crate::interpreter::Token;
pub struct Lexer<'c> {
expr_iter: Peekable<Chars<'c>>,
position: u32,
}
#[derive(Debug, PartialEq)]
pub enum LexerError {
UnexpectedCharacter(
String,
u32,
),
UnterminatedStringLiteral(
String,
u32,
),
ParseFloatError(
ParseFloatError,
u32,
),
}
impl<'c> Lexer<'c> {
pub fn new(expr_str: &'c str) -> Self {
Self {
expr_iter: expr_str.chars().peekable(),
position: 0,
}
}
pub fn scan(&mut self) -> Result<Vec<Token>, LexerError> {
let mut tokens = Vec::new();
while let Some(token) = self.scan_single()? {
tokens.push(token);
}
Ok(tokens)
}
fn scan_single(&mut self) -> Result<Option<Token>, LexerError> {
let token = match self.consume_character() {
Some('(') => Some(Token::LeftParen),
Some(')') => Some(Token::RightParen),
Some('{') => Some(Token::LeftBrace),
Some('}') => Some(Token::RightBrace),
Some('[') => Some(Token::LeftBracket),
Some(']') => Some(Token::RightBracket),
Some(',') => Some(Token::Comma),
Some('.') => Some(Token::Dot),
Some('-') => Some(Token::Minus),
Some('+') => Some(Token::Plus),
Some('/') => Some(Token::Slash),
Some('*') => Some(Token::Star),
Some('!') => {
if self.match_next('=') {
Some(Token::BangEqual)
} else {
Some(Token::Bang)
}
}
Some('=') => {
if self.match_next('=') {
Some(Token::EqualEqual)
} else {
Some(Token::Equal)
}
}
Some('>') => {
if self.match_next('=') {
Some(Token::GreaterThanOrEqualTo)
} else {
Some(Token::GreaterThan)
}
}
Some('<') => {
if self.match_next('=') {
Some(Token::LessThanOrEqualTo)
} else {
Some(Token::LessThan)
}
}
Some('\'') => Some(self.capture_string_literal()?),
Some(x) if x.is_digit(10) => Some(self.capture_numerical_literal(x)?),
Some(x) if x.is_alphabetic() || x == '_' => Some(self.capture_identifier(x)),
Some(x) if x.is_ascii_whitespace() => {
self.scan_single()?
}
Some(unexpected_char) => {
return Err(LexerError::UnexpectedCharacter(
unexpected_char.to_string(),
self.position,
))
}
None => None,
};
Ok(token)
}
fn match_next(&mut self, expected: char) -> bool {
if self
.expr_iter
.peek()
.map_or_else(|| false, |v| v == &expected)
{
self.consume_character();
true
} else {
false
}
}
fn capture_string_literal(&mut self) -> Result<Token, LexerError> {
let mut string_chars = Vec::new();
let start = self.position;
while let Some(next_char) = Some(self.consume_character().ok_or_else(|| {
LexerError::UnterminatedStringLiteral(string_chars.clone().into_iter().collect(), start)
})?)
.filter(|v| v != &'\'')
{
string_chars.push(next_char);
}
Ok(Token::String(string_chars.into_iter().collect()))
}
fn capture_numerical_literal(&mut self, first_char: char) -> Result<Token, LexerError> {
let start = self.position;
let mut numerical_chars = vec![first_char];
let mut decimal_encountered = false;
while let Some(next_char) = self
.expr_iter
.peek()
.filter(|v| v.is_digit(10) || v == &&'.')
{
if next_char == &'.' {
if decimal_encountered {
return Err(LexerError::UnexpectedCharacter(
next_char.to_string(),
self.position + 1,
));
} else {
decimal_encountered = true;
}
}
numerical_chars.push(*next_char);
self.consume_character();
}
let numerical_string: String = numerical_chars.into_iter().collect();
numerical_string
.parse::<f64>()
.map(Token::Number)
.map_err(|err| LexerError::ParseFloatError(err, start))
}
fn capture_identifier(&mut self, first_char: char) -> Token {
let mut identifier_chars = vec![first_char];
while let Some(next_char) = self
.expr_iter
.peek()
.filter(|v| v.is_alphanumeric() || v == &&'_')
{
identifier_chars.push(*next_char);
self.consume_character();
}
Token::Identifier(identifier_chars.into_iter().collect())
}
fn consume_character(&mut self) -> Option<char> {
self.position += 1;
self.expr_iter.next()
}
}
impl Error for LexerError {}
impl fmt::Display for LexerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::UnexpectedCharacter(lexeme, position) => {
write!(
f,
"Unexpected character '{}' at position {} in expression",
lexeme, position
)
}
Self::UnterminatedStringLiteral(string, start_pos) => {
write!(
f,
"Unterminated string literal '{}' starting at position {}",
string, start_pos
)
}
Self::ParseFloatError(parse_err, start) => {
write!(
f,
"ParseFloatError '{:?}' encountered in number beginning at position {}",
parse_err, start
)
}
}
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use crate::interpreter::lexer::{Lexer, LexerError};
type TestResult = Result<(), Box<dyn Error>>;
#[test]
fn output_test() -> TestResult {
let expr_str = "_event.data[0] == 'OPER.PLUS'";
let mut lexer = Lexer::new(expr_str);
eprintln!("Tokens from '{}':\n{:?}", expr_str, lexer.scan());
Ok(())
}
#[test]
fn unexpected_char() -> TestResult {
let mut lexer_a = Lexer::new("`");
let mut lexer_b = Lexer::new("5.5.5");
assert_eq!(
lexer_a.scan(),
Err(LexerError::UnexpectedCharacter(String::from('`'), 1)),
);
assert_eq!(
lexer_b.scan(),
Err(LexerError::UnexpectedCharacter(String::from('.'), 4)),
);
Ok(())
}
#[test]
fn unterm_string_literal() -> TestResult {
let mut lexer = Lexer::new("'unterm_string_literal");
assert_eq!(
lexer.scan(),
Err(LexerError::UnterminatedStringLiteral(
String::from("unterm_string_literal"),
1
)),
);
Ok(())
}
}