Compare commits

...

8 commits

Author SHA1 Message Date
467e1e7188 fixed BVTON test
Some checks failed
per-push tests / build (pull_request) Failing after 33s
per-push tests / test-frontend (pull_request) Has been skipped
per-push tests / timed-decomposer-parse (pull_request) Has been skipped
per-push tests / test-utility (pull_request) Has been skipped
per-push tests / test-backend (pull_request) Has been skipped
2025-12-04 12:07:45 -08:00
a99175669f updated BVTON 2025-12-04 12:07:45 -08:00
6f16c2e084 another attempt to make BVTON 2025-12-04 12:07:45 -08:00
333beba0bb WIP: Adding BVTON instruction to convert from ByteVector to Number 2025-12-04 12:07:45 -08:00
d008ce8060 updated NTOBV and test 2025-12-04 12:07:45 -08:00
54a0e32fe1 Wrote number to bytevector conversion and test 2025-12-04 12:07:45 -08:00
18770575cb Add instruction definition for number to bytevector conversion 2025-12-04 12:07:45 -08:00
5582da5b41 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.
2025-12-03 22:14:48 +00:00
8 changed files with 356 additions and 661 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"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fairy-ring"
version = "0.1.0"
dependencies = [
"clap",
"hyphae",
]
[[package]]
name = "hashbrown"
version = "0.15.4"

View file

@ -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

View file

@ -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<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";
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<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";
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<u8, &'static str> {\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();

View file

@ -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<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
macro_rules! shallow_copy_rc {
( $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)]
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::<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
impl Clone for Datum {
fn clone(&self) -> Datum {

View file

@ -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<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 {
fn byte_length(&self) -> u8 {
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 {
fn byte_length(&self) -> u8 {
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 {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
@ -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 =
@ -359,6 +516,35 @@ mod tests {
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]
fn test_program_parse() {
let bytes1 = [

View file

@ -16,7 +16,7 @@
*/
use organelle::{Fraction, Number, Numeric};
use organelle::{Float, Fraction, Number, Numeric};
use crate::hmap::QuickMap;
use crate::stackstack::StackStack;
@ -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 {
@ -391,6 +399,28 @@ impl VM {
}
}),
i::NTOBV => access!(&instr.1[0], {
if let Datum::Number(snum) = **access!(&instr.1[0]) {
let n = snum.make_inexact();
if !snum.is_exact() || n.0.fract() != 0.0 ||
n.0 > u8::MAX.into() || n.0 < 0.0 {
e!("input to NTOBV cannot cleanly convert");
}
Datum::ByteVector(n.0.to_ne_bytes().to_vec()).into()
} else {
e!("illegal argument to NTOBV instruction");
}
}),
i::BVTON => access!(&instr.1[0], {
if let Datum::ByteVector(sbv) = &**access!(&instr.1[0]) {
let sf64 = f64::from_ne_bytes(sbv.clone().try_into().unwrap());
Datum::Number(Number::Flt(Float(sf64))).into()
} else {
e!("illegal argument to BVTON instruction");
}
}),
i::NTOC => access!(&instr.1[0], {
if let Datum::Number(snum) = **access!(&instr.1[0]) {
let n = snum.make_inexact();
@ -1090,16 +1120,16 @@ mod tests {
#[test]
fn isa_conversions() {
let mut vm = VM::from(Program(vec![], vec![
// load from stack into expr
Instruction(i::DUPL, vec![Operand(Address::Stack, 0),
Operand(Address::Expr, 0)]),
// create a strunct and push to stack
// create a struct and push to stack
Instruction(i::CTOS, vec![Operand(Address::Expr, 0)]),
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
// reset expr to original char
Instruction(i::DUPL, vec![Operand(Address::Stack, 1),
Operand(Address::Expr, 0)]),
@ -1115,9 +1145,18 @@ mod tests {
Instruction(i::NTOE, vec![Operand(Address::Expr, 0)]),
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
// convert to ByteVector and push to stack
Instruction(i::NTOBV, vec![Operand(Address::Expr, 0)]),
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
// convert back to inexact and push to stack
Instruction(i::BVTON, vec![Operand(Address::Expr, 0)]),
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
// create a char and push to stack
Instruction(i::NTOC, vec![Operand(Address::Expr, 0)]),
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
])).with_stack({
let mut i = StackStack::new();
i.push_current_stack(Datum::Char(b'a').into());
@ -1127,12 +1166,14 @@ mod tests {
let case = TestResult{
expr: None,
stack: vec![
(0, Datum::Char(b'a').into()),
(1, Datum::Number(Number::Fra(Fraction(97, 1))).into()),
(2, Datum::Number(Number::Flt(Float(97.0))).into()),
(3, Datum::Number(Number::Fra(Fraction(97, 1))).into()),
(4, Datum::String(vec![b'a']).into()),
(5, Datum::Char(b'a').into()),
(0, Datum::Char(b'a').into()), // NTOC
(1, Datum::Number(Number::Flt(Float(97.0))).into()), // BVTON
(2, Datum::ByteVector(vec![0, 0, 0, 0, 0, 64, 88, 64]).into()), // NTOBV
(3, Datum::Number(Number::Fra(Fraction(97, 1))).into()), // NTOE
(4, Datum::Number(Number::Flt(Float(97.0))).into()), // NTOI
(5, Datum::Number(Number::Fra(Fraction(97, 1))).into()), // CTON
(6, Datum::String(vec![b'a']).into()), // CTOS
(7, Datum::Char(b'a').into()), // DUPL
],
syms: vec![],
errr: None,

View file

@ -641,6 +641,34 @@ character byte.
Requires mutable access to input address.
"""
[[instructions]]
name = "ntobv"
args = ["src"]
output = ""
description = """
The ntobv instruction accepts a single number input. This operand is overwritten
by a new number datum that represents the inexact form of the source number.
The inexact form is a normalization of fraction or scientific notation datum to
float datum casted to ByteVector datum.
Requires mutable access to input address.
"""
[[instructions]]
name = "bvton"
args = ["src"]
output = ""
description = """
The bvton instruction accepts a byte vector input. This operand is overwritten
by a new number datum that represents the inexact form of the source byte vector.
The normalized fraction or scientific notation data represented in a
ByteVector datum is casted to an inexact floating point number.
Requires mutable access to input address.
"""
[[instructions]]
name = "ntoc"
args = ["src"]

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