First one-shot pass
This commit is contained in:
122
src/generator/code_syntax.rs
Normal file
122
src/generator/code_syntax.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::engine::filter::CharFilter;
|
||||
use crate::generator::TextGenerator;
|
||||
|
||||
pub struct CodeSyntaxGenerator {
|
||||
rng: SmallRng,
|
||||
language: String,
|
||||
}
|
||||
|
||||
impl CodeSyntaxGenerator {
|
||||
pub fn new(rng: SmallRng, language: &str) -> Self {
|
||||
Self {
|
||||
rng,
|
||||
language: language.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_snippets() -> Vec<&'static str> {
|
||||
vec![
|
||||
"fn main() { println!(\"hello\"); }",
|
||||
"let mut x = 0; x += 1;",
|
||||
"for i in 0..10 { println!(\"{}\", i); }",
|
||||
"if x > 0 { return true; }",
|
||||
"match val { Some(x) => x, None => 0 }",
|
||||
"struct Point { x: f64, y: f64 }",
|
||||
"impl Point { fn new(x: f64, y: f64) -> Self { Self { x, y } } }",
|
||||
"let v: Vec<i32> = vec![1, 2, 3];",
|
||||
"fn add(a: i32, b: i32) -> i32 { a + b }",
|
||||
"use std::collections::HashMap;",
|
||||
"pub fn process(input: &str) -> Result<String, Error> { Ok(input.to_string()) }",
|
||||
"let result = items.iter().filter(|x| x > &0).map(|x| x * 2).collect::<Vec<_>>();",
|
||||
"enum Color { Red, Green, Blue }",
|
||||
"trait Display { fn show(&self) -> String; }",
|
||||
"while let Some(item) = stack.pop() { process(item); }",
|
||||
"#[derive(Debug, Clone)] struct Config { name: String, value: i32 }",
|
||||
]
|
||||
}
|
||||
|
||||
fn python_snippets() -> Vec<&'static str> {
|
||||
vec![
|
||||
"def main(): print(\"hello\")",
|
||||
"for i in range(10): print(i)",
|
||||
"if x > 0: return True",
|
||||
"class Point: def __init__(self, x, y): self.x = x",
|
||||
"import os; path = os.path.join(\"a\", \"b\")",
|
||||
"result = [x * 2 for x in items if x > 0]",
|
||||
"with open(\"file.txt\") as f: data = f.read()",
|
||||
"def add(a: int, b: int) -> int: return a + b",
|
||||
"try: result = process(data) except ValueError as e: print(e)",
|
||||
"from collections import defaultdict",
|
||||
"lambda x: x * 2 + 1",
|
||||
"dict_comp = {k: v for k, v in pairs.items()}",
|
||||
]
|
||||
}
|
||||
|
||||
fn javascript_snippets() -> Vec<&'static str> {
|
||||
vec![
|
||||
"const x = 42; console.log(x);",
|
||||
"function add(a, b) { return a + b; }",
|
||||
"const arr = [1, 2, 3].map(x => x * 2);",
|
||||
"if (x > 0) { return true; }",
|
||||
"for (let i = 0; i < 10; i++) { console.log(i); }",
|
||||
"class Point { constructor(x, y) { this.x = x; this.y = y; } }",
|
||||
"const { name, age } = person;",
|
||||
"async function fetch(url) { const res = await get(url); return res.json(); }",
|
||||
"const obj = { ...defaults, ...overrides };",
|
||||
"try { parse(data); } catch (e) { console.error(e); }",
|
||||
"export default function handler(req, res) { res.send(\"ok\"); }",
|
||||
"const result = items.filter(x => x > 0).reduce((a, b) => a + b, 0);",
|
||||
]
|
||||
}
|
||||
|
||||
fn go_snippets() -> Vec<&'static str> {
|
||||
vec![
|
||||
"func main() { fmt.Println(\"hello\") }",
|
||||
"for i := 0; i < 10; i++ { fmt.Println(i) }",
|
||||
"if err != nil { return err }",
|
||||
"type Point struct { X float64; Y float64 }",
|
||||
"func add(a, b int) int { return a + b }",
|
||||
"import \"fmt\"",
|
||||
"result := make([]int, 0, 10)",
|
||||
"switch val { case 1: return \"one\" default: return \"other\" }",
|
||||
"go func() { ch <- result }()",
|
||||
"defer file.Close()",
|
||||
]
|
||||
}
|
||||
|
||||
fn get_snippets(&self) -> Vec<&'static str> {
|
||||
match self.language.as_str() {
|
||||
"rust" => Self::rust_snippets(),
|
||||
"python" => Self::python_snippets(),
|
||||
"javascript" | "js" => Self::javascript_snippets(),
|
||||
"go" => Self::go_snippets(),
|
||||
_ => Self::rust_snippets(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextGenerator for CodeSyntaxGenerator {
|
||||
fn generate(
|
||||
&mut self,
|
||||
_filter: &CharFilter,
|
||||
_focused: Option<char>,
|
||||
word_count: usize,
|
||||
) -> String {
|
||||
let snippets = self.get_snippets();
|
||||
let mut result = Vec::new();
|
||||
let target_words = word_count;
|
||||
let mut current_words = 0;
|
||||
|
||||
while current_words < target_words {
|
||||
let idx = self.rng.gen_range(0..snippets.len());
|
||||
let snippet = snippets[idx];
|
||||
current_words += snippet.split_whitespace().count();
|
||||
result.push(snippet);
|
||||
}
|
||||
|
||||
result.join(" ")
|
||||
}
|
||||
}
|
||||
41
src/generator/github_code.rs
Normal file
41
src/generator/github_code.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use crate::engine::filter::CharFilter;
|
||||
use crate::generator::TextGenerator;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct GitHubCodeGenerator {
|
||||
cached_snippets: Vec<String>,
|
||||
current_idx: usize,
|
||||
}
|
||||
|
||||
impl GitHubCodeGenerator {
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cached_snippets: Vec::new(),
|
||||
current_idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GitHubCodeGenerator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextGenerator for GitHubCodeGenerator {
|
||||
fn generate(
|
||||
&mut self,
|
||||
_filter: &CharFilter,
|
||||
_focused: Option<char>,
|
||||
_word_count: usize,
|
||||
) -> String {
|
||||
if self.cached_snippets.is_empty() {
|
||||
return "// GitHub code fetching not yet configured. Use settings to add a repository."
|
||||
.to_string();
|
||||
}
|
||||
let snippet = self.cached_snippets[self.current_idx % self.cached_snippets.len()].clone();
|
||||
self.current_idx += 1;
|
||||
snippet
|
||||
}
|
||||
}
|
||||
12
src/generator/mod.rs
Normal file
12
src/generator/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod code_syntax;
|
||||
pub mod github_code;
|
||||
pub mod passage;
|
||||
pub mod phonetic;
|
||||
pub mod transition_table;
|
||||
|
||||
use crate::engine::filter::CharFilter;
|
||||
|
||||
pub trait TextGenerator {
|
||||
fn generate(&mut self, filter: &CharFilter, focused: Option<char>, word_count: usize)
|
||||
-> String;
|
||||
}
|
||||
49
src/generator/passage.rs
Normal file
49
src/generator/passage.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::engine::filter::CharFilter;
|
||||
use crate::generator::TextGenerator;
|
||||
|
||||
const PASSAGES: &[&str] = &[
|
||||
"the quick brown fox jumps over the lazy dog and then runs across the field while the sun sets behind the distant hills",
|
||||
"it was the best of times it was the worst of times it was the age of wisdom it was the age of foolishness",
|
||||
"in the beginning there was nothing but darkness and then the light appeared slowly spreading across the vast empty space",
|
||||
"she walked along the narrow path through the forest listening to the birds singing in the trees above her head",
|
||||
"the old man sat on the bench watching the children play in the park while the autumn leaves fell softly around him",
|
||||
"there is nothing either good or bad but thinking makes it so for the mind is its own place and in itself can make a heaven of hell",
|
||||
"to be or not to be that is the question whether it is nobler in the mind to suffer the slings and arrows of outrageous fortune",
|
||||
"all that glitters is not gold and not all those who wander are lost for the old that is strong does not wither",
|
||||
"the river flowed quietly through the green valley and the mountains rose high on either side covered with trees and snow",
|
||||
"a long time ago in a land far away there lived a wise king who ruled his people with kindness and justice",
|
||||
"the rain fell steadily on the roof making a soft drumming sound that filled the room and made everything feel calm",
|
||||
"she opened the door and stepped outside into the cool morning air breathing deeply as the first light of dawn appeared",
|
||||
"he picked up the book and began to read turning the pages slowly as the story drew him deeper and deeper into its world",
|
||||
"the stars shone brightly in the clear night sky and the moon cast a silver light over the sleeping town below",
|
||||
"they gathered around the fire telling stories and laughing while the wind howled outside and the snow piled up against the door",
|
||||
];
|
||||
|
||||
pub struct PassageGenerator {
|
||||
current_idx: usize,
|
||||
}
|
||||
|
||||
impl PassageGenerator {
|
||||
pub fn new() -> Self {
|
||||
Self { current_idx: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PassageGenerator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextGenerator for PassageGenerator {
|
||||
fn generate(
|
||||
&mut self,
|
||||
_filter: &CharFilter,
|
||||
_focused: Option<char>,
|
||||
_word_count: usize,
|
||||
) -> String {
|
||||
let passage = PASSAGES[self.current_idx % PASSAGES.len()];
|
||||
self.current_idx += 1;
|
||||
passage.to_string()
|
||||
}
|
||||
}
|
||||
169
src/generator/phonetic.rs
Normal file
169
src/generator/phonetic.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::engine::filter::CharFilter;
|
||||
use crate::generator::transition_table::TransitionTable;
|
||||
use crate::generator::TextGenerator;
|
||||
|
||||
pub struct PhoneticGenerator {
|
||||
table: TransitionTable,
|
||||
rng: SmallRng,
|
||||
}
|
||||
|
||||
impl PhoneticGenerator {
|
||||
pub fn new(table: TransitionTable, rng: SmallRng) -> Self {
|
||||
Self { table, rng }
|
||||
}
|
||||
|
||||
fn pick_weighted_from(
|
||||
rng: &mut SmallRng,
|
||||
options: &[(char, f64)],
|
||||
filter: &CharFilter,
|
||||
) -> Option<char> {
|
||||
let filtered: Vec<(char, f64)> = options
|
||||
.iter()
|
||||
.filter(|(ch, _)| filter.is_allowed(*ch))
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
if filtered.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let total: f64 = filtered.iter().map(|(_, w)| w).sum();
|
||||
if total <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut roll = rng.gen_range(0.0..total);
|
||||
for (ch, weight) in &filtered {
|
||||
roll -= weight;
|
||||
if roll <= 0.0 {
|
||||
return Some(*ch);
|
||||
}
|
||||
}
|
||||
|
||||
Some(filtered.last().unwrap().0)
|
||||
}
|
||||
|
||||
fn generate_word(&mut self, filter: &CharFilter, focused: Option<char>) -> String {
|
||||
let min_len = 3;
|
||||
let max_len = 10;
|
||||
let mut word = String::new();
|
||||
|
||||
let start_char = if let Some(focus) = focused {
|
||||
if self.rng.gen_bool(0.4) {
|
||||
let probs = self.table.get_next_probs(' ', focus).cloned();
|
||||
if let Some(probs) = probs {
|
||||
let filtered: Vec<(char, f64)> = probs
|
||||
.iter()
|
||||
.filter(|(ch, _)| filter.is_allowed(*ch))
|
||||
.copied()
|
||||
.collect();
|
||||
if !filtered.is_empty() {
|
||||
word.push(focus);
|
||||
Self::pick_weighted_from(&mut self.rng, &filtered, filter)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(focus)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if word.is_empty() {
|
||||
let starters: Vec<(char, f64)> = filter
|
||||
.allowed
|
||||
.iter()
|
||||
.map(|&ch| {
|
||||
(
|
||||
ch,
|
||||
if ch == 'e' || ch == 't' || ch == 'a' {
|
||||
3.0
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some(ch) = Self::pick_weighted_from(&mut self.rng, &starters, filter) {
|
||||
word.push(ch);
|
||||
} else {
|
||||
return "the".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ch) = start_char {
|
||||
word.push(ch);
|
||||
}
|
||||
|
||||
while word.len() < max_len {
|
||||
let chars: Vec<char> = word.chars().collect();
|
||||
let len = chars.len();
|
||||
|
||||
let (prev, curr) = if len >= 2 {
|
||||
(chars[len - 2], chars[len - 1])
|
||||
} else {
|
||||
(' ', chars[len - 1])
|
||||
};
|
||||
|
||||
let space_prob = 1.3f64.powi(word.len() as i32 - min_len as i32);
|
||||
if word.len() >= min_len
|
||||
&& self
|
||||
.rng
|
||||
.gen_bool((space_prob / (space_prob + 5.0)).min(0.8))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
let probs = self.table.get_next_probs(prev, curr).cloned();
|
||||
if let Some(probs) = probs {
|
||||
if let Some(next) = Self::pick_weighted_from(&mut self.rng, &probs, filter) {
|
||||
word.push(next);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let vowels: Vec<(char, f64)> = ['a', 'e', 'i', 'o', 'u']
|
||||
.iter()
|
||||
.filter(|&&v| filter.is_allowed(v))
|
||||
.map(|&v| (v, 1.0))
|
||||
.collect();
|
||||
if let Some(v) = Self::pick_weighted_from(&mut self.rng, &vowels, filter) {
|
||||
word.push(v);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if word.is_empty() {
|
||||
"the".to_string()
|
||||
} else {
|
||||
word
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextGenerator for PhoneticGenerator {
|
||||
fn generate(
|
||||
&mut self,
|
||||
filter: &CharFilter,
|
||||
focused: Option<char>,
|
||||
word_count: usize,
|
||||
) -> String {
|
||||
let mut words: Vec<String> = Vec::new();
|
||||
|
||||
for _ in 0..word_count {
|
||||
words.push(self.generate_word(filter, focused));
|
||||
}
|
||||
|
||||
words.join(" ")
|
||||
}
|
||||
}
|
||||
98
src/generator/transition_table.rs
Normal file
98
src/generator/transition_table.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TransitionTable {
|
||||
pub transitions: HashMap<(char, char), Vec<(char, f64)>>,
|
||||
}
|
||||
|
||||
impl TransitionTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
transitions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, prev: char, curr: char, next: char, weight: f64) {
|
||||
self.transitions
|
||||
.entry((prev, curr))
|
||||
.or_default()
|
||||
.push((next, weight));
|
||||
}
|
||||
|
||||
pub fn get_next_probs(&self, prev: char, curr: char) -> Option<&Vec<(char, f64)>> {
|
||||
self.transitions.get(&(prev, curr))
|
||||
}
|
||||
|
||||
pub fn build_english() -> Self {
|
||||
let mut table = Self::new();
|
||||
|
||||
let common_patterns: &[(&str, f64)] = &[
|
||||
("the", 10.0), ("and", 8.0), ("ing", 7.0), ("tion", 6.0), ("ent", 5.0),
|
||||
("ion", 5.0), ("her", 4.0), ("for", 4.0), ("are", 4.0), ("his", 4.0),
|
||||
("hat", 3.0), ("tha", 3.0), ("ere", 3.0), ("ate", 3.0), ("ith", 3.0),
|
||||
("ver", 3.0), ("all", 3.0), ("not", 3.0), ("ess", 3.0), ("est", 3.0),
|
||||
("rea", 3.0), ("sta", 3.0), ("ted", 3.0), ("com", 3.0), ("con", 3.0),
|
||||
("oun", 2.5), ("pro", 2.5), ("oth", 2.5), ("igh", 2.5), ("ore", 2.5),
|
||||
("our", 2.5), ("ine", 2.5), ("ove", 2.5), ("ome", 2.5), ("use", 2.5),
|
||||
("ble", 2.0), ("ful", 2.0), ("ous", 2.0), ("str", 2.0), ("tri", 2.0),
|
||||
("ght", 2.0), ("whi", 2.0), ("who", 2.0), ("hen", 2.0), ("ter", 2.0),
|
||||
("man", 2.0), ("men", 2.0), ("ner", 2.0), ("per", 2.0), ("pre", 2.0),
|
||||
("ran", 2.0), ("lin", 2.0), ("kin", 2.0), ("din", 2.0), ("sin", 2.0),
|
||||
("out", 2.0), ("ind", 2.0), ("ith", 2.0), ("ber", 2.0), ("der", 2.0),
|
||||
("end", 2.0), ("hin", 2.0), ("old", 2.0), ("ear", 2.0), ("ain", 2.0),
|
||||
("ant", 2.0), ("urn", 2.0), ("ell", 2.0), ("ill", 2.0), ("ade", 2.0),
|
||||
("igh", 2.0), ("ong", 2.0), ("ung", 2.0), ("ast", 2.0), ("ist", 2.0),
|
||||
("ust", 2.0), ("ost", 2.0), ("ard", 2.0), ("ord", 2.0), ("art", 2.0),
|
||||
("ort", 2.0), ("ect", 2.0), ("act", 2.0), ("ack", 2.0), ("ick", 2.0),
|
||||
("ock", 2.0), ("uck", 2.0), ("ash", 2.0), ("ish", 2.0), ("ush", 2.0),
|
||||
("anc", 1.5), ("enc", 1.5), ("inc", 1.5), ("onc", 1.5), ("unc", 1.5),
|
||||
("unt", 1.5), ("int", 1.5), ("ont", 1.5), ("ent", 1.5), ("ment", 1.5),
|
||||
("ness", 1.5), ("less", 1.5), ("able", 1.5), ("ible", 1.5), ("ting", 1.5),
|
||||
("ring", 1.5), ("sing", 1.5), ("king", 1.5), ("ning", 1.5), ("ling", 1.5),
|
||||
("wing", 1.5), ("ding", 1.5), ("ping", 1.5), ("ging", 1.5), ("ving", 1.5),
|
||||
("bing", 1.5), ("ming", 1.5), ("fing", 1.0), ("hing", 1.0), ("cing", 1.0),
|
||||
];
|
||||
|
||||
for &(pattern, weight) in common_patterns {
|
||||
let chars: Vec<char> = pattern.chars().collect();
|
||||
for window in chars.windows(3) {
|
||||
table.add(window[0], window[1], window[2], weight);
|
||||
}
|
||||
}
|
||||
|
||||
let vowels = ['a', 'e', 'i', 'o', 'u'];
|
||||
let consonants = [
|
||||
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v',
|
||||
'w', 'x', 'y', 'z',
|
||||
];
|
||||
|
||||
for &c in &consonants {
|
||||
for &v in &vowels {
|
||||
table.add(' ', c, v, 1.0);
|
||||
table.add(v, c, 'e', 0.5);
|
||||
for &v2 in &vowels {
|
||||
table.add(c, v, v2.to_ascii_lowercase(), 0.3);
|
||||
}
|
||||
for &c2 in &consonants {
|
||||
table.add(v, c, c2, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for &v in &vowels {
|
||||
for &c in &consonants {
|
||||
table.add(' ', v, c, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TransitionTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user