Interp - an Interpreted Programming Language Pt.5 - Errors

1 minute read

Hey there! It’s time for Pt. 5. Today we take it easy and focus on errors our interpreter might throw us.

Errors

src/err.rs:

use crate::parser::loc;

pub enum Err {
    FileError { file: String, message: String },
    TokenExpected { expected: String },
}

pub fn throw(err: Err) {
    match err {
        Err::FileError { file, message } => eprintln!("{}: {}", file, message),
        Err::TokenExpected { expected } => eprintln!("expected {}", expected),
    }
}

pub fn throw_loc(err: Err, loc: &loc::Loc) {
    eprint!("{}:{}:{} ", loc.filename, loc.line_nr + 1, loc.index + 1);
    throw(err);
}

Don’t forget to declare the module. We also handle a failed file reading.

src/main.rs:


...

mod err; // Declare module

use std::io::{self, BufRead};
use std::fs;
use structopt::StructOpt;

fn main() {
    let opt = opt::Opt::from_args();

    let mut vm = vm::init_vm();

    let reader: Box<dyn BufRead>;
    let filename: String;
    match opt.source_file {
        Some(_filename) => {
            match fs::File::open(&_filename) {
                Ok(file) => {
                    reader = Box::new(io::BufReader::new(file));
                    filename = _filename;
                }
                Err(err) => {
                    err::throw(err::Err::FileError { file: _filename, message: err.to_string() });
                    return;
                }
            }
        }
        None => {
            reader = Box::new(io::BufReader::new(io::stdin()));
            filename = "<stdin>".to_owned();
        }
    }

    for (line_nr, line_str) in reader.lines().enumerate() {
        let line_str = line_str.unwrap();

        let parsed_line = parser::parse_line(&line_str, &filename, line_nr);
        match parsed_line {
            Ok(parsed_line) => {
                vm::exec_line(&mut vm, &parsed_line);
            }
            Err(()) => {
                if filename != "<stdin>" {
                    break;
                }
            }
        }
    }
}

Parse Errors

src/parser/mod.rs:


...

use crate::err;
use lalrpop_util::ParseError;

pub fn parse_line(line_str: &str, filename: &str, line_nr: usize) -> Result<line::Line, ()> {
    match grammar::LineParser::new().parse(filename, line_nr, line_str) {
        Ok(line) => Ok(line),
        Err(err) => {
            let (index, expected) = match err {
                ParseError::UnrecognizedToken { token, expected } => {
                    (token.0, expected)
                }
                ParseError::UnrecognizedEOF { location, expected } => {
                    (location, expected)
                }
                _ => panic!()
            };
            err::throw_loc(
                err::Err::TokenExpected { expected: expected.join(", ") },
                &loc::Loc { filename: filename.to_owned(), line_nr, index }
            );
            Err(())
        }
    }
}

Testing

Random characters will now raise an Err

> !
<stdin>:1:1 expected Identifier, String

Source Code

Source code for this part can be found here.

Next Up

Click for Pt. 6 or click here for all parts.

Updated: