use std::{iter::Peekable, str::Chars};
use crate::error::{Error, Result};
use crate::token::Token;
#[derive(Debug)]
pub struct Lexer<'a> {
pub iter: Peekable<Chars<'a>>,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
Self {
iter: input.chars().peekable(),
}
}
fn next_if<F: Fn(char) -> bool>(&mut self, predicate: F) -> Option<char> {
self.iter.peek().filter(|&c| predicate(*c))?;
self.iter.next()
}
fn next_if_token<F: Fn(char) -> Option<Token>>(&mut self, tokenizer: F) -> Option<Token> {
let token = self.iter.peek().and_then(|&c| tokenizer(c))?;
self.iter.next();
Some(token)
}
fn next_while<F: Fn(char) -> bool>(&mut self, predicate: F) -> Option<String> {
let mut value = String::new();
while let Some(c) = self.next_if(&predicate) {
value.push(c);
}
Some(value).filter(|v| !v.is_empty())
}
fn consume_whitespace(&mut self) {
self.next_while(|c| c.is_whitespace());
}
}
impl<'a> Lexer<'a> {
fn scan(&mut self) -> Option<Token> {
self.consume_whitespace();
None.or_else(|| self.scan_ident())
.or_else(|| self.scan_number())
.or_else(|| self.scan_operator())
.or_else(|| self.scan_punctuation())
}
fn scan_ident(&mut self) -> Option<Token> {
let mut name = self.next_if(|c| c.is_alphabetic())?.to_string();
while let Some(c) = self.next_if(|c| c.is_alphanumeric() || c == '_') {
name.push(c);
}
Some(Token::Ident(name))
}
fn scan_number(&mut self) -> Option<Token> {
let mut name = self.next_while(|c| c.is_digit(10))?;
if let Some(point) = self.next_if(|c| c == '.') {
name.push(point);
while let Some(num) = self.next_if(|c| c.is_digit(10)) {
name.push(num);
}
}
if let Some(e) = self.next_if(|c| c == 'e' || c == 'E') {
name.push(e);
if let Some(sign) = self.next_if(|c| c == '+' || c == '-') {
name.push(sign);
}
while let Some(num) = self.next_if(|c| c.is_digit(10)) {
name.push(num);
}
}
Some(Token::Number(name))
}
fn scan_operator(&mut self) -> Option<Token> {
self.next_if_token(|c| match c {
'+' => Some(Token::Plus),
'-' => Some(Token::Minus),
'*' => Some(Token::Asterisk),
'/' => Some(Token::Slash),
'^' => Some(Token::Caret),
'√' => Some(Token::SquareRoot),
'%' => Some(Token::Percent),
'!' => Some(Token::Exclamation),
_ => None,
})
}
fn scan_punctuation(&mut self) -> Option<Token> {
self.next_if_token(|c| match c {
'(' => Some(Token::OpenParen),
')' => Some(Token::CloseParen),
',' => Some(Token::Comma),
_ => None,
})
}
}
impl<'a> Iterator for Lexer<'a> {
type Item = Result<Token>;
fn next(&mut self) -> Option<Self::Item> {
self.scan().map(Ok).or_else(|| {
self.iter
.peek()
.map(|c| Err(Error::Parse(format!("Unexpected character {}", c))))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_peekable() {
let input = "1+2";
let mut lexer = Lexer::new(input);
println!("lexer: {:?}", lexer);
while let Some(next) = lexer.next() {
println!("next: {:?}", next);
}
println!("--------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
let vec = vec!["2 + 7 % 3", "sqrt(16)", "3.14 * 2.1", "degrees(4*pi)", "5%3"];
for v in vec {
let mut lexer = Lexer::new(v).peekable();
println!("lexer: {:?}", lexer);
let peek = lexer.peek();
println!("peek: {:?}", peek);
println!("--------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
}
}
}