1use super::prelude::*;
2use crate::{CssMetadata, MediaCondition, StyleQuery, StyleValue, SupportsCondition};
3use css_parse::{
4 ComponentValues, Declaration, FeatureConditionList,
5 token_macros::{Colon, Semicolon},
6};
7
8#[node]
19#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
21#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
22#[derive(csskit_derives::NodeWithMetadata)]
23pub enum IfTest<'a> {
24 SupportsDeclaration(
26 #[cfg_attr(feature = "visitable", visit(skip))] Function,
27 Box<'a, Declaration<'a, StyleValue<'a>, CssMetadata>>,
28 #[cfg_attr(feature = "visitable", visit(skip))]
29 #[semantic_eq(skip)]
30 RightParen,
31 ),
32 Supports(
34 #[cfg_attr(feature = "visitable", visit(skip))] Function,
35 SupportsCondition<'a>,
36 #[cfg_attr(feature = "visitable", visit(skip))]
37 #[semantic_eq(skip)]
38 RightParen,
39 ),
40 Media(
42 #[cfg_attr(feature = "visitable", visit(skip))] Function,
43 MediaCondition<'a>,
44 #[cfg_attr(feature = "visitable", visit(skip))]
45 #[semantic_eq(skip)]
46 RightParen,
47 ),
48 Style(
50 #[cfg_attr(feature = "visitable", visit(skip))] Function,
51 StyleQuery<'a>,
52 #[cfg_attr(feature = "visitable", visit(skip))]
53 #[semantic_eq(skip)]
54 RightParen,
55 ),
56 Group(
58 #[cfg_attr(feature = "visitable", visit(skip))]
59 #[semantic_eq(skip)]
60 LeftParen,
61 Box<'a, IfConditionExpr<'a>>,
62 #[cfg_attr(feature = "visitable", visit(skip))]
63 #[semantic_eq(skip)]
64 RightParen,
65 ),
66 #[cfg_attr(feature = "visitable", visit(skip))]
68 GeneralEnclosed(
69 #[cfg_attr(feature = "visitable", visit(skip))]
70 #[semantic_eq(skip)]
71 Function,
72 ComponentValues<'a>,
73 #[cfg_attr(feature = "visitable", visit(skip))]
74 #[semantic_eq(skip)]
75 RightParen,
76 ),
77}
78
79impl<'a> Peek<'a> for IfTest<'a> {
80 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::LeftParen, Kind::Function]);
81}
82
83impl<'a> Parse<'a> for IfTest<'a> {
84 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
85 where
86 I: Iterator<Item = Cursor> + Clone,
87 {
88 if p.peek::<LeftParen>() {
90 let open = p.parse::<LeftParen>()?;
91 let expr = p.parse::<IfConditionExpr>()?;
92 let close = p.parse::<RightParen>()?;
93 return Ok(Self::Group(open, Box::new_in(p.alloc(), expr), close));
94 }
95 let function = p.parse::<Function>()?;
96 match p.to_atom::<CssAtomSet>(function.into()) {
97 CssAtomSet::Supports => {
98 let c = p.peek_n(1);
102 if c == Kind::Ident && p.peek_n(2) == Kind::Colon {
103 let decl = p.parse::<Declaration<'a, StyleValue<'a>, CssMetadata>>()?;
104 let close = p.parse::<RightParen>()?;
105 Ok(Self::SupportsDeclaration(function, Box::new_in(p.alloc(), decl), close))
106 } else {
107 let condition = p.parse::<SupportsCondition>()?;
108 let close = p.parse::<RightParen>()?;
109 Ok(Self::Supports(function, condition, close))
110 }
111 }
112 CssAtomSet::Media => {
113 let condition = p.parse::<MediaCondition>()?;
114 let close = p.parse::<RightParen>()?;
115 Ok(Self::Media(function, condition, close))
116 }
117 CssAtomSet::Style => {
118 let query = p.parse::<StyleQuery>()?;
119 let close = p.parse::<RightParen>()?;
120 Ok(Self::Style(function, query, close))
121 }
122 _ => {
124 let values = p.parse::<ComponentValues>()?;
125 let close = p.parse::<RightParen>()?;
126 Ok(Self::GeneralEnclosed(function, values, close))
127 }
128 }
129 }
130}
131
132#[node]
136#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
138#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
139#[derive(csskit_derives::NodeWithMetadata)]
140pub enum IfConditionExpr<'a> {
141 Is(IfTest<'a>),
142 Not(Ident, IfTest<'a>),
143 #[cfg_attr(feature = "visitable", visit(skip))]
144 And(Vec<'a, (IfTest<'a>, Option<Ident>)>),
145 #[cfg_attr(feature = "visitable", visit(skip))]
146 Or(Vec<'a, (IfTest<'a>, Option<Ident>)>),
147}
148
149impl<'a> FeatureConditionList<'a> for IfConditionExpr<'a> {
150 type FeatureCondition = IfTest<'a>;
151 fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
152 where
153 I: Iterator<Item = Cursor> + Clone,
154 {
155 p.equals_atom(c, &CssAtomSet::Not)
156 }
157 fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
158 where
159 I: Iterator<Item = Cursor> + Clone,
160 {
161 p.equals_atom(c, &CssAtomSet::And)
162 }
163 fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
164 where
165 I: Iterator<Item = Cursor> + Clone,
166 {
167 p.equals_atom(c, &CssAtomSet::Or)
168 }
169 fn build_is(feature: IfTest<'a>) -> Self {
170 Self::Is(feature)
171 }
172 fn build_not(keyword: Ident, feature: IfTest<'a>) -> Self {
173 Self::Not(keyword, feature)
174 }
175 fn build_and(features: Vec<'a, (IfTest<'a>, Option<Ident>)>) -> Self {
176 Self::And(features)
177 }
178 fn build_or(features: Vec<'a, (IfTest<'a>, Option<Ident>)>) -> Self {
179 Self::Or(features)
180 }
181}
182
183impl<'a> Peek<'a> for IfConditionExpr<'a> {
184 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::LeftParen, Kind::Function, Kind::Ident]);
185}
186
187impl<'a> Parse<'a> for IfConditionExpr<'a> {
188 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
189 where
190 I: Iterator<Item = Cursor> + Clone,
191 {
192 Self::parse_condition(p)
193 }
194}
195
196#[node]
200#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
201#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
202#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
203#[derive(csskit_derives::NodeWithMetadata)]
204pub enum IfCondition<'a> {
205 Else(#[atom(CssAtomSet::Else)] Ident),
206 Expr(IfConditionExpr<'a>),
207}
208
209#[node]
214#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
216#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
217#[derive(csskit_derives::NodeWithMetadata)]
218pub struct IfBranch<'a, V> {
219 pub condition: IfCondition<'a>,
220 #[semantic_eq(skip)]
221 pub colon: Colon,
222 pub value: Option<V>,
223}
224
225#[node]
233#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
234#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
235#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
236#[derive(csskit_derives::NodeWithMetadata)]
237#[metadata(declaration_kinds = Computed)]
238pub struct IfFunction<'a, V> {
239 #[cfg_attr(feature = "visitable", visit(skip))]
240 #[semantic_eq(skip)]
241 pub name: Function,
242 pub branches: Vec<'a, (IfBranch<'a, V>, Option<Semicolon>)>,
243 #[cfg_attr(feature = "visitable", visit(skip))]
244 #[semantic_eq(skip)]
245 pub close: RightParen,
246}
247
248impl<'a, V: Peek<'a>> Peek<'a> for IfFunction<'a, V> {
249 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Function]);
250
251 #[inline(always)]
252 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
253 where
254 I: Iterator<Item = Cursor> + Clone,
255 {
256 <Function>::peek(p, c) && p.equals_atom(c, &CssAtomSet::If)
257 }
258}
259
260impl<'a, V: Parse<'a> + Peek<'a>> Parse<'a> for IfFunction<'a, V> {
261 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
262 where
263 I: Iterator<Item = Cursor> + Clone,
264 {
265 let name = p.parse::<Function>()?;
266 let mut branches = Vec::new_in(p.alloc());
267 loop {
268 let branch = p.parse::<IfBranch<'a, V>>()?;
269 let semicolon = p.parse_if_peek::<Semicolon>()?;
270 let had_semicolon = semicolon.is_some();
271 branches.push((branch, semicolon));
272 if !had_semicolon || p.peek::<RightParen>() {
273 break;
274 }
275 }
276 let close = p.parse::<RightParen>()?;
277 Ok(Self { name, branches, close })
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use crate::{CssAtomSet, Length, Value};
285 use css_parse::assert_parse;
286
287 type IfLength<'a> = IfFunction<'a, Value<'a, Length>>;
288
289 #[test]
290 fn test_if_function() {
291 assert_parse!(CssAtomSet::ATOMS, IfLength, "if(style(--x: 1px): 10px; else: 20px)");
292 assert_parse!(CssAtomSet::ATOMS, IfLength, "if(else: 1px)");
293 assert_parse!(CssAtomSet::ATOMS, IfLength, "if(supports(color: red): 1px)");
294 assert_parse!(CssAtomSet::ATOMS, IfLength, "if(media((width: 100px)): 1px)");
295 assert_parse!(CssAtomSet::ATOMS, IfLength, "if(supports(color: red) and style(--x: 1px): 1px)");
296 assert_parse!(CssAtomSet::ATOMS, IfLength, "if(not style(--x: 1px): 1px)");
297 assert_parse!(CssAtomSet::ATOMS, IfLength, "if(else: var(--fallback))");
298 }
299}