use crate::errors::{Error, ErrorKind}; #[derive(Debug, PartialEq)] pub enum TokenKind { // Single-character tokens. LeftParen, RightParen, LeftBrace, RightBrace, Comma, Dot, Minus, Plus, Semicolon, Slash, Star, // One or two character tokens. Bang, BangEqual, Equal, EqualEqual, Greater, GreaterEqual, Less, LessEqual, // Literals. Identifier(String), String(String), Number(f64), // Keywords. And, Class, Else, False, Fun, For, If, Nil, Or, Print, Return, Super, This, True, Var, While, // Special things Eof, } #[derive(Debug)] pub struct Token<'a> { pub kind: TokenKind, pub lexeme: &'a [char], pub line: usize, } struct Scanner<'a> { source: &'a [char], tokens: Vec<Token<'a>>, errors: Vec<Error>, start: usize, // offset of first character in current lexeme current: usize, // current offset into source line: usize, // current line in source } impl<'a> Scanner<'a> { fn is_at_end(&self) -> bool { return self.current >= self.source.len(); } fn advance(&mut self) -> char { self.current += 1; self.source[self.current - 1] } fn add_token(&mut self, kind: TokenKind) { let lexeme = &self.source[self.start..self.current]; self.tokens.push(Token { kind, lexeme, line: self.line, }) } fn scan_token(&mut self) { match self.advance() { // simple single-character tokens '(' => self.add_token(TokenKind::LeftParen), ')' => self.add_token(TokenKind::RightParen), '{' => self.add_token(TokenKind::LeftBrace), '}' => self.add_token(TokenKind::RightBrace), ',' => self.add_token(TokenKind::Comma), '.' => self.add_token(TokenKind::Dot), '-' => self.add_token(TokenKind::Minus), '+' => self.add_token(TokenKind::Plus), ';' => self.add_token(TokenKind::Semicolon), '*' => self.add_token(TokenKind::Star), // possible multi-character tokens '!' => self.add_if_next('=', TokenKind::BangEqual, TokenKind::Bang), '=' => self.add_if_next('=', TokenKind::EqualEqual, TokenKind::Equal), '<' => self.add_if_next('=', TokenKind::LessEqual, TokenKind::Less), '>' => self.add_if_next('=', TokenKind::GreaterEqual, TokenKind::Greater), '/' => { // support comments until EOL by discarding characters if self.match_next('/') { while self.peek() != '\n' && !self.is_at_end() { self.advance(); } } else { self.add_token(TokenKind::Slash); } } // ignore whitespace ws if ws.is_whitespace() => { if ws == '\n' { self.line += 1 } } '"' => self.scan_string(), digit if digit.is_digit(10) => self.scan_number(), chr if chr.is_alphabetic() || chr == '_' => self.scan_identifier(), unexpected => self.errors.push(Error { line: self.line, kind: ErrorKind::UnexpectedChar(unexpected), }), }; } fn match_next(&mut self, expected: char) -> bool { if self.is_at_end() || self.source[self.current] != expected { false } else { self.current += 1; true } } fn add_if_next(&mut self, expected: char, then: TokenKind, or: TokenKind) { if self.match_next(expected) { self.add_token(then); } else { self.add_token(or); } } fn peek(&self) -> char { if self.is_at_end() { return '\0'; } else { return self.source[self.current]; } } fn peek_next(&self) -> char { if self.current + 1 >= self.source.len() { return '\0'; } else { return self.source[self.current + 1]; } } fn scan_string(&mut self) { while self.peek() != '"' && !self.is_at_end() { if self.peek() == '\n' { self.line += 1; } self.advance(); } if self.is_at_end() { self.errors.push(Error { line: self.line, kind: ErrorKind::UnterminatedString, }); return; } // closing '"' self.advance(); // add token without surrounding quotes let string: String = self.source[(self.start + 1)..(self.current - 1)] .iter() .collect(); self.add_token(TokenKind::String(string)); } fn scan_number(&mut self) { while self.peek().is_digit(10) { self.advance(); } // Look for a fractional part if self.peek() == '.' && self.peek_next().is_digit(10) { // consume '.' self.advance(); while self.peek().is_digit(10) { self.advance(); } } let num: f64 = self.source[self.start..self.current] .iter() .collect::<String>() .parse() .expect("float parsing should always work"); self.add_token(TokenKind::Number(num)); } fn scan_identifier(&mut self) { while self.peek().is_alphanumeric() || self.peek() == '_' { self.advance(); } let ident: String = self.source[self.start..self.current].iter().collect(); // Determine whether this is an identifier, or a keyword: let token_kind = match ident.as_str() { "and" => TokenKind::And, "class" => TokenKind::Class, "else" => TokenKind::Else, "false" => TokenKind::False, "for" => TokenKind::For, "fun" => TokenKind::Fun, "if" => TokenKind::If, "nil" => TokenKind::Nil, "or" => TokenKind::Or, "print" => TokenKind::Print, "return" => TokenKind::Return, "super" => TokenKind::Super, "this" => TokenKind::This, "true" => TokenKind::True, "var" => TokenKind::Var, "while" => TokenKind::While, _ => TokenKind::Identifier(ident), }; self.add_token(token_kind); } fn scan_tokens(&mut self) { while !self.is_at_end() { self.start = self.current; self.scan_token(); } self.add_token(TokenKind::Eof); } } pub fn scan<'a>(input: &'a [char]) -> Result<Vec<Token<'a>>, Vec<Error>> { let mut scanner = Scanner { source: &input, tokens: vec![], errors: vec![], start: 0, current: 0, line: 0, }; scanner.scan_tokens(); if !scanner.errors.is_empty() { return Err(scanner.errors); } return Ok(scanner.tokens); }