1use crate::{Rule, Visit, VisitMut, Visitable as VisitableTrait, VisitableMut, diagnostics};
2use bumpalo::collections::Vec;
3use css_parse::{
4 AtRule, Build, ConditionKeyword, Cursor, FeatureConditionList, Kind, Parse, Parser, Peek, PreludeList,
5 Result as ParserResult, RuleList, T, atkeyword_set, keyword_set,
6};
7use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
8use csskit_proc_macro::visit;
9
10mod features;
11pub use features::*;
12
13atkeyword_set!(pub struct AtContainerKeyword "container");
14
15#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.container"))]
19#[visit]
20pub struct ContainerRule<'a>(pub AtRule<AtContainerKeyword, ContainerConditionList<'a>, ContainerRulesBlock<'a>>);
21
22#[derive(Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
24pub struct ContainerRulesBlock<'a>(RuleList<'a, Rule<'a>>);
25
26#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
28pub struct ContainerConditionList<'a>(pub Vec<'a, ContainerCondition<'a>>);
29
30impl<'a> PreludeList<'a> for ContainerConditionList<'a> {
31 type PreludeItem = ContainerCondition<'a>;
32}
33
34impl<'a> Parse<'a> for ContainerConditionList<'a> {
35 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
36 Ok(Self(Self::parse_prelude_list(p)?))
37 }
38}
39
40#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
42pub struct ContainerCondition<'a> {
43 #[visit(skip)]
44 pub name: Option<T![Ident]>,
45 pub condition: Option<ContainerQuery<'a>>,
46}
47
48impl<'a> Parse<'a> for ContainerCondition<'a> {
49 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
50 let mut name = None;
51 let c = p.peek_n(1);
52 if c == Kind::Ident {
53 match p.parse_str_lower(c) {
54 "none" | "and" | "not" | "or" => {}
55 _ => {
56 name = Some(p.parse::<T![Ident]>()?);
57 }
58 }
59 }
60 let condition =
61 if name.is_none() { Some(p.parse::<ContainerQuery>()?) } else { p.parse_if_peek::<ContainerQuery>()? };
62 Ok(Self { name, condition })
63 }
64}
65
66#[derive(ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
68#[visit]
69pub enum ContainerQuery<'a> {
70 Is(ContainerFeature<'a>),
71 Not(ConditionKeyword, ContainerFeature<'a>),
72 And(Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>),
73 Or(Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>),
74}
75
76impl<'a> Peek<'a> for ContainerQuery<'a> {
77 fn peek(p: &Parser<'a>, c: Cursor) -> bool {
78 <T![Function]>::peek(p, c) || <T![Ident]>::peek(p, c)
79 }
80}
81
82impl<'a> Parse<'a> for ContainerQuery<'a> {
83 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
84 Self::parse_condition(p)
85 }
86}
87
88impl<'a> FeatureConditionList<'a> for ContainerQuery<'a> {
89 type FeatureCondition = ContainerFeature<'a>;
90 fn build_is(feature: ContainerFeature<'a>) -> Self {
91 Self::Is(feature)
92 }
93 fn build_not(keyword: ConditionKeyword, feature: ContainerFeature<'a>) -> Self {
94 Self::Not(keyword, feature)
95 }
96 fn build_and(feature: Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>) -> Self {
97 Self::And(feature)
98 }
99 fn build_or(feature: Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>) -> Self {
100 Self::Or(feature)
101 }
102}
103
104impl<'a> VisitableTrait for ContainerQuery<'a> {
105 fn accept<V: Visit>(&self, v: &mut V) {
106 v.visit_container_query(self);
107 match self {
108 Self::Is(feature) => feature.accept(v),
109 Self::Not(_, feature) => feature.accept(v),
110 Self::And(features) => {
111 for (feature, _) in features {
112 feature.accept(v);
113 }
114 }
115 Self::Or(features) => {
116 for (feature, _) in features {
117 feature.accept(v);
118 }
119 }
120 }
121 }
122}
123
124impl<'a> VisitableMut for ContainerQuery<'a> {
125 fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
126 v.visit_container_query(self);
127 match self {
128 Self::Is(feature) => feature.accept_mut(v),
129 Self::Not(_, feature) => feature.accept_mut(v),
130 Self::And(features) => {
131 for (feature, _) in features {
132 feature.accept_mut(v);
133 }
134 }
135 Self::Or(features) => {
136 for (feature, _) in features {
137 feature.accept_mut(v);
138 }
139 }
140 }
141 }
142}
143
144macro_rules! container_feature {
145 ( $($name: ident($typ: ident): $str: tt,)+ ) => {
146 #[allow(clippy::large_enum_variant)] #[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
148 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
149 #[visit]
150 pub enum ContainerFeature<'a> {
151 $($name($typ),)+
152 Style(StyleQuery<'a>),
153 ScrollState(ScrollStateQuery<'a>),
154 }
155 }
156}
157
158apply_container_features!(container_feature);
159
160macro_rules! container_feature_keyword {
161 ( $($name: ident($typ: ident): $str: tt,)+) => {
162 keyword_set!(pub enum ContainerFeatureKeyword {
163 $($name: $str,)+
164 });
165 }
166}
167apply_container_features!(container_feature_keyword);
168
169impl<'a> Parse<'a> for ContainerFeature<'a> {
170 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
171 if p.peek::<T![Function]>() {
172 todo!();
173 }
174 let mut c = p.peek_n(2);
175 macro_rules! match_feature {
176 ( $($name: ident($typ: ident): $str: tt,)+) => {
177 {
179 if ContainerFeatureKeyword::peek(p, c) {
180 match ContainerFeatureKeyword::build(p, c) {
181 $(ContainerFeatureKeyword::$name(_) => {
182 let value = $typ::parse(p)?;
183 Self::$name(value)
184 },)+
185 }
186 } else {
187 let source_cursor = p.to_source_cursor(c);
188 Err(diagnostics::UnexpectedIdent(source_cursor.to_string(), c))?
189 }
190 }
191 }
192 }
193 if c == Kind::Ident {
194 Ok(apply_container_features!(match_feature))
195 } else {
196 c = p.peek_n(3);
198 if c != Kind::Ident {
199 c = p.peek_n(4)
200 }
201 Ok(apply_container_features!(match_feature))
202 }
203 }
204}
205
206macro_rules! apply_container_features {
207 ($macro: ident) => {
208 $macro! {
209 Width(WidthContainerFeature): "width",
211 Height(HeightContainerFeature): "height",
212 InlineSize(InlineSizeContainerFeature): "inline-size",
213 BlockSize(BlockSizeContainerFeature): "block-size",
214 AspectRatio(AspectRatioContainerFeature): "aspect-ratio",
215 Orientation(OrientationContainerFeature): "orientation",
216 }
217 };
218}
219use apply_container_features;
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use css_parse::assert_parse;
225
226 #[test]
227 fn size_test() {
228 assert_eq!(std::mem::size_of::<ContainerRule>(), 128);
229 assert_eq!(std::mem::size_of::<ContainerConditionList>(), 32);
230 assert_eq!(std::mem::size_of::<ContainerCondition>(), 416);
231 assert_eq!(std::mem::size_of::<ContainerQuery>(), 400);
232 }
233
234 #[test]
235 fn test_writes() {
236 assert_parse!(ContainerQuery, "(width:2px)");
237 assert_parse!(ContainerCondition, "(width:2px)");
238 assert_parse!(ContainerCondition, "(inline-size>30em)");
239 assert_parse!(ContainerCondition, "(1em<width<1em)");
240 assert_parse!(ContainerRule, "@container foo{}");
241 assert_parse!(ContainerRule, "@container foo (width:2px){}");
242 assert_parse!(ContainerRule, "@container foo (10em<width<10em){}");
243 assert_parse!(ContainerRule, "@container foo (width:2px){body{color:black}}");
244 }
245}