use std::cmp::Ordering;
use crate::Error;
pub(crate) const MAX_KEY: usize = 64;
#[derive(Clone)]
pub(crate) struct Alphabet {
letters: [char; 256],
cyrillic: [u8; 32],
ascii: [u8; 128],
other: Vec<(char, u8)>,
}
impl Alphabet {
pub fn new(letters: &str) -> Result<Self, Error> {
let mut alphabet = Alphabet {
letters: ['\0'; 256],
cyrillic: [0; 32],
ascii: [0; 128],
other: Vec::new(),
};
for (index, letter) in letters.chars().enumerate() {
let code = u8::try_from(index + 1)
.map_err(|_| Error::Format("больше 255 букв в алфавите".into()))?;
if letter == '\0' {
return Err(Error::Format("символ U+0000 в алфавите".into()));
}
if alphabet.code(letter).is_some() {
return Err(Error::Format("буква алфавита повторяется".into()));
}
alphabet.letters[usize::from(code)] = letter;
match letter {
'а'..='я' => alphabet.cyrillic[letter as usize - 'а' as usize] = code,
_ if letter.is_ascii() => alphabet.ascii[letter as usize] = code,
_ => alphabet.other.push((letter, code)),
}
}
alphabet.other.sort_unstable();
if alphabet.other.windows(2).any(|pair| pair[0].0 == pair[1].0) {
return Err(Error::Format("буква алфавита повторяется".into()));
}
Ok(alphabet)
}
pub fn extend(&mut self, word: &str) -> bool {
for letter in word.chars().flat_map(char::to_lowercase) {
let letter = if letter == 'ё' { 'е' } else { letter };
if letter == '\0' || self.code(letter).is_some() {
continue;
}
let Some(code) = (1..=u8::MAX).find(|&code| self.letters[usize::from(code)] == '\0')
else {
return false;
};
self.letters[usize::from(code)] = letter;
match letter {
'а'..='я' => self.cyrillic[letter as usize - 'а' as usize] = code,
_ if letter.is_ascii() => self.ascii[letter as usize] = code,
_ => {
let at = self.other.partition_point(|&(known, _)| known < letter);
self.other.insert(at, (letter, code));
}
}
}
true
}
fn code(&self, letter: char) -> Option<u8> {
let code = match letter {
'а'..='я' => self.cyrillic[letter as usize - 'а' as usize],
_ if letter.is_ascii() => self.ascii[letter as usize],
_ => match self
.other
.binary_search_by(|&(known, _)| known.cmp(&letter))
{
Ok(at) => self.other[at].1,
Err(_) => 0,
},
};
(code != 0).then_some(code)
}
pub fn input_key(&self, word: &str) -> Option<Key> {
let mut key = Key::new();
for letter in word.chars() {
let lower = match letter {
'А'..='Я' => char::from_u32(letter as u32 + 0x20)?,
'Ё' => 'ё',
_ if letter.is_ascii() => letter.to_ascii_lowercase(),
'а'..='я' | 'ё' => letter,
_ => {
for lower in letter.to_lowercase() {
self.push(&mut key, lower)?;
}
continue;
}
};
self.push(&mut key, lower)?;
}
Some(key)
}
pub fn spelling_key(&self, spelling: &str) -> Option<Key> {
let mut key = Key::new();
for letter in spelling.chars() {
self.push(&mut key, letter)?;
}
Some(key)
}
fn push(&self, key: &mut Key, letter: char) -> Option<()> {
if letter == 'ё' {
let at = key.len;
key.push(self.code('е')?)?;
key.yo |= 1 << at;
Some(())
} else {
key.push(self.code(letter)?)
}
}
pub fn key(&self, first: u8, text: &str) -> Option<Key> {
let mut key = Key::new();
key.push(first)?;
for letter in text.chars() {
key.push(self.code(letter)?)?;
}
Some(key)
}
pub fn spelling(&self, key: &Key, yo: u64) -> String {
self.decode(key.bytes(), yo)
}
pub fn decode(&self, bytes: &[u8], yo: u64) -> String {
let mut spelling = String::with_capacity(2 * bytes.len());
for (at, &code) in bytes.iter().enumerate() {
match self.letters[usize::from(code)] {
'е' if yo >> at & 1 == 1 => spelling.push('ё'),
letter => spelling.push(letter),
}
}
spelling
}
}
pub(crate) struct Key {
bytes: [u8; MAX_KEY],
len: usize,
pub yo: u64,
}
impl Key {
fn new() -> Self {
Key {
bytes: [0; MAX_KEY],
len: 0,
yo: 0,
}
}
fn push(&mut self, byte: u8) -> Option<()> {
*self.bytes.get_mut(self.len)? = byte;
self.len += 1;
Some(())
}
pub fn bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
}
pub(crate) fn yo_order(a: u64, b: u64) -> Ordering {
let (mut a, mut b) = (a, b);
loop {
match (a, b) {
(0, 0) => return Ordering::Equal,
(0, _) => return Ordering::Less,
(_, 0) => return Ordering::Greater,
_ => {}
}
let (left, right) = (a.trailing_zeros(), b.trailing_zeros());
if left != right {
return left.cmp(&right);
}
a &= a - 1;
b &= b - 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keys_of_words() {
let alphabet = Alphabet::new("-абвеёжья’").unwrap();
let key = alphabet.input_key("ЁЖ").unwrap();
assert_eq!(key.bytes(), [5, 7]);
assert_eq!(key.yo, 1);
assert_eq!(alphabet.spelling(&key, key.yo), "ёж");
assert_eq!(alphabet.spelling(&key, 0), "еж");
assert_eq!(alphabet.input_key("еж").unwrap().bytes(), key.bytes());
assert_eq!(alphabet.input_key("вабья’-").unwrap().bytes().len(), 7);
assert!(alphabet.input_key("ежик").is_none());
assert!(alphabet.spelling_key("Еж").is_none());
assert!(alphabet.input_key(&"а".repeat(MAX_KEY + 1)).is_none());
assert_eq!(
alphabet.input_key(&"а".repeat(MAX_KEY)).unwrap().len,
MAX_KEY
);
assert_eq!(alphabet.key(1, "ёж").unwrap().bytes(), [1, 6, 7]);
assert!(alphabet.key(1, "к").is_none());
}
#[test]
fn extends_alphabet() {
let mut alphabet = Alphabet::new("абвеёж").unwrap();
let before = alphabet.input_key("ёж").unwrap().bytes().to_vec();
assert!(alphabet.input_key("Ўёж").is_none());
assert!(alphabet.extend("ўёжz"));
assert_eq!(alphabet.input_key("ёж").unwrap().bytes(), before);
let key = alphabet.input_key("ЎЁЖZ").unwrap();
assert_eq!(alphabet.spelling(&key, key.yo), "ўёжz");
let letters: String = ('\u{100}'..).take(255).collect();
let mut full = Alphabet::new(&letters).unwrap();
assert!(!full.extend("я"));
}
#[test]
fn rejects_broken_alphabets() {
assert!(Alphabet::new("абa").is_ok());
assert!(Alphabet::new("аба").is_err());
assert!(Alphabet::new("’ё’").is_err());
assert!(Alphabet::new("’ёі").is_ok());
assert!(Alphabet::new("а\0").is_err());
let letters: String = ('\u{100}'..).take(256).collect();
assert!(Alphabet::new(&letters).is_err());
assert!(Alphabet::new(&letters[..letters.len() - 2]).is_ok());
}
#[test]
fn yo_positions_order() {
let mut masks = vec![0b110, 0b100, 0b010, 0b000, 0b011];
masks.sort_by(|&a, &b| yo_order(a, b));
assert_eq!(masks, [0b000, 0b011, 0b010, 0b110, 0b100]);
}
}