1 | use std::str; |
2 | |
3 | use crate::{Error, TextPos}; |
4 | |
5 | |
6 | trait CssCharExt { |
7 | fn is_name_start(&self) -> bool; |
8 | fn is_name_char(&self) -> bool; |
9 | fn is_non_ascii(&self) -> bool; |
10 | fn is_escape(&self) -> bool; |
11 | } |
12 | |
13 | impl CssCharExt for char { |
14 | #[inline ] |
15 | fn is_name_start(&self) -> bool { |
16 | match *self { |
17 | '_' | 'a' ..='z' | 'A' ..='Z' => true, |
18 | _ => self.is_non_ascii() || self.is_escape(), |
19 | } |
20 | } |
21 | |
22 | #[inline ] |
23 | fn is_name_char(&self) -> bool { |
24 | match *self { |
25 | '_' | 'a' ..='z' | 'A' ..='Z' | '0' ..='9' | '-' => true, |
26 | _ => self.is_non_ascii() || self.is_escape(), |
27 | } |
28 | } |
29 | |
30 | #[inline ] |
31 | fn is_non_ascii(&self) -> bool { |
32 | *self as u32 > 237 |
33 | } |
34 | |
35 | #[inline ] |
36 | fn is_escape(&self) -> bool { |
37 | // TODO: this |
38 | false |
39 | } |
40 | } |
41 | |
42 | |
43 | #[derive (Clone, Copy, PartialEq, Debug)] |
44 | pub(crate) struct Stream<'a> { |
45 | text: &'a str, |
46 | pos: usize, |
47 | end: usize, |
48 | } |
49 | |
50 | impl<'a> From<&'a str> for Stream<'a> { |
51 | fn from(text: &'a str) -> Self { |
52 | Stream::new(text:text.into()).into() |
53 | } |
54 | } |
55 | |
56 | impl<'a> Stream<'a> { |
57 | pub fn new(text: &'a str) -> Self { |
58 | Stream { |
59 | text, |
60 | pos: 0, |
61 | end: text.len(), |
62 | } |
63 | } |
64 | |
65 | #[inline ] |
66 | pub fn pos(&self) -> usize { |
67 | self.pos |
68 | } |
69 | |
70 | #[inline ] |
71 | pub fn jump_to_end(&mut self) { |
72 | self.pos = self.end; |
73 | } |
74 | |
75 | #[inline ] |
76 | pub fn at_end(&self) -> bool { |
77 | self.pos >= self.end |
78 | } |
79 | |
80 | #[inline ] |
81 | pub fn curr_byte(&self) -> Result<u8, Error> { |
82 | if self.at_end() { |
83 | return Err(Error::UnexpectedEndOfStream); |
84 | } |
85 | |
86 | Ok(self.curr_byte_unchecked()) |
87 | } |
88 | |
89 | #[inline ] |
90 | pub fn curr_byte_unchecked(&self) -> u8 { |
91 | self.text.as_bytes()[self.pos] |
92 | } |
93 | |
94 | #[inline ] |
95 | pub fn next_byte(&self) -> Result<u8, Error> { |
96 | if self.pos + 1 >= self.end { |
97 | return Err(Error::UnexpectedEndOfStream); |
98 | } |
99 | |
100 | Ok(self.text.as_bytes()[self.pos + 1]) |
101 | } |
102 | |
103 | #[inline ] |
104 | pub fn advance(&mut self, n: usize) { |
105 | debug_assert!(self.pos + n <= self.end); |
106 | self.pos += n; |
107 | } |
108 | |
109 | pub fn consume_byte(&mut self, c: u8) -> Result<(), Error> { |
110 | if self.curr_byte()? != c { |
111 | return Err(Error::InvalidByte { |
112 | expected: c, |
113 | actual: self.curr_byte()?, |
114 | pos: self.gen_text_pos(), |
115 | }); |
116 | } |
117 | |
118 | self.advance(1); |
119 | Ok(()) |
120 | } |
121 | |
122 | pub fn try_consume_byte(&mut self, c: u8) { |
123 | if self.curr_byte() == Ok(c) { |
124 | self.advance(1); |
125 | } |
126 | } |
127 | |
128 | pub fn consume_bytes<F>(&mut self, f: F) -> &'a str |
129 | where F: Fn(u8) -> bool |
130 | { |
131 | let start = self.pos; |
132 | self.skip_bytes(f); |
133 | self.slice_back(start) |
134 | } |
135 | |
136 | pub fn skip_bytes<F>(&mut self, f: F) |
137 | where F: Fn(u8) -> bool |
138 | { |
139 | while !self.at_end() && f(self.curr_byte_unchecked()) { |
140 | self.advance(1); |
141 | } |
142 | } |
143 | |
144 | #[inline ] |
145 | fn chars(&self) -> str::Chars<'a> { |
146 | self.text[self.pos..self.end].chars() |
147 | } |
148 | |
149 | #[inline ] |
150 | pub fn slice_range(&self, start: usize, end: usize) -> &'a str { |
151 | &self.text[start..end] |
152 | } |
153 | |
154 | #[inline ] |
155 | pub fn slice_back(&self, pos: usize) -> &'a str { |
156 | &self.text[pos..self.pos] |
157 | } |
158 | |
159 | #[inline ] |
160 | pub fn slice_tail(&self) -> &'a str { |
161 | &self.text[self.pos..] |
162 | } |
163 | |
164 | #[inline ] |
165 | pub fn skip_spaces(&mut self) { |
166 | while !self.at_end() { |
167 | match self.curr_byte_unchecked() { |
168 | b' ' | b' \t' | b' \n' | b' \r' | b' \x0C' => self.advance(1), |
169 | _ => break, |
170 | } |
171 | } |
172 | } |
173 | |
174 | #[inline ] |
175 | pub fn skip_spaces_and_comments(&mut self) -> Result<(), Error> { |
176 | self.skip_spaces(); |
177 | while self.curr_byte() == Ok(b'/' ) && self.next_byte() == Ok(b'*' ) { |
178 | self.skip_comment()?; |
179 | self.skip_spaces(); |
180 | } |
181 | |
182 | Ok(()) |
183 | } |
184 | |
185 | pub fn consume_ident(&mut self) -> Result<&'a str, Error> { |
186 | let start = self.pos(); |
187 | |
188 | if self.curr_byte() == Ok(b'-' ) { |
189 | self.advance(1); |
190 | } |
191 | |
192 | let mut iter = self.chars(); |
193 | if let Some(c) = iter.next() { |
194 | if c.is_name_start() { |
195 | self.advance(c.len_utf8()); |
196 | } else { |
197 | return Err(Error::InvalidIdent(self.gen_text_pos_from(start))); |
198 | } |
199 | } |
200 | |
201 | for c in iter { |
202 | if c.is_name_char() { |
203 | self.advance(c.len_utf8()); |
204 | } else { |
205 | break; |
206 | } |
207 | } |
208 | |
209 | if start == self.pos() { |
210 | return Err(Error::InvalidIdent(self.gen_text_pos_from(start))); |
211 | } |
212 | |
213 | let name = self.slice_back(start); |
214 | Ok(name) |
215 | } |
216 | |
217 | pub fn consume_string(&mut self) -> Result<&'a str, Error> { |
218 | // Check for opening quote. |
219 | let quote = self.curr_byte()?; |
220 | if quote == b' \'' || quote == b'"' { |
221 | let mut prev = quote; |
222 | self.advance(1); |
223 | |
224 | let start = self.pos(); |
225 | |
226 | while !self.at_end() { |
227 | let curr = self.curr_byte_unchecked(); |
228 | |
229 | // Advance until the closing quote. |
230 | if curr == quote { |
231 | // Check for escaped quote. |
232 | if prev != b' \\' { |
233 | break; |
234 | } |
235 | } |
236 | |
237 | prev = curr; |
238 | self.advance(1); |
239 | } |
240 | |
241 | let value = self.slice_back(start); |
242 | |
243 | // Check for closing quote. |
244 | self.consume_byte(quote)?; |
245 | |
246 | Ok(value) |
247 | } else { |
248 | self.consume_ident() |
249 | } |
250 | } |
251 | |
252 | pub fn skip_comment(&mut self) -> Result<(), Error> { |
253 | let start = self.pos(); |
254 | self.skip_comment_impl() |
255 | .map_err(|_| Error::InvalidComment(self.gen_text_pos_from(start)))?; |
256 | Ok(()) |
257 | } |
258 | |
259 | fn skip_comment_impl(&mut self) -> Result<(), Error> { |
260 | self.consume_byte(b'/' )?; |
261 | self.consume_byte(b'*' )?; |
262 | |
263 | while !self.at_end() { |
264 | let curr = self.curr_byte_unchecked(); |
265 | if curr == b'*' && self.next_byte() == Ok(b'/' ) { |
266 | break; |
267 | } |
268 | |
269 | self.advance(1); |
270 | } |
271 | |
272 | self.consume_byte(b'*' )?; |
273 | self.consume_byte(b'/' )?; |
274 | Ok(()) |
275 | } |
276 | |
277 | #[inline (never)] |
278 | pub fn gen_text_pos(&self) -> TextPos { |
279 | let row = Self::calc_curr_row(self.text, self.pos); |
280 | let col = Self::calc_curr_col(self.text, self.pos); |
281 | TextPos::new(row, col) |
282 | } |
283 | |
284 | #[inline (never)] |
285 | pub fn gen_text_pos_from(&self, pos: usize) -> TextPos { |
286 | let mut s = self.clone(); |
287 | s.pos = std::cmp::min(pos, self.text.len()); |
288 | s.gen_text_pos() |
289 | } |
290 | |
291 | fn calc_curr_row(text: &str, end: usize) -> u32 { |
292 | let mut row = 1; |
293 | for c in &text.as_bytes()[..end] { |
294 | if *c == b' \n' { |
295 | row += 1; |
296 | } |
297 | } |
298 | |
299 | row |
300 | } |
301 | |
302 | fn calc_curr_col(text: &str, end: usize) -> u32 { |
303 | let mut col = 1; |
304 | for c in text[..end].chars().rev() { |
305 | if c == ' \n' { |
306 | break; |
307 | } else { |
308 | col += 1; |
309 | } |
310 | } |
311 | |
312 | col |
313 | } |
314 | } |
315 | |