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