Skip to main content

logos/
source.rs

1//! This module contains a bunch of traits necessary for processing byte strings.
2//!
3//! Most notable are:
4//! * `Source` - implemented by default for `&str` and `&[u8]`, used by the `Lexer`.
5//! * `Slice` - slices of `Source`, returned by `Lexer::slice`.
6
7use std::fmt::Debug;
8use std::ops::Range;
9
10/// Trait for types the `Lexer` can read from.
11///
12/// Most notably this is implemented for `&str`. It is unlikely you will
13/// ever want to use this Trait yourself, unless implementing a new `Source`
14/// the `Lexer` can use.
15pub trait Source {
16    /// A type this `Source` can be sliced into.
17    type Slice: ?Sized + PartialEq + Eq + Debug;
18
19    /// Length of the source
20    fn len(&self) -> usize;
21
22    /// Read a chunk of bytes into an array. Returns `None` when reading
23    /// out of bounds would occur.
24    ///
25    /// This is very useful for matching fixed-size byte arrays, and tends
26    /// to be very fast at it too, since the compiler knows the byte lengths.
27    ///
28    /// ```rust
29    /// use logos::Source;
30    ///
31    /// let foo = "foo";
32    ///
33    /// assert_eq!(foo.read(0), Some(b"foo"));     // Option<&[u8; 3]>
34    /// assert_eq!(foo.read(0), Some(b"fo"));      // Option<&[u8; 2]>
35    /// assert_eq!(foo.read(2), Some(b'o'));       // Option<u8>
36    /// assert_eq!(foo.read::<&[u8; 4]>(0), None); // Out of bounds
37    /// assert_eq!(foo.read::<&[u8; 2]>(2), None); // Out of bounds
38    /// ```
39    fn read<'a, Chunk>(&'a self, offset: usize) -> Option<Chunk>
40    where
41        Chunk: self::Chunk<'a>;
42
43    /// Read a chunk of bytes into an array without doing bounds checks.
44    unsafe fn read_unchecked<'a, Chunk>(&'a self, offset: usize) -> Chunk
45    where
46        Chunk: self::Chunk<'a>;
47
48    /// Get a slice of the source at given range. This is analogous to
49    /// `slice::get(range)`.
50    ///
51    /// ```rust
52    /// use logos::Source;
53    ///
54    /// let foo = "It was the year when they finally immanentized the Eschaton.";
55    /// assert_eq!(<str as Source>::slice(&foo, 51..59), Some("Eschaton"));
56    /// ```
57    fn slice(&self, range: Range<usize>) -> Option<&Self::Slice>;
58
59    /// Get a slice of the source at given range. This is analogous to
60    /// `slice::get_unchecked(range)`.
61    ///
62    /// **Using this method with range out of bounds is undefined behavior!**
63    ///
64    /// ```rust
65    /// use logos::Source;
66    ///
67    /// let foo = "It was the year when they finally immanentized the Eschaton.";
68    ///
69    /// unsafe {
70    ///     assert_eq!(<str as Source>::slice_unchecked(&foo, 51..59), "Eschaton");
71    /// }
72    /// ```
73    unsafe fn slice_unchecked(&self, range: Range<usize>) -> &Self::Slice;
74
75    /// For `&str` sources attempts to find the closest `char` boundary at which source
76    /// can be sliced, starting from `index`.
77    ///
78    /// For binary sources (`&[u8]`) this should just return `index` back.
79    #[inline]
80    fn find_boundary(&self, index: usize) -> usize {
81        index
82    }
83
84    /// Check if `index` is valid for this `Source`, that is:
85    ///
86    /// + It's not larger than the byte length of the `Source`.
87    /// + (`str` only) It doesn't land in the middle of a UTF-8 code point.
88    fn is_boundary(&self, index: usize) -> bool;
89}
90
91impl Source for str {
92    type Slice = str;
93
94    #[inline]
95    fn len(&self) -> usize {
96        self.len()
97    }
98
99    #[inline]
100    fn read<'a, Chunk>(&'a self, offset: usize) -> Option<Chunk>
101    where
102        Chunk: self::Chunk<'a>,
103    {
104        if offset + (Chunk::SIZE - 1) < self.len() {
105            Some(unsafe { Chunk::from_ptr(self.as_ptr().add(offset)) })
106        } else {
107            None
108        }
109    }
110
111    #[inline]
112    unsafe fn read_unchecked<'a, Chunk>(&'a self, offset: usize) -> Chunk
113    where
114        Chunk: self::Chunk<'a>,
115    {
116        Chunk::from_ptr(self.as_ptr().add(offset))
117    }
118
119    #[inline]
120    fn slice(&self, range: Range<usize>) -> Option<&str> {
121        self.get(range)
122    }
123
124    #[inline]
125    unsafe fn slice_unchecked(&self, range: Range<usize>) -> &str {
126        debug_assert!(
127            range.start <= self.len() && range.end <= self.len(),
128            "Reading out of bounds {:?} for {}!",
129            range,
130            self.len()
131        );
132
133        self.get_unchecked(range)
134    }
135
136    #[inline]
137    fn find_boundary(&self, mut index: usize) -> usize {
138        while !self.is_char_boundary(index) {
139            index += 1;
140        }
141
142        index
143    }
144
145    #[inline]
146    fn is_boundary(&self, index: usize) -> bool {
147        self.is_char_boundary(index)
148    }
149}
150
151impl Source for [u8] {
152    type Slice = [u8];
153
154    #[inline]
155    fn len(&self) -> usize {
156        self.len()
157    }
158
159    #[inline]
160    fn read<'a, Chunk>(&'a self, offset: usize) -> Option<Chunk>
161    where
162        Chunk: self::Chunk<'a>,
163    {
164        if offset + (Chunk::SIZE - 1) < self.len() {
165            Some(unsafe { Chunk::from_ptr(self.as_ptr().add(offset)) })
166        } else {
167            None
168        }
169    }
170
171    #[inline]
172    unsafe fn read_unchecked<'a, Chunk>(&'a self, offset: usize) -> Chunk
173    where
174        Chunk: self::Chunk<'a>,
175    {
176        Chunk::from_ptr(self.as_ptr().add(offset))
177    }
178
179    #[inline]
180    fn slice(&self, range: Range<usize>) -> Option<&[u8]> {
181        self.get(range)
182    }
183
184    #[inline]
185    unsafe fn slice_unchecked(&self, range: Range<usize>) -> &[u8] {
186        debug_assert!(
187            range.start <= self.len() && range.end <= self.len(),
188            "Reading out of bounds {:?} for {}!",
189            range,
190            self.len()
191        );
192
193        self.get_unchecked(range)
194    }
195
196    #[inline]
197    fn is_boundary(&self, index: usize) -> bool {
198        index <= self.len()
199    }
200}
201
202/// A fixed, statically sized chunk of data that can be read from the `Source`.
203///
204/// This is implemented for `u8`, as well as byte arrays `&[u8; 1]` to `&[u8; 32]`.
205pub trait Chunk<'source>: Sized + Copy + PartialEq + Eq {
206    /// Size of the chunk being accessed in bytes.
207    const SIZE: usize;
208
209    /// Create a chunk from a raw byte pointer.
210    unsafe fn from_ptr(ptr: *const u8) -> Self;
211}
212
213impl<'source> Chunk<'source> for u8 {
214    const SIZE: usize = 1;
215
216    #[inline]
217    unsafe fn from_ptr(ptr: *const u8) -> Self {
218        *ptr
219    }
220}
221
222impl<'source, const N: usize> Chunk<'source> for &'source [u8; N] {
223    const SIZE: usize = N;
224
225    #[inline]
226    unsafe fn from_ptr(ptr: *const u8) -> Self {
227        &*(ptr as *const [u8; N])
228    }
229}