#[cfg(any(test, feature = "build"))]
use std::cmp::Ordering;
use crate::Error;
pub(crate) const MAX_KEY: usize = 64;
fn plain(letter: char) -> Option<char> {
match letter {
'á' => Some('a'),
'é' => Some('e'),
'í' => Some('i'),
'ó' => Some('o'),
'ú' => Some('u'),
_ => None,
}
}
fn accented(letter: char) -> Option<char> {
match letter {
'a' => Some('á'),
'e' => Some('é'),
'i' => Some('í'),
'o' => Some('ó'),
'u' => Some('ú'),
_ => None,
}
}
#[derive(Clone)]
pub(crate) struct Alphabet {
letters: [char; 256],
ascii: [u8; 128],
other: Vec<(char, u8)>,
}
impl Alphabet {
pub fn new(letters: &str) -> Result<Self, Error> {
let mut alphabet = Alphabet {
letters: ['\0'; 256],
ascii: [0; 128],
other: Vec::new(),
};
for (index, letter) in letters.chars().enumerate() {
let code = u8::try_from(index + 1)
.map_err(|_| Error::Format("more than 255 letters in the alphabet".into()))?;
if letter == '\0' {
return Err(Error::Format("U+0000 in the alphabet".into()));
}
if plain(letter).is_some() {
return Err(Error::Format("an accented vowel in the alphabet".into()));
}
if letter.is_ascii() && alphabet.ascii[letter as usize] != 0 {
return Err(Error::Format("a letter of the alphabet repeats".into()));
}
alphabet.letters[usize::from(code)] = letter;
if letter.is_ascii() {
alphabet.ascii[letter as usize] = code;
} else {
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("a letter of the alphabet repeats".into()));
}
Ok(alphabet)
}
fn code(&self, letter: char) -> Option<u8> {
let code = if letter.is_ascii() {
self.ascii[letter as usize]
} else {
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() {
if letter.is_ascii() {
key.push(self.code(letter.to_ascii_lowercase())?)?;
} else {
for lower in letter.to_lowercase() {
self.push(&mut key, lower)?;
}
}
}
Some(key)
}
#[cfg(any(test, feature = "build"))]
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<()> {
match plain(letter) {
Some(vowel) => {
let at = key.len;
key.push(self.code(vowel)?)?;
key.marks |= 1 << at;
Some(())
}
None => key.push(self.code(letter)?),
}
}
pub fn diaeresis_keys(&self, key: &Key) -> Vec<Key> {
let (Some(g), Some(u), Some(diaeresis)) = (self.code('g'), self.code('u'), self.code('ü'))
else {
return Vec::new();
};
let (e, i) = (self.code('e'), self.code('i'));
let bytes = key.bytes();
let places: Vec<usize> = (1..bytes.len().saturating_sub(1))
.filter(|&at| {
bytes[at] == u
&& bytes[at - 1] == g
&& (Some(bytes[at + 1]) == e || Some(bytes[at + 1]) == i)
})
.collect();
let mut keys: Vec<Key> = places
.iter()
.map(|&at| key.with_byte(at, diaeresis))
.collect();
if places.len() > 1 {
let mut all = key.clone();
for &at in &places {
all.bytes[at] = diaeresis;
}
keys.push(all);
}
keys
}
pub fn spelling(&self, key: &Key, marks: u64) -> String {
self.decode(key.bytes(), marks)
}
pub fn decode(&self, bytes: &[u8], marks: u64) -> String {
let mut spelling = String::with_capacity(2 * bytes.len());
for (at, &code) in bytes.iter().enumerate() {
let letter = self.letters[usize::from(code)];
let marked = at < 64 && marks >> at & 1 == 1;
spelling.push(accented(letter).filter(|_| marked).unwrap_or(letter));
}
spelling
}
}
#[derive(Clone)]
pub(crate) struct Key {
bytes: [u8; MAX_KEY],
len: usize,
pub marks: u64,
}
impl Key {
fn new() -> Self {
Key {
bytes: [0; MAX_KEY],
len: 0,
marks: 0,
}
}
fn push(&mut self, byte: u8) -> Option<()> {
*self.bytes.get_mut(self.len)? = byte;
self.len += 1;
Some(())
}
fn with_byte(&self, at: usize, byte: u8) -> Self {
let mut key = self.clone();
key.bytes[at] = byte;
key
}
pub fn bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
}
#[cfg(any(test, feature = "build"))]
pub(crate) fn mark_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::*;
fn alphabet() -> Alphabet {
Alphabet::new("-.aceginoqrstuñü").unwrap()
}
#[test]
fn keys_of_words() {
let alphabet = alphabet();
assert!(
alphabet.input_key("CAMIÓN").is_none(),
"m is not in the alphabet"
);
let key = alphabet.input_key("Canción").unwrap();
assert_eq!(key.marks, 1 << 5);
assert_eq!(alphabet.spelling(&key, key.marks), "canción");
assert_eq!(alphabet.spelling(&key, 0), "cancion");
assert_eq!(alphabet.input_key("CANCION").unwrap().bytes(), key.bytes());
assert_eq!(alphabet.input_key("CANCIÓN").unwrap().marks, key.marks);
assert!(alphabet.input_key("canción!").is_none());
assert!(alphabet.input_key(&"a".repeat(MAX_KEY + 1)).is_none());
assert_eq!(
alphabet.input_key(&"a".repeat(MAX_KEY)).unwrap().len,
MAX_KEY
);
let last = format!("{}á", "a".repeat(MAX_KEY - 1));
let key = alphabet.input_key(&last).unwrap();
assert_eq!(key.marks, 1 << (MAX_KEY - 1));
assert_eq!(alphabet.spelling(&key, key.marks), last);
}
#[test]
fn spelling_keys_keep_case() {
let alphabet = alphabet();
assert!(alphabet.spelling_key("Canción").is_none());
assert!(alphabet.spelling_key("canción").is_some());
}
#[test]
fn n_with_tilde_is_a_letter() {
let alphabet = Alphabet::new("anoñ").unwrap();
let (year, other) = (
alphabet.input_key("año").unwrap(),
alphabet.input_key("ano").unwrap(),
);
assert_ne!(year.bytes(), other.bytes());
assert_eq!(year.marks, 0);
assert_eq!(alphabet.spelling(&year, 0), "año");
}
#[test]
fn restores_the_diaeresis() {
let alphabet = Alphabet::new("aeginopstuü").unwrap();
let spelled = |word: &str| -> Vec<String> {
let key = alphabet.input_key(word).unwrap();
alphabet
.diaeresis_keys(&key)
.iter()
.map(|key| alphabet.spelling(key, key.marks))
.collect()
};
assert_eq!(spelled("pinguino"), ["pingüino"]);
assert_eq!(spelled("guiguei"), ["güiguei", "guigüei", "güigüei"]);
assert!(spelled("agua").is_empty());
assert!(spelled("gu").is_empty());
let key = alphabet.input_key("pingúino").unwrap();
let restored = alphabet.diaeresis_keys(&key);
assert_eq!(restored.len(), 1);
assert_eq!(restored[0].marks, key.marks);
let without = Alphabet::new("gueino").unwrap();
let key = without.input_key("guei").unwrap();
assert!(without.diaeresis_keys(&key).is_empty());
}
#[test]
fn rejects_broken_alphabets() {
assert!(Alphabet::new("aba").is_err());
assert!(Alphabet::new("ñañ").is_err());
assert!(Alphabet::new("a\0").is_err());
assert!(Alphabet::new("aá").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 order_of_marks() {
let mut masks = vec![0b110, 0b100, 0b010, 0b000, 0b011];
masks.sort_by(|&a, &b| mark_order(a, b));
assert_eq!(masks, [0b000, 0b011, 0b010, 0b110, 0b100]);
}
}