Skip to main content

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. Parsing is also a little bit delicate, as the two
4/// [Cursors][crate::Cursor] must appear next to each other - no whitespace nor comments can be present betwixt the
5/// colon and ident.
6///
7/// # Example
8///
9/// ```
10/// use css_parse::*;
11/// use csskit_derives::*;
12/// use derive_atom_set::*;
13///
14/// #[derive(Debug, Default, AtomSet, Copy, Clone, PartialEq)]
15/// pub enum MyAtomSet {
16///   #[default]
17///   _None,
18///   Foo,
19///   Bar,
20///   Baz,
21/// }
22/// impl MyAtomSet {
23///   const ATOMS: MyAtomSet = MyAtomSet::_None;
24/// }
25///
26/// pseudo_class!(
27///   /// Some docs on this type...
28///   #[derive(Debug, ToCursors, ToSpan)]
29///   pub enum MyPseudoClass {
30///     Foo: MyAtomSet::Foo,
31///     Bar: MyAtomSet::Bar,
32///     Baz: MyAtomSet::Baz,
33///   }
34/// );
35///
36/// // The result will be one of the variants in the enum, matching the keyword.
37/// assert_parse!(MyAtomSet::ATOMS, MyPseudoClass, ":foo");
38///
39/// // Matches are case insensitive
40/// assert_parse!(MyAtomSet::ATOMS, MyPseudoClass, ":BaR");
41///
42/// // Words that do not match will fail to Peek.
43/// assert_peek_false!(MyAtomSet::ATOMS, MyPseudoClass, ":bing");
44///
45/// // The `:` is also required
46/// assert_peek_false!(MyAtomSet::ATOMS, MyPseudoClass, "baz");
47///
48/// // Any tokens between the `:` and ident result in a parse error:
49/// assert_peek_false!(MyAtomSet::ATOMS, MyPseudoClass, ": foo");
50/// ```
51#[macro_export]
52macro_rules! pseudo_class {
53	($(#[$meta:meta])* $vis:vis enum $name: ident { $first_variant: ident: $atoms: ident::$first:ident, $( $variant: ident: $variant_pat: pat$(,)?)* }) => {
54		$(#[$meta])*
55		pub enum $name {
56			$first_variant($crate::T![:], $crate::T![Ident]),
57			$($variant($crate::T![:], $crate::T![Ident]),)*
58		}
59
60		impl<'a> $crate::Peek<'a> for $name {
61			const PEEK_KINDSET: $crate::KindSet = $crate::KindSet::new(&[$crate::Kind::Colon]);
62
63			#[inline(always)]
64			fn peek<I>(p: &$crate::Parser<'a, I>, c: $crate::Cursor) -> bool
65			where
66				I: Iterator<Item = $crate::Cursor> + Clone,
67			{
68				let c2 = p.peek_n_including_whitespace(2);
69				c == $crate::Kind::Colon &&
70				c2 == $crate::Kind::Ident &&
71				matches!(p.to_atom::<$atoms>(c2), $atoms::$first $(| $variant_pat)*)
72			}
73		}
74
75		impl<'a> $crate::Parse<'a> for $name {
76			fn parse<I>(p: &mut $crate::Parser<'a, I>) -> $crate::Result<Self>
77			where
78				I: Iterator<Item = $crate::Cursor> + Clone,
79			{
80				let colon = p.parse::<$crate::T![:]>()?;
81				let skip = p.set_skip($crate::KindSet::NONE);
82				let ident = p.parse::<$crate::T![Ident]>();
83				p.set_skip(skip);
84				let ident = ident?;
85				match p.to_atom::<$atoms>(ident.into()) {
86					$atoms::$first => Ok(Self::$first_variant(colon, ident)),
87					$($variant_pat => Ok(Self::$variant(colon, ident)),)*
88					_ => {
89						Err($crate::Diagnostic::new(ident.into(), $crate::Diagnostic::unexpected_ident))?
90					}
91				}
92			}
93		}
94	}
95}