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(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
154pub enum Multiplier {
155    /// Indicates a space-separated list.
156    Space,
157    /// Indicates a comma-separated list.
158    Comma,
159}
160
161impl ToCss for Multiplier {
162    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
163    where
164        W: Write,
165    {
166        dest.write_char(match *self {
167            Multiplier::Space => '+',
168            Multiplier::Comma => '#',
169        })
170    }
171}
172
173/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component>
174#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
175pub struct Component {
176    name: ComponentName,
177    multiplier: Option<Multiplier>,
178}
179
180impl Component {
181    /// Returns the component's name.
182    #[inline]
183    pub fn name(&self) -> &ComponentName {
184        &self.name
185    }
186
187    /// Returns the component's multiplier, if one exists.
188    #[inline]
189    pub fn multiplier(&self) -> Option<Multiplier> {
190        self.multiplier
191    }
192
193    /// If the component is premultiplied, return the un-premultiplied component.
194    #[inline]
195    pub fn unpremultiplied(&self) -> Cow<Self> {
196        match self.name.unpremultiply() {
197            Some(component) => {
198                debug_assert!(
199                    self.multiplier.is_none(),
200                    "Shouldn't have parsed a multiplier for a pre-multiplied data type name",
201                );
202                Cow::Owned(component)
203            },
204            None => Cow::Borrowed(self),
205        }
206    }
207}
208
209impl ToCss for Component {
210    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
211    where
212        W: Write,
213    {
214        self.name().to_css(dest)?;
215        self.multiplier().to_css(dest)
216    }
217}
218
219/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component-name>
220#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
221pub enum ComponentName {
222    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#data-type-name>
223    DataType(DataType),
224    /// <https://drafts.csswg.org/css-values-4/#custom-idents>
225    Ident(CustomIdent),
226}
227
228impl ComponentName {
229    fn unpremultiply(&self) -> Option<Component> {
230        match *self {
231            ComponentName::DataType(ref t) => t.unpremultiply(),
232            ComponentName::Ident(..) => None,
233        }
234    }
235
236    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#pre-multiplied-data-type-name>
237    fn is_pre_multiplied(&self) -> bool {
238        self.unpremultiply().is_some()
239    }
240}
241
242struct Parser<'a> {
243    input: &'a str,
244    position: usize,
245    output: &'a mut Vec<Component>,
246}
247
248/// <https://drafts.csswg.org/css-syntax-3/#letter>
249fn is_letter(byte: u8) -> bool {
250    match byte {
251        b'A'..=b'Z' | b'a'..=b'z' => true,
252        _ => false,
253    }
254}
255
256/// <https://drafts.csswg.org/css-syntax-3/#non-ascii-code-point>
257fn is_non_ascii(byte: u8) -> bool {
258    byte >= 0x80
259}
260
261/// <https://drafts.csswg.org/css-syntax-3/#name-start-code-point>
262fn is_name_start(byte: u8) -> bool {
263    is_letter(byte) || is_non_ascii(byte) || byte == b'_'
264}
265
266impl<'a> Parser<'a> {
267    fn new(input: &'a str, output: &'a mut Vec<Component>) -> Self {
268        Self {
269            input,
270            position: 0,
271            output,
272        }
273    }
274
275    fn peek(&self) -> Option<u8> {
276        self.input.as_bytes().get(self.position).cloned()
277    }
278
279    fn parse(&mut self) -> Result<(), ParseError> {
280        // 5. Repeatedly consume the next input code point from stream:
281        loop {
282            let component = self.parse_component()?;
283            self.output.push(component);
284            self.skip_whitespace();
285
286            let byte = match self.peek() {
287                None => return Ok(()),
288                Some(b) => b,
289            };
290
291            if byte != b'|' {
292                return Err(ParseError::ExpectedPipeBetweenComponents);
293            }
294
295            self.position += 1;
296        }
297    }
298
299    fn skip_whitespace(&mut self) {
300        loop {
301            match self.peek() {
302                Some(c) if c.is_ascii_whitespace() => self.position += 1,
303                _ => return,
304            }
305        }
306    }
307
308    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name>
309    fn parse_data_type_name(&mut self) -> Result<DataType, ParseError> {
310        let start = self.position;
311        loop {
312            let byte = match self.peek() {
313                Some(b) => b,
314                None => return Err(ParseError::UnclosedDataTypeName),
315            };
316            if byte != b'>' {
317                self.position += 1;
318                continue;
319            }
320            let ty = match DataType::from_str(&self.input[start..self.position]) {
321                Some(ty) => ty,
322                None => return Err(ParseError::UnknownDataTypeName),
323            };
324            self.position += 1;
325            return Ok(ty);
326        }
327    }
328
329    fn parse_name(&mut self) -> Result<ComponentName, ParseError> {
330        let b = match self.peek() {
331            Some(b) => b,
332            None => return Err(ParseError::UnexpectedEOF),
333        };
334
335        if b == b'<' {
336            self.position += 1;
337            return Ok(ComponentName::DataType(self.parse_data_type_name()?));
338        }
339
340        if b != b'\\' && !is_name_start(b) {
341            return Err(ParseError::InvalidNameStart);
342        }
343
344        let input = &self.input[self.position..];
345        let mut input = CSSParserInput::new(input);
346        let mut input = CSSParser::new(&mut input);
347        let name = match CustomIdent::parse(&mut input, &[]) {
348            Ok(name) => name,
349            Err(_) => return Err(ParseError::InvalidName),
350        };
351        self.position += input.position().byte_index();
352        return Ok(ComponentName::Ident(name));
353    }
354
355    fn parse_multiplier(&mut self) -> Option<Multiplier> {
356        let multiplier = match self.peek()? {
357            b'+' => Multiplier::Space,
358            b'#' => Multiplier::Comma,
359            _ => return None,
360        };
361        self.position += 1;
362        Some(multiplier)
363    }
364
365    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-component>
366    fn parse_component(&mut self) -> Result<Component, ParseError> {
367        // Consume as much whitespace as possible from stream.
368        self.skip_whitespace();
369        let name = self.parse_name()?;
370        let multiplier = if name.is_pre_multiplied() {
371            None
372        } else {
373            self.parse_multiplier()
374        };
375        Ok(Component { name, multiplier })
376    }
377}