use crate::generated::enums::{Alias, Condition, WrittenUnit};
use crate::shaper::{
next_adjacent_letter, next_letter, next_tok, prev_adjacent_letter, prev_letter, prev_tok,
Shaper,
};
use crate::tables::{Fvs, Locale, ParticleSym, Position};
use crate::token::Token;
pub(crate) struct Rule {
pub name: &'static str,
pub apply: fn(&mut [Token], &Shaper),
}
pub(crate) fn rules_for(locale: Locale) -> &'static [Rule] {
match locale {
Locale::Mng => RULES_MNG,
Locale::Tod | Locale::Sib | Locale::Mch => &[],
}
}
pub(crate) fn run_rules(rules: &[Rule], tokens: &mut [Token], shaper: &Shaper) {
for rule in rules {
(rule.apply)(tokens, shaper);
}
}
#[derive(Clone, Copy)]
struct View {
is_letter: bool,
alias: Option<Alias>,
position: Position,
has_fvs: bool,
first_fvs: Option<Fvs>,
condition: Option<Condition>,
}
fn view(tokens: &[Token], index: usize) -> View {
let token = &tokens[index];
View {
is_letter: token.is_letter(),
alias: token.alias,
position: token.position,
has_fvs: token.has_fvs(),
first_fvs: token.first_fvs(),
condition: token.condition,
}
}
fn set(tokens: &mut [Token], index: usize, condition: Condition) {
tokens[index].condition = Some(condition);
}
macro_rules! per_token {
($name:ident => $at:ident) => {
fn $name(tokens: &mut [Token], shaper: &Shaper) {
for index in 0..tokens.len() {
$at(tokens, index, shaper);
}
}
};
}
per_token!(iii1_chachlag => iii1_chachlag_at);
fn iii1_chachlag_at(tokens: &mut [Token], index: usize, _shaper: &Shaper) {
let tok = view(tokens, index);
if !tok.is_letter {
return;
}
if !matches!(tok.alias, Some(Alias::A | Alias::E)) {
return;
}
let Some(prev) = prev_tok(tokens, index) else {
return;
};
if !tokens[prev].is_mvs() {
return;
}
if tok.has_fvs {
return; }
set(tokens, index, Condition::Chachlag);
}
per_token!(iii2a_o_u_oe_ue_marked => iii2a_o_u_oe_ue_marked_at);
fn iii2a_o_u_oe_ue_marked_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() || !tok.is_letter {
return;
}
if !matches!(tok.alias, Some(Alias::O | Alias::U | Alias::Oe | Alias::Ue)) {
return;
}
let Some(prev) = prev_letter(tokens, index) else {
return;
};
if !(shaper.is_consonant(&tokens[prev]) && tokens[prev].position == Position::Init) {
return;
}
set(tokens, index, Condition::Marked);
}
per_token!(iii2a_o_u_oe_ue_marked_gb_a => iii2a_o_u_oe_ue_marked_gb_a_at);
fn iii2a_o_u_oe_ue_marked_gb_a_at(tokens: &mut [Token], index: usize, _shaper: &Shaper) {
let tok = view(tokens, index);
if !tok.is_letter {
return;
}
if !matches!(tok.alias, Some(Alias::O | Alias::U | Alias::Oe | Alias::Ue)) {
return;
}
if tok.condition != Some(Condition::Marked) {
return;
}
if tok.position != Position::Fina {
return;
}
if tok.has_fvs {
tokens[index].condition = None;
return;
}
if let Some(prev) = prev_letter(tokens, index) {
if tokens[prev].has_fvs() {
tokens[index].condition = None;
}
}
}
per_token!(iii2a_o_u_oe_ue_marked_gb_b => iii2a_o_u_oe_ue_marked_gb_b_at);
fn iii2a_o_u_oe_ue_marked_gb_b_at(tokens: &mut [Token], index: usize, _shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() || !tok.is_letter {
return;
}
if !matches!(tok.alias, Some(Alias::Oe | Alias::Ue)) || tok.position != Position::Fina {
return;
}
let Some(prev) = prev_letter(tokens, index) else {
return;
};
let prev = view(tokens, prev);
if !matches!(prev.alias, Some(Alias::H | Alias::G)) || prev.position != Position::Init {
return;
}
if !matches!(prev.first_fvs, Some(Fvs::Fvs2 | Fvs::Fvs4)) {
return;
}
set(tokens, index, Condition::Marked);
}
per_token!(iii2a_oe_ue_cluster_marked => iii2a_oe_ue_cluster_marked_at);
fn iii2a_oe_ue_cluster_marked_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() || !tok.is_letter {
return;
}
if !matches!(tok.alias, Some(Alias::O | Alias::U | Alias::Oe | Alias::Ue)) {
return;
}
if tok.position != Position::Medi || tok.has_fvs {
return;
}
let mut j = index;
let mut saw_medi = false;
while j > 0 {
j -= 1;
let t = &tokens[j];
if !t.is_letter() {
if t.is_nirugu() {
continue;
}
return; }
if !shaper.is_consonant(t) {
return; }
if t.position == Position::Init {
if saw_medi {
set(tokens, index, Condition::Marked);
}
return;
}
if t.position != Position::Medi {
return; }
saw_medi = true;
}
}
per_token!(iii2a_d_marked => iii2a_d_marked_at);
fn iii2a_d_marked_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() {
return;
}
if !tok.is_letter || tok.alias != Some(Alias::D) {
return;
}
if tok.position != Position::Init || tok.has_fvs {
return;
}
let Some(next) = next_letter(tokens, index) else {
return;
};
if !(shaper.is_vowel(&tokens[next]) && tokens[next].position == Position::Fina) {
return;
}
if tokens[next].has_fvs() {
return; }
set(tokens, index, Condition::Marked);
}
per_token!(iii2c_chachlag_onset => iii2c_chachlag_onset_at);
fn iii2c_chachlag_onset_at(tokens: &mut [Token], index: usize, _shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() || !tok.is_letter || tok.has_fvs {
return;
}
let Some(next) = next_tok(tokens, index) else {
return;
};
if !tokens[next].is_mvs() {
return;
}
let Some(after) = next_letter(tokens, next) else {
return;
};
let after = view(tokens, after);
if after.position != Position::Isol {
return;
}
if matches!(tok.alias, Some(Alias::N | Alias::J | Alias::W))
&& matches!(after.alias, Some(Alias::A | Alias::E))
{
set(tokens, index, Condition::ChachlagOnset);
return;
}
if matches!(tok.alias, Some(Alias::H | Alias::G)) && after.alias == Some(Alias::A) {
set(tokens, index, Condition::ChachlagOnset);
return;
}
if tok.alias == Some(Alias::G) && after.alias == Some(Alias::E) {
set(tokens, index, Condition::ChachlagOnsetGb);
}
}
per_token!(iii2e_n_t_d_onset_devsger => iii2e_n_t_d_onset_devsger_at);
fn iii2e_n_t_d_onset_devsger_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() {
return;
}
if !tok.is_letter || !matches!(tok.alias, Some(Alias::N | Alias::T | Alias::D)) {
return;
}
if let Some(next) = next_letter(tokens, index) {
if shaper.is_vowel(&tokens[next])
&& !(tok.alias == Some(Alias::T) && tokens[next].alias == Some(Alias::Ee))
{
set(tokens, index, Condition::Onset);
return;
}
}
if let Some(prev) = prev_letter(tokens, index) {
if shaper.is_vowel(&tokens[prev]) {
set(tokens, index, Condition::Devsger);
}
}
}
per_token!(iii2f_h_g_harmony => iii2f_h_g_harmony_at);
fn iii2f_h_g_harmony_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() {
return;
}
if !tok.is_letter || !matches!(tok.alias, Some(Alias::H | Alias::G)) || tok.has_fvs {
return;
}
let next = next_adjacent_letter(tokens, index);
let prev = prev_adjacent_letter(tokens, index);
if let Some(next) = next {
if shaper.is_masc_vowel(&tokens[next]) {
set(tokens, index, Condition::MasculineOnset);
return;
}
if shaper.is_fem_vowel(&tokens[next]) || shaper.is_neut_vowel(&tokens[next]) {
set(tokens, index, Condition::Feminine);
return;
}
}
if let Some(prev) = prev {
if shaper.is_masc_vowel(&tokens[prev]) {
set(tokens, index, Condition::MasculineDevsger);
return;
}
if shaper.is_fem_vowel(&tokens[prev]) {
set(tokens, index, Condition::Feminine);
return;
}
}
if let Some(prev) = prev {
if tokens[prev].alias == Some(Alias::I) {
if shaper.masc_marker_reaches_g_h(tokens, index) {
set(tokens, index, Condition::MasculineDevsger);
return;
}
if tok.alias == Some(Alias::G) {
set(tokens, index, Condition::Feminine);
return;
}
}
}
if tok.position == Position::Init {
if let Some(next) = next {
if shaper.is_consonant(&tokens[next]) {
set(tokens, index, Condition::Feminine);
}
}
}
}
per_token!(iii2g_t_devsger => iii2g_t_devsger_at);
fn iii2g_t_devsger_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() {
return;
}
if !tok.is_letter || tok.alias != Some(Alias::T) || tok.has_fvs {
return;
}
let Some(next) = next_letter(tokens, index) else {
return;
};
if tokens[next].alias == Some(Alias::Ee) || shaper.is_consonant(&tokens[next]) {
set(tokens, index, Condition::Devsger);
}
}
per_token!(iii2g_sh_dotless => iii2g_sh_dotless_at);
fn iii2g_sh_dotless_at(tokens: &mut [Token], index: usize, _shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() {
return;
}
if !tok.is_letter || tok.alias != Some(Alias::Sh) || tok.has_fvs {
return;
}
let Some(next) = next_letter(tokens, index) else {
return;
};
let next = view(tokens, next);
if next.alias != Some(Alias::I) {
return;
}
if tok.position == Position::Init && next.position == Position::Medi {
set(tokens, index, Condition::Dotless);
return;
}
if tok.position == Position::Medi {
set(tokens, index, Condition::Dotless);
}
}
per_token!(iii2g_g_dotless => iii2g_g_dotless_at);
fn iii2g_g_dotless_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if !tok.is_letter || tok.alias != Some(Alias::G) || tok.has_fvs {
return;
}
let Some(prev) = prev_letter(tokens, index) else {
return;
};
if !matches!(tokens[prev].alias, Some(Alias::S | Alias::D)) {
return;
}
if tok.position == Position::Medi {
if let Some(next) = next_letter(tokens, index) {
if shaper.is_masc_vowel(&tokens[next]) {
set(tokens, index, Condition::Dotless);
return;
}
}
}
if tok.position == Position::Fina {
if let Some(next) = next_tok(tokens, index) {
if tokens[next].is_mvs() {
if let Some(after) = next_letter(tokens, next) {
let after = view(tokens, after);
if after.alias == Some(Alias::A)
&& after.position == Position::Isol
&& after.condition == Some(Condition::Chachlag)
{
set(tokens, index, Condition::Dotless);
}
}
}
}
}
}
fn iii3_particle(tokens: &mut [Token], shaper: &Shaper) {
for (syms, indices) in build_mvs_segments(tokens) {
let mut particle_indices = shaper.particle(&syms);
let mut used: &[usize] = &indices;
if particle_indices.is_none() && syms.first() == Some(&ParticleSym::Mvs) {
particle_indices = shaper.particle(&syms[1..]);
used = &indices[1..];
}
let Some(particle_indices) = particle_indices else {
continue;
};
if indices
.iter()
.any(|&idx| tokens[idx].is_letter() && tokens[idx].has_fvs())
{
continue;
}
let first_letter = indices.iter().copied().find(|&idx| tokens[idx].is_letter());
if let Some(first) = first_letter {
if !matches!(tokens[first].position, Position::Init | Position::Isol) {
continue;
}
}
for &pidx in particle_indices {
if pidx >= used.len() {
continue; }
let token = &mut tokens[used[pidx]];
if token.is_letter()
&& matches!(
token.alias,
Some(
Alias::A | Alias::E | Alias::I | Alias::U | Alias::Ue | Alias::D | Alias::Y
)
)
{
token.condition = Some(Condition::Particle);
}
}
}
}
fn build_mvs_segments(tokens: &[Token]) -> Vec<(Vec<ParticleSym>, Vec<usize>)> {
let mut segments = Vec::new();
let mut syms: Vec<ParticleSym> = Vec::new();
let mut indices: Vec<usize> = Vec::new();
for (i, token) in tokens.iter().enumerate() {
if token.is_mvs() {
if !syms.is_empty() {
segments.push((std::mem::take(&mut syms), std::mem::take(&mut indices)));
}
syms.push(ParticleSym::Mvs);
indices.push(i);
} else if token.is_letter() {
syms.push(token.alias.map_or(ParticleSym::Unknown, ParticleSym::Alias));
indices.push(i);
}
}
if !syms.is_empty() {
segments.push((syms, indices));
}
segments
}
per_token!(iii4_vowel_devsger => iii4_vowel_devsger_at);
fn iii4_vowel_devsger_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition.is_some() {
return;
}
if !tok.is_letter || tok.alias != Some(Alias::I) || tok.position != Position::Medi {
return;
}
if tok.has_fvs {
return;
}
let Some(prev) = prev_letter(tokens, index) else {
return;
};
if !shaper.is_vowel(&tokens[prev]) {
return;
}
shaper.resolve_written(&mut tokens[prev]);
if tokens[prev].written_ends_with(WrittenUnit::I) {
return; }
set(tokens, index, Condition::VowelDevsger);
}
per_token!(iii5_post_bowed => iii5_post_bowed_at);
fn iii5_post_bowed_at(tokens: &mut [Token], index: usize, shaper: &Shaper) {
let tok = view(tokens, index);
if tok.condition == Some(Condition::Marked) {
return;
}
if !tok.is_letter {
return;
}
if !matches!(
tok.alias,
Some(Alias::O | Alias::U | Alias::Oe | Alias::Ue | Alias::A | Alias::E)
) {
return;
}
if tok.position != Position::Fina {
return;
}
let Some(prev) = prev_letter(tokens, index) else {
return;
};
shaper.resolve_written(&mut tokens[prev]);
let Some(last_unit) = tokens[prev]
.written
.and_then(|written| written.last().copied())
else {
return;
};
let prev = view(tokens, prev);
let is_bowed_g = matches!(last_unit, WrittenUnit::G | WrittenUnit::Gx);
let is_bowed_bk = matches!(
last_unit,
WrittenUnit::B | WrittenUnit::P | WrittenUnit::F | WrittenUnit::K | WrittenUnit::K2
);
if !(is_bowed_g || is_bowed_bk) {
return;
}
if is_bowed_g && tok.alias == Some(Alias::A) {
return;
}
if let Some(fvs) = prev.first_fvs {
if is_bowed_g
&& matches!(prev.alias, Some(Alias::G | Alias::H))
&& matches!(fvs, Fvs::Fvs2 | Fvs::Fvs4)
&& matches!(tok.alias, Some(Alias::O | Alias::U))
{
return;
}
if is_bowed_bk
&& prev.position == Position::Init
&& matches!(tok.alias, Some(Alias::Oe | Alias::Ue))
{
set(tokens, index, Condition::Marked);
return;
}
}
set(tokens, index, Condition::PostBowed);
}
pub(crate) static RULES_MNG: &[Rule] = &[
Rule {
name: "III.1.chachlag",
apply: iii1_chachlag,
},
Rule {
name: "III.2a.o_u_oe_ue.marked",
apply: iii2a_o_u_oe_ue_marked,
},
Rule {
name: "III.2a.o_u_oe_ue.marked.GB.A",
apply: iii2a_o_u_oe_ue_marked_gb_a,
},
Rule {
name: "III.2a.o_u_oe_ue.marked.GB.B",
apply: iii2a_o_u_oe_ue_marked_gb_b,
},
Rule {
name: "III.2a.oe_ue.cluster.marked",
apply: iii2a_oe_ue_cluster_marked,
},
Rule {
name: "III.2a.d.marked",
apply: iii2a_d_marked,
},
Rule {
name: "III.2c.chachlag_onset",
apply: iii2c_chachlag_onset,
},
Rule {
name: "III.2e.n_t_d.onset_devsger",
apply: iii2e_n_t_d_onset_devsger,
},
Rule {
name: "III.2f.h_g.harmony",
apply: iii2f_h_g_harmony,
},
Rule {
name: "III.2g.t.devsger",
apply: iii2g_t_devsger,
},
Rule {
name: "III.2g.sh.dotless",
apply: iii2g_sh_dotless,
},
Rule {
name: "III.2g.g.dotless",
apply: iii2g_g_dotless,
},
Rule {
name: "III.3.particle",
apply: iii3_particle,
},
Rule {
name: "III.4.vowel_devsger",
apply: iii4_vowel_devsger,
},
Rule {
name: "III.5.post_bowed",
apply: iii5_post_bowed,
},
];