//! A datastructure for holding key map entries use std::fmt::Debug; #[derive(Debug, Clone)] struct Node { label: u8, children: Vec>, value: Option, } impl Node { fn new(label: u8) -> Self { Self { label, children: Vec::new(), value: None, } } fn insert(&mut self, key: &[u8], value: Value) { if key.is_empty() { // We've reached the leaf self.value = Some(value); return; } match self .children .binary_search_by(|node| node.label.cmp(&key[0])) { Ok(idx) => { self.children[idx].insert(&key[1..], value); }, Err(idx) => { self.children.insert(idx, Node::new(key[0])); self.children[idx].insert(&key[1..], value); }, } } fn lookup(&self, key: &[u8], depth: usize, maybe_more: bool) -> NodeFind<&Value> { if key.is_empty() { // We've matched the maximum extent of the input key. if self.children.is_empty() { match self.value.as_ref() { Some(value) => { // An unambiguous match for the entire input return NodeFind::Exact(depth, value); }, None => panic!("Node has no children and no value!?"), } } return match self.value.as_ref() { Some(value) => { if maybe_more { NodeFind::AmbiguousMatch(depth, value) } else { NodeFind::Exact(depth, value) } }, None => NodeFind::AmbiguousBackTrack, }; } match self .children .binary_search_by(|node| node.label.cmp(&key[0])) { Ok(idx) => { match self.children[idx].lookup(&key[1..], depth + 1, maybe_more) { NodeFind::AmbiguousBackTrack => { // The child didn't have an exact match, so check // whether we do match self.value.as_ref() { Some(value) => { // We do! If we're expecting more, let's return an AmbiguousMatch, // otherwise, let's treat this as an Exact match // // see the "lookup_with_multiple_ambiguous_matches_" // test cases in this file for an example if maybe_more { NodeFind::AmbiguousMatch(depth, value) } else { NodeFind::Exact(depth, value) } }, None => NodeFind::AmbiguousBackTrack, } }, result => result, } }, Err(_) => { if depth == 0 { NodeFind::None } else { match self.value.as_ref() { Some(value) => NodeFind::Exact(depth, value), None => NodeFind::AmbiguousBackTrack, } } }, } } } /// Internal lookup disposition enum NodeFind { /// No possible matches None, /// Found an exact match. (match-len, value) Exact(usize, Value), /// Didn't find an exact match at the full extent of the key, /// so ask the upper layer to back track to find a partial. AmbiguousBackTrack, /// After backtracking, found a prefix match, but we know that /// there might be a more specific match given more data. (match-len, /// value). AmbiguousMatch(usize, Value), } /// Holds the result of a lookup operation #[derive(Debug, Clone, PartialEq, Eq)] pub enum Found { /// There are definitively no possible matches None, /// We found an unambiguous match. /// The data is (length-of-match, value) Exact(usize, Value), /// We found a match, but there are other longer matches /// that are possible. Ideally we'd accumulate more data /// to know for sure. /// The data is (length-of-shortest-match, value) Ambiguous(usize, Value), /// If we had more data, we might match something NeedData, } /// The `KeyMap` struct is intended to hold the text sequences /// generated by unix terminal programs. Those sequences have /// overlapping/ambiguous meaning which requires having more /// data to correctly interpret the sequence. /// The `lookup` operation returns an enum describing the /// confidence of a match rather than a simple map lookup. #[derive(Debug, Clone)] pub struct KeyMap { root: Node, } impl Default for KeyMap { fn default() -> Self { Self::new() } } impl KeyMap { pub fn new() -> Self { Self { root: Node::new(0) } } /// Insert a value into the keymap pub fn insert>(&mut self, key: K, value: Value) { self.root.insert(key.as_ref(), value) } /// Perform a lookup for `key`. /// `key` can be a string consisting of a sequence of bytes. /// The `lookup` operation considers the prefix of `key` and searches /// for a match. /// /// If `Found::None` is returned then the prefix for key has no matching /// keymap entry. /// /// If `Found::Exact` is returned then the returned value informs the caller /// of the length of the key that was matched; the remainder of the key /// was not considered and is something that should be considered again /// in a subsequent lookup operation. /// /// If `Found::Ambiguous` is returned then the key matched a valid /// entry (which is returned as the value), but there is at least one /// other entry that could match if more data were available. If the caller /// knows that no more data is available immediately then it may be valid /// to treat this result as equivalent to `Found::Exact`. The intended /// use for this variant is to handle the case where a sequence straddles /// a buffer boundary (eg: fixed size buffer receives a partial sequence, /// and the remainder is immediately available on the next read) without /// misinterpreting the read data. /// /// If `Found::NeedData` is returned it indicates that `key` is too short /// to determine a match. The purpose is similar to the `Found::Ambiguous` /// case; if the caller knows that no more data is available this can be /// treated as `Found::None`, but otherwise it would be best to read more /// data from the stream and retry with a longer input. pub fn lookup>(&self, key: S, maybe_more: bool) -> Found { match self.root.lookup(key.as_ref(), 0, maybe_more) { NodeFind::None => Found::None, NodeFind::AmbiguousBackTrack => Found::NeedData, NodeFind::Exact(depth, value) => Found::Exact(depth, value.clone()), NodeFind::AmbiguousMatch(depth, value) => Found::Ambiguous(depth, value.clone()), } } } #[cfg(test)] mod test { use super::*; const NO_MORE: bool = false; const MAYBE_MORE: bool = true; #[test] fn lookup_empty() { let km: KeyMap = KeyMap::new(); assert_eq!(km.lookup("boo", true), Found::None); } #[test] fn lookup() { let mut km = KeyMap::new(); km.insert("boa", true); km.insert("boo", true); km.insert("boom", false); assert_eq!(km.lookup("b", MAYBE_MORE), Found::NeedData); assert_eq!(km.lookup("bo", MAYBE_MORE), Found::NeedData); assert_eq!(km.lookup("boa", MAYBE_MORE), Found::Exact(3, true),); assert_eq!(km.lookup("boo", MAYBE_MORE), Found::Ambiguous(3, true),); assert_eq!(km.lookup("boom", MAYBE_MORE), Found::Exact(4, false),); assert_eq!(km.lookup("boom!", MAYBE_MORE), Found::Exact(4, false),); } #[test] fn lookup_with_multiple_ambiguous_matches_without_additional_input() { let mut km = KeyMap::new(); km.insert("boa", false); km.insert("boo", false); km.insert("boom", true); km.insert("boom!!", false); assert_eq!(km.lookup("boom!", NO_MORE), Found::Exact(4, true)); } #[test] fn lookup_with_multiple_ambiguous_matches_with_potential_additional_input() { let mut km = KeyMap::new(); km.insert("boa", false); km.insert("boo", false); km.insert("boom", true); km.insert("boom!!", false); assert_eq!(km.lookup("boom!", MAYBE_MORE), Found::Ambiguous(4, true)); } #[test] fn sequence() { let mut km = KeyMap::new(); km.insert("\x03", true); km.insert("\x27", true); km.insert("\x03XYZ", true); assert_eq!(km.lookup("\x03", MAYBE_MORE), Found::Ambiguous(1, true),); assert_eq!(km.lookup("\x03foo", MAYBE_MORE), Found::Exact(1, true),); assert_eq!(km.lookup("\x03X", MAYBE_MORE), Found::Ambiguous(1, true),); } }