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