1use std::str;
2
3use crate::{Error, TextPos};
4
5
6trait 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
13impl 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)]
44pub(crate) struct Stream<'a> {
45 text: &'a str,
46 pos: usize,
47 end: usize,
48}
49
50impl<'a> From<&'a str> for Stream<'a> {
51 fn from(text: &'a str) -> Self {
52 Stream::new(text:text.into()).into()
53 }
54}
55
56impl<'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