flesh/src/stl/math.rs

361 lines
13 KiB
Rust
Raw Normal View History

/* 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 crate::segment::{Ctr, Seg};
use crate::sym::SymTable;
fn isnumeric(arg: &Ctr) -> bool {
match arg {
Ctr::Integer(_) => true,
Ctr::Float(_) => true,
_ => false,
}
}
2023-03-07 21:31:54 -08:00
pub 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.";
pub fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
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(format!("{} is not a number!", culprit))
} else {
Ok(res)
}
}
pub 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.";
pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
if !isnumeric(&*ast.car) {
2023-03-07 21:31:54 -08:00
return Err(format!("{} is not a number", &*ast.car));
}
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(format!("{} is not a number", culprit))
} else {
Ok(res)
}
} else {
Err("requires at least two operands".to_string())
}
}
pub 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.";
pub fn div_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
}
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());
}
Ok(first / second)
} else {
Err("impossible error: needs two arguments".to_string())
}
}
2023-03-07 21:31:54 -08:00
pub 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.";
pub fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
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(format!("{} is not a number!", culprit))
} else {
Ok(res)
}
}
2023-03-07 13:43:51 -08:00
pub 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.";
pub fn intcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
// 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())
} else {
Ok(Ctr::Integer(int.ok().unwrap()))
}
} else {
Err("int cast only takes a float or a string".to_string())
}
}
pub 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.";
pub fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
// 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())
} else {
Ok(Ctr::Float(int.ok().unwrap()))
}
} else {
Err("float cast only takes an integer or a string".to_string())
}
}
pub 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";
pub fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
2023-03-07 21:31:54 -08:00
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
}
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());
}
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
}
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()),
},
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("exp not implemented for these arguments".to_string()),
}
}
pub 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
";
pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
2023-03-07 21:31:54 -08:00
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
}
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());
}
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
}
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)));
2023-03-07 21:31:54 -08:00
}
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)));
2023-03-07 21:31:54 -08:00
}
_ => return Err("mod not implemented for these arguments".to_string()),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => {
ret.append(Box::new(Ctr::Integer((li as f64 / rf) as i128)));
2023-03-07 21:31:54 -08:00
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)));
2023-03-07 21:31:54 -08:00
}
_ => return Err("mod not implemented for these arguments".to_string()),
},
_ => return Err("mod not implemented for these arguments".to_string()),
}
Ok(Ctr::Seg(ret))
}
pub 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.";
pub fn isgt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
}
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());
}
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
}
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()),
},
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("gt? not implemented for these arguments".to_string()),
}
}
pub 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.";
pub fn islt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
}
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());
}
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
}
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()),
},
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("gt? not implemented for these arguments".to_string()),
}
}
pub 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.";
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 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.";
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())
}
}