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) -> Self where N: Into, { 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 { 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::(); assert!(dc.is_ok()) } #[test] fn test_parse_invalid() { let dc = "test".parse::(); assert!(dc.is_err()) } }