1use std::borrow::Cow;
4
5use crate::error::{Error, Span};
6use crate::files::Files;
7
8type Result<T> = std::result::Result<T, Error>;
9
10#[derive(Clone, Debug)]
14pub struct Lexer<'src> {
15 src: &'src str,
16 pos: Pos,
17 lookahead: Option<(Pos, Token)>,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash, PartialOrd, Ord)]
22pub struct Pos {
23 pub file: usize,
28 pub offset: usize,
30}
31
32impl Pos {
33 pub fn new(file: usize, offset: usize) -> Self {
35 Self { file, offset }
36 }
37
38 pub fn is_unknown(&self) -> bool {
40 *self == Self::default()
41 }
42
43 pub fn pretty_print_line(&self, files: &Files) -> String {
45 format!(
46 "{} line {}",
47 files.file_name(self.file).unwrap(),
48 files.file_line_map(self.file).unwrap().line(self.offset)
49 )
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum Token {
56 LParen,
58 RParen,
60 Symbol(String),
62 Int(i128),
64 At,
66}
67
68impl<'src> Lexer<'src> {
69 pub fn new(file: usize, src: &'src str) -> Result<Lexer<'src>> {
71 let mut l = Lexer {
72 src,
73 pos: Pos::new(file, 0),
74 lookahead: None,
75 };
76 l.reload()?;
77 Ok(l)
78 }
79
80 pub fn pos(&self) -> Pos {
82 self.pos
83 }
84
85 fn advance_pos(&mut self) {
86 self.advance_by(1)
87 }
88
89 fn advance_by(&mut self, n: usize) {
90 self.pos.offset += n;
91 }
92
93 fn error(&self, pos: Pos, msg: impl Into<String>) -> Error {
94 Error::ParseError {
95 msg: msg.into(),
96 span: Span::new_single(pos),
97 }
98 }
99
100 fn next_token(&mut self) -> Result<Option<(Pos, Token)>> {
101 fn is_sym_first_char(c: u8) -> bool {
102 match c {
103 b'-' | b'0'..=b'9' | b'(' | b')' | b';' => false,
104 c if c.is_ascii_whitespace() => false,
105 _ => true,
106 }
107 }
108 fn is_sym_other_char(c: u8) -> bool {
109 match c {
110 b'(' | b')' | b';' | b'@' => false,
111 c if c.is_ascii_whitespace() => false,
112 _ => true,
113 }
114 }
115
116 while let Some(c) = self.peek_byte() {
118 match c {
119 b' ' | b'\t' | b'\n' | b'\r' => self.advance_pos(),
120 b';' => {
121 while let Some(c) = self.peek_byte() {
122 match c {
123 b'\n' | b'\r' => break,
124 _ => self.advance_pos(),
125 }
126 }
127 }
128 b'(' if self.lookahead_byte(1) == Some(b';') => {
129 let pos = self.pos();
130 self.advance_by(2);
131 let mut depth = 1usize;
132 loop {
133 match self.peek_byte() {
134 None => return Err(self.error(pos, "unterminated block comment")),
135 Some(b'(') if self.lookahead_byte(1) == Some(b';') => {
136 self.advance_by(2);
137 depth += 1;
138 }
139 Some(b';') if self.lookahead_byte(1) == Some(b')') => {
140 self.advance_by(2);
141 depth -= 1;
142 if depth == 0 {
143 break;
144 }
145 }
146 Some(_) => self.advance_pos(),
147 }
148 }
149 }
150 _ => break,
151 }
152 }
153
154 let Some(c) = self.peek_byte() else {
155 return Ok(None);
156 };
157 let char_pos = self.pos();
158 match c {
159 b'(' => {
160 self.advance_pos();
161 Ok(Some((char_pos, Token::LParen)))
162 }
163 b')' => {
164 self.advance_pos();
165 Ok(Some((char_pos, Token::RParen)))
166 }
167 b'@' => {
168 self.advance_pos();
169 Ok(Some((char_pos, Token::At)))
170 }
171 c if is_sym_first_char(c) => {
172 let start = self.pos.offset;
173 let start_pos = self.pos();
174 while let Some(c) = self.peek_byte() {
175 match c {
176 c if is_sym_other_char(c) => self.advance_pos(),
177 _ => break,
178 }
179 }
180 let end = self.pos.offset;
181 let s = &self.src[start..end];
182 debug_assert!(!s.is_empty());
183 Ok(Some((start_pos, Token::Symbol(s.to_string()))))
184 }
185 c @ (b'0'..=b'9' | b'-') => {
186 let start_pos = self.pos();
187 let mut neg = false;
188 if c == b'-' {
189 self.advance_pos();
190 neg = true;
191 }
192
193 let mut radix = 10;
194
195 match (
197 self.src.as_bytes().get(self.pos.offset),
198 self.src.as_bytes().get(self.pos.offset + 1),
199 ) {
200 (Some(b'0'), Some(b'x' | b'X')) => {
201 self.advance_by(2);
202 radix = 16;
203 }
204 (Some(b'0'), Some(b'o' | b'O')) => {
205 self.advance_by(2);
206 radix = 8;
207 }
208 (Some(b'0'), Some(b'b' | b'B')) => {
209 self.advance_by(2);
210 radix = 2;
211 }
212 _ => {}
213 }
214
215 let start = self.pos.offset;
219 while let Some(c) = self.peek_byte() {
220 match c {
221 b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' | b'_' => self.advance_pos(),
222 _ => break,
223 }
224 }
225 let end = self.pos.offset;
226 let s = &self.src[start..end];
227 let s = if s.contains('_') {
228 Cow::Owned(s.replace('_', ""))
229 } else {
230 Cow::Borrowed(s)
231 };
232
233 let num = match u128::from_str_radix(&s, radix) {
236 Ok(num) => num,
237 Err(err) => return Err(self.error(start_pos, err.to_string())),
238 };
239
240 let num = match (neg, num) {
241 (true, 0x80000000000000000000000000000000) => {
242 return Err(self.error(start_pos, "integer literal cannot fit in i128"));
243 }
244 (true, _) => -(num as i128),
245 (false, _) => num as i128,
246 };
247 let tok = Token::Int(num);
248
249 Ok(Some((start_pos, tok)))
250 }
251 c => Err(self.error(self.pos, format!("Unexpected character '{c}'"))),
252 }
253 }
254
255 pub fn next(&mut self) -> Result<Option<(Pos, Token)>> {
257 let tok = self.lookahead.take();
258 self.reload()?;
259 Ok(tok)
260 }
261
262 fn reload(&mut self) -> Result<()> {
263 if self.lookahead.is_none() && self.pos.offset < self.src.len() {
264 self.lookahead = self.next_token()?;
265 }
266 Ok(())
267 }
268
269 pub fn peek(&self) -> Option<&(Pos, Token)> {
271 self.lookahead.as_ref()
272 }
273
274 pub fn eof(&self) -> bool {
276 self.lookahead.is_none()
277 }
278
279 fn peek_byte(&self) -> Option<u8> {
280 self.lookahead_byte(0)
281 }
282
283 fn lookahead_byte(&self, n: usize) -> Option<u8> {
284 self.src.as_bytes().get(self.pos.offset + n).copied()
285 }
286}
287
288impl Token {
289 pub fn is_int(&self) -> bool {
291 matches!(self, Token::Int(_))
292 }
293
294 pub fn is_sym(&self) -> bool {
296 matches!(self, Token::Symbol(_))
297 }
298}
299
300#[cfg(test)]
301mod test {
302 use super::*;
303
304 #[track_caller]
305 fn lex(src: &str) -> Vec<Token> {
306 let mut toks = vec![];
307 let mut lexer = Lexer::new(0, src).unwrap();
308 while let Some((_, tok)) = lexer.next().unwrap() {
309 toks.push(tok);
310 }
311 toks
312 }
313
314 #[test]
315 fn lexer_basic() {
316 assert_eq!(
317 lex(
318 ";; comment\n; another\r\n \t(one two three (; block comment ;) 23 (; nested (; block ;) comment ;) -568 )\n"
319 ),
320 [
321 Token::LParen,
322 Token::Symbol("one".to_string()),
323 Token::Symbol("two".to_string()),
324 Token::Symbol("three".to_string()),
325 Token::Int(23),
326 Token::Int(-568),
327 Token::RParen
328 ]
329 );
330 }
331
332 #[test]
333 fn ends_with_sym() {
334 assert_eq!(lex("asdf"), [Token::Symbol("asdf".to_string())]);
335 }
336
337 #[test]
338 fn ends_with_num() {
339 assert_eq!(lex("23"), [Token::Int(23)]);
340 }
341
342 #[test]
343 fn weird_syms() {
344 assert_eq!(
345 lex("(+ [] => !! _test!;comment\n)"),
346 [
347 Token::LParen,
348 Token::Symbol("+".to_string()),
349 Token::Symbol("[]".to_string()),
350 Token::Symbol("=>".to_string()),
351 Token::Symbol("!!".to_string()),
352 Token::Symbol("_test!".to_string()),
353 Token::RParen,
354 ]
355 );
356 }
357
358 #[test]
359 fn integers() {
360 assert_eq!(
361 lex("0 1 -1"),
362 [Token::Int(0), Token::Int(1), Token::Int(-1)]
363 );
364
365 assert_eq!(
366 lex("340_282_366_920_938_463_463_374_607_431_768_211_455"),
367 [Token::Int(-1)]
368 );
369
370 assert_eq!(
371 lex("170_141_183_460_469_231_731_687_303_715_884_105_727"),
372 [Token::Int(i128::MAX)]
373 );
374
375 assert!(Lexer::new(0, "-170_141_183_460_469_231_731_687_303_715_884_105_728").is_err())
376 }
377}