css_parse/syntax/
component_value.rs

1use crate::{
2	AssociatedWhitespaceRules, Cursor, CursorSink, FunctionBlock, Kind, KindSet, Parse, Parser, Peek,
3	Result as ParserResult, SimpleBlock, Span, State, T, ToCursors, ToSpan, diagnostics,
4};
5
6// https://drafts.csswg.org/css-syntax-3/#consume-component-value
7// A compatible "Token" per CSS grammar, subsetted to the tokens possibly
8// rendered by ComponentValue (so no pairwise, function tokens, etc).
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(SimpleBlock<'a>),
13	Function(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> Peek<'a> for ComponentValue<'a> {
29	fn peek(_: &Parser<'a>, c: Cursor) -> bool {
30		let 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		c == kindset
49	}
50}
51
52// https://drafts.csswg.org/css-syntax-3/#consume-component-value
53impl<'a> Parse<'a> for ComponentValue<'a> {
54	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
55		if p.peek::<T![' ']>() {
56			p.parse::<T![' ']>().map(Self::Whitespace)
57		} else if p.peek::<T![PairWiseStart]>() {
58			let old_state = p.set_state(State::Nested);
59			let block = p.parse::<SimpleBlock>();
60			p.set_state(old_state);
61			Ok(Self::SimpleBlock(block?))
62		} else if p.peek::<T![Function]>() {
63			p.parse::<FunctionBlock>().map(Self::Function)
64		} else if p.peek::<T![Number]>() {
65			p.parse::<T![Number]>().map(Self::Number)
66		} else if p.peek::<T![Dimension]>() {
67			p.parse::<T![Dimension]>().map(Self::Dimension)
68		} else if p.peek::<T![Ident]>() {
69			p.parse::<T![Ident]>().map(Self::Ident)
70		} else if p.peek::<T![AtKeyword]>() {
71			p.parse::<T![AtKeyword]>().map(Self::AtKeyword)
72		} else if p.peek::<T![Hash]>() {
73			p.parse::<T![Hash]>().map(Self::Hash)
74		} else if p.peek::<T![String]>() {
75			p.parse::<T![String]>().map(Self::String)
76		} else if p.peek::<T![Url]>() {
77			p.parse::<T![Url]>().map(Self::Url)
78		} else if p.peek::<T![Delim]>() {
79			p.parse::<T![Delim]>().map(|delim| {
80				// Carefully handle Whitespace rules to ensure whitespace isn't lost when re-serializing
81				let mut rules = AssociatedWhitespaceRules::none();
82				if p.peek_next_including_whitespace() == Kind::Whitespace {
83					rules |= AssociatedWhitespaceRules::EnforceAfter;
84				} else {
85					rules |= AssociatedWhitespaceRules::BanAfter;
86				}
87				Self::Delim(delim.with_associated_whitespace(rules))
88			})
89		} else if p.peek::<T![:]>() {
90			p.parse::<T![:]>().map(Self::Colon)
91		} else if p.peek::<T![;]>() {
92			p.parse::<T![;]>().map(Self::Semicolon)
93		} else if p.peek::<T![,]>() {
94			p.parse::<T![,]>().map(Self::Comma)
95		} else {
96			Err(diagnostics::Unexpected(p.next()))?
97		}
98	}
99}
100
101impl<'a> ToCursors for ComponentValue<'a> {
102	fn to_cursors(&self, s: &mut impl CursorSink) {
103		match self {
104			Self::SimpleBlock(t) => ToCursors::to_cursors(t, s),
105			Self::Function(t) => ToCursors::to_cursors(t, s),
106			Self::Ident(t) => ToCursors::to_cursors(t, s),
107			Self::AtKeyword(t) => ToCursors::to_cursors(t, s),
108			Self::Hash(t) => ToCursors::to_cursors(t, s),
109			Self::String(t) => ToCursors::to_cursors(t, s),
110			Self::Url(t) => ToCursors::to_cursors(t, s),
111			Self::Delim(t) => ToCursors::to_cursors(t, s),
112			Self::Number(t) => ToCursors::to_cursors(t, s),
113			Self::Dimension(t) => ToCursors::to_cursors(t, s),
114			Self::Whitespace(t) => ToCursors::to_cursors(t, s),
115			Self::Colon(t) => ToCursors::to_cursors(t, s),
116			Self::Semicolon(t) => ToCursors::to_cursors(t, s),
117			Self::Comma(t) => ToCursors::to_cursors(t, s),
118		}
119	}
120}
121
122impl<'a> ToSpan for ComponentValue<'a> {
123	fn to_span(&self) -> Span {
124		match self {
125			Self::SimpleBlock(t) => t.to_span(),
126			Self::Function(t) => t.to_span(),
127			Self::Ident(t) => t.to_span(),
128			Self::AtKeyword(t) => t.to_span(),
129			Self::Hash(t) => t.to_span(),
130			Self::String(t) => t.to_span(),
131			Self::Url(t) => t.to_span(),
132			Self::Delim(t) => t.to_span(),
133			Self::Number(t) => t.to_span(),
134			Self::Dimension(t) => t.to_span(),
135			Self::Whitespace(t) => t.to_span(),
136			Self::Colon(t) => t.to_span(),
137			Self::Semicolon(t) => t.to_span(),
138			Self::Comma(t) => t.to_span(),
139		}
140	}
141}
142
143#[cfg(test)]
144mod tests {
145	use super::*;
146	use crate::test_helpers::*;
147
148	#[test]
149	fn size_test() {
150		assert_eq!(std::mem::size_of::<ComponentValue>(), 72);
151	}
152
153	#[test]
154	fn test_writes() {
155		assert_parse!(ComponentValue, "foo");
156		assert_parse!(ComponentValue, " ");
157		assert_parse!(ComponentValue, "{block}");
158	}
159}