diff --git a/Cargo.toml b/Cargo.toml index d58a929..d5a5d28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 1fdb8b5..4e1c850 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..c2fb25d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; +} + +pub enum Symbol { + Terminal(String), + NonTerminal(String), +} + +impl Purrchance for Symbol { + fn eval(&self, g: &Grammar) -> Option { + match self { + Symbol::Terminal(s) => Some(s.to_string()), + Symbol::NonTerminal(label) => g.0.get(label)?.eval(g), + } + } +} + +pub struct Expr(Vec); + +impl Purrchance for Expr { + fn eval(&self, g: &Grammar) -> Option { + Some(self.0.iter().map(|sym| sym.eval(g)).collect::>>()?.join("")) + } +} + +pub struct List(Vec); + +impl Purrchance for List { + fn eval(&self, g: &Grammar) -> Option { + self.0.choose(&mut thread_rng())?.eval(g) + } +} + +pub struct Grammar(HashMap); + +#[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); } }