From 5582da5b4149c8a0540d7e320620bf8e9d65fb02 Mon Sep 17 00:00:00 2001 From: Ava Affine Date: Wed, 3 Dec 2025 22:14:48 +0000 Subject: [PATCH] Begin implementing human interface for Hyphae This commit implements Display and FromStr for Datum, Operation, Operand, Instruction, and Program types. Additionally, tests are added for the new routines. This change was implemented in furtherance of a command line assembler and disassembler, as well as for the implementation of a debugger. --- Cargo.lock | 8 + Cargo.toml | 2 +- hyphae/build.rs | 20 +- hyphae/src/heap.rs | 58 ++- hyphae/src/serializer.rs | 200 +++++++++- hyphae/src/vm.rs | 8 + snippets/in_progress_numbers.rs | 642 -------------------------------- 7 files changed, 286 insertions(+), 652 deletions(-) delete mode 100644 snippets/in_progress_numbers.rs diff --git a/Cargo.lock b/Cargo.lock index ecdcdf9..35bd457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,6 +118,14 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fairy-ring" +version = "0.1.0" +dependencies = [ + "clap", + "hyphae", +] + [[package]] name = "hashbrown" version = "0.15.4" diff --git a/Cargo.toml b/Cargo.toml index df6ee70..32c5fb0 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ cargo-features = ["profile-rustflags"] [workspace] resolver = "2" -members = ["mycelium", "decomposer", "hyphae", "organelle"] +members = ["mycelium", "decomposer", "hyphae", "organelle", "fairy-ring"] [profile.release] opt-level = 3 diff --git a/hyphae/build.rs b/hyphae/build.rs index 7984d48..2502a65 100644 --- a/hyphae/build.rs +++ b/hyphae/build.rs @@ -105,13 +105,21 @@ fn main() { let mut isa_fromstr = "impl FromStr for Operation {\n".to_owned(); isa_fromstr += " type Err = &'static str;\n"; isa_fromstr += " fn from_str(v: &str) -> Result {\n"; + isa_fromstr += " let a = v.to_ascii_uppercase();\n"; + isa_fromstr += " let v = a.as_str();\n"; isa_fromstr += " match v {\n"; let mut isa_from_str = "impl TryFrom<&str> for Operation {\n".to_owned(); isa_from_str += " type Error = &'static str;\n"; isa_from_str += " fn try_from(v: &str) -> Result {\n"; + isa_from_str += " let a = v.to_ascii_uppercase();\n"; + isa_from_str += " let v = a.as_str();\n"; isa_from_str += " match v {\n"; + let mut isa_into_str = "impl Display for Operation {\n".to_owned(); + isa_into_str += " fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> {\n"; + isa_into_str += " match self.0 {\n"; + let mut isa_num_args = "impl Operation {\n".to_owned(); isa_num_args += " pub fn num_args(&self) -> Result {\n"; isa_num_args += " match self.0 {\n"; @@ -133,6 +141,9 @@ fn main() { isa_fromstr += format!(" \"{}\" => Ok({}),\n", const_name, const_name).as_str(); + isa_into_str += format!(" {} => write!(f, \"{}\"),\n", + idx, const_name).as_str(); + isa_num_args += format!(" {} => Ok({}),\n", idx, instr.args.len()) .as_str(); @@ -154,6 +165,11 @@ fn main() { isa_fromstr += " }\n"; isa_fromstr += "}\n\n"; + isa_into_str += " _ => panic!(\"illegal instruction\"),\n"; + isa_into_str += " }\n"; + isa_into_str += " }\n"; + isa_into_str += "}\n\n"; + isa_num_args += " _ => Err(\"illegal instruction\"),\n"; isa_num_args += " }\n"; isa_num_args += " }\n"; @@ -163,9 +179,11 @@ fn main() { isa += isa_from_byte.as_str(); isa += isa_from_str.as_str(); isa += isa_fromstr.as_str(); + isa += isa_into_str.as_str(); isa += isa_num_args.as_str(); - write!(&mut output_file, "use core::str::FromStr;\n\n\n").unwrap(); + write!(&mut output_file, "use core::str::FromStr;\n").unwrap(); + write!(&mut output_file, "use alloc::fmt::{{Display, Formatter, Error as E}};\n\n\n").unwrap(); write!(&mut output_file, "{}", isa).unwrap(); write!(&mut output_file, "\n\npub const TOTAL_INSTRUCTIONS: usize = {};", peak) .unwrap(); diff --git a/hyphae/src/heap.rs b/hyphae/src/heap.rs index b3d1ce4..f02a693 100644 --- a/hyphae/src/heap.rs +++ b/hyphae/src/heap.rs @@ -23,7 +23,8 @@ use core::ptr::NonNull; use alloc::{vec, vec::Vec}; use alloc::rc::Rc; use alloc::boxed::Box; -use alloc::fmt::Debug; +use alloc::fmt::{Error as E, Debug, Formatter, Display}; +use alloc::string::String; use organelle::{Number, Fraction, SymbolicNumber, Float, ScientificNotation}; @@ -97,6 +98,12 @@ impl DerefMut for Gc { } } +impl Display for Gc { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> { + write!(f, "{}", **self) + } +} + // takes a pointer to target Rc macro_rules! shallow_copy_rc { ( $src:expr ) => { @@ -264,6 +271,18 @@ impl Index for Cons { } } +impl Display for Cons { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> { + write!(f, "(")?; + self.0.clone() + .and_then(|x| write!(f, "{}", x).into()); + write!(f, " ")?; + self.1.clone() + .and_then(|x| write!(f, "{}", x).into()); + write!(f, ")") + } +} + #[derive(PartialEq, Debug)] pub enum Datum { Number(Number), @@ -276,6 +295,43 @@ pub enum Datum { None } +impl Display for Datum { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> { + match self { + Datum::Number(n) => write!(f, "{}", Into::::into(*n)), + Datum::Bool(b) => write!(f, "{}", b), + Datum::Cons(c) => write!(f, "'{}", c), + Datum::Char(c) => write!(f, "'{}'", c), + Datum::String(s) => write!(f, "\"{}\"", str::from_utf8(&s).unwrap()), + Datum::Vector(v) => { + write!(f, "#(")?; + if v.len() > 0 { + write!(f, "{}", v[0])?; + } + if v.len() > 1 { + for i in &v[1..] { + write!(f, ", {}", i)?; + } + } + write!(f, ")") + }, + Datum::ByteVector(v) => { + write!(f, "#u8(")?; + if v.len() > 0 { + write!(f, "{}", v[0])?; + } + if v.len() > 1 { + for i in &v[1..] { + write!(f, ", {}", i)?; + } + } + write!(f, ")") + }, + Datum::None => write!(f, "nil"), + } + } +} + // implemented by hand to force deep copy on Cons datum impl Clone for Datum { fn clone(&self) -> Datum { diff --git a/hyphae/src/serializer.rs b/hyphae/src/serializer.rs index c3cd48b..e0b990c 100644 --- a/hyphae/src/serializer.rs +++ b/hyphae/src/serializer.rs @@ -20,6 +20,8 @@ use crate::heap::Datum; use alloc::vec::Vec; use alloc::vec; +use alloc::str::FromStr; +use alloc::fmt::{Display, Formatter, Error as E}; use core::ops::Index; use core::mem::transmute; @@ -64,12 +66,6 @@ pub enum Address { Char = 0xfa, // immutable access only } -#[derive(Debug, Clone, PartialEq)] -pub struct Deserializer<'a> { - pub input: &'a [u8], - // TODO: Debug levels for errors -} - #[derive(Debug, Clone, PartialEq)] pub struct Operand(pub Address, pub usize); @@ -146,6 +142,56 @@ impl Into> for Operand { } } +impl FromStr for Operand { + type Err = &'static str; + fn from_str(v: &str) -> Result { + match v { + "$expr" => Ok(Operand(Address::Expr, 0)), + "$oper1" => Ok(Operand(Address::Oper1, 0)), + "$oper2" => Ok(Operand(Address::Oper2, 0)), + "$oper3" => Ok(Operand(Address::Oper3, 0)), + "$oper4" => Ok(Operand(Address::Oper4, 0)), + "true" => Ok(Operand(Address::Bool, 1)), + "false" => Ok(Operand(Address::Bool, 0)), + a if a.chars().nth(0).unwrap() == '%' && + a.len() > 1 && + a[1..].parse::().is_ok() => + Ok(Operand(Address::Stack, a[1..].parse::().unwrap())), + a if a.chars().nth(0).unwrap() == '@' && + a.len() > 1 && + a[1..].parse::().is_ok() => + Ok(Operand(Address::Instr, a[1..].parse::().unwrap())), + a if a.chars().nth(0).unwrap() == '\'' && + a.len() == 3 && + a.chars().nth(2).unwrap() == '\'' => + Ok(Operand(Address::Char, a.chars().nth(1).unwrap() as usize)), + a if a.parse::().is_ok() => + Ok(Operand(Address::Numer, a.parse::().unwrap())), + _ => Err("invalid operand") + } + } +} + +impl Display for Operand { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> { + match self.0 { + Address::Expr => write!(f, "$expr"), + Address::Oper1 => write!(f, "$oper1"), + Address::Oper2 => write!(f, "$oper2"), + Address::Oper3 => write!(f, "$oper3"), + Address::Oper4 => write!(f, "$oper4"), + Address::Bool => + write!(f, "{}", if self.1 > 0 { "true" } else { "false" }), + Address::Stack => write!(f, "%") + .and_then(|_| write!(f, "{}", self.1)), + Address::Instr => write!(f, "@") + .and_then(|_| write!(f, "{}", self.1)), + Address::Numer => write!(f, "{}", self.1), + Address::Char => write!(f, "'{}'", self.1 as u8 as char), + } + } +} + impl Operand { fn byte_length(&self) -> u8 { 1 + self.0.operand_size() @@ -183,6 +229,46 @@ impl Into> for Instruction { } } +impl FromStr for Instruction { + type Err = &'static str; + fn from_str(v: &str) -> Result { + let toks: Vec<&str> = v.trim().split(' ').collect(); + if toks.len() < 1 { + return Err("empty string"); + } + + let oper = Operation::from_str(toks[0])?; + let mut args = vec![]; + if toks.len() == 1 && oper.num_args()? == 0 { + return Ok(Instruction(oper, args)); + } + for i in toks[1..].iter() { + args.push(Operand::from_str(i.trim_matches(','))?); + } + + if oper.num_args()? as usize != args.len() { + return Err("instruction has incorrect number of operands"); + } + + Ok(Instruction(oper, args)) + } +} + +impl Display for Instruction { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> { + write!(f, "{}\t", self.0)?; + if self.1.len() > 0 { + write!(f, "{}", self.1[0])?; + } + if self.1.len() > 1 { + for i in self.1[1..].iter() { + write!(f, ", {}", i)?; + } + } + Ok(()) + } +} + impl Instruction { fn byte_length(&self) -> u8 { self.1.iter() @@ -247,6 +333,52 @@ impl<'a> Index for Program { } } +impl Display for Program { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> { + write!(f, "DATA:\n")?; + for i in self.0.iter() { + write!(f, " {}", i)?; + } + + write!(f, "\nCODE:\n")?; + for i in self.1.iter() { + write!(f, " {}", i)?; + } + + Ok(()) + } +} + +impl FromStr for Program { + type Err = &'static str; + fn from_str(val: &str) -> Result { + //let mut datum = vec![]; + let mut instrs = vec![]; + let lines: Vec<&str> = val.split('\n').collect(); + let mut cur = 0; + let mut toggle = 0; + + while cur < lines.len() { + if toggle == 1 { + instrs.push(lines[cur].parse::()?); + + // TODO: toggle == 2 case for interpreting a DATA chunk + + } else { + match lines[cur] { + "DATA:" => return Err("datum parser unimplemented"), + "CODE:" => toggle = 1, + a => return Err("unknown section in document: "), + } + } + + cur += 1; + } + + Ok(Program(vec![], instrs)) + } +} + impl TryFrom for DeserializerControlCode { type Error = &'static str; fn try_from(value: u8) -> Result { @@ -280,6 +412,31 @@ mod tests { use crate::instr; use super::*; + #[test] + fn test_operand_tofrom_str() { + let cases = vec![ + ("$expr", Operand(Address::Expr, 0)), + ("$oper1", Operand(Address::Oper1, 0)), + ("$oper2", Operand(Address::Oper2, 0)), + ("$oper3", Operand(Address::Oper3, 0)), + ("$oper4", Operand(Address::Oper4, 0)), + ("true", Operand(Address::Bool, 1)), + ("false", Operand(Address::Bool, 0)), + ("%12", Operand(Address::Stack, 12)), + ("%1", Operand(Address::Stack, 1)), + ("@1", Operand(Address::Instr, 1)), + ("@12", Operand(Address::Instr, 12)), + ("1234", Operand(Address::Numer, 1234)), + ("'c'", Operand(Address::Char, 'c' as usize)), + ]; + + for i in cases.iter() { + let a = Operand::from_str(i.0).unwrap(); + assert_eq!(a, i.1); + assert_eq!(i.0, a.to_string().as_str()); + } + } + #[test] fn test_operand_parse() { let bad_addressing = @@ -357,7 +514,36 @@ mod tests { assert_eq!(two_bytes.unwrap(), vec![instr::LINK.0, 0xf3, 0xf4]); assert_eq!(two_oper.1[0], Operand(Address::Oper1, 0)); assert_eq!(two_oper.1[1], Operand(Address::Oper2, 0)); - } + } + + #[test] + fn test_instruction_tofrom_string() { + let happy_cases = vec![ + "NOP", + " NOP", + "NOP ", + "nop", + "PUSH $expr", + "CONST $expr 4", + "jmp @3", + ]; + + let sad_cases = vec![ + "NOP 1", + "push", + "const 4", + ]; + + for i in happy_cases.iter() { + assert!(i.parse::().is_ok()); + } + + for i in sad_cases.iter() { + assert!(i.parse::().is_err()); + } + } + + // TODO: test program from and to string #[test] fn test_program_parse() { diff --git a/hyphae/src/vm.rs b/hyphae/src/vm.rs index 9372d16..2ce21d8 100644 --- a/hyphae/src/vm.rs +++ b/hyphae/src/vm.rs @@ -145,6 +145,14 @@ impl VM { self.running = false; } + pub fn run_step(&mut self) { + if self.ictr < self.prog.len() && + !self.err_state && self.running { + self.execute_instruction(); + self.ictr += 1; + } + } + #[inline(always)] fn execute_instruction(&mut self) { macro_rules! e { diff --git a/snippets/in_progress_numbers.rs b/snippets/in_progress_numbers.rs deleted file mode 100644 index 39333dc..0000000 --- a/snippets/in_progress_numbers.rs +++ /dev/null @@ -1,642 +0,0 @@ -use alloc::boxed::Box; -use alloc::{vec, vec::Vec}; -use alloc::fmt::Debug; -use lexer::{E_TOO_MANY_DECIMALS, E_TOO_MANY_SLASH}; - -use core::cmp::Ordering; -use core::{fmt, u8}; -use core::ops::{Add, Div, Mul, Sub}; - - -pub const E_INCOMPREHENSIBLE: &str = "could not parse number literal"; -pub const E_POUND_TRUNCATED: &str = "pound sign implies additional input"; -pub const E_BASE_PARSE_FAIL: &str = "failed to parse explicit base literal"; -pub const E_UNKNOWN_CONTROL: &str = "unknown character in number literal"; -pub const E_EMPTY_INPUT: &str = "empty string cannot be a number"; - -const NUM_INF: &str = "+inf.0"; -const NUM_NEG_INF: &str = "-inf.0"; -const NUM_NAN: &str = "+nan.0"; -const NUM_NEG_NAN: &str = "-nan.0"; - -pub const NegativeFlag: u8 = 0b10000000; // positive value if off -pub const DecimalFlag: u8 = 0b01000000; // single integer if off -pub const FractionFlag: u8 = 0b00100000; // decimal if off -pub const ScientificFlag: u8 = 0b00010000; // requires a second flags byte -pub const InfiniteFlag: u8 = 0b00001000; // can be positive or negative -pub const NotANumberFlag: u8 = 0b00000100; // can be positive or negative because r7rs -pub const OverflownFlag: u8 = 0b00000010; // poisons exactness - - -/* NUMBER BYTES FORMAT - * Generally the format within the byte array operates like this - * (guaranteed header) 1. NumberFlags (u8) - * (for each integer) 2. Byte Length (u8) - * (for each integer) 3. N proceeding bytes of data - * - * If Scientific Notation is used the leading number may be a decimal. - * In this case, there will be three total numbers - * - * All numbers are big endian - */ -#[repr(transparent)] -#[derive(Clone, Debug, PartialEq)] -pub struct Number<'src> (pub &'src [u8]); - - -/* WARNING - * member functions tend to assume that number encoding is consistent - * use Number::is_valid() to double check numbers from unknown sources - * - * TODO: maybe mark raw-indexing member functions as unsafe - */ -impl Number<'_> { - #[inline(always)] - pub fn byte_length(&self) -> u8 { - if self.0[0] & (InfiniteFlag | NotANumberFlag) != 0 { - return 1; - } - - let mut len = self.0[1] + 2; - if self.0[0] & (DecimalFlag | FractionFlag | ScientificFlag) != 0 { - len += self.0[len as usize] + 1; - } - - if self.0[0] & ScientificFlag != 0 && - self.0[0] & DecimalFlag != 0 { - len += self.0[len as usize]; - } - - len - } - - pub fn is_valid(&self) -> bool { - let len = self.0.len(); - if len < 1 { - return false; - } - - let decimal = self.0[0] & DecimalFlag != 0; - let fraction = self.0[0] & FractionFlag != 0; - let scientific = self.0[0] & ScientificFlag != 0; - let overflown = self.0[0] & OverflownFlag != 0; - let infinite = self.0[0] & InfiniteFlag != 0; - let notanumber = self.0[0] & NotANumberFlag != 0; - - // check flags - if overflown { - return false - } - - if (decimal && fraction) || (scientific && fraction) { - return false - } - - if (infinite || notanumber) && - (decimal || fraction || scientific || len != 1) { - return false - } - - // at least 3 bytes for a single u8 - if len < 3 { - return false - } - - let mut cur = self.0[1] + 2; - if len < cur as usize { - return false - } - - if decimal || fraction || scientific { - if len < (cur + 1) as usize { - return false; - } - - cur += self.0[cur as usize]; - if len < (cur + 1) as usize { - return false; - } - } - - if scientific && decimal { - cur += 1; - if len < (cur + 1) as usize { - return false - } - - cur += self.0[cur as usize]; - if len < (cur + 1) as usize { - return false - } - } - - true - } - - #[inline(always)] - pub fn is_exact(&self) -> bool { - self.0[0] & ScientificFlag == 0 - } - - #[inline(always)] - pub fn make_exact_into(&self, dst:&mut Vec) { - // expand scientific notation else just direct copy - if self.0[0] & ScientificFlag != 0 { - self.normalize_scientific_into(dst); - return - } - - self.copy_into(dst); - } - - #[inline(always)] - pub fn make_inexact_into(&self, dst: &mut Vec) { - // basically just convert a fraction into an actual division - todo!() - } - - // use this so you dont have to worry about clone while casting - #[inline(always)] - pub fn copy_into(&self, dst: &mut Vec) { - for i in self.0 { - dst.push(*i) - } - } - - #[inline(always)] - pub fn normalize_scientific_into(&self, dst: &mut Vec) { - todo!() - } - - #[inline(always)] - pub fn simplify_fraction_in_place(&mut self) { - if self.0[0] & FractionFlag == 0 { - return - } - - // can technically do this in place - // each element of the fraction will only shrink - todo!() - } - - #[inline(always)] - pub fn from_str_into(src: &str, dst: &mut Vec) -> Result<(), &'static str> { - // handle symbolic values - match src { - NUM_INF => { - dst.push(0 as u8 | InfiniteFlag); - return Ok(()); - }, - - NUM_NEG_INF => { - dst.push(0 as u8 | NegativeFlag | InfiniteFlag); - return Ok(()); - }, - - NUM_NAN => { - dst.push(0 as u8 | NotANumberFlag); - return Ok(()); - }, - - NUM_NEG_NAN => { - dst.push(0 as u8 | NegativeFlag | NotANumberFlag); - return Ok(()); - }, - - _ => (), - } - - let mut ctrl_flags = 0 as u8; - let mut operands = vec![]; - let mut digits_per_byte = 3; // default to decimal encoding - let mut base = 0; - let mut iter = src.chars().peekable(); - - match iter.next() { - Some('+') => (), - Some('-') => { - ctrl_flags |= NegativeFlag; - }, - Some('#') => { - match iter.next() { - None => return Err(E_POUND_TRUNCATED), - Some('i') => /* force_inexact = true */ (), - Some('e') => /* force_exact = true */ (), - Some('x') => { digits_per_byte = 2; base = 16 }, - Some('d') => { digits_per_byte = 3; base = 10 }, - Some('o') => { digits_per_byte = 4; base = 8 }, - Some('b') => { digits_per_byte = 8; base = 2 }, - _ => return Err(E_UNKNOWN_CONTROL), - } - }, - Some(a) if a.is_digit(10) => (), - Some(_) => return Err(E_INCOMPREHENSIBLE), - None => return Err(E_EMPTY_INPUT), - } - - let mut ops_needed = 1; - if base != 10 { - // cant mix non-decimal base and other number representations - let mut len = 0 as u8; - while let Some(chunk) = { - let mut chk = vec![]; - for _ in 0..digits_per_byte { - if let Some(c) = iter.next() { - chk.push(c as u8) - } - } - if chk.len() < 1 { None } else { Some(chk) } - } { - let Ok(val) = u8::from_str_radix( - unsafe {str::from_utf8_unchecked(chunk.as_slice())}, base) else { - return Err(E_BASE_PARSE_FAIL) - }; - operands.push(val); - len += 1; - } - // integer numbers prepended with their length - operands.insert(0, len); - ops_needed -= 1; - - } else { - // just a decimal number, but could have a weird format - loop { - macro_rules! pack_operand { - () => { - let s = unsafe { str::from_utf8_unchecked(operands.as_slice()) }; - let f = usize::from_str_radix(&s, 10).expect("str cast"); - let f = f.to_be_bytes(); - operands.clear(); - - dst.push(f.len() as u8); - dst.append(&mut f.to_vec()); - ops_needed -= 1; - } - } - - match iter.next() { - Some(c) if c.is_digit(10) => { - operands.push(c as u8); - }, - - Some('.') => { - ops_needed += 1; - if ctrl_flags & (FractionFlag | ScientificFlag) != 0 { - return Err(E_INCOMPREHENSIBLE) - } - - if ctrl_flags & DecimalFlag != 0 { - return Err(E_TOO_MANY_DECIMALS) - } - - ctrl_flags |= DecimalFlag; - pack_operand!(); - }, - - Some('/') => { - ops_needed += 1; - if ctrl_flags & (DecimalFlag | ScientificFlag) != 0 { - return Err(E_INCOMPREHENSIBLE) - } - - if ctrl_flags & FractionFlag != 0 { - return Err(E_TOO_MANY_SLASH) - } - - ctrl_flags |= DecimalFlag; - pack_operand!(); - }, - - Some('e') => { - ops_needed += 1; - if ctrl_flags & FractionFlag != 0 { - return Err(E_INCOMPREHENSIBLE) - } - - ctrl_flags |= ScientificFlag; - let mut newctrl = 0 as u8; - - if let Some('-') = iter.peek() { - newctrl |= NegativeFlag; - } - - pack_operand!(); - dst.push(newctrl); - }, - - Some(_) => return Err(E_INCOMPREHENSIBLE), - - None => { - pack_operand!(); - break; - } - } - } - } - - if ops_needed != 0 { - return Err(E_INCOMPREHENSIBLE); - } - - dst.insert(0, ctrl_flags); - Number(dst.as_slice()).simplify_fraction_in_place(); - Ok(()) - } - - pub fn from_u8_into(src: u8, dst: &mut Vec) -> Number { - dst.push(0 as u8); - dst.push(src); - Number(dst.as_slice()) - } -} - -impl fmt::Display for Number<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // can implement after I finish division - todo!() - } -} - -impl<'a> From<&'a Box<[u8]>> for Number<'a> { - fn from(value: &'a Box<[u8]>) -> Self { - Number(value.as_ref()) - } -} - -impl<'a> From<&'a Vec> for Number<'a> { - fn from(value: &'a Vec) -> Self { - Number(value.as_slice()) - } -} - -impl<'a> From<&'a [u8]> for Number<'a> { - fn from(value: &'a [u8]) -> Self { - Number(value) - } -} - -impl<'a> Into<&'a [u8]> for Number<'a> { - fn into(self) -> &'a [u8] { - self.0 - } -} - -impl Add for Number<'_> { - type Output = Box<[u8]>; - fn add(self, rhs: Self) -> Self::Output { - todo!() - } -} - -impl Sub for Number<'_> { - type Output = Box<[u8]>; - fn sub(self, rhs: Self) -> Self::Output { - todo!() - } -} - -impl Mul for Number<'_> { - type Output = Box<[u8]>; - fn mul(self, rhs: Self) -> Self::Output { - todo!() - } -} - -impl Div for Number<'_> { - type Output = Box<[u8]>; - fn div(self, rhs: Self) -> Self::Output { - // divide unsigned integer by unsigned integer - // the inputs (lh and rh) start with length byte - // returns a decimal index - fn div_ints(lh: &[u8], rh: &[u8], dest: &mut Vec) -> u8 { - todo!() - } - - /* Options - * divide a single int by a single int - * - (make fraction) - * divide a fraction by a single int - * - (multiply denominator) - * divide a decimal by a single int - * - (divide straight through) - * divide a scientific note by a single int - * - divide the first num - * - multiply by however much is needed for ones place (like 3.5) - * - add or subtract from the second number accordingly - * - * divide a single int by a fraction - * - output denom * lh / numer - * divide a single int by a decimal - */ - todo!() - } -} - -impl PartialEq for Number<'_> { - fn eq(&self, other: &Number) -> bool { - todo!() - } -} - -impl PartialOrd for Number<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - todo!() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[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))); - - 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!("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)); - } - - #[test] - fn test_number_addition_subtraction_cases() { - let cases = vec![ - vec!["1/5", "4/5", "1/1"], - vec!["1/5", "0.8", "1/1"], - vec!["1e1", "2.0", "12/1"], - vec!["1e1", "2/1", "12/1"], - vec!["1e1", "1/2", "10.5"], - ]; - - cases.iter().for_each(|case| { - println!("+ {:#?}", case); - let x = case[0].parse::().unwrap(); - let y = case[1].parse::().unwrap(); - let z = case[2].parse::().unwrap(); - - // test some mathematical properties - assert_eq!(x + y, z); - assert_eq!(x + y, y + x); - assert_eq!(z - x, y); - assert_eq!(x + y - x, y); - }); - - // theres no reason this should adhere to all the other rules - let x = "+inf.0".parse::().unwrap(); - let y = "1e1".parse::().unwrap(); - let z = "+inf.0".parse::().unwrap(); - assert_eq!(x + y, z); - } - - #[test] - fn test_number_multiplication_division_cases() { - let cases = vec![ - vec!["1/5", "5e0", "1/1"], - vec!["1/5", "5", "1/1"], - vec!["1/5", "2/1", "2/5"], - vec!["4.4", "1/2", "2.2"], - vec!["12.0", "1/2", "6/1"], - vec!["1e1", "2.0", "20/1"], - vec!["1e1", "2/1", "20/1"], - vec!["1e1", "1/2", "5/1"], - ]; - - cases.iter().for_each(|case| { - println!("+ {:#?}", case); - let x = case[0].parse::().unwrap(); - let y = case[1].parse::().unwrap(); - let z = case[2].parse::().unwrap(); - - // test some mathematical properties - assert_eq!(x * y, z); - assert_eq!(x * y, y * x); - assert_eq!(z / x, y); - assert_eq!(x * y / x, y); - }); - } - - #[test] - fn test_number_pow_cases() { - // TODO: add scientific notation cases - let cases = vec![ - vec!["2", "2", "4"], - vec!["2/1", "2/1", "4/1"], - vec!["2/1", "2/-1", "1/4"], - vec!["2/1", "2/2", "2/1"], - vec!["2/1", "2.0", "4/1"], - vec!["27/8", "2/-3", "4/9"] - ]; - - cases.iter().for_each(|case| { - println!("+ {:#?}", case); - let x = case[0].parse::().unwrap(); - let y = case[1].parse::().unwrap(); - let z = case[2].parse::().unwrap(); - assert_eq!(x.pow(y), z); - }); - } - - #[test] - fn test_number_ord_cases() { - // TODO: add more cases - let cases = vec![ - vec!["1/2", "1.0", "1e1"], - ]; - - cases.iter().for_each(|case| { - println!("+ {:#?}", case); - let x = case[0].parse::().unwrap(); - let y = case[1].parse::().unwrap(); - let z = case[2].parse::().unwrap(); - assert!(x < y); - assert!(y < z); - assert!(x < z); - }); - } - - #[test] - fn float_negative_exponent_case() { - if let Float(0.1) = "1e-1" - .parse::() - .unwrap() - .make_inexact() { - return - } - - assert!(false) - } -}