make cli
parent
7fb5637ecb
commit
8686a77190
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "incantata"
|
name = "incantata"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "incantata"
|
name = "incantata"
|
||||||
|
@ -9,7 +9,8 @@ path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "incantata"
|
name = "incantata"
|
||||||
path = "src/bin.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "3.2.21", features = ["derive"] }
|
||||||
rand = "0.8.4"
|
rand = "0.8.4"
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
use incantata::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
loop {
|
||||||
|
// structure of the language
|
||||||
|
let s = Structure {
|
||||||
|
// how many characters the onset is
|
||||||
|
onset: 1,
|
||||||
|
// allowed characters for the onset
|
||||||
|
onset_dict: CONSONANTS.chars().collect(),
|
||||||
|
|
||||||
|
// how many characters the nucleus is
|
||||||
|
nucleus: 1,
|
||||||
|
// allowed characters for the nucleus
|
||||||
|
nucleus_dict: VOCALS
|
||||||
|
.chars()
|
||||||
|
// .cycle()
|
||||||
|
// .take(VOCALS.len() * 5)
|
||||||
|
// .chain(VOCALS_ACCENTS.chars())
|
||||||
|
.collect(),
|
||||||
|
|
||||||
|
// how many characters the coda is
|
||||||
|
coda: 0,
|
||||||
|
// allowed characters for the coda
|
||||||
|
coda_dict: CONSONANTS.chars().collect(),
|
||||||
|
|
||||||
|
// minimum length of a word
|
||||||
|
min_len: 4,
|
||||||
|
// the words will be generated to be around this length
|
||||||
|
// due to the way incantata works (by combining valid syllables),
|
||||||
|
// we can't actually make a word of a given length
|
||||||
|
suggested_len: 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
// generate 10 words
|
||||||
|
for _ in 0..10 {
|
||||||
|
println!("{}", s.generate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if std::io::stdin().read_line(&mut String::new()).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/bin.rs
38
src/bin.rs
|
@ -1,38 +0,0 @@
|
||||||
use incantata::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// structure of the language
|
|
||||||
let s = Structure {
|
|
||||||
// how many characters the onset is
|
|
||||||
onset: 1,
|
|
||||||
// allowed characters for the onset
|
|
||||||
onset_dict: CONSONANTS.chars().collect(),
|
|
||||||
|
|
||||||
// how many characters the nucleus is
|
|
||||||
nucleus: 1,
|
|
||||||
// allowed characters for the nucleus
|
|
||||||
nucleus_dict: VOCALS
|
|
||||||
.chars()
|
|
||||||
.cycle()
|
|
||||||
.take(VOCALS.len() * 5)
|
|
||||||
// .chain(VOCALS_ACCENTS.chars())
|
|
||||||
.collect(),
|
|
||||||
|
|
||||||
// how many characters the coda is
|
|
||||||
coda: 0,
|
|
||||||
// allowed characters for the coda
|
|
||||||
coda_dict: CONSONANTS.chars().collect(),
|
|
||||||
|
|
||||||
// minimum length of a word
|
|
||||||
min_len: 4,
|
|
||||||
// the words will be generated to be around this length
|
|
||||||
// due to the way incantata works (by combining valid syllables),
|
|
||||||
// we can't actually make a word of a given length
|
|
||||||
suggested_len: 15,
|
|
||||||
};
|
|
||||||
|
|
||||||
// generate 10 words
|
|
||||||
for _ in 0..10 {
|
|
||||||
println!("{}", incantata(&s));
|
|
||||||
}
|
|
||||||
}
|
|
109
src/lib.rs
109
src/lib.rs
|
@ -1,47 +1,71 @@
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
pub const CONSONANTS: &'static str = "bcdfghjklmnpqrstvwxyz";
|
pub const CONSONANTS: &str = "bcdfghjklmnpqrstvwxyz";
|
||||||
|
|
||||||
pub const VOCALS: &'static str = "aeiou";
|
pub const VOCALS: &str = "aeiou";
|
||||||
pub const VOCALS_ACCENTS: &'static str = "aeiouàèìòùáéíóúäëïöü";
|
pub const VOCALS_ACCENTS: &str = "aeiouàèìòùáéíóúäëïöü";
|
||||||
|
|
||||||
// TODO replace Vec<char> with HashMap<char, f32>, where the value is the probability
|
|
||||||
|
|
||||||
|
/// structure of the language
|
||||||
pub struct Structure {
|
pub struct Structure {
|
||||||
|
/// how many characters the onset is
|
||||||
pub onset: usize,
|
pub onset: usize,
|
||||||
|
/// allowed characters for the onset
|
||||||
pub onset_dict: Vec<char>,
|
pub onset_dict: Vec<char>,
|
||||||
|
|
||||||
|
/// how many characters the nucleus is
|
||||||
pub nucleus: usize,
|
pub nucleus: usize,
|
||||||
|
/// allowed characters for the nucleus
|
||||||
pub nucleus_dict: Vec<char>,
|
pub nucleus_dict: Vec<char>,
|
||||||
|
|
||||||
|
/// how many characters the coda is
|
||||||
pub coda: usize,
|
pub coda: usize,
|
||||||
|
/// allowed characters for the coda
|
||||||
pub coda_dict: Vec<char>,
|
pub coda_dict: Vec<char>,
|
||||||
|
|
||||||
|
/// minimum length of a word
|
||||||
pub min_len: usize,
|
pub min_len: usize,
|
||||||
|
// 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
|
||||||
pub suggested_len: usize,
|
pub suggested_len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn incantata(structure: &Structure) -> String {
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Structure {
|
||||||
|
pub fn generate(&self) -> String {
|
||||||
|
assert!(
|
||||||
|
self.onset > 0 || self.nucleus > 0 || self.coda > 0,
|
||||||
|
"at least one should be non-zero"
|
||||||
|
);
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let len = rng.gen_range(structure.min_len..structure.suggested_len);
|
let len = rng.gen_range(self.min_len..self.suggested_len);
|
||||||
|
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
while s.len() < len {
|
while s.len() < len {
|
||||||
let syl = syllable(structure);
|
let syl = self.syllable();
|
||||||
s.push_str(&syl);
|
s.push_str(&syl);
|
||||||
}
|
}
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
fn syllable(&self) -> String {
|
||||||
enum State {
|
|
||||||
Onset(usize),
|
|
||||||
Nucleus(usize),
|
|
||||||
Coda(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn syllable(structure: &Structure) -> String {
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let mut state = State::Onset(0);
|
let mut state = State::Onset(0);
|
||||||
|
@ -50,33 +74,40 @@ fn syllable(structure: &Structure) -> String {
|
||||||
'syl: loop {
|
'syl: loop {
|
||||||
match state {
|
match state {
|
||||||
State::Onset(n) => {
|
State::Onset(n) => {
|
||||||
if n >= structure.onset || rng.gen_bool(0.3) {
|
if n >= self.onset || rng.gen_bool(0.3) {
|
||||||
state = State::Nucleus(0);
|
state = State::Nucleus(0);
|
||||||
} else {
|
} else {
|
||||||
state = State::Onset(n + 1);
|
state = State::Onset(n + 1);
|
||||||
s.push(structure.onset_dict[rng.gen_range(0..structure.onset_dict.len())]);
|
s.push(self.onset_dict[rng.gen_range(0..self.onset_dict.len())]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Nucleus(n) => {
|
State::Nucleus(n) => {
|
||||||
// nucleus is one or more
|
// nucleus is one or more
|
||||||
if n >= structure.nucleus || (n > 0 && rng.gen_bool(0.3)) {
|
if n >= self.nucleus || (n > 0 && rng.gen_bool(0.3)) {
|
||||||
state = State::Coda(0);
|
state = State::Coda(0);
|
||||||
} else {
|
} else {
|
||||||
state = State::Nucleus(n + 1);
|
state = State::Nucleus(n + 1);
|
||||||
s.push(structure.nucleus_dict[rng.gen_range(0..structure.nucleus_dict.len())]);
|
s.push(self.nucleus_dict[rng.gen_range(0..self.nucleus_dict.len())]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Coda(n) => {
|
State::Coda(n) => {
|
||||||
if n >= structure.coda || rng.gen_bool(0.3) {
|
if n >= self.coda || rng.gen_bool(0.3) {
|
||||||
break 'syl;
|
break 'syl;
|
||||||
} else {
|
} else {
|
||||||
state = State::Coda(n + 1);
|
state = State::Coda(n + 1);
|
||||||
s.push(structure.coda_dict[rng.gen_range(0..structure.coda_dict.len())]);
|
s.push(self.coda_dict[rng.gen_range(0..self.coda_dict.len())]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Onset(usize),
|
||||||
|
Nucleus(usize),
|
||||||
|
Coda(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -97,7 +128,37 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
let r = incantata(&s);
|
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();
|
||||||
assert!(r.len() >= 4);
|
assert!(r.len() >= 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
use clap::*;
|
||||||
|
use incantata::*;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[clap(short, long, default_value_t = 1)]
|
||||||
|
onset: usize,
|
||||||
|
#[clap(short, long, default_value_t = 1)]
|
||||||
|
nucleus: usize,
|
||||||
|
#[clap(short, long, default_value_t = 0)]
|
||||||
|
coda: usize,
|
||||||
|
/// minimum length of a word
|
||||||
|
#[clap(short, long, default_value_t = 4)]
|
||||||
|
min_len: usize,
|
||||||
|
#[clap(short, long, default_value_t = 15)]
|
||||||
|
suggested_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// structure of the language
|
||||||
|
let s = Structure {
|
||||||
|
// how many characters the onset is
|
||||||
|
onset: cli.onset,
|
||||||
|
// allowed characters for the onset
|
||||||
|
onset_dict: CONSONANTS.chars().collect(),
|
||||||
|
|
||||||
|
// how many characters the nucleus is
|
||||||
|
nucleus: cli.nucleus,
|
||||||
|
// allowed characters for the nucleus
|
||||||
|
nucleus_dict: VOCALS.chars().collect(),
|
||||||
|
|
||||||
|
// how many characters the coda is
|
||||||
|
coda: cli.coda,
|
||||||
|
// allowed characters for the coda
|
||||||
|
coda_dict: CONSONANTS.chars().collect(),
|
||||||
|
|
||||||
|
// minimum length of a word
|
||||||
|
min_len: cli.min_len,
|
||||||
|
// the words will be generated to be around this length
|
||||||
|
// due to the way incantata works (by combining valid syllables),
|
||||||
|
// we can't actually make a word of a given length
|
||||||
|
suggested_len: cli.suggested_len,
|
||||||
|
};
|
||||||
|
|
||||||
|
// generate 10 words
|
||||||
|
for _ in 0..10 {
|
||||||
|
println!("{}", s.generate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if std::io::stdin().read_line(&mut String::new()).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue