use super::types::{CaptureError, Spans};
use crate::token::{
CaptureBuildRefusal, CaptureLevel, CapturedAtom, CapturedDelimiter, CapturedInput,
LiteralReadCause, capture_literal,
};
use proc_macro::{Delimiter, Spacing, Span, TokenStream, TokenTree};
pub fn capture(stream: TokenStream, spans: &mut Spans) -> Result<CapturedInput, CaptureError> {
let level = spans.builder().open();
let level = capture_stream(stream, level).map_err(capture_refusal)?;
Ok(level.finish())
}
fn capture_stream(
stream: TokenStream,
mut level: CaptureLevel<'_, Span>,
) -> Result<CaptureLevel<'_, Span>, CaptureBuildRefusal<Span, LiteralReadCause>> {
for tree in stream {
let position = tree.span();
level = match tree {
TokenTree::Ident(word) => level.atom(position, |_| {
Ok::<_, LiteralReadCause>(captured_identifier(&word.to_string()))
})?,
TokenTree::Punct(punct) => level.atom(position, |_| {
Ok::<_, LiteralReadCause>(captured_punctuation(punct.as_char(), punct.spacing()))
})?,
TokenTree::Literal(literal) => {
level.atom(position, |_| capture_literal(&literal.to_string()))?
}
TokenTree::Group(group) => level.group(
position,
captured_delimiter(group.delimiter()),
|_span, inner| capture_stream(group.stream(), inner),
)?,
};
}
Ok(level)
}
fn captured_identifier(spelling: &str) -> CapturedAtom {
spelling.strip_prefix("r#").map_or_else(
|| CapturedAtom::Word(spelling.to_owned()),
|name| CapturedAtom::RawIdentifier(name.to_owned()),
)
}
const fn captured_punctuation(mark: char, spacing: Spacing) -> CapturedAtom {
match spacing {
Spacing::Joint => CapturedAtom::JointPunct(mark),
Spacing::Alone => CapturedAtom::Punct(mark),
}
}
fn capture_refusal(refusal: CaptureBuildRefusal<Span, LiteralReadCause>) -> CaptureError {
match refusal {
CaptureBuildRefusal::Unbounded { bound, at: _ } => CaptureError::Unbounded { bound },
CaptureBuildRefusal::ProducerRefused { cause, path, at } => {
CaptureError::Unread { cause, path, at }
}
}
}
const fn captured_delimiter(delimiter: Delimiter) -> CapturedDelimiter {
match delimiter {
Delimiter::Parenthesis => CapturedDelimiter::Parenthesis,
Delimiter::Brace => CapturedDelimiter::Brace,
Delimiter::Bracket => CapturedDelimiter::Bracket,
Delimiter::None => CapturedDelimiter::Bare,
}
}