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