Merge branch 'error-messaging' into 'main'
Error Messaging Redesign See merge request whom/relish!3
This commit is contained in:
commit
9631f84fc5
24 changed files with 837 additions and 374 deletions
|
|
@ -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.
|
||||
|
||||
** 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)
|
||||
- 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
|
||||
|
|
@ -538,3 +536,6 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
|
|||
- TCP Listener
|
||||
- HTTP 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.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use {
|
|||
ast::{
|
||||
eval, lex, run,
|
||||
Ctr, Seg, SymTable, Symbol,
|
||||
Traceback,
|
||||
},
|
||||
stdlib::{
|
||||
static_stdlib, dynamic_stdlib, load_defaults,
|
||||
|
|
@ -257,7 +258,7 @@ fn main() {
|
|||
// scope the below borrow of syms
|
||||
let cfg_file = env::var(CFG_FILE_VNAME).unwrap_or(cfg_file_name);
|
||||
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));
|
||||
|
||||
|
|
@ -332,19 +333,19 @@ fn main() {
|
|||
fn make_prompt(syms: &mut SymTable) -> CustomPrompt {
|
||||
let l_ctr = *syms
|
||||
.call_symbol(&L_PROMPT_VNAME.to_string(), &Seg::new(), true)
|
||||
.unwrap_or_else(|err: String| {
|
||||
.unwrap_or_else(|err: Traceback| {
|
||||
eprintln!("{}", err);
|
||||
Box::new(Ctr::String("<prompt broken!>".to_string()))
|
||||
});
|
||||
let r_ctr = *syms
|
||||
.call_symbol(&R_PROMPT_VNAME.to_string(), &Seg::new(), true)
|
||||
.unwrap_or_else(|err: String| {
|
||||
.unwrap_or_else(|err: Traceback| {
|
||||
eprintln!("{}", err);
|
||||
Box::new(Ctr::String("<prompt broken!>".to_string()))
|
||||
});
|
||||
let d_ctr = *syms
|
||||
.call_symbol(&PROMPT_DELIM_VNAME.to_string(), &Seg::new(), true)
|
||||
.unwrap_or_else(|err: String| {
|
||||
.unwrap_or_else(|err: Traceback| {
|
||||
eprintln!("{}", err);
|
||||
Box::new(Ctr::String("<prompt broken!>".to_string()))
|
||||
});
|
||||
|
|
|
|||
99
src/error.rs
Normal file
99
src/error.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/eval.rs
19
src/eval.rs
|
|
@ -17,12 +17,13 @@
|
|||
|
||||
use crate::segment::{Ctr, Seg};
|
||||
use crate::sym::{SymTable, call_lambda};
|
||||
use crate::error::Traceback;
|
||||
|
||||
/* iterates over a syntax tree
|
||||
* returns a NEW LIST of values
|
||||
* 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
|
||||
let mut ret = Box::from(Ctr::None);
|
||||
let mut first = true;
|
||||
|
|
@ -42,10 +43,7 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
|
|||
Ctr::Seg(ref inner) => {
|
||||
let interm = eval(inner, syms)?;
|
||||
if let Ctr::Lambda(ref l) = *interm {
|
||||
match call_lambda(l, arg_cdr, syms) {
|
||||
Ok(s) => return Ok(s.clone()),
|
||||
Err(s) => return Err(format!("err in call to lambda: {}", s)),
|
||||
}
|
||||
return call_lambda(l, arg_cdr, syms)
|
||||
} else {
|
||||
car = interm;
|
||||
}
|
||||
|
|
@ -63,11 +61,7 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
|
|||
args = &outer_scope_seg_holder;
|
||||
}
|
||||
|
||||
match syms.call_symbol(tok, args, first) {
|
||||
Ok(s) => car = s,
|
||||
Err(s) => return Err(format!("error in call to {}: {}", tok, s)),
|
||||
}
|
||||
|
||||
car = syms.call_symbol(tok, args, first)?;
|
||||
if let Some(b) = syms.is_function_type(tok) {
|
||||
if b {
|
||||
return Ok(car);
|
||||
|
|
@ -83,10 +77,7 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
|
|||
match **arg_cdr {
|
||||
Ctr::Symbol(ref tok) => {
|
||||
let blank = Seg::new();
|
||||
match syms.call_symbol(tok, &blank, false) {
|
||||
Ok(res) => cdr = res,
|
||||
Err(s) => return Err(format!("error fetching {}: {}", tok, s)),
|
||||
}
|
||||
cdr = syms.call_symbol(tok, &blank, false)?;
|
||||
none = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
11
src/lex.rs
11
src/lex.rs
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
use crate::segment::{Ctr, Seg};
|
||||
use crate::error::{Traceback, start_trace};
|
||||
use phf::{Map, phf_map};
|
||||
|
||||
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
|
||||
* 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() {
|
||||
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
|
||||
|
|
@ -43,7 +46,9 @@ pub fn lex(document: &String) -> Result<Box<Seg>, String> {
|
|||
// TODO: Make multiple forms of Ok()
|
||||
// To represent the multiple passable outcomes
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ mod lex;
|
|||
mod segment;
|
||||
mod stl;
|
||||
mod sym;
|
||||
mod error;
|
||||
|
||||
pub mod ast {
|
||||
pub use crate::run::run;
|
||||
|
|
@ -28,6 +29,7 @@ pub mod ast {
|
|||
pub use crate::lex::lex;
|
||||
pub use crate::segment::{Ctr, Seg, Type};
|
||||
pub use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType};
|
||||
pub use crate::error::{Traceback, start_trace};
|
||||
}
|
||||
|
||||
pub mod stdlib {
|
||||
|
|
|
|||
23
src/run.rs
23
src/run.rs
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
use crate::eval::eval;
|
||||
use crate::lex::lex;
|
||||
use crate::error::{Traceback, start_trace};
|
||||
use crate::segment::{Ctr, Seg};
|
||||
use crate::sym::SymTable;
|
||||
use std::path::Path;
|
||||
|
|
@ -47,10 +48,12 @@ pub fn find_on_path(filename: String) -> Option<String> {
|
|||
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);
|
||||
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 {
|
||||
let script_read = script_read_res.unwrap() + ")";
|
||||
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.
|
||||
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 filename.ends_with(".rls") {
|
||||
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 {
|
||||
let canonical_path_res = fs::canonicalize(filename);
|
||||
if canonical_path_res.is_err() {
|
||||
return Err(canonical_path_res
|
||||
return Err(start_trace(
|
||||
("<call script>", canonical_path_res
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string());
|
||||
.to_string())
|
||||
.into()))
|
||||
}
|
||||
let canonical_path = canonical_path_res.ok().unwrap();
|
||||
return run(
|
||||
|
|
@ -85,9 +90,13 @@ pub fn run_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
).and(Ok(Ctr::None))
|
||||
}
|
||||
} else {
|
||||
return Err("binary called, unimplemented!".to_string())
|
||||
return Err(start_trace(
|
||||
("<call script>", "binary called, unimplemented!")
|
||||
.into()))
|
||||
}
|
||||
} else {
|
||||
Err("impossible: not a string".to_string())
|
||||
Err(start_trace(
|
||||
("<call script>", "impossible: not a string")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
src/stl.rs
13
src/stl.rs
|
|
@ -18,6 +18,7 @@
|
|||
use crate::segment::{Ctr, Seg, Type};
|
||||
use crate::run::{run_callback, RUN_DOCSTRING};
|
||||
use crate::sym::{Args, SymTable, Symbol, ValueType};
|
||||
use crate::error::Traceback;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
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_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()))
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
|
|
@ -643,7 +644,7 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
|
|||
//get CFG_RELISH_ENV from syms
|
||||
let env_cfg_user_form = syms
|
||||
.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()
|
||||
.eq("true");
|
||||
|
||||
|
|
@ -656,7 +657,7 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
|
|||
conditional_branches: true,
|
||||
docs: decl::STORE_DOCSTRING.to_string(),
|
||||
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)
|
||||
},
|
||||
)),
|
||||
|
|
@ -668,7 +669,7 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
|
|||
if let Some(shell_state) = shell {
|
||||
let posix_cfg_user_form = syms
|
||||
.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()
|
||||
.eq("true");
|
||||
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@
|
|||
|
||||
use crate::segment::{Ctr, Seg};
|
||||
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.
|
||||
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 {
|
||||
let mut temp = s.clone();
|
||||
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.
|
||||
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::None = *s.car {
|
||||
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))
|
||||
}
|
||||
} 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.
|
||||
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::None = *s.car {
|
||||
Err("argument is empty".to_string())
|
||||
Err(start_trace(("len", "input is empty").into()))
|
||||
} else {
|
||||
Ok(*s.car.clone())
|
||||
}
|
||||
} 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.
|
||||
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 {
|
||||
let mut ret = &s.car;
|
||||
let mut iter = &s.cdr;
|
||||
|
|
@ -95,12 +96,12 @@ pub fn cdr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
iter = &next.cdr;
|
||||
}
|
||||
if let Ctr::None = **ret {
|
||||
Err("argument is empty".to_string())
|
||||
Err(start_trace(("cdr", "input is empty").into()))
|
||||
} else {
|
||||
Ok(*ret.clone())
|
||||
}
|
||||
} 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.";
|
||||
|
||||
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 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();
|
||||
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))
|
||||
} 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.";
|
||||
|
||||
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 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 iter = s;
|
||||
|
|
@ -164,14 +165,14 @@ pub fn dequeue_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
|
|||
|
||||
Ok(Ctr::Seg(ret))
|
||||
} 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.
|
||||
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 {
|
||||
let mut ret = Seg::new();
|
||||
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))
|
||||
} else {
|
||||
Err("Impossible condition: arg not a list".to_string())
|
||||
Err(start_trace(("reverse", "input is not a list").into()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
use crate::segment::{Ctr, Seg};
|
||||
use crate::error::{Traceback, start_trace};
|
||||
use crate::sym::{SymTable, ValueType};
|
||||
|
||||
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.
|
||||
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 cursor = 0;
|
||||
let result = ast.circuit(&mut |arg: &Ctr| -> bool {
|
||||
if let Ctr::Bool(b) = *arg {
|
||||
cursor += 1;
|
||||
b
|
||||
} else {
|
||||
eprintln!("{} is not a boolean", arg);
|
||||
type_error = true;
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
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 {
|
||||
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.
|
||||
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 cursor = 0;
|
||||
let correct_types = ast.circuit(&mut |arg: &Ctr| -> bool {
|
||||
if let Ctr::Bool(b) = *arg {
|
||||
cursor += 1;
|
||||
result = result || b;
|
||||
true
|
||||
} else {
|
||||
eprintln!("{} is not a boolean", arg);
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
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 {
|
||||
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).
|
||||
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 {
|
||||
Ok(Ctr::Bool(!b))
|
||||
} 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.
|
||||
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;
|
||||
Ok(Ctr::Bool(
|
||||
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.
|
||||
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;
|
||||
if let Ctr::Symbol(ref s) = *ast.car {
|
||||
var_name = s.clone();
|
||||
} 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
|
||||
|
|
@ -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)));
|
||||
} else {
|
||||
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 {
|
||||
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);
|
||||
|
|
@ -124,7 +127,7 @@ attempts to cast argument to a bool.
|
|||
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.";
|
||||
|
||||
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 {
|
||||
Ctr::Bool(_) => Ok(*ast.car.clone()),
|
||||
Ctr::String(s) => {
|
||||
|
|
@ -133,12 +136,12 @@ pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
|
|||
} else if s == "false" {
|
||||
Ok(Ctr::Bool(false))
|
||||
} 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::Float(f) => Ok(Ctr::Bool(*f == 0.0)),
|
||||
_ => Err(format!("cannot convert a {} to a boolean",
|
||||
ast.car.to_type())),
|
||||
_ => Err(start_trace(("bool", format!("cannot convert a {} to a boolean",
|
||||
ast.car.to_type())).into())),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
use crate::eval::eval;
|
||||
use crate::error::{Traceback, start_trace};
|
||||
use crate::segment::{Ctr, Seg};
|
||||
use crate::sym::{SymTable, Symbol};
|
||||
|
||||
|
|
@ -29,47 +30,63 @@ example: (if my-state-switch
|
|||
(do-my-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;
|
||||
match *ast.car {
|
||||
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;
|
||||
} 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) => {
|
||||
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;
|
||||
} 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,
|
||||
_ => 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;
|
||||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
then_form = s;
|
||||
} else {
|
||||
return Err("impossible condition: not enough args to if".to_string());
|
||||
return Err(start_trace(("if", "not enough args").into()));
|
||||
}
|
||||
|
||||
if cond {
|
||||
// then
|
||||
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_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 {
|
||||
Ok(*s.car.clone())
|
||||
} 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
|
||||
if let Ctr::Seg(ref else_form) = *then_form.cdr {
|
||||
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_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 {
|
||||
Ok(*s.car.clone())
|
||||
} else {
|
||||
Err("impossible condition: list evals to non list".to_string())
|
||||
Err(start_trace(("if", "impossible condition: list evaluates to non list")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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.
|
||||
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 locals = vec![];
|
||||
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 {
|
||||
locals_form = locals_form_list;
|
||||
} 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 {
|
||||
eval_forms = eval_forms_head;
|
||||
} 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
|
||||
if !locals_form.circuit(&mut |var_decl: &Ctr| -> bool {
|
||||
if let Ctr::Seg(ref var_form) = *var_decl {
|
||||
if let Ctr::Symbol(ref name) = *var_form.car {
|
||||
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 {
|
||||
var_val_res = eval(val_form, &mut localsyms);
|
||||
} else {
|
||||
|
|
@ -145,7 +173,10 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -162,21 +193,27 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
// nothing to declare
|
||||
return true;
|
||||
} 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;
|
||||
}
|
||||
} else {
|
||||
eprintln!("improper declaration form: {}", var_decl);
|
||||
err_trace = start_trace(
|
||||
("let", format!("improper declaration form: {}", var_decl))
|
||||
.into());
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
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 {
|
||||
res = eval(&eval_tree, &mut localsyms);
|
||||
} else {
|
||||
|
|
@ -192,15 +229,19 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
}
|
||||
|
||||
if let Err(e) = res {
|
||||
eprintln!("{}", e);
|
||||
err_trace = e.with_trace(
|
||||
("let", "evaluation failure")
|
||||
.into());
|
||||
return false;
|
||||
}
|
||||
|
||||
result = res.unwrap().clone();
|
||||
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 {
|
||||
localsyms.remove(&i);
|
||||
|
|
@ -217,12 +258,12 @@ example: (while (check-my-state)
|
|||
(do-thing-2 args)
|
||||
(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 outer_maybe: Seg;
|
||||
let eval_bodies_head: &Seg;
|
||||
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 {
|
||||
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 {
|
||||
eval_bodies_head = eval;
|
||||
} 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 {
|
||||
|
|
@ -255,7 +296,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
break;
|
||||
}
|
||||
} 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 {
|
||||
|
|
@ -271,7 +312,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
result = eval(eval_arg, syms);
|
||||
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";
|
||||
|
||||
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 error: String = String::new();
|
||||
let mut err_trace = Traceback::new();
|
||||
let result = ast.circuit(&mut |form: &Ctr| -> bool {
|
||||
cursor += 1;
|
||||
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);
|
||||
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 {
|
||||
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 {
|
||||
return i==0;
|
||||
} 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,
|
||||
|
||||
_ => 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
|
||||
});
|
||||
|
||||
if !result && !error.is_empty() {
|
||||
Err(format!("circuit stopped at form {cursor}: {error}"))
|
||||
if !result && err_trace.depth() > 0 {
|
||||
Err(err_trace)
|
||||
} else {
|
||||
if !result {
|
||||
eprintln!("circuit stopped at form {cursor}");
|
||||
}
|
||||
Ok(Ctr::Bool(result))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
156
src/stl/decl.rs
156
src/stl/decl.rs
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
use crate::eval::eval;
|
||||
use crate::error::{Traceback, start_trace};
|
||||
use crate::segment::{Ctr, Seg, Type};
|
||||
use crate::stdlib::{CONSOLE_XDIM_VNAME, RELISH_DEFAULT_CONS_WIDTH};
|
||||
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 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 {
|
||||
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 {
|
||||
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
|
||||
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 {
|
||||
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 {
|
||||
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) => {
|
||||
let intermediate = syms.call_symbol(sym, &Seg::new(), true)?;
|
||||
if let Ctr::Seg(ref s) = *intermediate {
|
||||
Ok(*eval(s, syms)?.clone())
|
||||
let intermediate = syms.call_symbol(sym, &Seg::new(), true);
|
||||
if let Err(e) = intermediate {
|
||||
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 {
|
||||
Ok(*intermediate)
|
||||
Ok(res)
|
||||
}
|
||||
},
|
||||
_ => 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 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 {
|
||||
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 Some(ref sym) = syms.get(symbol) {
|
||||
|
|
@ -98,10 +120,10 @@ CURRENT VALUE AND/OR BODY:
|
|||
sym.name, args_str, sym.docs, sym.value
|
||||
);
|
||||
} else {
|
||||
return Err("undefined symbol".to_string());
|
||||
return Err(start_trace(("help", format!("{symbol} is undefined")).into()));
|
||||
}
|
||||
} 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)
|
||||
|
|
@ -110,9 +132,9 @@ CURRENT VALUE AND/OR BODY:
|
|||
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.";
|
||||
|
||||
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 {
|
||||
Err("help only takes a single argument".to_string())
|
||||
Err(start_trace(("set?", "expcted one input").into()))
|
||||
} else {
|
||||
if let Ctr::Symbol(ref symbol) = *ast.car {
|
||||
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))
|
||||
}
|
||||
} 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
|
||||
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
|
||||
let xdim: i128;
|
||||
if let Ctr::Integer(dim) = *syms
|
||||
.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;
|
||||
} else {
|
||||
println!("{} contains non integer value, defaulting to {}",
|
||||
|
|
@ -218,7 +240,7 @@ which is functionally equivalent to:
|
|||
pub fn lambda_callback(
|
||||
ast: &Seg,
|
||||
_syms: &mut SymTable
|
||||
) -> Result<Ctr, String> {
|
||||
) -> Result<Ctr, Traceback> {
|
||||
let mut args = vec![];
|
||||
if let Ctr::Seg(ref arg_head) = *ast.car {
|
||||
if !arg_head.circuit(&mut |arg: &Ctr| -> bool {
|
||||
|
|
@ -232,7 +254,7 @@ pub fn lambda_callback(
|
|||
false
|
||||
}
|
||||
}) {
|
||||
Err("all elements of first argumnets must be symbols".to_string())
|
||||
Err(start_trace(("lambda", "lambda inputs should all be symbols").into()))
|
||||
} else {
|
||||
if let Ctr::Seg(ref eval_head) = *ast.cdr {
|
||||
if let Ctr::Seg(_) = *eval_head.car {
|
||||
|
|
@ -241,14 +263,14 @@ pub fn lambda_callback(
|
|||
arg_syms: args,
|
||||
}))
|
||||
} else {
|
||||
Err("function body must be in list form".to_string())
|
||||
Err(start_trace(("lambda", "expected list of forms for lambda body").into()))
|
||||
}
|
||||
} else {
|
||||
Err("not enough args".to_string())
|
||||
Err(start_trace(("lambda", "not enough args").into()))
|
||||
}
|
||||
}
|
||||
} 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:
|
||||
(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 Some(sym) = syms.get(symbol) {
|
||||
Ok(Ctr::String(sym.docs.clone()))
|
||||
} else {
|
||||
Err("undefined symbol".to_string())
|
||||
Err(start_trace(("get-doc", "input is undefined").into()))
|
||||
}
|
||||
} 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:
|
||||
(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 {
|
||||
Err("set-doc only takes two arguments".to_string())
|
||||
Err(start_trace(
|
||||
("set-doc", "expected two inputs")
|
||||
.into()))
|
||||
} else {
|
||||
if let Ctr::Symbol(ref symbol) = *ast.car {
|
||||
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)
|
||||
} else {
|
||||
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 {
|
||||
Err("impossible: not a second arg".to_string())
|
||||
Err(start_trace(
|
||||
("set-doc", "missing second input somehow")
|
||||
.into()))
|
||||
}
|
||||
} else {
|
||||
Err("undefined symbol".to_string())
|
||||
Err(start_trace(
|
||||
("set-doc", format!("{symbol} is undefined"))
|
||||
.into()))
|
||||
}
|
||||
|
||||
} 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 ";
|
||||
|
||||
|
||||
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 name: 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 {
|
||||
Ctr::String(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::Symbol(ref s) => name = s.clone(),
|
||||
_ => {
|
||||
println!("{}", *eval(s, syms)?);
|
||||
return Err("evaluated symbol name doesnt make sense".to_string());
|
||||
return Err(start_trace(
|
||||
("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
|
||||
|
|
@ -350,7 +388,7 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
|
|||
return Ok(Ctr::None)
|
||||
} else {
|
||||
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 {
|
||||
iter = s;
|
||||
} else {
|
||||
return Err("not enough args".to_string())
|
||||
return Err(start_trace(("def", "not enough inputs").into()))
|
||||
}
|
||||
|
||||
match *iter.car {
|
||||
Ctr::String(ref s) => docs = s.clone(),
|
||||
Ctr::Symbol(ref s) => {
|
||||
if let Ctr::String(doc) = *syms.call_symbol(&s, &Seg::new(), true)? {
|
||||
Ctr::Symbol(ref s) => match syms.call_symbol(s, &Seg::new(), true) {
|
||||
Ok(d) => if let Ctr::String(doc) = *d {
|
||||
docs = doc.clone();
|
||||
} 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 {
|
||||
iter = s;
|
||||
} 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();
|
||||
|
|
@ -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()));
|
||||
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
|
||||
}
|
||||
|
||||
if is_var {
|
||||
let var_val: Ctr;
|
||||
let var_eval_result = *eval(var_val_form, syms)?;
|
||||
match var_eval_result {
|
||||
let var_eval_result = eval(var_val_form, syms);
|
||||
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 = 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()));
|
||||
|
|
@ -438,7 +488,9 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
|
|||
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 {
|
||||
|
|
@ -452,6 +504,8 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
|
|||
);
|
||||
Ok(Ctr::None)
|
||||
} 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()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
256
src/stl/math.rs
256
src/stl/math.rs
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
use crate::segment::{Ctr, Seg};
|
||||
use crate::sym::{SymTable, ValueType};
|
||||
use crate::error::{Traceback, start_trace};
|
||||
|
||||
fn isnumeric(arg: &Ctr) -> bool {
|
||||
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.
|
||||
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 culprit: Ctr = Ctr::None;
|
||||
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 {
|
||||
Err(format!("{} is not a number!", culprit))
|
||||
Err(start_trace(
|
||||
("add", format!("{} is not a number!", culprit))
|
||||
.into()))
|
||||
} else {
|
||||
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.
|
||||
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> {
|
||||
if !isnumeric(&*ast.car) {
|
||||
return Err(format!("{} is not a number", &*ast.car));
|
||||
pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
|
||||
if !isnumeric(ast.car.as_ref()) {
|
||||
return Err(start_trace(
|
||||
("sub", format!("{} is not a number!", ast.car.as_ref()))
|
||||
.into()))
|
||||
}
|
||||
let mut res = *ast.car.clone();
|
||||
let mut culprit: Ctr = Ctr::None;
|
||||
|
|
@ -72,12 +77,17 @@ pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
|
|||
});
|
||||
|
||||
if !type_consistent {
|
||||
Err(format!("{} is not a number", culprit))
|
||||
Err(start_trace(
|
||||
("sub", format!("{} is not a number!", culprit))
|
||||
.into()))
|
||||
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
} 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.
|
||||
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();
|
||||
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;
|
||||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
second = *s.car.clone();
|
||||
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)
|
||||
} 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.
|
||||
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 culprit: Ctr = Ctr::None;
|
||||
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 {
|
||||
Err(format!("{} is not a number!", culprit))
|
||||
Err(start_trace(
|
||||
("mul", format!("{} is not a number!", culprit))
|
||||
.into()))
|
||||
} else {
|
||||
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.
|
||||
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
|
||||
if let Ctr::Float(f) = *ast.car {
|
||||
Ok(Ctr::Integer(f as i128))
|
||||
} else if let Ctr::String(ref s) = *ast.car {
|
||||
let int = str::parse::<i128>(s);
|
||||
if int.is_err() {
|
||||
Err(int.err().unwrap().to_string())
|
||||
Err(start_trace(
|
||||
("int", int.err().unwrap().to_string())
|
||||
.into()))
|
||||
} else {
|
||||
Ok(Ctr::Integer(int.ok().unwrap()))
|
||||
}
|
||||
} 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.
|
||||
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
|
||||
if let Ctr::Integer(i) = *ast.car {
|
||||
Ok(Ctr::Float(i as f64))
|
||||
} else if let Ctr::String(ref s) = *ast.car {
|
||||
let int = str::parse::<f64>(&s);
|
||||
if int.is_err() {
|
||||
Err(int.err().unwrap().to_string())
|
||||
let flt = str::parse::<f64>(&s);
|
||||
if flt.is_err() {
|
||||
Err(start_trace(
|
||||
("float", flt.err().unwrap().to_string())
|
||||
.into()))
|
||||
} else {
|
||||
Ok(Ctr::Float(int.ok().unwrap()))
|
||||
Ok(Ctr::Float(flt.ok().unwrap()))
|
||||
}
|
||||
} 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 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();
|
||||
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;
|
||||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
second = *s.car.clone();
|
||||
} else {
|
||||
return Err("impossible error: needs two arguments".to_string());
|
||||
return Err(start_trace(
|
||||
("exp", "expected at least two inputs")
|
||||
.into()))
|
||||
}
|
||||
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 {
|
||||
Ctr::Float(lf) => match second {
|
||||
Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(lf, rf))),
|
||||
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::Float(rf) => Ok(Ctr::Float(f64::powf(li as f64, rf))),
|
||||
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
|
||||
";
|
||||
|
||||
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();
|
||||
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;
|
||||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
second = *s.car.clone();
|
||||
} else {
|
||||
return Err("impossible error: needs two arguments".to_string());
|
||||
return Err(start_trace(
|
||||
("mod", "expected at least two inputs")
|
||||
.into()))
|
||||
}
|
||||
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();
|
||||
|
|
@ -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)));
|
||||
}
|
||||
_ => 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::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)));
|
||||
}
|
||||
_ => 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))
|
||||
|
|
@ -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.
|
||||
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();
|
||||
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;
|
||||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
second = *s.car.clone();
|
||||
} else {
|
||||
return Err("impossible error: needs two arguments".to_string());
|
||||
}
|
||||
return Err(start_trace(
|
||||
("gt?", "expected at least two inputs")
|
||||
.into())) }
|
||||
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 {
|
||||
Ctr::Float(lf) => match second {
|
||||
Ctr::Float(rf) => Ok(Ctr::Bool(lf > rf)),
|
||||
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::Float(rf) => Ok(Ctr::Bool(li as f64 > rf)),
|
||||
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.
|
||||
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();
|
||||
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;
|
||||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
second = *s.car.clone();
|
||||
} else {
|
||||
return Err("impossible error: needs two arguments".to_string());
|
||||
return Err(start_trace(
|
||||
("lt?", "expected at least two inputs")
|
||||
.into()))
|
||||
}
|
||||
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 {
|
||||
Ctr::Float(lf) => match second {
|
||||
Ctr::Float(rf) => Ok(Ctr::Bool(lf < rf)),
|
||||
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::Float(rf) => Ok(Ctr::Bool((li as f64) < rf)),
|
||||
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.
|
||||
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> {
|
||||
if let Ctr::Bool(b) = islt_callback(ast, syms)? {
|
||||
pub fn isgte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
|
||||
match islt_callback(ast, syms) {
|
||||
Ok(s) => if let Ctr::Bool(b) = s {
|
||||
Ok(Ctr::Bool(!b))
|
||||
} 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.
|
||||
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> {
|
||||
if let Ctr::Bool(b) = isgt_callback(ast, syms)? {
|
||||
pub fn islte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
|
||||
match isgt_callback(ast, syms) {
|
||||
Ok(s) => if let Ctr::Bool(b) = s {
|
||||
Ok(Ctr::Bool(!b))
|
||||
} 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))
|
||||
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;
|
||||
if let Ctr::Symbol(ref s) = *ast.car {
|
||||
var_name = s.clone();
|
||||
} 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
|
||||
.remove(&var_name)
|
||||
.expect(&format!("symbol {var_name} is not defined"));
|
||||
let sym_ret = syms
|
||||
.remove(&var_name);
|
||||
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 Ctr::Integer(ref b) = **var {
|
||||
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b + 1)));
|
||||
} else {
|
||||
syms.insert(var_name, sym);
|
||||
return Err("can only increment an integer".to_string());
|
||||
syms.insert(var_name.clone(), sym);
|
||||
return Err(start_trace(
|
||||
("inc", format!("expected {var_name} to be an integer"))
|
||||
.into()));
|
||||
}
|
||||
} else {
|
||||
syms.insert(var_name, sym);
|
||||
return Err("cannot increment a function".to_string());
|
||||
syms.insert(var_name.clone(), sym);
|
||||
return Err(start_trace(
|
||||
("inc", format!("expected {var_name} to be an integer"))
|
||||
.into()));
|
||||
}
|
||||
|
||||
syms.insert(var_name, sym);
|
||||
|
|
@ -405,28 +500,39 @@ This call is similar to the following:
|
|||
(def counter '' (sub counter 1))
|
||||
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;
|
||||
if let Ctr::Symbol(ref s) = *ast.car {
|
||||
var_name = s.clone();
|
||||
} 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
|
||||
.remove(&var_name)
|
||||
.expect(&format!("symbol {var_name} is not defined"));
|
||||
let sym_ret = syms
|
||||
.remove(&var_name);
|
||||
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 Ctr::Integer(ref b) = **var {
|
||||
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b - 1)));
|
||||
} else {
|
||||
syms.insert(var_name, sym);
|
||||
return Err("can only decrement an integer".to_string());
|
||||
syms.insert(var_name.clone(), sym);
|
||||
return Err(start_trace(
|
||||
("dec", format!("expected {var_name} to be an integer"))
|
||||
.into()));
|
||||
}
|
||||
} else {
|
||||
syms.insert(var_name, sym);
|
||||
return Err("cannot decrement a function".to_string());
|
||||
syms.insert(var_name.clone(), sym);
|
||||
return Err(start_trace(
|
||||
("dec", format!("expected {var_name} to be an integer"))
|
||||
.into()));
|
||||
}
|
||||
|
||||
syms.insert(var_name, sym);
|
||||
|
|
|
|||
175
src/stl/posix.rs
175
src/stl/posix.rs
|
|
@ -24,6 +24,9 @@ use {
|
|||
SymTable, ValueType,
|
||||
Symbol, Args,
|
||||
},
|
||||
error::{
|
||||
Traceback, start_trace,
|
||||
},
|
||||
eval::eval,
|
||||
run,
|
||||
},
|
||||
|
|
@ -284,16 +287,20 @@ examples:
|
|||
(l ping -c ping-count google.com))
|
||||
(l emacs -nw (concat HOME '/.relishrc'))
|
||||
";
|
||||
fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
|
||||
fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
||||
if ast.is_empty() {
|
||||
Err("need at least one argument".to_string())
|
||||
Err(start_trace(
|
||||
("load", "expected at least one input")
|
||||
.into()))
|
||||
} else {
|
||||
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
||||
if args.is_empty() {
|
||||
Err("empty command".to_string())
|
||||
Err(start_trace(
|
||||
("load", "empty command")
|
||||
.into()))
|
||||
} else {
|
||||
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
||||
launch_command(
|
||||
if let Err(e) = launch_command(
|
||||
filepath,
|
||||
&Vec::from(args.make_contiguous()),
|
||||
Stdio::inherit(),
|
||||
|
|
@ -301,10 +308,17 @@ fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Resu
|
|||
Stdio::inherit(),
|
||||
false,
|
||||
state,
|
||||
)?;
|
||||
Ok(Ctr::Integer(state.last_exit_code.into()))
|
||||
) {
|
||||
Err(start_trace(
|
||||
("load", e)
|
||||
.into()))
|
||||
} else {
|
||||
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)
|
||||
(grep '.rs')
|
||||
(tr -d '.rs'))";
|
||||
fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
|
||||
fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
||||
if ast.is_empty() {
|
||||
return Err("need at least one argument".to_string())
|
||||
return Err(start_trace(
|
||||
("pipe", "expected at least one input")
|
||||
.into()))
|
||||
}
|
||||
|
||||
let mut err: String = String::new();
|
||||
|
|
@ -366,7 +382,9 @@ fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Resu
|
|||
false
|
||||
}
|
||||
}) {
|
||||
Err(err)
|
||||
Err(start_trace(
|
||||
("pipe", err)
|
||||
.into()))
|
||||
} else {
|
||||
if lastpid > 0 {
|
||||
if let Some(pos) = state.children
|
||||
|
|
@ -384,10 +402,14 @@ fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Resu
|
|||
println!("{}", String::from_utf8_lossy(&exit.stdout));
|
||||
Ok(Ctr::Integer(state.last_exit_code.into()))
|
||||
} else {
|
||||
Err(format!("lost last child (pid {})", lastpid))
|
||||
Err(start_trace(
|
||||
("pipe", format!("lost last child (pid {})", lastpid))
|
||||
.into()))
|
||||
}
|
||||
} else {
|
||||
Err("impossible error state".to_string())
|
||||
Err(start_trace(
|
||||
("pipe", "impossible error state".to_string())
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -404,13 +426,17 @@ examples:
|
|||
|
||||
Unlike with the normal load function, the load to string function collects stdout output and returns it as a string.
|
||||
";
|
||||
fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
|
||||
fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
||||
if ast.is_empty() {
|
||||
Err("need at least one argument".to_string())
|
||||
Err(start_trace(
|
||||
("load-to-string", "expected at least one input")
|
||||
.into()))
|
||||
} else {
|
||||
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
||||
if args.is_empty() {
|
||||
Err("empty command".to_string())
|
||||
Err(start_trace(
|
||||
("load-to-string", "command is empty")
|
||||
.into()))
|
||||
} else {
|
||||
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
||||
let res = Command::new(filepath).args(args).output();
|
||||
|
|
@ -421,13 +447,19 @@ fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellStat
|
|||
if let Ok(string) = String::from_utf8(output.stdout) {
|
||||
Ok(Ctr::String(string.trim_end().to_string()))
|
||||
} else {
|
||||
Err(format!("couldn't marshall utf-8 command output into a string"))
|
||||
Err(start_trace(
|
||||
("load-to-string", format!("couldn't marshall utf-8 command output into a string"))
|
||||
.into()))
|
||||
}
|
||||
} else {
|
||||
Err(format!("{}", res.err().unwrap()))
|
||||
Err(start_trace(
|
||||
("load-to-string", format!("{}", res.err().unwrap()))
|
||||
.into()))
|
||||
}
|
||||
} else {
|
||||
Err("file not found".to_string())
|
||||
Err(start_trace(
|
||||
("load-to-string", "binary not found on PATH")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -453,9 +485,11 @@ const LOAD_WITH_DOCSTRING: &str = "Takes two arguments.
|
|||
Example invocation:
|
||||
(load-with (('stdout' '/dev/null'))
|
||||
(ping -c 4 google.com))";
|
||||
fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
|
||||
fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
||||
if ast.len() != 2 {
|
||||
Err("exactly two arguments needed".to_string())
|
||||
Err(start_trace(
|
||||
("load-with", "expected two inputs")
|
||||
.into()))
|
||||
} else {
|
||||
if let Ctr::Seg(ref fd_redirect_forms) = *ast.car {
|
||||
let mut stdout: Option<Stdio> = None;
|
||||
|
|
@ -538,7 +572,9 @@ fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) ->
|
|||
false
|
||||
}
|
||||
}) {
|
||||
return Err(e.unwrap())
|
||||
return Err(start_trace(
|
||||
("load-with", e.unwrap())
|
||||
.into()))
|
||||
}
|
||||
|
||||
// now deal with the actual command
|
||||
|
|
@ -547,14 +583,18 @@ fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) ->
|
|||
if let Ctr::Seg(ref command) = *command_forms.car {
|
||||
args = VecDeque::from(args_from_ast(command, syms));
|
||||
} else {
|
||||
return Err("command not list".to_string())
|
||||
return Err(start_trace(
|
||||
("load-with", "expected command input to be a list")
|
||||
.into()))
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
Err("empty command".to_string())
|
||||
Err(start_trace(
|
||||
("load-with", "empty command")
|
||||
.into()))
|
||||
} else {
|
||||
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
||||
launch_command(
|
||||
if let Err(e) = launch_command(
|
||||
filepath,
|
||||
&Vec::from(args.make_contiguous()),
|
||||
stdin.or(Some(Stdio::inherit())).unwrap(),
|
||||
|
|
@ -562,23 +602,34 @@ fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) ->
|
|||
stderr.or(Some(Stdio::inherit())).unwrap(),
|
||||
false,
|
||||
state,
|
||||
)?;
|
||||
) {
|
||||
Err(start_trace(
|
||||
("load-with", e)
|
||||
.into()))
|
||||
} else {
|
||||
Ok(Ctr::Integer(state.last_exit_code.into()))
|
||||
}
|
||||
} else {
|
||||
Err("file not found".to_string())
|
||||
Err(start_trace(
|
||||
("load-with", "binary not found on PATH")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err("second argument expected to be a list of command elements".to_string())
|
||||
Err(start_trace(
|
||||
("load-with", "expected second input to be list of command elements")
|
||||
.into()))
|
||||
}
|
||||
} else {
|
||||
Err("first argument expected to be a list of lists".to_string())
|
||||
Err(start_trace(
|
||||
("load-with", "expected first input to be a list of lists")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Q_DOCSTRING: &str = "returns exit code of last process to be run in posix layer";
|
||||
fn q_callback(_ast: &Seg, _syms: &SymTable, state: &mut ShellState) -> Result<Ctr, String> {
|
||||
fn q_callback(_ast: &Seg, _syms: &SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
||||
Ok(Ctr::Integer(state.last_exit_code.into()))
|
||||
}
|
||||
|
||||
|
|
@ -593,16 +644,20 @@ examples:
|
|||
(bg ping -c4 google.com)
|
||||
(bg vim) ;; vim waits patiently for you to foreground the process with 'fg'
|
||||
";
|
||||
fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
|
||||
fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
||||
if ast.is_empty() {
|
||||
Err("need at least one argument".to_string())
|
||||
Err(start_trace(
|
||||
("bg", "expected at least one input")
|
||||
.into()))
|
||||
} else {
|
||||
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
||||
if args.is_empty() {
|
||||
Err("empty command".to_string())
|
||||
Err(start_trace(
|
||||
("bg", "empty command")
|
||||
.into()))
|
||||
} else {
|
||||
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
||||
launch_command(
|
||||
if let Err(e) = launch_command(
|
||||
filepath,
|
||||
&Vec::from(args.make_contiguous()),
|
||||
Stdio::inherit(),
|
||||
|
|
@ -610,10 +665,17 @@ fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result
|
|||
Stdio::inherit(),
|
||||
true,
|
||||
state,
|
||||
)?;
|
||||
Ok(Ctr::Integer(state.last_exit_code.into()))
|
||||
) {
|
||||
Err(start_trace(
|
||||
("bg", e)
|
||||
.into()))
|
||||
} else {
|
||||
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,
|
||||
_syms: &mut SymTable,
|
||||
shell_state: &mut ShellState
|
||||
) -> Result <Ctr, String> {
|
||||
) -> Result <Ctr, Traceback> {
|
||||
if let Ctr::Integer(i) = *ast.car {
|
||||
make_foreground(i as u32, shell_state)?;
|
||||
Ok(Ctr::None)
|
||||
if let Err(e) = make_foreground(i as u32, shell_state) {
|
||||
Err(start_trace(
|
||||
("fg", e)
|
||||
.into()))
|
||||
} else {
|
||||
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 =
|
||||
"Expects 1 argument (a string). Changes to a new directory";
|
||||
pub fn cd_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
|
||||
pub fn cd_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
|
||||
if let Ctr::String(ref dir) = *ast.car {
|
||||
let dirp = Path::new(dir);
|
||||
if let Err(s) = set_current_dir(&dirp) {
|
||||
Err(format!("{}", s))
|
||||
Err(start_trace(
|
||||
("cd", s.to_string())
|
||||
.into()))
|
||||
} else {
|
||||
Ok(Ctr::None)
|
||||
}
|
||||
} else {
|
||||
Err(format!("impossible err: arg not a string"))
|
||||
Err(start_trace(
|
||||
("cd", "expected input to be a string")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -721,7 +794,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::Infinite,
|
||||
conditional_branches: true,
|
||||
docs: String::from(LOAD_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
load_callback(ast, symtable, &mut shell_state.borrow_mut())
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -735,7 +808,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::Infinite,
|
||||
conditional_branches: true,
|
||||
docs: String::from(LOAD_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
load_callback(ast, symtable, &mut load_ss.borrow_mut())
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -749,7 +822,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::Infinite,
|
||||
conditional_branches: true,
|
||||
docs: String::from(BG_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
bg_callback(ast, symtable, &mut bg_ss.borrow_mut())
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -763,7 +836,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::Strict(vec![Type::Integer]),
|
||||
conditional_branches: true,
|
||||
docs: String::from(FG_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
fg_callback(ast, symtable, &mut fg_ss.borrow_mut())
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -777,7 +850,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::None,
|
||||
conditional_branches: false,
|
||||
docs: String::from(Q_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
q_callback(ast, symtable, &mut q_ss.borrow_mut())
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -791,7 +864,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::Lazy(2),
|
||||
conditional_branches: true,
|
||||
docs: String::from(LOAD_WITH_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
load_with_callback(ast, symtable, &mut lw_ss.borrow_mut())
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -805,7 +878,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::Infinite,
|
||||
conditional_branches: true,
|
||||
docs: String::from(LOAD_TO_STRING_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
// extra nonsense needed to allow nested calls
|
||||
load_to_string_callback(
|
||||
ast,
|
||||
|
|
@ -834,7 +907,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
|
|||
args: Args::Infinite,
|
||||
conditional_branches: true,
|
||||
docs: String::from(PIPE_DOCSTRING),
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
||||
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
pipe_callback(ast, symtable, &mut p_ss.borrow_mut())
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -17,13 +17,14 @@
|
|||
|
||||
use crate::segment::{Ctr, Seg};
|
||||
use crate::sym::SymTable;
|
||||
use crate::error::{Traceback, start_trace};
|
||||
use std::io::Write;
|
||||
use std::io;
|
||||
|
||||
pub const ECHO_DOCSTRING: &str =
|
||||
"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 {
|
||||
Ctr::String(s) => print!("{}", s) == (),
|
||||
_ => 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.
|
||||
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("");
|
||||
if !ast.circuit(&mut |arg: &Ctr| {
|
||||
match arg {
|
||||
|
|
@ -52,7 +53,9 @@ pub fn concat_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
}
|
||||
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));
|
||||
}
|
||||
|
|
@ -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.
|
||||
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 {
|
||||
Ctr::Symbol(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.
|
||||
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 {
|
||||
Ctr::Symbol(s) => Ok(Ctr::String(s.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 =
|
||||
"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;
|
||||
if let Ctr::String(ref s) = *ast.car {
|
||||
parent_str = s.to_string();
|
||||
} 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;
|
||||
|
|
@ -108,13 +113,17 @@ pub fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
second_arg_obj = &*s.car;
|
||||
} 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 {
|
||||
child_str = s.clone();
|
||||
} 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)))
|
||||
|
|
@ -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.
|
||||
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;
|
||||
if let Ctr::String(ref s) = *ast.car {
|
||||
parent_str = s.to_string();
|
||||
} 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;
|
||||
|
|
@ -136,13 +147,17 @@ pub fn split_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
second_arg_obj = &*s.car;
|
||||
} 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 {
|
||||
delim_str = s.clone();
|
||||
} 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();
|
||||
|
|
@ -157,7 +172,7 @@ pub const INPUT_DOCSTRING: &str = "Takes one argument (string) and prints it.
|
|||
Then prompts for user input.
|
||||
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 {
|
||||
print!("{}", s);
|
||||
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");
|
||||
Ok(Ctr::String(input.trim().to_string()))
|
||||
} else {
|
||||
Err("impossible: arg not string".to_string())
|
||||
return Err(start_trace(
|
||||
("input", "expected a string input to prompt user with")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
81
src/sym.rs
81
src/sym.rs
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
use crate::eval::eval;
|
||||
use crate::error::{Traceback, start_trace};
|
||||
use crate::segment::{Ctr, Seg, Type};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
|
@ -39,7 +40,7 @@ pub struct UserFn {
|
|||
*/
|
||||
#[derive(Clone)]
|
||||
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),
|
||||
VarForm(Box<Ctr>),
|
||||
}
|
||||
|
|
@ -127,10 +128,13 @@ impl SymTable {
|
|||
name: &String,
|
||||
args: &Seg,
|
||||
call_func: bool,
|
||||
) -> Result<Box<Ctr>, String> {
|
||||
) -> Result<Box<Ctr>, Traceback> {
|
||||
let mut symbol = match self.remove(name) {
|
||||
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
|
||||
// but we dont want to increment it
|
||||
|
|
@ -175,17 +179,21 @@ impl Default for SymTable {
|
|||
}
|
||||
|
||||
impl Args {
|
||||
fn validate_inputs(&self, args: &Seg) -> Result<(), String> {
|
||||
fn validate_inputs(&self, args: &Seg, caller: &String) -> Result<(), Traceback> {
|
||||
match self {
|
||||
Args::None => {
|
||||
if args.len() == 1 {
|
||||
if let Ctr::None = *args.car {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err("expected no args".to_string());
|
||||
return Err(start_trace(
|
||||
(caller, "expected no args")
|
||||
.into()))
|
||||
}
|
||||
} 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() {
|
||||
return Ok(());
|
||||
} 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 {
|
||||
//pass
|
||||
} 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 {
|
||||
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 {
|
||||
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) {
|
||||
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 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) {
|
||||
return Err("too many arguments".to_string());
|
||||
return Err(start_trace(
|
||||
(caller, "too many arguments".to_string())
|
||||
.into()))
|
||||
}
|
||||
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
|
||||
* 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 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 !args.circuit(&mut |arg: &Ctr| -> bool {
|
||||
if let Ctr::Seg(ref s) = arg {
|
||||
|
|
@ -333,16 +358,22 @@ impl Symbol {
|
|||
} else {
|
||||
outer_scope_seg_storage.append(Box::new(arg.clone()));
|
||||
}
|
||||
cursor += 1;
|
||||
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;
|
||||
} else {
|
||||
evaluated_args = args;
|
||||
}
|
||||
};
|
||||
|
||||
self.args.validate_inputs(evaluated_args, &self.name)?;
|
||||
|
||||
self.args.validate_inputs(evaluated_args)?;
|
||||
match &self.value {
|
||||
ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())),
|
||||
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 {
|
||||
match eval(data, syms) {
|
||||
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 {
|
||||
let temp = Seg::from_mono(iterate.car.clone());
|
||||
|
|
@ -389,7 +420,9 @@ impl Symbol {
|
|||
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 {
|
||||
value: ValueType::Internal(
|
||||
Rc::new(
|
||||
|_: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
|
||||
|_: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
unimplemented!()
|
||||
}
|
||||
)
|
||||
|
|
@ -466,9 +499,9 @@ pub fn call_lambda(
|
|||
lam: &UserFn,
|
||||
args_ctr: &Box<Ctr>,
|
||||
syms: &mut SymTable,
|
||||
) -> Result<Box<Ctr>, String> {
|
||||
) -> Result<Box<Ctr>, Traceback> {
|
||||
let temp_sym = Symbol {
|
||||
name: String::from("anonymous"),
|
||||
name: String::from("<lambda>"),
|
||||
conditional_branches: false,
|
||||
docs: String::from("user defined lambda"),
|
||||
args: Args::Lazy(lam.arg_syms.len() as u128),
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ mod eval_tests {
|
|||
let doc_tree = lex(&test_doc).unwrap();
|
||||
match eval(&doc_tree, &mut syms) {
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod func_tests {
|
||||
use relish::ast::lex;
|
||||
use relish::ast::{Args, Ctr, Seg, Symbol, ValueType};
|
||||
use relish::ast::{SymTable, Type, UserFn};
|
||||
use relish::ast::{Args, Ctr, Seg, Symbol, ValueType, Traceback};
|
||||
use relish::ast::{SymTable, Type, UserFn, start_trace};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
|
|
@ -13,7 +13,7 @@ mod func_tests {
|
|||
docs: String::new(),
|
||||
args: Args::Strict(vec![Type::Bool]),
|
||||
value: ValueType::Internal(Rc::new(
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
let inner = a;
|
||||
let mut is_bool = false;
|
||||
if let Ctr::Bool(_) = *inner.car {
|
||||
|
|
@ -104,7 +104,7 @@ mod func_tests {
|
|||
args: Args::Strict(vec![Type::Bool]),
|
||||
docs: String::new(),
|
||||
value: ValueType::Internal(Rc::new(
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
let inner = a;
|
||||
if let Ctr::Bool(b) = *inner.car {
|
||||
if b {
|
||||
|
|
@ -113,7 +113,7 @@ mod func_tests {
|
|||
Ok(Ctr::None)
|
||||
}
|
||||
} 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]),
|
||||
docs: String::new(),
|
||||
value: ValueType::Internal(Rc::new(
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
let inner = a;
|
||||
let mut is_bool = false;
|
||||
if let Ctr::Bool(_) = *inner.car {
|
||||
|
|
@ -170,7 +170,11 @@ mod func_tests {
|
|||
assert_eq!(
|
||||
syms.call_symbol(&"test_func_in".to_string(), &args, true)
|
||||
.err()
|
||||
.unwrap(),
|
||||
.unwrap()
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.message,
|
||||
"arg 1 expected to be bool".to_string(),
|
||||
);
|
||||
}
|
||||
|
|
@ -200,7 +204,11 @@ mod func_tests {
|
|||
assert_eq!(
|
||||
syms.call_symbol(&"test_func_in".to_string(), &args, true)
|
||||
.err()
|
||||
.unwrap(),
|
||||
.unwrap()
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.message,
|
||||
"expected 1 args. Got 2.".to_string(),
|
||||
);
|
||||
}
|
||||
|
|
@ -226,7 +234,11 @@ mod func_tests {
|
|||
assert_eq!(
|
||||
syms.call_symbol(&"test_func_in".to_string(), &args, true)
|
||||
.err()
|
||||
.unwrap(),
|
||||
.unwrap()
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.message,
|
||||
"expected 1 args. Got 0.".to_string(),
|
||||
);
|
||||
}
|
||||
|
|
@ -240,7 +252,7 @@ mod func_tests {
|
|||
args: Args::Strict(vec![Type::Bool]),
|
||||
docs: String::new(),
|
||||
value: ValueType::Internal(Rc::new(
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
|
||||
|a: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
|
||||
let inner = a;
|
||||
let mut is_bool = false;
|
||||
if let Ctr::Bool(_) = *inner.car {
|
||||
|
|
@ -260,8 +272,12 @@ mod func_tests {
|
|||
assert_eq!(
|
||||
syms.call_symbol(&"test_func_in".to_string(), &args, true)
|
||||
.err()
|
||||
.unwrap(),
|
||||
"error evaluating args: undefined symbol: undefined-symbol".to_string(),
|
||||
.unwrap()
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.message,
|
||||
"(is an undefined symbol)".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ mod lex_tests {
|
|||
fn test_bad_symbol() {
|
||||
let document = String::from("(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]
|
||||
|
|
@ -42,14 +42,14 @@ mod lex_tests {
|
|||
fn test_unmatched_list_delim_flat() {
|
||||
let document = String::from("(one two");
|
||||
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]
|
||||
fn test_unmatched_list_delim_complex() {
|
||||
let document = String::from("(one two (three)");
|
||||
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]
|
||||
|
|
@ -100,6 +100,6 @@ mod lex_tests {
|
|||
fn test_bad_token_list() {
|
||||
let document = String::from("(one t(wo)");
|
||||
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(),);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,8 +132,11 @@ mod append_lib_tests {
|
|||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"error in call to car: argument is empty".to_string(),
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.message,
|
||||
"input is empty".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -166,8 +169,11 @@ mod append_lib_tests {
|
|||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"error in call to cdr: argument is empty".to_string(),
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.message,
|
||||
"input is empty".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,8 +163,8 @@ mod bool_lib_tests {
|
|||
eval(&doc_tree, &mut syms).unwrap();
|
||||
if let Err(s) = eval(&change_tree, &mut syms) {
|
||||
assert_eq!(
|
||||
s,
|
||||
"error in call to toggle: can only toggle a boolean".to_string()
|
||||
s.0.first().unwrap().message,
|
||||
"can only toggle a boolean".to_string()
|
||||
);
|
||||
let intermediate = *eval(&check_tree, &mut syms).unwrap();
|
||||
if let Ctr::Seg(ref s) = intermediate {
|
||||
|
|
@ -196,8 +196,8 @@ mod bool_lib_tests {
|
|||
eval(&doc_tree, &mut syms).unwrap();
|
||||
if let Err(s) = eval(&change_tree, &mut syms) {
|
||||
assert_eq!(
|
||||
s,
|
||||
"error in call to toggle: cannot toggle a function".to_string()
|
||||
s.0.first().unwrap().message,
|
||||
"cannot toggle a function".to_string()
|
||||
);
|
||||
if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() {
|
||||
assert_eq!(*s, "1".to_string());
|
||||
|
|
|
|||
|
|
@ -233,8 +233,8 @@ mod control_lib_tests {
|
|||
|
||||
eval(&doc_tree, &mut syms).unwrap();
|
||||
assert_eq!(
|
||||
eval(&test_tree, &mut syms).err().unwrap(),
|
||||
"error in call to result: undefined symbol: result".to_string()
|
||||
eval(&test_tree, &mut syms).err().unwrap().0.first().unwrap().message,
|
||||
"(is an undefined symbol)".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ mod decl_lib_tests {
|
|||
let eval_result = eval(&lex(&doc3.to_string()).unwrap(), &mut syms);
|
||||
if let Err(s) = eval_result {
|
||||
assert_eq!(
|
||||
s.to_string(),
|
||||
"error in call to test: undefined symbol: test".to_string()
|
||||
s.0.first().unwrap().message,
|
||||
"(is an undefined symbol)".to_string()
|
||||
);
|
||||
} else {
|
||||
assert!(false);
|
||||
|
|
|
|||
|
|
@ -665,8 +665,8 @@ mod math_lib_tests {
|
|||
eval(&doc_tree, &mut syms).unwrap();
|
||||
if let Err(s) = eval(&change_tree, &mut syms) {
|
||||
assert_eq!(
|
||||
s,
|
||||
"error in call to inc: can only increment an integer".to_string()
|
||||
s.0.first().unwrap().message,
|
||||
"expected tester to be an integer".to_string()
|
||||
);
|
||||
let intermediate = *eval(&check_tree, &mut syms).unwrap();
|
||||
if let Ctr::Seg(ref s) = intermediate {
|
||||
|
|
@ -698,8 +698,8 @@ mod math_lib_tests {
|
|||
eval(&doc_tree, &mut syms).unwrap();
|
||||
if let Err(s) = eval(&change_tree, &mut syms) {
|
||||
assert_eq!(
|
||||
s,
|
||||
"error in call to inc: cannot increment a function".to_string()
|
||||
s.0.first().unwrap().message,
|
||||
"expected tester to be an integer".to_string()
|
||||
);
|
||||
if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() {
|
||||
assert_eq!(*s, "1".to_string());
|
||||
|
|
@ -768,8 +768,8 @@ mod math_lib_tests {
|
|||
eval(&doc_tree, &mut syms).unwrap();
|
||||
if let Err(s) = eval(&change_tree, &mut syms) {
|
||||
assert_eq!(
|
||||
s,
|
||||
"error in call to dec: can only decrement an integer".to_string()
|
||||
s.0.first().unwrap().message,
|
||||
"expected tester to be an integer".to_string()
|
||||
);
|
||||
let intermediate = *eval(&check_tree, &mut syms).unwrap();
|
||||
if let Ctr::Seg(ref s) = intermediate {
|
||||
|
|
@ -801,8 +801,8 @@ mod math_lib_tests {
|
|||
eval(&doc_tree, &mut syms).unwrap();
|
||||
if let Err(s) = eval(&change_tree, &mut syms) {
|
||||
assert_eq!(
|
||||
s,
|
||||
"error in call to dec: cannot decrement a function".to_string()
|
||||
s.0.first().unwrap().message,
|
||||
"expected tester to be an integer".to_string()
|
||||
);
|
||||
if let Ctr::String(ref s) = *eval(&check_tree, &mut syms).unwrap() {
|
||||
assert_eq!(*s, "1".to_string());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue