cqlib_core/compile/knowledge/rule_dsl/
lexer.rs1#[derive(Debug, Clone, PartialEq)]
21pub enum TokenKind {
22 Rule,
24 Match,
26 Require,
28 Rewrite,
30 Mod,
32 Ident,
34 Number,
36 LBrace,
38 RBrace,
40 LParen,
42 RParen,
44 LBracket,
46 RBracket,
48 Comma,
50 Newline,
52 EqEq,
54 Plus,
56 Minus,
58 Star,
60 Slash,
62 Eof,
64}
65
66#[derive(Debug, Clone, PartialEq)]
71pub struct Token<'a> {
72 pub kind: TokenKind,
74 pub text: &'a str,
76 pub pos: usize,
78}
79
80#[derive(Debug, Clone, PartialEq, thiserror::Error)]
82#[error("lexer error at byte {pos}: {msg}")]
83pub struct LexError {
84 pub msg: String,
86 pub pos: usize,
88}
89
90pub struct Lexer<'a> {
102 input: &'a str,
104 pos: usize,
106}
107
108impl<'a> Lexer<'a> {
109 pub fn new(input: &'a str) -> Self {
111 Self { input, pos: 0 }
112 }
113
114 pub fn next_token(&mut self) -> Result<Token<'a>, LexError> {
120 self.skip_horizontal_whitespace_and_comments();
121
122 if self.pos >= self.input.len() {
123 return Ok(Token {
124 kind: TokenKind::Eof,
125 text: "",
126 pos: self.pos,
127 });
128 }
129
130 let start = self.pos;
131 let ch = self.peek_char().unwrap();
132
133 match ch {
134 '\n' | '\r' => {
135 self.advance();
136 if ch == '\r' && self.peek_char() == Some('\n') {
137 self.advance();
138 }
139 loop {
141 self.skip_horizontal_whitespace_and_comments();
142 match self.peek_char() {
143 Some('\n') => {
144 self.advance();
145 }
146 Some('\r') => {
147 self.advance();
148 if self.peek_char() == Some('\n') {
149 self.advance();
150 }
151 }
152 _ => break,
153 }
154 }
155 self.make_token(TokenKind::Newline, start)
156 }
157 '{' => {
158 self.advance();
159 self.make_token(TokenKind::LBrace, start)
160 }
161 '}' => {
162 self.advance();
163 self.make_token(TokenKind::RBrace, start)
164 }
165 '(' => {
166 self.advance();
167 self.make_token(TokenKind::LParen, start)
168 }
169 ')' => {
170 self.advance();
171 self.make_token(TokenKind::RParen, start)
172 }
173 '[' => {
174 self.advance();
175 self.make_token(TokenKind::LBracket, start)
176 }
177 ']' => {
178 self.advance();
179 self.make_token(TokenKind::RBracket, start)
180 }
181 ',' => {
182 self.advance();
183 self.make_token(TokenKind::Comma, start)
184 }
185 '+' => {
186 self.advance();
187 self.make_token(TokenKind::Plus, start)
188 }
189 '-' => {
190 self.advance();
191 self.make_token(TokenKind::Minus, start)
192 }
193 '*' => {
194 self.advance();
195 self.make_token(TokenKind::Star, start)
196 }
197 '/' => {
198 self.advance();
199 if self.peek_char() == Some('/') {
200 self.skip_line_comment();
201 self.next_token()
202 } else {
203 self.make_token(TokenKind::Slash, start)
204 }
205 }
206 '=' => {
207 self.advance();
208 if self.peek_char() == Some('=') {
209 self.advance();
210 self.make_token(TokenKind::EqEq, start)
211 } else {
212 Err(LexError {
213 msg: format!("unexpected character '{}'", ch),
214 pos: start,
215 })
216 }
217 }
218 _ if ch.is_alphabetic() || ch == '_' => self.read_ident(start),
219 _ if ch.is_ascii_digit() || ch == '.' => self.read_number(start),
220 _ => Err(LexError {
221 msg: format!("unexpected character '{}'", ch),
222 pos: start,
223 }),
224 }
225 }
226
227 fn read_ident(&mut self, start: usize) -> Result<Token<'a>, LexError> {
229 while let Some(ch) = self.peek_char() {
230 if ch.is_alphanumeric() || ch == '_' {
231 self.advance();
232 } else {
233 break;
234 }
235 }
236 let text = &self.input[start..self.pos];
237 let kind = match text {
238 "rule" => TokenKind::Rule,
239 "match" => TokenKind::Match,
240 "require" => TokenKind::Require,
241 "rewrite" => TokenKind::Rewrite,
242 "mod" => TokenKind::Mod,
243 _ => TokenKind::Ident,
244 };
245 Ok(Token {
246 kind,
247 text,
248 pos: start,
249 })
250 }
251
252 fn read_number(&mut self, start: usize) -> Result<Token<'a>, LexError> {
254 while let Some(ch) = self.peek_char() {
255 if ch.is_ascii_digit() || ch == '.' {
256 self.advance();
257 } else {
258 break;
259 }
260 }
261 let text = &self.input[start..self.pos];
262 Ok(Token {
263 kind: TokenKind::Number,
264 text,
265 pos: start,
266 })
267 }
268
269 fn skip_horizontal_whitespace_and_comments(&mut self) {
271 loop {
272 match self.peek_char() {
273 Some(ch) if ch.is_whitespace() && ch != '\n' && ch != '\r' => self.advance(),
274 Some('/') if self.peek_next() == Some('/') => self.skip_line_comment(),
275 _ => break,
276 }
277 }
278 }
279
280 fn skip_line_comment(&mut self) {
282 while let Some(ch) = self.peek_char() {
283 if ch == '\n' {
284 break;
285 }
286 self.advance();
287 }
288 }
289
290 fn make_token(&self, kind: TokenKind, start: usize) -> Result<Token<'a>, LexError> {
293 Ok(Token {
294 kind,
295 text: &self.input[start..self.pos],
296 pos: start,
297 })
298 }
299
300 fn peek_char(&self) -> Option<char> {
302 self.input[self.pos..].chars().next()
303 }
304
305 fn peek_next(&self) -> Option<char> {
307 self.input[self.pos..].chars().nth(1)
308 }
309
310 fn advance(&mut self) {
312 if let Some(ch) = self.peek_char() {
313 self.pos += ch.len_utf8();
314 }
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 fn lex_all(input: &str) -> Vec<TokenKind> {
323 let mut lexer = Lexer::new(input);
324 let mut kinds = Vec::new();
325 loop {
326 let token = lexer.next_token().unwrap();
327 kinds.push(token.kind.clone());
328 if token.kind == TokenKind::Eof {
329 break;
330 }
331 }
332 kinds
333 }
334
335 #[test]
336 fn lex_keywords_and_ident() {
337 let kinds = lex_all("rule match require rewrite mod foo");
338 assert_eq!(
339 kinds,
340 vec![
341 TokenKind::Rule,
342 TokenKind::Match,
343 TokenKind::Require,
344 TokenKind::Rewrite,
345 TokenKind::Mod,
346 TokenKind::Ident,
347 TokenKind::Eof,
348 ]
349 );
350 }
351
352 #[test]
353 fn lex_punctuation() {
354 let kinds = lex_all("{ } ( ) [ ] , \n == + - * /");
355 assert_eq!(
356 kinds,
357 vec![
358 TokenKind::LBrace,
359 TokenKind::RBrace,
360 TokenKind::LParen,
361 TokenKind::RParen,
362 TokenKind::LBracket,
363 TokenKind::RBracket,
364 TokenKind::Comma,
365 TokenKind::Newline,
366 TokenKind::EqEq,
367 TokenKind::Plus,
368 TokenKind::Minus,
369 TokenKind::Star,
370 TokenKind::Slash,
371 TokenKind::Eof,
372 ]
373 );
374 }
375
376 #[test]
377 fn lex_numbers() {
378 let kinds = lex_all("0 1 2.5 3.14");
379 assert_eq!(
380 kinds,
381 vec![
382 TokenKind::Number,
383 TokenKind::Number,
384 TokenKind::Number,
385 TokenKind::Number,
386 TokenKind::Eof,
387 ]
388 );
389 }
390
391 #[test]
392 fn lex_comment() {
393 let kinds = lex_all("rule // this is a comment\nmatch");
394 assert_eq!(
395 kinds,
396 vec![
397 TokenKind::Rule,
398 TokenKind::Newline,
399 TokenKind::Match,
400 TokenKind::Eof
401 ]
402 );
403 }
404
405 #[test]
406 fn lex_consecutive_newlines() {
407 let kinds = lex_all("rule\n\n\r\nmatch");
408 assert_eq!(
409 kinds,
410 vec![
411 TokenKind::Rule,
412 TokenKind::Newline,
413 TokenKind::Match,
414 TokenKind::Eof
415 ]
416 );
417 }
418
419 #[test]
420 fn lex_unicode_identifier_accepted() {
421 let kinds = lex_all("rule α");
423 assert_eq!(
424 kinds,
425 vec![
426 TokenKind::Rule,
427 TokenKind::Ident, TokenKind::Eof
429 ]
430 );
431 }
432}