openpql_macro/
fn_macro.rs1#![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}