Improve CLI

- Remove unused flags
- Show more helpful error messages
This commit is contained in:
Daniel M 2022-02-10 12:58:09 +01:00
parent c1bee69fa6
commit 4e92a416ed
3 changed files with 84 additions and 55 deletions

View File

@ -5,6 +5,7 @@ use crate::{
ast::{Ast, BinOpType, BlockScope, Expression, FunDecl, If, Statement, UnOpType}, ast::{Ast, BinOpType, BlockScope, Expression, FunDecl, If, Statement, UnOpType},
astoptimizer::{AstOptimizer, SimpleAstOptimizer}, astoptimizer::{AstOptimizer, SimpleAstOptimizer},
lexer::lex, lexer::lex,
nice_panic,
parser::parse, parser::parse,
stringstore::{Sid, StringStore}, stringstore::{Sid, StringStore},
}; };
@ -13,18 +14,25 @@ use crate::{
pub enum RuntimeError { pub enum RuntimeError {
#[error("Invalid error Index: {}", 0.to_string())] #[error("Invalid error Index: {}", 0.to_string())]
InvalidArrayIndex(Value), InvalidArrayIndex(Value),
#[error("Variable used but not declared: {0}")] #[error("Variable used but not declared: {0}")]
VarUsedNotDeclared(String), VarUsedNotDeclared(String),
#[error("Can't index into non-array variable: {0}")] #[error("Can't index into non-array variable: {0}")]
TryingToIndexNonArray(String), TryingToIndexNonArray(String),
#[error("Invalid value type for unary operation: {}", 0.to_string())] #[error("Invalid value type for unary operation: {}", 0.to_string())]
UnOpInvalidType(Value), UnOpInvalidType(Value),
#[error("Incompatible binary operations. Operands don't match: {} {}", 0.to_string(), 1.to_string())] #[error("Incompatible binary operations. Operands don't match: {} {}", 0.to_string(), 1.to_string())]
BinOpIncompatibleTypes(Value, Value), BinOpIncompatibleTypes(Value, Value),
#[error("Array access out of bounds: Accessed {0}, size is {1}")] #[error("Array access out of bounds: Accessed {0}, size is {1}")]
ArrayOutOfBounds(usize, usize), ArrayOutOfBounds(usize, usize),
#[error("Division by zero")] #[error("Division by zero")]
DivideByZero, DivideByZero,
#[error("Invalid number of arguments for function {0}. Expected {1}, got {2}")] #[error("Invalid number of arguments for function {0}. Expected {1}, got {2}")]
InvalidNumberOfArgs(String, usize, usize), InvalidNumberOfArgs(String, usize, usize),
} }
@ -83,14 +91,24 @@ impl Interpreter {
} }
pub fn run_str(&mut self, code: &str) { 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 { if self.print_tokens {
println!("Tokens: {:?}", 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> { pub fn run_ast(&mut self, mut ast: Ast) -> Result<(), RuntimeError> {
@ -137,15 +155,13 @@ impl Interpreter {
self.vartable.push(rhs); self.vartable.push(rhs);
} }
Statement::Block(block) => { Statement::Block(block) => match self.run_block(block)? {
match self.run_block(block)? { BlockExit::Return(val) => {
BlockExit::Return(val) => { self.vartable.truncate(framepointer);
self.vartable.truncate(framepointer); return Ok(BlockExit::Return(val));
return Ok(BlockExit::Return(val));
}
_ => ()
} }
} _ => (),
},
Statement::Loop(looop) => { Statement::Loop(looop) => {
// loop runs as long condition != 0 // 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, // All of the arg expressions must be resolved before pushing the vars on the stack,
// otherwise the stack positions are incorrect while resolving // otherwise the stack positions are incorrect while resolving
let args = args.iter().map(|arg| self.resolve_expr(arg)).collect::<Vec<_>>(); let args = args
.iter()
.map(|arg| self.resolve_expr(arg))
.collect::<Vec<_>>();
for arg in args { for arg in args {
self.vartable.push(arg?); self.vartable.push(arg?);
} }
@ -234,8 +253,16 @@ impl Interpreter {
let expected_num_args = self.funtable.get(*fun_stackpos).unwrap().argnames.len(); let expected_num_args = self.funtable.get(*fun_stackpos).unwrap().argnames.len();
if expected_num_args != args_len { if expected_num_args != args_len {
let fun_name = self.stringstore.lookup(*fun_name).cloned().unwrap_or("<unknown>".to_string()); let fun_name = self
return Err(RuntimeError::InvalidNumberOfArgs(fun_name, expected_num_args, args_len)); .stringstore
.lookup(*fun_name)
.cloned()
.unwrap_or("<unknown>".to_string());
return Err(RuntimeError::InvalidNumberOfArgs(
fun_name,
expected_num_args,
args_len,
));
} }
match self.run_block_fp_offset( match self.run_block_fp_offset(
@ -414,7 +441,12 @@ impl Interpreter {
match val { match val {
Value::I64(val) => format!("{}", val), Value::I64(val) => format!("{}", val),
Value::Array(val) => format!("{:?}", val.borrow()), Value::Array(val) => format!("{:?}", val.borrow()),
Value::String(text) => format!("{}", self.stringstore.lookup(*text).unwrap_or(&"<invalid string>".to_string())), Value::String(text) => format!(
"{}",
self.stringstore
.lookup(*text)
.unwrap_or(&"<invalid string>".to_string())
),
Value::Void => format!("void"), Value::Void => format!("void"),
} }
} }

View File

@ -1,18 +1,12 @@
use std::{ use std::{env::args, fs, process::exit};
env::args,
fs,
// io::{stdin, stdout, Write},
process::exit,
};
use nek_lang::interpreter::Interpreter; use nek_lang::{interpreter::Interpreter, nice_panic};
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct CliConfig { struct CliConfig {
print_tokens: bool, print_tokens: bool,
print_ast: bool, print_ast: bool,
no_optimizations: bool, no_optimizations: bool,
// interactive: bool,
file: Option<String>, file: Option<String>,
} }
@ -25,10 +19,11 @@ fn main() {
"--token" | "-t" => conf.print_tokens = true, "--token" | "-t" => conf.print_tokens = true,
"--ast" | "-a" => conf.print_ast = true, "--ast" | "-a" => conf.print_ast = true,
"--no-opt" | "-n" => conf.no_optimizations = true, "--no-opt" | "-n" => conf.no_optimizations = true,
// "--interactive" | "-i" => conf.interactive = true,
"--help" | "-h" => print_help(), "--help" | "-h" => print_help(),
file if conf.file.is_none() => conf.file = Some(file.to_string()), file if !arg.starts_with("-") && conf.file.is_none() => {
_ => panic!("Invalid argument: '{}'", arg), conf.file = Some(file.to_string())
}
_ => nice_panic!("Error: Invalid argument '{}'", arg),
} }
} }
@ -39,32 +34,15 @@ fn main() {
interpreter.optimize_ast = !conf.no_optimizations; interpreter.optimize_ast = !conf.no_optimizations;
if let Some(file) = &conf.file { 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); 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() { fn print_help() {
@ -73,7 +51,6 @@ fn print_help() {
println!("-t, --token Print the lexed tokens"); println!("-t, --token Print the lexed tokens");
println!("-a, --ast Print the abstract syntax tree"); println!("-a, --ast Print the abstract syntax tree");
println!("-n, --no-opt Disable the AST optimizations"); println!("-n, --no-opt Disable the AST optimizations");
// println!("-i, --interactive Interactive mode");
println!("-h, --help Show this help screen"); println!("-h, --help Show this help screen");
exit(0); exit(0);
} }

View File

@ -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 /// 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 /// 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. /// the next item in the iterator without removing it.
@ -14,7 +34,7 @@ impl<T> PutBackIter<T>
where where
T: Iterator, 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. /// adds the `peek` and `putback` functions.
pub fn new(iter: T) -> Self { pub fn new(iter: T) -> Self {
Self { Self {
@ -31,7 +51,7 @@ where
self.putback_stack.push(it); 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. /// also includes items that were previsouly put back and not yet removed.
pub fn peek(&mut self) -> Option<&T::Item> { pub fn peek(&mut self) -> Option<&T::Item> {
if self.putback_stack.is_empty() { if self.putback_stack.is_empty() {
@ -58,7 +78,7 @@ where
} }
pub trait PutBackableExt { 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. /// adds the `peek` and `putback` functions.
fn putbackable(self) -> PutBackIter<Self> fn putbackable(self) -> PutBackIter<Self>
where where
@ -117,7 +137,7 @@ mod tests {
let it4 = pb_iter.next(); let it4 = pb_iter.next();
assert_eq!(it4, 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 // item
pb_iter.putback(it0.unwrap()); pb_iter.putback(it0.unwrap());
assert_eq!(pb_iter.next(), it0); assert_eq!(pb_iter.next(), it0);