Skip to main content

css_parse/syntax/
component_values.rs

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