openpql_macro/
fn_macro.rs

1#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
2#![allow(clippy::needless_pass_by_value)]
3#![allow(clippy::missing_panics_doc)]
4#![cfg_attr(coverage_nightly, coverage(off))]
5
6mod codegen;
7mod meta_arg;
8mod meta_attr;
9mod meta_fn;
10mod meta_rtn;
11mod state;
12
13use codegen::generate_pqlfn_impl;
14use meta_arg::ArgMetadata;
15use meta_attr::AttrItems;
16use meta_fn::FnMetadata;
17use meta_rtn::RtnMetadata;
18use proc_macro::TokenStream;
19use quote::{ToTokens, quote};
20use state::{arms, register_match_arm};
21use syn::{
22    FnArg, GenericArgument, Ident, ItemFn, LitStr, PathArguments, ReturnType,
23    Token, Type, TypeBareFn, TypePath,
24    parse::{Parse, ParseStream, Result},
25    parse_quote,
26    punctuated::Punctuated,
27};
28
29type TokenStream2 = proc_macro2::TokenStream;
30
31#[proc_macro_attribute]
32pub fn pqlfn(attr: TokenStream, item: TokenStream) -> TokenStream {
33    let metadata = FnMetadata::new(&attr, &item);
34
35    if !metadata.no_parse {
36        register_match_arm(
37            &metadata.name,
38            &metadata.fn_type,
39            metadata.alias.as_deref(),
40        );
41    }
42
43    let fndef = TokenStream2::from(item);
44    let impl_pqlfn = generate_pqlfn_impl(&metadata);
45
46    let output = quote! {
47        #fndef
48        #impl_pqlfn
49    };
50
51    output.into()
52}
53
54#[proc_macro]
55pub fn pqlfn_fromstr(item: TokenStream) -> TokenStream {
56    let arms: Vec<TokenStream2> = arms()
57        .lock()
58        .unwrap()
59        .iter()
60        .filter_map(|ln| ln.parse().ok())
61        .collect();
62
63    let err: TokenStream2 = item.into();
64
65    quote! {
66        fn from_str(s: &str) -> Result<Self, Self::Err> {
67            match s.to_ascii_lowercase().as_str() {
68                #(#arms,)*
69                _ => #err,
70            }
71        }
72    }
73    .into()
74}
75
76fn typepath_to_id(ty_path: &TypePath) -> Ident {
77    ty_path
78        .path
79        .segments
80        .last()
81        .unwrap_or_else(|| {
82            panic!("{} not supported", ty_path.to_token_stream())
83        })
84        .ident
85        .clone()
86}
87
88fn type_to_id(ty: &Type) -> Ident {
89    fn inner(ty: &Type) -> Option<Ident> {
90        if let Type::Path(TypePath { path, .. }) = ty {
91            path.segments.last().map(|seg| seg.ident.clone())
92        } else {
93            None
94        }
95    }
96
97    inner(ty)
98        .unwrap_or_else(|| panic!("{} not supported", ty.to_token_stream()))
99}
100
101fn id_to_pql_type(id: &Ident) -> TokenStream2 {
102    let str_inner = id.to_string();
103    match str_inner.as_str() {
104        "PQLPlayer" => quote! { PQLType::PLAYER },
105        "PQLStreet" => quote! { PQLType::STREET },
106        "PQLEquity" => quote! { PQLType::EQUITY },
107        "PQLNumeric" => quote! { PQLType::NUMERIC },
108        "PQLCardCount" => quote! { PQLType::CARDCOUNT },
109        "PQLLong" => quote! { PQLType::LONG },
110        "PQLDouble" => quote! { PQLType::DOUBLE },
111        "PQLCard" => quote! { PQLType::CARD },
112        "PQLBoolean" => quote! { PQLType::BOOLEAN },
113        "PQLRankSet" => quote! { PQLType::RANKSET },
114        "PQLRank" => quote! { PQLType::RANK },
115        "PQLFlopHandCategory" => quote! { PQLType::FLOPHANDCATEGORY },
116        "PQLHiRating" => quote! { PQLType::HIRATING },
117        "PQLHandType" => quote! { PQLType::HANDTYPE },
118        "PQLFraction" => quote! { PQLType::FRACTION },
119        "PQLString" => quote! { PQLType::STRING },
120        "PQLRange" => quote! { PQLType::RANGE },
121        "PQLBoardRange" => quote! { PQLType::BOARDRANGE },
122
123        _ => panic!("Unsupported type: {str_inner}"),
124    }
125}
126
127#[cfg(test)]
128#[cfg_attr(coverage_nightly, coverage(off))]
129mod tests {
130    use super::*;
131
132    #[test]
133    #[should_panic(expected = "Unsupported type")]
134    fn test_unknown_type() {
135        let id: Ident = parse_quote!(Unknown);
136        id_to_pql_type(&id);
137    }
138}