dollcode-rs/src/lib.rs
Winter Hille d3756e4645 initial
2025-09-25 23:30:38 -07:00

166 lines
3.7 KiB
Rust

use rug::Integer as Int;
#[derive(Debug)]
pub struct InvalidCharError {
ch: char,
pos: usize,
}
impl std::error::Error for InvalidCharError {}
impl std::fmt::Display for InvalidCharError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid character {:?} as {:?}", self.ch, self.pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dollcode(String);
impl Dollcode {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn encode<N>(n: N) -> Self
where
N: Into<Int>,
{
let mut n = n.into();
if n.is_zero() {
return Dollcode(String::new());
}
let mut out = Vec::new();
while n > Int::ZERO {
n -= 1;
out.push(match Int::from(&n % 3).to_u8_wrapping() {
0 => '▖',
1 => '▘',
2 => '▌',
_ => unreachable!(),
});
n /= 3;
}
out.reverse();
Dollcode(out.into_iter().collect())
}
pub fn encode_str(s: &str) -> Self {
if s.is_empty() {
return Self::encode(Int::ZERO);
}
let mut n = Int::ZERO;
for byte in s.bytes() {
n = (n << 8) + byte;
}
Self::encode(n)
}
pub fn decode(&self) -> Int {
if self.0.is_empty() {
return Int::ZERO;
}
let mut n = Int::ZERO;
for ch in self.0.chars() {
n *= 3;
n += match ch {
'▖' => 0,
'▘' => 1,
'▌' => 2,
_ => unreachable!(),
};
n += 1;
}
n
}
pub fn decode_str(&self) -> String {
let mut n = self.decode();
if n.is_zero() {
return String::new();
}
let mut out = Vec::new();
while n > Int::ZERO {
out.push(char::from(Int::from(&n & u8::MAX).to_u8_wrapping()));
n >>= 8;
}
out.reverse();
out.into_iter().collect()
}
}
impl std::str::FromStr for Dollcode {
type Err = InvalidCharError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((pos, ch)) = s
.chars()
.enumerate()
.find(|&(_, ch)| !matches!(ch, '▖' | '▘' | '▌'))
{
return Err(InvalidCharError { ch, pos });
}
Ok(Dollcode(s.to_string()))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
mod int {
use super::*;
#[test]
fn test_encode() {
let dc = Dollcode::encode(Int::from(1234));
assert_eq!(dc.as_str(), "▖▖▘▌▖▌▖")
}
#[test]
fn test_decode() {
let dc = Dollcode::from_str("▖▖▘▌▖▌▖").unwrap();
assert_eq!(dc.decode(), Int::from(1234))
}
}
mod str {
use super::*;
#[test]
fn test_encode() {
let dc = Dollcode::encode_str("test");
assert_eq!(dc.as_str(), "▖▖▘▘▌▘▌▘▖▖▘▖▌▌▘▖▘▘▖▖")
}
#[test]
fn test_decode() {
let dc = Dollcode::from_str("▖▖▘▘▌▘▌▘▖▖▘▖▌▌▘▖▘▘▖▖").unwrap();
assert_eq!(dc.decode_str(), "test")
}
}
#[test]
fn test_parse_valid() {
let dc = "▖▖▘▘▌▘▌▘▖▖▘▖▌▌▘▖▘▘▖▖".parse::<Dollcode>();
assert!(dc.is_ok())
}
#[test]
fn test_parse_invalid() {
let dc = "test".parse::<Dollcode>();
assert!(dc.is_err())
}
}