osom_lib_primitives_proc_macros/
lib.rs

1//! A private crate that exposes macros for osom_lib_primitives crate.
2#![doc(hidden)]
3#![deny(warnings)]
4#![warn(clippy::all, clippy::pedantic)]
5
6use quote::quote;
7
8/// This macro works as `?` operator, but for `CResult` type.
9///
10/// Use it until `Try` trait is stabilized.
11#[proc_macro]
12pub fn try_unpack(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
13    let expression = match syn::parse::<syn::Expr>(tokens) {
14        Ok(ok) => ok,
15        Err(err) => {
16            return err.into_compile_error().into();
17        }
18    };
19
20    quote! {
21        {
22            use ::osom_lib_primitives::cresult::CResult;
23
24            match { #expression } {
25                CResult::Ok(ok) => ok,
26                CResult::Err(err) => {
27                    return CResult::Err(err);
28                }
29            }
30        }
31    }
32    .into()
33}
34
35/// Creates `Length` out of passed expression. If the expression is a literal
36/// integer value, then this will check the value at compile time instead
37/// of runtime. Otherwise it will produce potential runtime validity checks,
38/// depending on compiler's optimizations.
39#[proc_macro]
40pub fn length(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
41    let expression = match syn::parse::<syn::Expr>(tokens) {
42        Ok(ok) => ok,
43        Err(err) => {
44            return err.into_compile_error().into();
45        }
46    };
47
48    let org_expr = unpack_brackets(&expression);
49    let (sign, expr) = unpack_negative_sign(org_expr);
50
51    match expr {
52        syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
53            syn::Lit::Int(lit_int) => {
54                let Ok(value) = lit_int.base10_parse::<u32>() else {
55                    return quote! {
56                        compile_error!("Invalid literal integer.");
57                    }
58                    .into();
59                };
60
61                if sign < 0 {
62                    return quote! {
63                        compile_error!("Length has to be non-negative.");
64                    }
65                    .into();
66                }
67
68                if value == 0 {
69                    return quote! {
70                        ::osom_lib_primitives::length::Length::ZERO
71                    }
72                    .into();
73                }
74
75                if value == 1 {
76                    return quote! {
77                        ::osom_lib_primitives::length::Length::ONE
78                    }
79                    .into();
80                }
81
82                quote! {
83                    {
84                        const {
85                            match ::osom_lib_primitives::length::Length::try_from_u32(#value) {
86                                Ok(val) => val,
87                                Err(_) => panic!("The literal value is above Length::MAX_LENGTH."),
88                            }
89                        }
90                    }
91                }
92                .into()
93            }
94            _ => quote! {
95                compile_error!("Expected literal integer.");
96            }
97            .into(),
98        },
99        _ => quote! {
100            {
101                ::osom_lib_primitives::length::Length::try_from_u32(#org_expr).unwrap()
102            }
103        }
104        .into(),
105    }
106}
107
108/// Creates `Offset` out of passed expression. If the expression is a literal
109/// integer value, then this will check the value at compile time instead
110/// of runtime. Otherwise it will produce potential runtime validity checks,
111/// depending on compiler's optimizations.
112#[proc_macro]
113pub fn offset(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
114    let expression = match syn::parse::<syn::Expr>(tokens) {
115        Ok(ok) => ok,
116        Err(err) => {
117            return err.into_compile_error().into();
118        }
119    };
120
121    let org_expr = unpack_brackets(&expression);
122    let (sign, expr) = unpack_negative_sign(org_expr);
123
124    match expr {
125        syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
126            syn::Lit::Int(lit_int) => {
127                let Ok(value) = lit_int.base10_parse::<i32>() else {
128                    return quote! {
129                        compile_error!("Invalid literal integer.");
130                    }
131                    .into();
132                };
133
134                let real_value = value * sign;
135
136                if real_value == -1 {
137                    return quote! {
138                        ::osom_lib_primitives::offset::Offset::MINUS_ONE
139                    }
140                    .into();
141                }
142
143                if real_value == 0 {
144                    return quote! {
145                        ::osom_lib_primitives::offset::Offset::ZERO
146                    }
147                    .into();
148                }
149
150                if real_value == 1 {
151                    return quote! {
152                        ::osom_lib_primitives::offset::Offset::ONE
153                    }
154                    .into();
155                }
156
157                quote! {
158                        {
159                            const {
160                                match ::osom_lib_primitives::offset::Offset::try_from_i32(#real_value) {
161                                    Ok(val) => val,
162                                    Err(_) => panic!("The literal value is out of [Offset::MIN_OFFSET..Offset::MAX_OFFSET] range."),
163                                }
164                            }
165                        }
166                    }.into()
167            }
168            _ => quote! {
169                compile_error!("Expected literal integer.");
170            }
171            .into(),
172        },
173        _ => quote! {
174            {
175                ::osom_lib_primitives::offset::Offset::try_from_i32(#org_expr).unwrap()
176            }
177        }
178        .into(),
179    }
180}
181
182fn unpack_brackets(expr: &syn::Expr) -> &syn::Expr {
183    match expr {
184        syn::Expr::Paren(expr_paren) => unpack_brackets(&expr_paren.expr),
185        expr => expr,
186    }
187}
188
189fn unpack_negative_sign(expr: &syn::Expr) -> (i32, &syn::Expr) {
190    match expr {
191        syn::Expr::Unary(expr_unary) => {
192            match expr_unary.op {
193                syn::UnOp::Neg(_) => {}
194                _ => {
195                    panic!("Invalid unary expression, expected either positive or negative integer.");
196                }
197            }
198            (-1, &expr_unary.expr)
199        }
200        expr => (1, expr),
201    }
202}