1use super::prelude::*;
2use crate::selector::ComplexSelector;
3
4#[derive(Parse, Peek, ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
37#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
38#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.property"))]
39pub struct SupportsRule<'a> {
40 #[cfg_attr(feature = "visitable", visit(skip))]
41 #[atom(CssAtomSet::Supports)]
42 pub name: T![AtKeyword],
43 pub prelude: SupportsCondition<'a>,
44 pub block: SupportsRuleBlock<'a>,
45}
46
47#[derive(Parse, Peek, ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
48#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
49#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
50pub struct SupportsRuleBlock<'a>(RuleList<'a, Rule<'a>>);
51
52#[derive(ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
54pub enum SupportsCondition<'a> {
55 Is(SupportsFeature<'a>),
56 Not(T![Ident], SupportsFeature<'a>),
57 And(Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>),
58 Or(Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>),
59}
60
61impl<'a> FeatureConditionList<'a> for SupportsCondition<'a> {
62 type FeatureCondition = SupportsFeature<'a>;
63 fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
64 where
65 I: Iterator<Item = Cursor> + Clone,
66 {
67 p.equals_atom(c, &CssAtomSet::Not)
68 }
69 fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
70 where
71 I: Iterator<Item = Cursor> + Clone,
72 {
73 p.equals_atom(c, &CssAtomSet::And)
74 }
75 fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
76 where
77 I: Iterator<Item = Cursor> + Clone,
78 {
79 p.equals_atom(c, &CssAtomSet::Or)
80 }
81 fn build_is(feature: SupportsFeature<'a>) -> Self {
82 Self::Is(feature)
83 }
84 fn build_not(keyword: T![Ident], feature: SupportsFeature<'a>) -> Self {
85 Self::Not(keyword, feature)
86 }
87 fn build_and(feature: Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>) -> Self {
88 Self::And(feature)
89 }
90 fn build_or(feature: Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>) -> Self {
91 Self::Or(feature)
92 }
93}
94
95impl<'a> Parse<'a> for SupportsCondition<'a> {
96 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
97 where
98 I: Iterator<Item = Cursor> + Clone,
99 {
100 if p.peek::<T![Function]>() || p.peek::<T!['(']>() {
101 return Ok(Self::Is(p.parse::<SupportsFeature>()?));
102 }
103 Self::parse_condition(p)
104 }
105}
106
107#[cfg(feature = "visitable")]
108impl<'a> VisitableTrait for SupportsCondition<'a> {
109 fn accept<V: Visit>(&self, v: &mut V) {
110 match self {
111 Self::Is(feature) => feature.accept(v),
112 Self::Not(_, feature) => feature.accept(v),
113 Self::And(features) => {
114 for (feature, _) in features {
115 feature.accept(v);
116 }
117 }
118 Self::Or(features) => {
119 for (feature, _) in features {
120 feature.accept(v);
121 }
122 }
123 }
124 }
125}
126
127#[cfg(feature = "visitable")]
128impl<'a> VisitableMut for SupportsCondition<'a> {
129 fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
130 match self {
131 Self::Is(feature) => feature.accept_mut(v),
132 Self::Not(_, feature) => feature.accept_mut(v),
133 Self::And(features) => {
134 for (feature, _) in features {
135 feature.accept_mut(v);
136 }
137 }
138 Self::Or(features) => {
139 for (feature, _) in features {
140 feature.accept_mut(v);
141 }
142 }
143 }
144 }
145}
146
147#[allow(clippy::large_enum_variant)] #[derive(ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
150pub enum SupportsFeature<'a> {
151 FontTech(Option<T!['(']>, T![Function], ComponentValues<'a>, T![')'], Option<T![')']>),
152 FontFormat(Option<T!['(']>, T![Function], ComponentValues<'a>, T![')'], Option<T![')']>),
153 Selector(Option<T!['(']>, T![Function], ComplexSelector<'a>, T![')'], Option<T![')']>),
154 Property(T!['('], Declaration<'a, StyleValue<'a>>, Option<T![')']>),
155}
156
157impl<'a> Parse<'a> for SupportsFeature<'a> {
158 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
159 where
160 I: Iterator<Item = Cursor> + Clone,
161 {
162 let open = p.parse_if_peek::<T!['(']>()?;
163 if p.peek::<T![Function]>() {
164 let function = p.parse::<T![Function]>()?;
165 match p.to_atom::<CssAtomSet>(function.into()) {
166 CssAtomSet::Selector => {
167 let selector = p.parse::<ComplexSelector>()?;
168 let close = p.parse::<T![')']>()?;
170 let open_close = if open.is_some() { Some(p.parse::<T![')']>()?) } else { None };
171 Ok(Self::Selector(open, function, selector, close, open_close))
172 }
173 CssAtomSet::FontTech => {
174 todo!();
175 }
176 CssAtomSet::FontFormat => {
177 todo!();
178 }
179 _ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected_function))?,
180 }
181 } else if let Some(open) = open {
182 let property = p.parse::<Declaration<'a, StyleValue<'a>>>()?;
183 let close = p.parse_if_peek::<T![')']>()?;
184 Ok(Self::Property(open, property, close))
185 } else {
186 Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
187 }
188 }
189}
190
191#[cfg(feature = "visitable")]
192impl<'a> VisitableTrait for SupportsFeature<'a> {
193 fn accept<V: Visit>(&self, v: &mut V) {
194 match self {
195 Self::FontTech(_, _, _, _, _) => todo!(),
196 Self::FontFormat(_, _, _, _, _) => todo!(),
197 Self::Selector(_, _, selector, _, _) => selector.accept(v),
198 Self::Property(_, property, _) => property.accept(v),
199 }
200 }
201}
202
203#[cfg(feature = "visitable")]
204impl<'a> VisitableMut for SupportsFeature<'a> {
205 fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
206 match self {
207 Self::FontTech(_, _, _, _, _) => todo!(),
208 Self::FontFormat(_, _, _, _, _) => todo!(),
209 Self::Selector(_, _, selector, _, _) => selector.accept_mut(v),
210 Self::Property(_, property, _) => property.accept_mut(v),
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::CssAtomSet;
219 use css_parse::assert_parse;
220
221 #[test]
222 fn size_test() {
223 assert_eq!(std::mem::size_of::<SupportsRule>(), 496);
224 assert_eq!(std::mem::size_of::<SupportsCondition>(), 416);
225 assert_eq!(std::mem::size_of::<SupportsRuleBlock>(), 64);
226 }
227
228 #[test]
229 fn test_writes() {
230 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(color:black){}");
231 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(width:1px){body{width:1px}}");
232 }
243}