1use bumpalo::collections::Vec;
2use css_parse::{
3 AtRule, Build, ComponentValues, Cursor, Parse, Parser, Peek, QualifiedRule, Result as ParserResult, RuleVariants,
4 StyleSheet as StyleSheetTrait, T, atkeyword_set, diagnostics,
5};
6use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
7
8use crate::{StyleValue, rules, stylerule::StyleRule};
9
10#[derive(ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(tag = "type", rename = "stylesheet"))]
13#[visit]
14pub struct StyleSheet<'a> {
15 pub rules: Vec<'a, Rule<'a>>,
16}
17
18impl<'a> Parse<'a> for StyleSheet<'a> {
23 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
24 Ok(Self { rules: Self::parse_stylesheet(p)? })
25 }
26}
27
28impl<'a> StyleSheetTrait<'a> for StyleSheet<'a> {
29 type Rule = Rule<'a>;
30}
31
32macro_rules! apply_rules {
33 ($macro: ident) => {
34 $macro! {
35 Charset(CharsetRule): "charset",
36 ColorProfile(ColorProfileRule): "color-profile",
37 Container(ContainerRule<'a>): "container",
38 CounterStyle(CounterStyleRule): "counter-style",
39 FontFace(FontFaceRule<'a>): "font-face",
40 FontFeatureValues(FontFeatureValuesRule): "font-feature-values",
41 FontPaletteValues(FontPaletteValuesRule): "font-palette-values",
42 Import(ImportRule): "import",
43 Keyframes(KeyframesRule<'a>): "keyframes",
44 Layer(LayerRule<'a>): "layer",
45 Media(MediaRule<'a>): "media",
46 Namespace(NamespaceRule): "namespace",
47 Page(PageRule<'a>): "page",
48 Property(PropertyRule<'a>): "property",
49 Scope(ScopeRule): "scope",
50 StartingStyle(StartingStyleRule): "starting-style",
51 Supports(SupportsRule<'a>): "supports",
52
53 Document(DocumentRule<'a>): "document",
55
56 WebkitKeyframes(WebkitKeyframesRule<'a>): "-webkit-keyframes",
58
59 MozDocument(MozDocumentRule<'a>): "-moz-document",
61 }
62 };
63}
64
65#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
67#[visit(self)]
68pub struct UnknownAtRule<'a>(AtRule<T![AtKeyword], ComponentValues<'a>, ComponentValues<'a>>);
69
70#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
72#[visit(self)]
73pub struct UnknownQualifiedRule<'a>(QualifiedRule<'a, ComponentValues<'a>, StyleValue<'a>, ComponentValues<'a>>);
74
75macro_rules! rule {
76 ( $(
77 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
78 )+ ) => {
79 #[allow(clippy::large_enum_variant)] #[derive(ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
82 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
83 pub enum Rule<'a> {
84 $(
85 $name(rules::$ty$(<$a>)?),
86 )+
87 UnknownAt(UnknownAtRule<'a>),
88 Style(StyleRule<'a>),
89 Unknown(UnknownQualifiedRule<'a>)
90 }
91 }
92}
93
94apply_rules!(rule);
95
96macro_rules! define_atkeyword_set {
97 ( $(
98 $name:ident($ty:ty): $str:tt,
99 )+ ) => {
100 atkeyword_set!(
101 enum AtRuleKeywords {
102 $($name: $str),+
103 }
104 );
105 }
106}
107
108apply_rules!(define_atkeyword_set);
109
110impl<'a> RuleVariants<'a> for Rule<'a> {
111 fn parse_at_rule(p: &mut Parser<'a>, c: Cursor) -> ParserResult<Self> {
112 if !AtRuleKeywords::peek(p, c) {
113 Err(diagnostics::Unexpected(p.next()))?;
114 }
115 let kw = AtRuleKeywords::build(p, c);
116 macro_rules! parse_rule {
117 ( $(
118 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
119 )+ ) => {
120 match kw {
121 $(AtRuleKeywords::$name(_) => p.parse::<rules::$ty>().map(Self::$name),)+
122 }
123 }
124 }
125 apply_rules!(parse_rule)
126 }
127
128 fn parse_unknown_at_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
129 p.parse::<UnknownAtRule>().map(Self::UnknownAt)
130 }
131
132 fn parse_qualified_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
133 p.parse::<StyleRule>().map(Self::Style)
134 }
135
136 fn parse_unknown_qualified_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
137 p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
138 }
139}
140
141impl<'a> Parse<'a> for Rule<'a> {
142 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
143 Self::parse_rule_variants(p)
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use css_parse::assert_parse;
151
152 #[test]
153 fn size_test() {
154 assert_eq!(std::mem::size_of::<StyleSheet>(), 32);
155 assert_eq!(std::mem::size_of::<Rule>(), 512);
156 }
157
158 #[test]
159 fn test_writes() {
160 assert_parse!(StyleSheet, "body{}");
161 assert_parse!(StyleSheet, "body{color:red;}");
162 assert_parse!(StyleSheet, "body,tr:nth-child(n-1){}");
163 assert_parse!(StyleSheet, "body{width:1px;}");
164 assert_parse!(StyleSheet, "body{width:1px;}.a{width:2px;}");
165 assert_parse!(StyleSheet, "one:1;a{two:2}");
166 assert_parse!(Rule, "@media screen{}", Rule::Media(_));
167 assert_parse!(Rule, "@layer foo{}", Rule::Layer(_));
168 }
169}