From ef5c2e269e1bba792f10ab16e072467dbb64e265 Mon Sep 17 00:00:00 2001 From: Fomegne Brady Date: Sun, 30 Apr 2023 08:22:18 +0100 Subject: [PATCH] feat(clafrica): Implement a cursor --- clafrica/data/sample.txt | 14 ++++++ clafrica/src/lib.rs | 105 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 clafrica/data/sample.txt diff --git a/clafrica/data/sample.txt b/clafrica/data/sample.txt new file mode 100644 index 0000000..f5fd622 --- /dev/null +++ b/clafrica/data/sample.txt @@ -0,0 +1,14 @@ +2a_ á̠ +2aa áá +2af_ ɑ̠́ +2aff ɑ́ɑ́ +2ai_ έ̠ +2ee éé +2ia íá +2iaf íɑ́ +2ie íé +2ii íí +2oo óó +2ua úá +2uaf úɑ́ +2uuaf ʉ́ɑ́ \ No newline at end of file diff --git a/clafrica/src/lib.rs b/clafrica/src/lib.rs index 5ee888c..13cf152 100644 --- a/clafrica/src/lib.rs +++ b/clafrica/src/lib.rs @@ -1,8 +1,36 @@ use clafrica_lib::bst; +use std::{fmt, rc::Rc}; struct Cursor<'a> { - stack: Vec<&'a str>, - node: &'a bst::Node, + stack: Vec>, + root: Rc, + on_hit: &'a dyn Fn(String), + on_undo: &'a dyn Fn(String), +} + +impl Cursor<'_> { + fn hit(&mut self, character: char) { + let root = Rc::clone(&self.root); + let node = self + .stack + .last() + .unwrap_or(&root) + .goto(character) + .unwrap_or(root); + + node.take().map(self.on_hit); + self.stack.push(node); + } + + fn undo(&mut self) { + let node = self.stack.pop(); + + node.and_then(|node| node.take().map(self.on_undo)); + } + + fn to_path(&self) -> Vec> { + self.stack.iter().map(|node| node.take()).collect() + } } fn run() { @@ -10,4 +38,75 @@ fn run() { } #[cfg(test)] -mod tests {} +mod tests { + #[test] + fn test_cursor() { + use crate::Cursor; + use clafrica_lib::utils; + use std::rc::Rc; + + macro_rules! hit { + ( $cursor:ident $( $c:expr ),* ) => ( + $( $cursor.hit($c); )* + ); + } + + macro_rules! undo { + ( $cursor:ident $occ:expr ) => { + (0..$occ).into_iter().for_each(|_| $cursor.undo()); + }; + } + + let data = utils::load_data("data/sample.txt").expect("Failed to load the clafrica code"); + let root = utils::build_map( + data.iter() + .map(|e| [e[0].as_str(), e[1].as_str()]) + .collect(), + ); + + let mut cursor = Cursor { + stack: Vec::with_capacity(10), + root: Rc::new(root), + on_hit: &|_| (), + on_undo: &|_| (), + }; + + hit!(cursor '2', 'i', 'a', 'f'); + assert_eq!( + cursor.to_path(), + vec![None, None, Some("íá".to_owned()), Some("íɑ́".to_owned())] + ); + + undo!(cursor 1); + assert_eq!(cursor.to_path(), vec![None, None, Some("íá".to_owned())]); + + undo!(cursor 1); + cursor.hit('e'); + assert_eq!(cursor.to_path(), vec![None, None, Some("íé".to_owned())]); + + undo!(cursor 2); + hit!(cursor 'o', 'o'); + assert_eq!(cursor.to_path(), vec![None, None, Some("óó".to_owned())]); + + undo!(cursor 3); + assert_eq!(cursor.to_path(), vec![]); + + hit!( + cursor + '2', 'i', 'e', '2', 'a', '_', '2', 'a', 'a', + '2', 'a', 'i', '_', '2', 'a', 'f', 'f', '2', + 'o', 'o', '2', 'i', 'a', 'f', '2', 'i', 'a', + '2', 'u', 'a', '2', 'e', 'e', '2', 'u', 'u', + 'a', 'f', '2', 'a', 'f', '_', '2', 'i', 'i', + '2', 'u', 'a', 'f', '2', 'i', 'a', 'f' + ); + assert_eq!( + cursor + .to_path() + .into_iter() + .filter_map(|e| e) + .collect::>(), + vec!["íé", "áá", "ɑ́ɑ́", "íá", "íɑ́", "úá", "ʉ́ɑ́", "íí", "íá", "íɑ́"] + ); + } +}