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},
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)? {
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::<Vec<_>>();
let args = args
.iter()
.map(|arg| self.resolve_expr(arg))
.collect::<Vec<_>>();
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("<unknown>".to_string());
return Err(RuntimeError::InvalidNumberOfArgs(fun_name, expected_num_args, args_len));
let fun_name = self
.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(
@ -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(&"<invalid string>".to_string())),
Value::String(text) => format!(
"{}",
self.stringstore
.lookup(*text)
.unwrap_or(&"<invalid string>".to_string())
),
Value::Void => format!("void"),
}
}

View File

@ -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<String>,
}
@ -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);
}

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
/// 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.