Merge branch 'error-messaging' into 'main'

Error Messaging Redesign

See merge request whom/relish!3
This commit is contained in:
Ava Apples Affine 2023-05-23 22:06:12 +00:00
commit 9631f84fc5
24 changed files with 837 additions and 374 deletions

View file

@ -488,8 +488,6 @@ Note: this section will not show the status of each item unless you are viewing
Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered. Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered.
** TODO alpha tasks ** TODO alpha tasks
- circuit should actually be ~(reduce (lambda (form state) (and state (bool form))) my-args)~
- error display must improve at all costs
- exit function (causes program to shut down and return code) - exit function (causes program to shut down and return code)
- probably push the symtable inserts into functions in individual stl modules (like posix.rs does) - probably push the symtable inserts into functions in individual stl modules (like posix.rs does)
- fix the links in the readme like the ones in shell.org - fix the links in the readme like the ones in shell.org
@ -538,3 +536,6 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
- TCP Listener - TCP Listener
- HTTP Listener - HTTP Listener
- UDP Listener - UDP Listener
* Special thanks
Special thanks to [[https://nul.srht.site/]['Underscore Nul']] for consulting with me in the early stages of this project. Meeting my goal of only using safe rust (with the exception of the posix module) would have been a much bigger challenge if not for having someone to experiment with design ideas with.

View file

@ -20,6 +20,7 @@ use {
ast::{ ast::{
eval, lex, run, eval, lex, run,
Ctr, Seg, SymTable, Symbol, Ctr, Seg, SymTable, Symbol,
Traceback,
}, },
stdlib::{ stdlib::{
static_stdlib, dynamic_stdlib, load_defaults, static_stdlib, dynamic_stdlib, load_defaults,
@ -257,7 +258,7 @@ fn main() {
// scope the below borrow of syms // scope the below borrow of syms
let cfg_file = env::var(CFG_FILE_VNAME).unwrap_or(cfg_file_name); let cfg_file = env::var(CFG_FILE_VNAME).unwrap_or(cfg_file_name);
run(cfg_file.clone(), &mut syms) run(cfg_file.clone(), &mut syms)
.unwrap_or_else(|err: String| eprintln!("failed to load script {}\n{}", cfg_file, err)); .unwrap_or_else(|err: Traceback| eprintln!("failed to load script {}\n{}", cfg_file, err));
} }
dynamic_stdlib(&mut syms, Some(shell_state_bindings)).unwrap_or_else(|err: String| eprintln!("{}", err)); dynamic_stdlib(&mut syms, Some(shell_state_bindings)).unwrap_or_else(|err: String| eprintln!("{}", err));
@ -332,19 +333,19 @@ fn main() {
fn make_prompt(syms: &mut SymTable) -> CustomPrompt { fn make_prompt(syms: &mut SymTable) -> CustomPrompt {
let l_ctr = *syms let l_ctr = *syms
.call_symbol(&L_PROMPT_VNAME.to_string(), &Seg::new(), true) .call_symbol(&L_PROMPT_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|err: String| { .unwrap_or_else(|err: Traceback| {
eprintln!("{}", err); eprintln!("{}", err);
Box::new(Ctr::String("<prompt broken!>".to_string())) Box::new(Ctr::String("<prompt broken!>".to_string()))
}); });
let r_ctr = *syms let r_ctr = *syms
.call_symbol(&R_PROMPT_VNAME.to_string(), &Seg::new(), true) .call_symbol(&R_PROMPT_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|err: String| { .unwrap_or_else(|err: Traceback| {
eprintln!("{}", err); eprintln!("{}", err);
Box::new(Ctr::String("<prompt broken!>".to_string())) Box::new(Ctr::String("<prompt broken!>".to_string()))
}); });
let d_ctr = *syms let d_ctr = *syms
.call_symbol(&PROMPT_DELIM_VNAME.to_string(), &Seg::new(), true) .call_symbol(&PROMPT_DELIM_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|err: String| { .unwrap_or_else(|err: Traceback| {
eprintln!("{}", err); eprintln!("{}", err);
Box::new(Ctr::String("<prompt broken!>".to_string())) Box::new(Ctr::String("<prompt broken!>".to_string()))
}); });

99
src/error.rs Normal file
View file

@ -0,0 +1,99 @@
/* relish: versatile lisp shell
* Copyright (C) 2021 Aidan Hahn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use std::fmt;
#[derive(Debug, Clone)]
pub struct TraceItem {
pub caller: String,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct Traceback(pub Vec<TraceItem>);
pub fn start_trace(item: TraceItem) -> Traceback {
Traceback::new().with_trace(item)
}
impl Traceback {
pub fn new() -> Traceback {
Traceback(vec![])
}
pub fn with_trace(&self, item: TraceItem) -> Traceback {
let mut next = Traceback(self.0.clone());
next.0.push(item);
next
}
pub fn depth(&self) -> usize {
self.0.len()
}
}
impl fmt::Display for Traceback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
_ = writeln!(f, "** ERROR TRACEBACK");
for trace in self.0.iter().rev() {
_ = writeln!(f, " > {}: {}", trace.caller, trace.message);
}
writeln!(f, "Please refactor forms and try again...")
}
}
impl std::convert::Into<String> for Traceback {
fn into(self) -> String {
format!("{}", self)
}
}
impl std::convert::From<(&String, &str)> for TraceItem {
fn from(value: (&String, &str)) -> Self {
TraceItem {
caller: value.0.clone(),
message: String::from(value.1),
}
}
}
impl std::convert::From<(&String, String)> for TraceItem {
fn from(value: (&String, String)) -> Self {
TraceItem {
caller: value.0.clone(),
message: value.1,
}
}
}
impl std::convert::From<(&str, String)> for TraceItem {
fn from(value: (&str, String)) -> Self {
TraceItem {
caller: String::from(value.0),
message: value.1,
}
}
}
impl std::convert::From<(&str, &str)> for TraceItem {
fn from(value: (&str, &str)) -> Self {
TraceItem {
caller: String::from(value.0),
message: String::from(value.1),
}
}
}

View file

@ -17,12 +17,13 @@
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::{SymTable, call_lambda}; use crate::sym::{SymTable, call_lambda};
use crate::error::Traceback;
/* iterates over a syntax tree /* iterates over a syntax tree
* returns a NEW LIST of values * returns a NEW LIST of values
* representing the simplest possible form of the input * representing the simplest possible form of the input
*/ */
pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> { pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, Traceback> {
// data to return // data to return
let mut ret = Box::from(Ctr::None); let mut ret = Box::from(Ctr::None);
let mut first = true; let mut first = true;
@ -42,10 +43,7 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
Ctr::Seg(ref inner) => { Ctr::Seg(ref inner) => {
let interm = eval(inner, syms)?; let interm = eval(inner, syms)?;
if let Ctr::Lambda(ref l) = *interm { if let Ctr::Lambda(ref l) = *interm {
match call_lambda(l, arg_cdr, syms) { return call_lambda(l, arg_cdr, syms)
Ok(s) => return Ok(s.clone()),
Err(s) => return Err(format!("err in call to lambda: {}", s)),
}
} else { } else {
car = interm; car = interm;
} }
@ -63,11 +61,7 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
args = &outer_scope_seg_holder; args = &outer_scope_seg_holder;
} }
match syms.call_symbol(tok, args, first) { car = syms.call_symbol(tok, args, first)?;
Ok(s) => car = s,
Err(s) => return Err(format!("error in call to {}: {}", tok, s)),
}
if let Some(b) = syms.is_function_type(tok) { if let Some(b) = syms.is_function_type(tok) {
if b { if b {
return Ok(car); return Ok(car);
@ -83,10 +77,7 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
match **arg_cdr { match **arg_cdr {
Ctr::Symbol(ref tok) => { Ctr::Symbol(ref tok) => {
let blank = Seg::new(); let blank = Seg::new();
match syms.call_symbol(tok, &blank, false) { cdr = syms.call_symbol(tok, &blank, false)?;
Ok(res) => cdr = res,
Err(s) => return Err(format!("error fetching {}: {}", tok, s)),
}
none = true; none = true;
} }

View file

@ -16,6 +16,7 @@
*/ */
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::error::{Traceback, start_trace};
use phf::{Map, phf_map}; use phf::{Map, phf_map};
const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input"; const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input";
@ -31,9 +32,11 @@ static ESCAPES: Map<char, char> = phf_map! {
/* takes a line of user input /* takes a line of user input
* returns an unsimplified tree of tokens. * returns an unsimplified tree of tokens.
*/ */
pub fn lex(document: &String) -> Result<Box<Seg>, String> { pub fn lex(document: &String) -> Result<Box<Seg>, Traceback> {
if !document.is_ascii() { if !document.is_ascii() {
return Err("document may only contain ascii characters".to_string()); return Err(start_trace(
("<lex>", "document may only contain ascii characters".to_string())
.into()))
} }
// finish a singlet token, or do nothing // finish a singlet token, or do nothing
@ -43,7 +46,9 @@ pub fn lex(document: &String) -> Result<Box<Seg>, String> {
// TODO: Make multiple forms of Ok() // TODO: Make multiple forms of Ok()
// To represent the multiple passable outcomes // To represent the multiple passable outcomes
return match tree { return match tree {
Err(e) => Err(format!("Problem lexing document: {:?}", e)), Err(e) => Err(start_trace(
("<lex>", format!("Problem lexing document: {:?}", e))
.into())),
Ok(t) => Ok(t), Ok(t) => Ok(t),
}; };
} }

View file

@ -21,6 +21,7 @@ mod lex;
mod segment; mod segment;
mod stl; mod stl;
mod sym; mod sym;
mod error;
pub mod ast { pub mod ast {
pub use crate::run::run; pub use crate::run::run;
@ -28,6 +29,7 @@ pub mod ast {
pub use crate::lex::lex; pub use crate::lex::lex;
pub use crate::segment::{Ctr, Seg, Type}; pub use crate::segment::{Ctr, Seg, Type};
pub use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType}; pub use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType};
pub use crate::error::{Traceback, start_trace};
} }
pub mod stdlib { pub mod stdlib {

View file

@ -17,6 +17,7 @@
use crate::eval::eval; use crate::eval::eval;
use crate::lex::lex; use crate::lex::lex;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::SymTable; use crate::sym::SymTable;
use std::path::Path; use std::path::Path;
@ -47,10 +48,12 @@ pub fn find_on_path(filename: String) -> Option<String> {
None None
} }
pub fn run(filename: String, syms: &mut SymTable) -> Result<(), String> { pub fn run(filename: String, syms: &mut SymTable) -> Result<(), Traceback> {
let script_read_res = fs::read_to_string(filename); let script_read_res = fs::read_to_string(filename);
if script_read_res.is_err() { if script_read_res.is_err() {
Err(format!("Couldnt read script: {}", script_read_res.err().unwrap())) Err(start_trace(
("<call script>", format!("Couldnt read script: {}", script_read_res.err().unwrap()))
.into()))
} else { } else {
let script_read = script_read_res.unwrap() + ")"; let script_read = script_read_res.unwrap() + ")";
let script = "(".to_string() + &script_read; let script = "(".to_string() + &script_read;
@ -62,7 +65,7 @@ pub fn run(filename: String, syms: &mut SymTable) -> Result<(), String> {
pub const RUN_DOCSTRING: &str = "Takes one string argument. pub const RUN_DOCSTRING: &str = "Takes one string argument.
Attempts to find argument in PATH and attempts to call argument"; Attempts to find argument in PATH and attempts to call argument";
pub fn run_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn run_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car { if let Ctr::String(ref filename) = *ast.car {
if filename.ends_with(".rls") { if filename.ends_with(".rls") {
if let Some(filepath) = find_on_path(filename.to_string()) { if let Some(filepath) = find_on_path(filename.to_string()) {
@ -71,10 +74,12 @@ pub fn run_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
} else { } else {
let canonical_path_res = fs::canonicalize(filename); let canonical_path_res = fs::canonicalize(filename);
if canonical_path_res.is_err() { if canonical_path_res.is_err() {
return Err(canonical_path_res return Err(start_trace(
("<call script>", canonical_path_res
.err() .err()
.unwrap() .unwrap()
.to_string()); .to_string())
.into()))
} }
let canonical_path = canonical_path_res.ok().unwrap(); let canonical_path = canonical_path_res.ok().unwrap();
return run( return run(
@ -85,9 +90,13 @@ pub fn run_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
).and(Ok(Ctr::None)) ).and(Ok(Ctr::None))
} }
} else { } else {
return Err("binary called, unimplemented!".to_string()) return Err(start_trace(
("<call script>", "binary called, unimplemented!")
.into()))
} }
} else { } else {
Err("impossible: not a string".to_string()) Err(start_trace(
("<call script>", "impossible: not a string")
.into()))
} }
} }

View file

@ -18,6 +18,7 @@
use crate::segment::{Ctr, Seg, Type}; use crate::segment::{Ctr, Seg, Type};
use crate::run::{run_callback, RUN_DOCSTRING}; use crate::run::{run_callback, RUN_DOCSTRING};
use crate::sym::{Args, SymTable, Symbol, ValueType}; use crate::sym::{Args, SymTable, Symbol, ValueType};
use crate::error::Traceback;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use std::env::vars; use std::env::vars;
@ -41,15 +42,15 @@ pub const CFG_FILE_VNAME: &str = "RELISH_CFG_FILE";
pub const RELISH_DEFAULT_CONS_HEIGHT: i16 = 24; pub const RELISH_DEFAULT_CONS_HEIGHT: i16 = 24;
pub const RELISH_DEFAULT_CONS_WIDTH: i16 = 80; pub const RELISH_DEFAULT_CONS_WIDTH: i16 = 80;
fn l_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> { fn l_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
Ok(Ctr::String(">".to_string())) Ok(Ctr::String(">".to_string()))
} }
fn r_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> { fn r_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
Ok(Ctr::String(String::new())) Ok(Ctr::String(String::new()))
} }
fn prompt_delimiter_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> { fn prompt_delimiter_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
Ok(Ctr::String("λ ".to_string())) Ok(Ctr::String("λ ".to_string()))
} }
@ -643,7 +644,7 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
//get CFG_RELISH_ENV from syms //get CFG_RELISH_ENV from syms
let env_cfg_user_form = syms let env_cfg_user_form = syms
.call_symbol(&MODENV_CFG_VNAME.to_string(), &Seg::new(), true) .call_symbol(&MODENV_CFG_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|_: String| Box::new(Ctr::None)) .unwrap_or_else(|_: Traceback| Box::new(Ctr::None))
.to_string() .to_string()
.eq("true"); .eq("true");
@ -656,7 +657,7 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
conditional_branches: true, conditional_branches: true,
docs: decl::STORE_DOCSTRING.to_string(), docs: decl::STORE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
move |ast: &Seg, syms: &mut SymTable| -> Result<Ctr, String> { move |ast: &Seg, syms: &mut SymTable| -> Result<Ctr, Traceback> {
decl::store_callback(ast, syms, env_cfg_user_form) decl::store_callback(ast, syms, env_cfg_user_form)
}, },
)), )),
@ -668,7 +669,7 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
if let Some(shell_state) = shell { if let Some(shell_state) = shell {
let posix_cfg_user_form = syms let posix_cfg_user_form = syms
.call_symbol(&POSIX_CFG_VNAME.to_string(), &Seg::new(), true) .call_symbol(&POSIX_CFG_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|_: String| Box::new(Ctr::None)) .unwrap_or_else(|_: Traceback| Box::new(Ctr::None))
.to_string() .to_string()
.eq("true"); .eq("true");

View file

@ -16,11 +16,12 @@
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::SymTable; use crate::sym::SymTable;
use crate::error::{Traceback, start_trace};
pub const CONS_DOCSTRING: &str = "traverses any number of arguments collecting them into a list. pub const CONS_DOCSTRING: &str = "traverses any number of arguments collecting them into a list.
If the first argument is a list, all other arguments are added sequentially to the end of the list contained in the first argument."; If the first argument is a list, all other arguments are added sequentially to the end of the list contained in the first argument.";
pub fn cons_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn cons_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car { if let Ctr::Seg(ref s) = *ast.car {
let mut temp = s.clone(); let mut temp = s.clone();
if let Ctr::Seg(ref list) = *ast.cdr { if let Ctr::Seg(ref list) = *ast.cdr {
@ -54,7 +55,7 @@ pub const LEN_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns the length of said list. Returns the length of said list.
Interpreter will panic if the length of the list is greater than the max value of a signed 128 bit integer"; Interpreter will panic if the length of the list is greater than the max value of a signed 128 bit integer";
pub fn len_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn len_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car { if let Ctr::Seg(ref s) = *ast.car {
if let Ctr::None = *s.car { if let Ctr::None = *s.car {
Ok(Ctr::Integer(0)) Ok(Ctr::Integer(0))
@ -62,7 +63,7 @@ pub fn len_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::Integer(s.len() as i128)) Ok(Ctr::Integer(s.len() as i128))
} }
} else { } else {
Err("impossible condition: argument to len not a list".to_string()) Err(start_trace(("len", "input is not a list").into()))
} }
} }
@ -70,15 +71,15 @@ pub const CAR_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a copy of the first value in that list. Returns a copy of the first value in that list.
If the list is empty, returns an err to avoid passing back a null value."; If the list is empty, returns an err to avoid passing back a null value.";
pub fn car_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn car_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car { if let Ctr::Seg(ref s) = *ast.car {
if let Ctr::None = *s.car { if let Ctr::None = *s.car {
Err("argument is empty".to_string()) Err(start_trace(("len", "input is empty").into()))
} else { } else {
Ok(*s.car.clone()) Ok(*s.car.clone())
} }
} else { } else {
Err("impossible condition: argument to car not a list".to_string()) Err(start_trace(("len", "input is not a list").into()))
} }
} }
@ -86,7 +87,7 @@ pub const CDR_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a copy of the last value in that list. Returns a copy of the last value in that list.
If the list is empty, returns an err to avoid passing back a null value."; If the list is empty, returns an err to avoid passing back a null value.";
pub fn cdr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn cdr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car { if let Ctr::Seg(ref s) = *ast.car {
let mut ret = &s.car; let mut ret = &s.car;
let mut iter = &s.cdr; let mut iter = &s.cdr;
@ -95,12 +96,12 @@ pub fn cdr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
iter = &next.cdr; iter = &next.cdr;
} }
if let Ctr::None = **ret { if let Ctr::None = **ret {
Err("argument is empty".to_string()) Err(start_trace(("cdr", "input is empty").into()))
} else { } else {
Ok(*ret.clone()) Ok(*ret.clone())
} }
} else { } else {
Err("impossible condition: argument to cdr not a list".to_string()) Err(start_trace(("cdr", "input is not a list").into()))
} }
} }
@ -111,10 +112,10 @@ Example: (pop (1 2 3)) -> (1 (2 3)).
The head can then be accessed by car and the rest can be accessed by cdr."; The head can then be accessed by car and the rest can be accessed by cdr.";
pub fn pop_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn pop_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car { if let Ctr::Seg(ref s) = *ast.car {
if s.len() < 1 { if s.len() < 1 {
return Err("cannot pop from empty list".to_string()); return Err(start_trace(("pop", "input is empty").into()));
} }
let mut ret = Seg::new(); let mut ret = Seg::new();
ret.append(s.car.clone()); ret.append(s.car.clone());
@ -125,7 +126,7 @@ pub fn pop_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
} }
Ok(Ctr::Seg(ret)) Ok(Ctr::Seg(ret))
} else { } else {
Err("Impossible condition: arg not a list".to_string()) Err(start_trace(("pop", "input is not a list").into()))
} }
} }
@ -136,10 +137,10 @@ Example: (dq (1 2 3)) -> (3 (1 2)).
The last element can then be accessed by car and the rest can be accessed by cdr."; The last element can then be accessed by car and the rest can be accessed by cdr.";
pub fn dequeue_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn dequeue_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car { if let Ctr::Seg(ref s) = *ast.car {
if s.len() < 1 { if s.len() < 1 {
return Err("cannot dequeue from empty list".to_string()); return Err(start_trace(("dequeue", "expected an input").into()));
} }
let mut rest = Seg::new(); let mut rest = Seg::new();
let mut iter = s; let mut iter = s;
@ -164,14 +165,14 @@ pub fn dequeue_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
Ok(Ctr::Seg(ret)) Ok(Ctr::Seg(ret))
} else { } else {
Err("Impossible condition: arg not a list".to_string()) Err(start_trace(("dequeue", "input is not a list").into()))
} }
} }
pub const REVERSE_DOCSTRING: &str = "Takes a single argument, expected to be a list. pub const REVERSE_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns the same list, but in reverse order."; Returns the same list, but in reverse order.";
pub fn reverse_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn reverse_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car { if let Ctr::Seg(ref s) = *ast.car {
let mut ret = Seg::new(); let mut ret = Seg::new();
s.circuit_reverse(&mut |arg: &Ctr| -> bool { s.circuit_reverse(&mut |arg: &Ctr| -> bool {
@ -181,6 +182,6 @@ pub fn reverse_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
Ok(Ctr::Seg(ret)) Ok(Ctr::Seg(ret))
} else { } else {
Err("Impossible condition: arg not a list".to_string()) Err(start_trace(("reverse", "input is not a list").into()))
} }
} }

View file

@ -15,6 +15,7 @@
*/ */
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::error::{Traceback, start_trace};
use crate::sym::{SymTable, ValueType}; use crate::sym::{SymTable, ValueType};
pub const AND_DOCSTRING: &str = pub const AND_DOCSTRING: &str =
@ -22,20 +23,21 @@ pub const AND_DOCSTRING: &str =
starts with arg1 AND arg2, and then calculates prev_result AND next_arg. starts with arg1 AND arg2, and then calculates prev_result AND next_arg.
returns final result."; returns final result.";
pub fn and_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn and_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut type_error = false; let mut type_error = false;
let mut cursor = 0;
let result = ast.circuit(&mut |arg: &Ctr| -> bool { let result = ast.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Bool(b) = *arg { if let Ctr::Bool(b) = *arg {
cursor += 1;
b b
} else { } else {
eprintln!("{} is not a boolean", arg);
type_error = true; type_error = true;
false false
} }
}); });
if type_error { if type_error {
Err("all arguments to and must evaluate to boolean".to_string()) Err(start_trace(("and", format!("input {} not a boolean", cursor)).into()))
} else { } else {
Ok(Ctr::Bool(result)) Ok(Ctr::Bool(result))
} }
@ -46,20 +48,21 @@ pub const OR_DOCSTRING: &str =
starts with arg1 OR arg2, and then calculates prev_result OR next_arg. starts with arg1 OR arg2, and then calculates prev_result OR next_arg.
returns final result."; returns final result.";
pub fn or_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn or_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut result = false; let mut result = false;
let mut cursor = 0;
let correct_types = ast.circuit(&mut |arg: &Ctr| -> bool { let correct_types = ast.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Bool(b) = *arg { if let Ctr::Bool(b) = *arg {
cursor += 1;
result = result || b; result = result || b;
true true
} else { } else {
eprintln!("{} is not a boolean", arg);
false false
} }
}); });
if !correct_types { if !correct_types {
Err("all arguments to 'or' must evaluate to boolean".to_string()) Err(start_trace(("or", format!("input {} not a boolean", cursor)).into()))
} else { } else {
Ok(Ctr::Bool(result)) Ok(Ctr::Bool(result))
} }
@ -68,11 +71,11 @@ pub fn or_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
pub const NOT_DOCSTRING: &str = "takes a single argument (expects a boolean). pub const NOT_DOCSTRING: &str = "takes a single argument (expects a boolean).
returns false if arg is true or true if arg is false."; returns false if arg is true or true if arg is false.";
pub fn not_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn not_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Bool(b) = *ast.car { if let Ctr::Bool(b) = *ast.car {
Ok(Ctr::Bool(!b)) Ok(Ctr::Bool(!b))
} else { } else {
Err("impossible state: non bool given to not".to_string()) Err(start_trace(("not", "input is not a bool").into()))
} }
} }
@ -80,7 +83,7 @@ pub const ISEQ_DOCSTRING: &str = "traverses a list of N arguments.
returns true if all arguments hold the same value. returns true if all arguments hold the same value.
NOTE: 1 and 1.0 are the same, but '1' 'one' or one (symbol) aren't"; NOTE: 1 and 1.0 are the same, but '1' 'one' or one (symbol) aren't";
pub fn iseq_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn iseq_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let head_ctr_ref = &*ast.car; let head_ctr_ref = &*ast.car;
Ok(Ctr::Bool( Ok(Ctr::Bool(
ast.circuit(&mut |arg: &Ctr| -> bool { arg == head_ctr_ref }), ast.circuit(&mut |arg: &Ctr| -> bool { arg == head_ctr_ref }),
@ -91,12 +94,12 @@ pub const TOGGLE_DOCSTRING: &str = "switches a boolean symbol between true or fa
Takes a single argument (a symbol). Looks it up in the variable table. Takes a single argument (a symbol). Looks it up in the variable table.
Either sets the symbol to true if it is currently false, or vice versa."; Either sets the symbol to true if it is currently false, or vice versa.";
pub fn toggle_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn toggle_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String; let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car { if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone(); var_name = s.clone();
} else { } else {
return Err("argument to toggle should be a symbol".to_string()); return Err(start_trace(("toggle", "input must be a symbol").into()));
} }
let mut sym = syms let mut sym = syms
@ -107,11 +110,11 @@ pub fn toggle_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
sym.value = ValueType::VarForm(Box::new(Ctr::Bool(!b))); sym.value = ValueType::VarForm(Box::new(Ctr::Bool(!b)));
} else { } else {
syms.insert(var_name, sym); syms.insert(var_name, sym);
return Err("can only toggle a boolean".to_string()); return Err(start_trace(("toggle", "can only toggle a boolean").into()));
} }
} else { } else {
syms.insert(var_name, sym); syms.insert(var_name, sym);
return Err("cannot toggle a function".to_string()); return Err(start_trace(("toggle", "cannot toggle a function").into()));
} }
syms.insert(var_name, sym); syms.insert(var_name, sym);
@ -124,7 +127,7 @@ attempts to cast argument to a bool.
Strings will cast to a bool if they are 'true' or 'false'. Strings will cast to a bool if they are 'true' or 'false'.
Integers and Floats will cast to true if they are 0 and false otherwise."; Integers and Floats will cast to true if they are 0 and false otherwise.";
pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
match &*ast.car { match &*ast.car {
Ctr::Bool(_) => Ok(*ast.car.clone()), Ctr::Bool(_) => Ok(*ast.car.clone()),
Ctr::String(s) => { Ctr::String(s) => {
@ -133,12 +136,12 @@ pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
} else if s == "false" { } else if s == "false" {
Ok(Ctr::Bool(false)) Ok(Ctr::Bool(false))
} else { } else {
Err("string cannot be parsed as a bool".to_string()) Err(start_trace(("bool", "string cannot be parsed as a bool").into()))
} }
}, },
Ctr::Integer(i) => Ok(Ctr::Bool(*i == 0)), Ctr::Integer(i) => Ok(Ctr::Bool(*i == 0)),
Ctr::Float(f) => Ok(Ctr::Bool(*f == 0.0)), Ctr::Float(f) => Ok(Ctr::Bool(*f == 0.0)),
_ => Err(format!("cannot convert a {} to a boolean", _ => Err(start_trace(("bool", format!("cannot convert a {} to a boolean",
ast.car.to_type())), ast.car.to_type())).into())),
} }
} }

View file

@ -16,6 +16,7 @@
*/ */
use crate::eval::eval; use crate::eval::eval;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::{SymTable, Symbol}; use crate::sym::{SymTable, Symbol};
@ -29,47 +30,63 @@ example: (if my-state-switch
(do-my-thing) (do-my-thing)
(else-an-other-thing))"; (else-an-other-thing))";
pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let cond: bool; let cond: bool;
match *ast.car { match *ast.car {
Ctr::Seg(ref cond_form) => { Ctr::Seg(ref cond_form) => {
if let Ctr::Bool(cond_from_eval) = *eval(cond_form, syms)? { let intermediate = eval(cond_form, syms);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
cond = cond_from_eval; cond = cond_from_eval;
} else { } else {
return Err("first argument to if must evaluate to be a boolean".to_string()); return Err(start_trace(("if", "first arg must be a bool").into()));
} }
} }
Ctr::Symbol(ref cond_name) => { Ctr::Symbol(ref cond_name) => {
if let Ctr::Bool(cond_from_eval) = *syms.call_symbol(cond_name, &Seg::new(), false)? { let intermediate = syms.call_symbol(cond_name, &Seg::new(), false);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
cond = cond_from_eval; cond = cond_from_eval;
} else { } else {
return Err("first argument to if must evaluate to be a boolean".to_string()); return Err(start_trace(("if", "first arg must be a bool").into()));
} }
} }
Ctr::Bool(cond_from_car) => cond = cond_from_car, Ctr::Bool(cond_from_car) => cond = cond_from_car,
_ => return Err("first argument to if must evaluate to be a boolean".to_string()), _ => return Err(start_trace(("if", "first arg must be a bool").into())),
} }
let then_form: &Seg; let then_form: &Seg;
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
then_form = s; then_form = s;
} else { } else {
return Err("impossible condition: not enough args to if".to_string()); return Err(start_trace(("if", "not enough args").into()));
} }
if cond { if cond {
// then // then
match *then_form.car { match *then_form.car {
Ctr::Seg(ref first_arg) => Ok(*eval(first_arg, syms)?), Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating then form").into())),
Ok(val) => Ok(*val)
},
_ => { _ => {
let eval_tree = &Seg::from_mono(then_form.car.clone()); let eval_tree = &Seg::from_mono(then_form.car.clone());
let eval_res = *eval(eval_tree, syms)?; let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating then form").into()))
}
let eval_res = *eval_intermediate?;
if let Ctr::Seg(ref s) = eval_res { if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone()) Ok(*s.car.clone())
} else { } else {
Err("impossible condition: list evals to non list".to_string()) Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
} }
} }
} }
@ -77,19 +94,27 @@ pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
// else // else
if let Ctr::Seg(ref else_form) = *then_form.cdr { if let Ctr::Seg(ref else_form) = *then_form.cdr {
match *else_form.car { match *else_form.car {
Ctr::Seg(ref second_arg) => Ok(*eval(second_arg, syms)?), Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating else form").into())),
Ok(val) => Ok(*val)
},
_ => { _ => {
let eval_tree = &Seg::from_mono(else_form.car.clone()); let eval_tree = &Seg::from_mono(else_form.car.clone());
let eval_res = *eval(eval_tree, syms)?; let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating else form").into()))
}
let eval_res = *eval_intermediate?;
if let Ctr::Seg(ref s) = eval_res { if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone()) Ok(*s.car.clone())
} else { } else {
Err("impossible condition: list evals to non list".to_string()) Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
} }
} }
} }
} else { } else {
Err("impossible condition: args not in standard form".to_string()) Err(start_trace(("if", "impossible condition: args not in standard form").into()))
} }
} }
} }
@ -108,7 +133,7 @@ Then, the echo form is evaluated, printing 'hello-world'.
Finally, the some-func form is evaluated. Finally, the some-func form is evaluated.
Since the call to some-func is the final form, its value is returned."; Since the call to some-func is the final form, its value is returned.";
pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut localsyms = syms.clone(); let mut localsyms = syms.clone();
let mut locals = vec![]; let mut locals = vec![];
let locals_form: &Seg; let locals_form: &Seg;
@ -116,21 +141,24 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(ref locals_form_list) = *ast.car { if let Ctr::Seg(ref locals_form_list) = *ast.car {
locals_form = locals_form_list; locals_form = locals_form_list;
} else { } else {
return Err("first element of let form must contain local variables".to_string()); return Err(start_trace(("let", "first form does not contain list of local declarations")
.into()));
} }
if let Ctr::Seg(ref eval_forms_head) = *ast.cdr { if let Ctr::Seg(ref eval_forms_head) = *ast.cdr {
eval_forms = eval_forms_head; eval_forms = eval_forms_head;
} else { } else {
return Err("let form should contain one or more elements to evaluate".to_string()); return Err(start_trace(("let", "missing one or more forms to evaluate").into()));
} }
let mut err_trace: Traceback = Traceback::new();
// process locals forms // process locals forms
if !locals_form.circuit(&mut |var_decl: &Ctr| -> bool { if !locals_form.circuit(&mut |var_decl: &Ctr| -> bool {
if let Ctr::Seg(ref var_form) = *var_decl { if let Ctr::Seg(ref var_form) = *var_decl {
if let Ctr::Symbol(ref name) = *var_form.car { if let Ctr::Symbol(ref name) = *var_form.car {
if let Ctr::Seg(ref var_val_form) = *var_form.cdr { if let Ctr::Seg(ref var_val_form) = *var_form.cdr {
let var_val_res: Result<Box<Ctr>, String>; let var_val_res: Result<Box<Ctr>, Traceback>;
if let Ctr::Seg(ref val_form) = *var_val_form.car { if let Ctr::Seg(ref val_form) = *var_val_form.car {
var_val_res = eval(val_form, &mut localsyms); var_val_res = eval(val_form, &mut localsyms);
} else { } else {
@ -145,7 +173,10 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
} }
} }
if let Err(e) = var_val_res { if let Err(e) = var_val_res {
eprintln!("failed to evaluate definition of {}: {}", name, e); err_trace = e
.with_trace(
("let", format!("failed to evaluate definition of {}", name))
.into());
return false; return false;
} }
@ -162,21 +193,27 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
// nothing to declare // nothing to declare
return true; return true;
} else { } else {
eprintln!("improper declaration of {}: not a list", var_decl); err_trace = start_trace(
("let", format!("improper declaration of {}: not a list", var_form))
.into());
return false; return false;
} }
} else { } else {
eprintln!("improper declaration form: {}", var_decl); err_trace = start_trace(
("let", format!("improper declaration form: {}", var_decl))
.into());
return false; return false;
} }
true true
}) { }) {
return Err("local variable declaration failure".to_string()); assert!(err_trace.depth() > 0);
return Err(err_trace);
} }
assert!(err_trace.depth() < 1);
let mut result: Box<Ctr> = Box::new(Ctr::None); let mut result: Box<Ctr> = Box::new(Ctr::None);
if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool { if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool {
let res: Result<Box<Ctr>, String>; let res: Result<Box<Ctr>, Traceback>;
if let Ctr::Seg(ref eval_tree) = eval_form { if let Ctr::Seg(ref eval_tree) = eval_form {
res = eval(&eval_tree, &mut localsyms); res = eval(&eval_tree, &mut localsyms);
} else { } else {
@ -192,15 +229,19 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
} }
if let Err(e) = res { if let Err(e) = res {
eprintln!("{}", e); err_trace = e.with_trace(
("let", "evaluation failure")
.into());
return false; return false;
} }
result = res.unwrap().clone(); result = res.unwrap().clone();
true true
}) { }) {
return Err("evaluation failure".to_string()); assert!(err_trace.depth() > 0);
return Err(err_trace);
} }
assert!(err_trace.depth() < 1);
for i in locals { for i in locals {
localsyms.remove(&i); localsyms.remove(&i);
@ -217,12 +258,12 @@ example: (while (check-my-state)
(do-thing-2 args) (do-thing-2 args)
(edit-state my-state))"; (edit-state my-state))";
pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let eval_cond: &Seg; let eval_cond: &Seg;
let outer_maybe: Seg; let outer_maybe: Seg;
let eval_bodies_head: &Seg; let eval_bodies_head: &Seg;
let mut unwrap = false; let mut unwrap = false;
let mut result: Result<Box<Ctr>, String> = Ok(Box::new(Ctr::None)); let mut result: Result<Box<Ctr>, Traceback> = Ok(Box::new(Ctr::None));
if let Ctr::Seg(ref cond) = *ast.car { if let Ctr::Seg(ref cond) = *ast.car {
eval_cond = cond; eval_cond = cond;
@ -235,7 +276,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(ref eval) = *ast.cdr { if let Ctr::Seg(ref eval) = *ast.cdr {
eval_bodies_head = eval; eval_bodies_head = eval;
} else { } else {
return Err("expected N more bodies in while form".to_string()); return Err(start_trace(("while", "expected one or more forms to evaluate").into()));
} }
loop { loop {
@ -255,7 +296,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
break; break;
} }
} else { } else {
return Err("first body of while form should evaluate to a bool".to_string()); return Err(start_trace(("while", "expected first form to evaluate to a boolean").into()));
} }
if !eval_bodies_head.circuit(&mut |body: &Ctr| -> bool { if !eval_bodies_head.circuit(&mut |body: &Ctr| -> bool {
@ -271,7 +312,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
result = eval(eval_arg, syms); result = eval(eval_arg, syms);
result.is_ok() result.is_ok()
}) { }) {
return Err(result.err().unwrap()); return Err(result.err().unwrap().with_trace(("while", "evaluation failure").into()));
} }
} }
@ -289,9 +330,9 @@ example: (circuit (eq? (do-operation) myresult)
in this example, do-another-operation will not be called"; in this example, do-another-operation will not be called";
pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut cursor = 0; let mut cursor = 0;
let mut error: String = String::new(); let mut err_trace = Traceback::new();
let result = ast.circuit(&mut |form: &Ctr| -> bool { let result = ast.circuit(&mut |form: &Ctr| -> bool {
cursor += 1; cursor += 1;
let operand: &Seg; let operand: &Seg;
@ -307,7 +348,9 @@ pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
let eval_result = eval(operand, syms); let eval_result = eval(operand, syms);
match eval_result { match eval_result {
Err(s) => error = format!("eval failed at form {cursor}: {s}"), Err(s) => err_trace = s.with_trace(
("circuit", format!("failed at form {cursor}"))
.into()),
Ok(s) => match *s { Ok(s) => match *s {
Ctr::Bool(b) => return b, Ctr::Bool(b) => return b,
@ -317,25 +360,26 @@ pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
} else if let Ctr::Integer(i) = *s.car { } else if let Ctr::Integer(i) = *s.car {
return i==0; return i==0;
} else { } else {
error = "impossible condition in circuit form".to_string(); err_trace = err_trace.with_trace(
("circuit", "impossible condition")
.into());
} }
}, },
Ctr::Integer(i) => return i == 0, Ctr::Integer(i) => return i == 0,
_ => error = format!("{cursor} form did not evaluate to a boolean"), _ => err_trace = err_trace.with_trace(
("circuit", format!("form {cursor} did not evaluate to a boolean"))
.into()),
}, },
} }
false false
}); });
if !result && !error.is_empty() { if !result && err_trace.depth() > 0 {
Err(format!("circuit stopped at form {cursor}: {error}")) Err(err_trace)
} else { } else {
if !result {
eprintln!("circuit stopped at form {cursor}");
}
Ok(Ctr::Bool(result)) Ok(Ctr::Bool(result))
} }
} }

View file

@ -16,6 +16,7 @@
*/ */
use crate::eval::eval; use crate::eval::eval;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg, Type}; use crate::segment::{Ctr, Seg, Type};
use crate::stdlib::{CONSOLE_XDIM_VNAME, RELISH_DEFAULT_CONS_WIDTH}; use crate::stdlib::{CONSOLE_XDIM_VNAME, RELISH_DEFAULT_CONS_WIDTH};
use crate::sym::{SymTable, Symbol, UserFn, ValueType}; use crate::sym::{SymTable, Symbol, UserFn, ValueType};
@ -23,9 +24,9 @@ use std::env;
pub const QUOTE_DOCSTRING: &str = "takes a single unevaluated tree and returns it as it is: unevaluated."; pub const QUOTE_DOCSTRING: &str = "takes a single unevaluated tree and returns it as it is: unevaluated.";
pub fn quote_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn quote_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() > 1 { if ast.len() > 1 {
Err("do not quote more than one thing at a time".to_string()) Err(start_trace(("quote", "do not quote more than one thing at a time").into()))
} else { } else {
Ok(*ast.car.clone()) Ok(*ast.car.clone())
} }
@ -36,18 +37,39 @@ Specifically, does one pass of the tree simplification algorithm.
If you have a variable referencing another variable you will get the If you have a variable referencing another variable you will get the
referenced variable."; referenced variable.";
pub fn eval_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn eval_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() > 1 { if ast.len() > 1 {
Err("do not eval more than one thing at a time".to_string()) Err(start_trace(
("eval", "do not eval more than one thing at a time")
.into()))
} else { } else {
match *ast.car { match *ast.car {
Ctr::Seg(ref s) => Ok(*eval(s, syms)?.clone()), Ctr::Seg(ref s) => {
match eval(s, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => Ok(*s.clone()),
}
}
Ctr::Symbol(ref sym) => { Ctr::Symbol(ref sym) => {
let intermediate = syms.call_symbol(sym, &Seg::new(), true)?; let intermediate = syms.call_symbol(sym, &Seg::new(), true);
if let Ctr::Seg(ref s) = *intermediate { if let Err(e) = intermediate {
Ok(*eval(s, syms)?.clone()) return Err(e.with_trace(
("eval", "evaluation failure")
.into()))
}
let res = *intermediate?;
if let Ctr::Seg(ref s) = res {
match eval(s, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => Ok(*s.clone()),
}
} else { } else {
Ok(*intermediate) Ok(res)
} }
}, },
_ => Ok(*ast.car.clone()) _ => Ok(*ast.car.clone())
@ -76,9 +98,9 @@ pub fn eval_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub const HELP_DOCSTRING: &str = "prints help text for a given symbol. Expects only one argument."; pub const HELP_DOCSTRING: &str = "prints help text for a given symbol. Expects only one argument.";
pub fn help_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn help_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 1 { if ast.len() != 1 {
return Err("help only takes a single argument".to_string()); return Err(start_trace(("help", "expected one input").into()));
} }
if let Ctr::Symbol(ref symbol) = *ast.car { if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(ref sym) = syms.get(symbol) { if let Some(ref sym) = syms.get(symbol) {
@ -98,10 +120,10 @@ CURRENT VALUE AND/OR BODY:
sym.name, args_str, sym.docs, sym.value sym.name, args_str, sym.docs, sym.value
); );
} else { } else {
return Err("undefined symbol".to_string()); return Err(start_trace(("help", format!("{symbol} is undefined")).into()));
} }
} else { } else {
return Err("help should only be called on a symbol".to_string()); return Err(start_trace(("help", "expected input to be a symbol").into()));
} }
Ok(Ctr::None) Ok(Ctr::None)
@ -110,9 +132,9 @@ CURRENT VALUE AND/OR BODY:
pub const ISSET_DOCSTRING: &str = "accepts a single argument: a symbol. pub const ISSET_DOCSTRING: &str = "accepts a single argument: a symbol.
returns true or false according to whether or not the symbol is found in the symbol table."; returns true or false according to whether or not the symbol is found in the symbol table.";
pub fn isset_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn isset_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 1 { if ast.len() != 1 {
Err("help only takes a single argument".to_string()) Err(start_trace(("set?", "expcted one input").into()))
} else { } else {
if let Ctr::Symbol(ref symbol) = *ast.car { if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(_) = syms.get(symbol) { if let Some(_) = syms.get(symbol) {
@ -121,7 +143,7 @@ pub fn isset_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::Bool(false)) Ok(Ctr::Bool(false))
} }
} else { } else {
Err("help should only be called on a symbol".to_string()) Err(start_trace(("set?", "expected argument to be a input").into()))
} }
} }
} }
@ -129,12 +151,12 @@ pub fn isset_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub const ENV_DOCSTRING: &str = "takes no arguments pub const ENV_DOCSTRING: &str = "takes no arguments
prints out all available symbols and their associated values"; prints out all available symbols and their associated values";
pub fn env_callback(_ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn env_callback(_ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
// get width of current output // get width of current output
let xdim: i128; let xdim: i128;
if let Ctr::Integer(dim) = *syms if let Ctr::Integer(dim) = *syms
.call_symbol(&CONSOLE_XDIM_VNAME.to_string(), &Seg::new(), true) .call_symbol(&CONSOLE_XDIM_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|_: String| Box::new(Ctr::None)) { .unwrap_or_else(|_: Traceback| Box::new(Ctr::None)) {
xdim = dim; xdim = dim;
} else { } else {
println!("{} contains non integer value, defaulting to {}", println!("{} contains non integer value, defaulting to {}",
@ -218,7 +240,7 @@ which is functionally equivalent to:
pub fn lambda_callback( pub fn lambda_callback(
ast: &Seg, ast: &Seg,
_syms: &mut SymTable _syms: &mut SymTable
) -> Result<Ctr, String> { ) -> Result<Ctr, Traceback> {
let mut args = vec![]; let mut args = vec![];
if let Ctr::Seg(ref arg_head) = *ast.car { if let Ctr::Seg(ref arg_head) = *ast.car {
if !arg_head.circuit(&mut |arg: &Ctr| -> bool { if !arg_head.circuit(&mut |arg: &Ctr| -> bool {
@ -232,7 +254,7 @@ pub fn lambda_callback(
false false
} }
}) { }) {
Err("all elements of first argumnets must be symbols".to_string()) Err(start_trace(("lambda", "lambda inputs should all be symbols").into()))
} else { } else {
if let Ctr::Seg(ref eval_head) = *ast.cdr { if let Ctr::Seg(ref eval_head) = *ast.cdr {
if let Ctr::Seg(_) = *eval_head.car { if let Ctr::Seg(_) = *eval_head.car {
@ -241,14 +263,14 @@ pub fn lambda_callback(
arg_syms: args, arg_syms: args,
})) }))
} else { } else {
Err("function body must be in list form".to_string()) Err(start_trace(("lambda", "expected list of forms for lambda body").into()))
} }
} else { } else {
Err("not enough args".to_string()) Err(start_trace(("lambda", "not enough args").into()))
} }
} }
} else { } else {
Err("first argument should be a list of symbols".to_string()) Err(start_trace(("lambda", "expected list of lambda inputs").into()))
} }
} }
@ -258,15 +280,15 @@ Returns an error if symbol is undefined.
Note: make sure to quote the input like this: Note: make sure to quote the input like this:
(get-doc (quote symbol-name))"; (get-doc (quote symbol-name))";
pub fn getdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn getdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Symbol(ref symbol) = *ast.car { if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(sym) = syms.get(symbol) { if let Some(sym) = syms.get(symbol) {
Ok(Ctr::String(sym.docs.clone())) Ok(Ctr::String(sym.docs.clone()))
} else { } else {
Err("undefined symbol".to_string()) Err(start_trace(("get-doc", "input is undefined").into()))
} }
} else { } else {
Err("get-doc should only be called on a symbol".to_string()) Err(start_trace(("get-doc", "expected input to be a symbol").into()))
} }
} }
@ -276,9 +298,11 @@ Returns an error if symbol is undefined, otherwise sets the symbols docstring to
Note: make sure to quote the input like this: Note: make sure to quote the input like this:
(set-doc (quote symbol-name) my-new-docs)"; (set-doc (quote symbol-name) my-new-docs)";
pub fn setdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn setdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 2 { if ast.len() != 2 {
Err("set-doc only takes two arguments".to_string()) Err(start_trace(
("set-doc", "expected two inputs")
.into()))
} else { } else {
if let Ctr::Symbol(ref symbol) = *ast.car { if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(mut sym) = syms.remove(symbol) { if let Some(mut sym) = syms.remove(symbol) {
@ -289,17 +313,25 @@ pub fn setdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::None) Ok(Ctr::None)
} else { } else {
syms.insert(sym.name.clone(), sym); syms.insert(sym.name.clone(), sym);
Err("second arg must be a string".to_string()) Err(start_trace(
("set-doc", "expected second input to be a string")
.into()))
} }
} else { } else {
Err("impossible: not a second arg".to_string()) Err(start_trace(
("set-doc", "missing second input somehow")
.into()))
} }
} else { } else {
Err("undefined symbol".to_string()) Err(start_trace(
("set-doc", format!("{symbol} is undefined"))
.into()))
} }
} else { } else {
Err("first argument must be a symbol".to_string()) Err(start_trace(
("set-doc", "first input must be a symbol")
.into()))
} }
} }
} }
@ -321,7 +353,7 @@ Additionally, passing a tree as a name will trigger def to evaluate the tree and
a value from it. If it does not return a "; a value from it. If it does not return a ";
pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<Ctr, String> { pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<Ctr, Traceback> {
let is_var = ast.len() == 3; let is_var = ast.len() == 3;
let name: String; let name: String;
let docs: String; let docs: String;
@ -329,15 +361,21 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
match *ast.car { match *ast.car {
Ctr::String(ref s) => name = s.clone(), Ctr::String(ref s) => name = s.clone(),
Ctr::Symbol(ref s) => name = s.clone(), Ctr::Symbol(ref s) => name = s.clone(),
Ctr::Seg(ref s) => match *eval(s, syms)? { Ctr::Seg(ref s) => match eval(s, syms) {
Err(e) => return Err(e.with_trace(("def", "failed to evaluate symbol name").into())),
Ok(s) => match *s {
Ctr::String(ref s) => name = s.clone(), Ctr::String(ref s) => name = s.clone(),
Ctr::Symbol(ref s) => name = s.clone(), Ctr::Symbol(ref s) => name = s.clone(),
_ => { _ => {
println!("{}", *eval(s, syms)?); return Err(start_trace(
return Err("evaluated symbol name doesnt make sense".to_string()); ("def", "expected symbol name input to evaluate to a symbol or a string")
.into()));
}, },
}
}, },
_ => return Err("symbol name doesnt make sense".to_string()), _ => return Err(start_trace(
("def", "expected a string or a symbol as input for symbol name")
.into()))
} }
// remove var case // remove var case
@ -350,7 +388,7 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
return Ok(Ctr::None) return Ok(Ctr::None)
} else { } else {
if ast.len() < 3 || ast.len() > 4 { if ast.len() < 3 || ast.len() > 4 {
return Err("expected 3 or 4 args".to_string()) return Err(start_trace(("def", "expected 3 or 4 inputs").into()))
} }
} }
@ -358,25 +396,31 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
iter = s; iter = s;
} else { } else {
return Err("not enough args".to_string()) return Err(start_trace(("def", "not enough inputs").into()))
} }
match *iter.car { match *iter.car {
Ctr::String(ref s) => docs = s.clone(), Ctr::String(ref s) => docs = s.clone(),
Ctr::Symbol(ref s) => { Ctr::Symbol(ref s) => match syms.call_symbol(s, &Seg::new(), true) {
if let Ctr::String(doc) = *syms.call_symbol(&s, &Seg::new(), true)? { Ok(d) => if let Ctr::String(doc) = *d {
docs = doc.clone(); docs = doc.clone();
} else { } else {
return Err("docs argument does not evaluate to a string".to_string()) return Err(start_trace(("def", "expected docs input to evaluate to a string").into()))
}
}, },
_ => return Err("docs argument does not evaluate to a string".to_string())
Err(e) => return Err(e.with_trace(
("def", "couldnt evaluate docs form")
.into()))
},
_ => return Err(start_trace(
("def", "expected docs input to at least evaluate to a string if not be one")
.into()))
} }
if let Ctr::Seg(ref s) = *iter.cdr { if let Ctr::Seg(ref s) = *iter.cdr {
iter = s; iter = s;
} else { } else {
return Err("not enough args".to_string()) return Err(start_trace(("def", "not enough inputs").into()))
} }
let mut outer_scope_val: Seg = Seg::new(); let mut outer_scope_val: Seg = Seg::new();
@ -392,17 +436,23 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
outer_scope_val = Seg::from_mono(Box::new(*iter.car.clone())); outer_scope_val = Seg::from_mono(Box::new(*iter.car.clone()));
var_val_form = &outer_scope_val; var_val_form = &outer_scope_val;
}, },
_ if !is_var => return Err("arg list must at least be a list".to_string()), _ if !is_var => return Err(start_trace(("def", "expected a list of inputs").into())),
_ => unimplemented!(), // rustc is haunted and cursed _ => unimplemented!(), // rustc is haunted and cursed
} }
if is_var { if is_var {
let var_val: Ctr; let var_val: Ctr;
let var_eval_result = *eval(var_val_form, syms)?; let var_eval_result = eval(var_val_form, syms);
match var_eval_result { if let Err(e) = var_eval_result {
return Err(e.with_trace(
("def", format!("couldnt evaluate {var_val_form}"))
.into()))
}
let var_eval_final = *var_eval_result?;
match var_eval_final {
Ctr::Seg(ref s) if expand => var_val = *s.car.clone(), Ctr::Seg(ref s) if expand => var_val = *s.car.clone(),
Ctr::Seg(ref s) if !expand => var_val = Ctr::Seg(s.clone()), Ctr::Seg(ref s) if !expand => var_val = Ctr::Seg(s.clone()),
_ => var_val = var_eval_result, _ => var_val = var_eval_final,
} }
let outer_seg = Seg::from_mono(Box::new(var_val.clone())); let outer_seg = Seg::from_mono(Box::new(var_val.clone()));
@ -438,7 +488,9 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
false false
} }
}) { }) {
return Err("all arguments defined for function must be of type symbol".to_string()) return Err(start_trace(
("def", "all inputs to function must be of type symbol")
.into()))
} }
if let Ctr::Seg(ref eval_bodies) = *iter.cdr { if let Ctr::Seg(ref eval_bodies) = *iter.cdr {
@ -452,6 +504,8 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
); );
Ok(Ctr::None) Ok(Ctr::None)
} else { } else {
Err("expected one or more bodies to evaluate in function".to_string()) Err(start_trace(
("def", "expected one or more forms to evaluate in function body")
.into()))
} }
} }

View file

@ -16,6 +16,7 @@
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::{SymTable, ValueType}; use crate::sym::{SymTable, ValueType};
use crate::error::{Traceback, start_trace};
fn isnumeric(arg: &Ctr) -> bool { fn isnumeric(arg: &Ctr) -> bool {
match arg { match arg {
@ -30,7 +31,7 @@ pub const ADD_DOCSTRING: &str =
Adds each arg up to a final result. WARNING: does not acocunt for under/overflows. Adds each arg up to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled."; Consult source code for a better understanding of how extreme values will be handled.";
pub fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let mut res = Ctr::Integer(0); let mut res = Ctr::Integer(0);
let mut culprit: Ctr = Ctr::None; let mut culprit: Ctr = Ctr::None;
let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool { let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool {
@ -44,7 +45,9 @@ pub fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
}); });
if !type_consistent { if !type_consistent {
Err(format!("{} is not a number!", culprit)) Err(start_trace(
("add", format!("{} is not a number!", culprit))
.into()))
} else { } else {
Ok(res) Ok(res)
} }
@ -54,9 +57,11 @@ pub const SUB_DOCSTRING: &str = "traverses over N args, which must all evaluate
Subtracts each arg from the first leading to a final result. WARNING: does not acocunt for under/overflows. Subtracts each arg from the first leading to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled."; Consult source code for a better understanding of how extreme values will be handled.";
pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
if !isnumeric(&*ast.car) { if !isnumeric(ast.car.as_ref()) {
return Err(format!("{} is not a number", &*ast.car)); return Err(start_trace(
("sub", format!("{} is not a number!", ast.car.as_ref()))
.into()))
} }
let mut res = *ast.car.clone(); let mut res = *ast.car.clone();
let mut culprit: Ctr = Ctr::None; let mut culprit: Ctr = Ctr::None;
@ -72,12 +77,17 @@ pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
}); });
if !type_consistent { if !type_consistent {
Err(format!("{} is not a number", culprit)) Err(start_trace(
("sub", format!("{} is not a number!", culprit))
.into()))
} else { } else {
Ok(res) Ok(res)
} }
} else { } else {
Err("requires at least two operands".to_string()) Err(start_trace(
("sub", "expected at least two inputs")
.into()))
} }
} }
@ -85,20 +95,26 @@ pub const DIV_DOCSTRING: &str = "takes two args, which must both evaluate to an
divides arg1 by arg2. WARNING: does not acocunt for under/overflows or float precision. divides arg1 by arg2. WARNING: does not acocunt for under/overflows or float precision.
Consult source code for a better understanding of how extreme values will be handled."; Consult source code for a better understanding of how extreme values will be handled.";
pub fn div_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn div_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone(); let first = *ast.car.clone();
if !isnumeric(&first) { if !isnumeric(&first) {
return Err("first argument must be numeric".to_string()); return Err(start_trace(
("div", format!("{} is not a number!", ast.car.as_ref()))
.into()))
} }
let second: Ctr; let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone(); second = *s.car.clone();
if !isnumeric(&second) { if !isnumeric(&second) {
return Err("second argument must be numeric".to_string()); return Err(start_trace(
("div", format!("{} is not a number!", second))
.into()))
} }
Ok(first / second) Ok(first / second)
} else { } else {
Err("impossible error: needs two arguments".to_string()) Err(start_trace(
("div", "expected exactly two inputs")
.into()))
} }
} }
@ -107,7 +123,7 @@ pub const MUL_DOCSTRING: &str =
Multiplies each arg up to a final result. WARNING: does not acocunt for under/overflows. Multiplies each arg up to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled."; Consult source code for a better understanding of how extreme values will be handled.";
pub fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let mut res = Ctr::Integer(1); let mut res = Ctr::Integer(1);
let mut culprit: Ctr = Ctr::None; let mut culprit: Ctr = Ctr::None;
let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool { let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool {
@ -121,7 +137,9 @@ pub fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
}); });
if !type_consistent { if !type_consistent {
Err(format!("{} is not a number!", culprit)) Err(start_trace(
("mul", format!("{} is not a number!", culprit))
.into()))
} else { } else {
Ok(res) Ok(res)
} }
@ -132,19 +150,23 @@ This will work for a float or a potentially a string.
If the cast to Integer fails, it will return Nothing and print an error. If the cast to Integer fails, it will return Nothing and print an error.
Casting a float to an int will drop its decimal."; Casting a float to an int will drop its decimal.";
pub fn intcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn intcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
// special case for float // special case for float
if let Ctr::Float(f) = *ast.car { if let Ctr::Float(f) = *ast.car {
Ok(Ctr::Integer(f as i128)) Ok(Ctr::Integer(f as i128))
} else if let Ctr::String(ref s) = *ast.car { } else if let Ctr::String(ref s) = *ast.car {
let int = str::parse::<i128>(s); let int = str::parse::<i128>(s);
if int.is_err() { if int.is_err() {
Err(int.err().unwrap().to_string()) Err(start_trace(
("int", int.err().unwrap().to_string())
.into()))
} else { } else {
Ok(Ctr::Integer(int.ok().unwrap())) Ok(Ctr::Integer(int.ok().unwrap()))
} }
} else { } else {
Err("int cast only takes a float or a string".to_string()) Err(start_trace(
("int", "expected a float or a string")
.into()))
} }
} }
@ -153,19 +175,23 @@ This will work for an integer or potentially a string.
If the cast to integer fails, this function will return nothing and print an error. If the cast to integer fails, this function will return nothing and print an error.
Casting an integer to a float can result in bad behaviour since float nodes are based on 64bit floats and int nodes are based on 128 bit integers."; Casting an integer to a float can result in bad behaviour since float nodes are based on 64bit floats and int nodes are based on 128 bit integers.";
pub fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
// special case for float // special case for float
if let Ctr::Integer(i) = *ast.car { if let Ctr::Integer(i) = *ast.car {
Ok(Ctr::Float(i as f64)) Ok(Ctr::Float(i as f64))
} else if let Ctr::String(ref s) = *ast.car { } else if let Ctr::String(ref s) = *ast.car {
let int = str::parse::<f64>(&s); let flt = str::parse::<f64>(&s);
if int.is_err() { if flt.is_err() {
Err(int.err().unwrap().to_string()) Err(start_trace(
("float", flt.err().unwrap().to_string())
.into()))
} else { } else {
Ok(Ctr::Float(int.ok().unwrap())) Ok(Ctr::Float(flt.ok().unwrap()))
} }
} else { } else {
Err("float cast only takes an integer or a string".to_string()) Err(start_trace(
("float", "expected a string or an integer")
.into()))
} }
} }
@ -178,34 +204,46 @@ PANIC CASES:
- an integer exceeding the size of a float64 is raised to a float power - an integer exceeding the size of a float64 is raised to a float power
- an integer is rased to the power of another integer exceeding the max size of an unsigned 32bit integer"; - an integer is rased to the power of another integer exceeding the max size of an unsigned 32bit integer";
pub fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone(); let first = *ast.car.clone();
if !isnumeric(&first) { if !isnumeric(&first) {
return Err("first argument must be numeric".to_string()); return Err(start_trace(
("exp", format!("{} is not a number!", first))
.into()))
} }
let second: Ctr; let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone(); second = *s.car.clone();
} else { } else {
return Err("impossible error: needs two arguments".to_string()); return Err(start_trace(
("exp", "expected at least two inputs")
.into()))
} }
if !isnumeric(&second) { if !isnumeric(&second) {
return Err("second argument must be numeric".to_string()); return Err(start_trace(
("exp", format!("{} is not a number!", second))
.into()))
} }
match first { match first {
Ctr::Float(lf) => match second { Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(lf, rf))), Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(lf, rf))),
Ctr::Integer(ri) => Ok(Ctr::Float(f64::powi(lf, ri as i32))), Ctr::Integer(ri) => Ok(Ctr::Float(f64::powi(lf, ri as i32))),
_ => Err("exp not implemented for these arguments".to_string()), _ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
}, },
Ctr::Integer(li) => match second { Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(li as f64, rf))), Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(li as f64, rf))),
Ctr::Integer(ri) => Ok(Ctr::Integer(li.pow(ri as u32))), Ctr::Integer(ri) => Ok(Ctr::Integer(li.pow(ri as u32))),
_ => Err("exp not implemented for these arguments".to_string()), _ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
}, },
_ => Err("exp not implemented for these arguments".to_string()), _ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
} }
} }
@ -218,19 +256,25 @@ PANIC CASES:
- An integer larger than a max f64 is modulo a float - An integer larger than a max f64 is modulo a float
"; ";
pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone(); let first = *ast.car.clone();
if !isnumeric(&first) { if !isnumeric(&first) {
return Err("first argument must be numeric".to_string()); return Err(start_trace(
("mod", format!("{} is not a number!", first))
.into()))
} }
let second: Ctr; let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone(); second = *s.car.clone();
} else { } else {
return Err("impossible error: needs two arguments".to_string()); return Err(start_trace(
("mod", "expected at least two inputs")
.into()))
} }
if !isnumeric(&second) { if !isnumeric(&second) {
return Err("second argument must be numeric".to_string()); return Err(start_trace(
("mod", format!("{} is not a number!", second))
.into()))
} }
let mut ret = Seg::new(); let mut ret = Seg::new();
@ -245,7 +289,9 @@ pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
ret.append(Box::new(Ctr::Integer((lf / ri as f64) as i128))); ret.append(Box::new(Ctr::Integer((lf / ri as f64) as i128)));
ret.append(Box::new(Ctr::Integer((lf % ri as f64) as i128))); ret.append(Box::new(Ctr::Integer((lf % ri as f64) as i128)));
} }
_ => return Err("mod not implemented for these arguments".to_string()), _ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
}, },
Ctr::Integer(li) => match second { Ctr::Integer(li) => match second {
Ctr::Float(rf) => { Ctr::Float(rf) => {
@ -256,10 +302,14 @@ pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
ret.append(Box::new(Ctr::Integer(li / ri))); ret.append(Box::new(Ctr::Integer(li / ri)));
ret.append(Box::new(Ctr::Integer(li % ri))); ret.append(Box::new(Ctr::Integer(li % ri)));
} }
_ => return Err("mod not implemented for these arguments".to_string()), _ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
}, },
_ => return Err("mod not implemented for these arguments".to_string()), _ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
} }
Ok(Ctr::Seg(ret)) Ok(Ctr::Seg(ret))
@ -269,34 +319,45 @@ pub const ISGT_DOCSTRING: &str = "takes two args, which must both evaluate to an
Returns true or false according to whether the first argument is bigger than the second argument. Returns true or false according to whether the first argument is bigger than the second argument.
May panic if an integer larger than a max f64 is compared to a float."; May panic if an integer larger than a max f64 is compared to a float.";
pub fn isgt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn isgt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone(); let first = *ast.car.clone();
if !isnumeric(&first) { if !isnumeric(&first) {
return Err("first argument must be numeric".to_string()); return Err(start_trace(
("gt?", format!("{} is not a number!", first))
.into()))
} }
let second: Ctr; let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone(); second = *s.car.clone();
} else { } else {
return Err("impossible error: needs two arguments".to_string()); return Err(start_trace(
} ("gt?", "expected at least two inputs")
.into())) }
if !isnumeric(&second) { if !isnumeric(&second) {
return Err("second argument must be numeric".to_string()); return Err(start_trace(
("gt?", format!("{} is not a number!", second))
.into()))
} }
match first { match first {
Ctr::Float(lf) => match second { Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(lf > rf)), Ctr::Float(rf) => Ok(Ctr::Bool(lf > rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(lf > ri as f64)), Ctr::Integer(ri) => Ok(Ctr::Bool(lf > ri as f64)),
_ => Err("gt? not implemented for these arguments".to_string()), _ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
}, },
Ctr::Integer(li) => match second { Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(li as f64 > rf)), Ctr::Float(rf) => Ok(Ctr::Bool(li as f64 > rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(li > ri)), Ctr::Integer(ri) => Ok(Ctr::Bool(li > ri)),
_ => Err("gt? not implemented for these arguments".to_string()), _ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
}, },
_ => Err("gt? not implemented for these arguments".to_string()), _ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
} }
} }
@ -304,34 +365,47 @@ pub const ISLT_DOCSTRING: &str = "takes two args, which must both evaluate to an
Returns true or false according to whether the first argument is smaller than the second argument. Returns true or false according to whether the first argument is smaller than the second argument.
May panic if an integer larger than a max f64 is compared to a float."; May panic if an integer larger than a max f64 is compared to a float.";
pub fn islt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> { pub fn islt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone(); let first = *ast.car.clone();
if !isnumeric(&first) { if !isnumeric(&first) {
return Err("first argument must be numeric".to_string()); return Err(start_trace(
("lt?", format!("{} is not a number!", first))
.into()))
} }
let second: Ctr; let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone(); second = *s.car.clone();
} else { } else {
return Err("impossible error: needs two arguments".to_string()); return Err(start_trace(
("lt?", "expected at least two inputs")
.into()))
} }
if !isnumeric(&second) { if !isnumeric(&second) {
return Err("second argument must be numeric".to_string()); return Err(start_trace(
("lt?", format!("{} is not a number!", second))
.into()))
} }
match first { match first {
Ctr::Float(lf) => match second { Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(lf < rf)), Ctr::Float(rf) => Ok(Ctr::Bool(lf < rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(lf < ri as f64)), Ctr::Integer(ri) => Ok(Ctr::Bool(lf < ri as f64)),
_ => Err("gt? not implemented for these arguments".to_string()), _ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
}, },
Ctr::Integer(li) => match second { Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool((li as f64) < rf)), Ctr::Float(rf) => Ok(Ctr::Bool((li as f64) < rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(li < ri)), Ctr::Integer(ri) => Ok(Ctr::Bool(li < ri)),
_ => Err("gt? not implemented for these arguments".to_string()), _ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
}, },
_ => Err("gt? not implemented for these arguments".to_string()), _ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
} }
} }
@ -339,11 +413,16 @@ pub const ISGTE_DOCSTRING: &str = "takes two args, which must both evaluate to a
Returns true or false according to whether the first argument is greater than or equal to the second argument. Returns true or false according to whether the first argument is greater than or equal to the second argument.
May panic if an integer larger than a max f64 is compared to a float."; May panic if an integer larger than a max f64 is compared to a float.";
pub fn isgte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn isgte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Bool(b) = islt_callback(ast, syms)? { match islt_callback(ast, syms) {
Ok(s) => if let Ctr::Bool(b) = s {
Ok(Ctr::Bool(!b)) Ok(Ctr::Bool(!b))
} else { } else {
Err("impossible state: islt returned non-bool".to_string()) Err(start_trace(
("gte?", format!("madness: lt? returned non bool {s}"))
.into()))
},
Err(e) => Err(e.with_trace(("gte?", "error calling lt?").into())),
} }
} }
@ -351,11 +430,16 @@ pub const ISLTE_DOCSTRING: &str = "takes two args, which must both evaluate to a
Returns true or false according to whether the first argument is less than or equal to the second argument. Returns true or false according to whether the first argument is less than or equal to the second argument.
May panic if an integer larger than a max f64 is compared to a float."; May panic if an integer larger than a max f64 is compared to a float.";
pub fn islte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn islte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Bool(b) = isgt_callback(ast, syms)? { match isgt_callback(ast, syms) {
Ok(s) => if let Ctr::Bool(b) = s {
Ok(Ctr::Bool(!b)) Ok(Ctr::Bool(!b))
} else { } else {
Err("impossible state: islt returned non-bool".to_string()) Err(start_trace(
("lte?", format!("madness: gt? returned non bool {s}"))
.into()))
},
Err(e) => Err(e.with_trace(("lte?", "error calling gt?").into())),
} }
} }
@ -368,28 +452,39 @@ This call is similar to the following:
(def counter '' (add counter 1)) (def counter '' (add counter 1))
with the caveat that your docstring is preserved."; with the caveat that your docstring is preserved.";
pub fn inc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn inc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String; let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car { if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone(); var_name = s.clone();
} else { } else {
return Err("argument should be a symbol".to_string()); return Err(start_trace(
("inc", "expected input to be a symbol")
.into()));
} }
let mut sym = syms let sym_ret = syms
.remove(&var_name) .remove(&var_name);
.expect(&format!("symbol {var_name} is not defined")); if let None = sym_ret {
return Err(start_trace(
("inc", format!("input ({var_name}) is not defined"))
.into()))
}
let mut sym = sym_ret.unwrap();
if let ValueType::VarForm(ref var) = sym.value { if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Integer(ref b) = **var { if let Ctr::Integer(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b + 1))); sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b + 1)));
} else { } else {
syms.insert(var_name, sym); syms.insert(var_name.clone(), sym);
return Err("can only increment an integer".to_string()); return Err(start_trace(
("inc", format!("expected {var_name} to be an integer"))
.into()));
} }
} else { } else {
syms.insert(var_name, sym); syms.insert(var_name.clone(), sym);
return Err("cannot increment a function".to_string()); return Err(start_trace(
("inc", format!("expected {var_name} to be an integer"))
.into()));
} }
syms.insert(var_name, sym); syms.insert(var_name, sym);
@ -405,28 +500,39 @@ This call is similar to the following:
(def counter '' (sub counter 1)) (def counter '' (sub counter 1))
with the caveat that your docstring is preserved."; with the caveat that your docstring is preserved.";
pub fn dec_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> { pub fn dec_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String; let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car { if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone(); var_name = s.clone();
} else { } else {
return Err("argument should be a symbol".to_string()); return Err(start_trace(
("dec", "expected input to be a symbol")
.into()));
} }
let mut sym = syms let sym_ret = syms
.remove(&var_name) .remove(&var_name);
.expect(&format!("symbol {var_name} is not defined")); if let None = sym_ret {
return Err(start_trace(
("dec", format!("input ({var_name}) is not defined"))
.into()))
}
let mut sym = sym_ret.unwrap();
if let ValueType::VarForm(ref var) = sym.value { if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Integer(ref b) = **var { if let Ctr::Integer(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b - 1))); sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b - 1)));
} else { } else {
syms.insert(var_name, sym); syms.insert(var_name.clone(), sym);
return Err("can only decrement an integer".to_string()); return Err(start_trace(
("dec", format!("expected {var_name} to be an integer"))
.into()));
} }
} else { } else {
syms.insert(var_name, sym); syms.insert(var_name.clone(), sym);
return Err("cannot decrement a function".to_string()); return Err(start_trace(
("dec", format!("expected {var_name} to be an integer"))
.into()));
} }
syms.insert(var_name, sym); syms.insert(var_name, sym);

View file

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

View file

@ -17,13 +17,14 @@
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::SymTable; use crate::sym::SymTable;
use crate::error::{Traceback, start_trace};
use std::io::Write; use std::io::Write;
use std::io; use std::io;
pub const ECHO_DOCSTRING: &str = pub const ECHO_DOCSTRING: &str =
"traverses any number of arguments. Prints their evaluated values on a new line for each."; "traverses any number of arguments. Prints their evaluated values on a new line for each.";
pub fn echo_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn echo_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
ast.circuit(&mut |arg: &Ctr| match arg { ast.circuit(&mut |arg: &Ctr| match arg {
Ctr::String(s) => print!("{}", s) == (), Ctr::String(s) => print!("{}", s) == (),
_ => print!("{}", arg) == (), _ => print!("{}", arg) == (),
@ -36,7 +37,7 @@ pub const CONCAT_DOCSTRING: &str = "Iterates over N args of any type other than
converts each argument to a string. Combines all strings. converts each argument to a string. Combines all strings.
Returns final (combined) string."; Returns final (combined) string.";
pub fn concat_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn concat_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut string = String::from(""); let mut string = String::from("");
if !ast.circuit(&mut |arg: &Ctr| { if !ast.circuit(&mut |arg: &Ctr| {
match arg { match arg {
@ -52,7 +53,9 @@ pub fn concat_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
} }
true true
}) { }) {
eprintln!("dont know what to do witha symbol here") return Err(start_trace(
("concat", "highly suspicious that an input was an unevaluated symbol")
.into()))
} }
return Ok(Ctr::String(string)); return Ok(Ctr::String(string));
} }
@ -61,7 +64,7 @@ pub const STRLEN_DOCSTRING: &str = "Takes a single arg of any type.
Arg is converted to a string if not already a string. Arg is converted to a string if not already a string.
Returns string length of arg."; Returns string length of arg.";
pub fn strlen_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn strlen_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
match &*ast.car { match &*ast.car {
Ctr::Symbol(s) => Ok(Ctr::Integer(s.len() as i128)), Ctr::Symbol(s) => Ok(Ctr::Integer(s.len() as i128)),
Ctr::String(s) => Ok(Ctr::Integer(s.len() as i128)), Ctr::String(s) => Ok(Ctr::Integer(s.len() as i128)),
@ -78,7 +81,7 @@ pub fn strlen_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
pub const STRCAST_DOCSTRING: &str = "Takes a single arg of any type. pub const STRCAST_DOCSTRING: &str = "Takes a single arg of any type.
Arg is converted to a string and returned."; Arg is converted to a string and returned.";
pub fn strcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn strcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
match &*ast.car { match &*ast.car {
Ctr::Symbol(s) => Ok(Ctr::String(s.clone())), Ctr::Symbol(s) => Ok(Ctr::String(s.clone())),
Ctr::String(_) => Ok(*ast.car.clone()), Ctr::String(_) => Ok(*ast.car.clone()),
@ -95,12 +98,14 @@ pub fn strcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
pub const SUBSTR_DOCSTRING: &str = pub const SUBSTR_DOCSTRING: &str =
"Takes two strings. Returns true if string1 contains at least one instance of string2"; "Takes two strings. Returns true if string1 contains at least one instance of string2";
pub fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let parent_str: String; let parent_str: String;
if let Ctr::String(ref s) = *ast.car { if let Ctr::String(ref s) = *ast.car {
parent_str = s.to_string(); parent_str = s.to_string();
} else { } else {
return Err("first argument must be a string".to_string()); return Err(start_trace(
("substr", "expected first input to be a string")
.into()))
} }
let second_arg_obj: &Ctr; let second_arg_obj: &Ctr;
@ -108,13 +113,17 @@ pub fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
second_arg_obj = &*s.car; second_arg_obj = &*s.car;
} else { } else {
return Err("impossible error: needs two arguments".to_string()); return Err(start_trace(
("substr", "expected two inputs")
.into()))
} }
if let Ctr::String(ref s) = &*second_arg_obj { if let Ctr::String(ref s) = &*second_arg_obj {
child_str = s.clone(); child_str = s.clone();
} else { } else {
return Err("second argument must be a string".to_string()); return Err(start_trace(
("substr", "expected second input to be a string")
.into()))
} }
Ok(Ctr::Bool(parent_str.contains(&child_str))) Ok(Ctr::Bool(parent_str.contains(&child_str)))
@ -123,12 +132,14 @@ pub fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
pub const SPLIT_DOCSTRING: &str = "Takes two strings. String 1 is a source string and string 2 is a delimiter. pub const SPLIT_DOCSTRING: &str = "Takes two strings. String 1 is a source string and string 2 is a delimiter.
Returns a list of substrings from string 1 that were found delimited by string 2."; Returns a list of substrings from string 1 that were found delimited by string 2.";
pub fn split_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn split_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let parent_str: String; let parent_str: String;
if let Ctr::String(ref s) = *ast.car { if let Ctr::String(ref s) = *ast.car {
parent_str = s.to_string(); parent_str = s.to_string();
} else { } else {
return Err("first argument must be a string".to_string()); return Err(start_trace(
("split", "expected first input to be a string")
.into()))
} }
let second_arg_obj: &Ctr; let second_arg_obj: &Ctr;
@ -136,13 +147,17 @@ pub fn split_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(ref s) = *ast.cdr { if let Ctr::Seg(ref s) = *ast.cdr {
second_arg_obj = &*s.car; second_arg_obj = &*s.car;
} else { } else {
return Err("impossible error: needs two arguments".to_string()); return Err(start_trace(
("split", "expected two inputs")
.into()))
} }
if let Ctr::String(ref s) = &*second_arg_obj { if let Ctr::String(ref s) = &*second_arg_obj {
delim_str = s.clone(); delim_str = s.clone();
} else { } else {
return Err("second argument must be a string".to_string()); return Err(start_trace(
("split", "expected second input to be a string")
.into()))
} }
let mut ret = Seg::new(); let mut ret = Seg::new();
@ -157,7 +172,7 @@ pub const INPUT_DOCSTRING: &str = "Takes one argument (string) and prints it.
Then prompts for user input. Then prompts for user input.
User input is returned as a string"; User input is returned as a string";
pub fn input_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> { pub fn input_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref s) = *ast.car { if let Ctr::String(ref s) = *ast.car {
print!("{}", s); print!("{}", s);
let _= io::stdout().flush(); let _= io::stdout().flush();
@ -165,6 +180,8 @@ pub fn input_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
io::stdin().read_line(&mut input).expect("couldnt read user input"); io::stdin().read_line(&mut input).expect("couldnt read user input");
Ok(Ctr::String(input.trim().to_string())) Ok(Ctr::String(input.trim().to_string()))
} else { } else {
Err("impossible: arg not string".to_string()) return Err(start_trace(
("input", "expected a string input to prompt user with")
.into()))
} }
} }

View file

@ -16,6 +16,7 @@
*/ */
use crate::eval::eval; use crate::eval::eval;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg, Type}; use crate::segment::{Ctr, Seg, Type};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
@ -39,7 +40,7 @@ pub struct UserFn {
*/ */
#[derive(Clone)] #[derive(Clone)]
pub enum ValueType { pub enum ValueType {
Internal(Rc<dyn Fn(&Seg, &mut SymTable) -> Result<Ctr, String>>), Internal(Rc<dyn Fn(&Seg, &mut SymTable) -> Result<Ctr, Traceback>>),
FuncForm(UserFn), FuncForm(UserFn),
VarForm(Box<Ctr>), VarForm(Box<Ctr>),
} }
@ -127,10 +128,13 @@ impl SymTable {
name: &String, name: &String,
args: &Seg, args: &Seg,
call_func: bool, call_func: bool,
) -> Result<Box<Ctr>, String> { ) -> Result<Box<Ctr>, Traceback> {
let mut symbol = match self.remove(name) { let mut symbol = match self.remove(name) {
Some(s) => s, Some(s) => s,
None => return Err(format!("undefined symbol: {}", name)), None => return Err(
Traceback::new()
.with_trace((name, "(is an undefined symbol)").into())
),
}; };
// will re-increment when inserted // will re-increment when inserted
// but we dont want to increment it // but we dont want to increment it
@ -175,17 +179,21 @@ impl Default for SymTable {
} }
impl Args { impl Args {
fn validate_inputs(&self, args: &Seg) -> Result<(), String> { fn validate_inputs(&self, args: &Seg, caller: &String) -> Result<(), Traceback> {
match self { match self {
Args::None => { Args::None => {
if args.len() == 1 { if args.len() == 1 {
if let Ctr::None = *args.car { if let Ctr::None = *args.car {
return Ok(()); return Ok(());
} else { } else {
return Err("expected no args".to_string()); return Err(start_trace(
(caller, "expected no args")
.into()))
} }
} else { } else {
return Err("expected no args".to_string()); return Err(start_trace(
(caller, "expected no args")
.into()))
} }
} }
@ -193,7 +201,9 @@ impl Args {
if !args.is_empty() { if !args.is_empty() {
return Ok(()); return Ok(());
} else { } else {
return Err("expected args but none were provided".to_string()); return Err(start_trace(
(caller, "expected args, but none were provided")
.into()))
} }
} }
@ -203,12 +213,18 @@ impl Args {
if let Ctr::None = *args.car { if let Ctr::None = *args.car {
//pass //pass
} else { } else {
return Err("expected 0 args. Got one or more.".to_string()); return Err(start_trace(
(caller, "expected no args, got 1 or more")
.into()))
} }
} else if *num != called_arg_count { } else if *num != called_arg_count {
return Err(format!("expected {} args. Got {}.", num, called_arg_count)); return Err(start_trace(
(caller, format!("expected {} args. Got {}.", num, called_arg_count))
.into()))
} else if let Ctr::None = *args.car { } else if let Ctr::None = *args.car {
return Err(format!("expected {} args. Got 0.", num,)); return Err(start_trace(
(caller, format!("expected {} args. Got 0.", num))
.into()))
} }
} }
@ -231,18 +247,26 @@ impl Args {
}); });
if passes && idx < (arg_types.len() - 1) { if passes && idx < (arg_types.len() - 1) {
return Err(format!("{} too few arguments", arg_types.len() - (idx + 1))); return Err(start_trace(
(caller, format!("{} too few arguments", arg_types.len() - (idx + 1)))
.into()))
} }
if !passes { if !passes {
if mismatch { if mismatch {
return Err(format!("arg {} expected to be {}", idx + 1, arg_types[idx])); return Err(start_trace(
(caller, format!("arg {} expected to be {}", idx + 1, arg_types[idx]))
.into()))
} }
if idx > (arg_types.len() - 1) { if idx > (arg_types.len() - 1) {
return Err("too many arguments".to_string()); return Err(start_trace(
(caller, "too many arguments".to_string())
.into()))
} }
if idx < (arg_types.len() - 1) { if idx < (arg_types.len() - 1) {
return Err("too few arguments".to_string()); return Err(start_trace(
(caller, "too few arguments".to_string())
.into()))
} }
} }
} }
@ -306,10 +330,11 @@ impl Symbol {
/* call /* call
* routine is called by eval when a symbol is expanded * routine is called by eval when a symbol is expanded
*/ */
pub fn call(&self, args: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> { pub fn call(&self, args: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, Traceback> {
let evaluated_args: &Seg; let evaluated_args: &Seg;
let mut outer_scope_seg_storage = Seg::new(); let mut outer_scope_seg_storage = Seg::new();
let mut errcon: String = String::new(); let mut cursor = 0;
let mut errcon: Traceback = Traceback::new();
if !self.conditional_branches { if !self.conditional_branches {
if !args.circuit(&mut |arg: &Ctr| -> bool { if !args.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Seg(ref s) = arg { if let Ctr::Seg(ref s) = arg {
@ -333,16 +358,22 @@ impl Symbol {
} else { } else {
outer_scope_seg_storage.append(Box::new(arg.clone())); outer_scope_seg_storage.append(Box::new(arg.clone()));
} }
cursor += 1;
true true
}) { }) {
return Err(format!("error evaluating args: {}", errcon)) return Err(
errcon.with_trace((
&self.name,
format!("error evaluating arg {cursor}"),
).into()))
} }
evaluated_args = &outer_scope_seg_storage; evaluated_args = &outer_scope_seg_storage;
} else { } else {
evaluated_args = args; evaluated_args = args;
} };
self.args.validate_inputs(evaluated_args, &self.name)?;
self.args.validate_inputs(evaluated_args)?;
match &self.value { match &self.value {
ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())), ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())),
ValueType::Internal(ref f) => Ok(Box::new(f(evaluated_args, syms)?)), ValueType::Internal(ref f) => Ok(Box::new(f(evaluated_args, syms)?)),
@ -377,7 +408,7 @@ impl Symbol {
if let Ctr::Seg(ref data) = *iterate.car { if let Ctr::Seg(ref data) = *iterate.car {
match eval(data, syms) { match eval(data, syms) {
Ok(ctr) => result = ctr, Ok(ctr) => result = ctr,
Err(e) => return Err(format!("evaluation failure\n{}", e)), Err(e) => return Err(e.with_trace((&self.name, "returned error").into())),
} }
} else { } else {
let temp = Seg::from_mono(iterate.car.clone()); let temp = Seg::from_mono(iterate.car.clone());
@ -389,7 +420,9 @@ impl Symbol {
result = ctr; result = ctr;
} }
} }
Err(e) => return Err(format!("evaluation failure\n{}", e)), Err(e) => return Err(
e.with_trace((&self.name, "returned error").into())
),
} }
} }
@ -448,7 +481,7 @@ impl Default for Symbol {
Symbol { Symbol {
value: ValueType::Internal( value: ValueType::Internal(
Rc::new( Rc::new(
|_: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |_: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
unimplemented!() unimplemented!()
} }
) )
@ -466,9 +499,9 @@ pub fn call_lambda(
lam: &UserFn, lam: &UserFn,
args_ctr: &Box<Ctr>, args_ctr: &Box<Ctr>,
syms: &mut SymTable, syms: &mut SymTable,
) -> Result<Box<Ctr>, String> { ) -> Result<Box<Ctr>, Traceback> {
let temp_sym = Symbol { let temp_sym = Symbol {
name: String::from("anonymous"), name: String::from("<lambda>"),
conditional_branches: false, conditional_branches: false,
docs: String::from("user defined lambda"), docs: String::from("user defined lambda"),
args: Args::Lazy(lam.arg_syms.len() as u128), args: Args::Lazy(lam.arg_syms.len() as u128),

View file

@ -81,7 +81,8 @@ mod eval_tests {
let doc_tree = lex(&test_doc).unwrap(); let doc_tree = lex(&test_doc).unwrap();
match eval(&doc_tree, &mut syms) { match eval(&doc_tree, &mut syms) {
Err(e) => { Err(e) => {
assert_eq!(e, "error in call to undefined: undefined symbol: undefined") assert_eq!(e.0.first().unwrap().message,
"(is an undefined symbol)")
} }
Ok(reduced) => { Ok(reduced) => {

View file

@ -1,7 +1,7 @@
mod func_tests { mod func_tests {
use relish::ast::lex; use relish::ast::lex;
use relish::ast::{Args, Ctr, Seg, Symbol, ValueType}; use relish::ast::{Args, Ctr, Seg, Symbol, ValueType, Traceback};
use relish::ast::{SymTable, Type, UserFn}; use relish::ast::{SymTable, Type, UserFn, start_trace};
use std::rc::Rc; use std::rc::Rc;
#[test] #[test]
@ -13,7 +13,7 @@ mod func_tests {
docs: String::new(), docs: String::new(),
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
let inner = a; let inner = a;
let mut is_bool = false; let mut is_bool = false;
if let Ctr::Bool(_) = *inner.car { if let Ctr::Bool(_) = *inner.car {
@ -104,7 +104,7 @@ mod func_tests {
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
docs: String::new(), docs: String::new(),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
let inner = a; let inner = a;
if let Ctr::Bool(b) = *inner.car { if let Ctr::Bool(b) = *inner.car {
if b { if b {
@ -113,7 +113,7 @@ mod func_tests {
Ok(Ctr::None) Ok(Ctr::None)
} }
} else { } else {
Err("not a bool".to_string()) Err(start_trace(("", "not a bool".to_string()).into()))
} }
}, },
)), )),
@ -153,7 +153,7 @@ mod func_tests {
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
docs: String::new(), docs: String::new(),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
let inner = a; let inner = a;
let mut is_bool = false; let mut is_bool = false;
if let Ctr::Bool(_) = *inner.car { if let Ctr::Bool(_) = *inner.car {
@ -170,7 +170,11 @@ mod func_tests {
assert_eq!( assert_eq!(
syms.call_symbol(&"test_func_in".to_string(), &args, true) syms.call_symbol(&"test_func_in".to_string(), &args, true)
.err() .err()
.unwrap(), .unwrap()
.0
.first()
.unwrap()
.message,
"arg 1 expected to be bool".to_string(), "arg 1 expected to be bool".to_string(),
); );
} }
@ -200,7 +204,11 @@ mod func_tests {
assert_eq!( assert_eq!(
syms.call_symbol(&"test_func_in".to_string(), &args, true) syms.call_symbol(&"test_func_in".to_string(), &args, true)
.err() .err()
.unwrap(), .unwrap()
.0
.first()
.unwrap()
.message,
"expected 1 args. Got 2.".to_string(), "expected 1 args. Got 2.".to_string(),
); );
} }
@ -226,7 +234,11 @@ mod func_tests {
assert_eq!( assert_eq!(
syms.call_symbol(&"test_func_in".to_string(), &args, true) syms.call_symbol(&"test_func_in".to_string(), &args, true)
.err() .err()
.unwrap(), .unwrap()
.0
.first()
.unwrap()
.message,
"expected 1 args. Got 0.".to_string(), "expected 1 args. Got 0.".to_string(),
); );
} }
@ -240,7 +252,7 @@ mod func_tests {
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
docs: String::new(), docs: String::new(),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
let inner = a; let inner = a;
let mut is_bool = false; let mut is_bool = false;
if let Ctr::Bool(_) = *inner.car { if let Ctr::Bool(_) = *inner.car {
@ -260,8 +272,12 @@ mod func_tests {
assert_eq!( assert_eq!(
syms.call_symbol(&"test_func_in".to_string(), &args, true) syms.call_symbol(&"test_func_in".to_string(), &args, true)
.err() .err()
.unwrap(), .unwrap()
"error evaluating args: undefined symbol: undefined-symbol".to_string(), .0
.first()
.unwrap()
.message,
"(is an undefined symbol)".to_string(),
); );
} }
} }

View file

@ -17,7 +17,7 @@ mod lex_tests {
fn test_bad_symbol() { fn test_bad_symbol() {
let document = String::from("(as@dd)"); let document = String::from("(as@dd)");
let output: &str = "Problem lexing document: \"Unparsable token: as@dd\""; let output: &str = "Problem lexing document: \"Unparsable token: as@dd\"";
assert_eq!(lex(&document).err().unwrap(), output.to_string(),); assert_eq!(lex(&document).err().unwrap().0.first().unwrap().message, output.to_string(),);
} }
#[test] #[test]
@ -42,14 +42,14 @@ mod lex_tests {
fn test_unmatched_list_delim_flat() { fn test_unmatched_list_delim_flat() {
let document = String::from("(one two"); let document = String::from("(one two");
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
assert_eq!(lex(&document).err().unwrap(), output.to_string(),); assert_eq!(lex(&document).err().unwrap().0.first().unwrap().message, output.to_string(),);
} }
#[test] #[test]
fn test_unmatched_list_delim_complex() { fn test_unmatched_list_delim_complex() {
let document = String::from("(one two (three)"); let document = String::from("(one two (three)");
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
assert_eq!(lex(&document).err().unwrap(), output.to_string(),); assert_eq!(lex(&document).err().unwrap().0.first().unwrap().message, output.to_string(),);
} }
#[test] #[test]
@ -100,6 +100,6 @@ mod lex_tests {
fn test_bad_token_list() { fn test_bad_token_list() {
let document = String::from("(one t(wo)"); let document = String::from("(one t(wo)");
let output: &str = "Problem lexing document: \"list started in middle of another token\""; let output: &str = "Problem lexing document: \"list started in middle of another token\"";
assert_eq!(lex(&document).err().unwrap(), output.to_string(),); assert_eq!(lex(&document).err().unwrap().0.first().unwrap().message, output.to_string(),);
} }
} }

View file

@ -132,8 +132,11 @@ mod append_lib_tests {
*eval(&lex(&document.to_string()).unwrap(), &mut syms) *eval(&lex(&document.to_string()).unwrap(), &mut syms)
.err() .err()
.unwrap() .unwrap()
.to_string(), .0
"error in call to car: argument is empty".to_string(), .first()
.unwrap()
.message,
"input is empty".to_string(),
); );
} }
@ -166,8 +169,11 @@ mod append_lib_tests {
*eval(&lex(&document.to_string()).unwrap(), &mut syms) *eval(&lex(&document.to_string()).unwrap(), &mut syms)
.err() .err()
.unwrap() .unwrap()
.to_string(), .0
"error in call to cdr: argument is empty".to_string(), .first()
.unwrap()
.message,
"input is empty".to_string(),
); );
} }

View file

@ -163,8 +163,8 @@ mod bool_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
if let Err(s) = eval(&change_tree, &mut syms) { if let Err(s) = eval(&change_tree, &mut syms) {
assert_eq!( assert_eq!(
s, s.0.first().unwrap().message,
"error in call to toggle: can only toggle a boolean".to_string() "can only toggle a boolean".to_string()
); );
let intermediate = *eval(&check_tree, &mut syms).unwrap(); let intermediate = *eval(&check_tree, &mut syms).unwrap();
if let Ctr::Seg(ref s) = intermediate { if let Ctr::Seg(ref s) = intermediate {
@ -196,8 +196,8 @@ mod bool_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
if let Err(s) = eval(&change_tree, &mut syms) { if let Err(s) = eval(&change_tree, &mut syms) {
assert_eq!( assert_eq!(
s, s.0.first().unwrap().message,
"error in call to toggle: cannot toggle a function".to_string() "cannot toggle a function".to_string()
); );
if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() { if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() {
assert_eq!(*s, "1".to_string()); assert_eq!(*s, "1".to_string());

View file

@ -233,8 +233,8 @@ mod control_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
assert_eq!( assert_eq!(
eval(&test_tree, &mut syms).err().unwrap(), eval(&test_tree, &mut syms).err().unwrap().0.first().unwrap().message,
"error in call to result: undefined symbol: result".to_string() "(is an undefined symbol)".to_string()
); );
} }
} }

View file

@ -80,8 +80,8 @@ mod decl_lib_tests {
let eval_result = eval(&lex(&doc3.to_string()).unwrap(), &mut syms); let eval_result = eval(&lex(&doc3.to_string()).unwrap(), &mut syms);
if let Err(s) = eval_result { if let Err(s) = eval_result {
assert_eq!( assert_eq!(
s.to_string(), s.0.first().unwrap().message,
"error in call to test: undefined symbol: test".to_string() "(is an undefined symbol)".to_string()
); );
} else { } else {
assert!(false); assert!(false);

View file

@ -665,8 +665,8 @@ mod math_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
if let Err(s) = eval(&change_tree, &mut syms) { if let Err(s) = eval(&change_tree, &mut syms) {
assert_eq!( assert_eq!(
s, s.0.first().unwrap().message,
"error in call to inc: can only increment an integer".to_string() "expected tester to be an integer".to_string()
); );
let intermediate = *eval(&check_tree, &mut syms).unwrap(); let intermediate = *eval(&check_tree, &mut syms).unwrap();
if let Ctr::Seg(ref s) = intermediate { if let Ctr::Seg(ref s) = intermediate {
@ -698,8 +698,8 @@ mod math_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
if let Err(s) = eval(&change_tree, &mut syms) { if let Err(s) = eval(&change_tree, &mut syms) {
assert_eq!( assert_eq!(
s, s.0.first().unwrap().message,
"error in call to inc: cannot increment a function".to_string() "expected tester to be an integer".to_string()
); );
if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() { if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() {
assert_eq!(*s, "1".to_string()); assert_eq!(*s, "1".to_string());
@ -768,8 +768,8 @@ mod math_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
if let Err(s) = eval(&change_tree, &mut syms) { if let Err(s) = eval(&change_tree, &mut syms) {
assert_eq!( assert_eq!(
s, s.0.first().unwrap().message,
"error in call to dec: can only decrement an integer".to_string() "expected tester to be an integer".to_string()
); );
let intermediate = *eval(&check_tree, &mut syms).unwrap(); let intermediate = *eval(&check_tree, &mut syms).unwrap();
if let Ctr::Seg(ref s) = intermediate { if let Ctr::Seg(ref s) = intermediate {
@ -801,8 +801,8 @@ mod math_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
if let Err(s) = eval(&change_tree, &mut syms) { if let Err(s) = eval(&change_tree, &mut syms) {
assert_eq!( assert_eq!(
s, s.0.first().unwrap().message,
"error in call to dec: cannot decrement a function".to_string() "expected tester to be an integer".to_string()
); );
if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() { if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() {
assert_eq!(*s, "1".to_string()); assert_eq!(*s, "1".to_string());