floem_css_parser/
lexer.rs1pub 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); 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}