use std::fmt;
use std::str::FromStr;
use crate::generated::enums::{Alias, Condition, WrittenUnit};
use crate::Error;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Locale {
Mng,
Tod,
Sib,
Mch,
}
impl Locale {
pub const ALL: [Locale; 4] = [Locale::Mng, Locale::Tod, Locale::Sib, Locale::Mch];
pub const fn as_str(self) -> &'static str {
match self {
Locale::Mng => "MNG",
Locale::Tod => "TOD",
Locale::Sib => "SIB",
Locale::Mch => "MCH",
}
}
}
impl FromStr for Locale {
type Err = Error;
fn from_str(name: &str) -> Result<Locale, Error> {
Locale::ALL
.iter()
.copied()
.find(|locale| locale.as_str() == name)
.ok_or_else(|| Error::UnknownName {
kind: "locale",
name: name.to_owned(),
})
}
}
impl fmt::Display for Locale {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Position {
Isol,
Init,
Medi,
Fina,
}
impl Position {
pub const ALL: [Position; 4] = [
Position::Isol,
Position::Init,
Position::Medi,
Position::Fina,
];
pub const fn as_str(self) -> &'static str {
match self {
Position::Isol => "isol",
Position::Init => "init",
Position::Medi => "medi",
Position::Fina => "fina",
}
}
}
impl FromStr for Position {
type Err = Error;
fn from_str(name: &str) -> Result<Position, Error> {
Position::ALL
.iter()
.copied()
.find(|position| position.as_str() == name)
.ok_or_else(|| Error::UnknownName {
kind: "position",
name: name.to_owned(),
})
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum UnitPosition {
Isol,
Init,
Medi,
Fina,
Control,
}
impl UnitPosition {
pub const ALL: [UnitPosition; 5] = [
UnitPosition::Isol,
UnitPosition::Init,
UnitPosition::Medi,
UnitPosition::Fina,
UnitPosition::Control,
];
pub const fn as_str(self) -> &'static str {
match self {
UnitPosition::Isol => "isol",
UnitPosition::Init => "init",
UnitPosition::Medi => "medi",
UnitPosition::Fina => "fina",
UnitPosition::Control => "control",
}
}
pub const fn as_position(self) -> Option<Position> {
match self {
UnitPosition::Isol => Some(Position::Isol),
UnitPosition::Init => Some(Position::Init),
UnitPosition::Medi => Some(Position::Medi),
UnitPosition::Fina => Some(Position::Fina),
UnitPosition::Control => None,
}
}
}
impl From<Position> for UnitPosition {
fn from(position: Position) -> UnitPosition {
match position {
Position::Isol => UnitPosition::Isol,
Position::Init => UnitPosition::Init,
Position::Medi => UnitPosition::Medi,
Position::Fina => UnitPosition::Fina,
}
}
}
impl FromStr for UnitPosition {
type Err = Error;
fn from_str(name: &str) -> Result<UnitPosition, Error> {
UnitPosition::ALL
.iter()
.copied()
.find(|position| position.as_str() == name)
.ok_or_else(|| Error::UnknownName {
kind: "unit position",
name: name.to_owned(),
})
}
}
impl fmt::Display for UnitPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Fvs {
Fvs1,
Fvs2,
Fvs3,
Fvs4,
}
impl Fvs {
pub const ALL: [Fvs; 4] = [Fvs::Fvs1, Fvs::Fvs2, Fvs::Fvs3, Fvs::Fvs4];
pub const fn cp(self) -> u32 {
match self {
Fvs::Fvs1 => 0x180B,
Fvs::Fvs2 => 0x180C,
Fvs::Fvs3 => 0x180D,
Fvs::Fvs4 => 0x180F,
}
}
pub const fn as_char(self) -> char {
match self {
Fvs::Fvs1 => '\u{180B}',
Fvs::Fvs2 => '\u{180C}',
Fvs::Fvs3 => '\u{180D}',
Fvs::Fvs4 => '\u{180F}',
}
}
pub const fn index(self) -> u8 {
match self {
Fvs::Fvs1 => 1,
Fvs::Fvs2 => 2,
Fvs::Fvs3 => 3,
Fvs::Fvs4 => 4,
}
}
pub const fn from_cp(cp: u32) -> Option<Fvs> {
match cp {
0x180B => Some(Fvs::Fvs1),
0x180C => Some(Fvs::Fvs2),
0x180D => Some(Fvs::Fvs3),
0x180F => Some(Fvs::Fvs4),
_ => None,
}
}
pub const fn from_index(index: u8) -> Option<Fvs> {
match index {
1 => Some(Fvs::Fvs1),
2 => Some(Fvs::Fvs2),
3 => Some(Fvs::Fvs3),
4 => Some(Fvs::Fvs4),
_ => None,
}
}
}
pub(crate) struct Letter {
pub cp: u32,
pub alias: Alias,
pub variants: &'static [Variant],
}
pub(crate) struct Variant {
pub position: Position,
pub fvs: Option<Fvs>,
pub written: &'static [WrittenUnit],
pub default: bool,
pub conditions: &'static [Condition],
}
pub(crate) struct Categories {
pub vowel: &'static [Alias],
pub consonant: &'static [Alias],
pub vowel_masculine: &'static [Alias],
pub vowel_feminine: &'static [Alias],
pub vowel_neuter: &'static [Alias],
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum ParticleSym {
Mvs,
Alias(Alias),
Unknown,
}
pub(crate) struct Particle {
pub key: &'static [ParticleSym],
pub indices: &'static [usize],
}
pub(crate) struct LocaleData {
pub letters: &'static [Letter],
pub categories: Categories,
pub particles: &'static [Particle],
}
pub(crate) struct UnitEntry {
pub position: Position,
pub units: &'static [WrittenUnit],
pub cp: u32,
pub fvs: Option<Fvs>,
}
pub(crate) struct NormalizeData {
pub canonical_version: &'static str,
pub unit_enc_max_len: usize,
pub unit_table: &'static [UnitEntry],
pub velar_fem: &'static [UnitEntry],
pub velar_fem_units: &'static [WrittenUnit],
pub masc_to_fem: &'static [(u32, u32)],
pub positioned_units: &'static [(WrittenUnit, Position)],
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn locale_names_round_trip() {
for locale in Locale::ALL {
assert_eq!(locale.as_str().parse::<Locale>().unwrap(), locale);
}
assert!("MNGx".parse::<Locale>().is_err());
assert!("mng".parse::<Locale>().is_err());
}
#[test]
fn position_names_round_trip() {
for position in Position::ALL {
assert_eq!(position.as_str().parse::<Position>().unwrap(), position);
assert_eq!(UnitPosition::from(position).as_position(), Some(position));
}
assert_eq!(
"control".parse::<UnitPosition>().unwrap(),
UnitPosition::Control
);
assert_eq!(UnitPosition::Control.as_position(), None);
assert!("control".parse::<Position>().is_err());
assert!("middle".parse::<UnitPosition>().is_err());
}
#[test]
fn fvs_code_points() {
for fvs in Fvs::ALL {
assert_eq!(Fvs::from_cp(fvs.cp()), Some(fvs));
assert_eq!(Fvs::from_index(fvs.index()), Some(fvs));
assert_eq!(fvs.as_char() as u32, fvs.cp());
}
assert_eq!(Fvs::from_cp(0x180E), None);
assert_eq!(Fvs::from_index(0), None);
}
#[test]
fn generated_enums_round_trip() {
for unit in WrittenUnit::ALL {
assert_eq!(unit.as_str().parse::<WrittenUnit>().unwrap(), unit);
assert_eq!(unit.to_string(), unit.as_str());
}
for condition in Condition::ALL {
assert_eq!(condition.as_str().parse::<Condition>().unwrap(), condition);
}
for alias in Alias::ALL {
assert_eq!(alias.as_str().parse::<Alias>().unwrap(), alias);
}
assert!(WrittenUnit::Mvs.is_structural());
assert!(WrittenUnit::Nirugu.is_structural());
assert!(WrittenUnit::Zwj.is_structural());
assert!(!WrittenUnit::A.is_structural());
assert_eq!(Condition::ChachlagOnsetGb.as_str(), "chachlag_onset_gb");
assert_eq!(Alias::K2.as_str(), "k2");
assert_eq!(Alias::Oe.as_str(), "oe");
assert!("mvs".parse::<WrittenUnit>().is_err());
assert!("MVS".parse::<WrittenUnit>().is_err());
}
#[test]
fn generated_tables_have_the_expected_shape() {
use crate::generated::{mng, mng_normalize};
assert_eq!(mng::DATA.letters.len(), 35);
assert_eq!(mng::DATA.particles.len(), 47);
assert_eq!(
mng::DATA.categories.vowel_masculine,
&[Alias::A, Alias::O, Alias::U]
);
let variants: usize = mng::DATA.letters.iter().map(|l| l.variants.len()).sum();
assert_eq!(variants, 216);
assert_eq!(mng_normalize::DATA.canonical_version, "mng-canonical/1");
assert_eq!(mng_normalize::DATA.unit_enc_max_len, 3);
assert_eq!(mng_normalize::DATA.unit_table.len(), 151);
assert_eq!(mng_normalize::DATA.velar_fem.len(), 15);
assert_eq!(mng_normalize::DATA.positioned_units.len(), 95);
assert_eq!(
mng_normalize::DATA.masc_to_fem,
&[(0x1820, 0x1821), (0x1823, 0x1825), (0x1824, 0x1826)]
);
for data in [
&crate::generated::mng::DATA,
&crate::generated::tod::DATA,
&crate::generated::sib::DATA,
&crate::generated::mch::DATA,
] {
for letter in data.letters {
for position in Position::ALL {
let defaults = letter
.variants
.iter()
.filter(|v| v.position == position && v.default)
.count();
let any = letter.variants.iter().any(|v| v.position == position);
assert_eq!(
defaults,
usize::from(any),
"U+{:04X} {:?}",
letter.cp,
position
);
}
}
}
}
}