1#![doc = include_str!("../README.md")]
2#![allow(clippy::needless_doctest_main)]
3
4use std::convert::identity;
5
6use proc_macro::{Delimiter, Group, Span, TokenStream, TokenTree as TT};
7use proc_macro_tool::{
8 err, rerr, try_pfunc, GetSpan, ParseIter, ParseIterExt,
9 SetSpan, TokenStreamExt, TokenTreeExt, WalkExt,
10};
11
12fn eoi(iter: impl IntoIterator<Item = TT>) -> Result<(), TokenStream> {
13 if let Some(tt) = iter.into_iter().next() {
14 Err(err("unexpected token, expected end of input", tt))
15 } else {
16 Ok(())
17 }
18}
19
20fn fmt(g: Group) -> String {
21 if g.is_delimiter(Delimiter::None) {
22 format!("Ø{g}Ø")
23 } else {
24 g.to_string()
25 }
26}
27
28const NUM_SUFFIXS: &[&str] = &[
29 "i8", "i16", "i32", "i64", "i128",
30 "u8", "u16", "u32", "u64", "u128",
31];
32trait StrExt {
33 fn remove_number_suffix(&self) -> &Self;
34}
35impl StrExt for str {
36 fn remove_number_suffix(&self) -> &Self {
37 for &suffix in NUM_SUFFIXS {
38 if let Some(s) = self.strip_suffix(suffix) {
39 return s;
40 }
41 }
42 return self;
43 }
44}
45
46#[must_use]
47fn index_tt<I>(mut tt: TT, iter: &mut ParseIter<I>) -> Result<TT, TokenStream>
48where I: Iterator<Item = TT>,
49{
50 while let Some((mut span, mut param)) = iter
51 .next_if(|tt| tt.is_delimiter(Delimiter::Bracket))
52 .map(|tt| tt.into_group().unwrap())
53 .map(|g| (g.span_close(), g.stream().into_iter()))
54 {
55 let i = param.next()
56 .ok_or_else(|| err!("unexpected token, expected literal", span))?
57 .span_as(&mut span)
58 .into_literal()
59 .map_err(|_| err!("unexpected token, expected literal", span))?
60 .to_string()
61 .remove_number_suffix()
62 .parse()
63 .map_err(|e| err!(@("parse number {e}"), span))?;
64 let g = tt.into_group()
65 .map_err(|t| err!(@("cannot index {t}, e.g [...]"), span))?;
66 tt = g.stream().into_iter().nth(i)
67 .ok_or_else(|| err!(@("index {i} out of range, of {}", fmt(g)), span))?
68 };
69 Ok(tt)
70}
71
72fn parse_input_span<I>(iter: I) -> Result<TT, TokenStream>
73where I: Iterator<Item = TT>,
74{
75 let mut iter = iter.parse_iter();
76 if iter.peek_puncts("#").is_some()
77 && iter.peek_i_is(1, |t| t.is_keyword("mixed"))
78 {
79 return Ok(iter.next().unwrap().set_spaned(Span::mixed_site()));
80 }
81
82 let mut tt = iter.next()
83 .ok_or_else(|| err!("unexpected comma of input start"))?;
84
85 tt = index_tt(tt, &mut iter)?;
86
87 if let Some(end) = iter.next() {
88 rerr!("unexpected token, expected [...] or comma", end)
89 }
90
91 Ok(tt)
92}
93
94fn extract_expand_body<I>(
95 input: &mut ParseIter<I>,
96 span: Span,
97) -> Result<TokenStream, TokenStream>
98where I: Iterator<Item = TT>,
99{
100 let Some(tt) = input.next() else {
101 rerr!("unexpected end of input, expected a brace {...}", span)
102 };
103 let Some(group) = tt.as_group() else {
104 rerr!("unexpected token, expected a brace {...}", tt)
105 };
106
107 let out = if group.is_solid_group() {
108 group.stream()
109 } else {
110 extract_expand_body(input, group.span())?
111 };
112
113 eoi(input)?;
114
115 Ok(out)
116}
117
118fn do_operation(
119 input: TokenStream,
120 spant: TT,
121) -> Result<TokenStream, TokenStream> {
122 try_pfunc(input, false, [
123 "set_span",
124 "set_index_span",
125 ], |i, param| {
126 Ok(match &*i.to_string() {
127 "set_span" => {
128 param.stream()
129 .walk(|tt| tt.set_spaned(spant.span()))
130 },
131 "set_index_span" => {
132 let iter = &mut param.stream().parse_iter();
133 let spant = index_tt(spant.clone(), iter)?;
134 let result = iter.next()
135 .ok_or_else(|| err!("unexpected end of input, expected {...}", param))?
136 .into_group()
137 .map_err(|t| err!("expected {...}", t))?
138 .stream()
139 .walk(|tt| tt.set_spaned(spant.span()));
140 eoi(iter)?;
141 result
142 },
143 _ => unreachable!(),
144 })
145 })
146}
147
148fn set_span_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
149 let Some((spani, mut input)) = input.split_puncts(",") else {
150 rerr!("unexpected end of input, expected comma");
151 };
152 let span = parse_input_span(spani.into_iter())?;
153 let input = extract_expand_body(&mut input, Span::call_site())?;
154 do_operation(input, span)
155}
156
157#[proc_macro]
195pub fn set_span(input: TokenStream) -> TokenStream {
196 set_span_impl(input).unwrap_or_else(identity)
197}