css_parse/traits/lists/
feature_condition_list.rs

1use crate::{Build, Parse, Parser, Peek, Result, diagnostics, keyword_set};
2use bumpalo::collections::Vec;
3
4keyword_set!(
5	pub enum ConditionKeyword {
6		And: "and",
7		Not: "not",
8		Or: "or",
9	}
10);
11
12/// This trait can be used for AST nodes representing a list of "Feature Conditions". This is an amalgamation of
13/// [Supports Conditions][1], [Media Conditions][2], and [Container Queries][3]
14/// This is an implementation of [`<at-rule-list>`][1].
15///
16/// Looking at `<supports-condition>` and `<container-query>` we can se almost identical grammars (eliding some tokens
17/// for brevity):
18///
19/// ```md
20/// <supports-condition>
21///  │├─╮─ <ident-token "not"> ─ <supports-in-parens> ──────────────────────────────╭──┤│
22///     ╰─ <supports-in-parens> ─╮─╭─ <ident-token "and"> ─ <supports-in-parens> ─╮─┤
23///                              │ ╰──────────────────────────────────────────────╯ │
24///                              ├─╭─ <ident-token "or"> ─ <supports-in-parens> ─╮──┤
25///                              │ ╰─────────────────────────────────────────────╯  │
26///                              ╰──────────────────────────────────────────────────╯
27///
28/// <container-query>
29///  │├─╮─ <ident-token "not"> ─ <query-in-parens> ───────────────────────────╭──┤│
30///     ╰─ <supports-in-parens> ─╮─╭─ <ident-token "and"> ─ <supports-in-parens> ─╮─┤
31///                              │ ╰──────────────────────────────────────────────╯ │
32///                              ├─╭─ <ident-token "or"> ─ <supports-in-parens> ─╮──┤
33///                              │ ╰─────────────────────────────────────────────╯  │
34///                              ╰──────────────────────────────────────────────────╯
35///
36/// <media-condition>
37///  │├─╮─ <ident-token "not"> ─ <media-in-parens> ───────────────────────────╭──┤│
38///     ╰─ <media-in-parens> ─╮─╭─ <ident-token "and"> ─ <media-in-parens> ─╮─┤
39///                           │ ╰───────────────────────────────────────────╯ │
40///                           │─╭─ <ident-token "or"> ─ <media-in-parens> ─╮──│
41///                           │ ╰──────────────────────────────────────────╯  │
42///                           ╰───────────────────────────────────────────────╯
43/// ```
44///
45/// The key difference between each of these is their own `<*-in-parens>` tokens. Thus they could all be defined as:
46///
47/// ```md
48/// <condition-prelude-list>
49///  │├─╮─ <ident-token "not"> ─ <feature> ───────────────────╭──┤│
50///     ╰─ <feature> ─╮─╭─ <ident-token "and"> ─ <feature> ─╮─┤
51///                   │ ╰───────────────────────────────────╯ │
52///                   │─╭─ <ident-token "or"> ─ <feature> ─╮──│
53///                   │ ╰──────────────────────────────────╯  │
54///                   ╰───────────────────────────────────────╯
55/// ```
56///
57/// Where `<feature>` is defined by `[FeatureConditionList::FeatureCondition]`, which is required to implement [Parse].
58/// There is a further subtle change for this trait, which is the introduction of the [ConditionKeyword] enum to better
59/// reason about the given condition keyword. This makes the final grammar:
60///
61/// ```md
62/// <condition-keyword>
63///  │├──╮─ <ident-token "not"> ─╭──┤│
64///      ├─ <ident-token "and"> ─┤
65///      ╰─ <ident-token "or"> ──╯
66///
67/// <condition-prelude-list>
68///  │├─╮─ <condition-keyword "not"> ─ <feature> ───────────────────╭──┤│
69///     ╰─ <feature> ─╮─╭─ <condition-keyword "and"> ─ <feature> ─╮─┤
70///                   │ ╰─────────────────────────────────────────╯ │
71///                   │─╭─ <condition-keyword "or"> ─ <feature> ─╮──│
72///                   │ ╰────────────────────────────────────────╯  │
73///                   ╰─────────────────────────────────────────────╯
74/// ```
75///
76/// [1]: https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition
77/// [2]: https://drafts.csswg.org/mediaqueries/#media-condition
78/// [3]: https://drafts.csswg.org/css-conditional-5/#typedef-container-query
79pub trait FeatureConditionList<'a>: Sized + Parse<'a>
80where
81	Self: 'a,
82{
83	type FeatureCondition: Sized + Parse<'a>;
84
85	fn build_is(feature: Self::FeatureCondition) -> Self;
86	fn build_not(keyword: ConditionKeyword, features: Self::FeatureCondition) -> Self;
87	fn build_and(features: Vec<'a, (Self::FeatureCondition, Option<ConditionKeyword>)>) -> Self;
88	fn build_or(features: Vec<'a, (Self::FeatureCondition, Option<ConditionKeyword>)>) -> Self;
89
90	fn parse_condition(p: &mut Parser<'a>) -> Result<Self> {
91		let c = p.peek_next();
92		if ConditionKeyword::peek(p, c) {
93			let keyword = ConditionKeyword::build(p, c);
94			if matches!(keyword, ConditionKeyword::Not(_)) {
95				return Ok(Self::build_is(p.parse::<Self::FeatureCondition>()?));
96			}
97			let source_cursor = p.to_source_cursor(c);
98			Err(diagnostics::UnexpectedIdent(source_cursor.to_string(), c))?
99		}
100		let mut feature = p.parse::<Self::FeatureCondition>()?;
101		let keyword = p.parse_if_peek::<ConditionKeyword>()?;
102		match keyword {
103			Some(ConditionKeyword::And(_)) => {
104				let mut features = Vec::new_in(p.bump());
105				let mut keyword = keyword.unwrap();
106				loop {
107					features.push((feature, Some(keyword)));
108					feature = p.parse::<Self::FeatureCondition>()?;
109					let c = p.peek_next();
110					if !ConditionKeyword::peek(p, c)
111						|| !matches!(ConditionKeyword::build(p, c), ConditionKeyword::And(_))
112					{
113						features.push((feature, None));
114						return Ok(Self::build_and(features));
115					}
116					keyword = ConditionKeyword::build(p, c);
117				}
118			}
119			Some(ConditionKeyword::Or(_)) => {
120				let mut features = Vec::new_in(p.bump());
121				let mut keyword = keyword.unwrap();
122				loop {
123					features.push((feature, Some(keyword)));
124					feature = p.parse::<Self::FeatureCondition>()?;
125					let c = p.peek_next();
126					if !ConditionKeyword::peek(p, c)
127						|| !matches!(ConditionKeyword::build(p, c), ConditionKeyword::And(_))
128					{
129						features.push((feature, None));
130						return Ok(Self::build_or(features));
131					}
132					keyword = ConditionKeyword::build(p, c);
133				}
134			}
135			Some(ConditionKeyword::Not(_)) => {
136				Ok(Self::build_not(keyword.unwrap(), p.parse::<Self::FeatureCondition>()?))
137			}
138			None => Ok(Self::build_is(feature)),
139		}
140	}
141}