diff --git a/src/error_reporter.rs b/src/error_reporter.rs index 721c83c..2428d7d 100644 --- a/src/error_reporter.rs +++ b/src/error_reporter.rs @@ -1,22 +1,91 @@ -#[derive(Default)] -pub struct ErrorReporter { +pub trait ErrorReporter { + fn error(&mut self, line: usize, message: &str) { + self.report(line, "", message) + } + + fn report(&mut self, line: usize, place: &str, message: &str); + + fn reset(&mut self); + fn had_error(&self) -> bool; +} + +pub struct ConsoleErrorReporter { had_error: bool, } -impl ErrorReporter { - pub fn error(&mut self, line: usize, message: &str) { - self.report(line, "", message) +impl ConsoleErrorReporter { + pub fn new() -> Self { + Self { had_error: false } } +} - pub fn report(&mut self, line: usize, place: &str, message: &str) { +impl ErrorReporter for ConsoleErrorReporter { + fn report(&mut self, line: usize, place: &str, message: &str) { eprintln!("[line {}] Error {}: {}", line, place, message); self.had_error = true; } - pub fn reset(&mut self) { + fn reset(&mut self) { self.had_error = false } - pub fn had_error(&self) -> bool { + fn had_error(&self) -> bool { self.had_error } } + +#[cfg(test)] +pub mod testing { + use super::ErrorReporter; + + #[derive(Debug, PartialEq, Eq)] + pub struct Logline { + pub line: usize, + pub place: String, + pub message: String, + } + + impl Logline { + pub fn new(line: usize, place: &str, message: &str) -> Self { + Self { + line, + place: place.into(), + message: message.into(), + } + } + } + + pub struct VectorErrorReporter { + had_error: bool, + errors: Vec, + } + + impl VectorErrorReporter { + pub fn new() -> Self { + Self { + had_error: false, + errors: Vec::new(), + } + } + pub fn errors(&self) -> &Vec { + &self.errors + } + } + + impl ErrorReporter for VectorErrorReporter { + fn report(&mut self, line: usize, place: &str, message: &str) { + self.errors.push(Logline { + line, + place: place.into(), + message: message.into(), + }); + self.had_error = true; + } + + fn reset(&mut self) { + self.had_error = false + } + fn had_error(&self) -> bool { + self.had_error + } + } +} diff --git a/src/expr.rs b/src/expr.rs index 1772f90..8ae9620 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -31,7 +31,9 @@ impl Expr { impl Display for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Binary(left, token, right) => write!(f, "({} {} {})", token.token_type, left, right), + Self::Binary(left, token, right) => { + write!(f, "({} {} {})", token.token_type, left, right) + } Self::Grouping(expr) => write!(f, "(group {})", expr), Self::Literal(literal) => write!(f, "({})", literal), Self::Unary(token, right) => write!(f, "({} {})", token.lexeme, right), diff --git a/src/lox.rs b/src/lox.rs index f8cdfd8..6324568 100644 --- a/src/lox.rs +++ b/src/lox.rs @@ -3,20 +3,29 @@ use std::{ io::{self, Write}, }; -use crate::{error_reporter::ErrorReporter, parser::Parser, scanner::Scanner}; +use crate::{ + error_reporter::{ConsoleErrorReporter, ErrorReporter}, + parser::Parser, + scanner::Scanner, +}; -#[derive(Default)] pub struct Lox { - error_reporter: ErrorReporter, + error_reporter: Box, } impl Lox { + pub fn new() -> Self { + Self { + error_reporter: Box::new(ConsoleErrorReporter::new()), + } + } + fn run(&mut self, source: String) { - let mut scanner = Scanner::new(source, &mut self.error_reporter); + let mut scanner = Scanner::new(source, self.error_reporter.as_mut()); let tokens = scanner.scan_tokens(); // tokens.iter().for_each(|x| println!("{:?}", x)); if !self.error_reporter.had_error() { - let mut parser = Parser::new(tokens); + let mut parser = Parser::new(tokens, self.error_reporter.as_mut()); println!("{}", parser.parse()); } } diff --git a/src/main.rs b/src/main.rs index 7ce4ed7..877472c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ mod token; fn main() { let args = Args::parse(); - let mut lox = Lox::default(); + let mut lox = Lox::new(); match args.file { Some(file) => { if !lox.run_file(file) { diff --git a/src/parser.rs b/src/parser.rs index ff37237..6b95a03 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,15 +1,21 @@ +use crate::error_reporter::ErrorReporter; use crate::expr::Literal::{self, Boolean}; use crate::{expr::Expr, token::Token}; use crate::token::TokenType::*; -pub struct Parser { +pub struct Parser<'a> { tokens: Vec, current: usize, + error_reporter: &'a mut dyn ErrorReporter, } -impl Parser { - pub fn new(tokens: Vec) -> Self { - Self { tokens, current: 0 } +impl<'a> Parser<'a> { + pub fn new(tokens: Vec, error_reporter: &'a mut dyn ErrorReporter) -> Self { + Self { + tokens, + current: 0, + error_reporter, + } } pub fn parse(&mut self) -> Expr { @@ -113,7 +119,7 @@ impl Parser { mod parser_tests { use crate::{ - expr::{Expr, Literal}, + error_reporter::{testing::VectorErrorReporter, ErrorReporter}, token::{Token, TokenType}, }; @@ -126,8 +132,10 @@ mod parser_tests { token(TokenType::String(string.clone())), token(TokenType::Eof), ]; - let mut parser = Parser::new(tokens); + let mut error_reporter = VectorErrorReporter::new(); + let mut parser = Parser::new(tokens, &mut error_reporter); let expr = parser.parse(); + assert!(!error_reporter.had_error()); assert_eq!(expr.to_string(), r#"("foo")"#); } @@ -142,9 +150,14 @@ mod parser_tests { token(TokenType::String(string.clone())), token(TokenType::Eof), ]; - let mut parser = Parser::new(tokens); + let mut error_reporter = VectorErrorReporter::new(); + let mut parser = Parser::new(tokens, &mut error_reporter); let expr = parser.parse(); - assert_eq!(expr.to_string(), r#"(EqualEqual (BangEqual ("foo") ("foo")) ("foo"))"#); + assert!(!error_reporter.had_error()); + assert_eq!( + expr.to_string(), + r#"(EqualEqual (BangEqual ("foo") ("foo")) ("foo"))"# + ); } fn token(token_type: TokenType) -> Token { diff --git a/src/scanner.rs b/src/scanner.rs index 31d7d7b..ddac1e8 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -11,7 +11,7 @@ pub struct Scanner<'a> { start: usize, current: usize, line: usize, - error_reporter: &'a mut ErrorReporter, + error_reporter: &'a mut dyn ErrorReporter, } enum ScanResult { @@ -40,7 +40,7 @@ static KEYWORDS: phf::Map<&'static str, TokenType> = phf_map! { }; impl<'a> Scanner<'a> { - pub fn new(source: String, e: &'a mut ErrorReporter) -> Self { + pub fn new(source: String, e: &'a mut dyn ErrorReporter) -> Self { Self { source, tokens: vec![], @@ -207,6 +207,9 @@ impl<'a> Scanner<'a> { #[cfg(test)] mod scanner_tests { + use crate::error_reporter::testing::Logline; + use crate::error_reporter::testing::VectorErrorReporter; + use crate::error_reporter::ErrorReporter; use super::Scanner; @@ -215,30 +218,33 @@ mod scanner_tests { #[test] fn parse_string() { let input = "\"test\""; - let mut error_reporter = ErrorReporter::default(); + let mut error_reporter = VectorErrorReporter::new(); let mut scanner = Scanner::new(input.into(), &mut error_reporter); let result = scanner.scan_tokens(); let head = &result[0].token_type; + assert!(!error_reporter.had_error()); assert_matches!(head, String(x) if x == "test"); } #[test] fn parse_float() { let input = "1.1"; - let mut error_reporter = ErrorReporter::default(); + let mut error_reporter = VectorErrorReporter::new(); let mut scanner = Scanner::new(input.into(), &mut error_reporter); let result = scanner.scan_tokens(); assert_eq!(result.len(), 2); let head = &result[0].token_type; + assert!(!error_reporter.had_error()); assert_matches!(head, Number(_)); } #[test] fn parse_identifier() { let input = "variable_name"; - let mut error_reporter = ErrorReporter::default(); + let mut error_reporter = VectorErrorReporter::new(); let mut scanner = Scanner::new(input.into(), &mut error_reporter); let result = scanner.scan_tokens(); let head = &result[0]; let token_type = &head.token_type; + assert!(!error_reporter.had_error()); assert_matches!(token_type, Identifier); assert_eq!(head.lexeme, input) } @@ -246,11 +252,28 @@ mod scanner_tests { #[test] fn parse_for() { let input = "for"; - let mut error_reporter = ErrorReporter::default(); + let mut error_reporter = VectorErrorReporter::new(); let mut scanner = Scanner::new(input.into(), &mut error_reporter); let result = scanner.scan_tokens(); let head = &result[0]; let token_type = &head.token_type; + assert!(!error_reporter.had_error()); assert_matches!(token_type, For); } + + #[test] + fn raise_error_on_unterminated_string() { + let input = "\""; + let mut error_reporter = VectorErrorReporter::new(); + let mut scanner = Scanner::new(input.into(), &mut error_reporter); + let result = scanner.scan_tokens(); + let head = &result[0]; + let token_type = &head.token_type; + assert!(error_reporter.had_error()); + assert_eq!( + error_reporter.errors(), + &vec![(Logline::new(1, "", "Unterminated string"))] + ); + assert_matches!(token_type, Eof); + } }