1use crate::{
2 Arena, Cursor, CursorSink, IntoIter, KindSet, Parse, Parser, Peek, Result as ParserResult, SemanticEq, Span,
3 ToCursors, ToSpan, Vec, token_macros::Comma,
4};
5use csskit_proc_macro::node;
6use std::{
7 ops::{Index, IndexMut},
8 slice::{Iter, IterMut},
9};
10
11#[node]
31#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
33pub struct CommaSeparated<'a, T, const MIN: usize = 1> {
34 items: Vec<'a, (T, Option<Comma>)>,
35}
36
37impl<'a, T, const MIN: usize> CommaSeparated<'a, T, MIN> {
38 pub fn new_in(alloc: &'a Arena) -> Self {
39 Self { items: Vec::new_in(alloc) }
40 }
41
42 pub fn is_empty(&self) -> bool {
43 self.items.is_empty()
44 }
45
46 pub fn len(&self) -> usize {
47 self.items.len()
48 }
49}
50
51impl<'a, T: Peek<'a>, const MIN: usize> Peek<'a> for CommaSeparated<'a, T, MIN> {
52 const PEEK_KINDSET: KindSet = T::PEEK_KINDSET;
53
54 #[inline(always)]
55 fn peek<Iter>(p: &Parser<'a, Iter>, c: Cursor) -> bool
56 where
57 Iter: Iterator<Item = crate::Cursor> + Clone,
58 {
59 T::peek(p, c)
60 }
61}
62
63impl<'a, T: Parse<'a> + Peek<'a>, const MIN: usize> Parse<'a> for CommaSeparated<'a, T, MIN> {
64 fn parse<Iter>(p: &mut Parser<'a, Iter>) -> ParserResult<Self>
65 where
66 Iter: Iterator<Item = crate::Cursor> + Clone,
67 {
68 let mut items = Self::new_in(p.alloc());
69 if MIN == 0 && !<T>::peek(p, p.peek_n(1)) {
70 return Ok(items);
71 }
72 loop {
73 let item = p.parse::<T>()?;
74 if p.peek::<Comma>() {
75 let checkpoint = p.checkpoint();
76 let comma = p.parse::<Comma>()?;
77 if !<T>::peek(p, p.peek_n(1)) {
78 p.rewind(checkpoint);
79 items.items.push((item, None));
80 break;
81 }
82 items.items.push((item, Some(comma)));
83 } else {
84 items.items.push((item, None));
85 break;
86 }
87 }
88 if MIN > items.len() {
89 p.parse::<Comma>()?;
90 }
91 Ok(items)
92 }
93}
94
95impl<'a, T: ToCursors, const MIN: usize> ToCursors for CommaSeparated<'a, T, MIN> {
96 fn to_cursors(&self, s: &mut impl CursorSink) {
97 ToCursors::to_cursors(&self.items, s);
98 }
99}
100
101impl<'a, T: ToSpan, const MIN: usize> ToSpan for CommaSeparated<'a, T, MIN> {
102 fn to_span(&self) -> Span {
103 let first = self.items[0].to_span();
104 first + self.items.last().map(|t| t.to_span()).unwrap_or(first)
105 }
106}
107
108impl<'a, T: SemanticEq, const MIN: usize> SemanticEq for CommaSeparated<'a, T, MIN> {
109 fn semantic_eq(&self, other: &Self) -> bool {
110 self.items.semantic_eq(&other.items)
111 }
112}
113
114impl<'a, T: 'a, const MIN: usize> IntoIterator for CommaSeparated<'a, T, MIN> {
115 type Item = (T, Option<Comma>);
116 type IntoIter = IntoIter<'a, Self::Item>;
117
118 fn into_iter(self) -> Self::IntoIter {
119 self.items.into_iter()
120 }
121}
122
123impl<'a, 'b, T, const MIN: usize> IntoIterator for &'b CommaSeparated<'a, T, MIN> {
124 type Item = &'b (T, Option<Comma>);
125 type IntoIter = Iter<'b, (T, Option<Comma>)>;
126
127 fn into_iter(self) -> Self::IntoIter {
128 self.items.iter()
129 }
130}
131
132impl<'a, 'b, T, const MIN: usize> IntoIterator for &'b mut CommaSeparated<'a, T, MIN> {
133 type Item = &'b mut (T, Option<Comma>);
134 type IntoIter = IterMut<'b, (T, Option<Comma>)>;
135
136 fn into_iter(self) -> Self::IntoIter {
137 self.items.iter_mut()
138 }
139}
140
141impl<'a, T, I, const MIN: usize> Index<I> for CommaSeparated<'a, T, MIN>
142where
143 I: ::core::slice::SliceIndex<[(T, Option<Comma>)]>,
144{
145 type Output = I::Output;
146
147 #[inline]
148 fn index(&self, index: I) -> &Self::Output {
149 Index::index(&self.items, index)
150 }
151}
152
153impl<'a, T, I, const MIN: usize> IndexMut<I> for CommaSeparated<'a, T, MIN>
154where
155 I: ::core::slice::SliceIndex<[(T, Option<Comma>)]>,
156{
157 #[inline]
158 fn index_mut(&mut self, index: I) -> &mut Self::Output {
159 IndexMut::index_mut(&mut self.items, index)
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::{EmptyAtomSet, T, test_helpers::*};
167
168 #[test]
169 fn test_writes() {
170 assert_parse!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident]>, "foo");
171 assert_parse!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident]>, "one,two");
172 assert_parse!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident]>, "one,two,three");
173 assert_parse!(EmptyAtomSet::ATOMS, CommaSeparated<(T![Number], CommaSeparated<T![Ident]>)>, "1 foo, 2 bar");
174 }
175
176 #[test]
177 fn test_spans() {
178 assert_parse_span!(
179 EmptyAtomSet::ATOMS,
180 CommaSeparated<T![Ident]>,
181 r#"
182 foo bar
183 ^^^
184 "#
185 );
186 assert_parse_span!(
187 EmptyAtomSet::ATOMS,
188 CommaSeparated<T![Ident]>,
189 r#"
190 foo, bar, baz 1
191 ^^^^^^^^^^^^^
192 "#
193 );
194 }
195
196 #[test]
197 fn test_peek() {
198 assert_peek_false!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident]>, "");
199 assert_peek_false!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident]>, ",");
200 }
201
202 #[test]
203 fn test_errors() {
204 assert_parse_error!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident]>, "one,two,three,");
205 assert_parse_error!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident]>, "one two");
206 assert_parse_error!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident], 2>, "one");
207 assert_parse_error!(EmptyAtomSet::ATOMS, CommaSeparated<T![Ident], 3>, "one, two");
208 }
209}