Skip to main content

css_parse/traits/lists/
feature_condition_list.rs

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