diff --git a/Cargo.lock b/Cargo.lock index 6481880..e251ead 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,12 @@ version = 4 name = "amanita" version = "0.1.0" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "enoki" version = "0.1.0" @@ -13,3 +19,79 @@ version = "0.1.0" [[package]] name = "mycelium" 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", +] diff --git a/mycelium/Cargo.toml b/mycelium/Cargo.toml index 83681f2..7bc4e79 100644 --- a/mycelium/Cargo.toml +++ b/mycelium/Cargo.toml @@ -3,3 +3,6 @@ name = "mycelium" version = "0.1.0" edition = "2021" +[dependencies] +num = { version = "0.4.3", features = ["alloc"] } + diff --git a/mycelium/src/lexer.rs b/mycelium/src/lexer.rs index 123af56..3bf4016 100644 --- a/mycelium/src/lexer.rs +++ b/mycelium/src/lexer.rs @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +use core::fmt; use alloc::rc::Rc; 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_UNSUPPORTED_ESC: &str = "unsupported escape"; 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_END_OF_DOCUMENT: &str = "no additional input left in document"; /* LexError * 0: error string * 1: index into document + * 2: document in question */ #[derive(Clone)] -pub struct LexError(pub &'static str, pub usize); +pub struct LexError(pub &'static str, pub usize, pub Rc); + +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)] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum LexTokenType { String = 0, Number, @@ -82,11 +143,12 @@ impl TryFrom for LexTokenType { } +#[derive(Clone)] pub struct LexToken { - token_type: LexTokenType, - start_idx: usize, - end_idx: usize, - source_doc: Rc, + pub token_type: LexTokenType, + pub start_idx: usize, + pub end_idx: usize, + pub source_doc: Rc, } @@ -94,7 +156,7 @@ pub struct Lexer { document: Rc, current_index: usize, current_token_start: usize, - has_error_state: Option, + pub has_error_state: Option, } impl From> for Lexer { @@ -214,7 +276,8 @@ impl Lexer { // TODO: support escaped quotes loop { 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() == '"' { return self.cut_new_token(LexTokenType::String) } @@ -227,7 +290,8 @@ impl Lexer { let a = self.current_char(); if NUMERICAL_BASE.contains(&a) { 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 { 'd' => base = 10, @@ -242,7 +306,8 @@ impl Lexer { let a = self.current_char(); if NUMERICAL_EXTRA.contains(&a) { 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; @@ -252,10 +317,12 @@ impl Lexer { return self.cut_new_token(LexTokenType::Number) } 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 { - 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() { @@ -269,7 +336,8 @@ impl Lexer { fn seek_end_of_block_comment(&mut self) -> Result { loop { 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() { @@ -287,7 +355,8 @@ impl Lexer { fn seek_end_of_line_comment(&mut self, directive: bool) -> Result { loop { 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() { @@ -302,7 +371,8 @@ impl Lexer { fn seek_closing_pipe(&mut self) -> Result { loop { 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(); @@ -313,7 +383,8 @@ impl Lexer { _ if LEX_SPECIAL.contains(&c) => continue, _ if c == ' ' || c == '\n' => continue, // 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), '\\' => self.seek_end_of_escape(false) .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(), - _ => return Err(LexError(E_INCOMPREHENSIBLE, self.current_token_start)), + _ => return Err(LexError(E_INCOMPREHENSIBLE, + self.current_token_start, self.document.clone())), } } 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() { let mut error_msg = E_CHAR_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() { // eat an escaped whitespace or delim ' ' | '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() => { () }, - _ => return Err(LexError(E_UNSUPPORTED_ESC, self.current_index)), + _ => return Err(LexError(E_UNSUPPORTED_ESC, + self.current_index, self.document.clone())), } return Ok(()) @@ -372,12 +447,14 @@ impl Lexer { let mut output: Option> = None; 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()) { 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 { let c = self.current_char(); 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; } @@ -466,7 +544,8 @@ mod tests { /* String Cases */ ( // HAPPY CASES vec!["\"asdf\"", "\"as sdf\"", "\"asdflkj\\n\"", - "\"LKsldkf;l\"", "\" sdlkfj \"", "\"#;sdf\""], + "\"LKsldkf;l\"", "\" sdlkfj \"", "\"#;sdf\"", + "\"\""], // SAD CASES vec!["\"sdf"] @@ -592,7 +671,7 @@ mod tests { vec![",@x", ",@(", ",@"], // SAD CASES - vec![","] + vec![] ), ]; diff --git a/mycelium/src/lib.rs b/mycelium/src/lib.rs index 8401f89..42130cc 100644 --- a/mycelium/src/lib.rs +++ b/mycelium/src/lib.rs @@ -18,8 +18,11 @@ #![cfg_attr(not(test), no_std)] #![feature(let_chains)] #![feature(iter_collect_into)] +#![feature(impl_trait_in_assoc_type)] pub mod sexpr; pub mod lexer; +pub mod parser; +pub mod number; extern crate alloc; diff --git a/mycelium/src/number.rs b/mycelium/src/number.rs new file mode 100644 index 0000000..f581531 --- /dev/null +++ b/mycelium/src/number.rs @@ -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 . + */ + +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 { + 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 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(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 { + //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 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 { + 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!("#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 { + Ok(Float(value.parse::().or(Err(E_FLOAT_PARSE_FAIL))?)) + } +} + +impl Into 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 { + 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 { + // 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::(), + 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"] + ]; + + 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); + }); + } +} diff --git a/mycelium/src/sexpr.rs b/mycelium/src/sexpr.rs index a143af1..2219f4e 100644 --- a/mycelium/src/sexpr.rs +++ b/mycelium/src/sexpr.rs @@ -20,12 +20,15 @@ use alloc::rc::Rc; use alloc::vec::Vec; use alloc::string::String; +use crate::number::Number; + #[derive(Default, Clone)] pub enum Datum { - Number(f64), + Number(Number), Bool(bool), List(Ast), Symbol(String), + Char(u8), String(Vec), Vector(Vec), ByteVector(Vec), @@ -33,13 +36,19 @@ pub enum Datum { None, } +fn byte_to_escaped_char(b: u8) -> String { + unimplemented!() +} + impl fmt::Display for Datum { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Datum::Number(n) => write!(f, "{n}"), + Datum::Number(n) => write!(f, "{}", Into::::into(*n)), Datum::Bool(n) => write!(f, "{n}"), Datum::List(n) => write!(f, "{n}"), Datum::Symbol(n) => write!(f, "{n}"), + Datum::Char(n) => write!(f, "{}", + byte_to_escaped_char(*n)), Datum::String(n) => write!(f, "\"{}\"", String::from_utf8_lossy(&*n)), Datum::Vector(n) => write!(f, "#({n:?})"), @@ -58,9 +67,11 @@ impl fmt::Display for Datum { impl fmt::Debug for Datum { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Datum::Number(n) => write!(f, "{n}"), + Datum::Number(n) => write!(f, "{}", Into::::into(*n)), Datum::Bool(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::String(n) => write!(f, "\"{}\"", String::from_utf8_lossy(&*n)), @@ -71,6 +82,7 @@ impl fmt::Debug for Datum { } } + #[derive(Default, Clone)] pub struct Ast(Rc, Rc);