use crate::error_handler::ErrorHandler;
use crate::tokens::{Token, TokenType};
use phf::phf_map;
use std::str::FromStr;
static KEYWORDS: phf::Map<&'static str, TokenType> = phf_map! {
"and" => TokenType::And,
"else" => TokenType::Else,
"elseif" => TokenType::ElseIf,
"false" => TokenType::False,
"function" => TokenType::Function,
"fn" => TokenType::Function,
"if" => TokenType::If,
"or" => TokenType::Or,
"print" => TokenType::Print,
"input" => TokenType::Input,
"return" => TokenType::Return,
"true" => TokenType::True,
"while" => TokenType::While,
"not" => TokenType::Not,
"end" => TokenType::End,
"wend" => TokenType::Wend,
"for" => TokenType::For,
"to" => TokenType::To,
"next" => TokenType::Next,
"then" => TokenType::Then
};
pub struct Scanner<'a> {
chars: Vec<char>,
tokens: Vec<Token>,
start: usize,
current: usize,
line: usize,
error_handler: &'a mut ErrorHandler,
}
impl<'a> Scanner<'a> {
pub fn new(source: &str, error_handler: &'a mut ErrorHandler) -> Self {
let chars = source.chars().collect();
Self {
chars,
tokens: Vec::new(),
start: 0,
current: 0,
line: 1,
error_handler,
}
}
pub fn scan_tokens(&mut self) -> Result<Vec<Token>, ()> {
while !self.is_at_end() {
self.start = self.current;
if let Err((line, msg)) = self.scan_token() {
self.error_handler.error(line, &msg);
return Err(());
}
}
self.tokens
.push(Token::new(TokenType::Eof, "".to_string(), self.line));
Ok(self.tokens.clone())
}
pub fn scan_token(&mut self) -> Result<(), (usize, String)> {
let c = self.advance();
let token = match c {
'(' => TokenType::LeftParen,
')' => TokenType::RightParen,
'{' => TokenType::LeftBrace,
'}' => TokenType::RightBrace,
',' => TokenType::Comma,
'.' => TokenType::Dot,
'-' => {
if self.match_next('-') {
TokenType::MinusMinus
} else if self.match_next('=') {
TokenType::MinusEqual
} else {
TokenType::Minus
}
}
'<' => {
if self.match_next('=') {
TokenType::LessEqual
} else {
TokenType::Less
}
}
'>' => {
if self.match_next('=') {
TokenType::GreaterEqual
} else {
TokenType::Greater
}
}
'+' => {
if self.match_next('+') {
TokenType::PlusPlus
} else if self.match_next('=') {
TokenType::PlusEqual
} else {
TokenType::Plus
}
}
'*' => {
if self.match_next('=') {
TokenType::StarEqual
} else {
TokenType::Star
}
}
'/' => {
if self.match_next('/') {
while self.peek() != '\n' && !self.is_at_end() {
self.advance();
}
TokenType::NoToken
} else if self.match_next('=') {
TokenType::SlashEqual
} else {
TokenType::Slash
}
}
'^' => {
if self.match_next('=') {
TokenType::CaretEqual
} else {
TokenType::Caret
}
}
'%' => {
if self.match_next('=') {
TokenType::PercentEqual
} else {
TokenType::Percent
}
}
'=' => TokenType::Equal,
' ' | '\r' | '\t' => TokenType::NoToken,
'\n' => {
self.line += 1;
let last_newline = match self.tokens.last() {
Some(x) => x.token_type == TokenType::Newline,
None => false,
};
if last_newline {
TokenType::NoToken
} else {
TokenType::Newline
}
}
'"' => {
if let Err(x) = self.string() {
return Err(x);
}
TokenType::NoToken
}
'0'..='9' => {
self.number();
TokenType::NoToken
}
_ => {
if is_alpha(c) {
self.identifier();
TokenType::NoToken
} else {
return Err((self.line, "Unexpected character.".to_string()));
}
}
};
self.add_token(token);
Ok(())
}
fn is_at_end(&self) -> bool {
self.current >= self.chars.len()
}
fn add_token(&mut self, token_type: TokenType) {
if token_type == TokenType::NoToken {
return;
}
let text: String = self.chars[self.start..self.current].iter().collect();
self.tokens.push(Token::new(token_type, text, self.line));
}
fn advance(&mut self) -> char {
self.current += 1;
self.chars[self.current - 1]
}
fn previous(&self) -> char {
if self.current == 0 {
return '\0';
}
self.chars[self.current - 1]
}
fn peek(&self) -> char {
if self.is_at_end() {
return '\0';
}
self.chars[self.current]
}
fn peek_next(&self) -> char {
if self.current + 1 >= self.chars.len() {
return '\0';
}
self.chars[self.current + 1]
}
fn match_next(&mut self, expected: char) -> bool {
if self.is_at_end() {
return false;
}
if self.chars[self.current] != expected {
return false;
}
self.current += 1;
true
}
fn string(&mut self) -> Result<(), (usize, String)> {
while (self.peek() != '"' || self.previous() == '\\') && !self.is_at_end() {
if self.peek() == '\n' {
self.line += 1;
}
self.advance();
}
if self.is_at_end() {
return Err((self.line, "Unterminated string.".to_string()));
}
self.advance();
let mut value: String = String::new();
let mut last = '\0';
for &char in self.chars[(self.start + 1)..(self.current - 1)].iter() {
let mut null_last = false;
if char == 'n' && last == '\\' {
value.push('\n');
null_last = true;
} else if char == '"' && last == '\\' {
value.push('"');
null_last = true;
} else if char == '\\' && last == '\\' {
value.push('\\');
null_last = true;
} else {
if last == '\\' {
value.push('\\');
}
if char != '\\' {
value.push(char);
}
}
if null_last {
last = '\0';
} else {
last = char;
}
}
self.add_token(TokenType::String(value));
Ok(())
}
fn number(&mut self) {
while is_digit(self.peek()) {
self.advance();
}
if self.peek() == '.' && is_digit(self.peek_next()) {
self.advance();
while is_digit(self.peek()) {
self.advance();
} }
let next_char = self.peek();
let s: String = self.chars[self.start..self.current].iter().collect();
if next_char == 'i' || next_char == 'f' {
self.advance();
}
let t = match next_char {
'i' => TokenType::Integer(i64::from_str(&s).unwrap_or_else(|_| {
self.error_handler
.error(self.line, &format!("Cannot parse {} as integer", s));
0
})),
_ => TokenType::Decimal(f64::from_str(&s).unwrap_or_else(|_| {
self.error_handler
.error(self.line, &format!("Cannot parse {} as decimal", s));
0.0
})),
};
self.add_token(t)
}
fn identifier(&mut self) {
while is_alphanumeric(self.peek()) {
self.advance();
}
let text: String = self.chars[self.start..self.current].iter().collect();
let token: TokenType = match KEYWORDS.get(text.as_str()) {
Some(x) => x.clone(),
None => TokenType::Identifier,
};
self.add_token(token);
}
}
fn is_digit(c: char) -> bool {
('0'..='9').contains(&c)
}
fn is_alpha(c: char) -> bool {
('a'..='z').contains(&c) || ('A'..='Z').contains(&c) || c == '_'
}
fn is_alphanumeric(c: char) -> bool {
is_digit(c) || is_alpha(c)
}