floem_css_parser/
lexer.rs

1pub enum Token<'a> {
2    Selector { value: &'a str, line: usize },
3    Property { value: &'a str, line: usize },
4    Value { value: &'a str, line: usize },
5    BlockOpen { line: usize },
6    BlockClose { line: usize },
7    Colon { line: usize },
8    Semicolon { line: usize },
9    EOF,
10}
11
12impl std::fmt::Debug for Token<'_> {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        match self {
15            Token::Selector { value, .. } => write!(f, "Selector({value})"),
16            Token::Property { value, .. } => write!(f, "Property({value})"),
17            Token::Value { value, .. } => write!(f, "Value({value})"),
18            Token::BlockOpen { .. } => write!(f, "BlockOpen"),
19            Token::BlockClose { .. } => write!(f, "BlockClose"),
20            Token::Colon { .. } => write!(f, "Colon"),
21            Token::Semicolon { .. } => write!(f, "Semicolon"),
22            Token::EOF => write!(f, "EOF"),
23        }
24    }
25}
26
27pub struct Lexer<'a> {
28    input: &'a str,
29    position: usize,
30    line: usize,
31}
32
33impl<'a> Lexer<'a> {
34    #[must_use]
35    pub const fn new(input: &'a str) -> Self {
36        Lexer {
37            input,
38            position: 0,
39            line: 1,
40        }
41    }
42
43    fn advance(&mut self) -> Option<char> {
44        let ch = self.input[self.position..].chars().next()?;
45        self.position += ch.len_utf8();
46        if ch == '\n' {
47            self.line += 1;
48        }
49        Some(ch)
50    }
51
52    #[allow(clippy::while_let_loop)]
53    fn advance_until_line_stop(&mut self) {
54        loop {
55            if let Some(peek) = self.peek_char() {
56                if peek == ':' || peek == ';' || peek == '\n' {
57                    break;
58                }
59            }
60            self.advance();
61        }
62    }
63
64    #[allow(clippy::while_let_loop)]
65    fn advance_until_comment_end(&mut self) {
66        loop {
67            let Some(c) = self.advance() else {
68                break;
69            };
70            if let Some(peek) = self.peek_char() {
71                if c == '*' && peek == '/' {
72                    break;
73                }
74            }
75        }
76    }
77
78    fn peek_char(&self) -> Option<char> {
79        self.input[self.position..].chars().next()
80    }
81
82    #[allow(clippy::while_let_loop)]
83    fn skip_whitespace(&mut self) {
84        loop {
85            let Some(c) = self.peek_char() else {
86                break;
87            };
88            if c.is_whitespace() {
89                self.advance();
90            } else {
91                break;
92            }
93        }
94    }
95
96    #[must_use]
97    #[allow(clippy::while_let_loop)]
98    pub fn tokens(&mut self) -> Vec<Token<'a>> {
99        let mut tokens = Vec::with_capacity(1024 * 16); // Some size
100        let mut inside_block = false;
101        let mut start_pos;
102        loop {
103            let Some(c) = self.advance() else {
104                tokens.push(Token::EOF);
105                break;
106            };
107            match c {
108                '{' => {
109                    tokens.push(Token::BlockOpen { line: self.line });
110                    inside_block = true;
111                }
112                '}' => {
113                    tokens.push(Token::BlockClose { line: self.line });
114                    inside_block = false;
115                }
116                ':' if inside_block => {
117                    tokens.push(Token::Colon { line: self.line });
118                }
119                ';' => {
120                    tokens.push(Token::Semicolon { line: self.line });
121                }
122                '/' => {
123                    if let Some(peek) = self.peek_char() {
124                        if peek == '*' {
125                            self.advance_until_comment_end();
126                        }
127                    }
128                }
129                ' ' | '\n' | '\t' => {
130                    self.skip_whitespace();
131                }
132                _ => {
133                    start_pos = self.position - c.len_utf8();
134                    if inside_block {
135                        self.advance_until_line_stop();
136                        let value = &self.input[start_pos..self.position];
137                        if let Some(peek) = self.peek_char() {
138                            if peek == ':' || peek == '\n' {
139                                tokens.push(Token::Property {
140                                    line: self.line,
141                                    value,
142                                });
143                            } else if peek == ';' || peek == '\n' {
144                                tokens.push(Token::Value {
145                                    line: self.line,
146                                    value,
147                                });
148                            }
149                        }
150                    } else {
151                        loop {
152                            if let Some(peek) = self.peek_char() {
153                                if peek == '{' || peek == '\n' {
154                                    break;
155                                }
156                            } else {
157                                break;
158                            }
159                            self.advance();
160                        }
161
162                        tokens.push(Token::Selector {
163                            line: self.line,
164                            value: &self.input[start_pos..self.position],
165                        });
166                    }
167                }
168            }
169        }
170        tokens
171    }
172}