1#![doc = include_str!("../README.md")]
2
3use std::{convert::identity, str::FromStr};
4
5use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing::*, Span, TokenStream, TokenTree};
6
7fn err<T>(msg: &str, span: Span) -> Result<T, TokenStream> {
8 let s = |mut t: TokenTree| {
9 t.set_span(span.clone());
10 t
11 };
12 Err(TokenStream::from_iter([
13 s(Punct::new(':', Joint).into()),
14 s(Punct::new(':', Joint).into()),
15 s(Ident::new("core", span.clone()).into()),
16 s(Punct::new(':', Joint).into()),
17 s(Punct::new(':', Joint).into()),
18 s(Ident::new("compile_error", span.clone()).into()),
19 s(Punct::new('!', Joint).into()),
20 s(Group::new(Delimiter::Parenthesis, TokenStream::from_iter([
21 s(Literal::string(msg).into()),
22 ])).into()),
23 ]))
24}
25
26fn extract_str(s: &str, span: Span) -> Result<String, TokenStream> {
27 if s.starts_with('"') { return Ok(s[1..s.len()-1].to_owned()); }
28 if !s.starts_with('r') {
29 return err("invalid string literal", span);
30 }
31 let mut s = &s[1..];
32 while s.starts_with('#') {
33 s = &s[1..s.len()-1];
34 }
35 let escaped = format!("{:?}", &s[1..s.len()-1]);
36 return Ok(escaped[1..escaped.len()-1].to_string());
37}
38
39fn merge_str(a: &Literal, b: &Literal) -> Result<Literal, TokenStream> {
40 let (sa, sb) = (a.span(), b.span());
41 let (a, b) = (a.to_string(), b.to_string());
42 let (a, b) = (extract_str(&a, sa)?, extract_str(&b, sb)?);
43 Ok(Literal::from_str(&format!("\"{a}{b}\"")).unwrap())
44}
45
46fn expr_impl(stream: TokenStream) -> Result<TokenStream, TokenStream> {
47 let mut result = TokenStream::new();
48 let mut iter = stream.into_iter();
49 while let Some(tok) = iter.next() {
50 match tok {
51 proc_macro::TokenTree::Group(group) => {
52 let mut new_group = Group::new(
53 group.delimiter(),
54 expr_impl(group.stream())?,
55 );
56 new_group.set_span(group.span());
57 result.extend([TokenTree::from(new_group)]);
58 },
59 proc_macro::TokenTree::Punct(ref punct) if punct.as_char() == '#' => {
60 let Some(op) = iter.next() else {
61 return err("unexpected end of input", punct.span());
62 };
63 match op {
64 TokenTree::Punct(ref punct) => {
65 if punct.as_char() == '#' {
66 result.extend([op]);
67 } else {
68 return err("invalid operator", punct.span());
69 }
70 },
71 TokenTree::Group(_) => {
72 result.extend([tok]);
73 result.extend(expr_impl(
74 TokenStream::from_iter([op])
75 )?);
76 },
77 TokenTree::Literal(tt) => return err("invalid operator", tt.span()),
78 TokenTree::Ident(ident) => {
79 let Some(param) = iter.next() else {
80 return err("unexpected end of input", ident.span());
81 };
82 let TokenTree::Group(param) = param else {
83 return err("invalid operation param", param.span());
84 };
85 let out_span = param.stream().into_iter().next()
86 .map_or(param.span(), |t| t.span());
87 let param = param.stream();
88 match &*ident.to_string() {
89 "stringify" => {
90 let s = param.to_string();
91 let mut tt = Literal::string(&s);
92 tt.set_span(out_span);
93 result.extend([TokenTree::from(tt)]);
94 },
95 "concat" => {
96 let param = expr_impl(param)?;
97 let mut s = Literal::string("");
98 let mut iter = param.into_iter().peekable();
99 while let Some(tt) = iter.next() {
100 iter.next_if(|p| matches!(p,
101 TokenTree::Punct(p) if p.as_char() == ','));
102 let TokenTree::Literal(lit) = tt else {
103 return err("is not a literal", tt.span())
104 };
105 match merge_str(&s, &lit) {
106 Ok(merged) => s = merged,
107 Err(e) => return Err(e),
108 }
109 }
110 s.set_span(out_span);
111 result.extend([TokenTree::from(s)]);
112 },
113 _ => return err("unknown operator", ident.span()),
114 }
115 },
116 }
117 },
118 _ => result.extend([tok]),
119 }
120 }
121 Ok(result)
122}
123
124#[proc_macro]
140pub fn sexpr(stream: TokenStream) -> TokenStream {
141 expr_impl(stream).map_or_else(identity, identity)
142}
143
144#[proc_macro_attribute]
159pub fn sexpr_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
160 TokenStream::from_iter([
161 expr_impl(TokenStream::from_iter([
162 TokenTree::from(Punct::new('#', Joint)),
163 Group::new(Delimiter::Bracket, attr).into(),
164 ])).unwrap_or_else(|mut e| {
165 e.extend([TokenTree::from(Punct::new(';', Alone))]);
166 e
167 }),
168 item,
169 ])
170}