1use text_scanner::{ext::CssScannerExt, Scanner};
2
3use crate::{impl_lexer_from_scanner, ScanToken, ScannerExt, TokenSpan};
4
5const DELIMITERS: [char; 6] = ['{', '}', '[', ']', '(', ')'];
6const PUNCTUATIONS: [char; 12] = [',', '.', ';', ':', '-', '+', '*', '=', '#', '!', '@', '%'];
7
8#[derive(PartialEq, Eq, Clone, Copy, Debug)]
9pub enum CssToken {
10 Space,
11 BlockComment,
12 Ident,
13 AtKeyword,
14 Hash,
15 String,
16 Number,
17 Punct,
19 Delim,
21 Unknown,
27}
28
29impl ScanToken for CssToken {
30 fn scan_token<'text>(scanner: &mut Scanner<'text>) -> Option<(Self, TokenSpan<'text>)> {
31 let (r, _s) = scanner.skip_whitespace();
32 if !r.is_empty() {
33 return Some((Self::Space, scanner.span(r)));
34 }
35
36 if let Ok((r, _c)) = scanner.scan_css_identifier() {
37 return Some((Self::Ident, scanner.span(r)));
38 } else if let Ok((r, _c)) = scanner.scan_css_at_keyword() {
39 return Some((Self::AtKeyword, scanner.span(r)));
40 } else if let Ok((r, _c)) = scanner.scan_css_hash() {
41 return Some((Self::Hash, scanner.span(r)));
42 }
43
44 if let Ok((r, _c)) = scanner.scan_css_number() {
45 return Some((Self::Number, scanner.span(r)));
46 } else if let Ok((r, _c)) = scanner.scan_css_string() {
47 return Some((Self::String, scanner.span(r)));
48 }
49
50 if let Ok((r, _c)) = scanner.accept_char_any(&DELIMITERS) {
51 return Some((Self::Delim, scanner.span(r)));
52 } else if let Ok((r, _c)) = scanner.accept_char_any(&PUNCTUATIONS) {
53 return Some((Self::Punct, scanner.span(r)));
54 }
55
56 if let Ok((r, _c)) = scanner.scan_css_block_comment() {
57 return Some((Self::BlockComment, scanner.span(r)));
58 }
59
60 let (r, _c) = scanner.next().ok()?;
61 Some((Self::Unknown, scanner.span(r)))
62 }
63}
64
65#[derive(Clone, Debug)]
75pub struct CssLexer<'text> {
76 scanner: Scanner<'text>,
77}
78
79impl<'text> CssLexer<'text> {
80 #[inline]
81 pub fn new(text: &'text str) -> Self {
82 Self {
83 scanner: Scanner::new(text),
84 }
85 }
86}
87
88impl_lexer_from_scanner!('text, CssLexer<'text>, CssToken, scanner);
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_css_lexer_spans() {
96 let input = include_str!("../../../text-scanner/src/ext/rust.rs");
99 let mut output = String::new();
100
101 let lexer = CssLexer::new(input);
102 for (_tok, span) in lexer {
103 output.push_str(span.as_str());
104 }
105
106 assert_eq!(input, output);
107 }
108}