use std::fs::File;
use std::io::{self, Read, BufReader};
use std::error::Error;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum TokenType {
Plus,
Minus,
Star,
Slash,
IntLit,
}
#[derive(Debug)]
pub struct Token {
pub token_type: TokenType,
pub int_value: Option<i32>,
}
pub struct Scanner {
reader: BufReader<File>,
line: u32,
putback: Option<char>,
}
impl Scanner {
pub fn new(filename: &str) -> Result<Self, Box<dyn Error>> {
let file = File::open(filename)?;
Ok(Scanner {
reader: BufReader::new(file),
line: 1,
putback: Some('\n'), })
}
fn next(&mut self) -> Result<char, Box<dyn Error>> {
if let Some(c) = self.putback.take() {
return Ok(c);
}
let mut buf = [0; 1];
match self.reader.read(&mut buf) {
Ok(0) => Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()),
Ok(_) => {
let c = buf[0] as char;
if c == '\n' {
self.line += 1;
}
Ok(c)
},
Err(e) => Err(e.into()),
}
}
fn putback(&mut self, c: char) {
self.putback = Some(c);
}
fn skip(&mut self) -> Result<char, Box<dyn Error>> {
let mut c = self.next()?;
while c.is_whitespace() {
c = self.next()?;
}
Ok(c)
}
fn scan_int(&mut self, first_char: char) -> Result<i32, Box<dyn Error>> {
let mut val = 0;
let mut c = first_char;
while let Some(digit) = c.to_digit(10) {
val = val * 10 + digit as i32;
c = match self.next() {
Ok(ch) => ch,
Err(e) => {
if e.downcast_ref::<io::Error>().is_some_and(|ioe| ioe.kind() == io::ErrorKind::UnexpectedEof) {
return Ok(val);
}
return Err(e);
},
};
}
self.putback(c);
Ok(val)
}
pub fn line(&self) -> u32 {
self.line
}
pub fn scan(&mut self) -> Result<Option<Token>, Box<dyn Error>> {
let c = match self.skip() {
Ok(ch) => ch,
Err(e) => {
if let Some(ioe) = e.downcast_ref::<io::Error>()
&& ioe.kind() == io::ErrorKind::UnexpectedEof {
return Ok(None);
}
return Err(e);
},
};
match c {
'+' => Ok(Some(Token { token_type: TokenType::Plus, int_value: None })),
'-' => Ok(Some(Token { token_type: TokenType::Minus, int_value: None })),
'*' => Ok(Some(Token { token_type: TokenType::Star, int_value: None })),
'/' => Ok(Some(Token { token_type: TokenType::Slash, int_value: None })),
'0'..='9' => {
let int_value = self.scan_int(c)?;
Ok(Some(Token { token_type: TokenType::IntLit, int_value: Some(int_value) }))
},
_ => {
Err(format!("Unrecognised character '{}' on line {}", c, self.line).into())
},
}
}
}