initial
This commit is contained in:
commit
d3756e4645
9 changed files with 675 additions and 0 deletions
166
src/lib.rs
Normal file
166
src/lib.rs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
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())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue