Skip to main content

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::derives::*;
13use crate::parser::{Parse, ParserContext};
14use crate::values::CustomIdent;
15use cssparser::{Parser as CSSParser, Token};
16use style_traits::{
17    CssWriter, ParseError as StyleParseError, PropertySyntaxParseError as ParseError,
18    StyleParseErrorKind, ToCss,
19};
20
21use self::data_type::{DataType, DependentDataTypes};
22
23mod ascii;
24pub 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)]
28pub 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
36impl 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(input: &mut CSSParser) -> Result<Self, StyleParseError> {
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).map_err(
70                |err| StyleParseError::custom(StyleParseErrorKind::PropertySyntaxField(err)),
71            );
72        }
73
74        loop {
75            let name = Self::try_parse_component_name(input).map_err(|err| {
76                StyleParseError::custom(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(StyleParseError::custom(
91                    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(input: &mut CSSParser) -> Option<Multiplier> {
105        input
106            .try_parse(|input| {
107                let next = input.next_including_whitespace().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(input: &mut CSSParser) -> Result<ComponentName, ParseError> {
118        if input.try_parse(|input| input.expect_delim('<')).is_ok() {
119            let name = Self::parse_component_data_type_name(input)?;
120            match input.next_including_whitespace() {
121                Ok(&Token::Delim('>')) => Ok(ComponentName::DataType(name)),
122                _ => Err(ParseError::UnclosedDataTypeName),
123            }
124        } else {
125            input.try_parse(|input| {
126                let name = CustomIdent::parse(input, &[]).map_err(|_| ParseError::InvalidName)?;
127                Ok(ComponentName::Ident(name))
128            })
129        }
130    }
131
132    fn parse_component_data_type_name(input: &mut CSSParser) -> Result<DataType, ParseError> {
133        let ty = match input.next_including_whitespace() {
134            Ok(Token::Ident(name)) => DataType::from_str(name),
135            _ => None,
136        };
137        ty.ok_or(ParseError::UnknownDataTypeName)
138    }
139
140    /// Parse a syntax descriptor.
141    /// https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-definition
142    pub fn from_str(css: &str, save_specified: bool) -> Result<Self, ParseError> {
143        // 1. Strip leading and trailing ASCII whitespace from string.
144        let input = ascii::trim_ascii_whitespace(css);
145
146        // 2. If string's length is 0, return failure.
147        if input.is_empty() {
148            return Err(ParseError::EmptyInput);
149        }
150
151        let specified = if save_specified {
152            Some(Box::from(css))
153        } else {
154            None
155        };
156
157        // 3. If string's length is 1, and the only code point in string is U+002A
158        //    ASTERISK (*), return the universal syntax descriptor.
159        if input.len() == 1 && input.as_bytes()[0] == b'*' {
160            return Ok(Self {
161                components: Default::default(),
162                specified,
163            });
164        }
165
166        // 4. Let stream be an input stream created from the code points of string,
167        //    preprocessed as specified in [css-syntax-3]. Let descriptor be an
168        //    initially empty list of syntax components.
169        //
170        // NOTE(emilio): Instead of preprocessing we cheat and treat new-lines and
171        // nulls in the parser specially.
172        let mut components = vec![];
173        {
174            let mut input = Parser::new(input, &mut components);
175            // 5. Repeatedly consume the next input code point from stream.
176            input.parse()?;
177        }
178        Ok(Self {
179            components,
180            specified,
181        })
182    }
183
184    /// Returns the dependent types this syntax might contain.
185    pub fn dependent_types(&self) -> DependentDataTypes {
186        let mut types = DependentDataTypes::empty();
187        for component in self.components.iter() {
188            let t = match &component.name {
189                ComponentName::DataType(t) => t,
190                ComponentName::Ident(_) => continue,
191            };
192            types.insert(t.dependent_types());
193        }
194        types
195    }
196}
197
198impl ToCss for Descriptor {
199    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
200    where
201        W: Write,
202    {
203        if let Some(ref specified) = self.specified {
204            return specified.to_css(dest);
205        }
206
207        if self.is_universal() {
208            return dest.write_char('*');
209        }
210
211        let mut first = true;
212        for component in &*self.components {
213            if !first {
214                dest.write_str(" | ")?;
215            }
216            component.to_css(dest)?;
217            first = false;
218        }
219
220        Ok(())
221    }
222}
223
224impl Parse for Descriptor {
225    /// Parse a syntax descriptor.
226    fn parse(_: &ParserContext, parser: &mut CSSParser) -> Result<Self, StyleParseError> {
227        let input = parser.expect_string()?;
228        Descriptor::from_str(input.as_ref(), /* save_specified = */ true)
229            .map_err(|err| StyleParseError::custom(StyleParseErrorKind::PropertySyntaxField(err)))
230    }
231}
232
233/// <https://drafts.css-houdini.org/css-properties-values-api-1/#multipliers>
234#[derive(
235    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
236)]
237pub enum Multiplier {
238    /// Indicates a space-separated list.
239    Space,
240    /// Indicates a comma-separated list.
241    Comma,
242}
243
244impl ToCss for Multiplier {
245    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
246    where
247        W: Write,
248    {
249        dest.write_char(match *self {
250            Multiplier::Space => '+',
251            Multiplier::Comma => '#',
252        })
253    }
254}
255
256/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component>
257#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
258pub struct Component {
259    name: ComponentName,
260    multiplier: Option<Multiplier>,
261}
262
263impl Component {
264    /// Returns the component's name.
265    #[inline]
266    pub fn name(&self) -> &ComponentName {
267        &self.name
268    }
269
270    /// Returns the component's multiplier, if one exists.
271    #[inline]
272    pub fn multiplier(&self) -> Option<Multiplier> {
273        self.multiplier
274    }
275
276    /// If the component is premultiplied, return the un-premultiplied component.
277    #[inline]
278    pub fn unpremultiplied(&self) -> Cow<'_, Self> {
279        match self.name.unpremultiply() {
280            Some(component) => {
281                debug_assert!(
282                    self.multiplier.is_none(),
283                    "Shouldn't have parsed a multiplier for a pre-multiplied data type name",
284                );
285                Cow::Owned(component)
286            },
287            None => Cow::Borrowed(self),
288        }
289    }
290}
291
292impl ToCss for Component {
293    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
294    where
295        W: Write,
296    {
297        self.name().to_css(dest)?;
298        self.multiplier().to_css(dest)
299    }
300}
301
302/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component-name>
303#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
304pub enum ComponentName {
305    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#data-type-name>
306    DataType(DataType),
307    /// <https://drafts.csswg.org/css-values-4/#custom-idents>
308    Ident(CustomIdent),
309}
310
311impl ComponentName {
312    fn unpremultiply(&self) -> Option<Component> {
313        match *self {
314            ComponentName::DataType(ref t) => t.unpremultiply(),
315            ComponentName::Ident(..) => None,
316        }
317    }
318
319    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#pre-multiplied-data-type-name>
320    fn is_pre_multiplied(&self) -> bool {
321        self.unpremultiply().is_some()
322    }
323}
324
325struct Parser<'a> {
326    input: &'a str,
327    position: usize,
328    output: &'a mut Vec<Component>,
329}
330
331impl<'a> Parser<'a> {
332    fn new(input: &'a str, output: &'a mut Vec<Component>) -> Self {
333        Self {
334            input,
335            position: 0,
336            output,
337        }
338    }
339
340    fn peek(&self) -> Option<u8> {
341        self.input.as_bytes().get(self.position).cloned()
342    }
343
344    fn parse(&mut self) -> Result<(), ParseError> {
345        // 5. Repeatedly consume the next input code point from stream:
346        loop {
347            let component = self.parse_component()?;
348            self.output.push(component);
349            self.skip_whitespace();
350
351            let byte = match self.peek() {
352                None => return Ok(()),
353                Some(b) => b,
354            };
355
356            if byte != b'|' {
357                return Err(ParseError::ExpectedPipeBetweenComponents);
358            }
359
360            self.position += 1;
361        }
362    }
363
364    fn skip_whitespace(&mut self) {
365        loop {
366            match self.peek() {
367                Some(c) if c.is_ascii_whitespace() => self.position += 1,
368                _ => return,
369            }
370        }
371    }
372
373    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name>
374    fn parse_data_type_name(&mut self) -> Result<DataType, ParseError> {
375        let start = self.position;
376        loop {
377            let byte = match self.peek() {
378                Some(b) => b,
379                None => return Err(ParseError::UnclosedDataTypeName),
380            };
381            if byte != b'>' {
382                self.position += 1;
383                continue;
384            }
385            let ty = match DataType::from_str(&self.input[start..self.position]) {
386                Some(ty) => ty,
387                None => return Err(ParseError::UnknownDataTypeName),
388            };
389            self.position += 1;
390            return Ok(ty);
391        }
392    }
393
394    fn parse_name(&mut self) -> Result<ComponentName, ParseError> {
395        let b = match self.peek() {
396            Some(b) => b,
397            None => return Err(ParseError::UnexpectedEOF),
398        };
399
400        if b == b'<' {
401            self.position += 1;
402            return Ok(ComponentName::DataType(self.parse_data_type_name()?));
403        }
404
405        let input = &self.input[self.position..];
406        let mut input = CSSParser::new(input);
407        let name = match CustomIdent::parse(&mut input, &[]) {
408            Ok(name) => name,
409            Err(_) => return Err(ParseError::InvalidName),
410        };
411        self.position += input.position().byte_index();
412        Ok(ComponentName::Ident(name))
413    }
414
415    fn parse_multiplier(&mut self) -> Option<Multiplier> {
416        let multiplier = match self.peek()? {
417            b'+' => Multiplier::Space,
418            b'#' => Multiplier::Comma,
419            _ => return None,
420        };
421        self.position += 1;
422        Some(multiplier)
423    }
424
425    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-component>
426    fn parse_component(&mut self) -> Result<Component, ParseError> {
427        // Consume as much whitespace as possible from stream.
428        self.skip_whitespace();
429        let name = self.parse_name()?;
430        let multiplier = if name.is_pre_multiplied() {
431            None
432        } else {
433            self.parse_multiplier()
434        };
435        Ok(Component { name, multiplier })
436    }
437}