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);