Begin implementing human interface for Hyphae
Some checks failed
per-push tests / build (push) Failing after 39s
per-push tests / test-frontend (push) Has been skipped
per-push tests / timed-decomposer-parse (push) Has been skipped
per-push tests / test-utility (push) Has been skipped
per-push tests / test-backend (push) Has been skipped

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.
This commit is contained in:
Ava Apples Affine 2025-12-03 22:14:48 +00:00
parent 389bf6e9a0
commit 5582da5b41
7 changed files with 286 additions and 652 deletions

8
Cargo.lock generated
View file

@ -118,6 +118,14 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fairy-ring"
version = "0.1.0"
dependencies = [
"clap",
"hyphae",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.4"

View file

@ -2,7 +2,7 @@ cargo-features = ["profile-rustflags"]
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["mycelium", "decomposer", "hyphae", "organelle"] members = ["mycelium", "decomposer", "hyphae", "organelle", "fairy-ring"]
[profile.release] [profile.release]
opt-level = 3 opt-level = 3

View file

@ -105,13 +105,21 @@ fn main() {
let mut isa_fromstr = "impl FromStr for Operation {\n".to_owned(); let mut isa_fromstr = "impl FromStr for Operation {\n".to_owned();
isa_fromstr += " type Err = &'static str;\n"; isa_fromstr += " type Err = &'static str;\n";
isa_fromstr += " fn from_str(v: &str) -> Result<Self, Self::Err> {\n"; isa_fromstr += " fn from_str(v: &str) -> Result<Self, Self::Err> {\n";
isa_fromstr += " let a = v.to_ascii_uppercase();\n";
isa_fromstr += " let v = a.as_str();\n";
isa_fromstr += " match v {\n"; isa_fromstr += " match v {\n";
let mut isa_from_str = "impl TryFrom<&str> for Operation {\n".to_owned(); let mut isa_from_str = "impl TryFrom<&str> for Operation {\n".to_owned();
isa_from_str += " type Error = &'static str;\n"; isa_from_str += " type Error = &'static str;\n";
isa_from_str += " fn try_from(v: &str) -> Result<Self, Self::Error> {\n"; isa_from_str += " fn try_from(v: &str) -> Result<Self, Self::Error> {\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"; 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(); let mut isa_num_args = "impl Operation {\n".to_owned();
isa_num_args += " pub fn num_args(&self) -> Result<u8, &'static str> {\n"; isa_num_args += " pub fn num_args(&self) -> Result<u8, &'static str> {\n";
isa_num_args += " match self.0 {\n"; isa_num_args += " match self.0 {\n";
@ -133,6 +141,9 @@ fn main() {
isa_fromstr += format!(" \"{}\" => Ok({}),\n", isa_fromstr += format!(" \"{}\" => Ok({}),\n",
const_name, const_name).as_str(); 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()) isa_num_args += format!(" {} => Ok({}),\n", idx, instr.args.len())
.as_str(); .as_str();
@ -154,6 +165,11 @@ fn main() {
isa_fromstr += " }\n"; isa_fromstr += " }\n";
isa_fromstr += "}\n\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 += " _ => Err(\"illegal instruction\"),\n";
isa_num_args += " }\n"; isa_num_args += " }\n";
isa_num_args += " }\n"; isa_num_args += " }\n";
@ -163,9 +179,11 @@ fn main() {
isa += isa_from_byte.as_str(); isa += isa_from_byte.as_str();
isa += isa_from_str.as_str(); isa += isa_from_str.as_str();
isa += isa_fromstr.as_str(); isa += isa_fromstr.as_str();
isa += isa_into_str.as_str();
isa += isa_num_args.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, "{}", isa).unwrap();
write!(&mut output_file, "\n\npub const TOTAL_INSTRUCTIONS: usize = {};", peak) write!(&mut output_file, "\n\npub const TOTAL_INSTRUCTIONS: usize = {};", peak)
.unwrap(); .unwrap();

View file

@ -23,7 +23,8 @@ use core::ptr::NonNull;
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
use alloc::rc::Rc; use alloc::rc::Rc;
use alloc::boxed::Box; 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}; use organelle::{Number, Fraction, SymbolicNumber, Float, ScientificNotation};
@ -97,6 +98,12 @@ impl<T> DerefMut for Gc<T> {
} }
} }
impl<T: Display> Display for Gc<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> {
write!(f, "{}", **self)
}
}
// takes a pointer to target Rc // takes a pointer to target Rc
macro_rules! shallow_copy_rc { macro_rules! shallow_copy_rc {
( $src:expr ) => { ( $src:expr ) => {
@ -264,6 +271,18 @@ impl Index<usize> 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)] #[derive(PartialEq, Debug)]
pub enum Datum { pub enum Datum {
Number(Number), Number(Number),
@ -276,6 +295,43 @@ pub enum Datum {
None None
} }
impl Display for Datum {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> {
match self {
Datum::Number(n) => write!(f, "{}", Into::<String>::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 // implemented by hand to force deep copy on Cons datum
impl Clone for Datum { impl Clone for Datum {
fn clone(&self) -> Datum { fn clone(&self) -> Datum {

View file

@ -20,6 +20,8 @@ use crate::heap::Datum;
use alloc::vec::Vec; use alloc::vec::Vec;
use alloc::vec; use alloc::vec;
use alloc::str::FromStr;
use alloc::fmt::{Display, Formatter, Error as E};
use core::ops::Index; use core::ops::Index;
use core::mem::transmute; use core::mem::transmute;
@ -64,12 +66,6 @@ pub enum Address {
Char = 0xfa, // immutable access only 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)] #[derive(Debug, Clone, PartialEq)]
pub struct Operand(pub Address, pub usize); pub struct Operand(pub Address, pub usize);
@ -146,6 +142,56 @@ impl Into<Vec<u8>> for Operand {
} }
} }
impl FromStr for Operand {
type Err = &'static str;
fn from_str(v: &str) -> Result<Self, Self::Err> {
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::<usize>().is_ok() =>
Ok(Operand(Address::Stack, a[1..].parse::<usize>().unwrap())),
a if a.chars().nth(0).unwrap() == '@' &&
a.len() > 1 &&
a[1..].parse::<usize>().is_ok() =>
Ok(Operand(Address::Instr, a[1..].parse::<usize>().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::<usize>().is_ok() =>
Ok(Operand(Address::Numer, a.parse::<usize>().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 { impl Operand {
fn byte_length(&self) -> u8 { fn byte_length(&self) -> u8 {
1 + self.0.operand_size() 1 + self.0.operand_size()
@ -183,6 +229,46 @@ impl Into<Vec<u8>> for Instruction {
} }
} }
impl FromStr for Instruction {
type Err = &'static str;
fn from_str(v: &str) -> Result<Self, Self::Err> {
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 { impl Instruction {
fn byte_length(&self) -> u8 { fn byte_length(&self) -> u8 {
self.1.iter() self.1.iter()
@ -247,6 +333,52 @@ impl<'a> Index<usize> 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<Self, Self::Err> {
//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::<Instruction>()?);
// 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<u8> for DeserializerControlCode { impl TryFrom<u8> for DeserializerControlCode {
type Error = &'static str; type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
@ -280,6 +412,31 @@ mod tests {
use crate::instr; use crate::instr;
use super::*; 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] #[test]
fn test_operand_parse() { fn test_operand_parse() {
let bad_addressing = let bad_addressing =
@ -357,7 +514,36 @@ mod tests {
assert_eq!(two_bytes.unwrap(), vec![instr::LINK.0, 0xf3, 0xf4]); 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[0], Operand(Address::Oper1, 0));
assert_eq!(two_oper.1[1], Operand(Address::Oper2, 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::<Instruction>().is_ok());
}
for i in sad_cases.iter() {
assert!(i.parse::<Instruction>().is_err());
}
}
// TODO: test program from and to string
#[test] #[test]
fn test_program_parse() { fn test_program_parse() {

View file

@ -145,6 +145,14 @@ impl VM {
self.running = false; 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)] #[inline(always)]
fn execute_instruction(&mut self) { fn execute_instruction(&mut self) {
macro_rules! e { macro_rules! e {

View file

@ -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<u8>) {
// 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<u8>) {
// 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<u8>) {
for i in self.0 {
dst.push(*i)
}
}
#[inline(always)]
pub fn normalize_scientific_into(&self, dst: &mut Vec<u8>) {
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<u8>) -> 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<u8>) -> 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<u8>> for Number<'a> {
fn from(value: &'a Vec<u8>) -> 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>) -> 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<Ordering> {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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)));
assert_eq!("2e3".parse::<Number>(),
Ok(ScientificNotation(2.0, 3)));
assert_eq!("0e1".parse::<Number>(),
Ok(ScientificNotation(0.0, 1)));
assert_eq!("-1e34".parse::<Number>(),
Ok(ScientificNotation(-1.0, 34)));
assert_eq!("3.3e3".parse::<Number>(),
Ok(ScientificNotation(3.3, 3)));
assert_eq!("2".parse::<Number>(),
Err(E_SCIENTIFIC_E));
assert_eq!("2e2e2".parse::<Number>(),
Err(E_SCIENTIFIC_MULTI_E));
assert_eq!("2/3".parse::<Number>(),
Ok(Fraction(2, 3)));
assert_eq!("0/1".parse::<Number>(),
Ok(Fraction(0, 1)));
assert_eq!("-1/34".parse::<Number>(),
Ok(Fraction(-1, 34)));
assert_eq!("2".parse::<Number>(),
Err(E_NO_DENOMINATOR));
assert_eq!("2/2/2".parse::<Number>(),
Err(E_MULTI_DENOMINATOR));
assert_eq!("2/0".parse::<Number>(),
Err(E_ZERO_DENOMINATOR));
assert_eq!("3.3/3".parse::<Number>(),
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::<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"],
vec!["27/8", "2/-3", "4/9"]
];
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);
});
}
#[test]
fn float_negative_exponent_case() {
if let Float(0.1) = "1e-1"
.parse::<Number>()
.unwrap()
.make_inexact() {
return
}
assert!(false)
}
}