use std::error;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum GVLIError {
BaseTooShort(usize),
ThresholdsTooShort(usize),
ThresholdTooLarge(usize, usize),
DigitNotInBase(char),
EndOfDigits,
}
impl error::Error for GVLIError {}
impl fmt::Display for GVLIError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GVLIError::BaseTooShort(b) => {
write!(f, "base is too short: got length {}, should be >= 2", b)
}
GVLIError::ThresholdsTooShort(t) => {
write!(f,
"thresholds is too short: got length {}, should be >= 1", t)
}
GVLIError::ThresholdTooLarge(b, t) => {
write!(f,
"threshold {} is too large for base of length {}", t, b)
}
GVLIError::DigitNotInBase(d) => {
write!(f, "digit {} is not present in the base", d)
}
GVLIError::EndOfDigits => {
write!(f, "digit sequence ended without a terminator digit")
}
}
}
}
#[derive(Debug, PartialEq)]
pub struct Parameters<const B: usize, const T: usize> {
base: [char; B],
thresholds: [usize; T],
}
impl<const B: usize, const T: usize> Parameters<B, T> {
pub fn from_parts(base: &[char;B], thresholds: &[usize;T])
-> Result<Self, GVLIError>
{
if B < 2 {
return Err(GVLIError::BaseTooShort(B));
}
if T < 1 {
return Err(GVLIError::ThresholdsTooShort(T));
}
if let Some(t) = thresholds.iter().filter(|t| B <= **t).next() {
return Err(GVLIError::ThresholdTooLarge(B, *t))
}
Ok(Parameters {
base: base.clone(),
thresholds: thresholds.clone()
})
}
pub fn base_len(&self) -> usize { B }
pub fn digit(&self, position: usize) -> Option<char>
{
self.base.get(position).copied()
}
pub fn position(&self, digit: char) -> Result<usize, GVLIError>
{
self.base.iter()
.position(|c| *c == digit)
.ok_or(GVLIError::DigitNotInBase(digit))
}
pub fn threshold(&self, i: usize) -> usize
{
*self.thresholds.get(i).or(self.thresholds.last()).unwrap()
}
}
pub const BASE_OCTAL: [char; 8] = ['0', '1', '2', '3', '4', '5', '6', '7'];
pub const BASE_DECIMAL: [char; 10] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
pub const BASE_HEX: [char; 16] = [
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
pub const BASE_PUNYCODE: [char; 36] = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
pub const THRESHOLDS_PUNYCODE: [usize; 3] = [1, 1, 26];
pub const PUNYCODE: Parameters<36, 3> = Parameters {
base: BASE_PUNYCODE,
thresholds: THRESHOLDS_PUNYCODE
};
pub fn encode_one_noalloc<const B: usize, const T: usize>
(mut n: usize, params: &Parameters<B, T>, result: &mut String)
-> ()
{
let mut i = 0;
loop {
let t: usize = params.threshold(i);
if n < t {
result.push(params.digit(n).unwrap());
return;
}
let digit = t + ((n - t) % (B - t));
result.push(params.digit(digit).unwrap());
n = (n - t) / (B - t);
i += 1;
}
}
pub fn encode_one<const B: usize, const T: usize>
(n: usize, params: &Parameters<B, T>)
-> String
{
let mut result = String::new();
encode_one_noalloc(n, params, &mut result);
result
}
pub fn encode_noalloc<const B: usize, const T: usize>
(values: &[usize], params: &Parameters<B, T>, result: &mut String)
-> ()
{
for n in values.iter() {
encode_one_noalloc(*n, params, result);
}
}
pub fn encode<const B: usize, const T: usize>
(values: &[usize], params: &Parameters<B, T>)
-> String
{
let mut result = String::new();
encode_noalloc(values, params, &mut result);
result
}
pub fn decode<const B: usize, const T: usize>
(digits: &str, params: &Parameters<B, T>)
-> Result<Vec<usize>, GVLIError>
{
let mut result: Vec<usize> = Vec::new();
let mut i = 0;
let mut w = 1;
let mut n = 0;
for digit in digits.chars() {
let d: usize = params.position(digit)?;
let t: usize = params.threshold(i);
n += d * w;
if d < t {
result.push(n);
i = 0;
w = 1;
n = 0;
} else {
w = w * (B - t); i += 1
}
}
if i != 0 {
Err(GVLIError::EndOfDigits)
} else {
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parameters_errors() {
assert_eq!(
Parameters::from_parts(&[], &[]),
Err(GVLIError::BaseTooShort(0)));
assert_eq!(
Parameters::from_parts(&['0'], &[]),
Err(GVLIError::BaseTooShort(1)));
assert_eq!(
Parameters::from_parts(&['0', '1'], &[]),
Err(GVLIError::ThresholdsTooShort(0)));
assert_eq!(
Parameters::from_parts(&['0', '1'], &[2]),
Err(GVLIError::ThresholdTooLarge(2, 2)));
}
#[test]
fn encode_one_rfc3492() {
let params = Parameters::from_parts(&BASE_OCTAL, &[2, 3, 5]).unwrap();
assert_eq!(encode_one(145, ¶ms), "734");
assert_eq!(encode_one(62, ¶ms), "251");
}
#[test]
fn encode_one_wikipedia() {
assert_eq!(
encode_one(745, &PUNYCODE), "kva");
}
#[test]
fn encode_rfc3492() {
let params = Parameters::from_parts(&BASE_OCTAL, &[2, 3, 5]).unwrap();
assert_eq!(encode(&[145, 62], ¶ms), "734251");
}
#[test]
fn decode_rfc3492() {
let params = Parameters::from_parts(&BASE_OCTAL, &[2, 3, 5]).unwrap();
assert_eq!(decode("734251", ¶ms), Ok(vec![145, 62]));
assert_eq!(decode("kva", &PUNYCODE), Ok(vec![745]));
}
#[test]
fn decode_errors() {
assert_eq!(
decode("123-", &PUNYCODE),
Err(GVLIError::DigitNotInBase('-')));
assert_eq!(decode("kv", &PUNYCODE), Err(GVLIError::EndOfDigits));
}
#[test]
fn check_punycode_construction() {
let _ =
Parameters::from_parts(&PUNYCODE.base, &PUNYCODE.thresholds)
.unwrap();
}
}