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