style/properties_and_values/syntax/
mod.rs

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