incantata/src/lib.rs

166 lines
4.3 KiB
Rust
Raw Normal View History

2021-08-26 12:04:31 +00:00
use rand::{thread_rng, Rng};
2022-10-31 18:23:52 +00:00
pub const CONSONANTS: &str = "bcdfghjklmnpqrstvwxyz";
2021-08-26 12:04:31 +00:00
2022-10-31 18:23:52 +00:00
pub const VOCALS: &str = "aeiou";
pub const VOCALS_ACCENTS: &str = "aeiouàèìòùáéíóúäëïöü";
2021-08-26 12:04:31 +00:00
2022-10-31 18:23:52 +00:00
/// structure of the language
2021-08-26 12:04:31 +00:00
pub struct Structure {
2022-10-31 18:23:52 +00:00
/// how many characters the onset is
2021-08-26 12:04:31 +00:00
pub onset: usize,
2022-10-31 18:23:52 +00:00
/// allowed characters for the onset
2021-08-26 12:04:31 +00:00
pub onset_dict: Vec<char>,
2022-10-31 18:23:52 +00:00
/// how many characters the nucleus is
2021-08-26 12:04:31 +00:00
pub nucleus: usize,
2022-10-31 18:23:52 +00:00
/// allowed characters for the nucleus
2021-08-26 12:04:31 +00:00
pub nucleus_dict: Vec<char>,
2022-10-31 18:23:52 +00:00
/// how many characters the coda is
2021-08-26 12:04:31 +00:00
pub coda: usize,
2022-10-31 18:23:52 +00:00
/// allowed characters for the coda
2021-08-26 12:04:31 +00:00
pub coda_dict: Vec<char>,
2022-10-31 18:23:52 +00:00
/// minimum length of a word
2021-08-26 12:04:31 +00:00
pub min_len: usize,
2022-10-31 18:23:52 +00:00
// the words will try to be close to `suggested_len`
//
// due to the way incantata works (by combining valid syllables),
// it can't actually make a word of a given length
2021-08-26 12:04:31 +00:00
pub suggested_len: usize,
}
2022-10-31 18:23:52 +00:00
impl Default for Structure {
fn default() -> Self {
Self {
onset: 1,
onset_dict: CONSONANTS.chars().collect(),
nucleus: 1,
nucleus_dict: VOCALS.chars().collect(),
coda: 0,
coda_dict: Default::default(),
min_len: 4,
suggested_len: 5,
}
2021-08-26 12:04:31 +00:00
}
}
2022-10-31 18:23:52 +00:00
impl Structure {
pub fn generate(&self) -> String {
assert!(
self.onset > 0 || self.nucleus > 0 || self.coda > 0,
"at least one should be non-zero"
);
2021-08-26 12:04:31 +00:00
2022-10-31 18:23:52 +00:00
let mut rng = thread_rng();
2021-08-26 12:04:31 +00:00
2022-10-31 18:23:52 +00:00
let len = rng.gen_range(self.min_len..self.suggested_len);
2021-08-26 12:04:31 +00:00
2022-10-31 18:23:52 +00:00
let mut s = String::new();
while s.len() < len {
let syl = self.syllable();
s.push_str(&syl);
}
s
}
fn syllable(&self) -> String {
let mut rng = thread_rng();
let mut state = State::Onset(0);
let mut s = String::new();
'syl: loop {
match state {
State::Onset(n) => {
if n >= self.onset || rng.gen_bool(0.3) {
state = State::Nucleus(0);
} else {
state = State::Onset(n + 1);
s.push(self.onset_dict[rng.gen_range(0..self.onset_dict.len())]);
}
2021-08-26 12:04:31 +00:00
}
2022-10-31 18:23:52 +00:00
State::Nucleus(n) => {
// nucleus is one or more
if n >= self.nucleus || (n > 0 && rng.gen_bool(0.3)) {
state = State::Coda(0);
} else {
state = State::Nucleus(n + 1);
s.push(self.nucleus_dict[rng.gen_range(0..self.nucleus_dict.len())]);
}
2021-08-26 12:04:31 +00:00
}
2022-10-31 18:23:52 +00:00
State::Coda(n) => {
if n >= self.coda || rng.gen_bool(0.3) {
break 'syl;
} else {
state = State::Coda(n + 1);
s.push(self.coda_dict[rng.gen_range(0..self.coda_dict.len())]);
}
2021-08-26 12:04:31 +00:00
}
}
}
2022-10-31 18:23:52 +00:00
s
2021-08-26 12:04:31 +00:00
}
2022-10-31 18:23:52 +00:00
}
enum State {
Onset(usize),
Nucleus(usize),
Coda(usize),
2021-08-26 12:04:31 +00:00
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let s = Structure {
onset: 1,
onset_dict: CONSONANTS.chars().collect(),
nucleus: 1,
nucleus_dict: VOCALS.chars().collect(),
coda: 1,
coda_dict: CONSONANTS.chars().collect(),
min_len: 4,
suggested_len: 10,
};
for _ in 0..100 {
2022-10-31 18:23:52 +00:00
let r = s.generate();
assert!(r.len() >= 4);
}
}
#[test]
fn default_works() {
let s = Structure::default();
for _ in 0..100 {
let r = s.generate();
assert!(r.len() >= 4);
}
}
#[test]
#[should_panic]
fn empty_panics() {
let s = Structure {
onset: 0,
onset_dict: CONSONANTS.chars().collect(),
nucleus: 0,
nucleus_dict: VOCALS.chars().collect(),
coda: 0,
coda_dict: CONSONANTS.chars().collect(),
min_len: 4,
suggested_len: 10,
};
for _ in 0..100 {
let r = s.generate();
2021-08-26 12:04:31 +00:00
assert!(r.len() >= 4);
}
}
}