initial version
This commit is contained in:
2
bqst-core/.cargo/config.toml
Normal file
2
bqst-core/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
1
bqst-core/.gitignore
vendored
Normal file
1
bqst-core/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
151
bqst-core/Cargo.lock
generated
Normal file
151
bqst-core/Cargo.lock
generated
Normal file
@@ -0,0 +1,151 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bqst-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dashu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "dashu"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b3e5ac1e23ff1995ef05b912e2b012a8784506987a2651552db2c73fb3d7e0"
|
||||
dependencies = [
|
||||
"dashu-base",
|
||||
"dashu-float",
|
||||
"dashu-int",
|
||||
"dashu-macros",
|
||||
"dashu-ratio",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashu-base"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0b80bf6b85aa68c58ffea2ddb040109943049ce3fbdf4385d0380aef08ef289"
|
||||
|
||||
[[package]]
|
||||
name = "dashu-float"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85078445a8dbd2e1bd21f04a816f352db8d333643f0c9b78ca7c3d1df71063e7"
|
||||
dependencies = [
|
||||
"dashu-base",
|
||||
"dashu-int",
|
||||
"num-modular",
|
||||
"num-order",
|
||||
"rustversion",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashu-int"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee99d08031ca34a4d044efbbb21dff9b8c54bb9d8c82a189187c0651ffdb9fbf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dashu-base",
|
||||
"num-modular",
|
||||
"num-order",
|
||||
"rustversion",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashu-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93381c3ef6366766f6e9ed9cf09e4ef9dec69499baf04f0c60e70d653cf0ab10"
|
||||
dependencies = [
|
||||
"dashu-base",
|
||||
"dashu-float",
|
||||
"dashu-int",
|
||||
"dashu-ratio",
|
||||
"paste",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashu-ratio"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e33b04dd7ce1ccf8a02a69d3419e354f2bbfdf4eb911a0b7465487248764c9"
|
||||
dependencies = [
|
||||
"dashu-base",
|
||||
"dashu-float",
|
||||
"dashu-int",
|
||||
"num-modular",
|
||||
"num-order",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-modular"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
|
||||
|
||||
[[package]]
|
||||
name = "num-order"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
|
||||
dependencies = [
|
||||
"num-modular",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
14
bqst-core/Cargo.toml
Normal file
14
bqst-core/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "bqst-core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
strip = "symbols"
|
||||
|
||||
[dependencies]
|
||||
dashu = { version = "0.4.2", default-features = false, features = ["num-order"] }
|
||||
54
bqst-core/src/api.rs
Normal file
54
bqst-core/src/api.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use dashu::rational::RBig;
|
||||
|
||||
use crate::{PrecDec, prefixes, qty::Qty, unit};
|
||||
|
||||
macro_rules! _t {
|
||||
($e:expr) => {
|
||||
$e.map_err(|e| Box::new(e) as Box<dyn Debug>)
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_decimal<const B: u32>(s: &str) -> Result<RBig, dashu::base::ParseError> {
|
||||
if let Some((int_part, frac_part)) = s.split_once('.') {
|
||||
let denominator = B.pow(frac_part.len() as u32);
|
||||
let numerator = RBig::from_str_radix(&format!("{}{}", int_part, frac_part), B)?;
|
||||
Ok(numerator / RBig::from(denominator))
|
||||
} else {
|
||||
Ok(RBig::from_str_radix(s, B)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn qty(q: &str) -> Result<Qty, Box<dyn Debug>> {
|
||||
let split_idx = q
|
||||
.char_indices()
|
||||
.rev()
|
||||
.find(|&(_, c)| c.is_ascii_digit())
|
||||
.map(|(i, _)| i + 1) // found digit and digits are 1 byte so no worries byt ugly
|
||||
.unwrap_or(0);
|
||||
|
||||
let (amt, unt) = q.split_at(split_idx);
|
||||
let mut amt: PrecDec = if amt.is_empty() {
|
||||
PrecDec::from(1)
|
||||
} else {
|
||||
_t!(parse_decimal::<10>(amt))?
|
||||
};
|
||||
|
||||
let unt = if let Some((_, rem, exp)) = prefixes::extract_prefix_and_rem(unt.trim()) {
|
||||
let e = PrecDec::from(10isize.pow(exp.unsigned_abs().into()));
|
||||
if exp < 0 {
|
||||
amt /= e;
|
||||
} else {
|
||||
amt *= e;
|
||||
}
|
||||
|
||||
rem
|
||||
} else {
|
||||
unt
|
||||
};
|
||||
|
||||
let unt = _t!(unit::Unit::try_from(unt.trim()))?;
|
||||
|
||||
Ok(Qty { n: amt, u: unt })
|
||||
}
|
||||
83
bqst-core/src/lib.rs
Normal file
83
bqst-core/src/lib.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use dashu::rational::RBig;
|
||||
|
||||
pub mod api;
|
||||
pub mod prefixes;
|
||||
pub mod prot;
|
||||
pub mod qty;
|
||||
pub mod unit;
|
||||
|
||||
pub type PrecDec = RBig;
|
||||
|
||||
macro_rules! prot_try {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
prot::send_str(format!("{}:{}:{}: {e:?}", file!(), line!(), column!()));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "qty")]
|
||||
pub extern "C" fn qty(arg1_len: usize) -> usize {
|
||||
let qty = prot_try!(unsafe { prot::recv_string(arg1_len) });
|
||||
let qty = prot_try!(api::qty(&qty));
|
||||
prot::send_bytes(qty.into_bytes());
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "raw_num")]
|
||||
pub extern "C" fn raw_num(prec_len: usize, qty_len: usize) -> usize {
|
||||
let (prec, qty) = prot_try!(unsafe { prot::recv_byte_n_qty(prec_len, qty_len) });
|
||||
prot::send_bytes(format!("{:.*}", prec as usize, qty.n.to_f64().value()));
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "fmt")]
|
||||
pub extern "C" fn fmt(prec_len: usize, qty_len: usize) -> usize {
|
||||
let (prec, qty) = prot_try!(unsafe { prot::recv_byte_n_qty(prec_len, qty_len) });
|
||||
prot::send_bytes(format!("{qty:.*}", prec as usize));
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "op_add")]
|
||||
pub extern "C" fn op_add(lhs_len: usize, rhs_len: usize) -> usize {
|
||||
let (lhs, rhs) = prot_try!(unsafe { prot::recv_2qty(lhs_len, rhs_len) });
|
||||
|
||||
prot::send_bytes((lhs + rhs).into_bytes());
|
||||
0
|
||||
}
|
||||
#[unsafe(export_name = "op_sub")]
|
||||
pub extern "C" fn op_sub(lhs_len: usize, rhs_len: usize) -> usize {
|
||||
let (lhs, rhs) = prot_try!(unsafe { prot::recv_2qty(lhs_len, rhs_len) });
|
||||
|
||||
prot::send_bytes((lhs - rhs).into_bytes());
|
||||
0
|
||||
}
|
||||
#[unsafe(export_name = "op_mul")]
|
||||
pub extern "C" fn op_mul(lhs_len: usize, rhs_len: usize) -> usize {
|
||||
let (lhs, rhs) = prot_try!(unsafe { prot::recv_2qty(lhs_len, rhs_len) });
|
||||
|
||||
prot::send_bytes((lhs * rhs).into_bytes());
|
||||
0
|
||||
}
|
||||
#[unsafe(export_name = "op_div")]
|
||||
pub extern "C" fn op_div(lhs_len: usize, rhs_len: usize) -> usize {
|
||||
let (lhs, rhs) = prot_try!(unsafe { prot::recv_2qty(lhs_len, rhs_len) });
|
||||
|
||||
prot::send_bytes((lhs / rhs).into_bytes());
|
||||
0
|
||||
}
|
||||
|
||||
// #[unsafe(export_name = "debug_bytes")]
|
||||
// pub extern "C" fn b(blen: usize) -> usize {
|
||||
// let q = unsafe { prot::recv_bytes(blen) };
|
||||
// prot::send_bytes(format!("{q:?}"));
|
||||
|
||||
// 0
|
||||
// }
|
||||
43
bqst-core/src/prefixes.rs
Normal file
43
bqst-core/src/prefixes.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
pub type Prefix = (&'static str, char);
|
||||
|
||||
// each step is 10**±3
|
||||
pub static BIG_PREFS: &[Prefix] = &[
|
||||
("kilo", 'k'),
|
||||
("mega", 'M'),
|
||||
("giga", 'G'),
|
||||
("tera", 'T'),
|
||||
("peta", 'P'),
|
||||
];
|
||||
pub static SMOL_PREFS: &[Prefix] = &[
|
||||
("milli", 'm'),
|
||||
("micro", 'µ'),
|
||||
("nano", 'n'),
|
||||
("pico", 'p'),
|
||||
("femto", 'f'),
|
||||
];
|
||||
|
||||
pub fn extract_prefix_and_rem(s: &str) -> Option<(Prefix, &str, i8)> {
|
||||
for (i, b) in BIG_PREFS.iter().enumerate() {
|
||||
if let Some(stripped) = s.strip_prefix(b.0) {
|
||||
return Some((*b, stripped, (i as i8 + 1) * 3));
|
||||
}
|
||||
}
|
||||
for (i, b) in SMOL_PREFS.iter().enumerate() {
|
||||
if let Some(stripped) = s.strip_prefix(b.0) {
|
||||
return Some((*b, stripped, (i as i8 + 1) * -3));
|
||||
}
|
||||
}
|
||||
|
||||
for (i, b) in BIG_PREFS.iter().enumerate() {
|
||||
if let Some(stripped) = s.strip_prefix(b.1) {
|
||||
return Some((*b, stripped, (i as i8 + 1) * 3));
|
||||
}
|
||||
}
|
||||
for (i, b) in SMOL_PREFS.iter().enumerate() {
|
||||
if let Some(stripped) = s.strip_prefix(b.1) {
|
||||
return Some((*b, stripped, (i as i8 + 1) * -3));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
74
bqst-core/src/prot.rs
Normal file
74
bqst-core/src/prot.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::qty::Qty;
|
||||
|
||||
#[link(wasm_import_module = "typst_env")]
|
||||
unsafe extern "C" {
|
||||
#[link_name = "wasm_minimal_protocol_send_result_to_host"]
|
||||
pub fn send_result_to_host(ptr: *const u8, len: usize);
|
||||
#[link_name = "wasm_minimal_protocol_write_args_to_buffer"]
|
||||
pub fn write_args_to_buffer(ptr: *mut MaybeUninit<u8>);
|
||||
}
|
||||
|
||||
pub fn send_bytes(s: impl AsRef<[u8]>) {
|
||||
let r = s.as_ref();
|
||||
unsafe { send_result_to_host(r.as_ptr(), r.len()) };
|
||||
}
|
||||
|
||||
pub fn send_str(s: impl AsRef<str>) {
|
||||
let r = s.as_ref();
|
||||
unsafe { send_result_to_host(r.as_ptr(), r.len()) };
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// `len` MUST be the taken size from the fn arg
|
||||
pub unsafe fn recv_bytes(len: usize) -> Box<[u8]> {
|
||||
let mut bytes = Box::<[u8]>::new_uninit_slice(len);
|
||||
unsafe {
|
||||
write_args_to_buffer(bytes.as_mut_ptr());
|
||||
bytes.assume_init()
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// `len` MUST be the taken size from the fn arg
|
||||
pub unsafe fn recv_string(len: usize) -> Result<String, std::string::FromUtf8Error> {
|
||||
let b = unsafe { recv_bytes(len) };
|
||||
String::from_utf8(b.into_vec())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Must be the size of valid and come from qty bytes
|
||||
pub unsafe fn recv_qty(len: usize) -> Result<Qty, String> {
|
||||
let qty = unsafe { recv_bytes(len) };
|
||||
unsafe { Qty::try_from_bytes(&qty).map_err(|e| format!("{e:?}")) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Must be the size of valid and come from qty bytes
|
||||
pub unsafe fn recv_byte_n_qty(byte_len: usize, len: usize) -> Result<(u8, Qty), String> {
|
||||
assert_eq!(byte_len, 1, "byte_len should be 1");
|
||||
|
||||
let qty = unsafe { recv_bytes(byte_len + len) };
|
||||
unsafe {
|
||||
Qty::try_from_bytes(&qty[1..])
|
||||
.map_err(|e| format!("{e:?}"))
|
||||
.map(|s| (qty[0], s))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Must be the size of valid and come from qty bytes
|
||||
pub unsafe fn recv_2qty(len1: usize, len2: usize) -> Result<(Qty, Qty), String> {
|
||||
let qtybuf = unsafe { recv_bytes(len1 + len2) };
|
||||
|
||||
let qty1 = unsafe { Qty::try_from_bytes(&qtybuf[0..len1]).map_err(|e| format!("{e:?}")) }?;
|
||||
let qty2 = unsafe { Qty::try_from_bytes(&qtybuf[len1..]).map_err(|e| format!("{e:?}")) }?;
|
||||
|
||||
Ok((qty1, qty2))
|
||||
}
|
||||
181
bqst-core/src/qty.rs
Normal file
181
bqst-core/src/qty.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
PrecDec,
|
||||
prefixes::{BIG_PREFS, SMOL_PREFS},
|
||||
unit::Unit,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Qty {
|
||||
pub n: PrecDec,
|
||||
pub u: Unit,
|
||||
}
|
||||
|
||||
impl Display for Qty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn normalize_num(mut n: PrecDec) -> (PrecDec, Option<char>) {
|
||||
let mut i = 0;
|
||||
|
||||
if n.is_zero() {
|
||||
return (n, None);
|
||||
}
|
||||
|
||||
if n > PrecDec::from(1000) {
|
||||
while n > PrecDec::from(1000) && i + 1 < BIG_PREFS.len() {
|
||||
i += 1;
|
||||
n /= PrecDec::from(1000);
|
||||
}
|
||||
|
||||
return (n, Some(BIG_PREFS[i - 1].1));
|
||||
}
|
||||
|
||||
if n < PrecDec::from(1) {
|
||||
while n < PrecDec::from(1) && i + 1 < SMOL_PREFS.len() {
|
||||
i += 1;
|
||||
n *= PrecDec::from(1000);
|
||||
}
|
||||
|
||||
return (n, Some(SMOL_PREFS[i - 1].1));
|
||||
}
|
||||
|
||||
(n, None)
|
||||
}
|
||||
|
||||
let is_none_unit = self.u == Unit::None;
|
||||
let (n, pref) = if is_none_unit {
|
||||
(self.n.clone(), None)
|
||||
} else {
|
||||
normalize_num(self.n.clone())
|
||||
};
|
||||
|
||||
if n != PrecDec::from(1) || is_none_unit {
|
||||
let flt = n.to_f64().value();
|
||||
Display::fmt(&flt, f)?;
|
||||
|
||||
if !is_none_unit {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
|
||||
if !is_none_unit {
|
||||
if let Some(pref) = pref {
|
||||
write!(f, "{}", pref)?;
|
||||
}
|
||||
write!(f, "{}", TryInto::<char>::try_into(self.u).unwrap())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct InvalidQtyBytes<'a>(&'a [u8]);
|
||||
|
||||
// impl Qty {
|
||||
// pub fn into_bytes(self) -> Vec<u8> {
|
||||
// let mut r = Vec::new();
|
||||
// r.push(self.u as u8);
|
||||
// {
|
||||
// let (ibig, exp) = self.n.into_repr().into_parts();
|
||||
// r.extend_from_slice(&exp.to_le_bytes());
|
||||
// r.extend(ibig.to_string().as_bytes());
|
||||
// }
|
||||
// r
|
||||
// }
|
||||
|
||||
// /// # Safety
|
||||
// ///
|
||||
// /// `b` must come from valid bytes
|
||||
// pub unsafe fn try_from_bytes(b: &[u8]) -> Result<Self, InvalidQtyBytes<'_>> {
|
||||
// let u = unsafe { std::mem::transmute::<u8, Unit>(b[0]) };
|
||||
// let n = {
|
||||
// let le_bytes: [u8; 4] = [b[1], b[2], b[3], b[4]]; // T-T
|
||||
// let exp = isize::from_le_bytes(le_bytes);
|
||||
// let ibig_str = str::from_utf8(&b[5..])
|
||||
// .map_err(|_| InvalidQtyBytes(b))?
|
||||
// .to_string();
|
||||
// let ibig: IBig = ibig_str.parse().map_err(|_| InvalidQtyBytes(b))?;
|
||||
// PrecDec::from_parts(ibig, exp)
|
||||
// };
|
||||
|
||||
// Ok(Self { u, n })
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Qty {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
let mut r = Vec::new();
|
||||
r.push(self.u as u8);
|
||||
let (n, d) = self.n.into_parts();
|
||||
r.extend(format!("{n}/{d}").as_bytes());
|
||||
r
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// `b` must come from valid bytes
|
||||
pub unsafe fn try_from_bytes(b: &[u8]) -> Result<Self, InvalidQtyBytes<'_>> {
|
||||
let u = unsafe { std::mem::transmute::<u8, Unit>(b[0]) };
|
||||
let n = {
|
||||
let s = str::from_utf8(&b[1..]).expect("should be valid utf8");
|
||||
let (ns, ds) = s.split_once('/').expect("couldnt split");
|
||||
let (n, d) = (ns.parse().unwrap(), ds.parse().unwrap());
|
||||
PrecDec::from_parts(n, d)
|
||||
};
|
||||
|
||||
Ok(Self { u, n })
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Qty {
|
||||
type Output = Qty;
|
||||
|
||||
fn add(mut self, rhs: Self) -> Self::Output {
|
||||
assert_eq!(
|
||||
self.u, rhs.u,
|
||||
"attempted to add qty's of different dimensions"
|
||||
);
|
||||
|
||||
self.n += rhs.n;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Qty {
|
||||
type Output = Qty;
|
||||
|
||||
fn sub(mut self, rhs: Self) -> Self::Output {
|
||||
assert_eq!(
|
||||
self.u, rhs.u,
|
||||
"attempted to sub qty's of different dimensions"
|
||||
);
|
||||
|
||||
self.n -= rhs.n;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Qty {
|
||||
type Output = Qty;
|
||||
|
||||
fn mul(mut self, rhs: Self) -> Self::Output {
|
||||
self.u *= rhs.u;
|
||||
self.n *= rhs.n;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Qty {
|
||||
type Output = Qty;
|
||||
|
||||
fn div(mut self, rhs: Self) -> Self::Output {
|
||||
self.u /= rhs.u;
|
||||
self.n /= rhs.n;
|
||||
self
|
||||
}
|
||||
}
|
||||
103
bqst-core/src/unit.rs
Normal file
103
bqst-core/src/unit.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use std::ops::{DivAssign, MulAssign};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Unit {
|
||||
Ampere,
|
||||
Watt,
|
||||
Volt,
|
||||
Ohm,
|
||||
None,
|
||||
}
|
||||
|
||||
use Unit::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnknownUnit;
|
||||
|
||||
impl TryFrom<Unit> for char {
|
||||
type Error = UnknownUnit;
|
||||
|
||||
fn try_from(value: Unit) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Ampere => Ok('A'),
|
||||
Watt => Ok('W'),
|
||||
Volt => Ok('V'),
|
||||
Ohm => Ok('Ω'),
|
||||
None => Err(UnknownUnit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<char> for Unit {
|
||||
type Error = UnknownUnit;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
'V' => Ok(Volt),
|
||||
'A' => Ok(Ampere),
|
||||
'W' => Ok(Watt),
|
||||
'Ω' => Ok(Ohm),
|
||||
_ => Err(UnknownUnit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Unit {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"V" | "volt" | "volts" => Ok(Volt),
|
||||
"A" | "amp" | "amps" | "ampere" | "amperes" => Ok(Ampere),
|
||||
"W" | "watt" | "watts" => Ok(Watt),
|
||||
"Ω" | "ohm" | "ohms" => Ok(Ohm),
|
||||
"" => Ok(None),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Unit> for Unit {
|
||||
fn mul_assign(&mut self, rhs: Unit) {
|
||||
*self = match (*self, rhs) {
|
||||
(Ampere, Volt) | (Volt, Ampere) => Watt,
|
||||
(Ampere, Ohm) | (Ohm, Ampere) => Volt,
|
||||
(Watt, _) | (_, Watt) | (Ampere, Ampere) | (Volt, Volt) | (Ohm, Ohm) => {
|
||||
panic!("Unavailable unit")
|
||||
}
|
||||
(Volt, Ohm) | (Ohm, Volt) => panic!("Unknown unit"),
|
||||
(t, None) => t,
|
||||
(None, t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<Unit> for Unit {
|
||||
fn div_assign(&mut self, rhs: Unit) {
|
||||
*self = match (*self, rhs) {
|
||||
(t, None) => t,
|
||||
// FIXME: bodge to allow calculating parallel circuit
|
||||
// resistance (1 / sum(1 / R_i))
|
||||
(None, Ohm) => Ohm,
|
||||
(None, _) => panic!("idk the inverse of any of the types"),
|
||||
(a, b) if a == b => None,
|
||||
|
||||
(Ampere, Ampere) | (Watt, Watt) | (Volt, Volt) | (Ohm, Ohm) => None,
|
||||
|
||||
(Watt, Ampere) => Volt,
|
||||
(Watt, Volt) => Ampere,
|
||||
(Volt, Ampere) => Ohm,
|
||||
(Volt, Ohm) => Ampere,
|
||||
|
||||
(Ampere, Volt)
|
||||
| (Ampere, Ohm)
|
||||
| (Ohm, Volt)
|
||||
| (Ohm, Ampere)
|
||||
| (Watt, Ohm)
|
||||
| (_, Watt) => {
|
||||
panic!("Unavailable unit")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user