Error Messaging Redesign

This commit contains the following:

* New data types to support full tracebacks
* New traceback data type used across stl and ast
* Updates to tests
* fixes for error messaging in sym and some stl functions
This commit is contained in:
Ava Apples Affine 2023-05-23 22:06:11 +00:00
parent 91ad4eed12
commit 789349df48
24 changed files with 837 additions and 374 deletions

View file

@ -24,6 +24,9 @@ use {
SymTable, ValueType,
Symbol, Args,
},
error::{
Traceback, start_trace,
},
eval::eval,
run,
},
@ -284,16 +287,20 @@ examples:
(l ping -c ping-count google.com))
(l emacs -nw (concat HOME '/.relishrc'))
";
fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
if ast.is_empty() {
Err("need at least one argument".to_string())
Err(start_trace(
("load", "expected at least one input")
.into()))
} else {
let mut args = VecDeque::from(args_from_ast(ast, syms));
if args.is_empty() {
Err("empty command".to_string())
Err(start_trace(
("load", "empty command")
.into()))
} else {
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
launch_command(
if let Err(e) = launch_command(
filepath,
&Vec::from(args.make_contiguous()),
Stdio::inherit(),
@ -301,10 +308,17 @@ fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Resu
Stdio::inherit(),
false,
state,
)?;
Ok(Ctr::Integer(state.last_exit_code.into()))
) {
Err(start_trace(
("load", e)
.into()))
} else {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
} else {
Err("file not found".to_string())
Err(start_trace(
("load", "binary not found on PATH")
.into()))
}
}
}
@ -318,9 +332,11 @@ Example:
(ls -la)
(grep '.rs')
(tr -d '.rs'))";
fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
if ast.is_empty() {
return Err("need at least one argument".to_string())
return Err(start_trace(
("pipe", "expected at least one input")
.into()))
}
let mut err: String = String::new();
@ -366,7 +382,9 @@ fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Resu
false
}
}) {
Err(err)
Err(start_trace(
("pipe", err)
.into()))
} else {
if lastpid > 0 {
if let Some(pos) = state.children
@ -384,10 +402,14 @@ fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Resu
println!("{}", String::from_utf8_lossy(&exit.stdout));
Ok(Ctr::Integer(state.last_exit_code.into()))
} else {
Err(format!("lost last child (pid {})", lastpid))
Err(start_trace(
("pipe", format!("lost last child (pid {})", lastpid))
.into()))
}
} else {
Err("impossible error state".to_string())
Err(start_trace(
("pipe", "impossible error state".to_string())
.into()))
}
}
}
@ -404,13 +426,17 @@ examples:
Unlike with the normal load function, the load to string function collects stdout output and returns it as a string.
";
fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
if ast.is_empty() {
Err("need at least one argument".to_string())
Err(start_trace(
("load-to-string", "expected at least one input")
.into()))
} else {
let mut args = VecDeque::from(args_from_ast(ast, syms));
if args.is_empty() {
Err("empty command".to_string())
Err(start_trace(
("load-to-string", "command is empty")
.into()))
} else {
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
let res = Command::new(filepath).args(args).output();
@ -421,13 +447,19 @@ fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellStat
if let Ok(string) = String::from_utf8(output.stdout) {
Ok(Ctr::String(string.trim_end().to_string()))
} else {
Err(format!("couldn't marshall utf-8 command output into a string"))
Err(start_trace(
("load-to-string", format!("couldn't marshall utf-8 command output into a string"))
.into()))
}
} else {
Err(format!("{}", res.err().unwrap()))
Err(start_trace(
("load-to-string", format!("{}", res.err().unwrap()))
.into()))
}
} else {
Err("file not found".to_string())
Err(start_trace(
("load-to-string", "binary not found on PATH")
.into()))
}
}
}
@ -453,9 +485,11 @@ const LOAD_WITH_DOCSTRING: &str = "Takes two arguments.
Example invocation:
(load-with (('stdout' '/dev/null'))
(ping -c 4 google.com))";
fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
if ast.len() != 2 {
Err("exactly two arguments needed".to_string())
Err(start_trace(
("load-with", "expected two inputs")
.into()))
} else {
if let Ctr::Seg(ref fd_redirect_forms) = *ast.car {
let mut stdout: Option<Stdio> = None;
@ -538,7 +572,9 @@ fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) ->
false
}
}) {
return Err(e.unwrap())
return Err(start_trace(
("load-with", e.unwrap())
.into()))
}
// now deal with the actual command
@ -547,14 +583,18 @@ fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) ->
if let Ctr::Seg(ref command) = *command_forms.car {
args = VecDeque::from(args_from_ast(command, syms));
} else {
return Err("command not list".to_string())
return Err(start_trace(
("load-with", "expected command input to be a list")
.into()))
}
if args.is_empty() {
Err("empty command".to_string())
Err(start_trace(
("load-with", "empty command")
.into()))
} else {
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
launch_command(
if let Err(e) = launch_command(
filepath,
&Vec::from(args.make_contiguous()),
stdin.or(Some(Stdio::inherit())).unwrap(),
@ -562,23 +602,34 @@ fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) ->
stderr.or(Some(Stdio::inherit())).unwrap(),
false,
state,
)?;
Ok(Ctr::Integer(state.last_exit_code.into()))
) {
Err(start_trace(
("load-with", e)
.into()))
} else {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
} else {
Err("file not found".to_string())
Err(start_trace(
("load-with", "binary not found on PATH")
.into()))
}
}
} else {
Err("second argument expected to be a list of command elements".to_string())
Err(start_trace(
("load-with", "expected second input to be list of command elements")
.into()))
}
} else {
Err("first argument expected to be a list of lists".to_string())
Err(start_trace(
("load-with", "expected first input to be a list of lists")
.into()))
}
}
}
const Q_DOCSTRING: &str = "returns exit code of last process to be run in posix layer";
fn q_callback(_ast: &Seg, _syms: &SymTable, state: &mut ShellState) -> Result<Ctr, String> {
fn q_callback(_ast: &Seg, _syms: &SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
@ -593,16 +644,20 @@ examples:
(bg ping -c4 google.com)
(bg vim) ;; vim waits patiently for you to foreground the process with 'fg'
";
fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
if ast.is_empty() {
Err("need at least one argument".to_string())
Err(start_trace(
("bg", "expected at least one input")
.into()))
} else {
let mut args = VecDeque::from(args_from_ast(ast, syms));
if args.is_empty() {
Err("empty command".to_string())
Err(start_trace(
("bg", "empty command")
.into()))
} else {
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
launch_command(
if let Err(e) = launch_command(
filepath,
&Vec::from(args.make_contiguous()),
Stdio::inherit(),
@ -610,10 +665,17 @@ fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result
Stdio::inherit(),
true,
state,
)?;
Ok(Ctr::Integer(state.last_exit_code.into()))
) {
Err(start_trace(
("bg", e)
.into()))
} else {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
} else {
Err("file not found".to_string())
Err(start_trace(
("bg", "binary not found on PATH")
.into()))
}
}
}
@ -631,27 +693,38 @@ pub fn fg_callback(
ast: &Seg,
_syms: &mut SymTable,
shell_state: &mut ShellState
) -> Result <Ctr, String> {
) -> Result <Ctr, Traceback> {
if let Ctr::Integer(i) = *ast.car {
make_foreground(i as u32, shell_state)?;
Ok(Ctr::None)
if let Err(e) = make_foreground(i as u32, shell_state) {
Err(start_trace(
("fg", e)
.into()))
} else {
Ok(Ctr::None)
}
} else {
Err(format!("illegal args to fg"))
Err(start_trace(
("fg", "expected input to be an integer")
.into()))
}
}
pub const CD_DOCSTRING: &str =
"Expects 1 argument (a string). Changes to a new directory";
pub fn cd_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
pub fn cd_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref dir) = *ast.car {
let dirp = Path::new(dir);
if let Err(s) = set_current_dir(&dirp) {
Err(format!("{}", s))
Err(start_trace(
("cd", s.to_string())
.into()))
} else {
Ok(Ctr::None)
}
} else {
Err(format!("impossible err: arg not a string"))
Err(start_trace(
("cd", "expected input to be a string")
.into()))
}
}
@ -721,7 +794,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
load_callback(ast, symtable, &mut shell_state.borrow_mut())
})),
..Default::default()
@ -735,7 +808,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
load_callback(ast, symtable, &mut load_ss.borrow_mut())
})),
..Default::default()
@ -749,7 +822,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::Infinite,
conditional_branches: true,
docs: String::from(BG_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
bg_callback(ast, symtable, &mut bg_ss.borrow_mut())
})),
..Default::default()
@ -763,7 +836,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::Strict(vec![Type::Integer]),
conditional_branches: true,
docs: String::from(FG_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
fg_callback(ast, symtable, &mut fg_ss.borrow_mut())
})),
..Default::default()
@ -777,7 +850,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::None,
conditional_branches: false,
docs: String::from(Q_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
q_callback(ast, symtable, &mut q_ss.borrow_mut())
})),
..Default::default()
@ -791,7 +864,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::Lazy(2),
conditional_branches: true,
docs: String::from(LOAD_WITH_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
load_with_callback(ast, symtable, &mut lw_ss.borrow_mut())
})),
..Default::default()
@ -805,7 +878,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_TO_STRING_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
// extra nonsense needed to allow nested calls
load_to_string_callback(
ast,
@ -834,7 +907,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
args: Args::Infinite,
conditional_branches: true,
docs: String::from(PIPE_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
pipe_callback(ast, symtable, &mut p_ss.borrow_mut())
})),
..Default::default()