clafrica_lib/
lib.rs

1//! # Clafrica Lib
2//!
3//! `clafrica-lib` is a collection of utilities to make handling
4//!  of clafrica code more convenient.
5//!
6//! Example
7//! ```
8//! use clafrica_lib::{text_buffer, utils};
9//!
10//! // Build a TextBuffer
11//! let root = text_buffer::Node::default();
12//! root.insert(vec!['a', 'f'], "ɑ".to_owned());
13//! root.insert(vec!['a', 'f', '1'], "ɑ̀".to_owned());
14//!
15//! // Bulk insert of data in the TextBuffer
16//! let data = vec![["af11", "ɑ̀ɑ̀"], ["?.", "ʔ"]];
17//! utils::build_map(data);
18//!
19//! // or directly from a file
20//! let data = utils::load_data("data/sample.txt")
21//!                   .expect("Failed to load the clafrica code file");
22//! let data = data.iter()
23//!                .map(|e| [e[0].as_str(), e[1].as_str()])
24//!                .collect();
25//!
26//! utils::build_map(data);
27//!
28//! // Traverse the tree
29//! let node = root.goto('a').and_then(|e| e.goto('f'));
30//! assert_eq!(node.unwrap().take(), Some("ɑ".to_owned()));
31//!
32//! // Test our cursor
33//! let mut cursor = text_buffer::Cursor::new(root, 10);
34//! let code = "af1";
35//! code.chars().for_each(|c| {cursor.hit(c);});
36//! assert_eq!(cursor.state(), (Some("ɑ̀".to_owned()), 3, '1'));
37//! assert_eq!(cursor.undo(), Some("ɑ̀".to_owned()));
38//! ```
39
40pub mod text_buffer {
41    use std::collections::{HashMap, VecDeque};
42    use std::{cell::RefCell, rc::Rc};
43
44    #[derive(Debug)]
45    pub struct Node {
46        neighbors: RefCell<HashMap<char, Rc<Node>>>,
47        pub depth: usize,
48        pub key: char,
49        value: RefCell<Option<String>>,
50    }
51
52    impl Default for Node {
53        fn default() -> Self {
54            Self::new('\0', 0)
55        }
56    }
57
58    impl Node {
59        /// Initialize a new node.
60        pub fn new(key: char, depth: usize) -> Self {
61            Self {
62                neighbors: HashMap::new().into(),
63                depth,
64                key,
65                value: None.into(),
66            }
67        }
68
69        /// Insert a sequence in the TextBuffer.
70        pub fn insert(&self, sequence: Vec<char>, value: String) {
71            if let Some(character) = sequence.clone().first() {
72                let new_node = Rc::new(Self::new(*character, self.depth + 1));
73
74                self.neighbors
75                    .borrow()
76                    .get(character)
77                    .unwrap_or(&new_node)
78                    .insert(sequence.into_iter().skip(1).collect(), value);
79
80                self.neighbors
81                    .borrow_mut()
82                    .entry(*character)
83                    .or_insert(new_node);
84            } else {
85                *self.value.borrow_mut() = Some(value);
86            };
87        }
88
89        /// Move from one node to another
90        pub fn goto(&self, character: char) -> Option<Rc<Self>> {
91            self.neighbors.borrow().get(&character).map(Rc::clone)
92        }
93
94        /// Extract the value from a node .
95        pub fn take(&self) -> Option<String> {
96            self.value.borrow().as_ref().map(ToOwned::to_owned)
97        }
98
99        /// Return true is the node is at the initial depth
100        pub fn is_root(&self) -> bool {
101            self.depth == 0
102        }
103    }
104
105    #[derive(Clone)]
106    pub struct Cursor {
107        buffer: VecDeque<Rc<Node>>,
108        root: Rc<Node>,
109    }
110
111    impl Cursor {
112        /// Initialize the cursor
113        pub fn new(root: Node, capacity: usize) -> Self {
114            Self {
115                buffer: VecDeque::with_capacity(capacity),
116                root: Rc::new(root),
117            }
118        }
119        /// Enter a character and return his corresponding out
120        pub fn hit(&mut self, character: char) -> Option<String> {
121            let node = self
122                .buffer
123                .iter()
124                .last()
125                .unwrap_or(&Rc::new(Node::new('\0', 0)))
126                .goto(character)
127                .or_else(|| {
128                    // We end the current sequence
129                    self.insert(Rc::new(Node::new('\0', 0)));
130                    // and start a new one
131                    self.root.goto(character)
132                })
133                .unwrap_or(Rc::new(Node::new(character, 0)));
134
135            let out = node.take();
136            self.insert(node);
137
138            out
139        }
140
141        fn insert(&mut self, node: Rc<Node>) {
142            if self.buffer.len() == self.buffer.capacity() {
143                self.buffer.pop_front();
144            }
145            self.buffer.push_back(node);
146        }
147
148        /// Remove the previous enter and return his corresponding out
149        pub fn undo(&mut self) -> Option<String> {
150            let node = self.buffer.pop_back();
151
152            node.and_then(|node| {
153                if node.key == '\0' {
154                    self.undo()
155                } else {
156                    node.take()
157                }
158            })
159        }
160
161        /// Return the current state of the cursor
162        pub fn state(&self) -> (Option<String>, usize, char) {
163            self.buffer
164                .iter()
165                .last()
166                .map(|n| (n.take(), n.depth, n.key))
167                .unwrap_or_default()
168        }
169
170        /// Return the current sequence in the cursor
171        pub fn to_sequence(&self) -> Vec<char> {
172            self.buffer.iter().map(|node| node.key).collect()
173        }
174
175        /// Clear the memory of the cursor
176        pub fn clear(&mut self) {
177            self.buffer.clear();
178        }
179
180        /// Verify if the cursor is empty
181        pub fn is_empty(&self) -> bool {
182            return self.buffer.iter().filter(|c| c.key != '\0').count() == 0;
183        }
184    }
185}
186
187pub mod utils {
188    use crate::text_buffer;
189    use std::{fs, io};
190
191    /// Load the clafrica code from a plain text file.
192    pub fn load_data(file_path: &str) -> Result<Vec<Vec<String>>, io::Error> {
193        let data = fs::read_to_string(file_path)?;
194        let data = data
195            .trim()
196            .split('\n')
197            .map(|line| {
198                line.split_whitespace()
199                    .filter(|token| !token.is_empty())
200                    .map(ToOwned::to_owned)
201                    .collect()
202            })
203            .collect();
204        Ok(data)
205    }
206
207    /// Build a TextBuffer from the clafrica code.
208    pub fn build_map(data: Vec<[&str; 2]>) -> text_buffer::Node {
209        let root = text_buffer::Node::default();
210
211        data.iter().for_each(|e| {
212            root.insert(e[0].chars().collect(), e[1].to_owned());
213        });
214
215        root
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    #[test]
222    fn test_load_data() {
223        use crate::utils;
224
225        utils::load_data("data/sample.txt")
226            .unwrap()
227            .iter()
228            .for_each(|pair| assert_eq!(pair.len(), 2));
229    }
230
231    #[test]
232    fn test_build_map() {
233        use crate::utils;
234
235        let data = vec![["af11", "ɑ̀ɑ̀"], ["?.", "ʔ"]];
236        utils::build_map(data);
237
238        let data = utils::load_data("data/sample.txt").unwrap();
239        utils::build_map(
240            data.iter()
241                .map(|e| [e[0].as_str(), e[1].as_str()])
242                .collect(),
243        );
244    }
245
246    #[test]
247    fn test_node() {
248        use crate::text_buffer;
249
250        let root = text_buffer::Node::default();
251
252        assert!(root.is_root());
253
254        root.insert(vec!['a', 'f'], "ɑ".to_owned());
255        root.insert(vec!['a', 'f', '1'], "ɑ̀".to_owned());
256
257        assert!(root.goto('a').is_some());
258        assert!(!root.goto('a').unwrap().is_root());
259        assert!(root.goto('b').is_none());
260
261        let node = root.goto('a').and_then(|e| e.goto('f'));
262        assert_eq!(node.as_ref().unwrap().take(), Some("ɑ".to_owned()));
263
264        let node = node.and_then(|e| e.goto('1'));
265        assert_eq!(node.as_ref().unwrap().take(), Some("ɑ̀".to_owned()));
266    }
267
268    #[test]
269    fn test_cursor() {
270        use crate::text_buffer;
271        use crate::utils;
272
273        macro_rules! hit {
274            ( $cursor:ident $( $c:expr ),* ) => (
275                $( $cursor.hit($c); )*
276            );
277        }
278
279        macro_rules! undo {
280            ( $cursor:ident $occ:expr ) => {
281                (0..$occ).into_iter().for_each(|_| {
282                    $cursor.undo();
283                });
284            };
285        }
286
287        let data = utils::load_data("data/sample.txt").unwrap();
288        let root = utils::build_map(
289            data.iter()
290                .map(|e| [e[0].as_str(), e[1].as_str()])
291                .collect(),
292        );
293
294        let mut cursor = text_buffer::Cursor::new(root, 8);
295
296        assert_eq!(cursor.state(), (None, 0, '\0'));
297
298        hit!(cursor '2', 'i', 'a', 'f');
299        assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'a', 'f']);
300
301        assert_eq!(cursor.state(), (Some("íɑ́".to_owned()), 4, 'f'));
302
303        undo!(cursor 1);
304        assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'a']);
305
306        undo!(cursor 1);
307        cursor.hit('e');
308        assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'e']);
309
310        undo!(cursor 2);
311        hit!(cursor 'o', 'o');
312        assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'o', 'o']);
313
314        undo!(cursor 3);
315        assert_eq!(cursor.to_sequence(), vec!['\0']);
316
317        hit!(cursor '2', '2', 'u', 'a');
318        assert_eq!(
319            cursor.to_sequence(),
320            vec!['\0', '\0', '2', '\0', '2', 'u', 'a']
321        );
322        undo!(cursor 4);
323        assert_eq!(cursor.to_sequence(), vec!['\0', '\0']);
324        assert!(cursor.is_empty());
325        undo!(cursor 1);
326        assert_eq!(cursor.to_sequence(), vec![]);
327
328        hit!(
329            cursor
330            'a', 'a', '2', 'a', 'e', 'a', '2', 'f', 'a',
331            '2', '2', 'x', 'x', '2', 'i', 'a', '2', '2', '_', 'f',
332            '2', 'a', '2', 'a', '_'
333        );
334        assert_eq!(
335            cursor.to_sequence(),
336            vec!['f', '\0', '2', 'a', '\0', '2', 'a', '_']
337        );
338
339        cursor.clear();
340        assert_eq!(cursor.to_sequence(), vec![]);
341    }
342}