Skip to main content

m2_syn/
punct.rs

1//! Comma-punctuated syntax sequences and their iterator views.
2
3use std::{option, slice, vec};
4
5use crate::cst::Token;
6use crate::{Parse, ParseStream, Span, Spanned, TextRange, ToTokens, TokenParseError, TokenStream};
7
8type Comma = Token![,];
9type Semicolon = Token![;];
10
11/// A zero-width component occupying an omitted position beside a comma.
12///
13/// Empty components evaluate as null but emit no token text. Their spans mark
14/// the source position at which the omitted component occurs.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Spanned)]
16pub struct Empty {
17    span: Span,
18}
19
20impl Empty {
21    /// Creates an empty component at `span`.
22    pub fn new(span: Span) -> Self {
23        Self { span }
24    }
25}
26
27impl Parse for Empty {
28    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
29        let span = input
30            .peek()
31            .map(|token| empty_before(token.span()))
32            .unwrap_or_else(|| input.eof_span());
33        Ok(Self::new(span))
34    }
35}
36
37impl ToTokens for Empty {
38    fn to_tokens(&self, _output: &mut TokenStream) {}
39}
40
41/// One value together with its following punctuation, or the unpunctuated end.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Spanned)]
43pub enum Pair<S, P = Comma> {
44    Punctuated(S, P),
45    End(S),
46}
47
48impl<S, P> Pair<S, P> {
49    /// Returns the value independently of whether it has following punctuation.
50    pub fn value(&self) -> &S {
51        match self {
52            Self::Punctuated(value, _) | Self::End(value) => value,
53        }
54    }
55
56    /// Separates the value from its optional following punctuation.
57    pub fn into_tuple(self) -> (S, Option<P>) {
58        match self {
59            Self::Punctuated(value, punctuation) => (value, Some(punctuation)),
60            Self::End(value) => (value, None),
61        }
62    }
63}
64
65impl<S, P> Parse for Pair<S, P>
66where
67    S: Parse,
68    P: Parse,
69{
70    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
71        let value = S::parse(input)?;
72        let mut fork = input.fork();
73        match P::parse(&mut fork) {
74            Ok(punctuation) => {
75                input.advance_to(&fork);
76                Ok(Self::Punctuated(value, punctuation))
77            }
78            Err(_) => Ok(Self::End(value)),
79        }
80    }
81}
82
83impl<S: ToTokens, P: ToTokens> ToTokens for Pair<S, P> {
84    fn to_tokens(&self, output: &mut TokenStream) {
85        match self {
86            Self::Punctuated(value, punctuation) => {
87                value.to_tokens(output);
88                punctuation.to_tokens(output);
89            }
90            Self::End(value) => value.to_tokens(output),
91        }
92    }
93}
94
95/// Names or constructs a comma-punctuated sequence.
96///
97/// - `punct!(T)` names `Punctuated<T>`.
98/// - `punct!()` constructs an empty sequence.
99/// - `punct!(value first)` constructs one value.
100/// - `punct!(pairs first; comma => second, ...)` constructs a sequence while
101///   retaining every supplied typed comma and its span.
102#[macro_export]
103macro_rules! punct {
104    () => {
105        $crate::Punctuated::new()
106    };
107    (value $value:expr) => {
108        $crate::Punctuated::from_value($value)
109    };
110    (pairs $first:expr; $($punctuation:expr => $next:expr),+ $(,)?) => {{
111        let mut values = $crate::Punctuated::from_value($first);
112        $(values.push($punctuation, $next);)+
113        values
114    }};
115    ($value:ty) => {
116        $crate::Punctuated<$value>
117    };
118}
119
120/// A sequence of syntax values separated by typed comma tokens.
121///
122/// Ordinary iteration exposes only values. Use [`Punctuated::pairs`] or
123/// [`Punctuated::into_pairs`] when punctuation and its spans are significant.
124///
125/// ```
126/// use m2_syn::{Span, ToTokens};
127///
128/// type Pluses = m2_syn::punct!(m2_syn::Token![+]);
129/// let comma = m2_syn::Token![,](Span::detached());
130/// let values: Pluses = m2_syn::punct!(
131///     pairs m2_syn::Token![+](Span::detached());
132///     comma => m2_syn::Token![+](Span::detached()),
133/// );
134/// assert_eq!(values.to_m2(), "+,+");
135/// ```
136#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
137pub struct Punctuated<S> {
138    inner: Vec<(S, Comma)>,
139    last: Option<Box<S>>,
140}
141
142impl<S> Punctuated<S> {
143    /// Creates an empty sequence.
144    pub const fn new() -> Self {
145        Self {
146            inner: Vec::new(),
147            last: None,
148        }
149    }
150
151    /// Returns the number of values, excluding punctuation.
152    pub fn len(&self) -> usize {
153        self.inner.len() + usize::from(self.last.is_some())
154    }
155
156    /// Reports whether the sequence contains no values or punctuation.
157    pub fn is_empty(&self) -> bool {
158        self.inner.is_empty() && self.last.is_none()
159    }
160
161    /// Creates a sequence containing one value.
162    pub fn from_value(value: S) -> Self {
163        Self {
164            inner: Vec::new(),
165            last: Some(Box::new(value)),
166        }
167    }
168
169    /// Borrows the values without exposing punctuation.
170    pub fn iter(&self) -> Iter<'_, S> {
171        Iter {
172            inner: self.pairs(),
173        }
174    }
175
176    /// Mutably borrows the values without exposing punctuation.
177    pub fn iter_mut(&mut self) -> IterMut<'_, S> {
178        IterMut {
179            inner: self.pairs_mut(),
180        }
181    }
182
183    /// Borrows values together with their optional following commas.
184    pub fn pairs(&self) -> Pairs<'_, S> {
185        Pairs {
186            inner: self.inner.iter(),
187            last: self.last.as_deref().into_iter(),
188        }
189    }
190
191    /// Mutably borrows values together with their optional following commas.
192    pub fn pairs_mut(&mut self) -> PairsMut<'_, S> {
193        PairsMut {
194            inner: self.inner.iter_mut(),
195            last: self.last.as_deref_mut().into_iter(),
196        }
197    }
198
199    /// Consumes the sequence while preserving commas in the yielded pairs.
200    pub fn into_pairs(self) -> IntoPairs<S> {
201        IntoPairs {
202            inner: self.inner.into_iter(),
203            last: self.last.into_iter(),
204        }
205    }
206
207    /// Adds a comma and the component on its right.
208    ///
209    /// Panics if the sequence is empty. Empty components must be represented
210    /// explicitly by converting [`Empty`] into `S` before calling this method.
211    pub fn push(&mut self, punctuation: Comma, next: S) {
212        let previous = self
213            .last
214            .take()
215            .expect("cannot punctuate an empty sequence");
216        self.inner.push((*previous, punctuation));
217        self.last = Some(Box::new(next));
218    }
219
220    /// Transforms the values while retaining every comma token and its span.
221    pub fn map<T>(self, mut map: impl FnMut(S) -> T) -> Punctuated<T> {
222        Punctuated {
223            inner: self
224                .inner
225                .into_iter()
226                .map(|(value, comma)| (map(value), comma))
227                .collect(),
228            last: self.last.map(|value| Box::new(map(*value))),
229        }
230    }
231}
232
233impl<S> Default for Punctuated<S> {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239impl<S> FromIterator<S> for Punctuated<S> {
240    fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
241        let mut iter = iter.into_iter();
242        let Some(first) = iter.next() else {
243            return Self::new();
244        };
245        let mut values = Self::from_value(first);
246        for value in iter {
247            values.push(Token![,](Span::detached()), value);
248        }
249        values
250    }
251}
252
253impl<S> Parse for Punctuated<S>
254where
255    S: Parse + From<Empty>,
256{
257    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
258        let mut values = Self::new();
259        if input.is_eof() {
260            return Ok(values);
261        }
262
263        let first = if input
264            .peek()
265            .is_some_and(<Comma as Token>::matches_token_tree)
266        {
267            S::from(Empty::new(empty_before(input.peek().unwrap().span())))
268        } else {
269            S::parse(input)?
270        };
271        values = Self::from_value(first);
272
273        while input
274            .peek()
275            .is_some_and(<Comma as Token>::matches_token_tree)
276        {
277            let punctuation = Comma::parse(input)?;
278            let next = if input.is_eof()
279                || input.peek().is_some_and(|token| {
280                    <Comma as Token>::matches_token_tree(token)
281                        || <Semicolon as Token>::matches_token_tree(token)
282                }) {
283                let span = input
284                    .peek()
285                    .map(|token| empty_before(token.span()))
286                    .unwrap_or_else(|| input.eof_span());
287                S::from(Empty::new(span))
288            } else {
289                S::parse(input)?
290            };
291            values.push(punctuation, next);
292        }
293        Ok(values)
294    }
295}
296
297impl<S: ToTokens> ToTokens for Punctuated<S> {
298    fn to_tokens(&self, output: &mut TokenStream) {
299        for pair in self.pairs() {
300            match pair {
301                Pair::Punctuated(value, punctuation) => {
302                    value.to_tokens(output);
303                    punctuation.to_tokens(output);
304                }
305                Pair::End(value) => value.to_tokens(output),
306            }
307        }
308    }
309}
310
311impl<S> IntoIterator for Punctuated<S> {
312    type Item = S;
313    type IntoIter = IntoIter<S>;
314
315    fn into_iter(self) -> Self::IntoIter {
316        let mut values = Vec::with_capacity(self.len());
317        values.extend(self.inner.into_iter().map(|(value, _)| value));
318        values.extend(self.last.map(|value| *value));
319        IntoIter {
320            inner: values.into_iter(),
321        }
322    }
323}
324
325impl<'a, S> IntoIterator for &'a Punctuated<S> {
326    type Item = &'a S;
327    type IntoIter = Iter<'a, S>;
328
329    fn into_iter(self) -> Self::IntoIter {
330        self.iter()
331    }
332}
333
334impl<'a, S> IntoIterator for &'a mut Punctuated<S> {
335    type Item = &'a mut S;
336    type IntoIter = IterMut<'a, S>;
337
338    fn into_iter(self) -> Self::IntoIter {
339        self.iter_mut()
340    }
341}
342
343/// An iterator over owned values that intentionally discards commas.
344pub struct IntoIter<S> {
345    inner: vec::IntoIter<S>,
346}
347
348impl<S> Iterator for IntoIter<S> {
349    type Item = S;
350
351    fn next(&mut self) -> Option<Self::Item> {
352        self.inner.next()
353    }
354
355    fn size_hint(&self) -> (usize, Option<usize>) {
356        (self.inner.len(), Some(self.inner.len()))
357    }
358}
359
360impl<S> DoubleEndedIterator for IntoIter<S> {
361    fn next_back(&mut self) -> Option<Self::Item> {
362        self.inner.next_back()
363    }
364}
365
366impl<S> ExactSizeIterator for IntoIter<S> {}
367
368/// An iterator over borrowed values that hides commas.
369pub struct Iter<'a, S> {
370    inner: Pairs<'a, S>,
371}
372
373impl<'a, S> Iterator for Iter<'a, S> {
374    type Item = &'a S;
375
376    fn next(&mut self) -> Option<Self::Item> {
377        self.inner.next().map(|pair| pair.into_tuple().0)
378    }
379
380    fn size_hint(&self) -> (usize, Option<usize>) {
381        self.inner.size_hint()
382    }
383}
384
385impl<S> DoubleEndedIterator for Iter<'_, S> {
386    fn next_back(&mut self) -> Option<Self::Item> {
387        self.inner.next_back().map(|pair| pair.into_tuple().0)
388    }
389}
390
391impl<S> ExactSizeIterator for Iter<'_, S> {}
392
393/// An iterator over mutably borrowed values that hides commas.
394pub struct IterMut<'a, S> {
395    inner: PairsMut<'a, S>,
396}
397
398impl<'a, S> Iterator for IterMut<'a, S> {
399    type Item = &'a mut S;
400
401    fn next(&mut self) -> Option<Self::Item> {
402        self.inner.next().map(|pair| pair.into_tuple().0)
403    }
404
405    fn size_hint(&self) -> (usize, Option<usize>) {
406        self.inner.size_hint()
407    }
408}
409
410impl<S> DoubleEndedIterator for IterMut<'_, S> {
411    fn next_back(&mut self) -> Option<Self::Item> {
412        self.inner.next_back().map(|pair| pair.into_tuple().0)
413    }
414}
415
416impl<S> ExactSizeIterator for IterMut<'_, S> {}
417
418/// An iterator over borrowed value/comma pairs.
419pub struct Pairs<'a, S> {
420    inner: slice::Iter<'a, (S, Comma)>,
421    last: option::IntoIter<&'a S>,
422}
423
424impl<'a, S> Iterator for Pairs<'a, S> {
425    type Item = Pair<&'a S, &'a Comma>;
426
427    fn next(&mut self) -> Option<Self::Item> {
428        self.inner
429            .next()
430            .map(|(value, punctuation)| Pair::Punctuated(value, punctuation))
431            .or_else(|| self.last.next().map(Pair::End))
432    }
433
434    fn size_hint(&self) -> (usize, Option<usize>) {
435        let len = self.inner.len() + self.last.len();
436        (len, Some(len))
437    }
438}
439
440impl<S> DoubleEndedIterator for Pairs<'_, S> {
441    fn next_back(&mut self) -> Option<Self::Item> {
442        self.last.next().map(Pair::End).or_else(|| {
443            self.inner
444                .next_back()
445                .map(|(value, punctuation)| Pair::Punctuated(value, punctuation))
446        })
447    }
448}
449
450impl<S> ExactSizeIterator for Pairs<'_, S> {}
451
452/// An iterator over mutably borrowed value/comma pairs.
453pub struct PairsMut<'a, S> {
454    inner: slice::IterMut<'a, (S, Comma)>,
455    last: option::IntoIter<&'a mut S>,
456}
457
458impl<'a, S> Iterator for PairsMut<'a, S> {
459    type Item = Pair<&'a mut S, &'a mut Comma>;
460
461    fn next(&mut self) -> Option<Self::Item> {
462        self.inner
463            .next()
464            .map(|(value, punctuation)| Pair::Punctuated(value, punctuation))
465            .or_else(|| self.last.next().map(Pair::End))
466    }
467
468    fn size_hint(&self) -> (usize, Option<usize>) {
469        let len = self.inner.len() + self.last.len();
470        (len, Some(len))
471    }
472}
473
474impl<S> DoubleEndedIterator for PairsMut<'_, S> {
475    fn next_back(&mut self) -> Option<Self::Item> {
476        self.last.next().map(Pair::End).or_else(|| {
477            self.inner
478                .next_back()
479                .map(|(value, punctuation)| Pair::Punctuated(value, punctuation))
480        })
481    }
482}
483
484impl<S> ExactSizeIterator for PairsMut<'_, S> {}
485
486/// An iterator over owned value/comma pairs.
487pub struct IntoPairs<S> {
488    inner: vec::IntoIter<(S, Comma)>,
489    last: option::IntoIter<Box<S>>,
490}
491
492impl<S> Iterator for IntoPairs<S> {
493    type Item = Pair<S>;
494
495    fn next(&mut self) -> Option<Self::Item> {
496        self.inner
497            .next()
498            .map(|(value, punctuation)| Pair::Punctuated(value, punctuation))
499            .or_else(|| self.last.next().map(|value| Pair::End(*value)))
500    }
501
502    fn size_hint(&self) -> (usize, Option<usize>) {
503        let len = self.inner.len() + self.last.len();
504        (len, Some(len))
505    }
506}
507
508impl<S> DoubleEndedIterator for IntoPairs<S> {
509    fn next_back(&mut self) -> Option<Self::Item> {
510        self.last.next().map(|value| Pair::End(*value)).or_else(|| {
511            self.inner
512                .next_back()
513                .map(|(value, punctuation)| Pair::Punctuated(value, punctuation))
514        })
515    }
516}
517
518impl<S> ExactSizeIterator for IntoPairs<S> {}
519
520fn empty_before(span: Span) -> Span {
521    span.source()
522        .ok()
523        .zip(span.start_point().ok())
524        .map(|(source, point)| Span::new(source, TextRange::from_point(point)))
525        .unwrap_or_else(Span::detached)
526}