osom_primitives_proc_macros/
lib.rs

1//! A private crate that exposes macros for osom_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_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 `Offset` 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 runtime validity checks.
38#[proc_macro]
39pub fn offset(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
40    let expression = match syn::parse::<syn::Expr>(tokens) {
41        Ok(ok) => ok,
42        Err(err) => {
43            return err.into_compile_error().into();
44        }
45    };
46
47    let org_expr = unpack_brackets(&expression);
48    let (sign, expr) = unpack_negative_sign(org_expr);
49
50    match expr {
51        syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
52            syn::Lit::Int(lit_int) => {
53                let Ok(value) = lit_int.base10_parse::<i32>() else {
54                    return quote! {
55                        compile_error!("Invalid literal integer.");
56                    }
57                    .into();
58                };
59
60                let real_value = value * sign;
61
62                quote! {
63                        {
64                            const {
65                                match ::osom_primitives::offset::Offset::try_from_i32(#real_value) {
66                                    Ok(val) => val,
67                                    Err(_) => panic!("The literal value is out of [Offset::MIN_OFFSET..Offset::MAX_OFFSET] range."),
68                                }
69                            }
70                        }
71                    }.into()
72            }
73            _ => quote! {
74                compile_error!("Expected literal integer.");
75            }
76            .into(),
77        },
78        _ => quote! {
79            {
80                ::osom_primitives::offset::Offset::try_from_i32(#org_expr).unwrap()
81            }
82        }
83        .into(),
84    }
85}
86
87fn unpack_brackets(expr: &syn::Expr) -> &syn::Expr {
88    match expr {
89        syn::Expr::Paren(expr_paren) => unpack_brackets(&expr_paren.expr),
90        expr => expr,
91    }
92}
93
94fn unpack_negative_sign(expr: &syn::Expr) -> (i32, &syn::Expr) {
95    match expr {
96        syn::Expr::Unary(expr_unary) => {
97            match expr_unary.op {
98                syn::UnOp::Neg(_) => {}
99                _ => {
100                    panic!("Invalid unary expression, expected either positive or negative integer.");
101                }
102            }
103            (-1, &expr_unary.expr)
104        }
105        expr => (1, expr),
106    }
107}