css_parse/syntax/
rule_list.rs

1use crate::{
2	Cursor, CursorSink, NodeMetadata, NodeWithMetadata, Parse, Parser, Peek, Result, SemanticEq, T, ToCursors, ToSpan,
3	token_macros,
4};
5use bumpalo::collections::Vec;
6
7/// A struct representing an AST node block that only accepts child "Rules". This is defined as:
8///
9/// ```md
10/// <rule-list>
11///  │├─ "{" ─╭─ <R> ─╮─╮─ "}" ─╭──┤│
12///           ╰───────╯ ╰───────╯
13/// ```
14///
15/// This is an implementation of [`<at-rule-list>`][1] or [`<qualified-rule-list>`][2].
16///
17/// It simply parses the open `{` and iterates collecing `<R>`s until the closing `}`.
18///
19/// Every item in the list must implement the [Parse], [ToCursors] and [ToSpan] traits.
20///
21/// [1]: https://drafts.csswg.org/css-syntax-3/#typedef-at-rule-list
22/// [2]: https://drafts.csswg.org/css-syntax-3/#typedef-qualified-rule-list
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[cfg_attr(
25	feature = "serde",
26	derive(serde::Serialize),
27	serde(bound(serialize = "R: serde::Serialize, M: serde::Serialize"))
28)]
29pub struct RuleList<'a, R, M>
30where
31	R: NodeWithMetadata<M>,
32	M: NodeMetadata,
33{
34	pub open_curly: token_macros::LeftCurly,
35	pub rules: Vec<'a, R>,
36	pub close_curly: Option<token_macros::RightCurly>,
37	#[cfg_attr(feature = "serde", serde(skip))]
38	pub meta: M,
39}
40
41impl<'a, R, M> Peek<'a> for RuleList<'a, R, M>
42where
43	R: NodeWithMetadata<M>,
44	M: NodeMetadata,
45{
46	fn peek<Iter>(p: &Parser<'a, Iter>, c: Cursor) -> bool
47	where
48		Iter: Iterator<Item = crate::Cursor> + Clone,
49	{
50		<token_macros::LeftCurly>::peek(p, c)
51	}
52}
53
54impl<'a, R, M> Parse<'a> for RuleList<'a, R, M>
55where
56	R: Parse<'a> + NodeWithMetadata<M>,
57	M: NodeMetadata,
58{
59	fn parse<Iter>(p: &mut Parser<'a, Iter>) -> Result<Self>
60	where
61		Iter: Iterator<Item = crate::Cursor> + Clone,
62	{
63		let open_curly = p.parse::<T!['{']>()?;
64		let mut rules = Vec::new_in(p.bump());
65		let mut meta = M::default();
66		loop {
67			p.parse_if_peek::<T![;]>().ok();
68			if p.at_end() {
69				return Ok(Self { open_curly, rules, close_curly: None, meta });
70			}
71			let close_curly = p.parse_if_peek::<T!['}']>()?;
72			if close_curly.is_some() {
73				return Ok(Self { open_curly, rules, close_curly, meta });
74			}
75			let rule = p.parse::<R>()?;
76			meta.merge(&rule.metadata());
77			rules.push(rule);
78		}
79	}
80}
81
82impl<'a, R, M> ToCursors for RuleList<'a, R, M>
83where
84	R: ToCursors + NodeWithMetadata<M>,
85	M: NodeMetadata,
86{
87	fn to_cursors(&self, s: &mut impl CursorSink) {
88		ToCursors::to_cursors(&self.open_curly, s);
89		ToCursors::to_cursors(&self.rules, s);
90		ToCursors::to_cursors(&self.close_curly, s);
91	}
92}
93
94impl<'a, R, M> ToSpan for RuleList<'a, R, M>
95where
96	R: ToSpan + NodeWithMetadata<M>,
97	M: NodeMetadata,
98{
99	fn to_span(&self) -> css_lexer::Span {
100		self.open_curly.to_span()
101			+ if let Some(close) = self.close_curly { close.to_span() } else { self.rules.to_span() }
102	}
103}
104
105impl<'a, R, M> NodeWithMetadata<M> for RuleList<'a, R, M>
106where
107	R: NodeWithMetadata<M>,
108	M: NodeMetadata,
109{
110	fn metadata(&self) -> M {
111		self.meta
112	}
113}
114
115impl<'a, R, M> SemanticEq for RuleList<'a, R, M>
116where
117	R: NodeWithMetadata<M> + SemanticEq,
118	M: NodeMetadata,
119{
120	fn semantic_eq(&self, other: &Self) -> bool {
121		self.open_curly.semantic_eq(&other.open_curly)
122			&& self.rules.semantic_eq(&other.rules)
123			&& self.close_curly.semantic_eq(&other.close_curly)
124	}
125}