1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::parse::{Parse, ParseStream};
6use syn::{parse_macro_input, Expr, Ident, LitInt, Token, Visibility, Type};
7
8struct PackTableInput {
19 vis: Visibility,
20 fn_name: Ident,
21 _arg_name: Ident,
22 _ret_type: Type,
23 data: Vec<i64>,
24 default: Option<i64>,
25 compression: f64,
26 unsafe_access: bool,
27}
28
29impl Parse for PackTableInput {
30 fn parse(input: ParseStream) -> syn::Result<Self> {
31 let vis: Visibility = input.parse()?;
32 input.parse::<Token![fn]>()?;
33 let fn_name: Ident = input.parse()?;
34
35 let paren_content;
36 syn::parenthesized!(paren_content in input);
37 let arg_name: Ident = paren_content.parse()?;
38 paren_content.parse::<Token![:]>()?;
39 let _arg_type: Type = paren_content.parse()?;
40
41 input.parse::<Token![->]>()?;
42 let ret_type: Type = input.parse()?;
43
44 let brace_content;
45 syn::braced!(brace_content in input);
46
47 let data_ident: Ident = brace_content.parse()?;
49 if data_ident != "data" {
50 return Err(syn::Error::new_spanned(data_ident, "expected 'data'"));
51 }
52 brace_content.parse::<Token![:]>()?;
53
54 let bracket_content;
55 syn::bracketed!(bracket_content in brace_content);
56 let mut data = Vec::new();
57 while !bracket_content.is_empty() {
58 if bracket_content.peek(Token![-]) {
59 bracket_content.parse::<Token![-]>()?;
60 let lit: LitInt = bracket_content.parse()?;
61 data.push(-(lit.base10_parse::<i64>()?));
62 } else {
63 let lit: LitInt = bracket_content.parse()?;
64 data.push(lit.base10_parse::<i64>()?);
65 }
66 if bracket_content.peek(Token![,]) {
67 bracket_content.parse::<Token![,]>()?;
68 }
69 }
70 brace_content.parse::<Token![,]>()?;
71
72 let mut default = None;
74 if !brace_content.is_empty() && !brace_content.peek(Token![,]) {
75 let ident: Ident = brace_content.parse()?;
76 if ident != "default" {
77 return Err(syn::Error::new_spanned(ident, "expected 'default'"));
78 }
79 brace_content.parse::<Token![:]>()?;
80 default = Some(if brace_content.peek(Token![-]) {
81 brace_content.parse::<Token![-]>()?;
82 let lit: LitInt = brace_content.parse()?;
83 -(lit.base10_parse::<i64>()?)
84 } else {
85 let lit: LitInt = brace_content.parse()?;
86 lit.base10_parse::<i64>()?
87 });
88 }
89
90 let mut compression = 1.0f64;
92 let mut unsafe_access = false;
93 while brace_content.peek(Token![,]) {
94 brace_content.parse::<Token![,]>()?;
95 if brace_content.is_empty() {
96 break;
97 }
98 if brace_content.peek(Token![unsafe]) {
99 let kw: Token![unsafe] = brace_content.parse()?;
100 brace_content.parse::<Token![:]>()?;
101 let lit: syn::LitBool = brace_content.parse()
102 .map_err(|_| syn::Error::new_spanned(kw, "expected bool after 'unsafe:'"))?;
103 unsafe_access = lit.value;
104 } else {
105 let ident: Ident = brace_content.parse()?;
106 match ident.to_string().as_str() {
107 "compression" => {
108 brace_content.parse::<Token![:]>()?;
109 let expr: Expr = brace_content.parse()?;
110 compression = match &expr {
111 Expr::Lit(lit) => match &lit.lit {
112 syn::Lit::Float(f) => f.base10_parse::<f64>()?,
113 syn::Lit::Int(i) => i.base10_parse::<f64>()?,
114 _ => return Err(syn::Error::new_spanned(lit, "expected number")),
115 },
116 _ => return Err(syn::Error::new_spanned(expr, "expected number literal")),
117 };
118 }
119 _ => return Err(syn::Error::new_spanned(ident, "expected 'compression' or 'unsafe'")),
120 }
121 }
122 }
123
124 Ok(PackTableInput {
125 vis,
126 fn_name,
127 _arg_name: arg_name,
128 _ret_type: ret_type,
129 data,
130 default,
131 compression,
132 unsafe_access,
133 })
134 }
135}
136
137#[proc_macro]
150pub fn pack_table(input: TokenStream) -> TokenStream {
151 let input = parse_macro_input!(input as PackTableInput);
152
153 let (info, best_idx) = packtab::pack_table(&input.data, input.default, input.compression);
154 let code_str = packtab::generate(
155 &info,
156 best_idx,
157 &input.fn_name.to_string(),
158 packtab::codegen::Language::Rust { unsafe_access: input.unsafe_access },
159 );
160
161 let vis_str = match &input.vis {
163 Visibility::Public(_) => "pub",
164 Visibility::Inherited => "",
165 _ => "pub(crate)",
166 };
167
168 let fn_name_str = input.fn_name.to_string();
169 let adjusted = code_str.replace(
170 &format!("pub(crate) fn {}_get", fn_name_str),
171 &format!("{} fn {}", vis_str, fn_name_str),
172 );
173 let adjusted = adjusted.replace(
175 &format!("{}_get", fn_name_str),
176 &fn_name_str,
177 );
178
179 let generated: proc_macro2::TokenStream = adjusted
180 .parse()
181 .unwrap_or_else(|e| panic!("Failed to parse generated code: {}\n\nCode:\n{}", e, adjusted));
182
183 let output = quote! {
184 #generated
185 };
186
187 output.into()
188}