diff --git a/Readme.org b/Readme.org index 22739a6..6e8e41e 100644 --- a/Readme.org +++ b/Readme.org @@ -149,11 +149,11 @@ Will need a concatenate function for tables *** TODO Can enter multiple lines of text, with formatting in repl *** TODO arithmetic operations **** TODO typecast (int) -**** TODO typecast (zfloat) -**** TODO add -**** TODO sub -**** TODO div -**** TODO mul +**** TODO typecast (float) +**** DONE add +**** DONE sub +**** DONE div +**** DONE mul **** TODO exp **** TODO mod **** TODO inc diff --git a/src/segment.rs b/src/segment.rs index 5a4e4f2..1620fb3 100644 --- a/src/segment.rs +++ b/src/segment.rs @@ -16,7 +16,7 @@ */ use std::fmt; use std::marker::PhantomData; -use std::ops::Index; +use std::ops::{Add, Sub, Mul, Div, Index}; // Container #[derive(Debug, Default)] @@ -268,6 +268,108 @@ impl PartialEq for Ctr { } } +impl Add for Ctr { + type Output = Ctr; + + fn add(self, rh: Ctr) -> Ctr { + match self { + Ctr::Float(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn + rhn), + Ctr::Integer(rhn) => Ctr::Float(lhn + rhn as f64), + _ => unimplemented!("non-numeral on right hand side of add"), + }, + + Ctr::Integer(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn as f64 + rhn), + Ctr::Integer(rhn) => Ctr::Integer(lhn + rhn), + _ => unimplemented!("non-numeral on right hand side of add"), + }, + + Ctr::String(lhs) => match rh { + Ctr::String(rhs) => Ctr::String(lhs + &rhs), + _ => unimplemented!("add to string only implemented for other strings"), + }, + + _ => { + unimplemented!("datum does not support add") + } + } + } +} + +impl Sub for Ctr { + type Output = Ctr; + + fn sub(self, rh: Ctr) -> Ctr { + match self { + Ctr::Float(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn - rhn), + Ctr::Integer(rhn) => Ctr::Float(lhn - rhn as f64), + _ => unimplemented!("non-numeral on right hand side of sub"), + }, + + Ctr::Integer(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn as f64 - rhn), + Ctr::Integer(rhn) => Ctr::Integer(lhn - rhn), + _ => unimplemented!("non-numeral on right hand side of sub"), + }, + + _ => { + unimplemented!("datum does not support sub") + } + } + } +} + + +impl Mul for Ctr { + type Output = Ctr; + + fn mul(self, rh: Ctr) -> Ctr { + match self { + Ctr::Float(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn * rhn), + Ctr::Integer(rhn) => Ctr::Float(lhn * rhn as f64), + _ => unimplemented!("non-numeral on right hand side of mul"), + }, + + Ctr::Integer(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn as f64 * rhn), + Ctr::Integer(rhn) => Ctr::Integer(lhn * rhn), + _ => unimplemented!("non-numeral on right hand side of mul"), + }, + + _ => { + unimplemented!("datum does not support mul") + } + } + } +} + +impl Div for Ctr { + type Output = Ctr; + + fn div(self, rh: Ctr) -> Ctr { + match self { + Ctr::Float(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn / rhn), + Ctr::Integer(rhn) => Ctr::Float(lhn / rhn as f64), + _ => unimplemented!("non-numeral on right hand side of div"), + }, + + Ctr::Integer(lhn) => match rh { + Ctr::Float(rhn) => Ctr::Float(lhn as f64 / rhn), + Ctr::Integer(rhn) => Ctr::Integer(lhn / rhn), + _ => unimplemented!("non-numeral on right hand side of div"), + }, + + _ => { + unimplemented!("datum does not support div") + } + } + } +} + impl fmt::Display for Seg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", seg_to_string(self, true)) diff --git a/src/stl.rs b/src/stl.rs index b0a4907..078900f 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -23,6 +23,7 @@ pub mod append; pub mod boolean; pub mod control; pub mod decl; +pub mod math; //pub mod str; /// static_stdlib @@ -183,6 +184,50 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> { }, ); + syms.insert( + "add".to_string(), + Symbol { + name: String::from("add"), + args: Args::Infinite, + conditional_branches: false, + docs: math::ADD_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(math::add_callback)), + }, + ); + + syms.insert( + "sub".to_string(), + Symbol { + name: String::from("sub"), + args: Args::Infinite, + conditional_branches: false, + docs: math::SUB_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(math::sub_callback)), + }, + ); + + syms.insert( + "div".to_string(), + Symbol { + name: String::from("div"), + args: Args::Lazy(2), + conditional_branches: false, + docs: math::DIV_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(math::div_callback)), + }, + ); + + syms.insert( + "mul".to_string(), + Symbol { + name: String::from("mul"), + args: Args::Infinite, + conditional_branches: false, + docs: math::MUL_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(math::mul_callback)), + }, + ); + Ok(()) } diff --git a/src/stl/math.rs b/src/stl/math.rs new file mode 100644 index 0000000..1f1e8e2 --- /dev/null +++ b/src/stl/math.rs @@ -0,0 +1,127 @@ +/* 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 . + */ + +use crate::segment::{Ctr, Seg}; +use crate::sym::SymTable; + +fn isnumeric(arg: &Ctr) -> bool { + match arg { + Ctr::Integer(_) => true, + Ctr::Float(_) => true, + _ => false, + } +} + +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 { + 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 { + if !isnumeric(&*ast.car) { + 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 { + 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()) + } +} + +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 { + 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) + } +} diff --git a/tests/test_lib_math.rs b/tests/test_lib_math.rs new file mode 100644 index 0000000..f208c7d --- /dev/null +++ b/tests/test_lib_math.rs @@ -0,0 +1,84 @@ +mod math_lib_tests { + use relish::ast::{eval, lex, SymTable}; + use relish::stdlib::{dynamic_stdlib, static_stdlib}; + + #[test] + fn test_add_chain() { + let document = "(add 1 2 3 4)"; + let result = "10"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } + + #[test] + fn test_add_chain_mixed() { + let document = "(add 1 2.2 3 4)"; + let result = "10.2"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } + + #[test] + fn test_mul_chain() { + let document = "(mul 1 2 3 4)"; + let result = "24"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } + + #[test] + fn test_sub_chain() { + let document = "(sub 1 2.2 3 4)"; + let result = "-8.2"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } + + #[test] + fn test_div() { + let document = "(div 10 5)"; + let result = "2"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } +}