use std::str::Chars;
use super::token::Token;
use super::token::Category;
pub struct StateFunction(pub fn(&mut Tokenizer) -> Option<StateFunction>);
pub struct Tokenizer<'a> {
data: Chars<'a>,
current_token: String,
tokens: Vec<Token>,
pub states: Vec<StateFunction>,
}
impl<'a> Tokenizer<'a> {
pub fn new(data: &str) -> Tokenizer {
Tokenizer{
data: data.chars(),
current_token: String::new(),
tokens: vec![],
states: vec![]
}
}
pub fn tokens(&self) -> Vec<Token> {
let mut tokens = self.tokens.clone();
let data_iter = self.data.clone();
let mut remaining_data = self.current_token.clone();
for c in data_iter {
remaining_data.push(c);
}
if !remaining_data.is_empty() {
tokens.push(Token{ lexeme: remaining_data, category: Category::Text});
}
tokens
}
pub fn advance(&mut self) {
match self.data.next() {
Some(c) => self.current_token.push(c),
None => ()
}
}
pub fn current_char(&self) -> Option<char> {
match self.data.clone().peekable().peek() {
Some(c) => Some(c.clone()),
None => None
}
}
pub fn next_non_whitespace_char(&self) -> Option<char> {
let mut data_iter = self.data.clone();
data_iter.find(|&c| c != ' ' && c != '\n')
}
pub fn has_prefix(&self, prefix: &str) -> bool {
let mut data_iter = self.data.clone();
prefix.chars().all(|c| {
match data_iter.next() {
Some(d) => c == d,
None => false
}
})
}
pub fn starts_with_lexeme(&self, lexeme: &str) -> bool {
let data_iter = self.data.clone();
self.has_prefix(lexeme) && match data_iter.skip(lexeme.len()).next() {
Some(' ') | Some('\n') | Some(',') => true,
None => true,
_ => false
}
}
pub fn tokenize(&mut self, category: Category) {
if !self.current_token.is_empty() {
let token = Token{
lexeme: self.current_token.clone(),
category: category,
};
self.tokens.push(token);
self.current_token = String::new();
}
}
pub fn tokenize_next(&mut self, amount: usize, category: Category) {
self.tokenize(Category::Text);
for _ in 0..amount { self.advance(); }
self.tokenize(category);
}
pub fn consume_whitespace(&mut self) {
let mut found_whitespace = false;
loop {
match self.current_char() {
Some(' ') | Some('\n') => {
if !found_whitespace {
self.tokenize(Category::Text);
found_whitespace = true;
}
self.advance();
},
_ => {
if found_whitespace {
self.tokenize(Category::Whitespace);
}
return
}
}
}
}
}
#[cfg(test)]
mod tests {
use tokenizer::Tokenizer;
use token::{Category, Token};
#[test]
fn current_char_returns_the_char_at_head() {
let data = "él";
let tokenizer = Tokenizer::new(data);
assert_eq!(tokenizer.current_char().unwrap(), 'é');
}
#[test]
fn current_char_returns_none_if_at_the_end() {
let data = "él";
let mut tokenizer = Tokenizer::new(data);
tokenizer.advance();
tokenizer.advance();
assert_eq!(tokenizer.current_char(), None);
}
#[test]
fn tokenize_creates_the_correct_token() {
let data = "élégant";
let mut tokenizer = Tokenizer::new(data);
tokenizer.advance();
tokenizer.advance();
tokenizer.tokenize(Category::Text);
let token = tokenizer.tokens.pop().unwrap();
let expected_token = Token{ lexeme: "él".to_string(), category: Category::Text};
assert_eq!(token, expected_token);
}
#[test]
fn tokenize_does_nothing_if_range_is_empty() {
let data = "élégant";
let mut tokenizer = Tokenizer::new(data);
tokenizer.tokenize(Category::Text);
assert_eq!(tokenizer.tokens.len(), 0);
}
#[test]
fn tokenize_next_tokenizes_previous_data_as_text() {
let data = "élégant";
let mut tokenizer = Tokenizer::new(data);
tokenizer.advance();
tokenizer.advance();
tokenizer.tokenize_next(1, Category::Keyword);
let token = tokenizer.tokens.remove(0);
let expected_token = Token{ lexeme: "él".to_string(), category: Category::Text};
assert_eq!(token, expected_token);
}
#[test]
fn tokenize_next_tokenizes_next_x_chars() {
let data = "élégant";
let mut tokenizer = Tokenizer::new(data);
tokenizer.advance();
tokenizer.advance();
tokenizer.tokenize_next(5, Category::Keyword);
let token = tokenizer.tokens.pop().unwrap();
let expected_token = Token{ lexeme: "égant".to_string(), category: Category::Keyword};
assert_eq!(token, expected_token);
}
#[test]
fn tokenize_next_takes_at_most_what_is_left() {
let data = "élégant";
let mut tokenizer = Tokenizer::new(data);
tokenizer.advance();
tokenizer.advance();
tokenizer.tokenize_next(15, Category::Keyword);
let token = tokenizer.tokens.pop().unwrap();
let expected_token = Token{ lexeme: "égant".to_string(), category: Category::Keyword};
assert_eq!(token, expected_token);
}
#[test]
fn consume_whitespace_handles_preexisting_noncategorized_chars() {
let data = "e ";
let mut tokenizer = Tokenizer::new(data);
tokenizer.advance();
tokenizer.consume_whitespace();
assert_eq!(
tokenizer.tokens()[0],
Token{ lexeme: "e".to_string(), category: Category::Text }
);
assert_eq!(
tokenizer.tokens()[1],
Token{ lexeme: " ".to_string(), category: Category::Whitespace }
);
}
#[test]
fn tokens_returns_unprocessed_data_as_text_token() {
let tokenizer = Tokenizer::new("luthor");
assert_eq!(
tokenizer.tokens()[0],
Token{ lexeme: "luthor".to_string(), category: Category::Text }
);
}
#[test]
fn tokens_joins_advanced_data_with_unprocessed_data_as_text_token() {
let mut tokenizer = Tokenizer::new("luthor");
tokenizer.advance();
assert_eq!(
tokenizer.tokens()[0],
Token{ lexeme: "luthor".to_string(), category: Category::Text }
);
}
}