Initial commit: base95/220 and some other basics
This commit is contained in:
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!");
|
||||
// }
|
||||
Reference in New Issue
Block a user