/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Used for parsing and serializing the [`@property`] syntax string. //! //! use std::fmt::{self, Debug}; use std::{borrow::Cow, fmt::Write}; use crate::parser::{Parse, ParserContext}; use crate::values::CustomIdent; use cssparser::{Parser as CSSParser, ParserInput as CSSParserInput}; use style_traits::{ CssWriter, ParseError as StyleParseError, PropertySyntaxParseError as ParseError, StyleParseErrorKind, ToCss, }; use self::data_type::{DataType, DependentDataTypes}; mod ascii; pub mod data_type; /// #[derive(Debug, Clone, Default, MallocSizeOf, PartialEq)] pub struct Descriptor { /// The parsed components, if any. /// TODO: Could be a Box<[]> if that supported const construction. pub components: Vec, /// The specified css syntax, if any. specified: Option>, } impl Descriptor { /// Returns the universal descriptor. pub const fn universal() -> Self { Self { components: Vec::new(), specified: None, } } /// Returns whether this is the universal syntax descriptor. #[inline] pub fn is_universal(&self) -> bool { self.components.is_empty() } /// Returns the specified string, if any. #[inline] pub fn specified_string(&self) -> Option<&str> { self.specified.as_deref() } /// Parse a syntax descriptor. /// https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-definition pub fn from_str(css: &str, save_specified: bool) -> Result { // 1. Strip leading and trailing ASCII whitespace from string. let input = ascii::trim_ascii_whitespace(css); // 2. If string's length is 0, return failure. if input.is_empty() { return Err(ParseError::EmptyInput); } let specified = if save_specified { Some(Box::from(css)) } else { None }; // 3. If string's length is 1, and the only code point in string is U+002A // ASTERISK (*), return the universal syntax descriptor. if input.len() == 1 && input.as_bytes()[0] == b'*' { return Ok(Self { components: Default::default(), specified, }); } // 4. Let stream be an input stream created from the code points of string, // preprocessed as specified in [css-syntax-3]. Let descriptor be an // initially empty list of syntax components. // // NOTE(emilio): Instead of preprocessing we cheat and treat new-lines and // nulls in the parser specially. let mut components = vec![]; { let mut parser = Parser::new(input, &mut components); // 5. Repeatedly consume the next input code point from stream. parser.parse()?; } Ok(Self { components, specified, }) } /// Returns the dependent types this syntax might contain. pub fn dependent_types(&self) -> DependentDataTypes { let mut types = DependentDataTypes::empty(); for component in self.components.iter() { let t = match &component.name { ComponentName::DataType(ref t) => t, ComponentName::Ident(_) => continue, }; types.insert(t.dependent_types()); } types } } impl ToCss for Descriptor { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { if let Some(ref specified) = self.specified { return specified.to_css(dest); } if self.is_universal() { return dest.write_char('*'); } let mut first = true; for component in &*self.components { if !first { dest.write_str(" | ")?; } component.to_css(dest)?; first = false; } Ok(()) } } impl Parse for Descriptor { /// Parse a syntax descriptor. fn parse<'i>( _: &ParserContext, parser: &mut CSSParser<'i, '_>, ) -> Result> { let input = parser.expect_string()?; Descriptor::from_str(input.as_ref(), /* save_specified = */ true) .map_err(|err| parser.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))) } } /// #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] pub enum Multiplier { /// Indicates a space-separated list. Space, /// Indicates a comma-separated list. Comma, } impl ToCss for Multiplier { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_char(match *self { Multiplier::Space => '+', Multiplier::Comma => '#', }) } } /// #[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub struct Component { name: ComponentName, multiplier: Option, } impl Component { /// Returns the component's name. #[inline] pub fn name(&self) -> &ComponentName { &self.name } /// Returns the component's multiplier, if one exists. #[inline] pub fn multiplier(&self) -> Option { self.multiplier } /// If the component is premultiplied, return the un-premultiplied component. #[inline] pub fn unpremultiplied(&self) -> Cow { match self.name.unpremultiply() { Some(component) => { debug_assert!( self.multiplier.is_none(), "Shouldn't have parsed a multiplier for a pre-multiplied data type name", ); Cow::Owned(component) }, None => Cow::Borrowed(self), } } } impl ToCss for Component { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { self.name().to_css(dest)?; self.multiplier().to_css(dest) } } /// #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] pub enum ComponentName { /// DataType(DataType), /// Ident(CustomIdent), } impl ComponentName { fn unpremultiply(&self) -> Option { match *self { ComponentName::DataType(ref t) => t.unpremultiply(), ComponentName::Ident(..) => None, } } /// fn is_pre_multiplied(&self) -> bool { self.unpremultiply().is_some() } } struct Parser<'a> { input: &'a str, position: usize, output: &'a mut Vec, } /// fn is_letter(byte: u8) -> bool { match byte { b'A'..=b'Z' | b'a'..=b'z' => true, _ => false, } } /// fn is_non_ascii(byte: u8) -> bool { byte >= 0x80 } /// fn is_name_start(byte: u8) -> bool { is_letter(byte) || is_non_ascii(byte) || byte == b'_' } impl<'a> Parser<'a> { fn new(input: &'a str, output: &'a mut Vec) -> Self { Self { input, position: 0, output, } } fn peek(&self) -> Option { self.input.as_bytes().get(self.position).cloned() } fn parse(&mut self) -> Result<(), ParseError> { // 5. Repeatedly consume the next input code point from stream: loop { let component = self.parse_component()?; self.output.push(component); self.skip_whitespace(); let byte = match self.peek() { None => return Ok(()), Some(b) => b, }; if byte != b'|' { return Err(ParseError::ExpectedPipeBetweenComponents); } self.position += 1; } } fn skip_whitespace(&mut self) { loop { match self.peek() { Some(c) if c.is_ascii_whitespace() => self.position += 1, _ => return, } } } /// fn parse_data_type_name(&mut self) -> Result { let start = self.position; loop { let byte = match self.peek() { Some(b) => b, None => return Err(ParseError::UnclosedDataTypeName), }; if byte != b'>' { self.position += 1; continue; } let ty = match DataType::from_str(&self.input[start..self.position]) { Some(ty) => ty, None => return Err(ParseError::UnknownDataTypeName), }; self.position += 1; return Ok(ty); } } fn parse_name(&mut self) -> Result { let b = match self.peek() { Some(b) => b, None => return Err(ParseError::UnexpectedEOF), }; if b == b'<' { self.position += 1; return Ok(ComponentName::DataType(self.parse_data_type_name()?)); } if b != b'\\' && !is_name_start(b) { return Err(ParseError::InvalidNameStart); } let input = &self.input[self.position..]; let mut input = CSSParserInput::new(input); let mut input = CSSParser::new(&mut input); let name = match CustomIdent::parse(&mut input, &[]) { Ok(name) => name, Err(_) => return Err(ParseError::InvalidName), }; self.position += input.position().byte_index(); return Ok(ComponentName::Ident(name)); } fn parse_multiplier(&mut self) -> Option { let multiplier = match self.peek()? { b'+' => Multiplier::Space, b'#' => Multiplier::Comma, _ => return None, }; self.position += 1; Some(multiplier) } /// fn parse_component(&mut self) -> Result { // Consume as much whitespace as possible from stream. self.skip_whitespace(); let name = self.parse_name()?; let multiplier = if name.is_pre_multiplied() { None } else { self.parse_multiplier() }; Ok(Component { name, multiplier }) } }