use crate::{error::Error, source::Source, token::Token};
pub trait LexerRule<'a, T> {
fn get_token(&self, lexer: &mut Lexer<'a, T>) -> Result<Option<Token<T>>, Error>;
fn generates_token(&self) -> bool {
true
}
}
pub struct Lexer<'a, T> {
pub source: &'a Source<'a>,
pub position: usize,
pub current_char: Option<char>,
rules: Vec<Box<dyn LexerRule<'a, T>>>,
}
impl<'a, T> Lexer<'a, T> {
pub fn new(source: &'a Source<'a>, rules: Vec<Box<dyn LexerRule<'a, T>>>) -> Self {
let mut lexer = Lexer {
source,
position: 0,
current_char: None,
rules,
};
if lexer.position < lexer.source.code.len() {
lexer.current_char = Some(lexer.source.code[lexer.position..].chars().next().unwrap());
} else {
lexer.current_char = None;
}
lexer
}
pub fn advance(&mut self) {
if self.position < self.source.code.len() - 1 {
self.position += 1;
self.current_char = Some(self.source.code[self.position..].chars().next().unwrap());
} else {
self.current_char = None;
}
}
pub fn jump_to(&mut self, position: usize) {
if position < self.source.code.len() {
self.position = position;
self.current_char = Some(self.source.code[self.position..].chars().next().unwrap());
} else {
self.position = self.source.code.len() + 1;
self.current_char = None;
}
}
pub fn get_token(&mut self) -> Result<Option<Token<T>>, Error> {
let self_ptr = self as *mut Self;
for rule in &self.rules {
let prev_position = self.position;
let token = unsafe { rule.get_token(&mut *self_ptr) }?;
if let Some(token) = token {
return Ok(Some(token));
} else if rule.generates_token() {
unsafe {
(*self_ptr).jump_to(prev_position);
}
}
}
Ok(None)
}
}
pub mod utils {
use crate::lexer::LexerRule;
mod macros {
#[macro_export]
macro_rules! rules_vec {
($($rule:expr),* $(,)?) => {
vec![$(Box::new($rule) as Box<dyn $crate::lexer::LexerRule<'_, _>>),*]
};
}
#[macro_export]
macro_rules! match_string {
($string:expr, $token_type:ty, $token_value:expr, $rule_name:ident) => {
struct $rule_name;
impl<'a> $crate::lexer::LexerRule<'a, $token_type> for $rule_name {
fn get_token(
&self,
lexer: &mut $crate::lexer::Lexer<'a, $token_type>,
) -> Result<Option<$crate::token::Token<$token_type>>, $crate::error::Error>
{
let start_pos = lexer.position;
let mut matched = true;
for c in $string.chars() {
if lexer.current_char == Some(c) {
lexer.advance();
} else {
matched = false;
break;
}
}
if matched {
Ok(Some($crate::token::Token::new(
$token_value,
$crate::span::Span::new(start_pos, lexer.position),
)))
} else {
Ok(None)
}
}
}
};
}
#[macro_export]
macro_rules! match_word {
($word:expr, $token_type:ty, $token_value:expr, $rule_name:ident) => {
struct $rule_name;
impl<'a> $crate::lexer::LexerRule<'a, $token_type> for $rule_name {
fn get_token(
&self,
lexer: &mut $crate::lexer::Lexer<'a, $token_type>,
) -> Result<Option<$crate::token::Token<$token_type>>, $crate::error::Error>
{
let start_pos = lexer.position;
let mut matched = true;
for c in $word.chars() {
if lexer.current_char == Some(c) {
lexer.advance();
} else {
matched = false;
break;
}
}
if matched
&& (lexer.current_char == Some(' ') || lexer.current_char.is_none())
{
Ok(Some($crate::token::Token::new(
$token_value,
$crate::span::Span::new(start_pos, lexer.position),
)))
} else {
Ok(None)
}
}
}
};
}
pub use match_string;
pub use match_word;
pub use rules_vec;
}
pub struct SkipWhitespaceRule;
impl<'a, T> LexerRule<'a, T> for SkipWhitespaceRule {
fn get_token(
&self,
lexer: &mut super::Lexer<'a, T>,
) -> Result<Option<crate::token::Token<T>>, crate::error::Error> {
while let Some(c) = lexer.current_char {
if c.is_whitespace() {
lexer.advance();
} else {
break;
}
}
Ok(None)
}
fn generates_token(&self) -> bool {
false
}
}
pub use macros::{match_string, match_word, rules_vec};
#[cfg(test)]
mod tests {
use super::*;
use crate::{lexer::Lexer, source::Source};
#[test]
fn test_skip_whitespace_rule() {
let source = Source::from_str("test_input.txt", " let x = 10;");
let rules = rules_vec![SkipWhitespaceRule];
let mut lexer = Lexer::<String>::new(&source, rules);
let token = lexer.get_token().unwrap();
assert!(token.is_none());
assert_eq!(lexer.position, 5);
assert_eq!(lexer.current_char, Some('l'));
}
#[test]
fn test_rules_vec_macro() {
let rules: Vec<Box<dyn LexerRule<'_, String> + 'static>> =
rules_vec![SkipWhitespaceRule];
assert_eq!(rules.len(), 1);
assert!(rules[0].generates_token() == false);
}
#[test]
fn test_match_string_macro() {
match_string!("let", String, "let".to_string(), LetRule);
let source = Source::from_str("test_input.txt", "let x = 10;");
let rules = rules_vec![LetRule];
let mut lexer = Lexer::<String>::new(&source, rules);
let token = lexer.get_token().unwrap();
assert!(token.is_some());
let token = token.unwrap();
assert_eq!(token.kind, "let");
let token = lexer.get_token().unwrap();
assert!(token.is_none());
assert_eq!(lexer.position, 3);
assert_eq!(lexer.current_char, Some(' '));
}
#[test]
fn test_match_word_macro() {
match_word!("let", String, "let".to_string(), LetRule);
let source = Source::from_str("test_input.txt", "let x = 10;");
let rules = rules_vec![LetRule];
let mut lexer = Lexer::<String>::new(&source, rules);
let token = lexer.get_token().unwrap();
assert!(token.is_some());
let token = token.unwrap();
assert_eq!(token.kind, "let");
let source = Source::from_str("test_input.txt", "letx = 10;");
let rules = rules_vec![LetRule];
let mut lexer = Lexer::<String>::new(&source, rules);
let token = lexer.get_token().unwrap();
assert!(token.is_none());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{error::Error, source::Source, span::Span, token::Token};
#[test]
fn test_lexer_new() {
let source = Source::from_str("test_input.txt", "let x = 10;");
let rules = utils::rules_vec![utils::SkipWhitespaceRule];
let lexer = Lexer::<u8>::new(&source, rules);
assert_eq!(lexer.position, 0);
assert_eq!(lexer.current_char, Some('l'));
}
#[test]
fn test_lexer_advance() {
let source = Source::from_str("test_input.txt", "let x = 10;");
let rules = utils::rules_vec![utils::SkipWhitespaceRule];
let mut lexer = Lexer::<u8>::new(&source, rules);
lexer.advance();
assert_eq!(lexer.position, 1);
assert_eq!(lexer.current_char, Some('e'));
}
#[test]
fn test_lexer_jump_to() {
let source = Source::from_str("test_input.txt", "let x = 10;");
let rules = utils::rules_vec![utils::SkipWhitespaceRule];
let mut lexer = Lexer::<u8>::new(&source, rules);
lexer.jump_to(4);
assert_eq!(lexer.position, 4);
assert_eq!(lexer.current_char, Some('x'));
}
#[test]
fn test_lexer_get_token() {
let source = Source::from_str("test_input.txt", "let x = 10;");
struct TestRule;
impl<'a> LexerRule<'a, String> for TestRule {
fn get_token(
&self,
lexer: &mut Lexer<'a, String>,
) -> Result<Option<Token<String>>, Error> {
if lexer.current_char == Some('l') {
lexer.advance();
Ok(Some(Token::new("let".to_string(), Span::new(0, 3))))
} else {
Ok(None)
}
}
}
let rules = utils::rules_vec![utils::SkipWhitespaceRule, TestRule];
let mut lexer = Lexer::<String>::new(&source, rules);
let token = lexer.get_token().unwrap();
assert!(token.is_some());
let token = token.unwrap();
assert_eq!(token.kind, "let");
assert_eq!(token.span.start, 0);
assert_eq!(token.span.end, 3);
}
}