css_parse/traits/lists/
feature_condition_list.rs

1use crate::{Cursor, Parse, Parser, Peek, Result, token_macros::Ident};
2use bumpalo::collections::Vec;
3
4/// This trait can be used for AST nodes representing a list of "Feature Conditions". This is an amalgamation of
5/// [Supports Conditions][1], [Media Conditions][2], and [Container Queries][3]
6/// This is an implementation of [`<at-rule-list>`][1].
7///
8/// Looking at `<supports-condition>` and `<container-query>` we can se almost identical grammars (eliding some tokens
9/// for brevity):
10///
11/// ```md
12/// <supports-condition>
13///  │├─╮─ <ident-token "not"> ─ <supports-in-parens> ──────────────────────────────╭──┤│
14///     ╰─ <supports-in-parens> ─╮─╭─ <ident-token "and"> ─ <supports-in-parens> ─╮─┤
15///                              │ ╰──────────────────────────────────────────────╯ │
16///                              ├─╭─ <ident-token "or"> ─ <supports-in-parens> ─╮──┤
17///                              │ ╰─────────────────────────────────────────────╯  │
18///                              ╰──────────────────────────────────────────────────╯
19///
20/// <container-query>
21///  │├─╮─ <ident-token "not"> ─ <query-in-parens> ───────────────────────────╭──┤│
22///     ╰─ <supports-in-parens> ─╮─╭─ <ident-token "and"> ─ <supports-in-parens> ─╮─┤
23///                              │ ╰──────────────────────────────────────────────╯ │
24///                              ├─╭─ <ident-token "or"> ─ <supports-in-parens> ─╮──┤
25///                              │ ╰─────────────────────────────────────────────╯  │
26///                              ╰──────────────────────────────────────────────────╯
27///
28/// <media-condition>
29///  │├─╮─ <ident-token "not"> ─ <media-in-parens> ───────────────────────────╭──┤│
30///     ╰─ <media-in-parens> ─╮─╭─ <ident-token "and"> ─ <media-in-parens> ─╮─┤
31///                           │ ╰───────────────────────────────────────────╯ │
32///                           │─╭─ <ident-token "or"> ─ <media-in-parens> ─╮──│
33///                           │ ╰──────────────────────────────────────────╯  │
34///                           ╰───────────────────────────────────────────────╯
35/// ```
36///
37/// The key difference between each of these is their own `<*-in-parens>` tokens. Thus they could all be defined as:
38///
39/// ```md
40/// <condition-prelude-list>
41///  │├─╮─ <ident-token "not"> ─ <feature> ───────────────────╭──┤│
42///     ╰─ <feature> ─╮─╭─ <ident-token "and"> ─ <feature> ─╮─┤
43///                   │ ╰───────────────────────────────────╯ │
44///                   │─╭─ <ident-token "or"> ─ <feature> ─╮──│
45///                   │ ╰──────────────────────────────────╯  │
46///                   ╰───────────────────────────────────────╯
47/// ```
48///
49/// [1]: https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition
50/// [2]: https://drafts.csswg.org/mediaqueries/#media-condition
51/// [3]: https://drafts.csswg.org/css-conditional-5/#typedef-container-query
52pub trait FeatureConditionList<'a>: Sized + Parse<'a>
53where
54	Self: 'a,
55{
56	type FeatureCondition: Sized + Parse<'a>;
57
58	fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
59	where
60		I: Iterator<Item = Cursor> + Clone;
61	fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
62	where
63		I: Iterator<Item = Cursor> + Clone;
64	fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
65	where
66		I: Iterator<Item = Cursor> + Clone;
67
68	fn build_is(feature: Self::FeatureCondition) -> Self;
69	fn build_not(keyword: Ident, feature: Self::FeatureCondition) -> Self;
70	fn build_and(features: Vec<'a, (Self::FeatureCondition, Option<Ident>)>) -> Self;
71	fn build_or(features: Vec<'a, (Self::FeatureCondition, Option<Ident>)>) -> Self;
72
73	fn parse_condition<I>(p: &mut Parser<'a, I>) -> Result<Self>
74	where
75		I: Iterator<Item = Cursor> + Clone,
76	{
77		let c = p.peek_n(1);
78		if Ident::peek(p, c) && Self::keyword_is_not(p, c) {
79			return Ok(Self::build_not(p.parse::<Ident>()?, p.parse::<Self::FeatureCondition>()?));
80		}
81		let mut feature = p.parse::<Self::FeatureCondition>()?;
82		let c = p.peek_n(1);
83		if Ident::peek(p, c) {
84			if Self::keyword_is_and(p, c) {
85				let mut features = Vec::new_in(p.bump());
86				let mut keyword = p.parse::<Ident>()?;
87				loop {
88					features.push((feature, Some(keyword)));
89					feature = p.parse::<Self::FeatureCondition>()?;
90					let c = p.peek_n(1);
91					if !(Ident::peek(p, c) && Self::keyword_is_and(p, c)) {
92						features.push((feature, None));
93						return Ok(Self::build_and(features));
94					}
95					keyword = p.parse::<Ident>()?
96				}
97			} else if Self::keyword_is_or(p, c) {
98				let mut features = Vec::new_in(p.bump());
99				let mut keyword = p.parse::<Ident>()?;
100				loop {
101					features.push((feature, Some(keyword)));
102					feature = p.parse::<Self::FeatureCondition>()?;
103					let c = p.peek_n(1);
104					if !(Ident::peek(p, c) && Self::keyword_is_or(p, c)) {
105						features.push((feature, None));
106						return Ok(Self::build_or(features));
107					}
108					keyword = p.parse::<Ident>()?
109				}
110			}
111		}
112		Ok(Self::build_is(feature))
113	}
114}