Skip to main content

serverkit_macros/
lib.rs

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}