1use std::{fmt::Write, str::FromStr};
2
3use proc_macro::{TokenStream, TokenTree};
4
5mod schema;
6
7#[proc_macro]
8pub fn impl_handlers(input: TokenStream) -> TokenStream {
9 match expand(input) {
10 Ok(output) => output,
11 Err(message) => compile_error(&message),
12 }
13}
14
15#[proc_macro]
16pub fn impl_routes(input: TokenStream) -> TokenStream {
17 match expand_routes(input) {
18 Ok(output) => output,
19 Err(message) => compile_error(&message),
20 }
21}
22
23#[proc_macro_derive(Schema, attributes(schema))]
24pub fn derive_schema(input: TokenStream) -> TokenStream {
25 match schema::expand(input) {
26 Ok(output) => output,
27 Err(message) => compile_error(&message),
28 }
29}
30
31fn expand(input: TokenStream) -> Result<TokenStream, String> {
32 let maximum = parse_maximum(input)?;
33 let mut output = String::new();
34
35 for arity in 1..=maximum {
36 output.push_str(&handler_invocation(arity)?);
37 }
38
39 TokenStream::from_str(&output).map_err(|error| error.to_string())
40}
41
42fn expand_routes(input: TokenStream) -> Result<TokenStream, String> {
43 let maximum = parse_maximum(input)?;
44 let mut output = String::new();
45
46 for arity in 1..=maximum {
47 output.push_str("impl_route_tuple!(");
48 for index in 1..=arity {
49 if index > 1 {
50 output.push(',');
51 }
52 write!(output, "R{index}").map_err(|error| error.to_string())?;
53 }
54 output.push_str(");");
55 }
56
57 TokenStream::from_str(&output).map_err(|error| error.to_string())
58}
59
60fn parse_maximum(input: TokenStream) -> Result<usize, String> {
61 let mut tokens = input.into_iter();
62
63 let literal = match tokens.next() {
64 Some(TokenTree::Literal(literal)) => literal,
65 _ => return Err("expected one positive decimal integer".to_owned()),
66 };
67
68 if tokens.next().is_some() {
69 return Err("expected one positive decimal integer".to_owned());
70 }
71
72 let maximum = literal
73 .to_string()
74 .parse::<usize>()
75 .map_err(|_| "expected one positive decimal integer".to_owned())?;
76
77 if maximum == 0 {
78 return Err("handler arity must be greater than zero".to_owned());
79 }
80
81 Ok(maximum)
82}
83
84fn handler_invocation(arity: usize) -> Result<String, String> {
85 let mut invocation = String::from("impl_handler!([");
86
87 for index in 0..arity.saturating_sub(1) {
88 if index > 0 {
89 invocation.push(',');
90 }
91
92 write!(invocation, "(A{index},a{index})").map_err(|error| error.to_string())?;
93 }
94
95 let last = arity - 1;
96 write!(invocation, "];(A{last},a{last}));").map_err(|error| error.to_string())?;
97
98 Ok(invocation)
99}
100
101fn compile_error(message: &str) -> TokenStream {
102 TokenStream::from_str(&format!("compile_error!({message:?});")).unwrap_or_default()
103}