Skip to main content

lexer/
lib.rs

1use crate::token::{lookup_identifier, Span, Token, TokenKind};
2
3mod lexer_test;
4pub mod token;
5
6pub struct Lexer<'a> {
7    input: &'a str,
8    position: usize,
9    read_position: usize,
10    ch: char,
11}
12
13impl<'a> Lexer<'a> {
14    pub fn new(input: &'a str) -> Self {
15        let mut l = Lexer {
16            input,
17            position: 0,
18            read_position: 0,
19            ch: '\0',
20        };
21
22        l.read_char();
23        return l;
24    }
25
26    fn read_char(&mut self) {
27        self.position = self.read_position;
28
29        if self.position >= self.input.len() {
30            self.ch = '\0'
31        } else {
32            self.ch = self.input[self.position..].chars().next().unwrap();
33            self.read_position = self.position + self.ch.len_utf8();
34        }
35    }
36
37    fn peek_char(&self) -> char {
38        if self.read_position >= self.input.len() {
39            '\0'
40        } else {
41            self.input[self.read_position..].chars().next().unwrap()
42        }
43    }
44
45    pub fn next_token(&mut self) -> Token {
46        // println!("self ch {}, position {} read_position {}", self.ch, self.position, self.read_position);
47        // Skip any whitespace and successive line comments before producing a token.
48        self.skip_ignorable();
49        let start = self.position;
50        if self.ch == '\0' {
51            // EOF consumes no source bytes; keep a zero-width span at input.len().
52            return Token {
53                span: Span {
54                    start,
55                    end: start,
56                },
57                kind: TokenKind::EOF,
58            };
59        }
60
61        let t = match self.ch {
62            '=' => {
63                if self.peek_char() == '=' {
64                    self.read_char();
65                    TokenKind::EQ
66                } else {
67                    TokenKind::ASSIGN
68                }
69            }
70            ';' => TokenKind::SEMICOLON,
71            '(' => TokenKind::LPAREN,
72            ')' => TokenKind::RPAREN,
73            ',' => TokenKind::COMMA,
74            '+' => TokenKind::PLUS,
75            '-' => TokenKind::MINUS,
76            '!' => {
77                if self.peek_char() == '=' {
78                    self.read_char();
79                    TokenKind::NotEq
80                } else {
81                    TokenKind::BANG
82                }
83            }
84            '*' => TokenKind::ASTERISK,
85            '/' => TokenKind::SLASH,
86            '<' => TokenKind::LT,
87            '>' => TokenKind::GT,
88            '{' => TokenKind::LBRACE,
89            '}' => TokenKind::RBRACE,
90            '[' => TokenKind::LBRACKET,
91            ':' => TokenKind::COLON,
92            '.' => TokenKind::DOT,
93            '?' => TokenKind::QUESTION,
94            ']' => TokenKind::RBRACKET,
95            '"' => {
96                let (start, end, string) = self.read_string();
97                return Token {
98                    span: Span {
99                        start,
100                        end,
101                    },
102                    kind: match string {
103                        Some(value) => TokenKind::STRING(value),
104                        // Unterminated literal: report the whole literal as a
105                        // single ILLEGAL token instead of accepting it.
106                        None => TokenKind::ILLEGAL,
107                    },
108                };
109            }
110            _ => {
111                if is_letter(self.ch) {
112                    let (start, end, identifier) = self.read_identifier();
113                    return Token {
114                        span: Span {
115                            start,
116                            end,
117                        },
118                        kind: lookup_identifier(&identifier),
119                    };
120                } else if is_digit(self.ch) {
121                    let (start, end, num) = self.read_number();
122                    return Token {
123                        span: Span {
124                            start,
125                            end,
126                        },
127                        kind: TokenKind::INT(num),
128                    };
129                } else {
130                    TokenKind::ILLEGAL
131                }
132            }
133        };
134
135        self.read_char();
136        return Token {
137            span: Span {
138                start,
139                end: self.position,
140            },
141            kind: t,
142        };
143    }
144
145    fn skip_whitespace(&mut self) {
146        while self.ch.is_ascii_whitespace() {
147            self.read_char();
148        }
149    }
150
151    fn skip_ignorable(&mut self) {
152        loop {
153            self.skip_whitespace();
154            if self.ch == '/' && self.peek_char() == '/' {
155                self.skip_comments();
156                // Continue the loop, in case there are more comments or whitespace
157                continue;
158            }
159            break;
160        }
161    }
162
163    fn skip_comments(&mut self) {
164        if self.ch == '/' && self.peek_char() == '/' {
165            self.read_char();
166            self.read_char();
167            // Check before advancing so an empty comment (`//` directly
168            // followed by a newline) does not swallow the next line.
169            while self.ch != '\n' && self.ch != '\u{0}' {
170                self.read_char();
171            }
172            // consume the comments end
173            if self.ch == '\n' {
174                self.read_char();
175            }
176        }
177    }
178
179    fn read_identifier(&mut self) -> (usize, usize, String) {
180        let pos = self.position;
181        while is_letter(self.ch) || is_digit(self.ch) {
182            self.read_char();
183        }
184
185        let x = self.input[pos..self.position].to_string();
186        return (pos, self.position, x);
187    }
188
189    fn read_number(&mut self) -> (usize, usize, i64) {
190        let pos = self.position;
191        while is_digit(self.ch) {
192            self.read_char();
193        }
194
195        let x = self.input[pos..self.position].parse().unwrap();
196
197        return (pos, self.position, x);
198    }
199
200    /// Reads a string literal starting at the opening `"`. Returns `None` when
201    /// the input ends before a closing quote, in which case the span still
202    /// covers everything scanned so the caller can emit one ILLEGAL token for
203    /// the whole literal.
204    fn read_string(&mut self) -> (usize, usize, Option<String>) {
205        let pos = self.position;
206        loop {
207            self.read_char();
208            if self.ch == '"' || self.ch == '\u{0}' {
209                break;
210            }
211        }
212
213        let value = if self.ch == '"' {
214            let x = self.input[pos + 1..self.position].to_string();
215            // consume the end "
216            self.read_char();
217            Some(x)
218        } else {
219            // The input ended before a closing quote.
220            None
221        };
222
223        return (pos, self.position, value);
224    }
225}
226
227fn is_letter(c: char) -> bool {
228    c.is_ascii_alphabetic() || c == '_'
229}
230
231fn is_digit(c: char) -> bool {
232    c.is_ascii_digit()
233}