Skip to main content

css_parse/syntax/
component_value.rs

1use super::prelude::*;
2use crate::{AssociatedWhitespaceRules, Box, FunctionBlock, Result, SimpleBlock};
3
4/// <https://drafts.csswg.org/css-syntax-3/#consume-component-value>
5///
6/// A compatible "Token" per CSS grammar, subsetted to the tokens possibly
7/// rendered by ComponentValue (so no pairwise, function tokens, etc).
8#[node]
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
11pub enum ComponentValue<'a> {
12	SimpleBlock(Box<'a, SimpleBlock<'a>>),
13	Function(Box<'a, FunctionBlock<'a>>),
14	Whitespace(T![Whitespace]),
15	Number(T![Number]),
16	Dimension(T![Dimension]),
17	Ident(T![Ident]),
18	AtKeyword(T![AtKeyword]),
19	Hash(T![Hash]),
20	String(T![String]),
21	Url(T![Url]),
22	Delim(T![Delim]),
23	Colon(T![:]),
24	Semicolon(T![;]),
25	Comma(T![,]),
26}
27
28impl<'a, M: NodeMetadata> NodeWithMetadata<M> for ComponentValue<'a> {
29	fn metadata(&self) -> M {
30		M::default()
31	}
32}
33
34impl<'a> Peek<'a> for ComponentValue<'a> {
35	const PEEK_KINDSET: KindSet = KindSet::new(&[
36		Kind::Whitespace,
37		Kind::Number,
38		Kind::Dimension,
39		Kind::Ident,
40		Kind::AtKeyword,
41		Kind::Hash,
42		Kind::String,
43		Kind::Url,
44		Kind::Delim,
45		Kind::Colon,
46		Kind::Semicolon,
47		Kind::Comma,
48		Kind::Function,
49		Kind::LeftCurly,
50		Kind::LeftParen,
51		Kind::LeftSquare,
52	]);
53	#[inline(always)]
54	fn peek<Iter>(p: &Parser<'a, Iter>, c: Cursor) -> bool
55	where
56		Iter: Iterator<Item = Cursor> + Clone,
57	{
58		c == Self::PEEK_KINDSET || <T![' ']>::peek(p, c)
59	}
60}
61
62// https://drafts.csswg.org/css-syntax-3/#consume-component-value
63impl<'a> Parse<'a> for ComponentValue<'a> {
64	fn parse<Iter>(p: &mut Parser<'a, Iter>) -> Result<Self>
65	where
66		Iter: Iterator<Item = Cursor> + Clone,
67	{
68		let c = p.peek_n(1);
69		Ok(if <T![' ']>::peek(p, c) {
70			Self::Whitespace(p.parse::<T![' ']>()?)
71		} else if <T![PairWiseStart]>::peek(p, c) {
72			let old_state = p.set_state(State::Nested);
73			let block = p.parse::<Box<SimpleBlock>>();
74			p.set_state(old_state);
75			Self::SimpleBlock(block?)
76		} else if <T![Function]>::peek(p, c) {
77			Self::Function(p.parse::<Box<FunctionBlock>>()?)
78		} else if <T![Number]>::peek(p, c) {
79			Self::Number(p.parse::<T![Number]>()?.preserve_type())
80		} else if <T![Dimension]>::peek(p, c) {
81			let dimension = p.parse::<T![Dimension]>()?;
82			let unit_start = p.to_source_cursor(c).source().as_bytes().get(c.token().numeric_len() as usize);
83			Self::Dimension(if matches!(unit_start, Some(b'n' | b'N' | b'\\')) {
84				dimension.preserve_type()
85			} else {
86				dimension
87			})
88		} else if <T![Ident]>::peek(p, c) {
89			Self::Ident(p.parse::<T![Ident]>()?)
90		} else if <T![AtKeyword]>::peek(p, c) {
91			Self::AtKeyword(p.parse::<T![AtKeyword]>()?)
92		} else if <T![Hash]>::peek(p, c) {
93			Self::Hash(p.parse::<T![Hash]>()?)
94		} else if <T![String]>::peek(p, c) {
95			Self::String(p.parse::<T![String]>()?)
96		} else if <T![Url]>::peek(p, c) {
97			Self::Url(p.parse::<T![Url]>()?)
98		} else if <T![Delim]>::peek(p, c) {
99			p.parse::<T![Delim]>().map(|delim| {
100				// Carefully handle Whitespace rules to ensure whitespace isn't lost when re-serializing
101				let mut rules = AssociatedWhitespaceRules::none();
102				if p.peek_n_with_skip(1, KindSet::COMMENTS) == Kind::Whitespace {
103					rules |= AssociatedWhitespaceRules::EnforceAfter;
104				} else {
105					rules |= AssociatedWhitespaceRules::BanAfter;
106				}
107				Self::Delim(delim.with_associated_whitespace(rules))
108			})?
109		} else if <T![:]>::peek(p, c) {
110			Self::Colon(p.parse::<T![:]>()?)
111		} else if <T![;]>::peek(p, c) {
112			Self::Semicolon(p.parse::<T![;]>()?)
113		} else if <T![,]>::peek(p, c) {
114			Self::Comma(p.parse::<T![,]>()?)
115		} else {
116			Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
117		})
118	}
119}
120
121impl<'a> ToCursors for ComponentValue<'a> {
122	fn to_cursors(&self, s: &mut impl CursorSink) {
123		match self {
124			Self::SimpleBlock(t) => ToCursors::to_cursors(t, s),
125			Self::Function(t) => ToCursors::to_cursors(t, s),
126			Self::Ident(t) => ToCursors::to_cursors(t, s),
127			Self::AtKeyword(t) => ToCursors::to_cursors(t, s),
128			Self::Hash(t) => ToCursors::to_cursors(t, s),
129			Self::String(t) => ToCursors::to_cursors(t, s),
130			Self::Url(t) => ToCursors::to_cursors(t, s),
131			Self::Delim(t) => ToCursors::to_cursors(t, s),
132			Self::Number(t) => ToCursors::to_cursors(t, s),
133			Self::Dimension(t) => ToCursors::to_cursors(t, s),
134			Self::Whitespace(t) => ToCursors::to_cursors(t, s),
135			Self::Colon(t) => ToCursors::to_cursors(t, s),
136			Self::Semicolon(t) => ToCursors::to_cursors(t, s),
137			Self::Comma(t) => ToCursors::to_cursors(t, s),
138		}
139	}
140}
141
142impl<'a> ToSpan for ComponentValue<'a> {
143	fn to_span(&self) -> Span {
144		match self {
145			Self::SimpleBlock(t) => t.to_span(),
146			Self::Function(t) => t.to_span(),
147			Self::Ident(t) => t.to_span(),
148			Self::AtKeyword(t) => t.to_span(),
149			Self::Hash(t) => t.to_span(),
150			Self::String(t) => t.to_span(),
151			Self::Url(t) => t.to_span(),
152			Self::Delim(t) => t.to_span(),
153			Self::Number(t) => t.to_span(),
154			Self::Dimension(t) => t.to_span(),
155			Self::Whitespace(t) => t.to_span(),
156			Self::Colon(t) => t.to_span(),
157			Self::Semicolon(t) => t.to_span(),
158			Self::Comma(t) => t.to_span(),
159		}
160	}
161}
162
163impl<'a> SemanticEq for ComponentValue<'a> {
164	fn semantic_eq(&self, other: &Self, source_text: &str) -> bool {
165		match (self, other) {
166			(Self::SimpleBlock(a), Self::SimpleBlock(b)) => a.semantic_eq(b, source_text),
167			(Self::Function(a), Self::Function(b)) => a.semantic_eq(b, source_text),
168			(Self::Number(a), Self::Number(b)) => a.semantic_eq(b, source_text),
169			(Self::Dimension(a), Self::Dimension(b)) => a.semantic_eq(b, source_text),
170			(Self::Ident(a), Self::Ident(b)) => a.semantic_eq(b, source_text),
171			(Self::AtKeyword(a), Self::AtKeyword(b)) => a.semantic_eq(b, source_text),
172			(Self::Hash(a), Self::Hash(b)) => a.semantic_eq(b, source_text),
173			(Self::String(a), Self::String(b)) => a.semantic_eq(b, source_text),
174			(Self::Url(a), Self::Url(b)) => a.semantic_eq(b, source_text),
175			(Self::Delim(a), Self::Delim(b)) => a.semantic_eq(b, source_text),
176			(Self::Colon(a), Self::Colon(b)) => a.semantic_eq(b, source_text),
177			(Self::Semicolon(a), Self::Semicolon(b)) => a.semantic_eq(b, source_text),
178			(Self::Comma(a), Self::Comma(b)) => a.semantic_eq(b, source_text),
179			// Whitespace has no semantic relevance, other than its presence, so it should always be true
180			(Self::Whitespace(_), Self::Whitespace(_)) => true,
181			_ => false, // Different variants are never equal
182		}
183	}
184}
185
186#[cfg(test)]
187mod tests {
188	use super::*;
189	use crate::{EmptyAtomSet, test_helpers::*};
190
191	#[test]
192	fn test_writes() {
193		assert_parse!(EmptyAtomSet::ATOMS, ComponentValue, "foo");
194		assert_parse!(EmptyAtomSet::ATOMS, ComponentValue, " ");
195		assert_parse!(EmptyAtomSet::ATOMS, ComponentValue, "{block}");
196	}
197}