Skip to content

Commit

Permalink
Implement alternative ErrorReporter for testing
Browse files Browse the repository at this point in the history
  • Loading branch information
froth committed Jan 11, 2024
1 parent ad64b45 commit d5597d9
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 29 deletions.
85 changes: 77 additions & 8 deletions src/error_reporter.rs
Original file line number Diff line number Diff line change
@@ -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<Logline>,
}

impl VectorErrorReporter {
pub fn new() -> Self {
Self {
had_error: false,
errors: Vec::new(),
}
}
pub fn errors(&self) -> &Vec<Logline> {
&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
}
}
}
4 changes: 3 additions & 1 deletion src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
19 changes: 14 additions & 5 deletions src/lox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn ErrorReporter>,
}

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());
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
29 changes: 21 additions & 8 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -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<Token>,
current: usize,
error_reporter: &'a mut dyn ErrorReporter,
}

impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, current: 0 }
impl<'a> Parser<'a> {
pub fn new(tokens: Vec<Token>, error_reporter: &'a mut dyn ErrorReporter) -> Self {
Self {
tokens,
current: 0,
error_reporter,
}
}

pub fn parse(&mut self) -> Expr {
Expand Down Expand Up @@ -113,7 +119,7 @@ impl Parser {
mod parser_tests {

use crate::{
expr::{Expr, Literal},
error_reporter::{testing::VectorErrorReporter, ErrorReporter},
token::{Token, TokenType},
};

Expand All @@ -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")"#);
}

Expand All @@ -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 {
Expand Down
35 changes: 29 additions & 6 deletions src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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![],
Expand Down Expand Up @@ -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;
Expand All @@ -215,42 +218,62 @@ 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)
}

#[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);
}
}

0 comments on commit d5597d9

Please sign in to comment.