1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Comment and blank-line structure over the lossless item model.
use core::ops::Range;
use crate::{Document, Entry, Item, ItemKind};
/// A contiguous run of nonblank items, separated by blank lines.
///
/// A group may contain comments, declarations and malformed statements. Blank
/// items are separators and are not part of any group.
#[derive(Debug, Clone, Copy)]
pub struct Group<'doc> {
document: &'doc Document<'doc>,
start: usize,
end: usize,
}
impl<'doc> Group<'doc> {
/// Items in source order.
///
/// # Panics
/// Panics only if an internal document-snapshot invariant is broken; groups
/// are always created from valid item ranges.
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn items(self) -> impl DoubleEndedIterator<Item = Item<'doc>> + ExactSizeIterator + 'doc {
(self.start..self.end).map(move |index| {
self.document
.item(index)
.expect("group item belongs to document")
})
}
/// Declarations in source order.
pub fn entries(self) -> impl Iterator<Item = Entry<'doc>> + 'doc {
self.items().filter_map(Item::entry)
}
/// Whole-line comments in source order.
pub fn comments(self) -> impl Iterator<Item = Item<'doc>> + 'doc {
self.items().filter(|item| item.kind() == ItemKind::Comment)
}
/// Contiguous comments at the start of this group, before its first entry.
pub fn leading_comments(self) -> impl Iterator<Item = Item<'doc>> + 'doc {
self.items()
.take_while(|item| item.kind() == ItemKind::Comment)
}
/// Exact source range from the first item's start to the last item's end.
///
/// # Panics
/// Panics only if an internal document-snapshot invariant is broken; groups
/// are never empty.
#[must_use]
pub fn span(self) -> Range<usize> {
let start = self
.document
.item(self.start)
.expect("nonempty group")
.span()
.start;
let end = self
.document
.item(self.end - 1)
.expect("nonempty group")
.span()
.end;
start..end
}
}
/// Blank-line-separated groups in source order.
#[derive(Debug, Clone)]
pub struct Groups<'doc> {
document: &'doc Document<'doc>,
next: usize,
}
impl<'doc> Iterator for Groups<'doc> {
type Item = Group<'doc>;
fn next(&mut self) -> Option<Self::Item> {
while self.next < self.document.items().len()
&& self
.document
.item(self.next)
.expect("group cursor belongs to document")
.kind()
== ItemKind::Blank
{
self.next += 1;
}
if self.next >= self.document.items().len() {
return None;
}
let start = self.next;
while self.next < self.document.items().len()
&& self
.document
.item(self.next)
.expect("group cursor belongs to document")
.kind()
!= ItemKind::Blank
{
self.next += 1;
}
Some(Group {
document: self.document,
start,
end: self.next,
})
}
}
impl Document<'_> {
/// Blank-line-separated groups in source order.
#[must_use]
pub fn groups(&self) -> Groups<'_> {
Groups {
document: self,
next: 0,
}
}
/// Items before the first declaration, when any exist.
///
/// This is a structural header view, not a separate syntax kind. It can
/// contain comments and blank lines and excludes the BOM prelude.
#[must_use]
pub fn header(&self) -> Option<Group<'_>> {
let end = self
.items()
.position(|item| item.kind() == ItemKind::Entry)
.unwrap_or(self.items().len());
(end != 0).then_some(Group {
document: self,
start: 0,
end,
})
}
}
impl<'doc> Entry<'doc> {
/// Contiguous whole-line comments immediately before this declaration.
///
/// A blank line breaks attachment. Inline comments are available through
/// [`Entry::inline_comment`](crate::Entry::inline_comment).
///
/// # Panics
/// Panics only if an internal document-snapshot invariant is broken; leading
/// comment indexes are derived from the same document.
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn leading_comments(
self,
) -> impl DoubleEndedIterator<Item = Item<'doc>> + ExactSizeIterator + 'doc {
let end = self.item_id();
let mut start = end;
while start != 0 {
let Some(previous) = self.document.item(start - 1) else {
break;
};
if previous.kind() != ItemKind::Comment {
break;
}
start -= 1;
}
(start..end).map(move |index| {
self.document
.item(index)
.expect("leading comment belongs to document")
})
}
}