/* 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 .
*/
#![cfg_attr(not(test), no_std)]
extern crate alloc;
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::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 {
todo!("rational approximation implementation")
}
}
}
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)
}
}