| 1 | // Copyright 2016 the SimpleCSS Authors |
| 2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
| 3 | |
| 4 | use core::str; |
| 5 | |
| 6 | use crate::{Error, TextPos}; |
| 7 | |
| 8 | trait 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 | |
| 15 | impl 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)] |
| 45 | pub(crate) struct Stream<'a> { |
| 46 | text: &'a str, |
| 47 | pos: usize, |
| 48 | end: usize, |
| 49 | } |
| 50 | |
| 51 | impl<'a> From<&'a str> for Stream<'a> { |
| 52 | fn from(text: &'a str) -> Self { |
| 53 | Stream::new(text) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | impl<'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 | } |
| 318 | |