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