Skip to main content

css_parse/syntax/
rule_list.rs

1use crate::{
2	CursorSink, Kind, KindSet, NodeMetadata, NodeWithMetadata, Parse, Parser, Peek, Result, SemanticEq, T, ToCursors,
3	ToSpan, Vec, token_macros,
4};
5use csskit_proc_macro::node;
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#[node]
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[cfg_attr(
26	feature = "serde",
27	derive(serde::Serialize),
28	serde(bound(serialize = "R: serde::Serialize, M: serde::Serialize"))
29)]
30pub struct RuleList<'a, R, M>
31where
32	R: NodeWithMetadata<M>,
33	M: NodeMetadata,
34{
35	pub open_curly: token_macros::LeftCurly,
36	pub rules: Vec<'a, R>,
37	pub close_curly: Option<token_macros::RightCurly>,
38	#[cfg_attr(feature = "serde", serde(skip))]
39	pub meta: M,
40}
41
42impl<'a, R, M> Peek<'a> for RuleList<'a, R, M>
43where
44	R: NodeWithMetadata<M>,
45	M: NodeMetadata,
46{
47	const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::LeftCurly]);
48}
49
50impl<'a, R, M> Parse<'a> for RuleList<'a, R, M>
51where
52	R: Parse<'a> + NodeWithMetadata<M>,
53	M: NodeMetadata,
54{
55	fn parse<Iter>(p: &mut Parser<'a, Iter>) -> Result<Self>
56	where
57		Iter: Iterator<Item = crate::Cursor> + Clone,
58	{
59		let open_curly = p.parse::<T!['{']>()?;
60		let mut rules = Vec::new_in(p.alloc());
61		let mut meta = M::default();
62		loop {
63			p.parse_if_peek::<T![;]>().ok();
64			if p.at_end() {
65				return Ok(Self { open_curly, rules, close_curly: None, meta });
66			}
67			let close_curly = p.parse_if_peek::<T!['}']>()?;
68			if close_curly.is_some() {
69				return Ok(Self { open_curly, rules, close_curly, meta });
70			}
71			let rule = p.parse::<R>()?;
72			meta = meta.merge(rule.metadata());
73			rules.push(rule);
74		}
75	}
76}
77
78impl<'a, R, M> ToCursors for RuleList<'a, R, M>
79where
80	R: ToCursors + NodeWithMetadata<M>,
81	M: NodeMetadata,
82{
83	fn to_cursors(&self, s: &mut impl CursorSink) {
84		ToCursors::to_cursors(&self.open_curly, s);
85		ToCursors::to_cursors(&self.rules, s);
86		ToCursors::to_cursors(&self.close_curly, s);
87	}
88}
89
90impl<'a, R, M> ToSpan for RuleList<'a, R, M>
91where
92	R: ToSpan + NodeWithMetadata<M>,
93	M: NodeMetadata,
94{
95	fn to_span(&self) -> css_lexer::Span {
96		self.open_curly.to_span()
97			+ if let Some(close) = self.close_curly { close.to_span() } else { self.rules.to_span() }
98	}
99}
100
101impl<'a, R, M> NodeWithMetadata<M> for RuleList<'a, R, M>
102where
103	R: NodeWithMetadata<M>,
104	M: NodeMetadata,
105{
106	fn metadata(&self) -> M {
107		self.meta
108	}
109}
110
111impl<'a, R, M> SemanticEq for RuleList<'a, R, M>
112where
113	R: NodeWithMetadata<M> + SemanticEq,
114	M: NodeMetadata,
115{
116	fn semantic_eq(&self, other: &Self) -> bool {
117		self.open_curly.semantic_eq(&other.open_curly)
118			&& self.rules.semantic_eq(&other.rules)
119			&& self.close_curly.semantic_eq(&other.close_curly)
120	}
121}