css_parse/syntax/
component_value.rs

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