Initial commit: base95/220 and some other basics
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/.cursor
|
||||||
|
/target
|
||||||
186
Cargo.lock
generated
Normal file
186
Cargo.lock
generated
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.49"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
|
||||||
|
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 = "rs-furc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.112"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "rs-furc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
authors = ["IceDragon <icedragon@quickfox.org>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "furc"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
303
src/base220.rs
Normal file
303
src/base220.rs
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
use core::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
// char offset for the zero value
|
||||||
|
pub const B220_ZERO: u8 = '#' as u8; // 0x23
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ERRORS /////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Base220Error {
|
||||||
|
IntOverflow(u32, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Base220Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Base220Error::IntOverflow(n, len) => write!(f, "input number {n} is too long for {len} bytes"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// STRUCTS ////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Base220Digit(u8);
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Base220Digit {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
const MAX_VALUE: u8 = 255 - B220_ZERO;
|
||||||
|
if value <= MAX_VALUE {
|
||||||
|
Ok(Self(value + B220_ZERO))
|
||||||
|
} else {
|
||||||
|
Err(format!("base220 value {value} is too high (bigger than {MAX_VALUE})"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Base220Digit> for u8 {
|
||||||
|
fn from(value: Base220Digit) -> Self {
|
||||||
|
// precaution
|
||||||
|
if value.0 < B220_ZERO {
|
||||||
|
panic!("Base220Digit({}) stored value is too low (min: {})!", value.0, B220_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
value.0 - B220_ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Base220(pub u32);
|
||||||
|
|
||||||
|
impl Base220 {
|
||||||
|
/// Returns the integer value of this base220 number
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use furc::base220::Base220;
|
||||||
|
/// let raw_b220_value = b"$$"; // 221
|
||||||
|
/// let b220 = Base220::from(raw_b220_value);
|
||||||
|
/// assert_eq!(b220.value(), 221);
|
||||||
|
/// ```
|
||||||
|
pub fn value(&self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_owned_str(&self) -> String {
|
||||||
|
to_base220vec(self.0).iter().map(|&c| c as char).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
|
to_base220vec(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec_size(&self, size: usize) -> Result<Vec<u8>, Base220Error> {
|
||||||
|
let mut b220: Vec<u8> = Vec::with_capacity(size);
|
||||||
|
match to_base220(self.0, &mut b220) {
|
||||||
|
Ok(_) => Ok(b220),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store<'a>(&self, bytes: &'a mut [u8]) -> Result<&'a [u8], Base220Error> {
|
||||||
|
to_base220(self.0, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Base220 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.as_owned_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Base220> for u32 {
|
||||||
|
fn from(value: Base220) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Base220 {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<&[u8; N]> for Base220 {
|
||||||
|
fn from(value: &[u8; N]) -> Self {
|
||||||
|
Self(from_base220(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[u8]> for Base220 {
|
||||||
|
fn from(value: &[u8]) -> Self {
|
||||||
|
Self(from_base220(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Base220 {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self::from(value.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub fn from_base220(b220: &[u8]) -> u32 {
|
||||||
|
let mut acc = 0;
|
||||||
|
let mut multiplier = 1;
|
||||||
|
for ch in b220.iter() {
|
||||||
|
acc += multiplier * ((*ch - B220_ZERO) as u32);
|
||||||
|
multiplier *= 220;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_base220(n: u32, dest: &mut [u8]) -> Result<&[u8], Base220Error> {
|
||||||
|
let mut acc = n;
|
||||||
|
|
||||||
|
// least significant digit first
|
||||||
|
for i in 0..dest.len() {
|
||||||
|
dest[i] = ((acc % 220) as u8) + B220_ZERO;
|
||||||
|
acc /= 220;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we processed all of the digits, acc will be 0
|
||||||
|
match acc {
|
||||||
|
0 => Ok(dest),
|
||||||
|
_ => Err(Base220Error::IntOverflow(n, dest.len())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_base220vec(n: u32) -> Vec<u8> {
|
||||||
|
if n == 0 {
|
||||||
|
return [B220_ZERO].to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut acc = n;
|
||||||
|
let mut b220: Vec<u8> = Vec::new();
|
||||||
|
while acc > 0 {
|
||||||
|
b220.push((acc % 220) as u8 + B220_ZERO);
|
||||||
|
acc /= 220;
|
||||||
|
}
|
||||||
|
|
||||||
|
return b220;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TESTS //////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Base220Digit ///////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base220digit() {
|
||||||
|
assert!(Base220Digit::try_from(0).is_err());
|
||||||
|
assert!(Base220Digit::try_from(1).is_err());
|
||||||
|
assert!(Base220Digit::try_from(220).is_err());
|
||||||
|
assert!(!Base220Digit::try_from(221).is_err());
|
||||||
|
|
||||||
|
// check value conversion
|
||||||
|
for value in vec![0, 1, 220] {
|
||||||
|
match Base220Digit::try_from(value) {
|
||||||
|
Ok(b220digit) => {
|
||||||
|
let v: u8 = b220digit.into();
|
||||||
|
assert_eq!(v, value);
|
||||||
|
},
|
||||||
|
Err(e) => panic!("Base220Digit::try_from({}) failed: {}", value, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check invalild values
|
||||||
|
for value in 221..256 {
|
||||||
|
match Base220Digit::try_from(value) {
|
||||||
|
Ok(b220digit) => panic!("Base220Digit::try_from({}) succeeded: {:?}", value, b220digit),
|
||||||
|
Err(e) => assert!(e.contains("too high")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// FUNCTIONS //////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns all *(bytes, u32)* example tuples for base220 conversion
|
||||||
|
fn get_base220_conversion_table() -> Vec<(&'static [u8], u32)> {
|
||||||
|
vec![
|
||||||
|
// special case
|
||||||
|
(b"", 0),
|
||||||
|
|
||||||
|
// 1 digit
|
||||||
|
(b"#", 0),
|
||||||
|
(b"$", 1),
|
||||||
|
(b"\xFE", 219),
|
||||||
|
|
||||||
|
// 2 digits
|
||||||
|
(b"##", 0),
|
||||||
|
(b"$#", 1),
|
||||||
|
(b"#$", 220),
|
||||||
|
(b"$$", 221),
|
||||||
|
(b"4)", 1337),
|
||||||
|
(b"\xFE\xFE", 48399),
|
||||||
|
|
||||||
|
// 3 digits
|
||||||
|
(b"$##", 1),
|
||||||
|
(b"##$", 48400),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_base220() {
|
||||||
|
for (b220, num) in get_base220_conversion_table() {
|
||||||
|
assert_eq!(num, from_base220(b220))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_base220() {
|
||||||
|
// test correct approach with properly sized buffer
|
||||||
|
for (b220, num) in get_base220_conversion_table() {
|
||||||
|
let mut buf: Vec<u8> = vec![0u8; b220.len()];
|
||||||
|
assert_eq!(to_base220(num, &mut buf).unwrap(), b220);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test correct approach with over-sized buffer
|
||||||
|
for (b220, num) in get_base220_conversion_table() {
|
||||||
|
let mut buf: Vec<u8> = vec![0u8; b220.len() + 1];
|
||||||
|
let b220result = to_base220(num, &mut buf).unwrap();
|
||||||
|
|
||||||
|
// most significant byte should change from 0 to B220_ZERO
|
||||||
|
assert_eq!(b220result[b220result.len() - 1], B220_ZERO);
|
||||||
|
assert_eq!(
|
||||||
|
&b220result[..b220result.len()-1],
|
||||||
|
b220,
|
||||||
|
"to_base220({num}, buf) -?-> {b220:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// test undersized buffer & Base220Error::IntOverflow
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
assert!(to_base220(219, &mut buf).is_ok());
|
||||||
|
|
||||||
|
let result = to_base220(220, &mut buf);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), Base220Error::IntOverflow(220, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_base220vec() {
|
||||||
|
for (b220, num) in get_base220_conversion_table() {
|
||||||
|
// skip zero padding, but expect at least one byte in the result!
|
||||||
|
let b220expected = match num {
|
||||||
|
0 => &[B220_ZERO],
|
||||||
|
_ => match b220.iter().rev().position(|&c| c != B220_ZERO) {
|
||||||
|
Some(end_off) => &b220[..b220.len()-end_off],
|
||||||
|
None => b220,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_ne!(b220expected.len(), 0, "invalid expected b220 {:?} for input {}", b220, num);
|
||||||
|
assert_eq!(to_base220vec(num), b220expected, "input: {num}, expected: {b220expected:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
276
src/base95.rs
Normal file
276
src/base95.rs
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
use core::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
// char offset for the zero value
|
||||||
|
static B95_ZERO: u8 = ' ' as u8; // 0x20
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ERRORS /////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Base95Error {
|
||||||
|
IntOverflow(u32, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Base95Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Base95Error::IntOverflow(n, len) => write!(f, "input number {n} is too long for {len} bytes"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// STRUCTS ////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
|
pub struct Base95(pub u32);
|
||||||
|
|
||||||
|
impl Base95 {
|
||||||
|
pub fn as_owned_str(&self) -> String {
|
||||||
|
to_base95vec(self.0).iter().map(|&c| c as char).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
|
to_base95vec(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec_size(&self, size: usize) -> Result<Vec<u8>, Base95Error> {
|
||||||
|
let mut b95: Vec<u8> = Vec::with_capacity(size);
|
||||||
|
match to_base95(self.0, &mut b95) {
|
||||||
|
Ok(_) => Ok(b95),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store<'a>(&self, bytes: &'a mut [u8]) -> Result<&'a [u8], Base95Error> {
|
||||||
|
to_base95(self.0, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Base95 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.as_owned_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Base95> for u32 {
|
||||||
|
fn from(value: Base95) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Base95 {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<&[u8; N]> for Base95 {
|
||||||
|
fn from(value: &[u8; N]) -> Self {
|
||||||
|
Self(from_base95(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[u8]> for Base95 {
|
||||||
|
fn from(value: &[u8]) -> Self {
|
||||||
|
Self(from_base95(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Base95 {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self::from(value.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Converts a base95 number represented by a given slice - into a 32
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use furc::base95::from_base95;
|
||||||
|
/// assert_eq!(1, from_base95(b" !"));
|
||||||
|
/// assert_eq!(1, from_base95(b"!"));
|
||||||
|
/// assert_eq!(95, from_base95(b"! "));
|
||||||
|
/// assert_eq!(1337, from_base95(b".'"));
|
||||||
|
/// assert_eq!(9025, from_base95(b"! "));
|
||||||
|
/// ```
|
||||||
|
pub fn from_base95(b95: &[u8]) -> u32 {
|
||||||
|
let mut acc = 0;
|
||||||
|
let mut multiplier = 1;
|
||||||
|
for ch in b95.iter().rev() {
|
||||||
|
acc += multiplier * ((*ch - B95_ZERO) as u32);
|
||||||
|
multiplier *= 95;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a number into a base95 number, and stores it in `dest`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use furc::base95::to_base95;
|
||||||
|
/// use furc::base95::Base95Error;
|
||||||
|
///
|
||||||
|
/// let mut dest = [0; 2];
|
||||||
|
/// assert_eq!(b" ", to_base95(0, &mut dest).unwrap());
|
||||||
|
/// assert_eq!(b" !", to_base95(1, &mut dest).unwrap());
|
||||||
|
/// assert_eq!(b"! ", to_base95(95, &mut dest).unwrap());
|
||||||
|
/// assert_eq!(b"!!", to_base95(96, &mut dest).unwrap());
|
||||||
|
/// assert_eq!(b".'", to_base95(1337, &mut dest).unwrap());
|
||||||
|
/// assert_eq!(b"~~", to_base95(9024, &mut dest).unwrap());
|
||||||
|
///
|
||||||
|
/// let mut buf1 = [0; 1];
|
||||||
|
/// assert!(to_base95(95, &mut buf1).is_err());
|
||||||
|
/// assert!(matches!(to_base95(95, &mut buf1).unwrap_err(), Base95Error::IntOverflow(95, 1)));
|
||||||
|
///
|
||||||
|
/// match to_base95(95, &mut buf1) {
|
||||||
|
/// Ok(_) => panic!("crammed a 2-digit number inside 1 byte, wtf?"),
|
||||||
|
/// Err(Base95Error::IntOverflow(n, len)) => println!("{n} is too big for an array of {len} :("),
|
||||||
|
/// Err(e) => panic!("unexpected error: {e:?}"),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn to_base95(n: u32, dest: &mut [u8]) -> Result<&[u8], Base95Error> {
|
||||||
|
let mut acc = n;
|
||||||
|
|
||||||
|
// least significant digit last
|
||||||
|
for i in (0..dest.len()).rev() {
|
||||||
|
dest[i] = ((acc % 95) as u8) + B95_ZERO;
|
||||||
|
acc /= 95;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we processed all of the digits, acc will be 0
|
||||||
|
match acc {
|
||||||
|
0 => Ok(dest),
|
||||||
|
_ => Err(Base95Error::IntOverflow(n, dest.len())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a number into a base95 vector of an arbitrary size
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use furc::base95::to_base95vec;
|
||||||
|
///
|
||||||
|
/// assert_eq!(to_base95vec(0), b" ");
|
||||||
|
/// assert_eq!(to_base95vec(1), b"!");
|
||||||
|
/// assert_eq!(to_base95vec(95), b"! ");
|
||||||
|
/// assert_eq!(to_base95vec(96), b"!!");
|
||||||
|
/// assert_eq!(to_base95vec(1337), b".'");
|
||||||
|
/// assert_eq!(to_base95vec(9024), b"~~");
|
||||||
|
/// ```
|
||||||
|
pub fn to_base95vec(n: u32) -> Vec<u8> {
|
||||||
|
if n == 0 {
|
||||||
|
return [B95_ZERO].to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut acc = n;
|
||||||
|
let mut b95: Vec<u8> = Vec::new();
|
||||||
|
while acc > 0 {
|
||||||
|
b95.push((acc % 95) as u8 + B95_ZERO);
|
||||||
|
acc /= 95;
|
||||||
|
}
|
||||||
|
|
||||||
|
b95.reverse();
|
||||||
|
return b95;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TESTS //////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Returns all *(bytes, u32)* example tuples for base95 conversion
|
||||||
|
fn get_base95_conversion_table() -> Vec<(&'static [u8], u32)> {
|
||||||
|
vec![
|
||||||
|
// special case
|
||||||
|
(b"", 0),
|
||||||
|
|
||||||
|
// 1 digit
|
||||||
|
(b" ", 0),
|
||||||
|
(b" ", 0),
|
||||||
|
(b"!", 1),
|
||||||
|
(b"~", 94),
|
||||||
|
|
||||||
|
// 2 digits & order
|
||||||
|
(b" ", 0),
|
||||||
|
(b" !", 1),
|
||||||
|
(b"! ", 95),
|
||||||
|
(b"!!", 96),
|
||||||
|
(b".'", 1337),
|
||||||
|
(b"~~", 9024),
|
||||||
|
|
||||||
|
// 3 digits
|
||||||
|
(b" !", 1),
|
||||||
|
(b"! ", 9025),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_base95() {
|
||||||
|
for (b95, num) in get_base95_conversion_table() {
|
||||||
|
assert_eq!(from_base95(b95), num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_base95() {
|
||||||
|
// test correct approach with properly sized buffer
|
||||||
|
for (b95, num) in get_base95_conversion_table() {
|
||||||
|
let mut buf: Vec<u8> = vec![0u8; b95.len()];
|
||||||
|
assert_eq!(to_base95(num, &mut buf).unwrap(), b95);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test correct approach with over-sized buffer
|
||||||
|
for (b95, num) in get_base95_conversion_table() {
|
||||||
|
let mut buf: Vec<u8> = vec![0u8; b95.len() + 1];
|
||||||
|
let b95result = to_base95(num, &mut buf).unwrap();
|
||||||
|
|
||||||
|
// most significant byte should change from 0 to B95_ZERO
|
||||||
|
assert_eq!(b95result[0], B95_ZERO);
|
||||||
|
assert_eq!(
|
||||||
|
&b95result[1..],
|
||||||
|
b95,
|
||||||
|
"to_base95({num}, buf) -?-> {b95:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// test undersized buffer & Base95Error::IntOverflow
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
assert!(to_base95(94, &mut buf).is_ok());
|
||||||
|
|
||||||
|
let result = to_base95(95, &mut buf);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), Base95Error::IntOverflow(95, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_base95vec() {
|
||||||
|
for (b95, num) in get_base95_conversion_table() {
|
||||||
|
// skip zero padding, but expect at least one byte in the result!
|
||||||
|
let b95expected = match num {
|
||||||
|
0 => b" ",
|
||||||
|
_ => match b95.iter().position(|&c| c != B95_ZERO) {
|
||||||
|
Some(start) => &b95[start..],
|
||||||
|
None => b95,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_ne!(b95expected.len(), 0, "invalid expected b95 {:?} for input {}", b95, num);
|
||||||
|
assert_eq!(to_base95vec(num), b95expected, "input: {num}, expected: {b95expected:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/bin/base-convert.rs
Normal file
73
src/bin/base-convert.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use furc::base95::{from_base95, to_base95vec};
|
||||||
|
use furc::base220::{from_base220, to_base220vec};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "base-convert")]
|
||||||
|
#[command(version = "1.0")]
|
||||||
|
#[command(about = "Convert base95 and base220 Furcadia numbers", long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[clap(short, long, conflicts_with = "to", help = "Convert FROM base(...) to decimal (default)")]
|
||||||
|
from: bool,
|
||||||
|
|
||||||
|
#[clap(short, long, default_value_t = true, conflicts_with = "from", help = "Convert TO base(...) from decimal")]
|
||||||
|
to: bool,
|
||||||
|
|
||||||
|
#[clap(long, default_value_t = true, conflicts_with = "b220", help = "Convert to/from base95 (default)")]
|
||||||
|
b95: bool,
|
||||||
|
|
||||||
|
#[clap(long, conflicts_with = "b95", help = "Convert to/from base220")]
|
||||||
|
b220: bool,
|
||||||
|
|
||||||
|
#[clap(short, long, conflicts_with = "from")]
|
||||||
|
size: Option<u8>, // <- optional argumnet
|
||||||
|
|
||||||
|
#[clap(short, long, help = "Input number")]
|
||||||
|
input: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut cli = Cli::parse();
|
||||||
|
if cli.b220 { cli.b95 = false }
|
||||||
|
if cli.from { cli.to = false }
|
||||||
|
|
||||||
|
// ->base(...)
|
||||||
|
if cli.to {
|
||||||
|
// read input as decimal
|
||||||
|
let input = match cli.input.parse::<u32>() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => panic!("Error: invalid input {:?} ({:?})", cli.input, e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert decimal input into base95 or base220 based on choice
|
||||||
|
let result: String = if cli.b95 {
|
||||||
|
to_base95vec(input)
|
||||||
|
} else if cli.b220 {
|
||||||
|
to_base220vec(input)
|
||||||
|
} else {
|
||||||
|
panic!("unknown conversion mode (neither --b95, nor --b220)");
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c as char)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
print!("{}", result);
|
||||||
|
} else if cli.from {
|
||||||
|
// read input as bytes
|
||||||
|
let input = cli.input.as_bytes();
|
||||||
|
|
||||||
|
// convert into decimal
|
||||||
|
let result: u32 = if cli.b95 {
|
||||||
|
from_base95(input)
|
||||||
|
} else if cli.b220 {
|
||||||
|
from_base220(input)
|
||||||
|
} else {
|
||||||
|
panic!("unknown conversion mode (neither --b95, nor --b220)")
|
||||||
|
};
|
||||||
|
|
||||||
|
print!("{result}");
|
||||||
|
} else {
|
||||||
|
panic!("unknown direction (neither --to, nor --from)");
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/colors.rs
Normal file
166
src/colors.rs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
use std::u8;
|
||||||
|
|
||||||
|
use crate::base220::Base220Digit;
|
||||||
|
|
||||||
|
use super::base220::B220_ZERO;
|
||||||
|
|
||||||
|
enum Gender {
|
||||||
|
Male,
|
||||||
|
Female,
|
||||||
|
Unspecified,
|
||||||
|
Unknown(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Gender> for u8 {
|
||||||
|
fn from(value: Gender) -> Self {
|
||||||
|
match value {
|
||||||
|
Gender::Female => 0,
|
||||||
|
Gender::Male => 1,
|
||||||
|
Gender::Unspecified => 2,
|
||||||
|
Gender::Unknown(value) => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Gender {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => Gender::Female,
|
||||||
|
1 => Gender::Male,
|
||||||
|
2 => Gender::Unspecified,
|
||||||
|
v => Gender::Unknown(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BaseSpecies {
|
||||||
|
Rodent,
|
||||||
|
Equine,
|
||||||
|
Feline,
|
||||||
|
Canine,
|
||||||
|
Mustline,
|
||||||
|
Lapine,
|
||||||
|
Squirrel,
|
||||||
|
Bovine,
|
||||||
|
Unknown(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BaseSpecies> for u8 {
|
||||||
|
fn from(value: BaseSpecies) -> Self {
|
||||||
|
match value {
|
||||||
|
BaseSpecies::Rodent => 0,
|
||||||
|
BaseSpecies::Equine => 1,
|
||||||
|
BaseSpecies::Feline => 2,
|
||||||
|
BaseSpecies::Canine => 3,
|
||||||
|
BaseSpecies::Mustline => 4,
|
||||||
|
BaseSpecies::Lapine => 5,
|
||||||
|
BaseSpecies::Squirrel => 6,
|
||||||
|
BaseSpecies::Bovine => 7,
|
||||||
|
BaseSpecies::Unknown(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for BaseSpecies {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => BaseSpecies::Rodent,
|
||||||
|
1 => BaseSpecies::Equine,
|
||||||
|
2 => BaseSpecies::Feline,
|
||||||
|
3 => BaseSpecies::Canine,
|
||||||
|
4 => BaseSpecies::Mustline,
|
||||||
|
5 => BaseSpecies::Lapine,
|
||||||
|
6 => BaseSpecies::Squirrel,
|
||||||
|
7 => BaseSpecies::Bovine,
|
||||||
|
v => BaseSpecies::Unknown(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Colors([u8; 14]);
|
||||||
|
|
||||||
|
impl Colors {
|
||||||
|
pub const TYPE_BYTE: u8 = b't';
|
||||||
|
|
||||||
|
// Offsets ////////////////////////////////////////////////////////////////
|
||||||
|
pub const OFFSET_FUR: usize = 1;
|
||||||
|
pub const OFFSET_MARKINGS: usize = 2;
|
||||||
|
pub const OFFSET_HAIR: usize = 3;
|
||||||
|
pub const OFFSET_EYE: usize = 4;
|
||||||
|
pub const OFFSET_BADGE: usize = 5;
|
||||||
|
pub const OFFSET_VEST: usize = 6;
|
||||||
|
pub const OFFSET_BRACER: usize = 7;
|
||||||
|
pub const OFFSET_CAPE: usize = 8;
|
||||||
|
pub const OFFSET_BOOT: usize = 9;
|
||||||
|
pub const OFFSET_TROUSER: usize = 10;
|
||||||
|
pub const OFFSET_GENDER: usize = 11;
|
||||||
|
pub const OFFSET_SPECIES: usize = 12;
|
||||||
|
pub const OFFSET_SPECIAL: usize = 13;
|
||||||
|
|
||||||
|
// Static /////////////////////////////////////////////////////////////////
|
||||||
|
pub fn new() -> Colors {
|
||||||
|
let mut c = [B220_ZERO; 14];
|
||||||
|
c[0] = Self::TYPE_BYTE;
|
||||||
|
Self(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods ////////////////////////////////////////////////////////////////
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gender(&self) -> Gender {
|
||||||
|
Gender::from(self.0[Self::OFFSET_GENDER])
|
||||||
|
}
|
||||||
|
pub fn set_gender(&mut self, gender: Gender) {
|
||||||
|
self.0[Self::OFFSET_GENDER] = gender.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn species(&self) -> BaseSpecies {
|
||||||
|
BaseSpecies::from(self.0[Self::OFFSET_SPECIES])
|
||||||
|
}
|
||||||
|
pub fn set_species(&mut self, species: BaseSpecies) {
|
||||||
|
self.0[Self::OFFSET_SPECIES] = species.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[u8]> for Colors {
|
||||||
|
fn from(value: &[u8]) -> Self {
|
||||||
|
// ensure value is a compatible color type
|
||||||
|
if value[0] != Self::TYPE_BYTE {
|
||||||
|
panic!("Colors::from({:?}): input starts with {:?} and not with {:?}", value, value[0] as char, Self::TYPE_BYTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure value isn't too short
|
||||||
|
if value.len() < 14 {
|
||||||
|
panic!("Colors::from({:?}): expects a slice of at least 14 bytes, got {}", value, value.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut arr = [B220_ZERO; 14];
|
||||||
|
arr.copy_from_slice(&value[..14]);
|
||||||
|
Colors(arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Colors {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self::from(value.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_colors_default() {
|
||||||
|
let col = Colors::new();
|
||||||
|
|
||||||
|
// ensure the raw value starts with 't' and is all base220 zeroes
|
||||||
|
let col_bytes = col.as_bytes();
|
||||||
|
assert_eq!(col_bytes[0], b't');
|
||||||
|
for &c in col_bytes.iter().skip(1) {
|
||||||
|
assert_eq!(c, B220_ZERO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/conn.rs
Normal file
101
src/conn.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
static FURC_BUFSZ: i32 = 4096;
|
||||||
|
|
||||||
|
pub trait FurcOutput {
|
||||||
|
fn send_bytes(&self, msg: &[u8]);
|
||||||
|
|
||||||
|
fn send_str(&self, msg: String) {
|
||||||
|
self.send_bytes(msg.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Protocol
|
||||||
|
{
|
||||||
|
output: dyn FurcOutput,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum MoveDirection {
|
||||||
|
SW = 1,
|
||||||
|
SE = 3,
|
||||||
|
NW = 7,
|
||||||
|
NE = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum TurnDirection {
|
||||||
|
CW = b'>', // clockwise
|
||||||
|
CCW = b'<', // counter-clockwise
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<u8> for TurnDirection {
|
||||||
|
fn into(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub enum ClientMessage {
|
||||||
|
Raw(String),
|
||||||
|
Account { email: String, name: String, password: String },
|
||||||
|
Connect { name: String, password: String },
|
||||||
|
Turn(TurnDirection),
|
||||||
|
Move(MoveDirection),
|
||||||
|
Say(String),
|
||||||
|
Emote(String),
|
||||||
|
Shout(String),
|
||||||
|
ToggleShouts,
|
||||||
|
Whisper { name: String, msg: String },
|
||||||
|
WhisperExact { name: String, msg: String },
|
||||||
|
WhisperOffline { name: String, msg: String },
|
||||||
|
MakeCookie { name: String, msg: String },
|
||||||
|
GiveCookie { name: String, msg: String },
|
||||||
|
Who,
|
||||||
|
Join(String),
|
||||||
|
JoinLast,
|
||||||
|
SummonLast,
|
||||||
|
Summon(String),
|
||||||
|
Decline,
|
||||||
|
Vascodagama,
|
||||||
|
Quit,
|
||||||
|
Desc(String),
|
||||||
|
Colors([u8; 13]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Protocol {
|
||||||
|
pub fn account(&self, email: &str, name: &str, password: &str) {
|
||||||
|
self.output.send_str(format!("account {email} {name} {password}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(&self, name: &str, password: &str) {
|
||||||
|
self.output.send_str(format!("connect {name} {password}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn turn(&self, dir: TurnDirection) {
|
||||||
|
self.output.send_bytes(&[dir.into()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn turn_cw(&self) {
|
||||||
|
self.output.send_str(">".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn turn_ccw(&self) {
|
||||||
|
self.output.send_str("<".to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_turn_direction() {
|
||||||
|
let cw = TurnDirection::CW;
|
||||||
|
let cw_byte: u8 = cw.into();
|
||||||
|
assert_eq!(cw_byte, b'>');
|
||||||
|
|
||||||
|
let ccw = TurnDirection::CCW;
|
||||||
|
let ccw_byte: u8 = ccw.into();
|
||||||
|
assert_eq!(ccw_byte, b'<');
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/lib.rs
Normal file
8
src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
pub mod base95;
|
||||||
|
pub mod base220;
|
||||||
|
pub mod conn;
|
||||||
|
pub mod colors;
|
||||||
|
// fn main() {
|
||||||
|
// println!("Hello, world!");
|
||||||
|
// }
|
||||||
31
tests/test-base95.rs
Normal file
31
tests/test-base95.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use furc::base95::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_base95_from() {
|
||||||
|
assert_eq!(Base95(0), Base95::from(b""));
|
||||||
|
|
||||||
|
assert_eq!(Base95(0), Base95::from(b" "));
|
||||||
|
assert_eq!(Base95(1), Base95::from(b"!"));
|
||||||
|
assert_eq!(Base95(94), Base95::from(b"~"));
|
||||||
|
|
||||||
|
assert_eq!(Base95(0), Base95::from(b" "));
|
||||||
|
assert_eq!(Base95(1), Base95::from(b" !"));
|
||||||
|
assert_eq!(Base95(95), Base95::from(b"! "));
|
||||||
|
assert_eq!(Base95(96), Base95::from(b"!!"));
|
||||||
|
assert_eq!(Base95(9024), Base95::from(b"~~"));
|
||||||
|
|
||||||
|
assert_eq!(Base95(0), Base95::from(b" "));
|
||||||
|
assert_eq!(Base95(1), Base95::from(b" !"));
|
||||||
|
assert_eq!(Base95(9025), Base95::from(b"! "));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_base95_as_owned_str() {
|
||||||
|
assert_eq!(Base95(0).as_owned_str(), " ");
|
||||||
|
assert_eq!(Base95(1).as_owned_str(), "!");
|
||||||
|
assert_eq!(Base95(94).as_owned_str(), "~");
|
||||||
|
assert_eq!(Base95(95).as_owned_str(), "! ");
|
||||||
|
assert_eq!(Base95(96).as_owned_str(), "!!");
|
||||||
|
assert_eq!(Base95(9024).as_owned_str(), "~~");
|
||||||
|
assert_eq!(Base95(9025), Base95::from(b"! "));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user