1use super::{
7 AuthoredItem, AuthoredItemKind, AuthoredItemReadIssue, AuthoredItemReadRefusal,
8 CapturedDelimiter, CapturedFragment, CapturedInput, CapturedTokenTree, SpanHandle,
9};
10
11#[derive(Clone, Copy)]
13struct ItemCoordinates {
14 attributes_end: usize,
15 visibility_end: usize,
16 kind_index: usize,
17 kind: AuthoredItemKind,
18 name_index: Option<usize>,
19 terminator: usize,
20 body_index: Option<usize>,
21}
22
23impl CapturedInput {
24 pub fn authored_item(&self) -> Result<AuthoredItem<'_>, AuthoredItemReadRefusal> {
33 read_item(self.trees())
34 }
35}
36
37impl<'tokens> AuthoredItem<'tokens> {
38 #[must_use]
40 pub const fn preserved(self) -> CapturedFragment<'tokens> {
41 self.preserved
42 }
43
44 #[must_use]
46 pub const fn attributes(self) -> CapturedFragment<'tokens> {
47 self.attributes
48 }
49
50 #[must_use]
52 pub const fn visibility(self) -> CapturedFragment<'tokens> {
53 self.visibility
54 }
55
56 #[must_use]
58 pub const fn qualifiers(self) -> CapturedFragment<'tokens> {
59 self.qualifiers
60 }
61
62 #[must_use]
64 pub const fn kind(self) -> AuthoredItemKind {
65 self.kind
66 }
67
68 #[must_use]
70 pub const fn kind_token(self) -> &'tokens CapturedTokenTree {
71 self.kind_token
72 }
73
74 #[must_use]
76 pub fn name(self) -> Option<(&'tokens CapturedTokenTree, &'tokens str)> {
77 self.name_token
78 .and_then(|token| identifier(token).map(|spelling| (token, spelling)))
79 }
80
81 #[must_use]
83 pub const fn generics(self) -> Option<CapturedFragment<'tokens>> {
84 self.generics
85 }
86
87 #[must_use]
89 pub const fn where_clause(self) -> Option<CapturedFragment<'tokens>> {
90 self.where_clause
91 }
92
93 #[must_use]
95 pub const fn signature(self) -> CapturedFragment<'tokens> {
96 self.signature
97 }
98
99 #[must_use]
101 pub fn body(self) -> Option<(CapturedDelimiter, CapturedFragment<'tokens>)> {
102 self.body_delimiter.zip(self.body)
103 }
104
105 #[must_use]
109 pub const fn unsafe_token(self) -> Option<&'tokens CapturedTokenTree> {
110 self.unsafe_token
111 }
112}
113
114impl AuthoredItemReadRefusal {
115 pub const fn issue(self) -> AuthoredItemReadIssue {
117 self.issue
118 }
119
120 #[must_use]
122 pub const fn token(self) -> Option<SpanHandle> {
123 self.at
124 }
125}
126
127fn read_item(tokens: &[CapturedTokenTree]) -> Result<AuthoredItem<'_>, AuthoredItemReadRefusal> {
129 if tokens.is_empty() {
130 return Err(refused(AuthoredItemReadIssue::ItemMissing, None));
131 }
132 let attributes_end = attributes_end(tokens);
133 let visibility_end = visibility_end(tokens, attributes_end);
134 let (kind_index, kind) = item_kind(tokens, visibility_end)?;
135 let name_index = item_name(tokens, kind_index, kind)?;
136 let terminator = terminator(tokens, kind)?;
137 let body_index = body_index(tokens, name_index, kind, terminator);
138 assemble(
139 tokens,
140 ItemCoordinates {
141 attributes_end,
142 visibility_end,
143 kind_index,
144 kind,
145 name_index,
146 terminator,
147 body_index,
148 },
149 )
150}
151
152fn assemble(
154 tokens: &[CapturedTokenTree],
155 coordinates: ItemCoordinates,
156) -> Result<AuthoredItem<'_>, AuthoredItemReadRefusal> {
157 let signature_end = coordinates.body_index.unwrap_or(coordinates.terminator);
158 let generic_start = coordinates.name_index.map_or_else(
159 || coordinates.kind_index.checked_add(1),
160 |index| index.checked_add(1),
161 );
162 let generics = generic_start
163 .and_then(|start| generic_range(tokens, start, signature_end))
164 .map(|(start, end)| fragment(tokens, start, end, None))
165 .transpose()?;
166 let where_clause = word_index(
167 tokens,
168 "where",
169 coordinates.kind_index,
170 coordinates.terminator,
171 )
172 .map(|start| fragment(tokens, start, coordinates.terminator, None))
173 .transpose()?;
174 let body_token = coordinates.body_index.and_then(|index| tokens.get(index));
175 let (body_delimiter, body) = body_token.and_then(CapturedTokenTree::group).map_or(
176 (None, None),
177 |(delimiter, members)| {
178 (
179 Some(delimiter),
180 Some(CapturedFragment::over(
181 members,
182 body_token.map(CapturedTokenTree::span),
183 )),
184 )
185 },
186 );
187 Ok(AuthoredItem {
188 preserved: fragment(tokens, 0, tokens.len(), None)?,
189 attributes: fragment(tokens, 0, coordinates.attributes_end, None)?,
190 visibility: fragment(
191 tokens,
192 coordinates.attributes_end,
193 coordinates.visibility_end,
194 None,
195 )?,
196 qualifiers: fragment(
197 tokens,
198 coordinates.visibility_end,
199 coordinates.kind_index,
200 None,
201 )?,
202 signature: fragment(tokens, coordinates.attributes_end, signature_end, None)?,
203 generics,
204 where_clause,
205 body,
206 body_delimiter,
207 kind: coordinates.kind,
208 kind_token: token_at(tokens, coordinates.kind_index)?,
209 name_token: coordinates.name_index.and_then(|index| tokens.get(index)),
210 unsafe_token: word_token(
211 tokens,
212 "unsafe",
213 coordinates.visibility_end,
214 coordinates.kind_index,
215 ),
216 })
217}
218
219fn attributes_end(tokens: &[CapturedTokenTree]) -> usize {
221 let mut next = 0usize;
222 loop {
223 let Some(mark) = tokens.get(next) else {
224 return next;
225 };
226 let Some(group) = next.checked_add(1).and_then(|index| tokens.get(index)) else {
227 return next;
228 };
229 let is_outer = mark.punct() == Some('#')
230 && group
231 .group()
232 .is_some_and(|(delimiter, _)| delimiter == CapturedDelimiter::Bracket);
233 if !is_outer {
234 return next;
235 }
236 next = next.saturating_add(2);
237 }
238}
239
240fn visibility_end(tokens: &[CapturedTokenTree], start: usize) -> usize {
242 let Some(token) = tokens.get(start) else {
243 return start;
244 };
245 if token.word() != Some("pub") {
246 return start;
247 }
248 let after_pub = start.saturating_add(1);
249 tokens.get(after_pub).map_or(after_pub, |next| {
250 if next
251 .group()
252 .is_some_and(|(delimiter, _)| delimiter == CapturedDelimiter::Parenthesis)
253 {
254 after_pub.saturating_add(1)
255 } else {
256 after_pub
257 }
258 })
259}
260
261fn item_kind(
263 tokens: &[CapturedTokenTree],
264 start: usize,
265) -> Result<(usize, AuthoredItemKind), AuthoredItemReadRefusal> {
266 let mut next = start;
267 while let Some(token) = tokens.get(next) {
268 let word = token.word();
269 let found = match word {
270 Some("mod") => Some(AuthoredItemKind::Module),
271 Some("struct") => Some(AuthoredItemKind::Structure),
272 Some("enum") => Some(AuthoredItemKind::Enumeration),
273 Some("union") => Some(AuthoredItemKind::Union),
274 Some("trait") => Some(AuthoredItemKind::Trait),
275 Some("fn") => Some(AuthoredItemKind::Function),
276 Some("impl") => Some(AuthoredItemKind::Implementation),
277 Some("type") => Some(AuthoredItemKind::TypeAlias),
278 Some("static") => Some(AuthoredItemKind::Static),
279 Some("use") => Some(AuthoredItemKind::Use),
280 Some("const") if !function_follows(tokens, next.saturating_add(1)) => {
281 Some(AuthoredItemKind::Constant)
282 }
283 Some("extern") if next_word(tokens, next) == Some("crate") => {
284 Some(AuthoredItemKind::ExternalCrate)
285 }
286 Some("unsafe" | "async" | "default" | "auto" | "extern" | "const") => None,
287 _ => {
288 return Err(refused(
289 AuthoredItemReadIssue::ItemKindMissing,
290 Some(token.span()),
291 ));
292 }
293 };
294 if let Some(kind) = found {
295 return Ok((next, kind));
296 }
297 next = next.saturating_add(1);
298 if word == Some("extern")
299 && tokens
300 .get(next)
301 .is_some_and(|candidate| candidate.text().is_some())
302 {
303 next = next.saturating_add(1);
304 }
305 }
306 Err(refused(
307 AuthoredItemReadIssue::ItemKindMissing,
308 tokens.last().map(CapturedTokenTree::span),
309 ))
310}
311
312fn function_follows(tokens: &[CapturedTokenTree], start: usize) -> bool {
314 let mut next = start;
315 while let Some(token) = tokens.get(next) {
316 match token.word() {
317 Some("fn") => return true,
318 Some("unsafe" | "async" | "extern") => {
319 next = next.saturating_add(1);
320 if token.word() == Some("extern")
321 && tokens
322 .get(next)
323 .is_some_and(|candidate| candidate.text().is_some())
324 {
325 next = next.saturating_add(1);
326 }
327 }
328 _ => return false,
329 }
330 }
331 false
332}
333
334fn item_name(
336 tokens: &[CapturedTokenTree],
337 kind_index: usize,
338 kind: AuthoredItemKind,
339) -> Result<Option<usize>, AuthoredItemReadRefusal> {
340 if matches!(
341 kind,
342 AuthoredItemKind::Implementation | AuthoredItemKind::Use
343 ) {
344 return Ok(None);
345 }
346 let mut candidate = kind_index.saturating_add(1);
347 if kind == AuthoredItemKind::ExternalCrate {
348 candidate = candidate.saturating_add(1);
349 }
350 if kind == AuthoredItemKind::Static
351 && tokens.get(candidate).and_then(CapturedTokenTree::word) == Some("mut")
352 {
353 candidate = candidate.saturating_add(1);
354 }
355 let token = tokens.get(candidate).ok_or(refused(
356 AuthoredItemReadIssue::ItemNameMissing(kind),
357 tokens.get(kind_index).map(CapturedTokenTree::span),
358 ))?;
359 identifier(token).ok_or(refused(
360 AuthoredItemReadIssue::ItemNameMissing(kind),
361 Some(token.span()),
362 ))?;
363 Ok(Some(candidate))
364}
365
366fn terminator(
368 tokens: &[CapturedTokenTree],
369 kind: AuthoredItemKind,
370) -> Result<usize, AuthoredItemReadRefusal> {
371 let index = tokens
372 .len()
373 .checked_sub(1)
374 .ok_or(refused(AuthoredItemReadIssue::ItemMissing, None))?;
375 let token = token_at(tokens, index)?;
376 let finished = token.punct() == Some(';')
377 || token
378 .group()
379 .is_some_and(|(delimiter, _)| delimiter == CapturedDelimiter::Brace);
380 if finished {
381 Ok(index)
382 } else {
383 Err(refused(
384 AuthoredItemReadIssue::ItemBoundaryUnfinished(kind),
385 Some(token.span()),
386 ))
387 }
388}
389
390fn body_index(
392 tokens: &[CapturedTokenTree],
393 name: Option<usize>,
394 kind: AuthoredItemKind,
395 terminator: usize,
396) -> Option<usize> {
397 if tokens
398 .get(terminator)
399 .is_some_and(|token| token.group().is_some())
400 {
401 return Some(terminator);
402 }
403 if kind != AuthoredItemKind::Structure {
404 return None;
405 }
406 let start = name.and_then(|index| index.checked_add(1))?;
407 tokens
408 .iter()
409 .enumerate()
410 .skip(start)
411 .take(terminator.saturating_sub(start))
412 .find_map(|(index, token)| {
413 token.group().and_then(|(delimiter, _)| {
414 matches!(
415 delimiter,
416 CapturedDelimiter::Parenthesis | CapturedDelimiter::Brace
417 )
418 .then_some(index)
419 })
420 })
421}
422
423fn generic_range(tokens: &[CapturedTokenTree], start: usize, end: usize) -> Option<(usize, usize)> {
425 if tokens.get(start).and_then(CapturedTokenTree::punct) != Some('<') {
426 return None;
427 }
428 let mut depth = 0usize;
429 for (index, token) in tokens
430 .iter()
431 .enumerate()
432 .skip(start)
433 .take(end.saturating_sub(start))
434 {
435 match token.punct() {
436 Some('<') => depth = depth.saturating_add(1),
437 Some('>') if !arrow_close(tokens, index) => {
438 depth = depth.checked_sub(1)?;
439 if depth == 0 {
440 return index.checked_add(1).map(|after| (start, after));
441 }
442 }
443 Some(_) | None => {}
444 }
445 }
446 None
447}
448
449fn arrow_close(tokens: &[CapturedTokenTree], index: usize) -> bool {
451 index
452 .checked_sub(1)
453 .and_then(|previous| tokens.get(previous))
454 .and_then(CapturedTokenTree::joint_punct)
455 == Some('-')
456}
457
458fn fragment(
460 tokens: &[CapturedTokenTree],
461 start: usize,
462 end: usize,
463 enclosing: Option<SpanHandle>,
464) -> Result<CapturedFragment<'_>, AuthoredItemReadRefusal> {
465 tokens.get(start..end).map_or_else(
466 || {
467 Err(refused(
468 AuthoredItemReadIssue::LensRangeContradiction,
469 tokens.get(start).map(CapturedTokenTree::span),
470 ))
471 },
472 |borrowed| Ok(CapturedFragment::over(borrowed, enclosing)),
473 )
474}
475
476fn token_at(
478 tokens: &[CapturedTokenTree],
479 index: usize,
480) -> Result<&CapturedTokenTree, AuthoredItemReadRefusal> {
481 tokens.get(index).ok_or(refused(
482 AuthoredItemReadIssue::LensRangeContradiction,
483 tokens.last().map(CapturedTokenTree::span),
484 ))
485}
486
487fn identifier(token: &CapturedTokenTree) -> Option<&str> {
489 token.word().or_else(|| token.raw_identifier())
490}
491
492fn next_word(tokens: &[CapturedTokenTree], index: usize) -> Option<&str> {
494 index
495 .checked_add(1)
496 .and_then(|next| tokens.get(next))
497 .and_then(CapturedTokenTree::word)
498}
499
500fn word_index(tokens: &[CapturedTokenTree], word: &str, start: usize, end: usize) -> Option<usize> {
502 tokens
503 .iter()
504 .enumerate()
505 .skip(start)
506 .take(end.saturating_sub(start))
507 .find_map(|(index, token)| (token.word() == Some(word)).then_some(index))
508}
509
510fn word_token<'tokens>(
512 tokens: &'tokens [CapturedTokenTree],
513 word: &str,
514 start: usize,
515 end: usize,
516) -> Option<&'tokens CapturedTokenTree> {
517 word_index(tokens, word, start, end).and_then(|index| tokens.get(index))
518}
519
520const fn refused(issue: AuthoredItemReadIssue, at: Option<SpanHandle>) -> AuthoredItemReadRefusal {
522 AuthoredItemReadRefusal { issue, at }
523}