1use crate::{CssAtomSet, CssMetadata, DeclarationKind, DeclarationMetadata, values};
2use css_lexer::Kind;
3use css_parse::{
4 ComponentValues, Cursor, Declaration, DeclarationValue, Diagnostic, KindSet, NodeWithMetadata, Parser, Peek,
5 Result as ParserResult, SemanticEq as SemanticEqTrait, State, T,
6};
7use csskit_derives::{Parse, SemanticEq, ToCursors, ToSpan};
8use std::{fmt::Debug, hash::Hash};
9
10include!(concat!(env!("OUT_DIR"), "/css_apply_properties.rs"));
12
13#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
15#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
16#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
17pub struct Custom<'a>(pub ComponentValues<'a>);
18
19#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
21#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
22#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
23pub struct Computed<'a>(pub ComponentValues<'a>);
24
25impl<'a> Peek<'a> for Computed<'a> {
26 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
27 where
28 I: Iterator<Item = Cursor> + Clone,
29 {
30 <T![Function]>::peek(p, c)
31 && matches!(
32 p.to_atom::<CssAtomSet>(c),
33 CssAtomSet::Var
34 | CssAtomSet::Calc
35 | CssAtomSet::Min
36 | CssAtomSet::Max
37 | CssAtomSet::Clamp
38 | CssAtomSet::Round
39 | CssAtomSet::Mod
40 | CssAtomSet::Rem
41 | CssAtomSet::Sin
42 | CssAtomSet::Cos
43 | CssAtomSet::Tan
44 | CssAtomSet::Asin
45 | CssAtomSet::Atan
46 | CssAtomSet::Atan2
47 | CssAtomSet::Pow
48 | CssAtomSet::Sqrt
49 | CssAtomSet::Hypot
50 | CssAtomSet::Log
51 | CssAtomSet::Exp
52 | CssAtomSet::Abs
53 | CssAtomSet::Sign
54 )
55 }
56}
57
58#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
60#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
61#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
62pub struct Unknown<'a>(pub ComponentValues<'a>);
63
64macro_rules! style_value {
65 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
66 #[derive(ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
67 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
68 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
69 pub enum StyleValue<'a> {
70 #[cfg_attr(feature = "visitable", visit(skip))]
71 Initial(T![Ident]),
72 #[cfg_attr(feature = "visitable", visit(skip))]
73 Inherit(T![Ident]),
74 #[cfg_attr(feature = "visitable", visit(skip))]
75 Unset(T![Ident]),
76 #[cfg_attr(feature = "visitable", visit(skip))]
77 Revert(T![Ident]),
78 #[cfg_attr(feature = "visitable", visit(skip))]
79 RevertLayer(T![Ident]),
80 #[cfg_attr(feature = "serde", serde(untagged))]
81 Custom(Custom<'a>),
82 #[cfg_attr(feature = "serde", serde(untagged))]
83 Computed(Computed<'a>),
84 #[cfg_attr(feature = "serde", serde(untagged))]
85 Unknown(Unknown<'a>),
86 $(
87 #[cfg_attr(feature = "serde", serde(untagged))]
88 $name(values::$ty$(<$a>)?),
89 )+
90 }
91 }
92}
93
94apply_properties!(style_value);
95
96impl<'a> NodeWithMetadata<CssMetadata> for StyleValue<'a> {
97 fn metadata(&self) -> CssMetadata {
98 macro_rules! metadata {
99 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
100 match self {
101 Self::Initial(_) |
102 Self::Inherit(_)|
103 Self::Unset(_)|
104 Self::Revert(_)|
105 Self::RevertLayer(_) => {
106 CssMetadata {
107 declaration_kinds: DeclarationKind::CssWideKeywords,
108 ..Default::default()
109 }
110 }
111 Self::Custom(_) => {
112 CssMetadata {
113 declaration_kinds: DeclarationKind::Custom,
114 ..Default::default()
115 }
116 }
117 Self::Computed(_) => {
118 CssMetadata {
119 declaration_kinds: DeclarationKind::Computed,
120 ..Default::default()
121 }
122 },
123 Self::Unknown(_) => {
124 CssMetadata {
125 declaration_kinds: DeclarationKind::Unknown,
126 ..Default::default()
127 }
128 },
129 $(
130 Self::$name(_) => {
131 CssMetadata {
132 property_groups: values::$ty::property_group(),
133 applies_to: values::$ty::applies_to(),
134 box_sides: values::$ty::box_side(),
135 box_portions: values::$ty::box_portion(),
136 ..Default::default()
137 }
138 }
139 )+
140 }
141 };
142 }
143 apply_properties!(metadata)
144 }
145}
146
147impl<'a> DeclarationValue<'a, CssMetadata> for StyleValue<'a> {
148 type ComputedValue = Computed<'a>;
149
150 fn declaration_metadata(decl: &Declaration<'a, Self, CssMetadata>) -> CssMetadata {
151 let mut meta = decl.value.metadata();
152 if decl.important.is_some() {
153 meta.declaration_kinds |= DeclarationKind::Important;
154 }
155 meta
156 }
157
158 fn valid_declaration_name<I>(p: &Parser<'a, I>, c: Cursor) -> bool
159 where
160 I: Iterator<Item = Cursor> + Clone,
161 {
162 macro_rules! match_name {
163 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
164 match p.to_atom::<CssAtomSet>(c) {
165 $(CssAtomSet::$name => true,)+
166 _ => false,
167 }
168 }
169 }
170 apply_properties!(match_name)
171 }
172
173 fn is_unknown(&self) -> bool {
174 matches!(self, Self::Unknown(_))
175 }
176
177 fn is_initial(&self) -> bool {
178 matches!(self, Self::Initial(_))
179 }
180
181 fn is_inherit(&self) -> bool {
182 matches!(self, Self::Inherit(_))
183 }
184
185 fn is_unset(&self) -> bool {
186 matches!(self, Self::Unset(_))
187 }
188
189 fn is_revert(&self) -> bool {
190 matches!(self, Self::Revert(_))
191 }
192
193 fn is_revert_layer(&self) -> bool {
194 matches!(self, Self::RevertLayer(_))
195 }
196
197 fn needs_computing(&self) -> bool {
198 matches!(self, Self::Computed(_))
199 }
200
201 fn parse_custom_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
202 where
203 I: Iterator<Item = Cursor> + Clone,
204 {
205 p.parse::<Custom>().map(Self::Custom)
206 }
207
208 fn parse_computed_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
209 where
210 I: Iterator<Item = Cursor> + Clone,
211 {
212 p.parse::<Computed>().map(Self::Computed)
213 }
214
215 fn parse_specified_declaration_value<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
216 where
217 I: Iterator<Item = Cursor> + Clone,
218 {
219 let c = p.peek_n(1);
220 if c == Kind::Ident {
221 match p.to_atom::<CssAtomSet>(c) {
222 CssAtomSet::Initial => return Ok(Self::Initial(p.parse::<T![Ident]>()?)),
223 CssAtomSet::Inherit => return Ok(Self::Inherit(p.parse::<T![Ident]>()?)),
224 CssAtomSet::Unset => return Ok(Self::Unset(p.parse::<T![Ident]>()?)),
225 CssAtomSet::Revert => return Ok(Self::Revert(p.parse::<T![Ident]>()?)),
226 CssAtomSet::RevertLayer => return Ok(Self::RevertLayer(p.parse::<T![Ident]>()?)),
227 _ => {}
228 }
229 }
230 macro_rules! parse_declaration_value {
231 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $atom: ident,)+ ) => {
232 match p.to_atom::<CssAtomSet>(name) {
233 $(CssAtomSet::$atom => p.parse::<values::$ty>().map(Self::$name),)+
234 _ => Err(Diagnostic::new(name, Diagnostic::unexpected))?,
235 }
236 }
237 }
238 apply_properties!(parse_declaration_value)
239 }
240
241 fn parse_unknown_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
242 where
243 I: Iterator<Item = Cursor> + Clone,
244 {
245 p.parse::<Unknown>().map(Self::Unknown)
246 }
247}
248
249impl<'a> SemanticEqTrait for crate::StyleValue<'a> {
250 fn semantic_eq(&self, other: &Self) -> bool {
251 macro_rules! semantic_eq {
252 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
253 match (self, other) {
254 (Self::Initial(_), Self::Initial(_)) => true,
255 (Self::Inherit(_), Self::Inherit(_)) => true,
256 (Self::Unset(_), Self::Unset(_)) => true,
257 (Self::Revert(_), Self::Revert(_)) => true,
258 (Self::RevertLayer(_), Self::RevertLayer(_)) => true,
259 (Self::Custom(a), Self::Custom(b)) => a.semantic_eq(b),
260 (Self::Computed(a), Self::Computed(b)) => a.semantic_eq(b),
261 (Self::Unknown(a), Self::Unknown(b)) => a.semantic_eq(b),
262 $((Self::$name(a), Self::$name(b)) => a.semantic_eq(b),)+
263 (_, _) => false,
264 }
265 };
266 }
267 apply_properties!(semantic_eq)
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274 use crate::{CssAtomSet, CssMetadata};
275 use css_parse::{Declaration, assert_parse};
276
277 type Property<'a> = Declaration<'a, StyleValue<'a>, CssMetadata>;
278
279 #[test]
280 fn size_test() {
281 assert_eq!(std::mem::size_of::<Property>(), 368);
282 assert_eq!(std::mem::size_of::<StyleValue>(), 296);
283 }
284
285 #[test]
286 fn test_writes() {
287 assert_parse!(CssAtomSet::ATOMS, Property, "width:inherit", Property { value: StyleValue::Inherit(_), .. });
288 assert_parse!(
289 CssAtomSet::ATOMS,
290 Property,
291 "width:inherit!important",
292 Property { value: StyleValue::Inherit(_), important: Some(_), .. }
293 );
294 assert_parse!(
295 CssAtomSet::ATOMS,
296 Property,
297 "width:revert;",
298 Property { value: StyleValue::Revert(_), semicolon: Some(_), .. }
299 );
300 assert_parse!(CssAtomSet::ATOMS, Property, "width:var(--a)", Property { value: StyleValue::Computed(_), .. });
301
302 assert_parse!(CssAtomSet::ATOMS, Property, "float:none!important");
303 assert_parse!(CssAtomSet::ATOMS, Property, "width:1px");
304 assert_parse!(CssAtomSet::ATOMS, Property, "width:min(1px, 2px)");
305 assert_parse!(CssAtomSet::ATOMS, Property, "border:1px solid var(--red)");
306 assert_parse!(CssAtomSet::ATOMS, Property, "dunno:like whatever");
308 assert_parse!(CssAtomSet::ATOMS, Property, "rotate:1.21gw");
309 assert_parse!(CssAtomSet::ATOMS, Property, "_background:black");
310 assert_parse!(CssAtomSet::ATOMS, Property, "--custom:{foo:{bar};baz:(bing);}");
311 }
312}