Skip to main content

convrt_macros/
lib.rs

1use core::iter::Iterator;
2
3use proc_macro::{TokenStream, TokenTree};
4
5fn append_as(buf: &mut String, struct_name: &str, ty: &str, name: &str, field_name: &str, op: char, ops: &[usize]) {
6    let sufix = if ty == "usize" { "".to_string() } else { format!("_{ty}") };
7    let as_ty = if ty == "usize" { "".to_string() } else { format!("as {ty}") };
8
9    let mut operation = format!("self.{field_name} {as_ty}");
10
11    let op_sufix = if ty == "usize" { "" } else { ".0" };
12
13    for n in ops {
14        operation.push(op);
15        operation.push_str(&format!("{n}{op_sufix}"));
16    }
17
18    buf.push_str(&format!(r#"#[doc = "Returns this [`{struct_name}`] converted to {name}"]"#));
19    buf.push_str(&format!("pub fn as_{name}{sufix} (&self) -> {ty} {{"));
20    buf.push_str(&operation);
21    buf.push('}');
22}
23
24fn append_from(buf: &mut String, struct_name: &str, name: &str, field_name: &str, op: char, ops: &[usize]) {
25    buf.push_str(&format!(r#"
26        #[doc = "Builds a [`{struct_name}`] from it's value on {name}"]"#));
27    buf.push_str(&format!("pub fn from_{name} (n: usize) -> Self {{ Self {{ {field_name}: n"));
28
29    for n in ops {
30        buf.push(op);
31        buf.push_str(&format!("{n}"));
32    }
33
34    buf.push_str("}}");
35}
36
37macro_rules! err {
38    ($msg:expr) => {
39        return format!("compile_error!(\"{}\");", $msg).parse().unwrap()
40    };
41}
42macro_rules! unwrap {
43    ($args:expr, $t:ident) => {{
44        let Some(nx) = $args.next() else { err!("Unexpected end of token stream") };
45        let TokenTree:: $t  (val) = nx else { err!(format!("Expected '{}' found '{nx}'", stringify!($t))) };
46        val
47    }};
48}
49
50macro_rules! expect_punct {
51    ($args:expr, $p:literal) => {{
52        let c = unwrap!($args, Punct).as_char();
53        if c != $p {
54            err!(format!("Expected punctuation to be '{}' but found '{c}'", $p));
55        }
56    }};
57}
58
59#[proc_macro]
60pub fn impl_as(args: TokenStream) -> TokenStream {
61    let mut args = args.into_iter().peekable();
62
63    let struct_name = unwrap!(args, Ident).to_string();
64    expect_punct!(args, ',');
65
66    let field_name = unwrap!(args, Ident).to_string();
67    expect_punct!(args, ',');
68
69    let mut block = format!("impl {struct_name} {{");
70
71    while args.peek().is_some() {
72        let func_name = unwrap!(args, Ident).to_string();
73        expect_punct!(args, '=');
74
75        let mut ops = vec![];
76        let mut op = '/';
77        while args.peek().unwrap().to_string() != ";" {
78           if !ops.is_empty() {
79               op = unwrap!(args, Punct).as_char();
80           }
81           let Ok(num) = unwrap!(args, Literal).to_string().parse::<usize>() else { err!("Error parsing literal, expected usize") };
82           ops.push(num);
83        }
84        expect_punct!(args, ';');
85
86        append_as(&mut block, &struct_name, "usize", &func_name, &field_name, op, &ops);
87        append_as(&mut block, &struct_name, "f32", &func_name,  &field_name, op, &ops);
88        append_as(&mut block, &struct_name, "f64", &func_name, &field_name, op, &ops);
89    }
90    block.push('}');
91
92    block.parse().unwrap()
93}
94
95#[proc_macro]
96pub fn impl_from(args: TokenStream) -> TokenStream {
97    let mut args = args.into_iter().peekable();
98
99    let struct_name = unwrap!(args, Ident).to_string();
100    expect_punct!(args, ',');
101
102    let field_name = unwrap!(args, Ident).to_string();
103    expect_punct!(args, ',');
104
105    let mut block = format!("impl {struct_name} {{");
106
107    while args.peek().is_some() {
108        let func_name = unwrap!(args, Ident).to_string();
109        expect_punct!(args, '=');
110
111        let mut ops = vec![];
112        let mut op = '*';
113        while args.peek().unwrap().to_string() != ";" {
114           if !ops.is_empty() {
115               op = unwrap!(args, Punct).as_char();
116           }
117           let Ok(num) = unwrap!(args, Literal).to_string().parse::<usize>() else { err!("Error parsing literal, expected usize") };
118           ops.push(num);
119        }
120        expect_punct!(args, ';');
121
122        append_from(&mut block, &struct_name, &func_name, &field_name, op, &ops);
123    }
124    block.push('}');
125
126    block.parse().unwrap()
127}
128