use regex::Regex;
use std::fmt;
use std::str::FromStr;
use std::sync::LazyLock;
use crate::RNGError;
use crate::rng_joiner::Joiner;
static _CONSONANTS: [char; 57] = [
'b', 'ɓ', 'ʙ', 'β', 'c', 'd', 'ɗ', 'ɖ', 'ð', 'f', 'g', 'h', 'j', 'k', 'l', 'ł', 'm', 'ɱ', 'n',
'ɳ', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z', 'б', 'в', 'г', 'д', 'ж', 'з', 'к', 'л',
'м', 'н', 'п', 'р', 'с', 'т', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'ѕ', 'ѳ', 'ѯ',
'ѱ', ];
static VOWELS: [char; 56] = [
'i', 'y', 'ɨ', 'ʉ', 'ɯ', 'u', 'ū', 'ɪ', 'ʏ', 'ʊ', 'ɯ', 'ʊ', 'e', 'ø', 'ɘ', 'ɵ', 'ɤ', 'o', 'ö',
'ø', 'ə', 'ɵ', 'ɤ', 'o', 'ɛ', 'œ', 'ɜ', 'ɞ', 'ʌ', 'ɔ', 'æ', 'ɐ', 'ɞ', 'a', 'ɶ', 'ä', 'ɒ', 'ɑ',
'е', 'ё', 'э', 'и', 'й', 'ю', 'ѭ', 'я', 'ѧ', 'ѫ', 'ꙛ', 'ꙙ', 'ꙝ', 'ѩ', 'і', 'ѣ', 'ѵ',
'ѡ', ];
static FULL_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^([-+]{0,1})([\p{Cyrillic}\p{Greek}\p{Arabic}\p{Hiragana}A-Za-zūæöäüß']+)\s*([\+\-][vcVC]){0,1}\s{0,1}([\+\-][vcVC]){0,1}$")
.expect("FULL_RE is a valid regex")
});
static PREFIX_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(.+)(\-[vcVC]).*").expect("PREFIX_RE is a valid regex"));
static SUFFIX_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(.+)(\+[vcVC]).*").expect("SUFFIX_RE is a valid regex"));
#[derive(Clone, Debug, PartialEq)]
pub struct Syllable {
pub value: String,
pub classification: Classification,
pub jprevious: Joiner,
pub jnext: Joiner,
}
impl Syllable {
pub fn ends_with_vowel(&self) -> bool {
Syllable::str_ends_with_vowel(self.value.as_str())
}
pub fn str_ends_with_vowel(s: &str) -> bool {
s.chars().last().is_some_and(|c| VOWELS.contains(&c))
}
pub fn starts_with_vowel(&self) -> bool {
Syllable::str_starts_with_vowel(self.value.as_str())
}
pub fn str_starts_with_vowel(s: &str) -> bool {
s.chars().next().is_some_and(|c| VOWELS.contains(&c))
}
fn determine_classification(s: &str) -> Classification {
match s {
"-" => Classification::Prefix,
"+" => Classification::Suffix,
_ => Classification::Center,
}
}
fn determine_next_joiner(s: &str) -> Joiner {
let (_, pure) = Syllable::classify(s);
let ends = if Syllable::str_ends_with_vowel(pure.as_str()) {
Joiner::VOWEL
} else {
Joiner::SOME
};
if SUFFIX_RE.is_match(s) {
Joiner::SOME | ends | Syllable::vowel_or_consonant_only_joiner(s, "+v")
} else {
Joiner::SOME | ends
}
}
fn determine_previous_joiner(s: &str) -> Joiner {
let (_, pure) = Syllable::classify(s);
let starts = if Syllable::str_starts_with_vowel(pure.as_str()) {
Joiner::VOWEL
} else {
Joiner::SOME
};
if PREFIX_RE.is_match(s) {
Joiner::SOME | starts | Syllable::vowel_or_consonant_only_joiner(s, "-v")
} else {
Joiner::SOME | starts
}
}
fn vowel_or_consonant_only_joiner(s: &str, matcher: &str) -> Joiner {
if s.to_ascii_lowercase().contains(matcher) {
Joiner::ONLY_VOWEL
} else {
Joiner::ONLY_CONSONANT
}
}
fn classify(raw: &str) -> (Classification, String) {
let cap = FULL_RE
.captures(raw)
.expect("callers verify the syllable matches FULL_RE first");
(
Syllable::determine_classification(&cap[1]),
cap[2].to_string(),
)
}
pub fn connects(&self, syllable: &Syllable) -> bool {
self.jnext.joins(syllable.jprevious)
}
}
impl fmt::Display for Syllable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}",
self.classification.value(),
self.value,
self.jprevious.value_previous(),
self.jnext.value_next(),
)
}
}
impl FromStr for Syllable {
type Err = RNGError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if FULL_RE.is_match(s) {
let (classification, value) = Syllable::classify(s);
let syllable = Syllable {
value,
classification,
jnext: Syllable::determine_next_joiner(s),
jprevious: Syllable::determine_previous_joiner(s),
};
Ok(syllable)
} else {
Err(RNGError::ParsingError)
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Classification {
Prefix,
Center,
Suffix,
}
impl Classification {
fn value(&self) -> String {
match *self {
Classification::Prefix => "-".to_string(),
Classification::Suffix => "+".to_string(),
Classification::Center => String::new(),
}
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod syllable_tests {
use super::*;
use rstest::rstest;
#[test]
fn new() {
let syllable = Syllable::from_str("-ваа +c");
assert!(syllable.is_ok());
}
#[rstest(from, to, from_i, to_i,
case(Syllable::from_str("ch").unwrap(), Syllable::from_str("ch").unwrap(), 1, 1),
case(Syllable::from_str("ch").unwrap(), Syllable::from_str("abc").unwrap(), 1, 3),
case(Syllable::from_str("ch").unwrap(), Syllable::from_str("ch -c").unwrap(), 1, 9),
case(Syllable::from_str("ch").unwrap(), Syllable::from_str("ach -c").unwrap(), 1, 11),
case(Syllable::from_str("cha").unwrap(), Syllable::from_str("ch").unwrap(), 3, 1),
case(Syllable::from_str("cha").unwrap(), Syllable::from_str("ach").unwrap(), 3, 3),
case(Syllable::from_str("cha").unwrap(), Syllable::from_str("ch -v").unwrap(), 3, 5),
case(Syllable::from_str("cha").unwrap(), Syllable::from_str("ich -v").unwrap(), 3, 7),
case(Syllable::from_str("ch +v").unwrap(), Syllable::from_str("ich").unwrap(), 5, 3),
case(Syllable::from_str("ch +v").unwrap(), Syllable::from_str("ich -c").unwrap(), 5, 11),
case(Syllable::from_str("chi +v").unwrap(), Syllable::from_str("abc").unwrap(), 7, 3),
case(Syllable::from_str("chi +v").unwrap(), Syllable::from_str("abc -v").unwrap(), 7, 7),
case(Syllable::from_str("ch +c").unwrap(), Syllable::from_str("ch").unwrap(), 9, 1),
case(Syllable::from_str("ch +c").unwrap(), Syllable::from_str("ch -c").unwrap(), 9, 9),
case(Syllable::from_str("chi +c").unwrap(), Syllable::from_str("ch").unwrap(), 11, 1),
case(Syllable::from_str("chi +c").unwrap(), Syllable::from_str("ch -v").unwrap(), 11, 5),
)]
fn connects_matrix(from: Syllable, to: Syllable, from_i: u8, to_i: u8) {
assert_eq!(from.jnext.bits(), from_i);
assert_eq!(to.jprevious.bits(), to_i);
assert!(from.connects(&to));
}
#[rstest(from, to, from_i, to_i,
case(Syllable::from_str("ass").unwrap(), Syllable::from_str("hole -v").unwrap(), 1, 5),
case(Syllable::from_str("fu").unwrap(), Syllable::from_str("ck -c").unwrap(), 3, 9),
case(Syllable::from_str("bi").unwrap(), Syllable::from_str("atch -c").unwrap(), 3, 11),
case(Syllable::from_str("sh +v").unwrap(), Syllable::from_str("ch").unwrap(), 5, 1),
case(Syllable::from_str("sh +v").unwrap(), Syllable::from_str("ch -v").unwrap(), 5, 5),
case(Syllable::from_str("sh +v").unwrap(), Syllable::from_str("ach -v").unwrap(), 5, 7),
case(Syllable::from_str("sh +v").unwrap(), Syllable::from_str("ch -c").unwrap(), 5, 9),
case(Syllable::from_str("shi +v").unwrap(), Syllable::from_str("ch").unwrap(), 7, 1),
case(Syllable::from_str("shi +v").unwrap(), Syllable::from_str("ts -v").unwrap(), 7, 5),
case(Syllable::from_str("shi +v").unwrap(), Syllable::from_str("tty -c").unwrap(), 7, 9),
case(Syllable::from_str("shi +v").unwrap(), Syllable::from_str("ach -c").unwrap(), 7, 11),
case(Syllable::from_str("sh +c").unwrap(), Syllable::from_str("ach").unwrap(), 9, 3),
case(Syllable::from_str("sh +c").unwrap(), Syllable::from_str("ch -v").unwrap(), 9, 5),
case(Syllable::from_str("sh +c").unwrap(), Syllable::from_str("it -v").unwrap(), 9, 7),
case(Syllable::from_str("sh +c").unwrap(), Syllable::from_str("it -c").unwrap(), 9, 11),
case(Syllable::from_str("bo +c").unwrap(), Syllable::from_str("oty").unwrap(), 11, 3),
case(Syllable::from_str("bo +c").unwrap(), Syllable::from_str("oty -v").unwrap(), 11, 7),
case(Syllable::from_str("boo +c").unwrap(), Syllable::from_str("ty -c").unwrap(), 11, 9),
case(Syllable::from_str("bo +c").unwrap(), Syllable::from_str("oger -c").unwrap(), 11, 11),
)]
fn connects_matrix__neg(from: Syllable, to: Syllable, from_i: u8, to_i: u8) {
assert_eq!(from.jnext.bits(), from_i);
assert_eq!(to.jprevious.bits(), to_i);
assert!(!from.connects(&to));
}
#[test]
fn new__center() {
let expected = Syllable {
value: "idr".to_string(),
classification: Classification::Center,
jnext: Joiner::SOME | Joiner::ONLY_VOWEL,
jprevious: Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT,
};
let actual = Syllable::from_str("idr -c +v");
assert_eq!(expected, actual.unwrap());
}
#[test]
fn new__prefix__any() {
let expected = Syllable {
value: "asd".to_string(),
classification: Classification::Prefix,
jnext: Joiner::SOME,
jprevious: Joiner::SOME | Joiner::VOWEL,
};
let actual = Syllable::from_str("-asd");
assert_eq!(expected, actual.unwrap());
}
#[test]
fn new__suffix__any() {
let expected = Syllable {
value: "adly".to_string(),
classification: Classification::Suffix,
jprevious: Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL,
jnext: Joiner::SOME | Joiner::VOWEL,
};
let actual = Syllable::from_str("+adly -v");
assert_eq!(expected, actual.unwrap());
}
#[test]
fn starts_with_vowel() {
let actual = Syllable::from_str("+sadly -v");
assert_eq!(false, actual.unwrap().starts_with_vowel());
}
#[test]
fn starts_with_vowel_not() {
let actual = Syllable::from_str("+adly -v");
assert_eq!(true, actual.unwrap().starts_with_vowel());
}
#[test]
fn ends_with_vowel() {
let actual = Syllable::from_str("+sadly -v");
assert_eq!(true, actual.unwrap().ends_with_vowel());
}
#[test]
fn ends_with_vowel_not() {
let actual = Syllable::from_str("-asdf -v");
assert_eq!(false, actual.unwrap().ends_with_vowel());
}
#[test]
fn determine_classification_prefix() {
let v = "-";
assert_eq!(
Classification::Prefix,
Syllable::determine_classification(v)
);
}
#[test]
fn determine_classification_center() {
let v = "";
assert_eq!(
Classification::Center,
Syllable::determine_classification(v)
);
}
#[test]
fn determine_classification_suffix() {
let v = "+";
assert_eq!(
Classification::Suffix,
Syllable::determine_classification(v)
);
}
#[test]
fn determine_classification_garbage() {
assert_eq!(
Classification::Center,
Syllable::determine_classification(" ")
);
assert_eq!(
Classification::Center,
Syllable::determine_classification("asd")
);
}
fn _micro() -> Vec<Syllable> {
vec![
Syllable::from_str("-a").unwrap(),
Syllable::from_str("b").unwrap(),
Syllable::from_str("+c").unwrap(),
]
}
#[rstest(
input,
case(""),
case("!"),
case("+-"),
case("++asda"),
case("+123asfd3ew")
)]
fn new__invalid__error(input: &str) {
assert_eq!(
Syllable::from_str(input).unwrap_err(),
RNGError::ParsingError
);
}
#[rstest(input, case("!"), case("+-"), case("+123asfd3ew"))]
fn full_re(input: &str) {
assert!(!FULL_RE.is_match(input))
}
#[rstest(input, expected,
case("-ang +v", "-ang +v".to_string()),
case("-ang +V", "-ang +v".to_string()),
case("-ang +V -C", "-ang -c +v".to_string()),
case("+ean -c", "+ean -c".to_string()),
case("+emar ", "+emar".to_string()),
case("ladd -v +v", "ladd -v +v".to_string()),
)]
fn to_string(input: &str, expected: String) {
assert_eq!(Syllable::from_str(input).unwrap().to_string(), expected);
}
#[test]
fn to_string_tmp() {
let s = Syllable::from_str("-ang +v").unwrap();
assert_eq!(s.to_string(), "-ang +v".to_string());
}
#[test]
fn value_next() {
let syl = Syllable::from_str("-ang +v").unwrap();
assert_eq!(syl.jprevious.value_previous(), "".to_string());
}
#[rstest(input, expected,
case(Classification::Prefix, "-".to_string()),
case(Classification::Center, "".to_string()),
case(Classification::Suffix, "+".to_string()),
)]
fn classification_value(input: Classification, expected: String) {
assert_eq!(input.value(), expected);
}
#[rstest(input, classification, value,
case("+sakku -V", Classification::Suffix, "sakku".to_string()),
case("-darr +v", Classification::Prefix, "darr".to_string()),
case("drov", Classification::Center, "drov".to_string()),
)]
fn classify(input: &str, classification: Classification, value: String) {
let (actual_classification, actual_value) = Syllable::classify(input);
assert_eq!(classification, actual_classification);
assert_eq!(value, actual_value);
}
}