Improve CLI
- Remove unused flags - Show more helpful error messages
This commit is contained in:
parent
c1bee69fa6
commit
4e92a416ed
@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/main.rs
49
src/main.rs
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/util.rs
20
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
|
/// 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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user