css_parse/macros/
pseudo_class.rs

1/// A macro for defining pseudo classes.
2///
3/// This makes it much easier to define a pseudo class, which would otherwise need to define a
4/// [keyword_set][crate::keyword_set] or similar, in order to build up the two [Cursors][crate::Cursor] required to
5/// parse. Parsing is also a little bit delicate, as the two [Cursors][crate::Cursor] must appear next to each
6/// other - no whitespace nor comments can be present betwixt the colon and ident.
7///
8/// # Example
9///
10/// ```
11/// use css_parse::*;
12/// use bumpalo::Bump;
13/// pseudo_class!(
14///   /// Some docs on this type...
15///   pub enum MyPseudoClass {
16///     Foo: "foo",
17///     Bar: "bar",
18///     Baz: "baz"
19///   }
20/// );
21///
22/// // The result will be one of the variants in the enum, matching the keyword.
23/// assert_parse!(MyPseudoClass, ":foo");
24///
25/// // Matches are case insensitive
26/// assert_parse!(MyPseudoClass, ":BaR");
27///
28/// // Words that do not match will fail to parse.
29/// assert_parse_error!(MyPseudoClass, ":bing");
30///
31/// // The `:` is also required
32/// assert_parse_error!(MyPseudoClass, "baz");
33///
34/// // Any tokens between the `:` and ident result in a parse error:
35/// assert_parse_error!(MyPseudoClass, ": foo");
36/// ```
37#[macro_export]
38macro_rules! pseudo_class {
39	($(#[$meta:meta])* $vis:vis enum $name: ident { $( $variant: ident: $variant_str: tt$(,)?)+ }) => {
40		$(#[$meta])*
41		#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
42		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
43		pub enum $name {
44			$($variant($crate::T![:], $crate::T![Ident]),)+
45		}
46
47		impl<'a> $crate::Peek<'a> for $name {
48			fn peek(p: &$crate::Parser<'a>, c: $crate::Cursor) -> bool {
49				let c2 = p.peek_n(2);
50				c == $crate::Kind::Colon && (c2 == $crate::Kind::Ident && Self::MAP.get(&p.parse_str_lower(c2)).is_some())
51			}
52		}
53
54		impl<'a> $crate::Parse<'a> for $name {
55			fn parse(p: &mut $crate::Parser<'a>) -> $crate::Result<Self> {
56				let colon = p.parse::<$crate::T![:]>()?;
57				let skip = p.set_skip($crate::KindSet::NONE);
58				let ident = p.parse::<$crate::T![Ident]>();
59				p.set_skip(skip);
60				let ident = ident?;
61				if let Some(val) = Self::MAP.get(&p.parse_str_lower(ident.into())) {
62					match val {
63						$(Self::$variant(_, _) => Ok(Self::$variant(colon, ident)),)+
64					}
65				} else {
66					use $crate::ToSpan;
67					Err($crate::diagnostics::UnexpectedIdent(p.parse_str(ident.into()).into(), ident.into()))?
68				}
69			}
70		}
71
72		impl $crate::ToCursors for $name {
73			fn to_cursors(&self, s: &mut impl $crate::CursorSink) {
74				match self {
75					$(Self::$variant(colon, ident) => {
76						s.append((*colon).into());
77						s.append((*ident).into());
78					})+
79				}
80			}
81		}
82
83		impl $crate::ToSpan for $name {
84			fn to_span(&self) -> $crate::Span {
85				match self {
86					$($name::$variant(a, b) => a.to_span() + b.to_span(),)+
87				}
88			}
89		}
90
91		impl $name {
92			const MAP: phf::Map<&'static str, $name> = phf::phf_map! {
93					$($variant_str => $name::$variant(<$crate::T![:]>::dummy(), <$crate::T![Ident]>::dummy()),)+
94			};
95		}
96	}
97}