simplecss/
stream.rs

1// Copyright 2016 the SimpleCSS Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use 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        // TODO: this
40        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        // Check for opening quote.
222        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                // Advance until the closing quote.
233                if curr == quote {
234                    // Check for escaped quote.
235                    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            // Check for closing quote.
247            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}