Skip to main content

css_parse/syntax/
component_value.rs

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