Error Messaging Redesign

This commit contains the following:

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

View file

@ -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()))
}
}

View file

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

View file

@ -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))
}
}

View file

@ -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::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());
},
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(),
_ => {
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()))
},
Err(e) => return Err(e.with_trace(
("def", "couldnt evaluate docs form")
.into()))
},
_ => return Err("docs argument does not evaluate to a string".to_string())
_ => 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()))
}
}

View file

@ -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)? {
Ok(Ctr::Bool(!b))
} else {
Err("impossible state: islt returned non-bool".to_string())
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(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)? {
Ok(Ctr::Bool(!b))
} else {
Err("impossible state: islt returned non-bool".to_string())
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(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);

View file

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

View file

@ -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()))
}
}