#![no_std]
pub trait Random {
fn random(&mut self) -> f64;
}
impl<T: Random> Random for &mut T {
fn random(&mut self) -> f64 {
(*self).random()
}
}
mod private {
#[derive(Debug, Clone)]
pub enum ConfigRange {
None,
Exact(u32),
Range(u32, u32),
}
impl ConfigRange {
pub fn calc(&self, rng: &mut impl super::Random, default_min: u32, default_max: u32) -> u32 {
let (x, y) = match self {
ConfigRange::None => (default_min, default_max),
ConfigRange::Exact(x) => (*x, *x),
ConfigRange::Range(x, y) => (*x, *y),
};
let y = y.max(x);
let x = x as f64;
let y = y as f64;
let norm = rng.random().clamp(0.0, 1.0f64.next_down());
(norm * (y - x) + x) as u32
}
}
pub trait IntoConfigRange {
fn range(self) -> ConfigRange;
}
impl IntoConfigRange for () {
fn range(self) -> ConfigRange {
ConfigRange::None
}
}
impl IntoConfigRange for u32 {
fn range(self) -> ConfigRange {
ConfigRange::Exact(self)
}
}
impl IntoConfigRange for core::ops::Range<u32> {
fn range(self) -> ConfigRange {
ConfigRange::Range(self.start, self.end.saturating_sub(1))
}
}
impl IntoConfigRange for core::ops::RangeInclusive<u32> {
fn range(self) -> ConfigRange {
ConfigRange::Range(*self.start(), *self.end())
}
}
}
pub const DEFAULT_END: &[(f64, &str)] = &[
(0.05, "!"),
(0.02, "!!"),
(0.07, "?"),
(0.11, " :3"),
(0.02, "..."),
];
pub const DEFAULT_END_FALLBACK: &str = ".";
pub const DEFAULT_MID: &[(f64, &str)] = &[
(0.08, ", "),
];
pub const DEFAULT_MID_FALLBACK: &str = " ";
#[derive(Debug, Clone)]
pub struct Config<'a> {
paragraph_range: private::ConfigRange,
sentence_range: private::ConfigRange,
word_range: private::ConfigRange,
mid: (&'a str, &'a [(f64, &'a str)]),
end: (&'a str, &'a [(f64, &'a str)]),
}
impl<'a> Config<'a> {
pub fn new() -> Self {
Self {
paragraph_range: private::ConfigRange::None,
sentence_range: private::ConfigRange::None,
word_range: private::ConfigRange::None,
mid: (
DEFAULT_MID_FALLBACK,
DEFAULT_MID,
),
end: (
DEFAULT_END_FALLBACK,
DEFAULT_END,
),
}
}
pub fn word(mut self, range: impl private::IntoConfigRange) -> Self {
self.word_range = range.range();
self
}
pub fn sentence(mut self, range: impl private::IntoConfigRange) -> Self {
self.sentence_range = range.range();
self
}
pub fn paragraph(mut self, range: impl private::IntoConfigRange) -> Self {
self.paragraph_range = range.range();
self
}
pub const fn end(mut self, values: &'a [(f64, &'a str)]) -> Self {
self.end.1 = values;
self
}
pub fn end_fallback(mut self, value: &'a str) -> Self {
self.end.0 = value;
self
}
pub fn mid(mut self, values: &'a [(f64, &'a str)]) -> Self {
self.mid.1 = values;
self
}
pub fn mid_fallback(mut self, value: &'a str) -> Self {
self.mid.0 = value;
self
}
}
impl Default for Config<'_> {
fn default() -> Self {
Self::new()
}
}
pub fn word<W, R>(mut write: W, mut rng: R, config: &Config) -> Result<(), core::fmt::Error>
where W: core::fmt::Write, R: Random {
const FULL: &[&str] = &[
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
];
const VOWEL: &[&str] = &[
"a",
"e",
"i",
"o",
"u",
];
const FILL: &[&str] = &[
"b",
"c",
"d",
"f",
"g",
"h",
"j",
"k",
"l",
"m",
"n",
"p",
"q",
"r",
"s",
"t",
"v",
"w",
"x",
"y",
"z",
];
const LAC: &[&str] = &[
"s",
"l",
"w",
];
enum Kind {
None,
Consonant,
Vowel,
Lac,
}
#[inline]
fn go(table: &[&'static str], random: f64) -> &'static str {
let len = table.len() as f64;
let index = (random * len).clamp(0.0, len.next_down()) as usize;
table.get(index).expect("unreachable")
}
let len = config.word_range.calc(&mut rng, 2, 12);
let mut state = Kind::None;
for _ in 0..len {
let s = match state {
Kind::None => {
state = Kind::Consonant;
go(FULL, rng.random())
}
Kind::Consonant => {
if rng.random() < 0.33 {
state = Kind::Lac;
go(LAC, rng.random())
}
else {
state = Kind::Vowel;
go(VOWEL, rng.random())
}
}
Kind::Lac => {
state = Kind::Vowel;
go(VOWEL, rng.random())
}
Kind::Vowel => {
if rng.random() < 0.25 {
state = Kind::Vowel;
go(VOWEL, rng.random())
}
else {
state = Kind::Consonant;
go(FILL, rng.random())
}
}
};
write!(write, "{}", s)?;
}
Ok(())
}
pub fn sentence<W, R>(mut write: W, mut rng: R, config: &Config) -> Result<(), core::fmt::Error>
where W: core::fmt::Write, R: Random {
let len = config.sentence_range.calc(&mut rng, 2, 14);
for i in 0..len {
word(&mut write, &mut rng, config)?;
let (def, list) = if i == len - 1 {
config.end
}
else {
config.mid
};
let mut random = rng.random();
let mut get = None;
for (chance, value) in list {
if random < *chance {
get = Some(*value);
break;
}
random -= *chance;
}
let mark = get.unwrap_or(def);
write!(write, "{}", mark)?;
}
Ok(())
}
pub fn paragraph<W, R>(mut write: W, mut rng: R, config: &Config) -> Result<(), core::fmt::Error>
where W: core::fmt::Write, R: Random {
let len = config.paragraph_range.calc(&mut rng, 6, 12);
for _ in 0..len {
sentence(&mut write, &mut rng, config)?;
write!(write, " ")?;
}
Ok(())
}
#[cfg(test)]
mod test {
extern crate std;
struct Fake(f64);
impl crate::Random for Fake {
fn random(&mut self) -> f64 {
self.0
}
}
#[test]
fn test() {
let mut string = std::string::String::new();
crate::word(&mut string, &mut Fake(0.0), &crate::Config::new().word(4)).unwrap();
assert_eq!(string.len(), 4);
let mut string = std::string::String::new();
crate::word(&mut string, &mut Fake(0.0), &crate::Config::new().word(4..6)).unwrap();
assert_eq!(string.len(), 4);
let mut string = std::string::String::new();
crate::word(&mut string, &mut Fake(1.0), &crate::Config::new().word(4..6)).unwrap();
assert_eq!(string.len(), 5);
let mut string = std::string::String::new();
crate::word(&mut string, &mut Fake(1.0), &crate::Config::new().word(4..=6)).unwrap();
assert_eq!(string.len(), 6);
}
}