Implement Perchance grammar evaluation with basic lists

main
xenofem 2020-06-18 21:00:42 -04:00
parent 9c484e6bc1
commit 8a4963d5e9
3 changed files with 107 additions and 6 deletions

View File

@ -8,3 +8,4 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.7.3"

View File

@ -6,7 +6,7 @@ Purrchance is an unofficial Rust implementation of the
## Features I might implement eventually
- [ ] Parsing grammars from text format
- [ ] Basic lists
- [x] Basic lists
- [ ] Probability weights
- [ ] Single-item lists
- [ ] Escape sequences

View File

@ -1,7 +1,107 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
extern crate rand;
use rand::{seq::SliceRandom, thread_rng};
use std::collections::HashMap;
pub trait Purrchance {
fn eval(&self, g: &Grammar) -> Option<String>;
}
pub enum Symbol {
Terminal(String),
NonTerminal(String),
}
impl Purrchance for Symbol {
fn eval(&self, g: &Grammar) -> Option<String> {
match self {
Symbol::Terminal(s) => Some(s.to_string()),
Symbol::NonTerminal(label) => g.0.get(label)?.eval(g),
}
}
}
pub struct Expr(Vec<Symbol>);
impl Purrchance for Expr {
fn eval(&self, g: &Grammar) -> Option<String> {
Some(self.0.iter().map(|sym| sym.eval(g)).collect::<Option<Vec<String>>>()?.join(""))
}
}
pub struct List(Vec<Expr>);
impl Purrchance for List {
fn eval(&self, g: &Grammar) -> Option<String> {
self.0.choose(&mut thread_rng())?.eval(g)
}
}
pub struct Grammar(HashMap<String,List>);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eval_terminal() {
let sym = Symbol::Terminal("hello world".to_string());
let g = Grammar(HashMap::new());
assert_eq!(sym.eval(&g), Some("hello world".to_string()));
}
#[test]
fn eval_terminals_expr() {
let sym1 = Symbol::Terminal("hell".to_string());
let sym2 = Symbol::Terminal("o world".to_string());
let expr = Expr(vec![sym1, sym2]);
let g = Grammar(HashMap::new());
assert_eq!(expr.eval(&g), Some("hello world".to_string()));
}
#[test]
fn eval_single_terminals_expr_list() {
let sym1 = Symbol::Terminal("hell".to_string());
let sym2 = Symbol::Terminal("o world".to_string());
let expr = Expr(vec![sym1, sym2]);
let list = List(vec![expr]);
let g = Grammar(HashMap::new());
assert_eq!(list.eval(&g), Some("hello world".to_string()));
}
#[test]
fn eval_multiple_terminals_expr_list() {
let sym1 = Symbol::Terminal("hello".to_string());
let sym2 = Symbol::Terminal("goodbye".to_string());
let list = List(vec![Expr(vec![sym1]), Expr(vec![sym2])]);
let g = Grammar(HashMap::new());
assert!(vec![Some("hello".to_string()), Some("goodbye".to_string())].contains(&list.eval(&g)));
}
#[test]
fn eval_empty_list() {
let list = List(vec![]);
let g = Grammar(HashMap::new());
assert_eq!(list.eval(&g), None);
}
#[test]
fn eval_valid_nonterminal() {
let term = Symbol::Terminal("hello world".to_string());
let list = List(vec![Expr(vec![term])]);
let mut g = Grammar(HashMap::new());
g.0.insert("output".to_string(), list);
let nt = Symbol::NonTerminal("output".to_string());
assert_eq!(nt.eval(&g), Some("hello world".to_string()));
}
#[test]
fn eval_missing_nonterminal() {
let term = Symbol::Terminal("hello world".to_string());
let list = List(vec![Expr(vec![term])]);
let mut g = Grammar(HashMap::new());
g.0.insert("output".to_string(), list);
let nt = Symbol::NonTerminal("missing".to_string());
assert_eq!(nt.eval(&g), None);
}
}