mirror of
https://github.com/inzerosight/ZWUS-rs.git
synced 2026-04-06 20:52:14 +00:00
Refactor: remove inline tests from lib
Co-authored-by: gpt-5.3-codex <no-reply@openai.com>
This commit is contained in:
151
src/lib.rs
Normal file
151
src/lib.rs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
pub const DEFAULT_BASE: u8 = 3;
|
||||||
|
pub const SUPPORTED_BASES: [u8; 3] = [3, 6, 8];
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Alphabet {
|
||||||
|
unifier: char,
|
||||||
|
digits: &'static [char],
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE_3: Alphabet = Alphabet {
|
||||||
|
unifier: '\u{00AD}',
|
||||||
|
digits: &['\u{180E}', '\u{200B}', '\u{200D}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const BASE_6: Alphabet = Alphabet {
|
||||||
|
unifier: '\u{200C}',
|
||||||
|
digits: &[
|
||||||
|
'\u{200D}', '\u{200F}', '\u{00AD}', '\u{2060}', '\u{200B}', '\u{200E}',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const BASE_8: Alphabet = Alphabet {
|
||||||
|
unifier: '\u{200C}',
|
||||||
|
digits: &[
|
||||||
|
'\u{200D}', '\u{200F}', '\u{00AD}', '\u{2060}', '\u{200B}', '\u{200E}', '\u{180E}',
|
||||||
|
'\u{FEFF}',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn alphabet(base: u8) -> Alphabet {
|
||||||
|
match base {
|
||||||
|
3 => BASE_3,
|
||||||
|
6 => BASE_6,
|
||||||
|
8 => BASE_8,
|
||||||
|
_ => panic!("Unsupported base {base}. Use 3, 6, or 8."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn to_base_digits(mut n: u32, base: u32) -> Vec<usize> {
|
||||||
|
if n == 0 {
|
||||||
|
return vec![0];
|
||||||
|
}
|
||||||
|
let mut out = Vec::new();
|
||||||
|
while n > 0 {
|
||||||
|
out.push((n % base) as usize);
|
||||||
|
n /= base;
|
||||||
|
}
|
||||||
|
out.reverse();
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_numbers<I>(numbers: I, base: u8) -> String
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = u32>,
|
||||||
|
{
|
||||||
|
let abc = alphabet(base);
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
for (i, n) in numbers.into_iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
out.push(abc.unifier);
|
||||||
|
}
|
||||||
|
for d in to_base_digits(n, base as u32) {
|
||||||
|
out.push(abc.digits[d]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn retain_zwus_chars(text: &str, abc: Alphabet) -> String {
|
||||||
|
text.chars()
|
||||||
|
.filter(|c| *c == abc.unifier || abc.digits.contains(c))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn decode_chunk(chunk: &str, abc: Alphabet, base: u8) -> Option<u32> {
|
||||||
|
chunk.chars().try_fold(0u32, |acc, ch| {
|
||||||
|
let digit = abc.digits.iter().position(|&x| x == ch)? as u32;
|
||||||
|
acc.checked_mul(base as u32)?.checked_add(digit)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_numbers(text: &str, base: u8) -> Vec<u32> {
|
||||||
|
let abc = alphabet(base);
|
||||||
|
let filtered = retain_zwus_chars(text, abc);
|
||||||
|
|
||||||
|
filtered
|
||||||
|
.split(abc.unifier)
|
||||||
|
.filter(|chunk| !chunk.is_empty())
|
||||||
|
.filter_map(|chunk| decode_chunk(chunk, abc, base))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zero Width Unicode Standard (ZWUS)
|
||||||
|
pub struct Zwus;
|
||||||
|
|
||||||
|
impl Zwus {
|
||||||
|
/// Encode a string using base 3 (default/safest).
|
||||||
|
pub fn encode_string(text: &str) -> String {
|
||||||
|
Self::encode_string_with_base(text, DEFAULT_BASE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode a string using base 3, 6, or 8.
|
||||||
|
pub fn encode_string_with_base(text: &str, base: u8) -> String {
|
||||||
|
encode_numbers(text.chars().map(|c| c as u32), base)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode numbers using base 3 (default/safest).
|
||||||
|
pub fn encode_number_array(numbers: &[u32]) -> String {
|
||||||
|
Self::encode_number_array_with_base(numbers, DEFAULT_BASE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode numbers using base 3, 6, or 8.
|
||||||
|
pub fn encode_number_array_with_base(numbers: &[u32], base: u8) -> String {
|
||||||
|
encode_numbers(numbers.iter().copied(), base)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode to string using base 3 (default/safest).
|
||||||
|
/// Non-ZWUS chars are ignored automatically.
|
||||||
|
pub fn decode_to_string(text: &str) -> String {
|
||||||
|
Self::decode_to_string_with_base(text, DEFAULT_BASE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode to string using base 3, 6, or 8.
|
||||||
|
/// Non-ZWUS chars are ignored automatically.
|
||||||
|
pub fn decode_to_string_with_base(text: &str, base: u8) -> String {
|
||||||
|
decode_numbers(text, base)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(char::from_u32)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode to numbers using base 3 (default/safest).
|
||||||
|
/// Non-ZWUS chars are ignored automatically.
|
||||||
|
pub fn decode_to_number_array(text: &str) -> Vec<u32> {
|
||||||
|
Self::decode_to_number_array_with_base(text, DEFAULT_BASE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode to numbers using base 3, 6, or 8.
|
||||||
|
/// Non-ZWUS chars are ignored automatically.
|
||||||
|
pub fn decode_to_number_array_with_base(text: &str, base: u8) -> Vec<u32> {
|
||||||
|
decode_numbers(text, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user