Compare commits
8 commits
d759937b11
...
467e1e7188
| Author | SHA1 | Date | |
|---|---|---|---|
| 467e1e7188 | |||
| a99175669f | |||
| 6f16c2e084 | |||
| 333beba0bb | |||
| d008ce8060 | |||
| 54a0e32fe1 | |||
| 18770575cb | |||
| 5582da5b41 |
8 changed files with 356 additions and 661 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue