1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::convert::TryFrom; |
4 | use std::fmt; |
5 | use std::str::{CharIndices, FromStr}; |
6 | |
7 | #[derive (Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] |
8 | pub enum ReservedChar { |
9 | Comma, |
10 | OpenParenthese, |
11 | CloseParenthese, |
12 | OpenCurlyBrace, |
13 | CloseCurlyBrace, |
14 | OpenBracket, |
15 | CloseBracket, |
16 | Colon, |
17 | SemiColon, |
18 | Dot, |
19 | Quote, |
20 | DoubleQuote, |
21 | ExclamationMark, |
22 | QuestionMark, |
23 | Slash, |
24 | Modulo, |
25 | Star, |
26 | Minus, |
27 | Plus, |
28 | EqualSign, |
29 | Backslash, |
30 | Space, |
31 | Tab, |
32 | Backline, |
33 | LessThan, |
34 | SuperiorThan, |
35 | Pipe, |
36 | Ampersand, |
37 | BackTick, |
38 | } |
39 | |
40 | impl ReservedChar { |
41 | pub fn is_white_character(&self) -> bool { |
42 | *self == ReservedChar::Space |
43 | || *self == ReservedChar::Tab |
44 | || *self == ReservedChar::Backline |
45 | } |
46 | } |
47 | |
48 | impl fmt::Display for ReservedChar { |
49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
50 | write!( |
51 | f, |
52 | " {}" , |
53 | match *self { |
54 | ReservedChar::Comma => ',' , |
55 | ReservedChar::OpenParenthese => '(' , |
56 | ReservedChar::CloseParenthese => ')' , |
57 | ReservedChar::OpenCurlyBrace => '{' , |
58 | ReservedChar::CloseCurlyBrace => '}' , |
59 | ReservedChar::OpenBracket => '[' , |
60 | ReservedChar::CloseBracket => ']' , |
61 | ReservedChar::Colon => ':' , |
62 | ReservedChar::SemiColon => ';' , |
63 | ReservedChar::Dot => '.' , |
64 | ReservedChar::Quote => ' \'' , |
65 | ReservedChar::DoubleQuote => '"' , |
66 | ReservedChar::ExclamationMark => '!' , |
67 | ReservedChar::QuestionMark => '?' , |
68 | ReservedChar::Slash => '/' , |
69 | ReservedChar::Modulo => '%' , |
70 | ReservedChar::Star => '*' , |
71 | ReservedChar::Minus => '-' , |
72 | ReservedChar::Plus => '+' , |
73 | ReservedChar::EqualSign => '=' , |
74 | ReservedChar::Backslash => ' \\' , |
75 | ReservedChar::Space => ' ' , |
76 | ReservedChar::Tab => ' \t' , |
77 | ReservedChar::Backline => ' \n' , |
78 | ReservedChar::LessThan => '<' , |
79 | ReservedChar::SuperiorThan => '>' , |
80 | ReservedChar::Pipe => '|' , |
81 | ReservedChar::Ampersand => '&' , |
82 | ReservedChar::BackTick => '`' , |
83 | } |
84 | ) |
85 | } |
86 | } |
87 | |
88 | impl TryFrom<char> for ReservedChar { |
89 | type Error = &'static str; |
90 | |
91 | fn try_from(value: char) -> Result<ReservedChar, Self::Error> { |
92 | match value { |
93 | ',' => Ok(ReservedChar::Comma), |
94 | '(' => Ok(ReservedChar::OpenParenthese), |
95 | ')' => Ok(ReservedChar::CloseParenthese), |
96 | '{' => Ok(ReservedChar::OpenCurlyBrace), |
97 | '}' => Ok(ReservedChar::CloseCurlyBrace), |
98 | '[' => Ok(ReservedChar::OpenBracket), |
99 | ']' => Ok(ReservedChar::CloseBracket), |
100 | ':' => Ok(ReservedChar::Colon), |
101 | ';' => Ok(ReservedChar::SemiColon), |
102 | '.' => Ok(ReservedChar::Dot), |
103 | ' \'' => Ok(ReservedChar::Quote), |
104 | '"' => Ok(ReservedChar::DoubleQuote), |
105 | '!' => Ok(ReservedChar::ExclamationMark), |
106 | '?' => Ok(ReservedChar::QuestionMark), |
107 | '/' => Ok(ReservedChar::Slash), |
108 | '%' => Ok(ReservedChar::Modulo), |
109 | '*' => Ok(ReservedChar::Star), |
110 | '-' => Ok(ReservedChar::Minus), |
111 | '+' => Ok(ReservedChar::Plus), |
112 | '=' => Ok(ReservedChar::EqualSign), |
113 | ' \\' => Ok(ReservedChar::Backslash), |
114 | ' ' => Ok(ReservedChar::Space), |
115 | ' \t' => Ok(ReservedChar::Tab), |
116 | ' \n' | ' \r' => Ok(ReservedChar::Backline), |
117 | '<' => Ok(ReservedChar::LessThan), |
118 | '>' => Ok(ReservedChar::SuperiorThan), |
119 | '|' => Ok(ReservedChar::Pipe), |
120 | '&' => Ok(ReservedChar::Ampersand), |
121 | '`' => Ok(ReservedChar::BackTick), |
122 | _ => Err("Unknown reserved char" ), |
123 | } |
124 | } |
125 | } |
126 | |
127 | #[derive (Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] |
128 | pub enum Keyword { |
129 | Break, |
130 | Case, |
131 | Catch, |
132 | Const, |
133 | Continue, |
134 | Default, |
135 | Do, |
136 | Else, |
137 | False, |
138 | Finally, |
139 | Function, |
140 | For, |
141 | If, |
142 | In, |
143 | InstanceOf, |
144 | Let, |
145 | New, |
146 | Null, |
147 | Private, |
148 | Protected, |
149 | Public, |
150 | Return, |
151 | Switch, |
152 | This, |
153 | Throw, |
154 | True, |
155 | Try, |
156 | Typeof, |
157 | Static, |
158 | Var, |
159 | While, |
160 | } |
161 | |
162 | impl Keyword { |
163 | fn requires_before(&self) -> bool { |
164 | matches!(*self, Keyword::In | Keyword::InstanceOf) |
165 | } |
166 | } |
167 | |
168 | impl fmt::Display for Keyword { |
169 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
170 | write!( |
171 | f, |
172 | " {}" , |
173 | match *self { |
174 | Keyword::Break => "break" , |
175 | Keyword::Case => "case" , |
176 | Keyword::Catch => "catch" , |
177 | Keyword::Const => "const" , |
178 | Keyword::Continue => "continue" , |
179 | Keyword::Default => "default" , |
180 | Keyword::Do => "do" , |
181 | Keyword::Else => "else" , |
182 | Keyword::False => "false" , |
183 | Keyword::Finally => "finally" , |
184 | Keyword::Function => "function" , |
185 | Keyword::For => "for" , |
186 | Keyword::If => "if" , |
187 | Keyword::In => "in" , |
188 | Keyword::InstanceOf => "instanceof" , |
189 | Keyword::Let => "let" , |
190 | Keyword::New => "new" , |
191 | Keyword::Null => "null" , |
192 | Keyword::Private => "private" , |
193 | Keyword::Protected => "protected" , |
194 | Keyword::Public => "public" , |
195 | Keyword::Return => "return" , |
196 | Keyword::Switch => "switch" , |
197 | Keyword::This => "this" , |
198 | Keyword::Throw => "throw" , |
199 | Keyword::True => "true" , |
200 | Keyword::Try => "try" , |
201 | Keyword::Typeof => "typeof" , |
202 | Keyword::Static => "static" , |
203 | Keyword::Var => "var" , |
204 | Keyword::While => "while" , |
205 | } |
206 | ) |
207 | } |
208 | } |
209 | |
210 | impl<'a> TryFrom<&'a str> for Keyword { |
211 | type Error = &'static str; |
212 | |
213 | fn try_from(value: &str) -> Result<Keyword, Self::Error> { |
214 | match value { |
215 | "break" => Ok(Keyword::Break), |
216 | "case" => Ok(Keyword::Case), |
217 | "catch" => Ok(Keyword::Catch), |
218 | "const" => Ok(Keyword::Const), |
219 | "continue" => Ok(Keyword::Continue), |
220 | "default" => Ok(Keyword::Default), |
221 | "do" => Ok(Keyword::Do), |
222 | "else" => Ok(Keyword::Else), |
223 | "false" => Ok(Keyword::False), |
224 | "finally" => Ok(Keyword::Finally), |
225 | "function" => Ok(Keyword::Function), |
226 | "for" => Ok(Keyword::For), |
227 | "if" => Ok(Keyword::If), |
228 | "in" => Ok(Keyword::In), |
229 | "instanceof" => Ok(Keyword::InstanceOf), |
230 | "let" => Ok(Keyword::Let), |
231 | "new" => Ok(Keyword::New), |
232 | "null" => Ok(Keyword::Null), |
233 | "private" => Ok(Keyword::Private), |
234 | "protected" => Ok(Keyword::Protected), |
235 | "public" => Ok(Keyword::Public), |
236 | "return" => Ok(Keyword::Return), |
237 | "switch" => Ok(Keyword::Switch), |
238 | "this" => Ok(Keyword::This), |
239 | "throw" => Ok(Keyword::Throw), |
240 | "true" => Ok(Keyword::True), |
241 | "try" => Ok(Keyword::Try), |
242 | "typeof" => Ok(Keyword::Typeof), |
243 | "static" => Ok(Keyword::Static), |
244 | "var" => Ok(Keyword::Var), |
245 | "while" => Ok(Keyword::While), |
246 | _ => Err("Unkown keyword" ), |
247 | } |
248 | } |
249 | } |
250 | |
251 | #[derive (Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] |
252 | pub enum Condition { |
253 | And, |
254 | Or, |
255 | DifferentThan, |
256 | SuperDifferentThan, |
257 | EqualTo, |
258 | SuperEqualTo, |
259 | SuperiorThan, |
260 | SuperiorOrEqualTo, |
261 | InferiorThan, |
262 | InferiorOrEqualTo, |
263 | } |
264 | |
265 | impl fmt::Display for Condition { |
266 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
267 | write!( |
268 | f, |
269 | " {}" , |
270 | match *self { |
271 | Condition::And => "&&" , |
272 | Condition::Or => "||" , |
273 | Condition::DifferentThan => "!=" , |
274 | Condition::SuperDifferentThan => "!==" , |
275 | Condition::EqualTo => "==" , |
276 | Condition::SuperEqualTo => "===" , |
277 | Condition::SuperiorThan => ">" , |
278 | Condition::SuperiorOrEqualTo => ">=" , |
279 | Condition::InferiorThan => "<" , |
280 | Condition::InferiorOrEqualTo => "<=" , |
281 | } |
282 | ) |
283 | } |
284 | } |
285 | |
286 | impl TryFrom<ReservedChar> for Condition { |
287 | type Error = &'static str; |
288 | |
289 | fn try_from(value: ReservedChar) -> Result<Condition, Self::Error> { |
290 | Ok(match value { |
291 | ReservedChar::SuperiorThan => Condition::SuperiorThan, |
292 | ReservedChar::LessThan => Condition::InferiorThan, |
293 | _ => return Err("Unkown condition" ), |
294 | }) |
295 | } |
296 | } |
297 | |
298 | #[derive (Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] |
299 | pub enum Operation { |
300 | Addition, |
301 | AdditionEqual, |
302 | Subtract, |
303 | SubtractEqual, |
304 | Multiply, |
305 | MultiplyEqual, |
306 | Divide, |
307 | DivideEqual, |
308 | Modulo, |
309 | ModuloEqual, |
310 | Equal, |
311 | } |
312 | |
313 | impl Operation { |
314 | pub fn is_assign(&self) -> bool { |
315 | matches!( |
316 | *self, |
317 | Operation::AdditionEqual |
318 | | Operation::SubtractEqual |
319 | | Operation::MultiplyEqual |
320 | | Operation::DivideEqual |
321 | | Operation::ModuloEqual |
322 | | Operation::Equal |
323 | ) |
324 | } |
325 | } |
326 | |
327 | impl fmt::Display for Operation { |
328 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
329 | write!( |
330 | f, |
331 | " {}" , |
332 | match *self { |
333 | Operation::Addition => "+" , |
334 | Operation::AdditionEqual => "+=" , |
335 | Operation::Subtract => "-" , |
336 | Operation::SubtractEqual => "-=" , |
337 | Operation::Multiply => "*" , |
338 | Operation::MultiplyEqual => "*=" , |
339 | Operation::Divide => "/" , |
340 | Operation::DivideEqual => "/=" , |
341 | Operation::Modulo => "%" , |
342 | Operation::ModuloEqual => "%=" , |
343 | Operation::Equal => "=" , |
344 | } |
345 | ) |
346 | } |
347 | } |
348 | |
349 | impl TryFrom<ReservedChar> for Operation { |
350 | type Error = &'static str; |
351 | |
352 | fn try_from(value: ReservedChar) -> Result<Operation, Self::Error> { |
353 | Ok(match value { |
354 | ReservedChar::Plus => Operation::Addition, |
355 | ReservedChar::Minus => Operation::Subtract, |
356 | ReservedChar::Slash => Operation::Divide, |
357 | ReservedChar::Star => Operation::Multiply, |
358 | ReservedChar::Modulo => Operation::Modulo, |
359 | ReservedChar::EqualSign => Operation::Equal, |
360 | _ => return Err("Unkown operation" ), |
361 | }) |
362 | } |
363 | } |
364 | |
365 | #[derive (Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] |
366 | pub enum Token<'a> { |
367 | Keyword(Keyword), |
368 | Char(ReservedChar), |
369 | String(&'a str), |
370 | Comment(&'a str), |
371 | License(&'a str), |
372 | Other(&'a str), |
373 | Regex { |
374 | regex: &'a str, |
375 | is_global: bool, |
376 | is_interactive: bool, |
377 | }, |
378 | Condition(Condition), |
379 | Operation(Operation), |
380 | CreatedVarDecl(String), |
381 | CreatedVar(String), |
382 | Number(usize), |
383 | FloatingNumber(&'a str), |
384 | } |
385 | |
386 | impl<'a> fmt::Display for Token<'a> { |
387 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
388 | match *self { |
389 | Token::Keyword(x) => write!(f, " {}" , x), |
390 | Token::Char(x) => write!(f, " {}" , x), |
391 | Token::String(x) | Token::Comment(x) | Token::Other(x) => write!(f, " {}" , x), |
392 | Token::License(x) => write!(f, "/*! {}*/" , x), |
393 | Token::Regex { |
394 | regex, |
395 | is_global, |
396 | is_interactive, |
397 | } => { |
398 | let x = write!(f, "/ {}/" , regex); |
399 | if is_global { |
400 | write!(f, "g" )?; |
401 | } |
402 | if is_interactive { |
403 | write!(f, "i" )?; |
404 | } |
405 | x |
406 | } |
407 | Token::Condition(x) => write!(f, " {}" , x), |
408 | Token::Operation(x) => write!(f, " {}" , x), |
409 | Token::CreatedVarDecl(ref x) => write!(f, " {}" , x), |
410 | Token::CreatedVar(ref x) => write!(f, " {}" , x), |
411 | Token::Number(x) => write!(f, " {}" , x), |
412 | Token::FloatingNumber(ref x) => write!(f, " {}" , x), |
413 | } |
414 | } |
415 | } |
416 | |
417 | impl<'a> Token<'a> { |
418 | pub fn is_comment(&self) -> bool { |
419 | matches!(*self, Token::Comment(_)) |
420 | } |
421 | |
422 | pub fn is_license(&self) -> bool { |
423 | matches!(*self, Token::License(_)) |
424 | } |
425 | |
426 | pub fn is_reserved_char(&self) -> bool { |
427 | matches!(*self, Token::Char(_)) |
428 | } |
429 | |
430 | pub fn get_char(&self) -> Option<ReservedChar> { |
431 | match *self { |
432 | Token::Char(c) => Some(c), |
433 | _ => None, |
434 | } |
435 | } |
436 | |
437 | pub fn eq_char(&self, rc: ReservedChar) -> bool { |
438 | match *self { |
439 | Token::Char(c) => c == rc, |
440 | _ => false, |
441 | } |
442 | } |
443 | |
444 | pub fn eq_operation(&self, ope: Operation) -> bool { |
445 | match *self { |
446 | Token::Operation(o) => o == ope, |
447 | _ => false, |
448 | } |
449 | } |
450 | |
451 | pub fn is_operation(&self) -> bool { |
452 | matches!(*self, Token::Operation(_)) |
453 | } |
454 | |
455 | pub fn eq_condition(&self, cond: Condition) -> bool { |
456 | match *self { |
457 | Token::Condition(c) => c == cond, |
458 | _ => false, |
459 | } |
460 | } |
461 | |
462 | pub fn is_condition(&self) -> bool { |
463 | matches!(*self, Token::Condition(_)) |
464 | } |
465 | |
466 | pub fn is_other(&self) -> bool { |
467 | matches!(*self, Token::Other(_)) |
468 | } |
469 | |
470 | pub fn get_other(&self) -> Option<&str> { |
471 | match *self { |
472 | Token::Other(s) => Some(s), |
473 | _ => None, |
474 | } |
475 | } |
476 | |
477 | pub fn is_white_character(&self) -> bool { |
478 | match *self { |
479 | Token::Char(c) => c.is_white_character(), |
480 | _ => false, |
481 | } |
482 | } |
483 | |
484 | pub fn is_keyword(&self) -> bool { |
485 | matches!(*self, Token::Keyword(_)) |
486 | } |
487 | |
488 | pub fn get_keyword(&self) -> Option<Keyword> { |
489 | match *self { |
490 | Token::Keyword(k) => Some(k), |
491 | _ => None, |
492 | } |
493 | } |
494 | |
495 | pub fn is_string(&self) -> bool { |
496 | matches!(*self, Token::String(_)) |
497 | } |
498 | |
499 | pub fn get_string(&self) -> Option<&str> { |
500 | match *self { |
501 | Token::String(s) => Some(s), |
502 | _ => None, |
503 | } |
504 | } |
505 | |
506 | pub fn is_regex(&self) -> bool { |
507 | matches!(*self, Token::Regex { .. }) |
508 | } |
509 | |
510 | pub fn is_created_var_decl(&self) -> bool { |
511 | matches!(*self, Token::CreatedVarDecl(_)) |
512 | } |
513 | |
514 | pub fn is_created_var(&self) -> bool { |
515 | matches!(*self, Token::CreatedVar(_)) |
516 | } |
517 | |
518 | pub fn is_number(&self) -> bool { |
519 | matches!(*self, Token::Number(_)) |
520 | } |
521 | |
522 | pub fn is_floating_number(&self) -> bool { |
523 | matches!(*self, Token::FloatingNumber(_)) |
524 | } |
525 | |
526 | fn get_required(&self) -> Option<char> { |
527 | match *self { |
528 | Token::Keyword(_) |
529 | | Token::Other(_) |
530 | | Token::CreatedVarDecl(_) |
531 | | Token::Number(_) |
532 | | Token::FloatingNumber(_) => Some(' ' ), |
533 | _ => None, |
534 | } |
535 | } |
536 | |
537 | fn requires_before(&self) -> bool { |
538 | match *self { |
539 | Token::Keyword(k) => k.requires_before(), |
540 | _ => false, |
541 | } |
542 | } |
543 | } |
544 | |
545 | fn get_line_comment<'a>( |
546 | source: &'a str, |
547 | iterator: &mut MyPeekable<'_>, |
548 | start_pos: &mut usize, |
549 | ) -> Option<Token<'a>> { |
550 | *start_pos += 1; |
551 | for (pos: usize, c: char) in iterator { |
552 | if let Ok(c: ReservedChar) = ReservedChar::try_from(c) { |
553 | if c == ReservedChar::Backline { |
554 | let ret: Option> = Some(Token::Comment(&source[*start_pos..pos])); |
555 | *start_pos = pos; |
556 | return ret; |
557 | } |
558 | } |
559 | } |
560 | None |
561 | } |
562 | |
563 | fn get_regex<'a>( |
564 | source: &'a str, |
565 | iterator: &mut MyPeekable<'_>, |
566 | start_pos: &mut usize, |
567 | v: &[Token<'_>], |
568 | ) -> Option<Token<'a>> { |
569 | let mut back = v.len(); |
570 | while back > 0 { |
571 | back -= 1; |
572 | if v[back].is_white_character() || v[back].is_comment() || v[back].is_license() { |
573 | continue; |
574 | } |
575 | match &v[back] { |
576 | Token::Char(ReservedChar::SemiColon) |
577 | | Token::Char(ReservedChar::Colon) |
578 | | Token::Char(ReservedChar::Comma) |
579 | | Token::Char(ReservedChar::OpenBracket) |
580 | | Token::Char(ReservedChar::OpenParenthese) |
581 | | Token::Char(ReservedChar::ExclamationMark) |
582 | | Token::Char(ReservedChar::OpenCurlyBrace) |
583 | | Token::Char(ReservedChar::QuestionMark) |
584 | | Token::Char(ReservedChar::Backline) |
585 | | Token::Char(ReservedChar::Pipe) |
586 | | Token::Char(ReservedChar::Ampersand) => break, |
587 | t if t.is_operation() || t.is_condition() => break, |
588 | _ => return None, |
589 | } |
590 | } |
591 | iterator.start_save(); |
592 | while let Some((pos, c)) = iterator.next() { |
593 | if c == ' \\' { |
594 | // we skip next character |
595 | iterator.next(); |
596 | continue; |
597 | } |
598 | if let Ok(c) = ReservedChar::try_from(c) { |
599 | if c == ReservedChar::Slash { |
600 | let mut is_global = false; |
601 | let mut is_interactive = false; |
602 | let mut add = 0; |
603 | loop { |
604 | match iterator.peek() { |
605 | Some((_, 'i' )) => is_interactive = true, |
606 | Some((_, 'g' )) => is_global = true, |
607 | _ => break, |
608 | }; |
609 | iterator.next(); |
610 | add += 1; |
611 | } |
612 | let ret = Some(Token::Regex { |
613 | regex: &source[*start_pos + 1..pos], |
614 | is_interactive, |
615 | is_global, |
616 | }); |
617 | *start_pos = pos + add; |
618 | iterator.drop_save(); |
619 | return ret; |
620 | } else if c == ReservedChar::Backline { |
621 | break; |
622 | } |
623 | } |
624 | } |
625 | iterator.stop_save(); |
626 | None |
627 | } |
628 | |
629 | fn get_comment<'a>( |
630 | source: &'a str, |
631 | iterator: &mut MyPeekable<'_>, |
632 | start_pos: &mut usize, |
633 | ) -> Token<'a> { |
634 | let mut prev = ReservedChar::Quote; |
635 | *start_pos += 1; |
636 | let builder = if let Some((_, c)) = iterator.next() { |
637 | if c == '!' { |
638 | *start_pos += 1; |
639 | Token::License |
640 | } else { |
641 | if let Ok(c) = ReservedChar::try_from(c) { |
642 | prev = c; |
643 | } |
644 | Token::Comment |
645 | } |
646 | } else { |
647 | Token::Comment |
648 | }; |
649 | |
650 | let mut current_pos = *start_pos; |
651 | for (pos, c) in iterator { |
652 | current_pos = pos; |
653 | if let Ok(c) = ReservedChar::try_from(c) { |
654 | if c == ReservedChar::Slash && prev == ReservedChar::Star { |
655 | current_pos -= 2; |
656 | break; |
657 | } |
658 | prev = c; |
659 | } else { |
660 | prev = ReservedChar::Space; |
661 | } |
662 | } |
663 | // Unclosed comment so returning it anyway... |
664 | let ret = builder(&source[*start_pos..=current_pos]); |
665 | *start_pos = current_pos + 2; |
666 | ret |
667 | } |
668 | |
669 | fn get_string<'a>( |
670 | source: &'a str, |
671 | iterator: &mut MyPeekable<'_>, |
672 | start_pos: &mut usize, |
673 | start: ReservedChar, |
674 | ) -> Option<Token<'a>> { |
675 | while let Some((pos: usize, c: char)) = iterator.next() { |
676 | if c == ' \\' { |
677 | // we skip next character |
678 | iterator.next(); |
679 | continue; |
680 | } |
681 | if let Ok(c: ReservedChar) = ReservedChar::try_from(c) { |
682 | if c == start { |
683 | let ret: Option> = Some(Token::String(&source[*start_pos..pos + 1])); |
684 | *start_pos = pos; |
685 | return ret; |
686 | } |
687 | } |
688 | } |
689 | None |
690 | } |
691 | |
692 | fn get_backtick_string<'a>( |
693 | source: &'a str, |
694 | iterator: &mut MyPeekable<'_>, |
695 | start_pos: &mut usize, |
696 | ) -> Option<Token<'a>> { |
697 | while let Some((pos, c)) = iterator.next() { |
698 | if c == ' \\' { |
699 | // we skip next character |
700 | iterator.next(); |
701 | continue; |
702 | } |
703 | if c == '$' && iterator.peek().map(|(_, c)| c == '{' ).unwrap_or(false) { |
704 | let mut count = 0; |
705 | |
706 | loop { |
707 | if let Some((mut pos, c)) = iterator.next() { |
708 | if c == ' \\' { |
709 | // we skip next character |
710 | iterator.next(); |
711 | continue; |
712 | } else if c == '"' || c == ' \'' { |
713 | // We don't care about the result |
714 | get_string( |
715 | source, |
716 | iterator, |
717 | &mut pos, |
718 | ReservedChar::try_from(c) |
719 | .expect("ReservedChar::try_from unexpectedly failed..." ), |
720 | ); |
721 | } else if c == '`' { |
722 | get_backtick_string(source, iterator, &mut pos); |
723 | } else if c == '{' { |
724 | count += 1; |
725 | } else if c == '}' { |
726 | count -= 1; |
727 | if count == 0 { |
728 | break; |
729 | } |
730 | } |
731 | } else { |
732 | return None; |
733 | } |
734 | } |
735 | } else if c == '`' { |
736 | let ret = Some(Token::String(&source[*start_pos..pos + 1])); |
737 | *start_pos = pos; |
738 | return ret; |
739 | } |
740 | } |
741 | None |
742 | } |
743 | |
744 | fn first_useful<'a>(v: &'a [Token<'a>]) -> Option<&'a Token<'a>> { |
745 | for x: &Token<'_> in v.iter().rev() { |
746 | if x.is_white_character() { |
747 | continue; |
748 | } |
749 | return Some(x); |
750 | } |
751 | None |
752 | } |
753 | |
754 | fn fill_other<'a>(source: &'a str, v: &mut Vec<Token<'a>>, start: usize, pos: usize) { |
755 | if start < pos { |
756 | if let Ok(w: Keyword) = Keyword::try_from(&source[start..pos]) { |
757 | v.push(Token::Keyword(w)); |
758 | } else if let Ok(n: usize) = usize::from_str(&source[start..pos]) { |
759 | v.push(Token::Number(n)) |
760 | } else if f64::from_str(&source[start..pos]).is_ok() { |
761 | v.push(Token::FloatingNumber(&source[start..pos])) |
762 | } else { |
763 | v.push(Token::Other(&source[start..pos])); |
764 | } |
765 | } |
766 | } |
767 | |
768 | fn handle_equal_sign(v: &mut Vec<Token<'_>>, c: ReservedChar) -> bool { |
769 | if c != ReservedChar::EqualSign { |
770 | return false; |
771 | } |
772 | match v.last().unwrap_or(&Token::Other("" )) { |
773 | Token::Operation(Operation::Equal) => { |
774 | v.pop(); |
775 | v.push(Token::Condition(Condition::EqualTo)); |
776 | } |
777 | Token::Condition(Condition::EqualTo) => { |
778 | v.pop(); |
779 | v.push(Token::Condition(Condition::SuperEqualTo)); |
780 | } |
781 | Token::Char(ReservedChar::ExclamationMark) => { |
782 | v.pop(); |
783 | v.push(Token::Condition(Condition::DifferentThan)); |
784 | } |
785 | Token::Condition(Condition::DifferentThan) => { |
786 | v.pop(); |
787 | v.push(Token::Condition(Condition::SuperDifferentThan)); |
788 | } |
789 | Token::Operation(Operation::Divide) => { |
790 | v.pop(); |
791 | v.push(Token::Operation(Operation::DivideEqual)); |
792 | } |
793 | Token::Operation(Operation::Multiply) => { |
794 | v.pop(); |
795 | v.push(Token::Operation(Operation::MultiplyEqual)); |
796 | } |
797 | Token::Operation(Operation::Addition) => { |
798 | v.pop(); |
799 | v.push(Token::Operation(Operation::AdditionEqual)); |
800 | } |
801 | Token::Operation(Operation::Subtract) => { |
802 | v.pop(); |
803 | v.push(Token::Operation(Operation::SubtractEqual)); |
804 | } |
805 | Token::Operation(Operation::Modulo) => { |
806 | v.pop(); |
807 | v.push(Token::Operation(Operation::ModuloEqual)); |
808 | } |
809 | Token::Condition(Condition::SuperiorThan) => { |
810 | v.pop(); |
811 | v.push(Token::Condition(Condition::SuperiorOrEqualTo)); |
812 | } |
813 | Token::Condition(Condition::InferiorThan) => { |
814 | v.pop(); |
815 | v.push(Token::Condition(Condition::InferiorOrEqualTo)); |
816 | } |
817 | _ => { |
818 | return false; |
819 | } |
820 | } |
821 | true |
822 | } |
823 | |
824 | fn check_if_number(iterator: &mut MyPeekable<'_>, start: usize, pos: usize, source: &str) -> bool { |
825 | if source[start..pos].find('.' ).is_some() { |
826 | return false; |
827 | } else if u64::from_str(&source[start..pos]).is_ok() { |
828 | return true; |
829 | } else if let Some((_, x: char)) = iterator.peek() { |
830 | return x as u8 >= b'0' && x as u8 <= b'9' ; |
831 | } |
832 | false |
833 | } |
834 | |
835 | struct MyPeekable<'a> { |
836 | inner: CharIndices<'a>, |
837 | saved: Vec<(usize, char)>, |
838 | peeked: Option<(usize, char)>, |
839 | is_saving: bool, |
840 | } |
841 | |
842 | impl<'a> MyPeekable<'a> { |
843 | fn new(indices: CharIndices<'a>) -> MyPeekable<'a> { |
844 | MyPeekable { |
845 | inner: indices, |
846 | saved: Vec::with_capacity(500), |
847 | peeked: None, |
848 | is_saving: false, |
849 | } |
850 | } |
851 | |
852 | fn start_save(&mut self) { |
853 | self.is_saving = true; |
854 | if let Some(p) = self.peeked { |
855 | self.saved.push(p); |
856 | } |
857 | } |
858 | |
859 | fn drop_save(&mut self) { |
860 | self.is_saving = false; |
861 | self.saved.clear(); |
862 | } |
863 | |
864 | fn stop_save(&mut self) { |
865 | self.is_saving = false; |
866 | if let Some(p) = self.peeked { |
867 | self.saved.push(p); |
868 | } |
869 | self.peeked = None; |
870 | } |
871 | |
872 | /// Returns None if saving. |
873 | fn peek(&mut self) -> Option<(usize, char)> { |
874 | if self.peeked.is_none() { |
875 | self.peeked = self.inner.next(); |
876 | if self.is_saving { |
877 | if let Some(p) = self.peeked { |
878 | self.saved.push(p); |
879 | } |
880 | } |
881 | } |
882 | self.peeked |
883 | } |
884 | } |
885 | |
886 | impl<'a> Iterator for MyPeekable<'a> { |
887 | type Item = (usize, char); |
888 | |
889 | fn next(&mut self) -> Option<Self::Item> { |
890 | if self.peeked.is_some() { |
891 | self.peeked.take() |
892 | } else { |
893 | if !self.is_saving && !self.saved.is_empty() { |
894 | return Some(self.saved.remove(index:0)); |
895 | } |
896 | match self.inner.next() { |
897 | Some(r: (usize, char)) if self.is_saving => { |
898 | self.saved.push(r); |
899 | Some(r) |
900 | } |
901 | r: Option<(usize, char)> => r, |
902 | } |
903 | } |
904 | } |
905 | } |
906 | |
907 | pub fn tokenize(source: &str) -> Tokens<'_> { |
908 | let mut v = Vec::with_capacity(1000); |
909 | let mut start = 0; |
910 | let mut iterator = MyPeekable::new(source.char_indices()); |
911 | |
912 | loop { |
913 | let (mut pos, c) = match iterator.next() { |
914 | Some(x) => x, |
915 | None => { |
916 | fill_other(source, &mut v, start, source.len()); |
917 | break; |
918 | } |
919 | }; |
920 | if let Ok(c) = ReservedChar::try_from(c) { |
921 | if c == ReservedChar::Dot && check_if_number(&mut iterator, start, pos, source) { |
922 | let mut cont = true; |
923 | if let Some(x) = iterator.peek() { |
924 | if !"0123456789,; \t\n<>/*&|{}[]-+=~%^:!" .contains(x.1) { |
925 | fill_other(source, &mut v, start, pos); |
926 | start = pos; |
927 | cont = false; |
928 | } |
929 | } |
930 | if cont { |
931 | continue; |
932 | } |
933 | } |
934 | fill_other(source, &mut v, start, pos); |
935 | match c { |
936 | ReservedChar::Quote | ReservedChar::DoubleQuote => { |
937 | if let Some(s) = get_string(source, &mut iterator, &mut pos, c) { |
938 | v.push(s); |
939 | } |
940 | } |
941 | ReservedChar::BackTick => { |
942 | if let Some(s) = get_backtick_string(source, &mut iterator, &mut pos) { |
943 | v.push(s); |
944 | } |
945 | } |
946 | ReservedChar::Slash |
947 | if v.last() |
948 | .unwrap_or(&Token::Other("" )) |
949 | .eq_operation(Operation::Divide) => |
950 | { |
951 | v.pop(); |
952 | if let Some(s) = get_line_comment(source, &mut iterator, &mut pos) { |
953 | v.push(s); |
954 | } |
955 | } |
956 | ReservedChar::Slash |
957 | if iterator.peek().is_some() |
958 | && iterator.peek().unwrap().1 != '/' |
959 | && iterator.peek().unwrap().1 != '*' |
960 | && !first_useful(&v).unwrap_or(&Token::String("" )).is_other() => |
961 | { |
962 | if let Some(r) = get_regex(source, &mut iterator, &mut pos, &v) { |
963 | v.push(r); |
964 | } else { |
965 | v.push(Token::Operation(Operation::Divide)); |
966 | } |
967 | } |
968 | ReservedChar::Star |
969 | if v.last() |
970 | .unwrap_or(&Token::Other("" )) |
971 | .eq_operation(Operation::Divide) => |
972 | { |
973 | v.pop(); |
974 | v.push(get_comment(source, &mut iterator, &mut pos)); |
975 | } |
976 | ReservedChar::Pipe |
977 | if v.last() |
978 | .unwrap_or(&Token::Other("" )) |
979 | .eq_char(ReservedChar::Pipe) => |
980 | { |
981 | v.pop(); |
982 | v.push(Token::Condition(Condition::Or)); |
983 | } |
984 | ReservedChar::Ampersand |
985 | if v.last() |
986 | .unwrap_or(&Token::Other("" )) |
987 | .eq_char(ReservedChar::Ampersand) => |
988 | { |
989 | v.pop(); |
990 | v.push(Token::Condition(Condition::And)); |
991 | } |
992 | _ if handle_equal_sign(&mut v, c) => {} |
993 | _ => { |
994 | if let Ok(o) = Operation::try_from(c) { |
995 | v.push(Token::Operation(o)); |
996 | } else if let Ok(o) = Condition::try_from(c) { |
997 | v.push(Token::Condition(o)); |
998 | } else { |
999 | v.push(Token::Char(c)); |
1000 | } |
1001 | } |
1002 | } |
1003 | start = pos + 1; |
1004 | } |
1005 | } |
1006 | Tokens(v) |
1007 | } |
1008 | |
1009 | #[derive (Debug, PartialEq, Eq)] |
1010 | pub struct Tokens<'a>(pub Vec<Token<'a>>); |
1011 | |
1012 | macro_rules! tokens_writer { |
1013 | ($self:ident, $w:ident) => { |
1014 | let tokens = &$self.0; |
1015 | for i in 0..tokens.len() { |
1016 | if i > 0 |
1017 | && tokens[i].requires_before() |
1018 | && !tokens[i - 1].is_keyword() |
1019 | && !tokens[i - 1].is_other() |
1020 | && !tokens[i - 1].is_reserved_char() |
1021 | && !tokens[i - 1].is_string() |
1022 | { |
1023 | write!($w, " " )?; |
1024 | } |
1025 | write!($w, "{}" , tokens[i])?; |
1026 | if let Some(c) = match tokens[i] { |
1027 | Token::Keyword(_) | Token::Other(_) if i + 1 < tokens.len() => { |
1028 | tokens[i + 1].get_required() |
1029 | } |
1030 | _ => None, |
1031 | } { |
1032 | write!($w, "{}" , c)?; |
1033 | } |
1034 | } |
1035 | }; |
1036 | } |
1037 | |
1038 | impl<'a> Tokens<'a> { |
1039 | pub(super) fn write<W: std::io::Write>(self, mut w: W) -> std::io::Result<()> { |
1040 | tokens_writer!(self, w); |
1041 | Ok(()) |
1042 | } |
1043 | } |
1044 | |
1045 | impl<'a> fmt::Display for Tokens<'a> { |
1046 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1047 | tokens_writer!(self, f); |
1048 | Ok(()) |
1049 | } |
1050 | } |
1051 | |
1052 | impl<'a> Tokens<'a> { |
1053 | #[must_use ] |
1054 | pub fn apply<F>(self, func: F) -> Tokens<'a> |
1055 | where |
1056 | F: Fn(Tokens<'a>) -> Tokens<'a>, |
1057 | { |
1058 | func(self) |
1059 | } |
1060 | } |
1061 | |
1062 | impl<'a> IntoIterator for Tokens<'a> { |
1063 | type Item = Token<'a>; |
1064 | type IntoIter = std::vec::IntoIter<Token<'a>>; |
1065 | |
1066 | fn into_iter(self) -> Self::IntoIter { |
1067 | self.0.into_iter() |
1068 | } |
1069 | } |
1070 | |
1071 | impl<'a> std::ops::Deref for Tokens<'a> { |
1072 | type Target = Vec<Token<'a>>; |
1073 | |
1074 | fn deref(&self) -> &Self::Target { |
1075 | &self.0 |
1076 | } |
1077 | } |
1078 | |
1079 | impl<'a> From<Vec<Token<'a>>> for Tokens<'a> { |
1080 | fn from(v: Vec<Token<'a>>) -> Self { |
1081 | Tokens(v) |
1082 | } |
1083 | } |
1084 | |
1085 | impl<'a> From<&[Token<'a>]> for Tokens<'a> { |
1086 | fn from(v: &[Token<'a>]) -> Self { |
1087 | Tokens(v.to_vec()) |
1088 | } |
1089 | } |
1090 | |
1091 | #[test ] |
1092 | fn check_regex() { |
1093 | let source = r#"var x = /"\.x/g;"# ; |
1094 | let expected_result = r#"var x=/"\.x/g"# ; |
1095 | assert_eq!(crate::js::minify(source).to_string(), expected_result); |
1096 | |
1097 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1098 | assert_eq!( |
1099 | v.0[3], |
1100 | Token::Regex { |
1101 | regex: " \"\\.x" , |
1102 | is_global: true, |
1103 | is_interactive: false, |
1104 | } |
1105 | ); |
1106 | |
1107 | let source = r#"var x = /"\.x/gigigigig;var x = "hello";"# ; |
1108 | let expected_result = r#"var x=/"\.x/gi;var x="hello""# ; |
1109 | assert_eq!(crate::js::minify(source).to_string(), expected_result); |
1110 | |
1111 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1112 | assert_eq!( |
1113 | v.0[3], |
1114 | Token::Regex { |
1115 | regex: " \"\\.x" , |
1116 | is_global: true, |
1117 | is_interactive: true, |
1118 | } |
1119 | ); |
1120 | } |
1121 | |
1122 | #[test ] |
1123 | fn more_regex() { |
1124 | let source = r#"var x = /"\.x\/a/i;"# ; |
1125 | let expected_result = r#"var x=/"\.x\/a/i"# ; |
1126 | assert_eq!(crate::js::minify(source).to_string(), expected_result); |
1127 | |
1128 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1129 | assert_eq!( |
1130 | v.0[3], |
1131 | Token::Regex { |
1132 | regex: " \"\\.x \\/a" , |
1133 | is_global: false, |
1134 | is_interactive: true, |
1135 | } |
1136 | ); |
1137 | |
1138 | let source = r#"var x = /\\/i;"# ; |
1139 | let expected_result = r#"var x=/\\/i"# ; |
1140 | assert_eq!(crate::js::minify(source).to_string(), expected_result); |
1141 | |
1142 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1143 | assert_eq!( |
1144 | v.0[3], |
1145 | Token::Regex { |
1146 | regex: " \\\\" , |
1147 | is_global: false, |
1148 | is_interactive: true, |
1149 | } |
1150 | ); |
1151 | } |
1152 | |
1153 | #[test ] |
1154 | fn even_more_regex() { |
1155 | let source: &str = r#"var x = /a-z /;"# ; |
1156 | |
1157 | let v: Tokens<'_> = tokenize(source).apply(func:crate::js::clean_tokens); |
1158 | assert_eq!( |
1159 | v.0[3], |
1160 | Token::Regex { |
1161 | regex: "a-z " , |
1162 | is_global: false, |
1163 | is_interactive: false, |
1164 | } |
1165 | ); |
1166 | } |
1167 | |
1168 | #[test ] |
1169 | fn not_regex_test() { |
1170 | let source = "( x ) / 2; x / y;x /= y" ; |
1171 | |
1172 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1173 | assert_eq!( |
1174 | &v.0, |
1175 | &[ |
1176 | Token::Char(ReservedChar::OpenParenthese), |
1177 | Token::Other("x" ), |
1178 | Token::Char(ReservedChar::CloseParenthese), |
1179 | Token::Operation(Operation::Divide), |
1180 | Token::Number(2), |
1181 | Token::Char(ReservedChar::SemiColon), |
1182 | Token::Other("x" ), |
1183 | Token::Operation(Operation::Divide), |
1184 | Token::Other("y" ), |
1185 | Token::Char(ReservedChar::SemiColon), |
1186 | Token::Other("x" ), |
1187 | Token::Operation(Operation::DivideEqual), |
1188 | Token::Other("y" ) |
1189 | ] |
1190 | ); |
1191 | |
1192 | let source = "let x = /x \ny/;" ; |
1193 | |
1194 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1195 | assert_eq!( |
1196 | &v.0, |
1197 | &[ |
1198 | Token::Keyword(Keyword::Let), |
1199 | Token::Other("x" ), |
1200 | Token::Operation(Operation::Equal), |
1201 | Token::Operation(Operation::Divide), |
1202 | Token::Other("x" ), |
1203 | Token::Other("y" ), |
1204 | Token::Operation(Operation::Divide) |
1205 | ] |
1206 | ); |
1207 | } |
1208 | |
1209 | #[test ] |
1210 | fn test_tokens_parsing() { |
1211 | let source: &str = "true = == 2.3 === 32" ; |
1212 | |
1213 | let v: Tokens<'_> = tokenize(source).apply(func:crate::js::clean_tokens); |
1214 | assert_eq!( |
1215 | &v.0, |
1216 | &[ |
1217 | Token::Keyword(Keyword::True), |
1218 | Token::Operation(Operation::Equal), |
1219 | Token::Condition(Condition::EqualTo), |
1220 | Token::FloatingNumber("2.3" ), |
1221 | Token::Condition(Condition::SuperEqualTo), |
1222 | Token::Number(32) |
1223 | ] |
1224 | ); |
1225 | } |
1226 | |
1227 | #[test ] |
1228 | fn test_string_parsing() { |
1229 | let source: &str = "var x = 'hello people!'" ; |
1230 | |
1231 | let v: Tokens<'_> = tokenize(source).apply(func:crate::js::clean_tokens); |
1232 | assert_eq!( |
1233 | &v.0, |
1234 | &[ |
1235 | Token::Keyword(Keyword::Var), |
1236 | Token::Other("x" ), |
1237 | Token::Operation(Operation::Equal), |
1238 | Token::String(" \'hello people! \'" ) |
1239 | ] |
1240 | ); |
1241 | } |
1242 | |
1243 | #[test ] |
1244 | fn test_number_parsing() { |
1245 | let source = "var x = .12; let y = 4.; var z = 12; .3 4. 'a' let u = 12.2" ; |
1246 | |
1247 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1248 | assert_eq!( |
1249 | &v.0, |
1250 | &[ |
1251 | Token::Keyword(Keyword::Var), |
1252 | Token::Other("x" ), |
1253 | Token::Operation(Operation::Equal), |
1254 | Token::FloatingNumber(".12" ), |
1255 | Token::Char(ReservedChar::SemiColon), |
1256 | Token::Keyword(Keyword::Let), |
1257 | Token::Other("y" ), |
1258 | Token::Operation(Operation::Equal), |
1259 | Token::FloatingNumber("4." ), |
1260 | Token::Char(ReservedChar::SemiColon), |
1261 | Token::Keyword(Keyword::Var), |
1262 | Token::Other("z" ), |
1263 | Token::Operation(Operation::Equal), |
1264 | Token::Number(12), |
1265 | Token::Char(ReservedChar::SemiColon), |
1266 | Token::FloatingNumber(".3" ), |
1267 | Token::FloatingNumber("4." ), |
1268 | Token::String("'a'" ), |
1269 | Token::Keyword(Keyword::Let), |
1270 | Token::Other("u" ), |
1271 | Token::Operation(Operation::Equal), |
1272 | Token::FloatingNumber("12.2" ) |
1273 | ] |
1274 | ); |
1275 | } |
1276 | |
1277 | #[test ] |
1278 | fn test_number_parsing2() { |
1279 | let source: &str = "var x = 12.a;" ; |
1280 | |
1281 | let v: Tokens<'_> = tokenize(source).apply(func:crate::js::clean_tokens); |
1282 | assert_eq!( |
1283 | &v.0, |
1284 | &[ |
1285 | Token::Keyword(Keyword::Var), |
1286 | Token::Other("x" ), |
1287 | Token::Operation(Operation::Equal), |
1288 | Token::Number(12), |
1289 | Token::Char(ReservedChar::Dot), |
1290 | Token::Other("a" ) |
1291 | ] |
1292 | ); |
1293 | } |
1294 | |
1295 | #[test ] |
1296 | fn tokens_spaces() { |
1297 | let source: &str = "t in e" ; |
1298 | |
1299 | let v: Tokens<'_> = tokenize(source).apply(func:crate::js::clean_tokens); |
1300 | assert_eq!( |
1301 | &v.0, |
1302 | &[ |
1303 | Token::Other("t" ), |
1304 | Token::Keyword(Keyword::In), |
1305 | Token::Other("e" ) |
1306 | ] |
1307 | ); |
1308 | } |
1309 | |
1310 | #[test ] |
1311 | fn division_by_id() { |
1312 | let source: &str = "100/abc" ; |
1313 | |
1314 | let v: Tokens<'_> = tokenize(source).apply(func:crate::js::clean_tokens); |
1315 | assert_eq!( |
1316 | &v.0, |
1317 | &[ |
1318 | Token::Number(100), |
1319 | Token::Operation(Operation::Divide), |
1320 | Token::Other("abc" ) |
1321 | ] |
1322 | ); |
1323 | } |
1324 | |
1325 | #[test ] |
1326 | fn weird_regex() { |
1327 | let source = "if (!/ \\/(contact|legal) \\//.test(a)) {}" ; |
1328 | |
1329 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1330 | assert_eq!( |
1331 | &v.0, |
1332 | &[ |
1333 | Token::Keyword(Keyword::If), |
1334 | Token::Char(ReservedChar::OpenParenthese), |
1335 | Token::Char(ReservedChar::ExclamationMark), |
1336 | Token::Regex { |
1337 | regex: " \\/(contact|legal) \\/" , |
1338 | is_global: false, |
1339 | is_interactive: false |
1340 | }, |
1341 | Token::Char(ReservedChar::Dot), |
1342 | Token::Other("test" ), |
1343 | Token::Char(ReservedChar::OpenParenthese), |
1344 | Token::Other("a" ), |
1345 | Token::Char(ReservedChar::CloseParenthese), |
1346 | Token::Char(ReservedChar::CloseParenthese), |
1347 | Token::Char(ReservedChar::OpenCurlyBrace), |
1348 | Token::Char(ReservedChar::CloseCurlyBrace), |
1349 | ] |
1350 | ); |
1351 | } |
1352 | |
1353 | #[test ] |
1354 | fn test_regexes() { |
1355 | let source = "/ \\/(contact|legal) \\//.test" ; |
1356 | |
1357 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1358 | assert_eq!( |
1359 | &v.0, |
1360 | &[ |
1361 | Token::Regex { |
1362 | regex: " \\/(contact|legal) \\/" , |
1363 | is_global: false, |
1364 | is_interactive: false |
1365 | }, |
1366 | Token::Char(ReservedChar::Dot), |
1367 | Token::Other("test" ), |
1368 | ] |
1369 | ); |
1370 | |
1371 | let source = "/ \\*(contact|legal)/.test" ; |
1372 | |
1373 | let v = tokenize(source).apply(crate::js::clean_tokens); |
1374 | assert_eq!( |
1375 | &v.0, |
1376 | &[ |
1377 | Token::Regex { |
1378 | regex: " \\*(contact|legal)" , |
1379 | is_global: false, |
1380 | is_interactive: false |
1381 | }, |
1382 | Token::Char(ReservedChar::Dot), |
1383 | Token::Other("test" ), |
1384 | ] |
1385 | ); |
1386 | } |
1387 | |
1388 | #[test ] |
1389 | fn test_comments() { |
1390 | let source: &str = "/*(contact|legal)/.test" ; |
1391 | |
1392 | let v: Tokens<'_> = tokenize(source); |
1393 | assert_eq!(&v.0, &[Token::Comment("(contact|legal)/.test" ),],); |
1394 | |
1395 | let source: &str = "/*(contact|legal)/.test*/ a" ; |
1396 | |
1397 | let v: Tokens<'_> = tokenize(source); |
1398 | assert_eq!( |
1399 | &v.0, |
1400 | &[ |
1401 | Token::Comment("(contact|legal)/.test" ), |
1402 | Token::Char(ReservedChar::Space), |
1403 | Token::Other("a" ), |
1404 | ], |
1405 | ); |
1406 | } |
1407 | |