use serde::Deserialize;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PhonotacticConstraint {
MaxClusterSize(usize),
NoGeminate,
ForbidBigram(String, String),
ForbidInOnset(Vec<String>),
ForbidInCoda(Vec<String>),
SonoritySequencing,
}
impl PhonotacticConstraint {
pub fn needs_syllables(&self) -> bool {
matches!(
self,
Self::ForbidInOnset(_) | Self::ForbidInCoda(_) | Self::SonoritySequencing
)
}
}
#[derive(Deserialize)]
struct RawConstraint {
kind: String,
#[serde(default)]
value: Option<usize>,
#[serde(default)]
a: Option<String>,
#[serde(default)]
b: Option<String>,
#[serde(default)]
classes: Option<Vec<String>>,
}
impl TryFrom<RawConstraint> for PhonotacticConstraint {
type Error = String;
fn try_from(r: RawConstraint) -> Result<Self, Self::Error> {
match r.kind.trim().to_ascii_lowercase().as_str() {
"max_cluster_size" | "max_cluster" => {
let n = r.value.ok_or("max_cluster_size needs `value`")?;
Ok(Self::MaxClusterSize(n))
}
"no_geminate" | "no_geminates" => Ok(Self::NoGeminate),
"forbid_bigram" => {
let a = r.a.ok_or("forbid_bigram needs `a`")?;
let b = r.b.ok_or("forbid_bigram needs `b`")?;
Ok(Self::ForbidBigram(a, b))
}
"forbid_in_onset" => {
Ok(Self::ForbidInOnset(r.classes.ok_or("forbid_in_onset needs `classes`")?))
}
"forbid_in_coda" => {
Ok(Self::ForbidInCoda(r.classes.ok_or("forbid_in_coda needs `classes`")?))
}
"sonority_sequencing" | "sonority" => Ok(Self::SonoritySequencing),
other => Err(format!("unknown constraint kind `{other}`")),
}
}
}
impl<'de> Deserialize<'de> for PhonotacticConstraint {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = RawConstraint::deserialize(d)?;
PhonotacticConstraint::try_from(raw).map_err(serde::de::Error::custom)
}
}