Source code
Revision control
Copy as Markdown
Other Tools
/* 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
//! 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<Component>,
/// The specified css syntax, if any.
specified: Option<Box<str>>,
}
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.
pub fn from_str(css: &str, save_specified: bool) -> Result<Self, ParseError> {
// 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<W>(&self, dest: &mut CssWriter<W>) -> 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<Self, StyleParseError<'i>> {
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<W>(&self, dest: &mut CssWriter<W>) -> 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<Multiplier>,
}
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<Multiplier> {
self.multiplier
}
/// If the component is premultiplied, return the un-premultiplied component.
#[inline]
pub fn unpremultiplied(&self) -> Cow<Self> {
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<W>(&self, dest: &mut CssWriter<W>) -> 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<Component> {
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<Component>,
}
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<Component>) -> Self {
Self {
input,
position: 0,
output,
}
}
fn peek(&self) -> Option<u8> {
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<DataType, ParseError> {
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<ComponentName, ParseError> {
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<Multiplier> {
let multiplier = match self.peek()? {
b'+' => Multiplier::Space,
b'#' => Multiplier::Comma,
_ => return None,
};
self.position += 1;
Some(multiplier)
}
fn parse_component(&mut self) -> Result<Component, ParseError> {
// 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 })
}
}