use crate::{
error::{ErrorKind, KabelError},
token,
};
pub struct Lexer {
input: Vec<char>,
start: usize,
current: usize,
line: usize,
line_start: usize,
column: usize,
c: char,
pub errors: Vec<KabelError>,
pub output: Vec<Token>,
}
impl Lexer {
pub fn new(input: String) -> Self {
Self {
input: input.chars().collect(),
start: 0,
current: 0,
line: 0,
line_start: 0,
column: 0,
c: '\0',
errors: Vec::new(),
output: Vec::new(),
}
}
pub fn next_token(&mut self) -> bool {
self.read_char();
match self.c {
'+' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::PlusEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Plus));
self.start = self.current;
}
}
'-' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::MinusEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Minus));
self.start = self.current;
}
}
'*' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::StarEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Star));
self.start = self.current;
}
}
'/' => {
if self.peek() == '/' {
while self.peek() != '\n' && self.current < self.input.len() {
self.read_char();
}
self.start = self.current;
} else if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::SlashEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Slash));
self.start = self.current;
}
}
'%' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::PercentEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Percent));
self.start = self.current;
}
}
'(' => {
self.output.push(token!(self, TokenType::LeftParen));
self.start = self.current;
}
')' => {
self.output.push(token!(self, TokenType::RightParen));
self.start = self.current;
}
'{' => {
self.output.push(token!(self, TokenType::LeftBrace));
self.start = self.current;
}
'}' => {
self.output.push(token!(self, TokenType::RightBrace));
self.start = self.current;
}
'[' => {
self.output.push(token!(self, TokenType::LeftSquare));
self.start = self.current;
}
']' => {
self.output.push(token!(self, TokenType::RightSquare));
self.start = self.current;
}
'.' => {
self.output.push(token!(self, TokenType::Period));
self.start = self.current;
}
',' => {
self.output.push(token!(self, TokenType::Comma));
self.start = self.current;
}
';' => {
self.output.push(token!(self, TokenType::Semicolon));
self.start = self.current;
}
'^' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::CaretEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Caret));
self.start = self.current;
}
}
'|' => {
if self.peek() == '|' {
self.read_char();
self.output.push(token!(self, TokenType::OrOr));
self.start = self.current;
} else if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::OrEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Or));
self.start = self.current;
}
}
'&' => {
if self.peek() == '&' {
self.read_char();
self.output.push(token!(self, TokenType::AndAnd));
self.start = self.current;
} else if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::AndEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::And));
self.start = self.current;
}
}
'=' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::EqualEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Equal));
self.start = self.current;
}
}
'!' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::BangEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Bang));
self.start = self.current;
}
}
'>' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::GreaterEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Greater));
self.start = self.current;
}
}
'<' => {
if self.peek() == '=' {
self.read_char();
self.output.push(token!(self, TokenType::LessEqual));
self.start = self.current;
} else {
self.output.push(token!(self, TokenType::Less));
self.start = self.current;
}
}
'"' => {
let mut contents = String::new();
while self.read_char() != '"' {
if self.c == '\0' {
self.errors.push(KabelError::new(
ErrorKind::UnexpectedEof,
"File ended before closing quote".to_string(),
self.line,
self.column,
self.input[self.start..self.current].iter().collect(),
));
return false;
}
contents.push(self.c as char);
}
self.output.push(token!(self, TokenType::Str(contents)));
self.start = self.current;
}
'\n' => {
self.line += 1;
self.line_start = self.current;
self.column = 0;
self.start = self.current;
}
' ' | '\r' | '\t' => {
self.start = self.current;
}
'\0' => return false,
c => {
if c.is_ascii_alphabetic() || c == '_' {
let mut content = (c as char).to_string();
while self.peek().is_ascii_alphanumeric() || self.c == '_' {
content.push(self.c as char);
self.read_char();
}
self.output.push(token!(self, TokenType::Ident(content)));
self.start = self.current;
} else if c.is_ascii_digit() {
let mut number = (c as char).to_string();
while self.peek().is_ascii_digit() {
number.push(self.c as char);
self.read_char();
}
if self.c == '.' {
number.push('.');
while self.read_char().is_ascii_digit() {
number.push(self.c as char);
}
}
// panic = error in this code
self.output
.push(token!(self, TokenType::Num(number.parse().unwrap())));
self.start = self.current;
} else {
self.errors.push(KabelError::new(
ErrorKind::UnexpectedToken,
format!("Stray \"{0}\"", c as char),
self.line,
self.column,
self.input[self.line_start..self.current].iter().collect(),
));
}
}
}
true
}
pub fn read_char(&mut self) -> char {
if self.current >= self.input.len() {
self.c = '\0'; // EOF
return self.c;
}
self.c = self.input[self.current];
self.current += 1;
self.column += 1;
return self.c;
}
pub fn peek(&mut self) -> char {
if self.current >= self.input.len() {
self.c = '\0'; // EOF
return self.c;
}
self.c = self.input[self.current];
return self.c;
}
}
#[derive(Debug, Clone)]
pub struct Token {
pub token_type: TokenType,
pub column: usize,
pub line: usize,
pub line_start: usize,
pub start: usize,
pub end: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TokenType {
Star,
StarEqual,
Slash,
SlashEqual,
Percent,
PercentEqual,
Plus,
PlusEqual,
Minus,
MinusEqual,
LeftParen,
RightParen,
LeftBrace,
RightBrace,
LeftSquare,
RightSquare,
Period,
Comma,
Semicolon,
Equal,
EqualEqual,
Bang,
BangEqual,
Greater,
GreaterEqual,
Less,
LessEqual,
And,
AndEqual,
AndAnd,
Or,
OrEqual,
OrOr,
Caret,
CaretEqual,
Ident(String),
Str(String),
Num(f32),
}