css_parse/syntax/
comma_separated.rs

1use crate::{
2	Cursor, CursorSink, KindSet, Parse, Parser, Peek, Result as ParserResult, Span, ToCursors, ToSpan,
3	token_macros::Comma,
4};
5use bumpalo::{
6	Bump,
7	collections::{Vec, vec::IntoIter},
8};
9use std::{
10	ops::{Index, IndexMut},
11	slice::{Iter, IterMut},
12};
13
14/// This is a generic type that can be used for AST nodes representing multiple multiple items separated with commas.
15///
16/// This can be used for any grammar which defines a Comma Separated group (`[]#`).
17///
18/// The given `<T>` will be parsed first, followed by a comma. Parsing completes if the comma isn't found.
19///
20/// As `<T>` is parsed first, it can have any number of interior commas, however if T should ideally not consume
21/// trailing commas, as doing so would likely mean only a single T in this struct.
22///
23/// The effective grammar for this struct is:
24///
25/// ```md
26/// <comma-separated>
27///  │├─╭─ <T> ─╮─ "," ─╭─┤│
28///     │       ╰───────╯
29///     ╰───────╯
30/// ```
31///
32/// [1]: https://drafts.csswg.org/css-syntax-3/#typedef-at-rule-list
33#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
35pub struct CommaSeparated<'a, T, const MIN: usize = 1> {
36	items: Vec<'a, (T, Option<Comma>)>,
37}
38
39impl<'a, T, const MIN: usize> CommaSeparated<'a, T, MIN> {
40	pub fn new_in(bump: &'a Bump) -> Self {
41		Self { items: Vec::new_in(bump) }
42	}
43
44	pub fn is_empty(&self) -> bool {
45		self.items.is_empty()
46	}
47
48	pub fn len(&self) -> usize {
49		self.items.len()
50	}
51}
52
53impl<'a, T: Peek<'a>, const MIN: usize> Peek<'a> for CommaSeparated<'a, T, MIN> {
54	const PEEK_KINDSET: KindSet = T::PEEK_KINDSET;
55	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
56		T::peek(p, c)
57	}
58}
59
60impl<'a, T: Parse<'a> + Peek<'a>, const MIN: usize> Parse<'a> for CommaSeparated<'a, T, MIN> {
61	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
62		let mut items = Self::new_in(p.bump());
63		if MIN == 0 && !p.peek::<T>() {
64			return Ok(items);
65		}
66		loop {
67			let item = p.parse::<T>()?;
68			let comma = p.parse_if_peek::<Comma>()?;
69			items.items.push((item, comma));
70			if comma.is_none() {
71				if MIN > items.len() {
72					p.parse::<Comma>()?;
73				}
74				return Ok(items);
75			}
76		}
77	}
78}
79
80impl<'a, T: ToCursors, const MIN: usize> ToCursors for CommaSeparated<'a, T, MIN> {
81	fn to_cursors(&self, s: &mut impl CursorSink) {
82		ToCursors::to_cursors(&self.items, s);
83	}
84}
85
86impl<'a, T: ToSpan, const MIN: usize> ToSpan for CommaSeparated<'a, T, MIN> {
87	fn to_span(&self) -> Span {
88		let first = self.items[0].to_span();
89		first + self.items.last().map(|t| t.to_span()).unwrap_or(first)
90	}
91}
92
93impl<'a, T, const MIN: usize> IntoIterator for CommaSeparated<'a, T, MIN> {
94	type Item = (T, Option<Comma>);
95	type IntoIter = IntoIter<'a, Self::Item>;
96
97	fn into_iter(self) -> Self::IntoIter {
98		self.items.into_iter()
99	}
100}
101
102impl<'a, 'b, T, const MIN: usize> IntoIterator for &'b CommaSeparated<'a, T, MIN> {
103	type Item = &'b (T, Option<Comma>);
104	type IntoIter = Iter<'b, (T, Option<Comma>)>;
105
106	fn into_iter(self) -> Self::IntoIter {
107		self.items.iter()
108	}
109}
110
111impl<'a, 'b, T, const MIN: usize> IntoIterator for &'b mut CommaSeparated<'a, T, MIN> {
112	type Item = &'b mut (T, Option<Comma>);
113	type IntoIter = IterMut<'b, (T, Option<Comma>)>;
114
115	fn into_iter(self) -> Self::IntoIter {
116		self.items.iter_mut()
117	}
118}
119
120impl<'a, T, I, const MIN: usize> Index<I> for CommaSeparated<'a, T, MIN>
121where
122	I: ::core::slice::SliceIndex<[(T, Option<Comma>)]>,
123{
124	type Output = I::Output;
125
126	#[inline]
127	fn index(&self, index: I) -> &Self::Output {
128		Index::index(&self.items, index)
129	}
130}
131
132impl<'a, T, I, const MIN: usize> IndexMut<I> for CommaSeparated<'a, T, MIN>
133where
134	I: ::core::slice::SliceIndex<[(T, Option<Comma>)]>,
135{
136	#[inline]
137	fn index_mut(&mut self, index: I) -> &mut Self::Output {
138		IndexMut::index_mut(&mut self.items, index)
139	}
140}
141
142#[cfg(test)]
143mod tests {
144	use super::*;
145	use crate::{T, test_helpers::*};
146
147	#[test]
148	fn size_test() {
149		assert_eq!(std::mem::size_of::<CommaSeparated<T![Ident]>>(), 32);
150	}
151
152	#[test]
153	fn test_writes() {
154		assert_parse!(CommaSeparated<T![Ident]>, "foo");
155		assert_parse!(CommaSeparated<T![Ident]>, "one,two");
156		assert_parse!(CommaSeparated<T![Ident]>, "one,two,three");
157		assert_parse!(CommaSeparated<T![Ident], 0>, "");
158	}
159
160	#[test]
161	fn test_spans() {
162		assert_parse_span!(
163			CommaSeparated<T![Ident]>,
164			r#"
165			foo bar
166			^^^
167		"#
168		);
169		assert_parse_span!(
170			CommaSeparated<T![Ident]>,
171			r#"
172			foo, bar, baz 1
173			^^^^^^^^^^^^^
174		"#
175		);
176	}
177
178	#[test]
179	fn test_errors() {
180		assert_parse_error!(CommaSeparated<T![Ident]>, "");
181		assert_parse_error!(CommaSeparated<T![Ident]>, ",");
182		assert_parse_error!(CommaSeparated<T![Ident]>, "one,two,three,");
183		assert_parse_error!(CommaSeparated<T![Ident]>, "one two");
184		assert_parse_error!(CommaSeparated<T![Ident], 2>, "one");
185		assert_parse_error!(CommaSeparated<T![Ident], 3>, "one, two");
186	}
187}