symbol_ty/
lib.rs

1//! A type-level string, which is a string that is encoded in the type system.
2
3#![no_std]
4
5use core::{fmt, fmt::Write, hash::Hash, iter::FusedIterator};
6
7pub use symbol_ty_macro::Symbol;
8
9/// A single character of the symbol, followed by the rest of the symbol.
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Cons<const C: char, Tail>(Tail);
12
13/// The end of the symbol.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Nil;
16
17/// A symbol, which is a type-level string.
18pub trait Symbol: fmt::Display + fmt::Debug + Default + Eq + Ord + Copy + Sized + Hash {
19    type Chars: Iterator<Item = char>;
20
21    /// Get an instance of the symbol.
22    fn new() -> Self;
23
24    /// Get an iterator over the characters of the symbol.
25    fn chars() -> Self::Chars;
26}
27
28impl Symbol for Nil {
29    type Chars = core::iter::Empty<char>;
30
31    #[inline(always)]
32    fn new() -> Self {
33        Self::new()
34    }
35
36    #[inline(always)]
37    fn chars() -> Self::Chars {
38        core::iter::empty()
39    }
40}
41
42impl<const C: char, Tail: Symbol> Symbol for Cons<C, Tail> {
43    type Chars = Chars<C, <Tail as Symbol>::Chars>;
44
45    fn new() -> Self {
46        Self(Tail::new())
47    }
48
49    fn chars() -> Self::Chars {
50        Chars {
51            used_c: false,
52            tail: Tail::chars(),
53        }
54    }
55}
56
57impl<const C: char, Tail: Symbol> Cons<C, Tail> {
58    #[inline(always)]
59    pub fn new() -> Self {
60        <Self as Symbol>::new()
61    }
62}
63
64impl Nil {
65    #[inline(always)]
66    pub const fn new() -> Self {
67        Self
68    }
69}
70
71impl<const C: char, Tail: Symbol> Default for Cons<C, Tail> {
72    #[inline(always)]
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl fmt::Display for Nil {
79    #[inline(always)]
80    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
81        Ok(())
82    }
83}
84
85impl<const C: char, Tail: fmt::Display> fmt::Display for Cons<C, Tail> {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        f.write_char(C)?;
88        self.0.fmt(f)
89    }
90}
91
92impl<const C: char, Tail: fmt::Debug> fmt::Debug for Cons<C, Tail> {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_tuple("Cons").field(&C).field(&self.0).finish()
95    }
96}
97
98/// Iterator over the characters of a given symbol.
99#[derive(Clone, Copy, PartialEq, Eq, Hash)]
100#[must_use = "iterators are lazy and do nothing unless consumed"]
101pub struct Chars<const C: char, Tail> {
102    used_c: bool,
103    tail: Tail,
104}
105
106impl<const C: char, Tail: Iterator<Item = char>> Iterator for Chars<C, Tail> {
107    type Item = char;
108
109    fn next(&mut self) -> Option<Self::Item> {
110        if self.used_c {
111            self.tail.next()
112        } else {
113            self.used_c = true;
114            Some(C)
115        }
116    }
117
118    fn size_hint(&self) -> (usize, Option<usize>) {
119        let (lower, upper) = self.tail.size_hint();
120        if self.used_c {
121            return (lower, upper);
122        }
123        (
124            lower.saturating_add(1),
125            upper.and_then(|upper| upper.checked_add(1)),
126        )
127    }
128}
129
130impl<const C: char, Tail: DoubleEndedIterator<Item = char>> DoubleEndedIterator for Chars<C, Tail> {
131    fn next_back(&mut self) -> Option<Self::Item> {
132        if let Some(c) = self.tail.next_back() {
133            return Some(c);
134        }
135        if self.used_c {
136            None
137        } else {
138            self.used_c = true;
139            Some(C)
140        }
141    }
142}
143
144impl<const C: char, Tail: ExactSizeIterator<Item = char>> ExactSizeIterator for Chars<C, Tail> {
145    fn len(&self) -> usize {
146        let len = self.tail.len();
147        if self.used_c {
148            len
149        } else {
150            len.saturating_add(1)
151        }
152    }
153}
154
155impl<const C: char, Tail: FusedIterator<Item = char>> FusedIterator for Chars<C, Tail> {}
156
157#[cfg(test)]
158mod tests {
159    extern crate std;
160
161    use std::string::{String, ToString};
162
163    use insta::{assert_debug_snapshot, with_settings};
164
165    use super::Symbol;
166
167    macro_rules! insta_assert {
168       ($e:expr) => {
169            with_settings!({prepend_module_to_snapshot => false}, {
170                assert_debug_snapshot!($e);
171            });
172        };
173    }
174
175    #[test]
176    fn test_empty_symbol() {
177        insta_assert!(<Symbol!("")>::new());
178    }
179
180    #[test]
181    fn test_display() {
182        insta_assert!(<Symbol!("hello")>::new().to_string());
183    }
184
185    #[test]
186    fn test_debug() {
187        insta_assert!(<Symbol!("hello")>::new());
188    }
189
190    #[test]
191    fn test_iter() {
192        insta_assert!(<Symbol!("hello")>::chars().collect::<String>());
193    }
194    
195    #[test]
196    fn test_iter_rev() {
197        insta_assert!(<Symbol!("hello")>::chars().rev().collect::<String>());
198    }
199    
200    #[test]
201    fn test_fuse() {
202        let mut chars = <Symbol!("abc")>::chars();
203        assert_eq!(chars.next(), Some('a'));
204        assert_eq!(chars.next(), Some('b'));
205        assert_eq!(chars.next(), Some('c'));
206        assert_eq!(chars.next(), None);
207        assert_eq!(chars.next(), None);
208        assert_eq!(chars.next(), None);
209    }
210
211    #[test]
212    fn test_rev_fuse() {
213        let mut chars = <Symbol!("abc")>::chars().rev();
214        assert_eq!(chars.next(), Some('c'));
215        assert_eq!(chars.next(), Some('b'));
216        assert_eq!(chars.next(), Some('a'));
217        assert_eq!(chars.next(), None);
218        assert_eq!(chars.next(), None);
219        assert_eq!(chars.next(), None);
220    }
221}