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:
parent
6554a0639a
commit
41216d3526
6 changed files with 992 additions and 31 deletions
|
|
@ -15,6 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<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)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum LexTokenType {
|
||||
String = 0,
|
||||
Number,
|
||||
|
|
@ -82,11 +143,12 @@ impl TryFrom<u8> for LexTokenType {
|
|||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LexToken {
|
||||
token_type: LexTokenType,
|
||||
start_idx: usize,
|
||||
end_idx: usize,
|
||||
source_doc: Rc<str>,
|
||||
pub token_type: LexTokenType,
|
||||
pub start_idx: usize,
|
||||
pub end_idx: usize,
|
||||
pub source_doc: Rc<str>,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -94,7 +156,7 @@ pub struct Lexer {
|
|||
document: Rc<str>,
|
||||
current_index: usize,
|
||||
current_token_start: usize,
|
||||
has_error_state: Option<LexError>,
|
||||
pub has_error_state: Option<LexError>,
|
||||
}
|
||||
|
||||
impl From<Rc<str>> 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<LexToken, LexError> {
|
||||
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<LexToken, LexError> {
|
||||
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<LexToken, LexError> {
|
||||
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<Result<LexToken, LexError>> = 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![]
|
||||
),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
782
mycelium/src/number.rs
Normal file
782
mycelium/src/number.rs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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<u8>),
|
||||
Vector(Vec<Datum>),
|
||||
ByteVector(Vec<u8>),
|
||||
|
|
@ -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::<String>::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::<String>::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<Datum>, Rc<Datum>);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue