242 lines
7.5 KiB
Rust
242 lines
7.5 KiB
Rust
use crate::strange::StrRange;
|
|
|
|
const CONSONANT_LOWER: &str = "bcdfgjklmnprstvzx";
|
|
const CONSONANT_UPPER: &str = "BCDFGJKLMNPRSTVZX";
|
|
const VOICED: &str = "BDGJVZbdgjvz";
|
|
const UNVOICED: &str = "CFKPSTXcfkpstx";
|
|
const SYLLABIC_CONSONANTS: &str = "lmnrLMNR";
|
|
#[rustfmt::skip]
|
|
const VOWEL_LOWER: &str = "aeiouyáạàảãăắặằẳẵâấậầẩẫeéẹèẻẽêếệềểễiíịìỉĩoóọòỏõôốộồổỗơớợờởỡuúụùủũưứựừửữyýỵỳỷỹ";
|
|
#[rustfmt::skip]
|
|
const VOWEL_UPPER: &str = "AEIOUYÁẠÀẢÃĂẮẶẰẲẴÂẤẬẦẨẪEÉẸÈẺẼÊẾỆỀỂỄIÍỊÌỈĨOÓỌÒỎÕÔỐỘỒỔỖƠỚỢỜỞỠUÚỤÙỦŨƯỨỰỪỬỮYÝỴỲỶỸ";
|
|
const APOSTROPHE_LOWER: &str = "'";
|
|
const APOSTROPHE_UPPER: &str = "h";
|
|
const STOP: &str = ".";
|
|
const GLIDE: &str = ",";
|
|
const NUMERAL: &str = "0123456789";
|
|
const A: &str = "aáạàảãăắặằẳẵâấậầẩẫAÁẠÀẢÃĂẮẶẰẲẴÂẤẬẦẨẪ";
|
|
const E: &str = "eeéẹèẻẽêếệềểễEEÉẸÈẺẼÊẾỆỀỂỄ";
|
|
const I: &str = "iiíịìỉĩIIÍỊÌỈĨ";
|
|
const O: &str = "ooóọòỏõôốộồổỗơớợờởỡOOÓỌÒỎÕÔỐỘỒỔỖƠỚỢỜỞỠ";
|
|
const U: &str = "uuúụùủũưứựừửữUUÚỤÙỦŨƯỨỰỪỬỮ";
|
|
const Y: &str = "yyýỵỳỷỹYYÝỴỲỶỸ";
|
|
const PERMISSIBLE_INITIAL_PAIRS: &[&str] = &[
|
|
"bl", "br", "cf", "ck", "cl", "cm", "cn", "cp", "cr", "ct", "dj", "dr", "dz", "fl", "fr", "gl",
|
|
"gr", "jb", "jd", "jg", "jm", "jv", "kl", "kr", "ml", "mr", "pl", "pr", "sf", "sk", "sl", "sm",
|
|
"sn", "sp", "sr", "st", "tc", "tr", "ts", "vl", "vr", "xl", "xr", "zb", "zd", "zg", "zm", "zv",
|
|
];
|
|
|
|
pub trait Lojbanic {
|
|
fn is_lojbanic(&self) -> bool;
|
|
fn is_lojban_consonant(&self) -> bool;
|
|
fn is_lojban_voiced(&self) -> bool;
|
|
fn is_lojban_unvoiced(&self) -> bool;
|
|
fn is_lojban_syllabic_consonant(&self) -> bool;
|
|
fn is_lojban_vowel(&self) -> bool;
|
|
fn is_lojban_uppercase(&self) -> bool;
|
|
fn is_lojban_lowercase(&self) -> bool;
|
|
fn is_lojban_apostrophe(&self) -> bool;
|
|
fn is_lojban_stop(&self) -> bool;
|
|
fn is_lojban_glide(&self) -> bool;
|
|
fn is_lojban_a(&self) -> bool;
|
|
fn is_lojban_e(&self) -> bool;
|
|
fn is_lojban_i(&self) -> bool;
|
|
fn is_lojban_o(&self) -> bool;
|
|
fn is_lojban_u(&self) -> bool;
|
|
fn is_lojban_y(&self) -> bool;
|
|
}
|
|
|
|
impl Lojbanic for char {
|
|
fn is_lojbanic(&self) -> bool {
|
|
[
|
|
CONSONANT_LOWER,
|
|
CONSONANT_UPPER,
|
|
VOWEL_LOWER,
|
|
VOWEL_UPPER,
|
|
APOSTROPHE_LOWER,
|
|
APOSTROPHE_UPPER,
|
|
NUMERAL,
|
|
GLIDE,
|
|
STOP,
|
|
]
|
|
.iter()
|
|
.any(|s| s.contains(*self))
|
|
}
|
|
fn is_lojban_consonant(&self) -> bool {
|
|
self.is_lojbanic()
|
|
&& [CONSONANT_LOWER, CONSONANT_UPPER]
|
|
.iter()
|
|
.any(|s| s.contains(*self))
|
|
}
|
|
fn is_lojban_vowel(&self) -> bool {
|
|
self.is_lojbanic() && [VOWEL_LOWER, VOWEL_UPPER].iter().any(|s| s.contains(*self))
|
|
}
|
|
fn is_lojban_uppercase(&self) -> bool {
|
|
self.is_lojbanic()
|
|
&& [CONSONANT_UPPER, VOWEL_UPPER, APOSTROPHE_UPPER]
|
|
.iter()
|
|
.any(|s| s.contains(*self))
|
|
}
|
|
fn is_lojban_voiced(&self) -> bool {
|
|
self.is_lojbanic() && VOICED.contains(*self)
|
|
}
|
|
fn is_lojban_unvoiced(&self) -> bool {
|
|
self.is_lojbanic() && UNVOICED.contains(*self)
|
|
}
|
|
fn is_lojban_syllabic_consonant(&self) -> bool {
|
|
self.is_lojbanic() && SYLLABIC_CONSONANTS.contains(*self)
|
|
}
|
|
fn is_lojban_lowercase(&self) -> bool {
|
|
self.is_lojbanic() && (CONSONANT_UPPER.contains(*self) || VOWEL_LOWER.contains(*self))
|
|
}
|
|
fn is_lojban_apostrophe(&self) -> bool {
|
|
[APOSTROPHE_LOWER, APOSTROPHE_UPPER]
|
|
.iter()
|
|
.any(|s| s.contains(*self))
|
|
}
|
|
fn is_lojban_stop(&self) -> bool {
|
|
STOP.contains(*self)
|
|
}
|
|
fn is_lojban_glide(&self) -> bool {
|
|
GLIDE.contains(*self)
|
|
}
|
|
fn is_lojban_a(&self) -> bool {
|
|
A.contains(*self)
|
|
}
|
|
fn is_lojban_e(&self) -> bool {
|
|
E.contains(*self)
|
|
}
|
|
fn is_lojban_i(&self) -> bool {
|
|
I.contains(*self)
|
|
}
|
|
fn is_lojban_o(&self) -> bool {
|
|
O.contains(*self)
|
|
}
|
|
fn is_lojban_u(&self) -> bool {
|
|
U.contains(*self)
|
|
}
|
|
fn is_lojban_y(&self) -> bool {
|
|
Y.contains(*self)
|
|
}
|
|
}
|
|
|
|
macro_rules! option_passthrough_methods {
|
|
($($nym:ident),*$(,)?) => {
|
|
$(fn $nym(&self) -> bool {
|
|
self.iter().any(|ch| ch.$nym())
|
|
})*
|
|
};
|
|
}
|
|
|
|
impl<T: Lojbanic + Copy> Lojbanic for Option<T> {
|
|
// If `None`, false, otherwise `Some(ch).unwrap().{method}()`
|
|
option_passthrough_methods! {
|
|
is_lojbanic,
|
|
is_lojban_consonant,
|
|
is_lojban_vowel,
|
|
is_lojban_uppercase,
|
|
is_lojban_voiced,
|
|
is_lojban_unvoiced,
|
|
is_lojban_syllabic_consonant,
|
|
is_lojban_lowercase,
|
|
is_lojban_apostrophe,
|
|
is_lojban_stop,
|
|
is_lojban_glide,
|
|
is_lojban_a,
|
|
is_lojban_e,
|
|
is_lojban_i,
|
|
is_lojban_o,
|
|
is_lojban_u,
|
|
is_lojban_y,
|
|
}
|
|
}
|
|
|
|
pub fn is_valid_consonant_pair(left: char, right: char) -> bool {
|
|
match left {
|
|
e if e == right => false,
|
|
e if e.is_lojban_voiced() => right.is_lojban_voiced() || right.is_lojban_syllabic_consonant(),
|
|
e if e.is_lojban_unvoiced() => {
|
|
right.is_lojban_unvoiced() || right.is_lojban_syllabic_consonant()
|
|
}
|
|
e if e.is_lojban_syllabic_consonant() => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn consonant_pairs() {
|
|
let permissible_pairs = [
|
|
('b', "dgjvzmnlr"),
|
|
('c', "fkptlrmn"),
|
|
('d', "bgjvlmnzr"),
|
|
('f', "ckpstxmnlr"),
|
|
('g', "bdjvzmnlr"),
|
|
('j', "bdgvmlnr"),
|
|
('k', "cfpstmnlr"),
|
|
('l', "bcdfgjkpstvxzmnr"),
|
|
('m', "bcdfgjkpstvxlrn"),
|
|
('n', "bcdfgjkpstvxzlmr"),
|
|
('p', "cfkstxmnlr"),
|
|
('r', "bcdfgjkpstvxzlmn"),
|
|
('s', "fklprtmnx"),
|
|
('t', "crsfklpxmn"),
|
|
('v', "bdgjzmnlr"),
|
|
('x', "fpstmnlr"),
|
|
('z', "bdgvmlnr"),
|
|
];
|
|
for (left, s) in permissible_pairs {
|
|
for right in s.chars() {
|
|
assert![is_valid_consonant_pair(left, right)];
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn is_valid_vowel_pair(left: char, right: char) -> bool {
|
|
(match left {
|
|
e if e.is_lojban_a() => !right.is_lojban_a() && (right.is_lojban_u() || right.is_lojban_i()),
|
|
e if e.is_lojban_e() => !right.is_lojban_e() && right.is_lojban_i(),
|
|
e if e.is_lojban_i() => true,
|
|
e if e.is_lojban_o() => false,
|
|
e if e.is_lojban_u() => true,
|
|
e if e.is_lojban_y() => false,
|
|
_ => {
|
|
println!["{left}, {right}"];
|
|
unreachable![]
|
|
}
|
|
}) && !right.is_lojban_y()
|
|
}
|
|
|
|
pub fn is_valid_vowel_pair_name(left: char, right: char) -> bool {
|
|
is_valid_vowel_pair(left, right) || left.is_lojban_u() || left.is_lojban_i()
|
|
}
|
|
|
|
pub fn starts_with_permissible_initial_pair(s: &StrRange) -> bool {
|
|
if s.len() > 1 {
|
|
for pair in PERMISSIBLE_INITIAL_PAIRS {
|
|
if s.as_str().starts_with(pair) {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vowel_pairs() {
|
|
let correct = [
|
|
[false, false, true, false, true, false],
|
|
[false, false, true, false, false, false],
|
|
[true, true, true, true, true, true],
|
|
[false, false, false, false, false, false],
|
|
[true, true, true, true, true, true],
|
|
[false, false, false, false, false, false],
|
|
];
|
|
for (i, left) in "aeiouy".chars().enumerate() {
|
|
for (j, right) in "aeiouy".chars().enumerate() {
|
|
assert![is_valid_vowel_pair_name(left, right) == correct[i][j]];
|
|
}
|
|
}
|
|
}
|