remdb_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{quote, quote_spanned};
4use syn::spanned::Spanned;
5use syn::Token;
6
7/// Proc-macro for table configuration
8#[proc_macro]
9pub fn table(input: TokenStream) -> TokenStream {
10    let parsed = syn::parse_macro_input!(input as TableInput);
11    parsed.generate().into()
12}
13
14/// Proc-macro for database configuration
15#[proc_macro]
16pub fn database(input: TokenStream) -> TokenStream {
17    let parsed = syn::parse_macro_input!(input as DatabaseInput);
18    parsed.generate().into()
19}
20
21// Table macro input structure
22struct TableInput {
23    table_name: syn::Ident,
24    max_records: syn::Expr,
25    primary_key: syn::Ident,
26    secondary_index: Option<syn::Ident>,
27    fields: Vec<FieldDef>,
28}
29
30impl syn::parse::Parse for TableInput {
31    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
32        // Parse table name: `TEST_TABLE`
33        let table_name: syn::Ident = input.parse()?;
34        input.parse::<Token![,]>()?;
35        
36        // Parse max records: `100`
37        let max_records: syn::Expr = input.parse()?;
38        input.parse::<Token![,]>()?;
39        
40        // Parse primary key: `primary_key: id`
41        let _: syn::Ident = input.parse()?; // "primary_key"
42        input.parse::<Token![:]>()?;
43        let primary_key: syn::Ident = input.parse()?;
44        input.parse::<Token![,]>()?;
45        
46        // Parse secondary index if present: `secondary_index: name`
47        let mut secondary_index = None;
48        if input.peek(syn::Ident) {
49            let fork = input.fork();
50            let ident: syn::Ident = fork.parse()?;
51            
52            if ident.to_string() == "secondary_index" {
53                let _: syn::Ident = input.parse()?; // "secondary_index"
54                input.parse::<Token![:]>()?;
55                secondary_index = Some(input.parse()?);
56                input.parse::<Token![,]>()?;
57            }
58        }
59        
60        // Parse fields: `fields: { ... }`
61        let _: syn::Ident = input.parse()?; // "fields"
62        input.parse::<Token![:]>()?;
63        
64        // Parse opening brace
65        let content;
66        let _braces = syn::braced!(content in input);
67        
68        // Parse field definitions
69        let mut fields = Vec::new();
70        
71        // Simple field parsing loop
72        while !content.is_empty() {
73            // Parse a single field
74            let field: FieldDef = content.parse()?;
75            fields.push(field);
76            
77            // Skip trailing comma if present
78            if content.peek(Token![,]) {
79                content.parse::<Token![,]>()?;
80            }
81            
82            // Exit if we've reached the end
83            if content.is_empty() {
84                break;
85            }
86        }
87        
88        Ok(Self {
89            table_name,
90            max_records,
91            primary_key,
92            secondary_index,
93            fields,
94        })
95    }
96}
97
98impl TableInput {
99    fn generate(&self) -> proc_macro2::TokenStream {
100        let table_name = &self.table_name;
101        let max_records = &self.max_records;
102        
103        // Calculate field offsets and record size
104        let mut current_offset = 0usize;
105        let mut total_record_size = 0usize;
106        let mut primary_key_index = None;
107        let mut secondary_index_index = None;
108        
109        // Generate field definitions with correct offsets
110        let mut field_defs = Vec::new();
111        
112        for (index, field) in self.fields.iter().enumerate() {
113            let name = &field.name;
114            let data_type = field.data_type.clone();
115            let size = &field.size;
116            
117            // Check if this is the primary key
118            if field.name == self.primary_key {
119                primary_key_index = Some(index);
120            }
121            
122            // Check if this is the secondary index
123            if let Some(secondary_key) = &self.secondary_index {
124                if field.name == *secondary_key {
125                    secondary_index_index = Some(index);
126                }
127            }
128            
129            // For each field, calculate its size and offset
130            if field.data_type.to_string() == "String" {
131                // Handle string type: str(32)
132                // size is an expression like 32
133                let string_size = match size {
134                    syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int_lit), .. }) => {
135                        int_lit.base10_parse::<usize>().unwrap()
136                    },
137                    _ => panic!("String field size must be a literal integer"),
138                };
139                
140                field_defs.push(quote_spanned! {field.name.span() =>
141                    remdb::types::FieldDef {
142                        name: stringify!(#name),
143                        data_type: remdb::types::DataType::#data_type,
144                        size: #string_size,
145                        offset: #current_offset,
146                    }
147                });
148                
149                current_offset += string_size;
150                total_record_size += string_size;
151            } else {
152                // Handle numeric types
153                let type_size = match field.data_type.to_string().as_str() {
154                    "Int8" => 1usize,
155                    "Int16" => 2usize,
156                    "Int32" => 4usize,
157                    "Int64" => 8usize,
158                    "Float32" => 4usize,
159                    "Float64" => 8usize,
160                    "Bool" => 1usize,
161                    "Timestamp" => 8usize,
162                    _ => panic!("Unsupported data type: {}", field.data_type),
163                };
164                
165                field_defs.push(quote_spanned! {field.name.span() =>
166                    remdb::types::FieldDef {
167                        name: stringify!(#name),
168                        data_type: remdb::types::DataType::#data_type,
169                        size: #type_size,
170                        offset: #current_offset,
171                    }
172                });
173                
174                current_offset += type_size;
175                total_record_size += type_size;
176            }
177        }
178        
179        let primary_key = primary_key_index.unwrap_or(0);
180        let secondary_index = match secondary_index_index {
181            Some(index) => quote! {Some(#index)},
182            None => quote! {None},
183        };
184        
185        // Debug: Check if field_defs is empty
186        if field_defs.is_empty() {
187            panic!("field_defs is empty! No fields were generated!");
188        }
189        
190        // Debug: Print how many fields were generated
191        println!("Generated {} fields for table {}", field_defs.len(), table_name);
192        
193        quote! {
194            static #table_name: remdb::types::TableDef = remdb::types::TableDef {
195                id: 0,
196                name: stringify!(#table_name),
197                fields: &[
198                    #(#field_defs),*
199                ],
200                primary_key: #primary_key,
201                secondary_index: #secondary_index,
202                record_size: #total_record_size as usize,
203                max_records: #max_records,
204            };
205            
206            // Compile-time assertion that the fields array is not empty
207            const _: () = assert!(#table_name.fields.len() > 0, "Fields array is empty!");
208        }
209    }
210}
211
212// Field definition structure
213struct FieldDef {
214    name: syn::Ident,
215    data_type: syn::Ident,
216    size: syn::Expr,
217}
218
219impl syn::parse::Parse for FieldDef {
220    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
221        // Parse field name
222        let name: syn::Ident = input.parse()?;
223        let name_span = name.span();
224        
225        // Parse colon
226        input.parse::<Token![:]>()?;
227        
228        // We need to handle two cases: string types and numeric/bool types
229        
230        // Case 1: String type (str(20))
231        if input.peek(syn::Ident) {
232            let fork = input.fork();
233            let ident: syn::Ident = fork.parse()?;
234            
235            if ident.to_string() == "str" && fork.peek(syn::token::Paren) {
236                // It's a string type: str(20)
237                let _: syn::Ident = input.parse()?; // Consume "str"
238                let content;
239                syn::parenthesized!(content in input);
240                let len: syn::Expr = content.parse()?;
241                
242                return Ok(Self {
243                    name,
244                    data_type: syn::Ident::new("String", name_span),
245                    size: len,
246                });
247            }
248        }
249        
250        // Case 2: Numeric or bool type (i64, i32, bool, etc.)
251        // Instead of trying to parse as Ident, we'll read tokens as strings
252        let mut type_str = String::new();
253        let mut is_done = false;
254        
255        // We'll use a loop to read tokens until we hit a comma or closing brace
256        while !is_done && !input.is_empty() {
257            // Check if we're at the end of the field
258            if input.peek(Token![,]) || input.peek(syn::token::Brace) {
259                is_done = true;
260                break;
261            }
262            
263            // Read the next token as a string
264            let token = input.step(|cursor| {
265                let (token, rest) = cursor.token_tree()
266                    .ok_or_else(|| syn::Error::new(cursor.span(), "Expected token"))?;
267                Ok((token.to_string(), rest))
268            })?;
269            
270            type_str.push_str(&token);
271        }
272        
273        type_str = type_str.trim().to_string();
274        
275        if type_str.is_empty() {
276            return Err(syn::Error::new(input.span(), "Expected field type"));
277        }
278        
279        // Map the type string to DataType and size
280        let (data_type, size) = match type_str.as_str() {
281            "i8" => ("Int8", "1"),
282            "i16" => ("Int16", "2"),
283            "i32" => ("Int32", "4"),
284            "i64" => ("Int64", "8"),
285            "f32" => ("Float32", "4"),
286            "f64" => ("Float64", "8"),
287            "bool" => ("Bool", "1"),
288            "u64" => ("Timestamp", "8"),
289            _ => {
290                return Err(syn::Error::new(
291                    input.span(),
292                    format!("Unsupported field type: {}", type_str),
293                ));
294            }
295        };
296        
297        Ok(Self {
298            name,
299            data_type: syn::Ident::new(data_type, name_span),
300            size: syn::parse_str(size)?,
301        })
302    }
303}
304
305
306
307// Database macro input structure
308struct DatabaseInput {
309    db_name: syn::Ident,
310    tables: Vec<syn::Ident>,
311    low_power: bool,
312    low_power_max_records: Option<usize>,
313}
314
315impl syn::parse::Parse for DatabaseInput {
316    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
317        let db_name: syn::Ident = input.parse()?;
318        input.parse::<Token![,]>()?;
319        
320        // Parse tables as identifier and verify
321        let tables_keyword: syn::Ident = input.parse()?;
322        if tables_keyword.to_string() != "tables" {
323            return Err(syn::Error::new(
324                tables_keyword.span(),
325                "expected 'tables'",
326            ));
327        }
328        input.parse::<Token![:]>()?;
329        
330        // Parse opening bracket
331        let content;
332        syn::bracketed!(content in input);
333        
334        let mut tables = Vec::new();
335        let mut content = content;
336        while !content.is_empty() {
337            tables.push(content.parse()?);
338            if !content.is_empty() {
339                content.parse::<Token![,]>()?;
340            }
341        }
342        
343        let mut low_power = false;
344        let mut low_power_max_records = None;
345        
346        // Parse optional parameters
347        while !input.is_empty() {
348            // Check if we have a comma before the next parameter
349            if input.peek(Token![,]) {
350                input.parse::<Token![,]>()?;
351            }
352            
353            // Check if we've reached the end of the macro input
354            if input.is_empty() {
355                break;
356            }
357            
358            let param_name: syn::Ident = input.parse()?;
359            input.parse::<Token![:]>()?;
360            
361            match param_name.to_string().as_str() {
362                "low_power" => {
363                    let lit: syn::LitBool = input.parse()?;
364                    low_power = lit.value;
365                },
366                "low_power_max_records" => {
367                    let lit: syn::LitInt = input.parse()?;
368                    let value = lit.base10_parse::<usize>().unwrap();
369                    low_power_max_records = Some(value);
370                },
371                _ => {
372                    return Err(syn::Error::new(
373                        param_name.span(),
374                        format!("Unexpected parameter: {}", param_name),
375                    ));
376                }
377            }
378        }
379        
380        Ok(Self {
381            db_name,
382            tables,
383            low_power,
384            low_power_max_records,
385        })
386    }
387}
388
389impl DatabaseInput {
390    fn generate(&self) -> proc_macro2::TokenStream {
391        let db_name = &self.db_name;
392        let tables = &self.tables;
393        let low_power = self.low_power;
394        let low_power_max_records = match &self.low_power_max_records {
395            Some(val) => quote! { Some(#val) },
396            None => quote! { None },
397        };
398        
399        quote! {
400            static #db_name: remdb::config::DbConfig = remdb::config::DbConfig {
401                tables: &[
402                    #(#tables),*
403                ],
404                total_memory: 0,
405                low_power_mode_supported: #low_power,
406                low_power_max_records: #low_power_max_records,
407            };
408        }
409    }
410}