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