more tests/start on code instructions

This commit is contained in:
Rowan Torbitzky-Lane 2025-04-06 23:50:14 -05:00
parent 5fe9ca40c6
commit 8eeef3a928
6 changed files with 494 additions and 81 deletions

296
src/instructions/code.rs Normal file
View File

@ -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<Gene>) -> Option<bool> {
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<Gene>) -> Option<bool> {
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<Gene>) -> Option<i128> {
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<Gene>) -> Option<Gene> {
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<Gene>) -> Option<Gene> {
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<Gene>) -> Option<Gene> {
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<Gene>) -> Option<Gene> {
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<Gene>) -> Option<Gene> {
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<Gene>) -> Option<Gene> {
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<Gene> = 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<Gene> = 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<Gene> = 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<Gene> = 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
}
}

View File

@ -2,8 +2,9 @@
//! //!
//! This file holds instructions for the boolean stack. //! This file holds instructions for the boolean stack.
use super::utils::{LogicalTrait, NumericTrait}; use super::utils::{CastingTrait, LogicalTrait};
use crate::push::state::PushState; use crate::push::state::PushState;
use rust_decimal::Decimal;
/// Runs logical and on two values /// Runs logical and on two values
fn _and<T>(vals: Vec<T>) -> Option<T> fn _and<T>(vals: Vec<T>) -> Option<T>
@ -59,17 +60,40 @@ where
} }
make_instruction!(boolean, boolean, _invert_second_then_and, bool, 2); make_instruction!(boolean, boolean, _invert_second_then_and, bool, 2);
// fn _to_int<T>(vals: Vec<T>) -> Option<T> fn _from_int<T>(vals: Vec<i128>) -> Option<T>
// where where
// T: Copy + NumericTrait, T: Copy + CastingTrait,
// { {
// Some(T::from_bool(vals)) T::from_int(vals[0])
// } }
make_instruction_out!(int, boolean, _from_int, i128, 1);
fn _from_float<T>(vals: Vec<Decimal>) -> Option<T>
where
T: Copy + CastingTrait,
{
T::from_float(vals[0])
}
make_instruction_out!(float, boolean, _from_float, Decimal, 1);
pub fn boolean_instructions() -> Vec<fn(&mut PushState)> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::push::state::EMPTY_STATE; use crate::push::state::EMPTY_STATE;
use rust_decimal::dec;
#[test] #[test]
fn and_test() { fn and_test() {
@ -147,4 +171,28 @@ mod tests {
boolean_invert_second_then_and(&mut test_state); boolean_invert_second_then_and(&mut test_state);
assert_eq!(vec![true], test_state.boolean); 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);
}
} }

View File

@ -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 logical;
pub mod numeric; pub mod numeric;
pub mod utils; pub mod utils;

View File

@ -11,7 +11,7 @@ use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::ops::{Add, Div, Mul, Sub}; use std::ops::{Add, Div, Mul, Sub};
use super::utils::NumericTrait; use super::utils::{CastingTrait, NumericTrait};
/// Adds two addable values together. /// Adds two addable values together.
fn _add<T>(vals: Vec<T>) -> Option<T> fn _add<T>(vals: Vec<T>) -> Option<T>
@ -203,27 +203,33 @@ where
make_instruction!(int, int, _arctan, i128, 1); make_instruction!(int, int, _arctan, i128, 1);
make_instruction!(float, float, _arctan, Decimal, 1); make_instruction!(float, float, _arctan, Decimal, 1);
/// Converts the top int to a float. /// Converts a single value from a float to an arbitrary type.
fn _to_int(vals: Vec<Decimal>) -> Option<i128> { fn _from_int<T>(vals: Vec<i128>) -> Option<T>
vals[0].to_i128()
}
make_instruction!(float, int, _to_int, Decimal, 1);
/// Converts the top float to an int.
fn _to_float(vals: Vec<i128>) -> Option<Decimal> {
Decimal::from_i128(vals[0])
}
make_instruction!(int, float, _to_float, i128, 1);
/// Converts a single number to a bool.
fn _from_bool<T>(vals: Vec<bool>) -> Option<T>
where 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_out!(int, float, _from_int, i128, 1);
make_instruction!(float, boolean, _from_bool, bool, 1);
/// Converts a single value from a float to an arbitrary type.
fn _from_float<T>(vals: Vec<Decimal>) -> Option<T>
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<T>(vals: Vec<bool>) -> Option<T>
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 /// Takes the log base 10 of a single Decimal. Acts as a
/// NoOp if the value is 0. If the value is negative, takes /// NoOp if the value is 0. If the value is negative, takes
@ -321,8 +327,8 @@ pub fn int_instructions() -> Vec<fn(&mut PushState)> {
int_arccos, int_arccos,
int_tan, int_tan,
int_arctan, int_arctan,
int_to_float, int_from_float,
int_to_bool, int_from_boolean,
int_log, int_log,
int_exp, int_exp,
int_sqrt, int_sqrt,
@ -355,8 +361,8 @@ pub fn float_instructions() -> Vec<fn(&mut PushState)> {
float_arccos, float_arccos,
float_tan, float_tan,
float_arctan, float_arctan,
float_to_int, float_from_int,
float_to_bool, float_from_boolean,
float_log, float_log,
float_exp, float_exp,
float_sqrt, float_sqrt,
@ -542,17 +548,6 @@ mod tests {
assert_eq!(Some(dec!(1.0000000043184676055890307049)), _arctan(vals)); 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<i128> = vec![2];
assert_eq!(Some(dec!(2.0)), _to_float(vals));
}
/// Tests that the various addition functions. /// Tests that the various addition functions.
#[test] #[test]
fn state_add() { fn state_add() {
@ -793,32 +788,13 @@ mod tests {
let mut test_state = EMPTY_STATE; let mut test_state = EMPTY_STATE;
test_state.int = vec![0, 1]; 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); assert_eq!(vec![dec!(1.0)], test_state.float);
test_state.int.clear(); test_state.int.clear();
test_state.float = vec![dec!(2.1)]; 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); 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 /// Tests the log function

View File

@ -21,10 +21,8 @@ pub trait NumericTrait: Sized + Div<Output = Self> {
fn absolute(self) -> Self; fn absolute(self) -> Self;
fn safe_log10(self) -> Option<Self>; fn safe_log10(self) -> Option<Self>;
fn safe_sqrt(self) -> Option<Self>; fn safe_sqrt(self) -> Option<Self>;
fn to_bool(self) -> bool;
fn sign_reverse(self) -> Self; fn sign_reverse(self) -> Self;
fn square(self) -> Self; fn square(self) -> Self;
fn from_bool(v: bool) -> Self;
} }
impl NumericTrait for Decimal { impl NumericTrait for Decimal {
@ -64,18 +62,12 @@ impl NumericTrait for Decimal {
fn safe_sqrt(self) -> Option<Self> { fn safe_sqrt(self) -> Option<Self> {
self.absolute().sqrt() self.absolute().sqrt()
} }
fn to_bool(self) -> bool {
if self == dec!(0.0) { false } else { true }
}
fn sign_reverse(self) -> Self { fn sign_reverse(self) -> Self {
self * dec!(-1) self * dec!(-1)
} }
fn square(self) -> Self { fn square(self) -> Self {
self * self self * self
} }
fn from_bool(v: bool) -> Self {
if v { dec!(1.0) } else { dec!(0.0) }
}
} }
impl NumericTrait for i128 { impl NumericTrait for i128 {
@ -120,20 +112,16 @@ impl NumericTrait for i128 {
fn safe_sqrt(self) -> Option<Self> { fn safe_sqrt(self) -> Option<Self> {
Decimal::from_i128(self)?.absolute().sqrt()?.to_i128() Decimal::from_i128(self)?.absolute().sqrt()?.to_i128()
} }
fn to_bool(self) -> bool {
if self == 0 { false } else { true }
}
fn sign_reverse(self) -> Self { fn sign_reverse(self) -> Self {
-1 * self -1 * self
} }
fn square(self) -> Self { fn square(self) -> Self {
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 { pub trait LogicalTrait {
fn logical_and(self, v: Self) -> Self; fn logical_and(self, v: Self) -> Self;
fn logical_or(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<Self>;
fn from_int(v: i128) -> Option<Self>;
fn from_float(v: Decimal) -> Option<Self>;
}
impl CastingTrait for i128 {
fn from_bool(v: bool) -> Option<Self> {
Some(if v { 1 } else { 0 })
}
fn from_int(v: i128) -> Option<Self> {
Some(v)
}
fn from_float(v: Decimal) -> Option<Self> {
v.to_i128()
}
}
impl CastingTrait for Decimal {
fn from_bool(v: bool) -> Option<Self> {
Some(if v { dec!(1.0) } else { dec!(0.0) })
}
fn from_int(v: i128) -> Option<Self> {
Decimal::from_i128(v)
}
fn from_float(v: Decimal) -> Option<Self> {
Some(v)
}
}
impl CastingTrait for bool {
fn from_bool(v: bool) -> Option<Self> {
Some(v)
}
fn from_int(v: i128) -> Option<Self> {
Some(if v != 0 { true } else { false })
}
fn from_float(v: Decimal) -> Option<Self> {
Some(if v != dec!(0.0) { true } else { false })
}
}

View File

@ -20,13 +20,17 @@ fn main() {
// let result = dec!(1.0) / Decimal::QUARTER_PI.tan(); // let result = dec!(1.0) / Decimal::QUARTER_PI.tan();
// let result = dec!(1.0) / Decimal::QUARTER_PI.cos(); // let result = dec!(1.0) / Decimal::QUARTER_PI.cos();
// let result = dec!(1.2).checked_exp(); // 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}"); // println!("{sixth_pi}");
// casting a function call to a usize is a way to // casting a function call to a usize is a way to
// test for function equality. // test for function equality.
let test_func_result = test_func as usize == test_func as usize; // let test_func_result = test_func as usize == test_func as usize;
println!("{test_func_result}"); // println!("{test_func_result}");
} }