Skip to main content

osom_lib_primitives_proc_macros/
lib.rs

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