166 lines
3.7 KiB
Rust
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())
|
|
}
|
|
}
|