From 8eeef3a9289135d675aab1ba17b7cd2d218ebcd8 Mon Sep 17 00:00:00 2001 From: Rowan Torbitzky-Lane Date: Sun, 6 Apr 2025 23:50:14 -0500 Subject: [PATCH] more tests/start on code instructions --- src/instructions/code.rs | 296 ++++++++++++++++++++++++++++++++++++ src/instructions/logical.rs | 62 +++++++- src/instructions/mod.rs | 58 +++++++ src/instructions/numeric.rs | 88 ++++------- src/instructions/utils.rs | 59 +++++-- src/main.rs | 12 +- 6 files changed, 494 insertions(+), 81 deletions(-) create mode 100644 src/instructions/code.rs diff --git a/src/instructions/code.rs b/src/instructions/code.rs new file mode 100644 index 0000000..b4cf358 --- /dev/null +++ b/src/instructions/code.rs @@ -0,0 +1,296 @@ +use std::ops::Not; + +use crate::push::state::{Gene, PushState}; + +/// Checks to see if a single gene is a block. +fn _is_block(vals: Vec) -> Option { + Some(match vals[0] { + Gene::Block(_) => true, + _ => false, + }) +} +make_instruction_clone!(code, boolean, _is_block, Gene, 1); + +/// Checks to see if a single gene is not a block. +fn _is_singular(vals: Vec) -> Option { + Some(_is_block(vals)?.not()) +} +make_instruction_clone!(code, boolean, _is_singular, Gene, 1); + +/// Returns the length of a block, else 1 if not a block +fn _length(vals: Vec) -> Option { + Some(match &vals[0] { + Gene::Block(x) => x.len() as i128, + _ => 1, + }) +} +make_instruction_clone!(code, int, _length, Gene, 1); + +/// Returns the first item in a block if doable, else None +fn _first(vals: Vec) -> Option { + match &vals[0] { + Gene::Block(x) => { + if x.len() > 1 { + Some(x[0].clone()) + } else { + None + } + } + _ => None, + } +} +make_instruction_clone!(code, code, _first, Gene, 1); + +/// Returns the first item in a block if applicable, else None +fn _last(vals: Vec) -> Option { + match &vals[0] { + Gene::Block(x) => { + if x.len() > 1 { + Some(x.last()?.clone()) + } else { + None + } + } + _ => None, + } +} +make_instruction_clone!(code, code, _last, Gene, 1); + +/// Returns all but the first code item in a block if applicable, else None +fn _rest(vals: Vec) -> Option { + match &vals[0] { + Gene::Block(x) => { + if x.len() > 1 { + Some(Gene::Block(Box::new(x[1..].to_vec()))) + } else { + None + } + } + _ => None, + } +} +make_instruction_clone!(code, code, _rest, Gene, 1); + +/// Returns all but the first code item in a block if applicable, else None +fn _but_last(vals: Vec) -> Option { + match &vals[0] { + Gene::Block(x) => { + let x_len = x.len(); + if x_len > 1 { + Some(Gene::Block(Box::new(x[..x_len - 1].to_vec()))) + } else { + None + } + } + _ => None, + } +} +make_instruction_clone!(code, code, _but_last, Gene, 1); + +/// Returns all of the vals wrapped in a code block +fn _wrap_block(vals: Vec) -> Option { + Some(Gene::Block(Box::new(vals))) +} +make_instruction_clone!(code, code, _wrap_block, Gene, 1); + +/// Combines two genes into one. Accounts for blocks. +/// If the second gene is a block and the first one isn't, +/// appends the first gene to the second gene. +fn _combine(vals: Vec) -> Option { + match (&vals[0], &vals[1]) { + (Gene::Block(x), Gene::Block(y)) => { + let mut x_clone = x.clone(); + let y_clone = y.clone(); + x_clone.extend(y_clone.into_iter()); + Some(Gene::Block(x_clone)) + } + (Gene::Block(x), y) => { + let mut x_clone = x.clone(); + x_clone.push(y.clone()); + Some(Gene::Block(x_clone)) + } + (x, Gene::Block(y)) => { + let mut y_clone = y.clone(); + y_clone.push(x.clone()); + Some(Gene::Block(y_clone)) + } + (x, y) => Some(Gene::Block(Box::new(vec![x.clone(), y.clone()]))), + } +} +make_instruction_clone!(code, code, _combine, Gene, 1); + +#[cfg(test)] +mod tests { + use super::*; + use crate::push::state::EMPTY_STATE; + use rust_decimal::dec; + + #[test] + fn is_block_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::Block(Box::new(vec![]))]; + code_is_block(&mut test_state); + assert_eq!(vec![true], test_state.boolean); + test_state.boolean.clear(); + + test_state.code = vec![(Gene::GeneInt(1))]; + code_is_block(&mut test_state); + assert_eq!(vec![false], test_state.boolean); + } + + #[test] + fn is_singular_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::Block(Box::new(vec![]))]; + code_is_singular(&mut test_state); + assert_eq!(vec![false], test_state.boolean); + test_state.boolean.clear(); + + test_state.code = vec![(Gene::GeneInt(1))]; + code_is_singular(&mut test_state); + assert_eq!(vec![true], test_state.boolean); + } + + #[test] + fn length_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::Block(Box::new(vec![ + Gene::GeneInt(1), + Gene::GeneFloat(dec!(3.8)), + ]))]; + code_length(&mut test_state); + assert_eq!(vec![2], test_state.int); + test_state.int.clear(); + + test_state.code = vec![Gene::Block(Box::new(vec![]))]; + code_length(&mut test_state); + assert_eq!(vec![0], test_state.int); + test_state.int.clear(); + + test_state.code = vec![Gene::GeneInt(3)]; + code_length(&mut test_state); + assert_eq!(vec![1], test_state.int); + } + + #[test] + fn first_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::Block(Box::new(vec![ + Gene::GeneInt(1), + Gene::GeneFloat(dec!(3.8)), + ]))]; + code_first(&mut test_state); + assert_eq!(vec![Gene::GeneInt(1)], test_state.code); + + test_state.code = vec![]; + code_first(&mut test_state); + let empty_vec: Vec = vec![]; + assert_eq!(empty_vec, test_state.code); + drop(empty_vec); + + test_state.code = vec![Gene::GeneInt(1)]; + code_first(&mut test_state); + assert_eq!(vec![Gene::GeneInt(1)], test_state.code); + } + + #[test] + fn last_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::Block(Box::new(vec![ + Gene::GeneInt(1), + Gene::GeneFloat(dec!(3.8)), + ]))]; + code_last(&mut test_state); + assert_eq!(vec![Gene::GeneFloat(dec!(3.8))], test_state.code); + + test_state.code = vec![]; + code_last(&mut test_state); + let empty_vec: Vec = vec![]; + assert_eq!(empty_vec, test_state.code); + drop(empty_vec); + + test_state.code = vec![Gene::GeneInt(1)]; + code_last(&mut test_state); + assert_eq!(vec![Gene::GeneInt(1)], test_state.code); + } + + #[test] + fn rest_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::Block(Box::new(vec![ + Gene::GeneInt(1), + Gene::GeneFloat(dec!(3.8)), + Gene::GeneBoolean(true), + ]))]; + code_rest(&mut test_state); + assert_eq!( + vec![Gene::Block(Box::new(vec![ + Gene::GeneFloat(dec!(3.8)), + Gene::GeneBoolean(true) + ]))], + test_state.code + ); + + test_state.code = vec![]; + code_rest(&mut test_state); + let empty_vec: Vec = vec![]; + assert_eq!(empty_vec, test_state.code); + drop(empty_vec); + + test_state.code = vec![Gene::GeneInt(1)]; + code_rest(&mut test_state); + assert_eq!(vec![Gene::GeneInt(1)], test_state.code); + } + + #[test] + fn but_last_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::Block(Box::new(vec![ + Gene::GeneInt(1), + Gene::GeneFloat(dec!(3.8)), + Gene::GeneBoolean(true), + ]))]; + code_but_last(&mut test_state); + assert_eq!( + vec![Gene::Block(Box::new(vec![ + Gene::GeneInt(1), + Gene::GeneFloat(dec!(3.8)), + ]))], + test_state.code + ); + + test_state.code = vec![]; + code_but_last(&mut test_state); + let empty_vec: Vec = vec![]; + assert_eq!(empty_vec, test_state.code); + drop(empty_vec); + + test_state.code = vec![Gene::GeneInt(1)]; + code_but_last(&mut test_state); + assert_eq!(vec![Gene::GeneInt(1)], test_state.code); + } + + #[test] + fn wrap_block_test() { + let mut test_state = EMPTY_STATE; + + test_state.code = vec![Gene::GeneInt(1)]; + code_wrap_block(&mut test_state); + assert_eq!( + vec![Gene::Block(Box::new(vec![Gene::GeneInt(1)]))], + test_state.code + ); + } + + #[test] + fn combine_test() { + // TODO: This later + } +} diff --git a/src/instructions/logical.rs b/src/instructions/logical.rs index 58cf011..62f291f 100644 --- a/src/instructions/logical.rs +++ b/src/instructions/logical.rs @@ -2,8 +2,9 @@ //! //! This file holds instructions for the boolean stack. -use super::utils::{LogicalTrait, NumericTrait}; +use super::utils::{CastingTrait, LogicalTrait}; use crate::push::state::PushState; +use rust_decimal::Decimal; /// Runs logical and on two values fn _and(vals: Vec) -> Option @@ -59,17 +60,40 @@ where } make_instruction!(boolean, boolean, _invert_second_then_and, bool, 2); -// fn _to_int(vals: Vec) -> Option -// where -// T: Copy + NumericTrait, -// { -// Some(T::from_bool(vals)) -// } +fn _from_int(vals: Vec) -> Option +where + T: Copy + CastingTrait, +{ + T::from_int(vals[0]) +} +make_instruction_out!(int, boolean, _from_int, i128, 1); + +fn _from_float(vals: Vec) -> Option +where + T: Copy + CastingTrait, +{ + T::from_float(vals[0]) +} +make_instruction_out!(float, boolean, _from_float, Decimal, 1); + +pub fn boolean_instructions() -> Vec { + vec![ + boolean_and, + boolean_or, + boolean_not, + boolean_xor, + boolean_invert_first_then_and, + boolean_invert_second_then_and, + boolean_from_int, + boolean_from_float, + ] +} #[cfg(test)] mod tests { use super::*; use crate::push::state::EMPTY_STATE; + use rust_decimal::dec; #[test] fn and_test() { @@ -147,4 +171,28 @@ mod tests { boolean_invert_second_then_and(&mut test_state); assert_eq!(vec![true], test_state.boolean); } + + #[test] + fn cast_test() { + let mut test_state = EMPTY_STATE; + + test_state.int = vec![1]; + boolean_from_int(&mut test_state); + assert_eq!(vec![true], test_state.boolean); + test_state.boolean.clear(); + + test_state.int = vec![0]; + boolean_from_int(&mut test_state); + assert_eq!(vec![false], test_state.boolean); + test_state.boolean.clear(); + + test_state.float = vec![dec!(2.0)]; + boolean_from_float(&mut test_state); + assert_eq!(vec![true], test_state.boolean); + test_state.boolean.clear(); + + test_state.float = vec![dec!(0.0)]; + boolean_from_float(&mut test_state); + assert_eq!(vec![false], test_state.boolean); + } } diff --git a/src/instructions/mod.rs b/src/instructions/mod.rs index 81bdaa5..0abb8ec 100644 --- a/src/instructions/mod.rs +++ b/src/instructions/mod.rs @@ -43,8 +43,66 @@ pub mod macros { } }; } + + /// The same as make_instruction above but prepends the output + /// stack to the function name rather than the input stack. + #[macro_export] + macro_rules! make_instruction_out { + ($in_stack:ident, $out_stack:ident, $fn_name:ident, $fn_type:ty, $fn_arity:stmt) => { + paste::item! { + /// Runs the $fn_name function on the top $fn_arity items from + /// the $in_stack and places the calculated value on the $out_stack. + pub fn [< $out_stack $fn_name >] (state: &mut PushState) { + let in_stack_len = state.$in_stack.len(); + if in_stack_len < $fn_arity { + return; + } + let mut inputs: Vec<$fn_type> = Vec::with_capacity($fn_arity); + for n in 1..=$fn_arity { + inputs.push(state.$in_stack[in_stack_len - n]); + } + if let Some(result) = $fn_name(inputs) { + for _ in 0..$fn_arity { + state.$in_stack.pop(); + } + state.$out_stack.push(result); + } + } + } + }; + } + + /// The same as make_instruction but uses clone() to fill the arguments + /// to each function rather than a reference. Is slower, but will be okay + /// for the time being. + #[macro_export] + macro_rules! make_instruction_clone { + ($in_stack:ident, $out_stack:ident, $fn_name:ident, $fn_type:ty, $fn_arity:stmt) => { + paste::item! { + /// Runs the $fn_name function on the top $fn_arity items from + /// the $in_stack and places the calculated value on the $out_stack. + pub fn [< $in_stack $fn_name >] (state: &mut PushState) { + let in_stack_len = state.$in_stack.len(); + if in_stack_len < $fn_arity { + return; + } + let mut inputs: Vec<$fn_type> = Vec::with_capacity($fn_arity); + for n in 1..=$fn_arity { + inputs.push(state.$in_stack[in_stack_len - n].clone()); + } + if let Some(result) = $fn_name(inputs) { + for _ in 0..$fn_arity { + state.$in_stack.pop(); + } + state.$out_stack.push(result); + } + } + } + }; + } } +pub mod code; pub mod logical; pub mod numeric; pub mod utils; diff --git a/src/instructions/numeric.rs b/src/instructions/numeric.rs index 39fc2bd..d5a1273 100644 --- a/src/instructions/numeric.rs +++ b/src/instructions/numeric.rs @@ -11,7 +11,7 @@ use rust_decimal::prelude::{FromPrimitive, ToPrimitive}; use std::cmp::{max, min}; use std::ops::{Add, Div, Mul, Sub}; -use super::utils::NumericTrait; +use super::utils::{CastingTrait, NumericTrait}; /// Adds two addable values together. fn _add(vals: Vec) -> Option @@ -203,27 +203,33 @@ where make_instruction!(int, int, _arctan, i128, 1); make_instruction!(float, float, _arctan, Decimal, 1); -/// Converts the top int to a float. -fn _to_int(vals: Vec) -> Option { - vals[0].to_i128() -} -make_instruction!(float, int, _to_int, Decimal, 1); - -/// Converts the top float to an int. -fn _to_float(vals: Vec) -> Option { - Decimal::from_i128(vals[0]) -} -make_instruction!(int, float, _to_float, i128, 1); - -/// Converts a single number to a bool. -fn _from_bool(vals: Vec) -> Option +/// Converts a single value from a float to an arbitrary type. +fn _from_int(vals: Vec) -> Option where - T: Copy + NumericTrait, + T: Copy + CastingTrait, { - Some(T::from_bool(vals[0])) + T::from_int(vals[0]) } -make_instruction!(int, boolean, _from_bool, bool, 1); -make_instruction!(float, boolean, _from_bool, bool, 1); +make_instruction_out!(int, float, _from_int, i128, 1); + +/// Converts a single value from a float to an arbitrary type. +fn _from_float(vals: Vec) -> Option +where + T: Copy + CastingTrait, +{ + T::from_float(vals[0]) +} +make_instruction_out!(float, int, _from_float, Decimal, 1); + +/// Converts a bool to a to a new type. +fn _from_boolean(vals: Vec) -> Option +where + T: Copy + CastingTrait, +{ + T::from_bool(vals[0]) +} +make_instruction_out!(boolean, int, _from_boolean, bool, 1); +make_instruction_out!(boolean, float, _from_boolean, bool, 1); /// Takes the log base 10 of a single Decimal. Acts as a /// NoOp if the value is 0. If the value is negative, takes @@ -321,8 +327,8 @@ pub fn int_instructions() -> Vec { int_arccos, int_tan, int_arctan, - int_to_float, - int_to_bool, + int_from_float, + int_from_boolean, int_log, int_exp, int_sqrt, @@ -355,8 +361,8 @@ pub fn float_instructions() -> Vec { float_arccos, float_tan, float_arctan, - float_to_int, - float_to_bool, + float_from_int, + float_from_boolean, float_log, float_exp, float_sqrt, @@ -542,17 +548,6 @@ mod tests { assert_eq!(Some(dec!(1.0000000043184676055890307049)), _arctan(vals)); } - /// Tests the functions that cast from one numeric type - /// to another - #[test] - fn cast_tests() { - let vals = vec![dec!(1.2)]; - assert_eq!(Some(1), _to_int(vals)); - - let vals: Vec = vec![2]; - assert_eq!(Some(dec!(2.0)), _to_float(vals)); - } - /// Tests that the various addition functions. #[test] fn state_add() { @@ -793,32 +788,13 @@ mod tests { let mut test_state = EMPTY_STATE; test_state.int = vec![0, 1]; - int_to_float(&mut test_state); + float_from_int(&mut test_state); assert_eq!(vec![dec!(1.0)], test_state.float); - test_state.int.clear(); + test_state.float = vec![dec!(2.1)]; - float_to_int(&mut test_state); + int_from_float(&mut test_state); assert_eq!(vec![2], test_state.int); - - test_state.int = vec![1]; - int_to_bool(&mut test_state); - assert_eq!(vec![true], test_state.boolean); - test_state.boolean.clear(); - - test_state.int = vec![0]; - int_to_bool(&mut test_state); - assert_eq!(vec![false], test_state.boolean); - test_state.boolean.clear(); - - test_state.float = vec![dec!(2.0)]; - float_to_bool(&mut test_state); - assert_eq!(vec![true], test_state.boolean); - test_state.boolean.clear(); - - test_state.float = vec![dec!(0.0)]; - float_to_bool(&mut test_state); - assert_eq!(vec![false], test_state.boolean); } /// Tests the log function diff --git a/src/instructions/utils.rs b/src/instructions/utils.rs index 59e7972..2ed90e5 100644 --- a/src/instructions/utils.rs +++ b/src/instructions/utils.rs @@ -21,10 +21,8 @@ pub trait NumericTrait: Sized + Div { fn absolute(self) -> Self; fn safe_log10(self) -> Option; fn safe_sqrt(self) -> Option; - fn to_bool(self) -> bool; fn sign_reverse(self) -> Self; fn square(self) -> Self; - fn from_bool(v: bool) -> Self; } impl NumericTrait for Decimal { @@ -64,18 +62,12 @@ impl NumericTrait for Decimal { fn safe_sqrt(self) -> Option { self.absolute().sqrt() } - fn to_bool(self) -> bool { - if self == dec!(0.0) { false } else { true } - } fn sign_reverse(self) -> Self { self * dec!(-1) } fn square(self) -> Self { self * self } - fn from_bool(v: bool) -> Self { - if v { dec!(1.0) } else { dec!(0.0) } - } } impl NumericTrait for i128 { @@ -120,20 +112,16 @@ impl NumericTrait for i128 { fn safe_sqrt(self) -> Option { Decimal::from_i128(self)?.absolute().sqrt()?.to_i128() } - fn to_bool(self) -> bool { - if self == 0 { false } else { true } - } fn sign_reverse(self) -> Self { -1 * self } fn square(self) -> Self { self * self } - fn from_bool(v: bool) -> Self { - if v { 1 } else { 0 } - } } +/// A trait for types to implement logical functions that work +/// for push types. pub trait LogicalTrait { fn logical_and(self, v: Self) -> Self; fn logical_or(self, v: Self) -> Self; @@ -158,3 +146,46 @@ impl LogicalTrait for bool { } } } + +/// A trait for uniform conversions between types. +pub trait CastingTrait: Sized { + fn from_bool(v: bool) -> Option; + fn from_int(v: i128) -> Option; + fn from_float(v: Decimal) -> Option; +} + +impl CastingTrait for i128 { + fn from_bool(v: bool) -> Option { + Some(if v { 1 } else { 0 }) + } + fn from_int(v: i128) -> Option { + Some(v) + } + fn from_float(v: Decimal) -> Option { + v.to_i128() + } +} + +impl CastingTrait for Decimal { + fn from_bool(v: bool) -> Option { + Some(if v { dec!(1.0) } else { dec!(0.0) }) + } + fn from_int(v: i128) -> Option { + Decimal::from_i128(v) + } + fn from_float(v: Decimal) -> Option { + Some(v) + } +} + +impl CastingTrait for bool { + fn from_bool(v: bool) -> Option { + Some(v) + } + fn from_int(v: i128) -> Option { + Some(if v != 0 { true } else { false }) + } + fn from_float(v: Decimal) -> Option { + Some(if v != dec!(0.0) { true } else { false }) + } +} diff --git a/src/main.rs b/src/main.rs index 91c96f5..53994f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,13 +20,17 @@ fn main() { // let result = dec!(1.0) / Decimal::QUARTER_PI.tan(); // let result = dec!(1.0) / Decimal::QUARTER_PI.cos(); // let result = dec!(1.2).checked_exp(); - let result = dec!(2).log10(); + // let result = dec!(2).log10(); + let result = vec![0, 1, 2]; + let r_len = result.len(); + let fin_result = &result[..r_len - 1]; + println!("{fin_result:?}"); - println!("{result:?}"); + // println!("{result:?}"); // println!("{sixth_pi}"); // casting a function call to a usize is a way to // test for function equality. - let test_func_result = test_func as usize == test_func as usize; - println!("{test_func_result}"); + // let test_func_result = test_func as usize == test_func as usize; + // println!("{test_func_result}"); }