/* Mycelium Scheme * Copyright (C) 2025 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 alloc::string::String; use alloc::format; use alloc::fmt::Debug; use core::{cmp::Ordering, f64, ops::{Add, Div, Mul, Sub}, str::FromStr}; use num::{integer::{gcd}, pow::{self, Pow}}; pub const E_INCOMPREHENSIBLE: &str = "could not comprehend number literal"; pub const E_BASE_PARSE_FAIL: &str = "failed to parse explicit base literal"; pub const E_POUND_TRUNCATED: &str = "pound sign implies additional input"; pub const E_UNKNOWN_CONTROL: &str = "unknown character in number literal"; pub const E_EMPTY_INPUT: &str = "empty string cannot be a number"; pub const E_UNKNOWN_SYMBOL: &str = "unknown symbolic number repr"; pub const E_NO_DENOMINATOR: &str = "fraction is missing a denominator"; pub const E_MULTI_DENOMINATOR: &str = "fraction has too many denominators"; pub const E_ZERO_DENOMINATOR: &str = "denominator cannot be zero"; pub const E_NUMERATOR_PARSE_FAIL: &str = "couldnt parse numerator"; pub const E_DENOMINATOR_PARSE_FAIL: &str = "couldnt parse denominator"; pub const E_FLOAT_PARSE_FAIL: &str = "couldnt parse float"; pub const E_SCIENTIFIC_E: &str = "scientific notation implies an 'e'"; pub const E_SCIENTIFIC_MULTI_E: &str = "scientific notation implies only a single 'e'"; pub const E_SCIENTIFIC_OPERAND: &str = "couldnt parse 32 bit float operand"; pub const E_SCIENTIFIC_POWER: &str = "couldnt parse integer power"; pub trait Numeric: Copy + Clone + Debug + FromStr + Into { fn is_exact(&self) -> bool; fn make_inexact(&self) -> Float; fn make_exact(&self) -> Fraction; } #[derive(Copy, Clone, Debug, PartialEq)] pub struct ScientificNotation (pub f32, pub isize); #[derive(Copy, Clone, Debug, PartialEq)] pub enum SymbolicNumber { Inf, NegInf, NaN, NegNan, } #[derive(Copy, Clone, Debug, PartialEq)] pub struct Fraction (pub isize, pub isize); #[derive(Copy, Clone, Debug, PartialEq)] pub struct Float (pub f64); #[derive(Copy, Clone, Debug)] pub enum Number { Sci(ScientificNotation), Fra(Fraction), Flt(Float), Sym(SymbolicNumber) } impl From for Number { fn from(value: SymbolicNumber) -> Self { Number::Sym(value) } } impl From for Number { fn from(value: ScientificNotation) -> Self { Number::Sci(value) } } impl From for Number { fn from(value: Fraction) -> Self { Number::Fra(value) } } impl From for Number { fn from(value: Float) -> Self { Number::Flt(value) } } // TODO: both the following impls should be done with a macro impl Into for Number { fn into(self) -> String { match self { Number::Sci(x) => x.into(), Number::Fra(x) => x.into(), Number::Flt(x) => x.into(), Number::Sym(x) => x.into(), } } } impl Numeric for Number { fn is_exact(&self) -> bool { match self { Number::Sci(x) => x.is_exact(), Number::Fra(x) => x.is_exact(), Number::Flt(x) => x.is_exact(), Number::Sym(x) => x.is_exact(), } } fn make_exact(&self) -> Fraction { match self { Number::Sci(x) => x.make_exact(), Number::Fra(x) => x.make_exact(), Number::Flt(x) => x.make_exact(), Number::Sym(x) => x.make_exact(), } } fn make_inexact(&self) -> Float { match self { Number::Sci(x) => x.make_inexact(), Number::Fra(x) => x.make_inexact(), Number::Flt(x) => x.make_inexact(), Number::Sym(x) => x.make_inexact(), } } } impl From for Number { fn from(value: f64) -> Self { match value { f64::INFINITY => Number::Sym(SymbolicNumber::Inf), f64::NEG_INFINITY => Number::Sym(SymbolicNumber::NegInf), _ if value.is_nan() => Number::Sym(SymbolicNumber::NaN), _ if value.fract() == 0.0 => Number::Fra(Fraction(value as isize, 1)), _ => Number::Flt(Float(value)) } } } impl FromStr for Number { type Err = &'static str; /* Number forms * - 1.3 * - 1e100 * - 1.3e100 * - +1.3 * - -2.3 * - #d124 * - #o2535 // base 8 * - #x8A3D // base 16 * - #b1011 // base 2 * - 2/4 // inexact * - #e1/5 // exact (fraction is as is) * - #e1e1 // exact 1e1 (= 10) * - #i1/5 // inexact (collapse fraction to decimal) * - +inf.0, -inf.0, +nan.0, -nan.0 */ fn from_str(value: &str) -> Result { let maybe_sym = value.parse::(); if maybe_sym.is_ok() { return Ok(Number::Sym(maybe_sym.unwrap())) } /* Only two things that we need to handle here. * 1. leading with a +/- * 2. leading with a #i or a #e * These are mutually exclusive options. * * Once they have been managed or ruled out we can * just try each number type */ let mut force_exact = false; let mut force_inexact = false; let mut base = 0; let mut iter = value.chars(); let mut start_idx: usize = 0; match iter.next() { Some('+') => start_idx = 1, Some('-') => start_idx = 0, Some('#') => { start_idx = 2; match iter.next() { None => return Err(E_POUND_TRUNCATED), Some('i') => force_inexact = true, Some('e') => force_exact = true, Some('x') => base = 16, Some('d') => base = 10, Some('o') => base = 8, Some('b') => base = 2, _ => return Err(E_UNKNOWN_CONTROL), } }, None => return Err(E_EMPTY_INPUT), _ => () } let substr = &value[start_idx..]; let res; if base > 0 { let num = isize::from_str_radix(substr, base) .or(Err(E_BASE_PARSE_FAIL))?; return Ok(Number::Fra(Fraction(num, 1))); } else if let Ok(num) = substr.parse::() { res = Number::Sci(num); } else if let Ok(num) = substr.parse::() { res = Number::Fra(num); } else if let Ok(num) = substr.parse::() { res = Number::Flt(num); } else { return Err(E_INCOMPREHENSIBLE) } if force_exact { return Ok(Number::Fra(res.make_exact())) } if force_inexact { return Ok(Number::Flt(res.make_inexact())) } Ok(res) } } impl Add for Number { type Output = Number; fn add(self, rhs: Self) -> Self::Output { if self.is_exact() && rhs.is_exact() { let Fraction(lnum, lden) = self.make_exact(); let Fraction(rnum, rden) = rhs.make_exact(); let num = (lnum * rden) + (rnum * lden); let den = lden * rden; Number::Fra(Fraction(num, den).simplify()) } else { let Float(l) = self.make_inexact(); let Float(r) = rhs.make_inexact(); let res = l + r; res.into() } } } impl Sub for Number { type Output = Number; fn sub(self, rhs: Self) -> Self::Output { if self.is_exact() && rhs.is_exact() { let Fraction(lnum, lden) = self.make_exact(); let Fraction(rnum, rden) = rhs.make_exact(); let num = (lnum * rden) - (rnum * lden); let den = lden * rden; Number::Fra(Fraction(num, den).simplify()) } else { let Float(l) = self.make_inexact(); let Float(r) = rhs.make_inexact(); let res = l - r; res.into() } } } impl Mul for Number { type Output = Number; fn mul(self, rhs: Self) -> Self::Output { if self.is_exact() && rhs.is_exact() { let Fraction(lnum, lden) = self.make_exact(); let Fraction(rnum, rden) = rhs.make_exact(); let num = lnum * rnum; let den = lden * rden; Number::Fra(Fraction(num, den).simplify()) } else { let Float(l) = self.make_inexact(); let Float(r) = rhs.make_inexact(); let res = l * r; res.into() } } } impl Div for Number { type Output = Number; fn div(self, rhs: Self) -> Self::Output { if self.is_exact() && rhs.is_exact() { let Fraction(lnum, lden) = self.make_exact(); let Fraction(rnum, rden) = rhs.make_exact(); let num = lnum * rden; let den = rnum * lden; Number::Fra(Fraction(num, den).simplify()) } else { let Float(l) = self.make_inexact(); let Float(r) = rhs.make_inexact(); let res = l / r; res.into() } } } impl Pow for Number { type Output = Number; fn pow(self, rhs: Number) -> Self::Output { if self.is_exact() && rhs.is_exact() { let Fraction(mut lnum, mut lden) = self .make_exact() .simplify(); let Fraction(rnum, rden) = rhs .make_exact() .simplify(); // flip first frac if second one is negative if rnum < 0 { let tmp = lnum; lnum = lden; lden = tmp; } // apply fractional exponent (denominator) let mut intermediate_numer = f64::powf(lnum as f64, 1.0 / rden as f64); let mut intermediate_denom = f64::powf(lden as f64, 1.0 / rden as f64); // apply whole exponent (numerator) intermediate_numer = f64::powf(intermediate_numer, rnum.abs() as f64); intermediate_denom = f64::powf(intermediate_denom, rnum.abs() as f64); if intermediate_numer.fract() == 0.0 && intermediate_denom.fract() == 0.0 { Number::Fra(Fraction(intermediate_numer as isize, intermediate_denom as isize) .simplify()) } else { (intermediate_numer / intermediate_denom).into() } // one or both are already inexact } else { let Float(l) = self.make_inexact(); let Float(r) = rhs.make_inexact(); let res = f64::powf(l, r); res.into() } } } impl PartialEq for Number { fn eq(&self, other: &Number) -> bool { //if self.is_exact() && other.is_exact() { // TODO: Figure out a way to comp two fractions without reducing // to a float and losing the precision of exact numbers //} else { self.make_inexact() == other.make_inexact() //} } } impl PartialOrd for Number{ fn partial_cmp(&self, other: &Self) -> Option { //if self.is_exact() && other.is_exact() { // TODO: Figure out a way to comp two fractions without reducing // to a float and losing the precision of exact numbers //} else { self.make_inexact().0.partial_cmp(&other.make_inexact().0) //} } } impl FromStr for SymbolicNumber { type Err = &'static str; fn from_str(value: &str) -> Result { match value { "+inf.0" => Ok(SymbolicNumber::Inf), "-inf.0" => Ok(SymbolicNumber::NegInf), "+nan.0" => Ok(SymbolicNumber::NaN), "-nan.0" => Ok(SymbolicNumber::NegNan), _ => Err(E_UNKNOWN_SYMBOL) } } } impl Into for SymbolicNumber { fn into(self) -> String { match self { SymbolicNumber::Inf => format!("+inf.0"), SymbolicNumber::NegInf => format!("-inf.0"), SymbolicNumber::NaN => format!("+nan.0"), SymbolicNumber::NegNan => format!("-nan.0"), } } } impl Numeric for SymbolicNumber { fn is_exact(&self) -> bool { false } fn make_inexact(&self) -> Float { match self { SymbolicNumber::Inf => Float(f64::INFINITY), SymbolicNumber::NegInf => Float(f64::NEG_INFINITY), SymbolicNumber::NaN | SymbolicNumber::NegNan => Float(f64::NAN), } } fn make_exact(&self) -> Fraction { panic!("attempted to make inf or nan into fraction") } } impl Fraction { fn simplify(&self) -> Fraction { let mut n = self.0; let mut d = self.1; /* normalize negative to numerator * if numerator is also negative this becomes a positive frac */ if self.1 < 0 { d = 0 - self.1; n = 0 - self.0; } /* divide both by their greatest common divisor * leading to simplest possible form */ let g = gcd(n, d); Fraction(n / g, d / g) } } impl FromStr for Fraction { type Err = &'static str; fn from_str(value: &str) -> Result { let part: usize; if let Some(idx) = value.find('/') { part = idx; } else { return Err(E_NO_DENOMINATOR) } // make sure there is ONLY ONE slash if let Some(idx) = value.rfind('/') && idx != part { return Err(E_MULTI_DENOMINATOR) } let numerator_text = &value[..part]; let denominator_text = &value[part+1..]; let numerator = numerator_text.parse::() .or(Err(E_NUMERATOR_PARSE_FAIL))?; let denominator = denominator_text.parse::() .or(Err(E_DENOMINATOR_PARSE_FAIL))?; if denominator == 0 { return Err(E_ZERO_DENOMINATOR) } Ok(Fraction(numerator, denominator)) } } impl Into for Fraction { fn into(self) -> String { format!("{}/{}", self.0, self.1) } } impl Numeric for Fraction { fn is_exact(&self) -> bool { true } fn make_inexact(&self) -> Float { Float(self.0 as f64 / self.1 as f64) } fn make_exact(&self) -> Fraction { *self } } impl FromStr for Float { type Err = &'static str; fn from_str(value: &str) -> Result { Ok(Float(value.parse::().or(Err(E_FLOAT_PARSE_FAIL))?)) } } impl Into for Float { fn into(self) -> String { if self.is_exact() { format!("{}", self.0) } else { format!("#i{}", self.0) } } } impl Numeric for Float { fn is_exact(&self) -> bool { self.0.fract() == 0.0 } fn make_inexact(&self) -> Float { *self } fn make_exact(&self) -> Fraction { if self.0.fract() == 0.0 { Fraction(self.0 as isize, 1) } else { unimplemented!("insert rational approximation procedure here") } } } impl FromStr for ScientificNotation { type Err = &'static str; fn from_str(value: &str) -> Result { let part: usize; if let Some(idx) = value.find('e') { part = idx; } else { return Err(E_SCIENTIFIC_E) } // make sure there is ONLY ONE slash if let Some(idx) = value.rfind('e') && idx != part { return Err(E_SCIENTIFIC_MULTI_E) } let operand_text = &value[..part]; let power_text = &value[part+1..]; let operand = operand_text.parse::() .or(Err(E_SCIENTIFIC_OPERAND))?; let power = power_text.parse::() .or(Err(E_SCIENTIFIC_POWER))?; Ok(ScientificNotation(operand, power)) } } impl Into for ScientificNotation { fn into(self) -> String { format!("{}e{}", self.0, self.1) } } impl Numeric for ScientificNotation { fn is_exact(&self) -> bool { self.0.fract() == 0.0 && self.1 >= 0 } fn make_inexact(&self) -> Float { Float(self.0 as f64 * f64::powi(10.0, self.1 as i32) as f64) } fn make_exact(&self) -> Fraction { self.make_inexact().make_exact() } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_fraction_tests() { assert_eq!("2/3".parse::(), Ok(Fraction(2, 3))); assert_eq!("0/1".parse::(), Ok(Fraction(0, 1))); assert_eq!("-1/34".parse::(), Ok(Fraction(-1, 34))); assert_eq!("2".parse::(), Err(E_NO_DENOMINATOR)); assert_eq!("2/2/2".parse::(), Err(E_MULTI_DENOMINATOR)); assert_eq!("2/0".parse::(), Err(E_ZERO_DENOMINATOR)); assert_eq!("3.3/3".parse::(), Err(E_NUMERATOR_PARSE_FAIL)); assert_eq!("2/two".parse::(), Err(E_DENOMINATOR_PARSE_FAIL)); } #[test] fn parse_scientific_notation_tests() { assert_eq!("2e3".parse::(), Ok(ScientificNotation(2.0, 3))); assert_eq!("0e1".parse::(), Ok(ScientificNotation(0.0, 1))); assert_eq!("-1e34".parse::(), Ok(ScientificNotation(-1.0, 34))); assert_eq!("3.3e3".parse::(), Ok(ScientificNotation(3.3, 3))); assert_eq!("2".parse::(), Err(E_SCIENTIFIC_E)); assert_eq!("2e2e2".parse::(), Err(E_SCIENTIFIC_MULTI_E)); assert_eq!("2etwo".parse::(), Err(E_SCIENTIFIC_POWER)); assert_eq!("twoe2".parse::(), Err(E_SCIENTIFIC_OPERAND)); } #[test] fn parse_number_tests() { assert_eq!("1.3".parse::(), Ok(Number::Flt(Float(1.3)))); assert_eq!("1".parse::(), Ok(Number::Flt(Float(1 as f64)))); assert_eq!("1.3e3".parse::(), Ok(Number::Sci(ScientificNotation(1.3, 3)))); assert_eq!("+1.3".parse::(), Ok(Number::Flt(Float(1.3)))); assert_eq!("-1.3".parse::(), Ok(Number::Flt(Float(-1.3)))); assert_eq!("#d234".parse::(), Ok(Number::Flt(Float(234.0)))); assert_eq!("#o17".parse::(), Ok(Number::Fra(Fraction(15, 1)))); assert_eq!("#xAA".parse::(), Ok(Number::Fra(Fraction(170, 1)))); assert_eq!("#b101".parse::(), Ok(Number::Flt(Float(5.0)))); assert_eq!("2/4".parse::(), Ok(Number::Fra(Fraction(2, 4)))); assert_eq!("#e1/5".parse::(), Ok(Number::Fra(Fraction(1, 5)))); assert_eq!("#i1/5".parse::(), Ok(Number::Flt(Float(0.2)))); assert_eq!("#e1e1".parse::(), Ok(Number::Sci(ScientificNotation(1.0, 1)))); assert_eq!("+inf.0".parse::(), Ok(Number::Sym(SymbolicNumber::Inf))); } #[test] fn test_number_addition_subtraction_cases() { let cases = vec![ vec!["1/5", "4/5", "1/1"], vec!["1/5", "0.8", "1/1"], vec!["1e1", "2.0", "12/1"], vec!["1e1", "2/1", "12/1"], vec!["1e1", "1/2", "10.5"], ]; cases.iter().for_each(|case| { println!("+ {:#?}", case); let x = case[0].parse::().unwrap(); let y = case[1].parse::().unwrap(); let z = case[2].parse::().unwrap(); // test some mathematical properties assert_eq!(x + y, z); assert_eq!(x + y, y + x); assert_eq!(z - x, y); assert_eq!(x + y - x, y); }); // theres no reason this should adhere to all the other rules let x = "+inf.0".parse::().unwrap(); let y = "1e1".parse::().unwrap(); let z = "+inf.0".parse::().unwrap(); assert_eq!(x + y, z); } #[test] fn test_number_multiplication_division_cases() { let cases = vec![ vec!["1/5", "5e0", "1/1"], vec!["1/5", "5", "1/1"], vec!["1/5", "2/1", "2/5"], vec!["4.4", "1/2", "2.2"], vec!["12.0", "1/2", "6/1"], vec!["1e1", "2.0", "20/1"], vec!["1e1", "2/1", "20/1"], vec!["1e1", "1/2", "5/1"], ]; cases.iter().for_each(|case| { println!("+ {:#?}", case); let x = case[0].parse::().unwrap(); let y = case[1].parse::().unwrap(); let z = case[2].parse::().unwrap(); // test some mathematical properties assert_eq!(x * y, z); assert_eq!(x * y, y * x); assert_eq!(z / x, y); assert_eq!(x * y / x, y); }); } #[test] fn test_number_pow_cases() { // TODO: add scientific notation cases let cases = vec![ vec!["2", "2", "4"], vec!["2/1", "2/1", "4/1"], vec!["2/1", "2/-1", "1/4"], vec!["2/1", "2/2", "2/1"], vec!["2/1", "2.0", "4/1"], vec!["27/8", "2/-3", "4/9"] ]; cases.iter().for_each(|case| { println!("+ {:#?}", case); let x = case[0].parse::().unwrap(); let y = case[1].parse::().unwrap(); let z = case[2].parse::().unwrap(); assert_eq!(x.pow(y), z); }); } #[test] fn test_number_ord_cases() { // TODO: add more cases let cases = vec![ vec!["1/2", "1.0", "1e1"], ]; cases.iter().for_each(|case| { println!("+ {:#?}", case); let x = case[0].parse::().unwrap(); let y = case[1].parse::().unwrap(); let z = case[2].parse::().unwrap(); assert!(x < y); assert!(y < z); assert!(x < z); }); } #[test] fn float_negative_exponent_case() { if let Float(0.1) = "1e-1" .parse::() .unwrap() .make_inexact() { return } assert!(false) } }