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}