/* Flesh: Flexible Shell * Copyright (C) 2021 Ava Affine * * 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 . */ use crate::segment::{Ctr, Seg}; use crate::sym::{SymTable, ValueType, Symbol, Args}; use crate::error::{Traceback, start_trace}; use std::rc::Rc; fn isnumeric(arg: &Ctr) -> bool { matches!(arg, Ctr::Integer(_) | Ctr::Float(_)) } const ADD_DOCSTRING: &str = "traverses over N args, which must all evaluate to an Integer or Float. 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."; fn add_callback(ast: &Seg, _: &mut SymTable) -> Result { let mut res = Ctr::Integer(0); let mut culprit: Ctr = Ctr::None; let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool { if !isnumeric(c) { culprit = c.clone(); false } else { res = res.clone() + c.clone(); true } }); if !type_consistent { Err(start_trace( ("add", format!("{} is not a number!", culprit)) .into())) } else { Ok(res) } } const SUB_DOCSTRING: &str = "traverses over N args, which must all evaluate to an Integer or Float. 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."; fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result { 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; if let Ctr::Seg(ref subsequent_operands) = *ast.cdr { let type_consistent = subsequent_operands.circuit(&mut |c: &Ctr| -> bool { if !isnumeric(c) { culprit = c.clone(); false } else { res = res.clone() - c.clone(); true } }); if !type_consistent { Err(start_trace( ("sub", format!("{} is not a number!", culprit)) .into())) } else { Ok(res) } } else { Err(start_trace( ("sub", "expected at least two inputs") .into())) } } const DIV_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float. 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."; fn div_callback(ast: &Seg, _: &mut SymTable) -> Result { let first = *ast.car.clone(); if !isnumeric(&first) { 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(start_trace( ("div", format!("{} is not a number!", second)) .into())) } Ok(first / second) } else { Err(start_trace( ("div", "expected exactly two inputs") .into())) } } const MUL_DOCSTRING: &str = "traverses over N args, which must all evaluate to an Integer or Float. 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."; fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result { let mut res = Ctr::Integer(1); let mut culprit: Ctr = Ctr::None; let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool { if !isnumeric(c) { culprit = c.clone(); false } else { res = res.clone() * c.clone(); true } }); if !type_consistent { Err(start_trace( ("mul", format!("{} is not a number!", culprit)) .into())) } else { Ok(res) } } const INTCAST_DOCSTRING: &str = "takes a single arg and attempts to cast it to an Integer. 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."; fn intcast_callback(ast: &Seg, _: &mut SymTable) -> Result { // 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::(s); if int.is_err() { Err(start_trace( ("int", int.err().unwrap().to_string()) .into())) } else { Ok(Ctr::Integer(int.ok().unwrap())) } } else { Err(start_trace( ("int", "expected a float or a string") .into())) } } const FLOATCAST_DOCSTRING: &str = "takes a single arg and attempts to cast it to a float. 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."; fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result { // 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 flt = str::parse::(s); if flt.is_err() { Err(start_trace( ("float", flt.err().unwrap().to_string()) .into())) } else { Ok(Ctr::Float(flt.ok().unwrap())) } } else { Err(start_trace( ("float", "expected a string or an integer") .into())) } } const EXP_DOCSTRING: &str = "Takes two args, both expected to be numeric. Returns the first arg to the power of the second arg. Does not handle overflow or underflow. PANIC CASES: - arg1 is a float and arg2 is greater than an int32 - 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"; fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result { let first = *ast.car.clone(); if !isnumeric(&first) { 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(start_trace( ("exp", "expected at least two inputs") .into())) } if !isnumeric(&second) { 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(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(start_trace( ("exp", "not implemented for these input types") .into())), }, _ => Err(start_trace( ("exp", "not implemented for these input types") .into())), } } const MOD_DOCSTRING: &str = "Takes two args, both expected to be numeric. Returns a list of two values: the modulus and the remainder. Example: (mod 5 3) -> (1 2) PANIC CASES: - A float is modulo an integer larger than a max f64 - An integer larger than a max f64 is modulo a float "; fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result { let first = *ast.car.clone(); if !isnumeric(&first) { 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(start_trace( ("mod", "expected at least two inputs") .into())) } if !isnumeric(&second) { return Err(start_trace( ("mod", format!("{} is not a number!", second)) .into())) } let mut ret = Seg::new(); match first { Ctr::Float(lf) => match second { Ctr::Float(rf) => { ret.append(Box::new(Ctr::Integer((lf / rf) as i128))); ret.append(Box::new(Ctr::Integer((lf % rf) as i128))); } Ctr::Integer(ri) => { 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(start_trace( ("mod", "not implemented for these input types") .into())), }, Ctr::Integer(li) => match second { Ctr::Float(rf) => { ret.append(Box::new(Ctr::Integer((li as f64 / rf) as i128))); ret.append(Box::new(Ctr::Integer((li as f64 % rf) as i128))); } Ctr::Integer(ri) => { ret.append(Box::new(Ctr::Integer(li / ri))); ret.append(Box::new(Ctr::Integer(li % ri))); } _ => return Err(start_trace( ("mod", "not implemented for these input types") .into())), }, _ => return Err(start_trace( ("mod", "not implemented for these input types") .into())), } Ok(Ctr::Seg(ret)) } const ISGT_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float. 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."; fn isgt_callback(ast: &Seg, _: &mut SymTable) -> Result { let first = *ast.car.clone(); if !isnumeric(&first) { 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(start_trace( ("gt?", "expected at least two inputs") .into())) } if !isnumeric(&second) { 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(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(start_trace( ("gt?", "not implemented for these input types") .into())), }, _ => Err(start_trace( ("gt?", "not implemented for these input types") .into())), } } const ISLT_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float. 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."; fn islt_callback(ast: &Seg, _: &mut SymTable) -> Result { let first = *ast.car.clone(); if !isnumeric(&first) { 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(start_trace( ("lt?", "expected at least two inputs") .into())) } if !isnumeric(&second) { 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(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(start_trace( ("lt?", "not implemented for these input types") .into())), }, _ => Err(start_trace( ("lt?", "not implemented for these input types") .into())), } } const ISGTE_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float. 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."; fn isgte_callback(ast: &Seg, syms: &mut SymTable) -> Result { 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())), } } const ISLTE_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float. 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."; fn islte_callback(ast: &Seg, syms: &mut SymTable) -> Result { 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())), } } const INC_DOCSTRING: &str = "Accepts a single argument, expects it to be a symbol. The symbol is fetched from the symbol table. If the symbol is not an integer an error is returned. The symbol is redefined as symbol + 1. This call is similar to the following: (def counter '' (add counter 1)) with the caveat that your docstring is preserved."; fn inc_callback(ast: &Seg, syms: &mut SymTable) -> Result { let var_name: String; if let Ctr::Symbol(ref s) = *ast.car { var_name = s.clone(); } else { return Err(start_trace( ("inc", "expected input to be a symbol") .into())); } let sym_ret = syms .remove(&var_name); if sym_ret.is_none() { 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.clone(), sym); return Err(start_trace( ("inc", format!("expected {var_name} to be an integer")) .into())); } } else { 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); Ok(Ctr::None) } const DEC_DOCSTRING: &str = "Accepts a single argument, expects it to be a symbol. The symbol is fetched from the symbol table. If the symbol is not an integer an error is returned. The symbol is redefined as symbol - 1. This call is similar to the following: (def counter '' (sub counter 1)) with the caveat that your docstring is preserved."; fn dec_callback(ast: &Seg, syms: &mut SymTable) -> Result { let var_name: String; if let Ctr::Symbol(ref s) = *ast.car { var_name = s.clone(); } else { return Err(start_trace( ("dec", "expected input to be a symbol") .into())); } let sym_ret = syms .remove(&var_name); if sym_ret.is_none() { 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.clone(), sym); return Err(start_trace( ("dec", format!("expected {var_name} to be an integer")) .into())); } } else { 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); Ok(Ctr::None) } pub fn add_math_lib(syms: &mut SymTable) { syms.insert( "add".to_string(), Symbol { name: String::from("add"), args: Args::Infinite, conditional_branches: false, docs: ADD_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(add_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "sub".to_string(), Symbol { name: String::from("sub"), args: Args::Infinite, conditional_branches: false, docs: SUB_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(sub_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "div".to_string(), Symbol { name: String::from("div"), args: Args::Lazy(2), conditional_branches: false, docs: DIV_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(div_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "mul".to_string(), Symbol { name: String::from("mul"), args: Args::Infinite, conditional_branches: false, docs: MUL_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(mul_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "int".to_string(), Symbol { name: String::from("int"), args: Args::Lazy(1), conditional_branches: false, docs: INTCAST_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(intcast_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "float".to_string(), Symbol { name: String::from("float"), args: Args::Lazy(1), conditional_branches: false, docs: FLOATCAST_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(floatcast_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "exp".to_string(), Symbol { name: String::from("exp"), args: Args::Lazy(2), conditional_branches: false, docs: EXP_DOCSTRING.to_string(), optimizable: true, value: ValueType::Internal(Rc::new(exp_callback)), ..Default::default() }, ); syms.insert( "mod".to_string(), Symbol { name: String::from("mod"), args: Args::Lazy(2), conditional_branches: false, docs: MOD_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(mod_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "gt?".to_string(), Symbol { name: String::from("gt?"), args: Args::Lazy(2), conditional_branches: false, docs: ISGT_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(isgt_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "lt?".to_string(), Symbol { name: String::from("lt?"), args: Args::Lazy(2), conditional_branches: false, docs: ISLT_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(islt_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "gte?".to_string(), Symbol { name: String::from("gt?"), args: Args::Lazy(2), conditional_branches: false, docs: ISGTE_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(isgte_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "lte?".to_string(), Symbol { name: String::from("lt?"), args: Args::Lazy(2), conditional_branches: false, docs: ISLTE_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(islte_callback)), optimizable: true, ..Default::default() }, ); syms.insert( "inc".to_string(), Symbol { name: String::from("inc"), args: Args::Lazy(1), conditional_branches: true, docs: INC_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(inc_callback)), ..Default::default() }, ); syms.insert( "dec".to_string(), Symbol { name: String::from("dec"), args: Args::Lazy(1), conditional_branches: true, docs: DEC_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(dec_callback)), ..Default::default() }, ); }