mod.rs (14496B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Used for parsing and serializing the [`@property`] syntax string. 6 //! 7 //! <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax> 8 9 use std::fmt::{self, Debug}; 10 use std::{borrow::Cow, fmt::Write}; 11 12 use crate::derives::*; 13 use crate::parser::{Parse, ParserContext}; 14 use crate::values::CustomIdent; 15 use cssparser::{Parser as CSSParser, ParserInput as CSSParserInput, Token}; 16 use style_traits::{ 17 CssWriter, ParseError as StyleParseError, PropertySyntaxParseError as ParseError, 18 StyleParseErrorKind, ToCss, 19 }; 20 21 use self::data_type::{DataType, DependentDataTypes}; 22 23 mod ascii; 24 pub mod data_type; 25 26 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax> 27 #[derive(Debug, Clone, Default, MallocSizeOf, PartialEq, ToShmem)] 28 pub struct Descriptor { 29 /// The parsed components, if any. 30 /// TODO: Could be a Box<[]> if that supported const construction. 31 pub components: Vec<Component>, 32 /// The specified css syntax, if any. 33 specified: Option<Box<str>>, 34 } 35 36 impl Descriptor { 37 /// Returns the universal descriptor. 38 pub const fn universal() -> Self { 39 Self { 40 components: Vec::new(), 41 specified: None, 42 } 43 } 44 45 /// Returns whether this is the universal syntax descriptor. 46 #[inline] 47 pub fn is_universal(&self) -> bool { 48 self.components.is_empty() 49 } 50 51 /// Returns the specified string, if any. 52 #[inline] 53 pub fn specified_string(&self) -> Option<&str> { 54 self.specified.as_deref() 55 } 56 57 /// Parse a syntax descriptor from a stream of tokens 58 /// https://drafts.csswg.org/css-values-5/#typedef-syntax 59 #[inline] 60 pub fn from_css_parser<'i>(input: &mut CSSParser<'i, '_>) -> Result<Self, StyleParseError<'i>> { 61 let mut components = vec![]; 62 63 if input.try_parse(|i| i.expect_delim('*')).is_ok() { 64 return Ok(Self::universal()); 65 } 66 67 // Parse <syntax-string> if given. 68 if let Ok(syntax_string) = input.try_parse(|i| i.expect_string_cloned()) { 69 return Self::from_str(syntax_string.as_ref(), /* save_specified = */ true).or_else( 70 |err| Err(input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))), 71 ); 72 } 73 74 loop { 75 let name = Self::try_parse_component_name(input).map_err(|err| { 76 input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err)) 77 })?; 78 79 let multiplier = if name.is_pre_multiplied() { 80 None 81 } else { 82 Self::try_parse_multiplier(input) 83 }; 84 85 let component = Component { multiplier, name }; 86 components.push(component); 87 let Ok(delim) = input.next() else { break }; 88 89 if delim != &Token::Delim('|') { 90 return Err( 91 input.new_custom_error(StyleParseErrorKind::PropertySyntaxField( 92 ParseError::ExpectedPipeBetweenComponents, 93 )), 94 ); 95 } 96 } 97 98 Ok(Self { 99 components, 100 specified: None, 101 }) 102 } 103 104 fn try_parse_multiplier<'i>(input: &mut CSSParser<'i, '_>) -> Option<Multiplier> { 105 input 106 .try_parse(|input| { 107 let next = input.next().map_err(|_| ())?; 108 match next { 109 Token::Delim('+') => Ok(Multiplier::Space), 110 Token::Delim('#') => Ok(Multiplier::Comma), 111 _ => Err(()), 112 } 113 }) 114 .ok() 115 } 116 117 fn try_parse_component_name<'i>( 118 input: &mut CSSParser<'i, '_>, 119 ) -> Result<ComponentName, ParseError> { 120 if input.try_parse(|input| input.expect_delim('<')).is_ok() { 121 let name = Self::parse_component_data_type_name(input)?; 122 input 123 .expect_delim('>') 124 .map_err(|_| ParseError::UnclosedDataTypeName)?; 125 Ok(ComponentName::DataType(name)) 126 } else { 127 input.try_parse(|input| { 128 let name = CustomIdent::parse(input, &[]).map_err(|_| ParseError::InvalidName)?; 129 Ok(ComponentName::Ident(name)) 130 }) 131 } 132 } 133 134 fn parse_component_data_type_name<'i>( 135 input: &mut CSSParser<'i, '_>, 136 ) -> Result<DataType, ParseError> { 137 input 138 .expect_ident() 139 .ok() 140 .and_then(|n| DataType::from_str(n)) 141 .ok_or(ParseError::UnknownDataTypeName) 142 } 143 144 /// Parse a syntax descriptor. 145 /// https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-definition 146 pub fn from_str(css: &str, save_specified: bool) -> Result<Self, ParseError> { 147 // 1. Strip leading and trailing ASCII whitespace from string. 148 let input = ascii::trim_ascii_whitespace(css); 149 150 // 2. If string's length is 0, return failure. 151 if input.is_empty() { 152 return Err(ParseError::EmptyInput); 153 } 154 155 let specified = if save_specified { 156 Some(Box::from(css)) 157 } else { 158 None 159 }; 160 161 // 3. If string's length is 1, and the only code point in string is U+002A 162 // ASTERISK (*), return the universal syntax descriptor. 163 if input.len() == 1 && input.as_bytes()[0] == b'*' { 164 return Ok(Self { 165 components: Default::default(), 166 specified, 167 }); 168 } 169 170 // 4. Let stream be an input stream created from the code points of string, 171 // preprocessed as specified in [css-syntax-3]. Let descriptor be an 172 // initially empty list of syntax components. 173 // 174 // NOTE(emilio): Instead of preprocessing we cheat and treat new-lines and 175 // nulls in the parser specially. 176 let mut components = vec![]; 177 { 178 let mut input = Parser::new(input, &mut components); 179 // 5. Repeatedly consume the next input code point from stream. 180 input.parse()?; 181 } 182 Ok(Self { 183 components, 184 specified, 185 }) 186 } 187 188 /// Returns the dependent types this syntax might contain. 189 pub fn dependent_types(&self) -> DependentDataTypes { 190 let mut types = DependentDataTypes::empty(); 191 for component in self.components.iter() { 192 let t = match &component.name { 193 ComponentName::DataType(ref t) => t, 194 ComponentName::Ident(_) => continue, 195 }; 196 types.insert(t.dependent_types()); 197 } 198 types 199 } 200 } 201 202 impl ToCss for Descriptor { 203 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 204 where 205 W: Write, 206 { 207 if let Some(ref specified) = self.specified { 208 return specified.to_css(dest); 209 } 210 211 if self.is_universal() { 212 return dest.write_char('*'); 213 } 214 215 let mut first = true; 216 for component in &*self.components { 217 if !first { 218 dest.write_str(" | ")?; 219 } 220 component.to_css(dest)?; 221 first = false; 222 } 223 224 Ok(()) 225 } 226 } 227 228 impl Parse for Descriptor { 229 /// Parse a syntax descriptor. 230 fn parse<'i>( 231 _: &ParserContext, 232 parser: &mut CSSParser<'i, '_>, 233 ) -> Result<Self, StyleParseError<'i>> { 234 let input = parser.expect_string()?; 235 Descriptor::from_str(input.as_ref(), /* save_specified = */ true) 236 .map_err(|err| parser.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))) 237 } 238 } 239 240 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#multipliers> 241 #[derive( 242 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 243 )] 244 pub enum Multiplier { 245 /// Indicates a space-separated list. 246 Space, 247 /// Indicates a comma-separated list. 248 Comma, 249 } 250 251 impl ToCss for Multiplier { 252 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 253 where 254 W: Write, 255 { 256 dest.write_char(match *self { 257 Multiplier::Space => '+', 258 Multiplier::Comma => '#', 259 }) 260 } 261 } 262 263 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component> 264 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 265 pub struct Component { 266 name: ComponentName, 267 multiplier: Option<Multiplier>, 268 } 269 270 impl Component { 271 /// Returns the component's name. 272 #[inline] 273 pub fn name(&self) -> &ComponentName { 274 &self.name 275 } 276 277 /// Returns the component's multiplier, if one exists. 278 #[inline] 279 pub fn multiplier(&self) -> Option<Multiplier> { 280 self.multiplier 281 } 282 283 /// If the component is premultiplied, return the un-premultiplied component. 284 #[inline] 285 pub fn unpremultiplied(&self) -> Cow<'_, Self> { 286 match self.name.unpremultiply() { 287 Some(component) => { 288 debug_assert!( 289 self.multiplier.is_none(), 290 "Shouldn't have parsed a multiplier for a pre-multiplied data type name", 291 ); 292 Cow::Owned(component) 293 }, 294 None => Cow::Borrowed(self), 295 } 296 } 297 } 298 299 impl ToCss for Component { 300 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 301 where 302 W: Write, 303 { 304 self.name().to_css(dest)?; 305 self.multiplier().to_css(dest) 306 } 307 } 308 309 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component-name> 310 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] 311 pub enum ComponentName { 312 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#data-type-name> 313 DataType(DataType), 314 /// <https://drafts.csswg.org/css-values-4/#custom-idents> 315 Ident(CustomIdent), 316 } 317 318 impl ComponentName { 319 fn unpremultiply(&self) -> Option<Component> { 320 match *self { 321 ComponentName::DataType(ref t) => t.unpremultiply(), 322 ComponentName::Ident(..) => None, 323 } 324 } 325 326 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#pre-multiplied-data-type-name> 327 fn is_pre_multiplied(&self) -> bool { 328 self.unpremultiply().is_some() 329 } 330 } 331 332 struct Parser<'a> { 333 input: &'a str, 334 position: usize, 335 output: &'a mut Vec<Component>, 336 } 337 338 /// <https://drafts.csswg.org/css-syntax-3/#letter> 339 fn is_letter(byte: u8) -> bool { 340 match byte { 341 b'A'..=b'Z' | b'a'..=b'z' => true, 342 _ => false, 343 } 344 } 345 346 /// <https://drafts.csswg.org/css-syntax-3/#non-ascii-code-point> 347 fn is_non_ascii(byte: u8) -> bool { 348 byte >= 0x80 349 } 350 351 /// <https://drafts.csswg.org/css-syntax-3/#name-start-code-point> 352 fn is_name_start(byte: u8) -> bool { 353 is_letter(byte) || is_non_ascii(byte) || byte == b'_' 354 } 355 356 impl<'a> Parser<'a> { 357 fn new(input: &'a str, output: &'a mut Vec<Component>) -> Self { 358 Self { 359 input, 360 position: 0, 361 output, 362 } 363 } 364 365 fn peek(&self) -> Option<u8> { 366 self.input.as_bytes().get(self.position).cloned() 367 } 368 369 fn parse(&mut self) -> Result<(), ParseError> { 370 // 5. Repeatedly consume the next input code point from stream: 371 loop { 372 let component = self.parse_component()?; 373 self.output.push(component); 374 self.skip_whitespace(); 375 376 let byte = match self.peek() { 377 None => return Ok(()), 378 Some(b) => b, 379 }; 380 381 if byte != b'|' { 382 return Err(ParseError::ExpectedPipeBetweenComponents); 383 } 384 385 self.position += 1; 386 } 387 } 388 389 fn skip_whitespace(&mut self) { 390 loop { 391 match self.peek() { 392 Some(c) if c.is_ascii_whitespace() => self.position += 1, 393 _ => return, 394 } 395 } 396 } 397 398 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name> 399 fn parse_data_type_name(&mut self) -> Result<DataType, ParseError> { 400 let start = self.position; 401 loop { 402 let byte = match self.peek() { 403 Some(b) => b, 404 None => return Err(ParseError::UnclosedDataTypeName), 405 }; 406 if byte != b'>' { 407 self.position += 1; 408 continue; 409 } 410 let ty = match DataType::from_str(&self.input[start..self.position]) { 411 Some(ty) => ty, 412 None => return Err(ParseError::UnknownDataTypeName), 413 }; 414 self.position += 1; 415 return Ok(ty); 416 } 417 } 418 419 fn parse_name(&mut self) -> Result<ComponentName, ParseError> { 420 let b = match self.peek() { 421 Some(b) => b, 422 None => return Err(ParseError::UnexpectedEOF), 423 }; 424 425 if b == b'<' { 426 self.position += 1; 427 return Ok(ComponentName::DataType(self.parse_data_type_name()?)); 428 } 429 430 if b != b'\\' && !is_name_start(b) { 431 return Err(ParseError::InvalidNameStart); 432 } 433 434 let input = &self.input[self.position..]; 435 let mut input = CSSParserInput::new(input); 436 let mut input = CSSParser::new(&mut input); 437 let name = match CustomIdent::parse(&mut input, &[]) { 438 Ok(name) => name, 439 Err(_) => return Err(ParseError::InvalidName), 440 }; 441 self.position += input.position().byte_index(); 442 return Ok(ComponentName::Ident(name)); 443 } 444 445 fn parse_multiplier(&mut self) -> Option<Multiplier> { 446 let multiplier = match self.peek()? { 447 b'+' => Multiplier::Space, 448 b'#' => Multiplier::Comma, 449 _ => return None, 450 }; 451 self.position += 1; 452 Some(multiplier) 453 } 454 455 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-component> 456 fn parse_component(&mut self) -> Result<Component, ParseError> { 457 // Consume as much whitespace as possible from stream. 458 self.skip_whitespace(); 459 let name = self.parse_name()?; 460 let multiplier = if name.is_pre_multiplied() { 461 None 462 } else { 463 self.parse_multiplier() 464 }; 465 Ok(Component { name, multiplier }) 466 } 467 }