1#![doc = include_str!("../README.md")]
2
3use core::{convert::identity, str::FromStr as _};
4
5use proc_macro::{
6 Delimiter, Group, Ident, Literal, Punct, Spacing::*, Span, TokenStream,
7 TokenTree,
8};
9use proc_macro_tool::{
10 rerr, stream, streams, try_pfunc, SetSpan as _, TokenTreeExt as _,
11};
12
13fn extract_str(s: &str, span: Span) -> Result<String, TokenStream> {
14 if !s.ends_with(['"', '#']) {
15 return rerr("invalid string suffix", span);
16 }
17 if s.starts_with('"') {
18 return Ok(s[1..s.len()-1].to_owned());
19 }
20 if !s.starts_with('r') {
21 return rerr("invalid string literal", span);
22 }
23 let mut s = &s[1..];
24 while s.starts_with('#') {
25 s = &s[1..s.len()-1];
26 }
27 let escaped = format!("{:?}", &s[1..s.len()-1]);
28 debug_assert!(escaped.starts_with('"') && escaped.ends_with('"'), "{escaped}");
29 Ok(escaped[1..escaped.len()-1].to_string())
30}
31
32fn merge_str(a: &Literal, b: &Literal) -> Result<Literal, TokenStream> {
33 let (sa, sb) = (a.span(), b.span());
34 let (a, b) = (a.to_string(), b.to_string());
35 let (a, b) = (extract_str(&a, sa)?, extract_str(&b, sb)?);
36 let lit = Literal::from_str(&format!("\"{a}{b}\"")).unwrap();
37 Ok(lit.set_spaned(sa))
38}
39
40fn do_operation(i: Ident, group: Group) -> Result<TokenStream, TokenStream> {
41 Ok(match &*i.to_string() {
42 "concat" => {
43 let gspan = group.span();
44 let param = expr_impl(group.stream())?;
45 let mut s = Literal::string("");
46 let mut span = None;
47 let mut iter = param.into_iter().peekable();
48
49 while let Some(tt) = iter.next() {
50 iter.next_if(|p| p.is_punch(','));
51 let TokenTree::Literal(lit) = tt else {
52 return rerr("is not a literal", tt.span());
53 };
54 span.get_or_insert(lit.span());
55 s = merge_str(&s, &lit)?;
56 }
57
58 s.set_span(span.unwrap_or(gspan));
59 stream([TokenTree::from(s)])
60 },
61 "stringify" => {
62 let out_span = group.stream().into_iter().next()
63 .map_or(group.span(), |t| t.span());
64
65 let s = group.stream().to_string();
66 let tt = Literal::string(&s).set_spaned(out_span);
67 stream([TokenTree::from(tt)])
68 },
69 _ => unreachable!(),
70 })
71}
72
73fn expr_impl(stream: TokenStream) -> Result<TokenStream, TokenStream> {
74 try_pfunc(
75 stream,
76 false,
77 ["concat", "stringify"],
78 do_operation,
79 )
80}
81
82#[proc_macro]
96pub fn sexpr(stream: TokenStream) -> TokenStream {
97 expr_impl(stream).map_or_else(identity, identity)
98}
99
100#[proc_macro_attribute]
113pub fn sexpr_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
114 streams([
115 expr_impl(stream([
116 TokenTree::from(Punct::new('#', Joint)),
117 Group::new(Delimiter::Bracket, attr).into(),
118 ])).map_or_else(identity, identity),
119 item,
120 ])
121}