1use crate::{CssAtomSet, CssMetadata, StyleValue, rules, stylerule::StyleRule};
2use bumpalo::collections::Vec;
3use css_parse::{
4 BumpBox, ComponentValues, Cursor, Diagnostic, NodeWithMetadata, Parse, Parser, QualifiedRule,
5 Result as ParserResult, RuleVariants, StyleSheet as StyleSheetTrait, T, UnknownRuleBlock,
6};
7use csskit_derives::{Parse, Peek, SemanticEq, ToCursors, ToSpan};
8
9#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
12#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
13pub struct StyleSheet<'a> {
14 pub rules: Vec<'a, Rule<'a>>,
15 #[to_cursors(skip)]
16 #[cfg_attr(feature = "serde", serde(skip))]
17 #[cfg_attr(feature = "visitable", visit(skip))]
18 meta: CssMetadata,
19}
20
21impl<'a> NodeWithMetadata<CssMetadata> for StyleSheet<'a> {
22 fn metadata(&self) -> CssMetadata {
23 self.meta
24 }
25}
26
27impl<'a> Parse<'a> for StyleSheet<'a> {
32 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
33 where
34 I: Iterator<Item = Cursor> + Clone,
35 {
36 let (rules, meta) = Self::parse_stylesheet(p)?;
37 Ok(Self { rules, meta })
38 }
39}
40
41impl<'a> StyleSheetTrait<'a, CssMetadata> for StyleSheet<'a> {
42 type Rule = Rule<'a>;
43}
44
45macro_rules! apply_rules {
46 ($macro: ident) => {
47 $macro! {
48 Charset(CharsetRule): CssAtomSet::Charset,
49 ColorProfile(ColorProfileRule): CssAtomSet::ColorProfile,
50 Container(ContainerRule<'a>): CssAtomSet::Container,
51 CounterStyle(CounterStyleRule<'a>): CssAtomSet::CounterStyle,
52 FontFace(FontFaceRule<'a>): CssAtomSet::FontFace,
53 FontFeatureValues(FontFeatureValuesRule): CssAtomSet::FontFeatureValues,
54 FontPaletteValues(FontPaletteValuesRule): CssAtomSet::FontPaletteValues,
55 Keyframes(KeyframesRule<'a>): CssAtomSet::Keyframes,
56 Layer(LayerRule<'a>): CssAtomSet::Layer,
57 Media(MediaRule<'a>): CssAtomSet::Media,
58 Namespace(NamespaceRule): CssAtomSet::Namespace,
59 Page(PageRule<'a>): CssAtomSet::Page,
60 Property(PropertyRule<'a>): CssAtomSet::Property,
61 Scope(ScopeRule): CssAtomSet::Scope,
62 StartingStyle(StartingStyleRule<'a>): CssAtomSet::StartingStyle,
63
64 Document(DocumentRule<'a>): CssAtomSet::Document,
66
67 WebkitKeyframes(WebkitKeyframesRule<'a>): CssAtomSet::_WebkitKeyframes,
69
70 MozDocument(MozDocumentRule<'a>): CssAtomSet::_MozDocument,
72 }
73 };
74}
75
76#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
78#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
79#[derive(csskit_derives::NodeWithMetadata)]
80#[metadata(node_kinds = Unknown)]
81pub struct UnknownAtRule<'a> {
82 name: T![AtKeyword],
83 prelude: ComponentValues<'a>,
84 block: ComponentValues<'a>,
85}
86
87#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
89#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
90#[derive(csskit_derives::NodeWithMetadata)]
91#[metadata(node_kinds = Unknown)]
92pub struct UnknownQualifiedRule<'a>(
93 #[metadata(delegate)]
94 QualifiedRule<
95 'a,
96 UnknownRuleBlock<'a>,
97 StyleValue<'a>,
98 UnknownRuleBlock<'a, StyleValue<'a>, CssMetadata>,
99 CssMetadata,
100 >,
101);
102
103macro_rules! rule {
104 ( $(
105 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
106 )+ ) => {
107 #[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
109 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
110 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
111 #[derive(csskit_derives::NodeWithMetadata)]
112 #[metadata(delegate)]
113 pub enum Rule<'a> {
114 $(
115 $name(rules::$ty$(<$a>)?),
116 )+
117 Import(BumpBox<'a, rules::ImportRule<'a>>),
119 Supports(BumpBox<'a, rules::SupportsRule<'a>>),
120
121 UnknownAt(UnknownAtRule<'a>),
122 Style(StyleRule<'a>),
123 Unknown(UnknownQualifiedRule<'a>)
124 }
125 }
126}
127
128apply_rules!(rule);
129
130impl<'a> RuleVariants<'a> for Rule<'a> {
131 type DeclarationValue = StyleValue<'a>;
132 type Metadata = CssMetadata;
133
134 fn parse_at_rule<I>(p: &mut Parser<'a, I>, c: Cursor) -> ParserResult<Self>
135 where
136 I: Iterator<Item = Cursor> + Clone,
137 {
138 macro_rules! parse_rule {
139 ( $(
140 $name: ident($ty: ident$(<$a: lifetime>)?): $atoms: pat,
141 )+ ) => {
142 match p.to_atom::<CssAtomSet>(c) {
143 $($atoms => p.parse::<rules::$ty>().map(Self::$name),)+
144 CssAtomSet::Import => p.parse::<rules::ImportRule>().map(|r| Self::Import(BumpBox::new_in(p.bump(), r))),
145 CssAtomSet::Supports => p.parse::<rules::SupportsRule>().map(|r| Self::Supports(BumpBox::new_in(p.bump(), r))),
146 _ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?,
147 }
148 }
149 }
150 apply_rules!(parse_rule)
151 }
152
153 fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
154 where
155 I: Iterator<Item = Cursor> + Clone,
156 {
157 p.parse::<UnknownAtRule>().map(Self::UnknownAt)
158 }
159
160 fn parse_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
161 where
162 I: Iterator<Item = Cursor> + Clone,
163 {
164 p.parse::<StyleRule>().map(Self::Style)
165 }
166
167 fn parse_unknown_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
168 where
169 I: Iterator<Item = Cursor> + Clone,
170 {
171 p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
172 }
173}
174
175impl<'a> Parse<'a> for Rule<'a> {
176 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
177 where
178 I: Iterator<Item = Cursor> + Clone,
179 {
180 Self::parse_rule_variants(p)
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::CssAtomSet;
188 use css_parse::assert_parse;
189
190 #[test]
191 fn size_test() {
192 assert_eq!(std::mem::size_of::<StyleSheet>(), 64);
193 assert_eq!(std::mem::size_of::<Rule>(), 208);
194 }
195
196 #[test]
197 fn test_writes() {
198 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{}");
199 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{color:red;}");
200 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body,tr:nth-child(n-1){}");
201 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}");
202 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}.a{width:2px;}");
203 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "one:1;a{two:2}");
204 assert_parse!(CssAtomSet::ATOMS, Rule, "@media screen{}", Rule::Media(_));
205 assert_parse!(CssAtomSet::ATOMS, Rule, "@layer foo{}", Rule::Layer(_));
206 }
207}