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}