use std::collections::VecDeque;
macro_rules! display_for_static_str {
($ty:ident) => {
impl $ty {
pub fn name(&self) -> &'static str {
self.into()
}
}
};
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
pub enum Place {
Bilabial,
Labiodental,
Linguolabial,
Dental,
Alveolar,
PostAlveolar,
Alveolopalatal,
Retroflex,
Palatal,
Velar,
Uvular,
Pharyngeal,
Epiglottal,
Glottal,
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "lowercase")]
pub enum PlaceCategory {
Labial,
Coronal,
Dorsal,
Laryngeal,
}
impl Place {
pub fn category(&self) -> PlaceCategory {
use Place::*;
match self {
Bilabial | Labiodental | Linguolabial => PlaceCategory::Labial,
Dental | Alveolar | PostAlveolar | Alveolopalatal | Retroflex => PlaceCategory::Coronal,
Palatal | Velar | Uvular => PlaceCategory::Dorsal,
Pharyngeal | Epiglottal | Glottal => PlaceCategory::Laryngeal,
}
}
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
pub enum Airstream {
#[strum(serialize = "")]
Median,
#[strum(serialize = "lateral ")]
Lateral,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FricativeKind {
Sibilant,
NonSibilant(Airstream),
}
impl From<FricativeKind> for &'static str {
fn from(value: FricativeKind) -> Self {
match value {
FricativeKind::Sibilant => "sibilant",
FricativeKind::NonSibilant(Airstream::Median) => "fricative",
FricativeKind::NonSibilant(Airstream::Lateral) => "lateral fricative",
}
}
}
impl<'a> From<&'a FricativeKind> for &'static str {
fn from(value: &'a FricativeKind) -> Self {
(*value).into()
}
}
display_for_static_str!(FricativeKind);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
pub enum RearArticulation {
#[strum(serialize = "")]
Velar,
#[strum(serialize = "uvular ")]
Uvular,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Manner {
Nasal,
Stop,
Affricate(FricativeKind),
Fricative(FricativeKind),
Trill,
Flap(Airstream),
Implosive,
Approximant(Airstream),
Click(RearArticulation),
}
impl From<Manner> for &'static str {
fn from(value: Manner) -> Self {
use Airstream::*;
use FricativeKind::*;
use Manner::*;
use RearArticulation::*;
match value {
Nasal => "nasal",
Stop => "stop",
Trill => "trill",
Implosive => "implosive",
Approximant(Median) => "approximant",
Approximant(Lateral) => "lateral approximant",
Flap(Median) => "flap",
Flap(Lateral) => "lateral flap",
Click(Velar) => "click",
Click(Uvular) => "uvular click",
Affricate(kind) => match kind {
Sibilant | NonSibilant(Median) => "affricate",
NonSibilant(Lateral) => "lateral affricate",
},
Fricative(kind) => match kind {
Sibilant | NonSibilant(Median) => "fricative",
NonSibilant(Lateral) => "lateral fricative",
},
}
}
}
impl<'a> From<&'a Manner> for &'static str {
fn from(value: &'a Manner) -> Self {
(*value).into()
}
}
display_for_static_str!(Manner);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
pub enum MannerCategory {
Obstruent,
Sonorant,
}
impl Manner {
pub fn category(&self) -> MannerCategory {
use Manner::*;
match self {
Stop | Affricate(_) | Fricative(_) | Click(_) => MannerCategory::Obstruent,
Nasal | Trill | Flap(_) | Implosive | Approximant(_) => MannerCategory::Sonorant,
}
}
pub fn is_obstruent(&self) -> bool {
self.category() == MannerCategory::Obstruent
}
pub fn is_sonorant(&self) -> bool {
self.category() == MannerCategory::Sonorant
}
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
pub enum Height {
Close,
NearClose,
CloseMid,
Mid,
OpenMid,
NearOpen,
Open,
}
display_for_static_str!(Height);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
pub enum Depth {
Front,
NearFront,
Central,
NearBack,
Back,
}
display_for_static_str!(Depth);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ConsonantFeature {
Voiced,
Place(Place),
DoubleArticulation(Place, Place),
Manner(Manner),
}
macro_rules! place_pairs {
($p1:expr, $p2:expr; $(($variant:ident, $name:expr)),* $(,)?) => {{
macro_rules! inner {
($lhs_variant:ident, $lhs_name:expr) => {
match $p2 {
$(crate::feature::Place::$variant => concat!($lhs_name, "-", $name),)*
}
};
}
match $p1 {
$(crate::feature::Place::$variant => inner!($variant, $name),)*
}
}};
}
impl From<ConsonantFeature> for &'static str {
fn from(value: ConsonantFeature) -> Self {
use ConsonantFeature::*;
match value {
Voiced => "voiced",
Place(p) => p.into(),
Manner(m) => m.into(),
DoubleArticulation(p1, p2) => place_pairs!(p1, p2;
(Bilabial, "labial"),
(Labiodental, "labiodental"),
(Linguolabial, "linguolabial"),
(Dental, "dental"),
(Alveolar, "alveolar"),
(PostAlveolar, "post-alveolar"),
(Alveolopalatal, "alveolopalatal"),
(Retroflex, "retroflex"),
(Palatal, "palatal"),
(Velar, "velar"),
(Uvular, "uvular"),
(Pharyngeal, "pharyngeal"),
(Epiglottal, "epiglottal"),
(Glottal, "glottal"),
),
}
}
}
impl<'a> From<&'a ConsonantFeature> for &'static str {
fn from(value: &'a ConsonantFeature) -> Self {
(*value).into()
}
}
display_for_static_str!(ConsonantFeature);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum VowelFeature {
Height(Height),
Depth(Depth),
Rounded,
}
impl From<VowelFeature> for &'static str {
fn from(value: VowelFeature) -> Self {
match value {
VowelFeature::Height(h) => h.into(),
VowelFeature::Depth(d) => d.into(),
VowelFeature::Rounded => "rounded",
}
}
}
impl<'a> From<&'a VowelFeature> for &'static str {
fn from(value: &'a VowelFeature) -> Self {
(*value).into()
}
}
display_for_static_str!(VowelFeature);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Feature {
Consonant(ConsonantFeature),
Vowel(VowelFeature),
}
impl From<Feature> for &'static str {
fn from(value: Feature) -> Self {
match value {
Feature::Consonant(f) => f.into(),
Feature::Vowel(f) => f.into(),
}
}
}
impl<'a> From<&'a Feature> for &'static str {
fn from(value: &'a Feature) -> Self {
(*value).into()
}
}
display_for_static_str!(Feature);
impl Feature {
pub fn phoneme_class(&self) -> PhonemeClass {
match self {
Feature::Consonant(_) => PhonemeClass::Consonant,
Feature::Vowel(_) => PhonemeClass::Vowel,
}
}
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "lowercase")]
pub enum Voice {
Voiceless,
Breathy,
Slack,
Voiced,
Stiff,
Creaky,
Glottalized,
Faucalized,
Strident,
Ventricular,
}
display_for_static_str!(Voice);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
pub enum RelativeArticulation {
Advanced,
Retracted,
Centralized,
MidCentralized,
Raised,
Lowered,
OverRounded,
UnderRounded,
}
display_for_static_str!(RelativeArticulation);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
pub enum SecondaryArticulation {
Labialized,
Palatalized,
LabioPalatalized,
Velarized,
Pharyngealized,
Epiglottalized,
}
display_for_static_str!(SecondaryArticulation);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
pub enum Length {
Long,
HalfLong,
Short,
ExtraShort,
}
display_for_static_str!(Length);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case", prefix = "with ", suffix = " tone")]
pub enum Tone {
ExtraHigh,
High,
Mid,
Low,
ExtraLow,
Rising,
Falling,
HighRising,
LowRising,
RisingFalling,
}
impl Tone {
pub fn as_number_str(self) -> &'static str {
match self {
Tone::ExtraHigh => "5",
Tone::High => "4",
Tone::Mid => "3",
Tone::Low => "2",
Tone::ExtraLow => "1",
Tone::Rising => "24",
Tone::Falling => "42",
Tone::HighRising => "45",
Tone::LowRising => "23",
Tone::RisingFalling => "243",
}
}
}
display_for_static_str!(Tone);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "lowercase", prefix = "with ", suffix = " release")]
pub enum Release {
#[strum(serialize = "no audible")]
None,
Schwa,
Lateral,
Nasal,
#[strum(serialize = "dental fricative")]
DentalFricative,
#[strum(serialize = "velar fricative")]
VelarFricative,
}
display_for_static_str!(Release);
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "lowercase", prefix = "with ", suffix = " tongue root")]
pub enum TongueRoot {
Advanced,
Retracted,
}
display_for_static_str!(TongueRoot);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive] pub enum Modifier {
NonSyllabic,
Syllabic,
Aspirated,
PreAspirated,
Apical,
Laminal,
Release(Release),
PreNasalized,
PostStopped,
PreStopped,
VelarFricativeOnset,
Voice(Voice),
SecondaryArticulation(SecondaryArticulation),
RelativeArticulation(RelativeArticulation),
TongueRoot(TongueRoot),
Nasalized,
Rhotacized,
Tone(Tone),
Length(Length),
Ejective,
}
impl From<Modifier> for &'static str {
fn from(value: Modifier) -> Self {
use Modifier::*;
match value {
NonSyllabic => "non-syllabic",
Syllabic => "syllabic",
Aspirated => "aspirated",
PreAspirated => "pre-aspirated",
Apical => "apical",
Laminal => "laminal",
Release(v) => v.into(),
PreNasalized => "pre-nasalized",
PostStopped => "post-stopped",
PreStopped => "pre-stopped",
VelarFricativeOnset => "with velar fricative onset",
Voice(v) => v.into(),
SecondaryArticulation(v) => v.into(),
RelativeArticulation(v) => v.into(),
TongueRoot(v) => v.into(),
Nasalized => "nasalized",
Rhotacized => "rhotacized",
Tone(v) => v.into(),
Length(v) => v.into(),
Ejective => "ejective",
}
}
}
impl<'a> From<&'a Modifier> for &'static str {
fn from(value: &'a Modifier) -> Self {
(*value).into()
}
}
display_for_static_str!(Modifier);
impl Modifier {
pub(crate) fn apply_modifier(&self, name: &mut VecDeque<&str>) {
use Modifier::*;
enum Affix {
Prefix,
Suffix,
}
let direction = match self {
NonSyllabic
| Syllabic
| Aspirated
| PreAspirated
| PreNasalized
| PreStopped
| Apical
| Laminal
| SecondaryArticulation(_)
| Nasalized
| Rhotacized
| Ejective
| Length(_)
| Voice(_)
| RelativeArticulation(_)
| PostStopped => Affix::Prefix,
Release(_) | VelarFricativeOnset | Tone(_) | TongueRoot(_) => Affix::Suffix,
};
match direction {
Affix::Prefix => name.push_front(self.into()),
Affix::Suffix => name.push_back(self.into()),
}
}
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, strum::Display, strum::IntoStaticStr,
)]
#[strum(serialize_all = "lowercase")]
pub enum PhonemeClass {
Consonant,
Vowel,
}
display_for_static_str!(PhonemeClass);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn name_labial_velar() {
let labial_velar = ConsonantFeature::DoubleArticulation(Place::Bilabial, Place::Velar);
assert_eq!("labial-velar", Into::<&'static str>::into(labial_velar));
}
}