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}