Number library and integrations

This commit adds a number library which handles fractions, floats,
whole numbers, scientific notation, and special symbolic numbers
all according to the R7RS small specification.

Numeric trait is used to abstract operations across all number types
and a Number enum is used to offer a non-opaque type that stores any
kind of number.

Upon the Number enum is implemented the following traits:
- Add, Div, Sub, Mul
- Pow
- PartialEq
- PartialOrd

Which then offer the following operators to use on the Number enum
instances themselves: + - / * == != < > <= >= and of course x.pow(y).

Additionally, the number package contains parsing logic for each type
of number. FromStr is implemented as part of the Numeric trait, and
then in turn implemented on Number. Additionally Into<String> is
implemented for the Numeric trait and then on the Number enum type
as well.

Test cases have been added for basic cases, but could be expanded.

Additional modifications:
- LexError has a custom display implementation that properly outputs
  formatted errors.
- Sexpr package updated to use new number package

Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2025-05-15 12:49:08 -07:00
parent 6554a0639a
commit 41216d3526
6 changed files with 992 additions and 31 deletions

82
Cargo.lock generated
View file

@ -6,6 +6,12 @@ version = 4
name = "amanita" name = "amanita"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "enoki" name = "enoki"
version = "0.1.0" version = "0.1.0"
@ -13,3 +19,79 @@ version = "0.1.0"
[[package]] [[package]]
name = "mycelium" name = "mycelium"
version = "0.1.0" version = "0.1.0"
dependencies = [
"num",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]

View file

@ -3,3 +3,6 @@ name = "mycelium"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies]
num = { version = "0.4.3", features = ["alloc"] }

View file

@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use core::fmt;
use alloc::rc::Rc; use alloc::rc::Rc;
pub const LEX_SPECIAL: [char; 18] = ['!', '$', '%', '&', '*', '+', '-', '/', pub const LEX_SPECIAL: [char; 18] = ['!', '$', '%', '&', '*', '+', '-', '/',
@ -37,19 +38,79 @@ pub const E_UNIMPLEMENTED_HEX: &str = "hexadecimal literals not supported";
pub const E_NUMER_BASE_ERR: &str = "digit in number exceeds specified base"; pub const E_NUMER_BASE_ERR: &str = "digit in number exceeds specified base";
pub const E_UNSUPPORTED_ESC: &str = "unsupported escape"; pub const E_UNSUPPORTED_ESC: &str = "unsupported escape";
pub const E_BAD_DOT: &str = "expected space after dot in dotted notation"; pub const E_BAD_DOT: &str = "expected space after dot in dotted notation";
pub const E_NO_SPLICE_TEMPL: &str = "expected more input after unquote splicing";
pub const E_INCOMPREHENSIBLE: &str = "token does not lex"; pub const E_INCOMPREHENSIBLE: &str = "token does not lex";
pub const E_END_OF_DOCUMENT: &str = "no additional input left in document"; pub const E_END_OF_DOCUMENT: &str = "no additional input left in document";
/* LexError /* LexError
* 0: error string * 0: error string
* 1: index into document * 1: index into document
* 2: document in question
*/ */
#[derive(Clone)] #[derive(Clone)]
pub struct LexError(pub &'static str, pub usize); pub struct LexError(pub &'static str, pub usize, pub Rc<str>);
impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let err_snippet_start = || -> usize {
/* backtrack from current index until we either hit
* - beginning of line
* - 25 characters ago
* - the doc Start
*/
if self.2.len() < 25 {
0
} else {
let mut idx = self.1;
while self.1 - idx > 25 {
idx -= 1;
if self.2[idx..]
.char_indices()
.next()
.is_some_and(|(i, x)| x == '\n' && i == idx) {
idx += 1;
break;
}
}
idx
}
};
let err_snippet_end = || -> usize {
/* read through document until we either hit
* - end of line
* - 25 characters forward
* - the doc end
*/
if self.2.len() - self.1 < 25 {
self.2.len()
} else {
let mut idx = self.1;
while idx - self.1 < 25 {
idx += 1;
if self.2[idx..]
.char_indices()
.next()
.is_some_and(|(i, x)| x == '\n' && i == idx) {
break;
}
}
idx
}
};
write!(f, "Error when lexing document here:\n\n")?;
write!(f, " {}\n", &self.2[err_snippet_start()..err_snippet_end()])?;
write!(f, "Error: {}\n", self.0)
}
}
#[repr(u8)] #[repr(u8)]
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub enum LexTokenType { pub enum LexTokenType {
String = 0, String = 0,
Number, Number,
@ -82,11 +143,12 @@ impl TryFrom<u8> for LexTokenType {
} }
#[derive(Clone)]
pub struct LexToken { pub struct LexToken {
token_type: LexTokenType, pub token_type: LexTokenType,
start_idx: usize, pub start_idx: usize,
end_idx: usize, pub end_idx: usize,
source_doc: Rc<str>, pub source_doc: Rc<str>,
} }
@ -94,7 +156,7 @@ pub struct Lexer {
document: Rc<str>, document: Rc<str>,
current_index: usize, current_index: usize,
current_token_start: usize, current_token_start: usize,
has_error_state: Option<LexError>, pub has_error_state: Option<LexError>,
} }
impl From<Rc<str>> for Lexer { impl From<Rc<str>> for Lexer {
@ -214,7 +276,8 @@ impl Lexer {
// TODO: support escaped quotes // TODO: support escaped quotes
loop { loop {
if let None = self.advance_char() { if let None = self.advance_char() {
return Err(LexError(E_NO_MATCHING_QUOTE, self.current_token_start)) return Err(LexError(E_NO_MATCHING_QUOTE,
self.current_token_start, self.document.clone()))
} else if self.current_char() == '"' { } else if self.current_char() == '"' {
return self.cut_new_token(LexTokenType::String) return self.cut_new_token(LexTokenType::String)
} }
@ -227,7 +290,8 @@ impl Lexer {
let a = self.current_char(); let a = self.current_char();
if NUMERICAL_BASE.contains(&a) { if NUMERICAL_BASE.contains(&a) {
if let None = self.advance_char() { if let None = self.advance_char() {
return Err(LexError(E_NUMBER_TRUNCATED, self.current_token_start)) return Err(LexError(E_NUMBER_TRUNCATED,
self.current_token_start, self.document.clone()))
} }
match a { match a {
'd' => base = 10, 'd' => base = 10,
@ -242,7 +306,8 @@ impl Lexer {
let a = self.current_char(); let a = self.current_char();
if NUMERICAL_EXTRA.contains(&a) { if NUMERICAL_EXTRA.contains(&a) {
if hasdot || base < 10 { if hasdot || base < 10 {
return Err(LexError(E_TOO_MANY_DECIMALS, self.current_token_start)) return Err(LexError(E_TOO_MANY_DECIMALS,
self.current_token_start, self.document.clone()))
} }
hasdot = true; hasdot = true;
@ -252,10 +317,12 @@ impl Lexer {
return self.cut_new_token(LexTokenType::Number) return self.cut_new_token(LexTokenType::Number)
} else if !a.is_numeric() { } else if !a.is_numeric() {
return Err(LexError(E_INCOMPREHENSIBLE, self.current_token_start)) return Err(LexError(E_INCOMPREHENSIBLE,
self.current_token_start, self.document.clone()))
} else if a.to_digit(10).unwrap() >= base { } else if a.to_digit(10).unwrap() >= base {
return Err(LexError(E_NUMER_BASE_ERR, self.current_token_start)) return Err(LexError(E_NUMER_BASE_ERR,
self.current_token_start, self.document.clone()))
} }
if let None = self.advance_char() { if let None = self.advance_char() {
@ -269,7 +336,8 @@ impl Lexer {
fn seek_end_of_block_comment(&mut self) -> Result<LexToken, LexError> { fn seek_end_of_block_comment(&mut self) -> Result<LexToken, LexError> {
loop { loop {
if let None = self.advance_char() { if let None = self.advance_char() {
return Err(LexError(E_UNCLOSED_COMMENT, self.current_token_start)) return Err(LexError(E_UNCLOSED_COMMENT,
self.current_token_start, self.document.clone()))
} }
match self.current_char() { match self.current_char() {
@ -287,7 +355,8 @@ impl Lexer {
fn seek_end_of_line_comment(&mut self, directive: bool) -> Result<LexToken, LexError> { fn seek_end_of_line_comment(&mut self, directive: bool) -> Result<LexToken, LexError> {
loop { loop {
if let None = self.advance_char() { if let None = self.advance_char() {
return Err(LexError(E_UNCLOSED_COMMENT, self.current_token_start)) return Err(LexError(E_UNCLOSED_COMMENT,
self.current_token_start, self.document.clone()))
} }
match self.current_char() { match self.current_char() {
@ -302,7 +371,8 @@ impl Lexer {
fn seek_closing_pipe(&mut self) -> Result<LexToken, LexError> { fn seek_closing_pipe(&mut self) -> Result<LexToken, LexError> {
loop { loop {
if let None = self.advance_char() { if let None = self.advance_char() {
return Err(LexError(E_NO_CLOSING_PIPE, self.current_token_start)); return Err(LexError(E_NO_CLOSING_PIPE,
self.current_token_start, self.document.clone()));
} }
let c = self.current_char(); let c = self.current_char();
@ -313,7 +383,8 @@ impl Lexer {
_ if LEX_SPECIAL.contains(&c) => continue, _ if LEX_SPECIAL.contains(&c) => continue,
_ if c == ' ' || c == '\n' => continue, _ if c == ' ' || c == '\n' => continue,
// quote case caught here // quote case caught here
_ => return Err(LexError(E_INCOMPREHENSIBLE, self.current_token_start)), _ => return Err(LexError(E_INCOMPREHENSIBLE,
self.current_token_start, self.document.clone())),
}; };
} }
} }
@ -331,12 +402,14 @@ impl Lexer {
'(' => return self.cut_new_token(LexTokenType::VectorStart), '(' => return self.cut_new_token(LexTokenType::VectorStart),
'\\' => self.seek_end_of_escape(false) '\\' => self.seek_end_of_escape(false)
.and_then(|_| self.cut_new_token(LexTokenType::Char)), .and_then(|_| self.cut_new_token(LexTokenType::Char)),
'x' => return Err(LexError(E_UNIMPLEMENTED_HEX, self.current_index)), 'x' => return Err(LexError(E_UNIMPLEMENTED_HEX,
self.current_index, self.document.clone())),
_ if NUMERICAL_BASE.contains(&ch) => return self.seek_end_of_number(), _ if NUMERICAL_BASE.contains(&ch) => return self.seek_end_of_number(),
_ => return Err(LexError(E_INCOMPREHENSIBLE, self.current_token_start)), _ => return Err(LexError(E_INCOMPREHENSIBLE,
self.current_token_start, self.document.clone())),
} }
} else { } else {
Err(LexError(E_NO_END_TO_HASH, self.current_token_start)) Err(LexError(E_NO_END_TO_HASH, self.current_token_start, self.document.clone()))
} }
} }
@ -349,15 +422,17 @@ impl Lexer {
if let None = self.advance_char() { if let None = self.advance_char() {
let mut error_msg = E_CHAR_TRUNCATED; let mut error_msg = E_CHAR_TRUNCATED;
if in_string { error_msg = E_STRING_TRUNCATED; } if in_string { error_msg = E_STRING_TRUNCATED; }
return Err(LexError(error_msg, self.current_token_start)) return Err(LexError(error_msg, self.current_token_start, self.document.clone()))
} }
match self.current_char() { match self.current_char() {
// eat an escaped whitespace or delim // eat an escaped whitespace or delim
' ' | 'n' | 'r' | 't' | '|' | '\\' | '"' => { () }, ' ' | 'n' | 'r' | 't' | '|' | '\\' | '"' => { () },
'x' => return Err(LexError(E_UNIMPLEMENTED_HEX, self.current_token_start)), 'x' => return Err(LexError(E_UNIMPLEMENTED_HEX,
self.current_token_start, self.document.clone())),
_ if self.current_char().is_alphabetic() => { () }, _ if self.current_char().is_alphabetic() => { () },
_ => return Err(LexError(E_UNSUPPORTED_ESC, self.current_index)), _ => return Err(LexError(E_UNSUPPORTED_ESC,
self.current_index, self.document.clone())),
} }
return Ok(()) return Ok(())
@ -372,12 +447,14 @@ impl Lexer {
let mut output: Option<Result<LexToken, LexError>> = None; let mut output: Option<Result<LexToken, LexError>> = None;
if self.current_index >= self.document.len() { if self.current_index >= self.document.len() {
return Err(LexError(E_END_OF_DOCUMENT, self.document.len())); return Err(LexError(E_END_OF_DOCUMENT,
self.document.len(), self.document.clone()));
} }
while LEX_WHITESPACE.contains(&self.current_char()) { while LEX_WHITESPACE.contains(&self.current_char()) {
if let None = self.advance_char() { if let None = self.advance_char() {
return Err(LexError(E_END_OF_DOCUMENT, self.document.len())); return Err(LexError(E_END_OF_DOCUMENT,
self.document.len(), self.document.clone()));
} }
} }
@ -426,7 +503,8 @@ impl Lexer {
loop { loop {
let c = self.current_char(); let c = self.current_char();
if !c.is_alphanumeric() && !LEX_SPECIAL.contains(&c) && c != ' ' { if !c.is_alphanumeric() && !LEX_SPECIAL.contains(&c) && c != ' ' {
output = Some(Err(LexError(E_INCOMPREHENSIBLE, self.current_index))); output = Some(Err(LexError(E_INCOMPREHENSIBLE,
self.current_index, self.document.clone())));
break; break;
} }
@ -466,7 +544,8 @@ mod tests {
/* String Cases */ ( /* String Cases */ (
// HAPPY CASES // HAPPY CASES
vec!["\"asdf\"", "\"as sdf\"", "\"asdflkj\\n\"", vec!["\"asdf\"", "\"as sdf\"", "\"asdflkj\\n\"",
"\"LKsldkf;l\"", "\" sdlkfj \"", "\"#;sdf\""], "\"LKsldkf;l\"", "\" sdlkfj \"", "\"#;sdf\"",
"\"\""],
// SAD CASES // SAD CASES
vec!["\"sdf"] vec!["\"sdf"]
@ -592,7 +671,7 @@ mod tests {
vec![",@x", ",@(", ",@"], vec![",@x", ",@(", ",@"],
// SAD CASES // SAD CASES
vec![","] vec![]
), ),
]; ];

View file

@ -18,8 +18,11 @@
#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_std)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(iter_collect_into)] #![feature(iter_collect_into)]
#![feature(impl_trait_in_assoc_type)]
pub mod sexpr; pub mod sexpr;
pub mod lexer; pub mod lexer;
pub mod parser;
pub mod number;
extern crate alloc; extern crate alloc;

782
mycelium/src/number.rs Normal file
View file

@ -0,0 +1,782 @@
/* 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 <https://www.gnu.org/licenses/>.
*/
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";
trait Numeric: Copy + Clone + Debug + FromStr + Into<String> {
fn is_exact(&self) -> bool;
fn make_inexact(&self) -> Float;
fn make_exact(&self) -> Fraction;
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScientificNotation (f32, isize);
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum SymbolicNumber {
Inf,
NegInf,
NaN,
NegNan,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Fraction (isize, isize);
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Float (f64);
#[derive(Copy, Clone, Debug)]
pub enum Number {
Sci(ScientificNotation),
Fra(Fraction),
Flt(Float),
Sym(SymbolicNumber)
}
impl From<SymbolicNumber> for Number {
fn from(value: SymbolicNumber) -> Self {
Number::Sym(value)
}
}
impl From<ScientificNotation> for Number {
fn from(value: ScientificNotation) -> Self {
Number::Sci(value)
}
}
impl From<Fraction> for Number {
fn from(value: Fraction) -> Self {
Number::Fra(value)
}
}
impl From<Float> for Number {
fn from(value: Float) -> Self {
Number::Flt(value)
}
}
// TODO: both the following impls should be done with a macro
impl Into<String> 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<f64> 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<Self, Self::Err> {
let maybe_sym = value.parse::<SymbolicNumber>();
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::<ScientificNotation>() {
res = Number::Sci(num);
} else if let Ok(num) = substr.parse::<Fraction>() {
res = Number::Fra(num);
} else if let Ok(num) = substr.parse::<Float>() {
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<Number> for Number {
type Output = Number;
fn pow(self, rhs: Number) -> Self::Output {
if self.is_exact() && rhs.is_exact() {
let Fraction(lnum, lden) = self.make_exact();
let Fraction(mut rnum, mut rden) = rhs.make_exact();
// normalize the negative to the top of the fraction
if rden < 0 {
rnum = 0 - rnum;
rden = 0 - rden;
}
// apply whole exponent (numerator)
let mut intermediate_numer =
pow::pow(lnum, rnum.abs() as usize) as f64;
let mut intermediate_denom =
pow::pow(lden, rnum.abs() as usize) as f64;
// handle negative exponent
if rnum < 0 {
intermediate_numer = 1.0 / intermediate_numer;
intermediate_denom = 1.0 / intermediate_denom;
}
// dont bother taking an nth root where n=1
if rden == 1 {
if intermediate_numer.fract() == 0.0 &&
intermediate_denom.fract() == 0.0 {
// return whatever the float decides :)
(intermediate_numer / intermediate_denom).into()
// we still have whole numbers everywhere
} else {
Number::Fra(Fraction(intermediate_numer as isize, intermediate_denom as isize))
}
// gotta take an nth root (right hand denom > 1)
} else {
let num_res =
f64::powf(intermediate_numer as f64, 1.0 / rden as f64);
let den_res =
f64::powf(intermediate_denom as f64, 1.0 / rden as f64);
if num_res.fract() == 0.0 && den_res.fract() == 0.0 {
Number::Fra(Fraction(num_res as isize, den_res as isize))
} else {
(num_res / den_res).into()
}
}
} 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<Ordering> {
//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<Self, Self::Err> {
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<String> 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 g = gcd(self.0, self.1);
Fraction(self.0 / g, self.1 / g)
}
}
impl FromStr for Fraction {
type Err = &'static str;
fn from_str(value: &str) -> Result<Self, Self::Err> {
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::<isize>()
.or(Err(E_NUMERATOR_PARSE_FAIL))?;
let denominator = denominator_text.parse::<isize>()
.or(Err(E_DENOMINATOR_PARSE_FAIL))?;
if denominator == 0 {
return Err(E_ZERO_DENOMINATOR)
}
Ok(Fraction(numerator, denominator))
}
}
impl Into<String> for Fraction {
fn into(self) -> String {
format!("#e{}/{}", 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<Self, Self::Err> {
Ok(Float(value.parse::<f64>().or(Err(E_FLOAT_PARSE_FAIL))?))
}
}
impl Into<String> for Float {
fn into(self) -> String {
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<Self, Self::Err> {
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::<f32>()
.or(Err(E_SCIENTIFIC_OPERAND))?;
let power = power_text.parse::<isize>()
.or(Err(E_SCIENTIFIC_POWER))?;
Ok(ScientificNotation(operand, power))
}
}
impl Into<String> 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 {
// TODO: This pow function needs to be replaced with one that can handle negative exponents
Float(self.0 as f64 * pow::pow(10, self.1 as usize) 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::<Fraction>(),
Ok(Fraction(2, 3)));
assert_eq!("0/1".parse::<Fraction>(),
Ok(Fraction(0, 1)));
assert_eq!("-1/34".parse::<Fraction>(),
Ok(Fraction(-1, 34)));
assert_eq!("2".parse::<Fraction>(),
Err(E_NO_DENOMINATOR));
assert_eq!("2/2/2".parse::<Fraction>(),
Err(E_MULTI_DENOMINATOR));
assert_eq!("2/0".parse::<Fraction>(),
Err(E_ZERO_DENOMINATOR));
assert_eq!("3.3/3".parse::<Fraction>(),
Err(E_NUMERATOR_PARSE_FAIL));
assert_eq!("2/two".parse::<Fraction>(),
Err(E_DENOMINATOR_PARSE_FAIL));
}
#[test]
fn parse_scientific_notation_tests() {
assert_eq!("2e3".parse::<ScientificNotation>(),
Ok(ScientificNotation(2.0, 3)));
assert_eq!("0e1".parse::<ScientificNotation>(),
Ok(ScientificNotation(0.0, 1)));
assert_eq!("-1e34".parse::<ScientificNotation>(),
Ok(ScientificNotation(-1.0, 34)));
assert_eq!("3.3e3".parse::<ScientificNotation>(),
Ok(ScientificNotation(3.3, 3)));
assert_eq!("2".parse::<ScientificNotation>(),
Err(E_SCIENTIFIC_E));
assert_eq!("2e2e2".parse::<ScientificNotation>(),
Err(E_SCIENTIFIC_MULTI_E));
assert_eq!("2etwo".parse::<ScientificNotation>(),
Err(E_SCIENTIFIC_POWER));
assert_eq!("twoe2".parse::<ScientificNotation>(),
Err(E_SCIENTIFIC_OPERAND));
}
#[test]
fn parse_number_tests() {
assert_eq!("1.3".parse::<Number>(),
Ok(Number::Flt(Float(1.3))));
assert_eq!("1".parse::<Number>(),
Ok(Number::Flt(Float(1 as f64))));
assert_eq!("1.3e3".parse::<Number>(),
Ok(Number::Sci(ScientificNotation(1.3, 3))));
assert_eq!("+1.3".parse::<Number>(),
Ok(Number::Flt(Float(1.3))));
assert_eq!("-1.3".parse::<Number>(),
Ok(Number::Flt(Float(-1.3))));
assert_eq!("#d234".parse::<Number>(),
Ok(Number::Flt(Float(234.0))));
assert_eq!("#o17".parse::<Number>(),
Ok(Number::Fra(Fraction(15, 1))));
assert_eq!("#xAA".parse::<Number>(),
Ok(Number::Fra(Fraction(170, 1))));
assert_eq!("#b101".parse::<Number>(),
Ok(Number::Flt(Float(5.0))));
assert_eq!("2/4".parse::<Number>(),
Ok(Number::Fra(Fraction(2, 4))));
assert_eq!("#e1/5".parse::<Number>(),
Ok(Number::Fra(Fraction(1, 5))));
assert_eq!("#i1/5".parse::<Number>(),
Ok(Number::Flt(Float(0.2))));
assert_eq!("#e1e1".parse::<Number>(),
Ok(Number::Sci(ScientificNotation(1.0, 1))));
assert_eq!("+inf.0".parse::<Number>(),
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::<Number>().unwrap();
let y = case[1].parse::<Number>().unwrap();
let z = case[2].parse::<Number>().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::<Number>().unwrap();
let y = "1e1".parse::<Number>().unwrap();
let z = "+inf.0".parse::<Number>().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::<Number>().unwrap();
let y = case[1].parse::<Number>().unwrap();
let z = case[2].parse::<Number>().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"]
];
cases.iter().for_each(|case| {
println!("+ {:#?}", case);
let x = case[0].parse::<Number>().unwrap();
let y = case[1].parse::<Number>().unwrap();
let z = case[2].parse::<Number>().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::<Number>().unwrap();
let y = case[1].parse::<Number>().unwrap();
let z = case[2].parse::<Number>().unwrap();
assert!(x < y);
assert!(y < z);
assert!(x < z);
});
}
}

View file

@ -20,12 +20,15 @@ use alloc::rc::Rc;
use alloc::vec::Vec; use alloc::vec::Vec;
use alloc::string::String; use alloc::string::String;
use crate::number::Number;
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub enum Datum { pub enum Datum {
Number(f64), Number(Number),
Bool(bool), Bool(bool),
List(Ast), List(Ast),
Symbol(String), Symbol(String),
Char(u8),
String(Vec<u8>), String(Vec<u8>),
Vector(Vec<Datum>), Vector(Vec<Datum>),
ByteVector(Vec<u8>), ByteVector(Vec<u8>),
@ -33,13 +36,19 @@ pub enum Datum {
None, None,
} }
fn byte_to_escaped_char(b: u8) -> String {
unimplemented!()
}
impl fmt::Display for Datum { impl fmt::Display for Datum {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
Datum::Number(n) => write!(f, "{n}"), Datum::Number(n) => write!(f, "{}", Into::<String>::into(*n)),
Datum::Bool(n) => write!(f, "{n}"), Datum::Bool(n) => write!(f, "{n}"),
Datum::List(n) => write!(f, "{n}"), Datum::List(n) => write!(f, "{n}"),
Datum::Symbol(n) => write!(f, "{n}"), Datum::Symbol(n) => write!(f, "{n}"),
Datum::Char(n) => write!(f, "{}",
byte_to_escaped_char(*n)),
Datum::String(n) => Datum::String(n) =>
write!(f, "\"{}\"", String::from_utf8_lossy(&*n)), write!(f, "\"{}\"", String::from_utf8_lossy(&*n)),
Datum::Vector(n) => write!(f, "#({n:?})"), Datum::Vector(n) => write!(f, "#({n:?})"),
@ -58,9 +67,11 @@ impl fmt::Display for Datum {
impl fmt::Debug for Datum { impl fmt::Debug for Datum {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
Datum::Number(n) => write!(f, "{n}"), Datum::Number(n) => write!(f, "{}", Into::<String>::into(*n)),
Datum::Bool(n) => write!(f, "{n}"), Datum::Bool(n) => write!(f, "{n}"),
Datum::List(n) => write!(f, "{n}"), Datum::List(n) => write!(f, "{n}"),
Datum::Char(n) => write!(f, "{}",
byte_to_escaped_char(*n)),
Datum::Symbol(n) => write!(f, "{n}"), Datum::Symbol(n) => write!(f, "{n}"),
Datum::String(n) => Datum::String(n) =>
write!(f, "\"{}\"", String::from_utf8_lossy(&*n)), write!(f, "\"{}\"", String::from_utf8_lossy(&*n)),
@ -71,6 +82,7 @@ impl fmt::Debug for Datum {
} }
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct Ast(Rc<Datum>, Rc<Datum>); pub struct Ast(Rc<Datum>, Rc<Datum>);