diff --git a/src/interpreter.rs b/src/interpreter.rs index 267d986..c088370 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -5,6 +5,7 @@ use crate::{ ast::{Ast, BinOpType, BlockScope, Expression, FunDecl, If, Statement, UnOpType}, astoptimizer::{AstOptimizer, SimpleAstOptimizer}, lexer::lex, + nice_panic, parser::parse, stringstore::{Sid, StringStore}, }; @@ -13,18 +14,25 @@ use crate::{ pub enum RuntimeError { #[error("Invalid error Index: {}", 0.to_string())] InvalidArrayIndex(Value), + #[error("Variable used but not declared: {0}")] VarUsedNotDeclared(String), + #[error("Can't index into non-array variable: {0}")] TryingToIndexNonArray(String), + #[error("Invalid value type for unary operation: {}", 0.to_string())] UnOpInvalidType(Value), + #[error("Incompatible binary operations. Operands don't match: {} {}", 0.to_string(), 1.to_string())] BinOpIncompatibleTypes(Value, Value), + #[error("Array access out of bounds: Accessed {0}, size is {1}")] ArrayOutOfBounds(usize, usize), + #[error("Division by zero")] DivideByZero, + #[error("Invalid number of arguments for function {0}. Expected {1}, got {2}")] InvalidNumberOfArgs(String, usize, usize), } @@ -83,14 +91,24 @@ impl Interpreter { } pub fn run_str(&mut self, code: &str) { - let tokens = lex(code).unwrap(); + let tokens = match lex(code) { + Ok(tokens) => tokens, + Err(e) => nice_panic!("Lexing error: {}", e), + }; + if self.print_tokens { println!("Tokens: {:?}", tokens); } - let ast = parse(tokens).unwrap(); + let ast = match parse(tokens) { + Ok(ast) => ast, + Err(e) => nice_panic!("Parsing error: {}", e), + }; - self.run_ast(ast).unwrap(); + match self.run_ast(ast) { + Ok(_) => (), + Err(e) => nice_panic!("Runtime error: {}", e), + } } pub fn run_ast(&mut self, mut ast: Ast) -> Result<(), RuntimeError> { @@ -137,15 +155,13 @@ impl Interpreter { self.vartable.push(rhs); } - Statement::Block(block) => { - match self.run_block(block)? { - BlockExit::Return(val) => { - self.vartable.truncate(framepointer); - return Ok(BlockExit::Return(val)); - } - _ => () + Statement::Block(block) => match self.run_block(block)? { + BlockExit::Return(val) => { + self.vartable.truncate(framepointer); + return Ok(BlockExit::Return(val)); } - } + _ => (), + }, Statement::Loop(looop) => { // loop runs as long condition != 0 @@ -225,7 +241,10 @@ impl Interpreter { // All of the arg expressions must be resolved before pushing the vars on the stack, // otherwise the stack positions are incorrect while resolving - let args = args.iter().map(|arg| self.resolve_expr(arg)).collect::>(); + let args = args + .iter() + .map(|arg| self.resolve_expr(arg)) + .collect::>(); for arg in args { self.vartable.push(arg?); } @@ -234,8 +253,16 @@ impl Interpreter { let expected_num_args = self.funtable.get(*fun_stackpos).unwrap().argnames.len(); if expected_num_args != args_len { - let fun_name = self.stringstore.lookup(*fun_name).cloned().unwrap_or("".to_string()); - return Err(RuntimeError::InvalidNumberOfArgs(fun_name, expected_num_args, args_len)); + let fun_name = self + .stringstore + .lookup(*fun_name) + .cloned() + .unwrap_or("".to_string()); + return Err(RuntimeError::InvalidNumberOfArgs( + fun_name, + expected_num_args, + args_len, + )); } match self.run_block_fp_offset( @@ -414,7 +441,12 @@ impl Interpreter { match val { Value::I64(val) => format!("{}", val), Value::Array(val) => format!("{:?}", val.borrow()), - Value::String(text) => format!("{}", self.stringstore.lookup(*text).unwrap_or(&"".to_string())), + Value::String(text) => format!( + "{}", + self.stringstore + .lookup(*text) + .unwrap_or(&"".to_string()) + ), Value::Void => format!("void"), } } diff --git a/src/main.rs b/src/main.rs index cf601d8..3feea7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,12 @@ -use std::{ - env::args, - fs, - // io::{stdin, stdout, Write}, - process::exit, -}; +use std::{env::args, fs, process::exit}; -use nek_lang::interpreter::Interpreter; +use nek_lang::{interpreter::Interpreter, nice_panic}; #[derive(Debug, Default)] struct CliConfig { print_tokens: bool, print_ast: bool, no_optimizations: bool, - // interactive: bool, file: Option, } @@ -25,10 +19,11 @@ fn main() { "--token" | "-t" => conf.print_tokens = true, "--ast" | "-a" => conf.print_ast = true, "--no-opt" | "-n" => conf.no_optimizations = true, - // "--interactive" | "-i" => conf.interactive = true, "--help" | "-h" => print_help(), - file if conf.file.is_none() => conf.file = Some(file.to_string()), - _ => panic!("Invalid argument: '{}'", arg), + file if !arg.starts_with("-") && conf.file.is_none() => { + conf.file = Some(file.to_string()) + } + _ => nice_panic!("Error: Invalid argument '{}'", arg), } } @@ -39,32 +34,15 @@ fn main() { interpreter.optimize_ast = !conf.no_optimizations; if let Some(file) = &conf.file { - let code = fs::read_to_string(file).expect(&format!("File not found: '{}'", file)); + let code = match fs::read_to_string(file) { + Ok(code) => code, + Err(_) => nice_panic!("Error: Could not read file '{}'", file), + }; interpreter.run_str(&code); + } else { + println!("Error: No file given\n"); + print_help(); } - - // TODO: The interactive prompt is currently broken due to the precalculated stack positions. - // For this to still work, there needs to be a way to keep the stack in the interpreter after - // runing once. Also somehow the stringstore and var stack from the last parsing stages would - // need to be reused for the parser to work correctly - - // if conf.interactive || conf.file.is_none() { - // let mut code = String::new(); - - // loop { - // print!(">> "); - // stdout().flush().unwrap(); - - // code.clear(); - // stdin().read_line(&mut code).unwrap(); - - // if code.trim() == "exit" { - // break; - // } - - // interpreter.run_str(&code); - // } - // } } fn print_help() { @@ -73,7 +51,6 @@ fn print_help() { println!("-t, --token Print the lexed tokens"); println!("-a, --ast Print the abstract syntax tree"); println!("-n, --no-opt Disable the AST optimizations"); - // println!("-i, --interactive Interactive mode"); println!("-h, --help Show this help screen"); exit(0); } diff --git a/src/util.rs b/src/util.rs index 30e7bb3..7da8060 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,23 @@ +/// Exit the program with error code 1 and format-print the given text on stderr. This pretty much +/// works like panic, but doesn't show the additional information that panic adds. Those can be +/// interesting for debugging, but don't look that great when building a release executable for an +/// end user. +#[macro_export] +macro_rules! nice_panic { + ($fmt:expr) => { + { + eprintln!($fmt); + std::process::exit(1); + } + }; + ($fmt:expr, $($arg:tt)*) => { + { + eprintln!($fmt, $($arg)*); + std::process::exit(1); + } + }; +} + /// The PutBackIter allows for items to be put back back and to be peeked. Putting an item back /// will cause it to be the next item returned by `next`. Peeking an item will get a reference to /// the next item in the iterator without removing it. @@ -14,7 +34,7 @@ impl PutBackIter where T: Iterator, { - /// Make the given iterator putbackable, wrapping it in the PutBackIter type. This effectively + /// Make the given iterator putbackable, wrapping it in the PutBackIter type. This effectively /// adds the `peek` and `putback` functions. pub fn new(iter: T) -> Self { Self { @@ -31,7 +51,7 @@ where self.putback_stack.push(it); } - /// Peek the next item, getting a reference to it without removing it from the iterator. This + /// Peek the next item, getting a reference to it without removing it from the iterator. This /// also includes items that were previsouly put back and not yet removed. pub fn peek(&mut self) -> Option<&T::Item> { if self.putback_stack.is_empty() { @@ -58,7 +78,7 @@ where } pub trait PutBackableExt { - /// Make the iterator putbackable, wrapping it in the PutBackIter type. This effectively + /// Make the iterator putbackable, wrapping it in the PutBackIter type. This effectively /// adds the `peek` and `putback` functions. fn putbackable(self) -> PutBackIter where @@ -117,7 +137,7 @@ mod tests { let it4 = pb_iter.next(); assert_eq!(it4, iter.next()); - // Put one value back and check if `next` works as expected, returning the just put back + // Put one value back and check if `next` works as expected, returning the just put back // item pb_iter.putback(it0.unwrap()); assert_eq!(pb_iter.next(), it0);