1use std::str::CharIndices;
2
3use log::trace;
4
5use crate::tokens::{
6 CommentedToken,
7 Token::{self, *},
8};
9
10pub struct Tokenizer<'a> {
14 offset: usize,
15 it: usize,
16 current_char: char,
17 source: CharIndices<'a>,
18 raw_source: &'a str,
19}
20
21const SYMBOL_ENDING: [char; 29] = [
22 ' ', '(', ')', '{', '}', '#', ';', '\n', '\t', '\r', '+', '-', '/', '\\', '%', '*', '^', '!',
23 '&', '|', '<', '>', '=', ',', '[', ']', '$', '`', '"',
24];
25impl<'a> Tokenizer<'a> {
26 pub fn new(input: &'a str) -> Self {
47 Self {
48 offset: 0,
49 it: 0,
50 current_char: '\0',
51 source: input.char_indices(),
52 raw_source: input,
53 }
54 }
55
56 pub fn tokenize(&mut self) -> Result<Vec<CommentedToken>, crate::Error> {
73 let mut tokens = vec![];
74 self.next();
75 while self.it < self.raw_source.len() {
76 match self.current_char {
77 ' ' | '\t' => {
78 self.next();
79 }
80 '\r' => {
81 self.next();
82 self.push_token(Newline, &mut tokens);
83 self.next();
84 }
85 '\n' => {
86 self.push_token(Newline, &mut tokens);
87 self.next();
88 }
89 ';' => {
90 self.push_token(Semicolon, &mut tokens);
91 self.next();
92 }
93 ',' => {
94 self.push_token(Comma, &mut tokens);
95 self.next();
96 }
97 '(' => {
98 self.push_token(LParen, &mut tokens);
99 self.next();
100 }
101 ')' => {
102 self.push_token(RParen, &mut tokens);
103 self.next();
104 }
105 '{' => {
106 self.push_token(LBrace, &mut tokens);
107 self.next();
108 }
109 '}' => {
110 self.push_token(RBrace, &mut tokens);
111 self.next();
112 }
113 '[' => {
114 self.push_token(LBracket, &mut tokens);
115 self.next();
116 }
117 ']' => {
118 self.push_token(RBracket, &mut tokens);
119 self.next();
120 }
121 '\'' | '\"' => {
122 self.string_literal(&mut tokens);
123 self.next();
124 }
125 '*' => {
126 let next_char = self.lookahead().expect("Script does not end on '*'");
127 match next_char {
128 '*' => {
130 self.push_token(Power, &mut tokens);
131 self.next();
132 }
133 _ => self.push_token(Multiply, &mut tokens),
134 }
135 self.next();
136 }
137 '/' => {
138 self.push_token(Divide, &mut tokens);
139 self.next();
140 }
141 '^' => {
142 self.push_token(Power, &mut tokens);
143 self.next();
144 }
145 '+' => {
146 self.push_token(Plus, &mut tokens);
147 self.next();
148 }
149 '?' => {
150 self.push_token(Help, &mut tokens);
151 self.next();
152 }
153 '<' => {
154 let next_char = self.lookahead().expect("Script does not end on '<'");
155 match next_char {
156 '-' => {
157 self.push_token(LAssign, &mut tokens);
158 self.next();
159 }
160 '=' => {
161 self.push_token(LowerEqual, &mut tokens);
162 self.next();
163 }
164 '<' => {
165 self.push_token(SuperAssign, &mut tokens);
166 self.next();
167 self.next();
168 }
169 _ => self.push_token(LowerThan, &mut tokens),
170 }
171 self.next();
172 }
173 '>' => {
174 let next_char = self.lookahead().expect("Script does not end on '>'");
175 match next_char {
176 '=' => {
177 self.push_token(GreaterEqual, &mut tokens);
178 self.next();
179 }
180 _ => {
181 self.push_token(GreaterThan, &mut tokens);
182 }
183 }
184 self.next();
185 }
186 '|' => {
187 let next_char = self.lookahead().expect("Script does not end on '|'");
188 match next_char {
189 '|' => {
190 self.push_token(Or, &mut tokens);
191 self.next();
192 }
193 '>' => {
194 self.push_token(Pipe, &mut tokens);
195 self.next();
196 }
197 _ => self.push_token(VectorizedOr, &mut tokens),
198 }
199 self.next();
200 }
201 '&' => {
202 let next_char = self.lookahead().expect("Script does not end on '&'");
203 match next_char {
204 '&' => {
205 self.push_token(And, &mut tokens);
206 self.next();
207 }
208 _ => self.push_token(VectorizedAnd, &mut tokens),
209 }
210 self.next();
211 }
212 '=' => {
213 let next_char = self.lookahead().expect("Script does not end on '='");
214 match next_char {
215 '=' => {
216 self.push_token(Equal, &mut tokens);
217 self.next();
218 }
219 _ => self.push_token(OldAssign, &mut tokens),
220 }
221 self.next();
222 }
223 '$' => {
224 self.push_token(Dollar, &mut tokens);
225 self.next();
226 }
227 '-' => {
228 let next_char = self.lookahead().expect("Script does not end on '-'");
229 match next_char {
230 '>' => {
231 self.push_token(RAssign, &mut tokens);
232 self.next();
233 }
234 _ => self.push_token(Minus, &mut tokens),
235 }
236 self.next();
237 }
238 '!' => {
239 self.next();
240 match self.current_char {
241 '=' => {
242 self.push_token(NotEqual, &mut tokens);
243 self.next();
244 }
245 _ => self.push_token(UnaryNot, &mut tokens),
246 }
247 }
248 '.' => {
249 let next_char = self.lookahead().expect("Script does not end on '.'");
250 match next_char {
251 'a'..='z' | 'A'..='Z' => {
252 self.identifier(&mut tokens);
253 }
254 '0'..='9' => {
255 self.number_literal(&mut tokens);
256 }
257 _ => {
258 trace!(
259 "Found not alphabetic and non-numeric character after a dot. \
260 Treating it as an identifier."
261 );
262 self.identifier(&mut tokens);
263 }
264 }
265 }
266 '`' | '_' => {
267 self.identifier(&mut tokens);
268 }
269 '%' => {
270 let next_char = self.lookahead().expect("Script does not end on '%'");
271 match next_char {
272 '%' => {
273 self.push_token(Modulo, &mut tokens);
274 self.next();
275 self.next();
276 }
277 _ => {
278 let custom_binary_start = self.it;
279 self.next();
280 while self.current_char != '%' {
281 self.next();
282 }
283 let custom_binary_end = self.it;
284 self.push_token(
285 Special(&self.raw_source[custom_binary_start..=custom_binary_end]),
286 &mut tokens,
287 );
288 self.next()
289 }
290 }
291 }
292 'a'..='z' | 'A'..='Z' => match self.lookahead() {
293 Some('"') | Some('\'') => {
294 self.raw_string_literal(&mut tokens);
295 }
296 Some(_) | None => self.identifier_or_reserved(&mut tokens),
297 },
298 '0'..='9' => {
299 self.number_literal(&mut tokens);
300 }
301 '\\' => {
302 self.push_token(Lambda, &mut tokens);
303 self.next();
304 }
305 '#' => {
306 self.comment(&mut tokens);
307 }
308 '~' => {
309 self.push_token(Tilde, &mut tokens);
310 self.next();
311 }
312 '@' => {
313 self.push_token(Slot, &mut tokens);
314 self.next();
315 }
316 ':' => {
317 self.next();
318 let next = self.lookahead();
319
320 match (self.current_char, next) {
321 (':', Some(':')) => {
323 self.push_token(NsGetInt, &mut tokens);
324 self.next();
325 self.next();
326 }
327 (':', _) => {
329 self.push_token(NsGet, &mut tokens);
330 self.next()
331 }
332 ('=', _) => {
334 self.push_token(ColonAssign, &mut tokens);
335 self.next()
336 }
337 _ => self.push_token(Colon, &mut tokens),
339 }
340 }
341 c => {
342 trace!(
343 "Unexpected character '{c}' around:\n\"\"\"\n{}\n\"\"\"",
344 &self.raw_source[self.it.saturating_sub(15)
345 ..self.it.saturating_add(15).min(self.raw_source.len())]
346 );
347 self.next();
348 }
349 }
350 }
351 tokens.push(CommentedToken::new(EOF, self.offset));
352 trace!("Tokenized: {:?}", tokens);
353 Ok(tokens)
354 }
355
356 fn push_token(&mut self, token: Token<'a>, tokens: &mut Vec<CommentedToken<'a>>) {
357 tokens.push(CommentedToken::new(token, self.offset));
358 }
359
360 fn raw_string_literal(&mut self, tokens: &mut Vec<CommentedToken<'a>>) {
361 let start_offset = self.offset;
362 let start_it = self.it;
363 self.next();
365
366 let delimiter = self.current_char;
368 self.next();
369
370 const START_DELIMS: [char; 3] = ['(', '{', '['];
372 const END_DELIMS: [char; 3] = [')', '}', ']'];
373 let mut end = vec![];
374 while !START_DELIMS.contains(&self.current_char) {
375 end.push(self.current_char);
376 self.next();
377 }
378
379 end.push(
380 END_DELIMS[START_DELIMS
381 .into_iter()
382 .position(|c| c == self.current_char)
383 .expect("We just checked that START_DELIMS contains the current character")],
384 );
385 end.reverse();
386 end.push(delimiter);
387
388 self.next();
390
391 let mut matching_end_chars = 0;
393 while matching_end_chars < end.len() {
394 if end[matching_end_chars] == self.current_char {
395 matching_end_chars += 1;
396 }
397 self.next();
398 }
399
400 tokens.push(CommentedToken::new(
403 Literal(&self.raw_source[start_it..self.it]),
404 start_offset,
405 ));
406 }
407
408 fn string_literal(&mut self, tokens: &mut Vec<CommentedToken<'a>>) {
409 let start_offset = self.offset;
410 let start_it = self.it;
411 let delimiter = self.current_char;
412 let mut in_escape = false;
413 self.next();
414 while self.current_char != delimiter || in_escape {
415 if in_escape {
416 in_escape = !in_escape;
417 } else if self.current_char == '\\' {
418 in_escape = true;
419 }
420 self.next()
421 }
422 tokens.push(CommentedToken::new(
423 Literal(&self.raw_source[start_it..=self.it]),
424 start_offset,
425 ));
426 }
427
428 fn parse_decimal(&mut self) {
429 while self.it < self.raw_source.len() && self.current_char.is_ascii_digit() {
430 self.next();
431 }
432 }
433
434 fn parse_hexadecimal(&mut self) {
435 while self.it < self.raw_source.len() && self.current_char.is_ascii_hexdigit() {
436 self.next();
437 }
438 }
439
440 fn number_literal(&mut self, tokens: &mut Vec<CommentedToken<'a>>) {
441 let start_it = self.it;
442 let next = self.lookahead();
443 match (self.current_char, next) {
444 ('0', Some(next)) if next == 'x' || next == 'X' => {
447 self.next();
448 self.next();
449 self.parse_hexadecimal();
450 if self.current_char == '.' {
451 self.next();
452 self.parse_hexadecimal();
453 if self.current_char == 'p' || self.current_char == 'P' {
454 self.next();
455 self.parse_hexadecimal();
456 }
457 }
458 }
459 _ => {
461 self.parse_decimal();
462 let next = self.lookahead();
463 match (self.current_char, next) {
464 ('.', _) => {
465 self.next();
466 self.parse_decimal();
467 let next = self.lookahead();
468 match (self.current_char, next) {
469 ('e', Some(next)) if next == '+' || next == '-' => {
470 self.next();
471 self.next();
472 self.parse_decimal();
473 }
474 ('E', Some(next)) if next == '+' || next == '-' => {
475 self.next();
476 self.next();
477 self.parse_decimal();
478 }
479 ('e', _) | ('E', _) => {
480 self.next();
481 self.parse_decimal();
482 }
483 ('i', _) => {
484 self.next();
485 }
486 _ => {}
487 }
488 }
489 ('e', Some(next)) if next == '+' || next == '-' => {
490 self.next();
491 self.next();
492 self.parse_decimal();
493 }
494 ('E', Some(next)) if next == '+' || next == '-' => {
495 self.next();
496 self.next();
497 self.parse_decimal();
498 }
499 ('e', _) | ('E', _) => {
500 self.next();
501 self.parse_decimal();
502 }
503 ('L', _) => {
504 self.next();
505 }
506 ('i', _) => {
507 self.next();
508 }
509 _ => {}
510 }
511 }
512 }
513 self.push_token(Literal(&self.raw_source[start_it..self.it]), tokens);
514 }
515
516 fn identifier(&mut self, tokens: &mut Vec<CommentedToken<'a>>) {
517 let start_it = self.it;
518 let mut in_backticks = false;
519 let mut escaped = false;
520 while self.it < self.raw_source.len() && in_backticks
521 || self.current_char.is_alphabetic()
522 || self.current_char.is_ascii_digit()
523 || self.current_char == '.'
524 || self.current_char == '_'
525 || self.current_char == '`'
526 {
527 match self.current_char {
528 '\\' => {
529 escaped = true;
530 self.next();
531 }
532 '`' if escaped => {
533 escaped = false;
534 self.next();
535 }
536 '`' => {
537 in_backticks = !in_backticks;
538 self.next();
539 }
540 _ => self.next(),
541 }
542 }
543 match &self.raw_source[start_it..self.it] {
544 "TRUE" | "T" => self.push_token(Literal("TRUE"), tokens),
545 "FALSE" | "F" => self.push_token(Literal("FALSE"), tokens),
546 _ => self.push_token(Symbol(&self.raw_source[start_it..self.it]), tokens),
547 }
548 }
549
550 fn identifier_or_reserved(&mut self, tokens: &mut Vec<CommentedToken<'a>>) {
551 let start_it = self.it;
552 while self.it < self.raw_source.len() && !SYMBOL_ENDING.contains(&self.current_char) {
553 self.next();
554 }
555
556 match &self.raw_source[start_it..self.it] {
557 "continue" => self.push_token(Continue, tokens),
558 "break" => self.push_token(Break, tokens),
559 "for" => self.push_token(For, tokens),
560 "if" => self.push_token(If, tokens),
561 "else" => self.push_token(Else, tokens),
562 "in" => self.push_token(In, tokens),
563 "while" => self.push_token(While, tokens),
564 "repeat" => self.push_token(Repeat, tokens),
565 "function" => self.push_token(Function, tokens),
566 "TRUE" | "T" => self.push_token(Literal("TRUE"), tokens),
567 "FALSE" | "F" => self.push_token(Literal("FALSE"), tokens),
568 _ => self.push_token(Symbol(&self.raw_source[start_it..self.it]), tokens),
569 }
570 }
571
572 fn comment(&mut self, tokens: &mut Vec<CommentedToken<'a>>) {
573 let start_it = self.it;
574 while self.it < self.raw_source.len() && self.current_char != '\n' {
575 self.next();
576 }
577
578 match tokens.last() {
579 Some(CommentedToken {
580 token: Newline,
581 offset: _,
582 leading_comments: _,
583 inline_comment: _,
584 }) => self.push_token(Comment(&self.raw_source[start_it..self.it]), tokens),
585 Some(_) => self.push_token(InlineComment(&self.raw_source[start_it..self.it]), tokens),
586 None => self.push_token(Comment(&self.raw_source[start_it..self.it]), tokens),
587 }
588 }
589
590 fn next(&mut self) {
591 if let Some((new_offset, new_char)) = self.source.next() {
592 self.offset = new_offset;
593 self.it = new_offset;
594 self.current_char = new_char;
595 } else {
596 self.offset = self.raw_source.len();
597 self.it = self.raw_source.len();
598 }
599 }
600
601 fn lookahead(&self) -> Option<char> {
602 self.source
603 .clone()
604 .peekable()
605 .next()
606 .map(|(_, new_char)| new_char)
607 }
608}