Skip to main content

css_ast/types/
syntax.rs

1use super::prelude::*;
2
3/// <https://drafts.csswg.org/css-values-5/#css-syntax>
4///
5/// ```text,ignore
6/// <syntax> = '*' | <syntax-component> [ <syntax-combinator> <syntax-component> ]* | <syntax-string>
7/// <syntax-component> = <syntax-single-component> <syntax-multiplier>?
8///                    | '<' transform-list '>'
9/// <syntax-single-component> = '<' <syntax-type-name> '>' | <ident>
10/// <syntax-type-name> = angle | color | custom-ident | image | integer
11///                    | length | length-percentage | number
12///                    | percentage | resolution | string | time
13///                    | url | transform-function
14/// <syntax-combinator> = '|'
15/// <syntax-multiplier> = [ '#' | '+' ]
16///
17/// <syntax-string> = <string>
18/// ```
19#[node]
20#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
22#[derive(csskit_derives::NodeWithMetadata)]
23pub enum Syntax<'a> {
24	Universal(T![*]),
25	Components(SyntaxComponent, Vec<'a, (T![|], SyntaxComponent)>),
26	String(T![String]),
27}
28
29/// ```text,ignore
30/// <syntax-component> = <syntax-single-component> <syntax-multiplier>?
31///                    | '<' transform-list '>'
32/// ```
33///
34/// Whitespace is not allowed between the angle brackets and the type name they enclose, nor between a component and
35/// its multiplier. `<transform-list>` may not be followed by a multiplier.
36#[node]
37#[derive(ToSpan, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
39#[derive(csskit_derives::NodeWithMetadata)]
40pub enum SyntaxComponent {
41	Type(T![<], SyntaxTypeName, T![>], Option<SyntaxMultiplier>),
42	TransformList(T![<], T![Ident], T![>]),
43	Keyword(T![Ident], Option<SyntaxMultiplier>),
44}
45
46impl SyntaxComponent {
47	fn parse_bracketed<'a, I>(p: &mut Parser<'a, I>, open: T![<]) -> ParserResult<Self>
48	where
49		I: Iterator<Item = Cursor> + Clone,
50	{
51		let c = p.peek_n(1);
52		if c == Kind::Ident && p.to_atom::<CssAtomSet>(c) == CssAtomSet::TransformList {
53			let ident = p.parse::<T![Ident]>()?;
54			let close = p.parse::<T![>]>()?;
55			return Ok(Self::TransformList(open, ident, close));
56		}
57		let name = p.parse::<SyntaxTypeName>()?;
58		let close = p.parse::<T![>]>()?;
59		let multiplier = p.parse_if_peek::<SyntaxMultiplier>()?;
60		Ok(Self::Type(open, name, close, multiplier))
61	}
62}
63
64impl<'a> Peek<'a> for SyntaxComponent {
65	const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Ident, Kind::Delim]);
66
67	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
68	where
69		I: Iterator<Item = Cursor> + Clone,
70	{
71		c == Kind::Ident || <T![<] as Peek>::peek(p, c)
72	}
73}
74
75impl<'a> Parse<'a> for SyntaxComponent {
76	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
77	where
78		I: Iterator<Item = Cursor> + Clone,
79	{
80		if p.peek::<T![Ident]>() {
81			let ident = p.parse::<T![Ident]>()?;
82			let skip = p.set_skip(KindSet::NONE);
83			let multiplier = p.parse_if_peek::<SyntaxMultiplier>();
84			p.set_skip(skip);
85			return Ok(Self::Keyword(ident, multiplier?));
86		}
87		let open = p.parse::<T![<]>()?;
88		let skip = p.set_skip(KindSet::NONE);
89		let component = Self::parse_bracketed(p, open);
90		p.set_skip(skip);
91		component
92	}
93}
94
95/// ```text,ignore
96/// <syntax-type-name> = angle | color | custom-ident | image | integer
97///                    | length | length-percentage | number
98///                    | percentage | resolution | string | time
99///                    | url | transform-function
100/// ```
101#[node]
102#[derive(
103	Parse, Peek, IntoCursor, ToSpan, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
104)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
106#[derive(csskit_derives::NodeWithMetadata)]
107pub enum SyntaxTypeName {
108	#[atom(CssAtomSet::Angle)]
109	Angle(T![Ident]),
110	#[atom(CssAtomSet::Color)]
111	Color(T![Ident]),
112	#[atom(CssAtomSet::CustomIdent)]
113	CustomIdent(T![Ident]),
114	#[atom(CssAtomSet::Image)]
115	Image(T![Ident]),
116	#[atom(CssAtomSet::Integer)]
117	Integer(T![Ident]),
118	#[atom(CssAtomSet::Length)]
119	Length(T![Ident]),
120	#[atom(CssAtomSet::LengthPercentage)]
121	LengthPercentage(T![Ident]),
122	#[atom(CssAtomSet::Number)]
123	Number(T![Ident]),
124	#[atom(CssAtomSet::Percentage)]
125	Percentage(T![Ident]),
126	#[atom(CssAtomSet::Resolution)]
127	Resolution(T![Ident]),
128	#[atom(CssAtomSet::String)]
129	String(T![Ident]),
130	#[atom(CssAtomSet::Time)]
131	Time(T![Ident]),
132	#[atom(CssAtomSet::Url)]
133	Url(T![Ident]),
134	#[atom(CssAtomSet::TransformFunction)]
135	TransformFunction(T![Ident]),
136}
137
138/// ```text,ignore
139/// <syntax-multiplier> = [ '#' | '+' ]
140/// ```
141#[node]
142#[derive(
143	Parse, Peek, IntoCursor, ToSpan, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
144)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
146#[derive(csskit_derives::NodeWithMetadata)]
147pub enum SyntaxMultiplier {
148	Hash(T![#]),
149	Plus(T![+]),
150}
151
152#[cfg(test)]
153mod tests {
154	use super::*;
155	use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
156
157	#[test]
158	fn test_writes() {
159		assert_parse!(CssAtomSet::ATOMS, Syntax, "*");
160		assert_parse!(CssAtomSet::ATOMS, Syntax, "auto");
161		assert_parse!(CssAtomSet::ATOMS, Syntax, "auto+");
162		assert_parse!(CssAtomSet::ATOMS, Syntax, "auto#");
163		assert_parse!(CssAtomSet::ATOMS, Syntax, "<length>");
164		assert_parse!(CssAtomSet::ATOMS, Syntax, "<length-percentage>");
165		assert_parse!(CssAtomSet::ATOMS, Syntax, "<custom-ident>");
166		assert_parse!(CssAtomSet::ATOMS, Syntax, "<color>#");
167		assert_parse!(CssAtomSet::ATOMS, Syntax, "<transform-function>+");
168		assert_parse!(CssAtomSet::ATOMS, Syntax, "<transform-list>");
169		assert_parse!(CssAtomSet::ATOMS, Syntax, "<percentage> | <number> | auto");
170		assert_parse!(CssAtomSet::ATOMS, Syntax, "red|<color>");
171		assert_parse!(CssAtomSet::ATOMS, Syntax, "\"<length> | auto\"");
172	}
173
174	#[test]
175	fn test_errors() {
176		assert_parse_error!(CssAtomSet::ATOMS, Syntax, "< length>");
177		assert_parse_error!(CssAtomSet::ATOMS, Syntax, "<length >");
178		assert_parse_error!(CssAtomSet::ATOMS, Syntax, "<length> +");
179		assert_parse_error!(CssAtomSet::ATOMS, Syntax, "<foo>");
180		assert_parse_error!(CssAtomSet::ATOMS, Syntax, "<transform-list>+");
181		assert_parse_error!(CssAtomSet::ATOMS, Syntax, "<length> <color>");
182		assert_parse_error!(CssAtomSet::ATOMS, Syntax, "auto |");
183		assert_peek_false!(CssAtomSet::ATOMS, Syntax, "1");
184	}
185}