enum-utils-from-str-0.1.2/Cargo.toml.orig010066400017500001750000000006111355112402200164660ustar0000000000000000[package] name = "enum-utils-from-str" version = "0.1.2" authors = ["Dylan MacKenzie "] edition = "2018" description = "Code generation for mapping from strings to arbitrary values" repository = "https://github.com/ecstatic-morse/enum-utils" readme = "../README.md" license = "MIT" categories = ["development-tools"] [dependencies] proc-macro2 = "1.0" quote = "1.0" enum-utils-from-str-0.1.2/Cargo.toml0000644000000016640000000000000127510ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "enum-utils-from-str" version = "0.1.2" authors = ["Dylan MacKenzie "] description = "Code generation for mapping from strings to arbitrary values" readme = "../README.md" categories = ["development-tools"] license = "MIT" repository = "https://github.com/ecstatic-morse/enum-utils" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" enum-utils-from-str-0.1.2/src/lib.rs010064400017500001750000000135701353435456200155270ustar0000000000000000//! Code generation for a compile-time trie-based mapping from strings to arbitrary values. mod trie; use std::collections::BTreeMap; use std::io; use quote::{quote, ToTokens}; use proc_macro2::{Literal, Ident, TokenStream, Span}; /// Generates a lookup function for all the key-value pairs contained in the tree. /// /// # Examples /// /// ```rust /// # #![recursion_limit="128"] /// # use quote::quote; /// use enum_utils_from_str::StrMapFunc; /// /// # fn main() { /// // Compiling this trie into a lookup function... /// let mut code = vec![]; /// StrMapFunc::new("custom_lookup", "bool") /// .entries(vec![ /// ("yes", true), /// ("yep", true), /// ("no", false), /// ]) /// .compile(&mut code); /// /// // results in the following generated code. /// /// # let generated = quote! { /// fn custom_lookup(s: &[u8]) -> Option { /// match s.len() { /// 2 => if s[0] == b'n' && s[1] == b'o' { /// return Some(false); /// }, /// 3 => if s[0] == b'y' && s[1] == b'e' { /// if s[2] == b'p' { /// return Some(true); /// } else if s[2] == b's' { /// return Some(true); /// } /// }, /// /// _ => {} /// } /// /// None /// } /// # }; /// /// # assert_eq!(String::from_utf8(code).unwrap(), format!("{}", generated)); /// # } /// ``` #[derive(Clone)] pub struct StrMapFunc { atoms: Forest, func_name: Ident, ret_ty: TokenStream, case: Case, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Case { Sensitive, Insensitive, } impl StrMapFunc { pub fn new(func_name: &str, ret_ty: &str) -> Self { StrMapFunc { atoms: Default::default(), func_name: Ident::new(func_name, Span::call_site()), ret_ty: ret_ty.parse().unwrap(), case: Case::Sensitive, } } pub fn case(&mut self, case: Case) -> &mut Self { self.case = case; self } pub fn entry(&mut self, k: &str, v: impl ToTokens) -> &mut Self { self.atoms.insert(k.as_bytes(), v.into_token_stream()); self } pub fn entries<'a, V: 'a>(&mut self, entries: impl IntoIterator) -> &mut Self where V: ToTokens, { for (s, v) in entries.into_iter() { self.entry(s, v); } self } pub fn compile(&self, mut w: impl io::Write) -> io::Result<()> { let tokens = self.into_token_stream(); w.write_all(format!("{}", tokens).as_bytes()) } } impl ToTokens for StrMapFunc { fn to_tokens(&self, tokens: &mut TokenStream) { let StrMapFunc { func_name, ret_ty, atoms, case } = self; let match_arms = atoms.0.iter() .map(|(&len, trie)| { let branch = Forest::branch_tokens(trie, *case == Case::Insensitive); let len = Literal::usize_unsuffixed(len); quote!(#len => #branch) }); let body = quote! { match s.len() { #( #match_arms, )* _ => {} } None }; tokens.extend(quote! { fn #func_name(s: &[u8]) -> Option<#ret_ty> { #body } }); } } /// A set of tries where each trie only stores strings of a single length. #[derive(Debug, Clone)] pub struct Forest(BTreeMap>); impl Default for Forest { fn default() -> Self { Forest(Default::default()) } } impl Forest { pub fn get(&mut self, bytes: &[u8]) -> Option<&T> { self.0.get(&bytes.len()) .and_then(|n| n.get(bytes)) } pub fn insert(&mut self, bytes: &[u8], value: T) -> Option { let node = self.0.entry(bytes.len()).or_default(); node.insert(bytes, value) } } fn byte_literal(b: u8) -> TokenStream { if b < 128 { let c: String = char::from(b).escape_default().collect(); format!("b'{}'", c).parse().unwrap() } else { Literal::u8_unsuffixed(b).into_token_stream() } } impl Forest where T: ToTokens { fn branch_tokens(node: &trie::Node, ignore_case: bool) -> TokenStream { use trie::TraversalOrder::*; let mut tok = vec![TokenStream::new()]; let mut depth = 0; let mut is_first_child = true; let mut dfs = node.dfs(); while let Some((order, node)) = dfs.next() { if node.bytes.is_empty() { continue; } match order { Pre => { if !is_first_child { tok.last_mut().unwrap().extend(quote!(else)); is_first_child = true; } let i = (depth..depth+node.bytes.len()).map(Literal::usize_unsuffixed); let b = node.bytes.iter().cloned().map(byte_literal); if !ignore_case { tok.last_mut().unwrap().extend(quote!(if #( s[#i] == #b )&&*)); } else { tok.last_mut().unwrap().extend(quote!(if #( s[#i].eq_ignore_ascii_case(&#b) )&&*)); } tok.push(TokenStream::new()); depth += node.bytes.len(); if let Some(v) = node.value { // TODO: debug_assert_eq!(dfs.next().0, Post); tok.last_mut().unwrap().extend(quote!(return Some(#v);)); } } Post => { let body = tok.pop().unwrap(); tok.last_mut().unwrap().extend(quote!({ #body })); depth -= node.bytes.len(); is_first_child = false; } } } let ret = tok.pop().unwrap(); assert!(tok.is_empty()); ret } } enum-utils-from-str-0.1.2/src/trie.rs010064400017500001750000000107771353435456200157320ustar0000000000000000use std::{cmp::min, iter, mem}; use std::collections::btree_map::{self, BTreeMap, Entry}; type Map = BTreeMap; #[derive(Debug, Clone)] pub struct Node { bytes: Vec, children: Map>>, value: Option, } impl Default for Node { fn default() -> Self { Node { bytes: Default::default(), children: Default::default(), value: None, } } } /// Returns the smallest index where two byte strings are not equal. fn differs_at(a: &[u8], b: &[u8]) -> Option { // debug_assert_eq!(a.len(), b.len()); for (i, (&a, &b)) in a.iter().zip(b.iter()).enumerate() { if a != b { return Some(i); } } None } impl Node { fn new(bytes: Vec, value: Option) -> Self { Node { bytes: bytes.to_owned(), value, children: Default::default(), } } fn split_at(&mut self, idx: usize) { let suffix = self.bytes.split_off(idx); let byte = suffix[0]; let mut child = Box::new(Node::new(suffix, self.value.take())); child.children = mem::replace(&mut self.children, Default::default()); self.children.insert(byte, child); } pub fn insert(&mut self, bytes: &[u8], value: T) -> Option { let l = min(bytes.len(), self.bytes.len()); let (prefix, mut suffix) = bytes.split_at(l); // prefix: "abc" // "abd" let split_idx = differs_at(prefix, &self.bytes) .or_else(|| if l < self.bytes.len() { Some(l) } else { None }); if let Some(idx) = split_idx { self.split_at(idx); suffix = &bytes[idx..]; } if suffix.is_empty() { return self.value.replace(value); } match self.children.entry(suffix[0]) { Entry::Occupied(mut n) => n.get_mut().insert(suffix, value), Entry::Vacant(n) => { n.insert(Box::new(Node::new(suffix.to_owned(), Some(value)))); None } } } pub fn get(&self, bytes: &[u8]) -> Option<&T> { if bytes.len() < self.bytes.len() { return None; } let (prefix, suffix) = bytes.split_at(self.bytes.len()); if prefix != &self.bytes[..] { return None; } if suffix.is_empty() { return self.value.as_ref(); } self.children .get(&suffix[0]) .map_or(None, |c| c.get(bytes)) } pub fn dfs(&self) -> impl Iterator)> { iter::once((TraversalOrder::Pre, self.into())) .chain(DfsIter::new(self)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TraversalOrder { Pre, Post, } #[derive(Debug)] pub struct NodeRef<'a, T> { pub bytes: &'a [u8], pub value: Option<&'a T>, } impl<'a, T> From<&'a Node> for NodeRef<'a, T> { fn from(node: &'a Node) -> Self { NodeRef { bytes: node.bytes.as_ref(), value: node.value.as_ref(), } } } struct DfsIter<'a, T>(Vec<(&'a Node, btree_map::Values<'a, u8, Box>>)>); impl<'a, T> DfsIter<'a, T> { fn new(node: &'a Node) -> Self { DfsIter(vec![(node, node.children.values())]) } } impl<'a, T> Iterator for DfsIter<'a, T> { type Item = (TraversalOrder, NodeRef<'a, T>); fn next(&mut self) -> Option { let (_, children) = self.0.last_mut()?; if let Some(node) = children.next() { self.0.push((node, node.children.values())); Some((TraversalOrder::Pre, NodeRef::from(&**node))) } else { let (node, _) = self.0.pop().unwrap(); Some((TraversalOrder::Post, NodeRef::from(&*node))) } } } #[cfg(test)] mod tests { use super::*; #[test] fn dfs() { use TraversalOrder::*; let mut trie = Node::default(); trie.insert(b"abcd", ()); trie.insert(b"abcde", ()); trie.insert(b"abz", ()); let order: Vec<_> = trie.dfs() .map(|(o, n)| (o, n.bytes.as_ref())) .collect(); let expected: Vec<(_, &[u8])> = vec![ (Pre, b""), (Pre, b"ab"), (Pre, b"cd"), (Pre, b"e"), (Post, b"e"), (Post, b"cd"), (Pre, b"z"), (Post, b"z"), (Post, b"ab"), (Post, b""), ]; assert_eq!(order, expected); } } enum-utils-from-str-0.1.2/.cargo_vcs_info.json0000644000000001120000000000000147360ustar00{ "git": { "sha1": "c1b8645459e8b9cad1e58295e3a79344e2052bd3" } }