css_parse/syntax/
component_values.rs

1use crate::{
2	AssociatedWhitespaceRules, Cursor, CursorSink, DeclarationValue, NodeMetadata, NodeWithMetadata, Parse, Parser,
3	Peek, Result, SemanticEq, Span, ToCursors, ToSpan,
4};
5use bumpalo::collections::Vec;
6
7use super::ComponentValue;
8
9// https://drafts.csswg.org/css-syntax-3/#consume-list-of-components
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
12pub struct ComponentValues<'a> {
13	values: Vec<'a, ComponentValue<'a>>,
14}
15
16impl<'a> Peek<'a> for ComponentValues<'a> {
17	fn peek<Iter>(p: &Parser<'a, Iter>, c: Cursor) -> bool
18	where
19		Iter: Iterator<Item = Cursor> + Clone,
20	{
21		ComponentValue::peek(p, c)
22	}
23}
24
25impl<'a> Parse<'a> for ComponentValues<'a> {
26	// https://drafts.csswg.org/css-syntax-3/#consume-list-of-components
27	fn parse<Iter>(p: &mut Parser<'a, Iter>) -> Result<Self>
28	where
29		Iter: Iterator<Item = Cursor> + Clone,
30	{
31		let mut values = Vec::new_in(p.bump());
32		let mut last_was_whitespace = false;
33		loop {
34			if p.at_end() {
35				break;
36			}
37			if p.next_is_stop() {
38				break;
39			}
40			let c = p.peek_n(1);
41			if <ComponentValue>::peek(p, c) {
42				let mut value = p.parse::<ComponentValue>()?;
43				if let ComponentValue::Delim(d) = value
44					&& last_was_whitespace
45				{
46					let rules = d.associated_whitespace() | AssociatedWhitespaceRules::EnforceBefore;
47					value = ComponentValue::Delim(d.with_associated_whitespace(rules))
48				}
49				last_was_whitespace = matches!(value, ComponentValue::Whitespace(_));
50				values.push(value);
51			} else {
52				break;
53			}
54		}
55		Ok(Self { values })
56	}
57}
58
59impl<'a, M: NodeMetadata> NodeWithMetadata<M> for ComponentValues<'a> {
60	fn metadata(&self) -> M {
61		M::default()
62	}
63}
64
65impl<'a> DeclarationValue<'a, ()> for ComponentValues<'a> {
66	type ComputedValue = ComponentValues<'a>;
67
68	fn is_initial(&self) -> bool {
69		false
70	}
71
72	fn is_inherit(&self) -> bool {
73		false
74	}
75
76	fn is_unset(&self) -> bool {
77		false
78	}
79
80	fn is_revert(&self) -> bool {
81		false
82	}
83
84	fn is_revert_layer(&self) -> bool {
85		false
86	}
87
88	fn needs_computing(&self) -> bool {
89		false
90	}
91
92	fn parse_custom_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
93	where
94		Iter: Iterator<Item = crate::Cursor> + Clone,
95	{
96		Self::parse(p)
97	}
98
99	fn parse_computed_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
100	where
101		Iter: Iterator<Item = crate::Cursor> + Clone,
102	{
103		Self::parse(p)
104	}
105
106	fn parse_unknown_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
107	where
108		Iter: Iterator<Item = crate::Cursor> + Clone,
109	{
110		Self::parse(p)
111	}
112}
113
114impl<'a> ToCursors for ComponentValues<'a> {
115	fn to_cursors(&self, s: &mut impl CursorSink) {
116		ToCursors::to_cursors(&self.values, s)
117	}
118}
119
120impl<'a> ToSpan for ComponentValues<'a> {
121	fn to_span(&self) -> Span {
122		self.values.to_span()
123	}
124}
125
126// Implement for ComponentValues - compare sequences, ignoring whitespace
127impl<'a> SemanticEq for ComponentValues<'a> {
128	fn semantic_eq(&self, other: &Self) -> bool {
129		self.values.semantic_eq(&other.values)
130	}
131}
132
133#[cfg(test)]
134mod tests {
135	use super::*;
136	use crate::{EmptyAtomSet, test_helpers::*};
137
138	#[test]
139	fn size_test() {
140		assert_eq!(std::mem::size_of::<ComponentValues>(), 32);
141	}
142
143	#[test]
144	fn test_writes() {
145		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "body{color:black}");
146		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "body");
147	}
148
149	#[test]
150	fn test_writes_with_trivia() {
151		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "/*comment*/foo");
152		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, " /*comment*/ foo");
153		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "/*a*/foo/*b*/bar");
154		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "foo/*comment*/bar");
155		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, " \t foo");
156		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, " /*start*/ foo /*mid*/ bar");
157		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "/*comment*/foo");
158	}
159}