use crate::{characters::*, Token};
use super::{ParseAction, TengwarMode};
pub const CONSONANT_I: char = TENGWA_ARA;
pub const ANDAITH: Tehta = Tehta::single(DC_OVER_ACUTE_1);
pub const VOWEL_A: char = TENGWA_OSSE;
pub const VOWEL_E: char = TENGWA_YANTA;
pub const VOWEL_I: char = TENGWA_TELCO;
pub const VOWEL_O: char = TENGWA_ANNA;
pub const VOWEL_U: char = TENGWA_URE;
pub const VOWEL_Y: char = TENGWA_SILME_NUQ;
pub const fn consonant_char(slice: &[char]) -> Option<char> {
Some(match slice {
['t'] => TEMA_TINCO.single_dn,
['d'] => TEMA_TINCO.double_dn,
['t', 'h']
| ['θ'] | ['þ'] => TEMA_TINCO.single_up,
['d', 'h']
| ['ð'] => TEMA_TINCO.double_up,
['n', 'n'] => TEMA_TINCO.double_sh,
['n'] => TEMA_TINCO.single_sh,
['p'] => TEMA_PARMA.single_dn,
['b'] => TEMA_PARMA.double_dn,
['p', 'h']
| ['φ'] => TEMA_PARMA.single_up,
['v'] => TEMA_PARMA.double_up,
['m', 'm'] => TEMA_PARMA.double_sh,
['m'] => TEMA_PARMA.single_sh,
['c'] | ['k'] => TEMA_CALMA.single_dn,
['g'] => TEMA_CALMA.double_dn,
['c', 'h']
| ['k', 'h'] => TEMA_CALMA.single_up,
['g', 'h'] => TEMA_CALMA.double_up,
['n', 'g']
| ['ñ'] => TEMA_CALMA.double_sh,
['w'] => TEMA_QESSE.single_sh,
['l'] => TENGWA_LAMBE,
['r'] => TENGWA_ROMEN,
['m', 'h'] => TENGWA_VALA_HOOKED,
['s'] => TENGWA_SILME,
['s', 's']
| ['z'] | ['ß'] => TENGWA_ESSE,
['h'] => TENGWA_HYARMEN,
['h', 'w'] => TENGWA_HWESTA_SINDARINWA,
['j'] => CONSONANT_I,
_ => { return None; }
})
}
pub const fn get_consonant(slice: &[char]) -> Option<Glyph> {
match consonant_char(slice) {
Some(cons) => Some(Glyph::new_base(cons)),
None => None,
}
}
pub const fn get_diphthong(slice: &[char]) -> Option<Glyph> {
match slice {
['a', 'i'] => Some(Glyph::new_both(VOWEL_A, TEHTA_Y)),
['e', 'i'] => Some(Glyph::new_both(VOWEL_E, TEHTA_Y)),
['u', 'i'] => Some(Glyph::new_both(VOWEL_U, TEHTA_Y)),
['a', 'u']
| ['a', 'w'] => Some(Glyph::new_base(VOWEL_A).with_labial(true)),
_ => None,
}
}
pub const fn get_vowel(slice: &[char]) -> Option<Glyph> {
Some(match slice {
['a'] | ['ä'] => Glyph::new_base(VOWEL_A),
['e'] | ['ë'] => Glyph::new_base(VOWEL_E),
['i'] | ['ï'] => Glyph::new_base(VOWEL_I),
['o'] | ['ö'] => Glyph::new_base(VOWEL_O),
['u'] | ['ü'] => Glyph::new_base(VOWEL_U),
['y'] | ['ÿ'] => Glyph::new_base(VOWEL_Y),
['á'] | ['â'] | ['a', 'a'] => Glyph::new_both(VOWEL_A, ANDAITH),
['é'] | ['ê'] | ['e', 'e'] => Glyph::new_both(VOWEL_E, ANDAITH),
['í'] | ['î'] | ['i', 'i'] => Glyph::new_both(VOWEL_I, ANDAITH),
['ó'] | ['ô'] | ['o', 'o'] => Glyph::new_both(VOWEL_O, ANDAITH),
['ú'] | ['û'] | ['u', 'u'] => Glyph::new_both(VOWEL_U, ANDAITH),
['ý'] | ['ŷ'] | ['y', 'y'] => Glyph::new_both(VOWEL_Y, ANDAITH),
_ => { return None; }
})
}
pub const fn get_vowel_either(slice: &[char]) -> Option<Glyph> {
if let Some(glyph) = get_diphthong(slice) {
Some(glyph)
} else if let Some(glyph) = get_vowel(slice) {
Some(glyph)
} else {
None
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Beleriand {
current: Option<Glyph>,
previous: Option<Glyph>,
}
impl Beleriand {
pub fn decide_f(next: &[char]) -> Glyph {
let mut mode = Self::default();
let mut is_final: bool = true;
let mut n: usize = next.len();
while 0 < n {
match mode.process(&next[..n]) {
ParseAction::MatchedNone => {
n -= 1;
}
ParseAction::MatchedPart(_) => {
is_final = false;
break;
}
ParseAction::MatchedToken { token: Token::Glyph(_), .. } => {
is_final = false;
break;
}
_ => {
is_final = true;
break;
}
}
}
let phonetic: &[char] = if is_final { &['v'] } else { &['p', 'h'] };
consonant_char(phonetic).unwrap().into()
}
}
impl TengwarMode for Beleriand {
fn finish_current(&mut self) -> Option<Token> {
self.previous = self.current.take();
self.previous.map(Token::Glyph)
}
fn process(&mut self, chunk: &[char]) -> ParseAction {
macro_rules! finish {
($glyph:expr) => {finish!($glyph, 0)};
($glyph:expr, $len:expr) => {{
let finished = $glyph;
self.current = None;
output!(finished, $len)
}};
}
macro_rules! output {
($glyph:expr, $len:expr) => {{
self.previous = Some($glyph);
ParseAction::MatchedToken {
token: Token::Glyph($glyph),
len: $len,
}
}};
}
let initial: bool = self.previous.is_none();
if let [ESC, ESC_NOP, ..] = chunk {
self.previous = None;
ParseAction::matched_opt(self.current.take().map(Token::Glyph), 2)
} else if let [ESC, _, ..] = chunk {
ParseAction::ESC_BACKSLASH
} else if let Some(current) = &mut self.current {
finish!(*current)
} else {
if let ['x'] = chunk {
self.current = Some(Glyph::new_base(TENGWA_SILME));
output!(Glyph::new_base(TENGWA_CALMA), 1)
} else if let ['æ'] = chunk {
self.current = Some(Glyph::new_base(VOWEL_E));
output!(Glyph::new_base(VOWEL_A), 1)
} else if let ['œ'] = chunk {
self.current = Some(Glyph::new_base(VOWEL_E));
output!(Glyph::new_base(VOWEL_O), 1)
}
else if initial && ['l', 'h'] == chunk {
finish!(Glyph::new_base(TENGWA_ALDA), 2)
} else if initial && ['r', 'h'] == chunk {
finish!(Glyph::new_base(TENGWA_ARDA), 2)
}
else if let ['f', ahead @ ..] = chunk {
finish!(Self::decide_f(ahead), 1)
}
else if let Some(new) = get_consonant(chunk) {
finish!(new, chunk.len())
} else {
if let ['m' | 'n', rest @ ..] = chunk {
if let Some(new) = get_consonant(rest) {
return finish!(new.with_nasal(true), chunk.len());
}
}
if let Some(new) = get_vowel_either(chunk) {
finish!(new, chunk.len())
} else {
if initial {
if let ['i', rest @ ..] = chunk {
if let Some(new) = get_vowel_either(rest) {
self.current = Some(new);
return ParseAction::MatchedToken {
token: Token::Glyph(CONSONANT_I.into()),
len: chunk.len(),
};
}
}
}
ParseAction::MatchedNone
}
}
}
}
}