Skip to main content

puruda_macro/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, Span};
4use quote::quote;
5
6// Helper: generate identifiers for Col1..Col32
7fn col_ident(n: usize) -> Ident {
8    Ident::new(&format!("Col{}", n), Span::call_site())
9}
10fn type_ident(n: usize) -> Ident {
11    Ident::new(&format!("T{}", n), Span::call_site())
12}
13fn field_ident(n: usize) -> Ident {
14    Ident::new(&format!("col_{}", n), Span::call_site())
15}
16fn accessor_ident(n: usize) -> Ident {
17    Ident::new(&format!("c{}", n), Span::call_site())
18}
19fn accessor_mut_ident(n: usize) -> Ident {
20    Ident::new(&format!("c{}_mut", n), Span::call_site())
21}
22fn sort_by_ident(n: usize) -> Ident {
23    Ident::new(&format!("sort_by_c{}", n), Span::call_site())
24}
25
26// =============================================================================
27// multi_col_def!() — generate Col1..Col32 struct definitions
28// =============================================================================
29#[proc_macro]
30pub fn multi_col_def(_item: TokenStream) -> TokenStream {
31    let mut all = proc_macro2::TokenStream::new();
32
33    for n in 1..=32 {
34        let col_name = col_ident(n);
35        let type_params: Vec<_> = (1..=n).map(type_ident).collect();
36        let field_names: Vec<_> = (1..=n).map(field_ident).collect();
37        let type_refs: Vec<_> = (1..=n).map(type_ident).collect();
38
39        let fields = field_names.iter().zip(type_refs.iter()).map(|(f, t)| {
40            quote! { pub #f: #t }
41        });
42
43        let where_clauses = type_params.iter().map(|t| {
44            quote! { #t: Column }
45        });
46
47        let def = quote! {
48            #[derive(Debug, Clone)]
49            pub struct #col_name<#(#type_params),*> where #(#where_clauses),* {
50                pub header: Vec<String>,
51                #(#fields),*
52            }
53        };
54        all.extend(def);
55    }
56
57    all.into()
58}
59
60// =============================================================================
61// multi_col_impl!() — generate impl blocks (new, from_cols, accessors, etc.)
62// =============================================================================
63#[proc_macro]
64pub fn multi_col_impl(_item: TokenStream) -> TokenStream {
65    let mut all = proc_macro2::TokenStream::new();
66
67    for n in 1..=32 {
68        let col_name = col_ident(n);
69        let type_params: Vec<_> = (1..=n).map(type_ident).collect();
70        let field_names: Vec<_> = (1..=n).map(field_ident).collect();
71
72        // where T1: Column + Default, T2: Column + Default, ...
73        let where_clauses = type_params.iter().map(|t| {
74            quote! { #t: Column + Default }
75        });
76
77        // Default field initializers: col_1: T1::default(), ...
78        let default_fields = field_names.iter().zip(type_params.iter()).map(|(f, t)| {
79            quote! { #f: #t::default() }
80        });
81
82        // from_cols parameters: col_1: T1, col_2: T2, ...
83        let from_cols_params = field_names.iter().zip(type_params.iter()).map(|(f, t)| {
84            quote! { #f: #t }
85        });
86
87        // from_cols field assignments: col_1, col_2, ...
88        let from_cols_fields = field_names.iter().map(|f| {
89            quote! { #f }
90        });
91
92        // Accessor methods
93        let accessors = (1..=n).map(|j| {
94            let acc = accessor_ident(j);
95            let acc_mut = accessor_mut_ident(j);
96            let field = field_ident(j);
97            let ty = type_ident(j);
98            quote! {
99                pub fn #acc(&self) -> &#ty {
100                    &self.#field
101                }
102
103                pub fn #acc_mut(&mut self) -> &mut #ty {
104                    &mut self.#field
105                }
106            }
107        });
108
109        let n_lit = proc_macro2::Literal::usize_unsuffixed(n);
110
111        let block = quote! {
112            impl<#(#type_params),*> #col_name<#(#type_params),*>
113            where #(#where_clauses),*
114            {
115                pub fn new() -> Self {
116                    Self {
117                        header: vec![],
118                        #(#default_fields),*
119                    }
120                }
121
122                pub fn set_header(&mut self, header: Vec<&str>) {
123                    self.header = header.into_iter().map(|s| s.to_string()).collect();
124                }
125
126                pub fn header(&self) -> &Vec<String> {
127                    &self.header
128                }
129
130                pub fn from_cols(#(#from_cols_params),*) -> Self {
131                    Self {
132                        header: vec![],
133                        #(#from_cols_fields),*
134                    }
135                }
136
137                #(#accessors)*
138
139                pub fn nrows(&self) -> usize {
140                    self.col_1.row()
141                }
142
143                pub fn ncols(&self) -> usize {
144                    #n_lit
145                }
146
147                pub fn shape(&self) -> (usize, usize) {
148                    (self.nrows(), self.ncols())
149                }
150
151                pub fn len(&self) -> usize {
152                    self.nrows()
153                }
154
155                pub fn is_empty(&self) -> bool {
156                    self.nrows() == 0
157                }
158            }
159        };
160        all.extend(block);
161    }
162
163    all.into()
164}
165
166// =============================================================================
167// multi_col_extra_impl!() — head, tail, slice, filter, push_row,
168//                            append, concat, reindex, sort_by_cN
169// =============================================================================
170#[proc_macro]
171pub fn multi_col_extra_impl(_item: TokenStream) -> TokenStream {
172    let mut all = proc_macro2::TokenStream::new();
173
174    for n in 1..=32 {
175        let col_name = col_ident(n);
176        let type_params: Vec<_> = (1..=n).map(type_ident).collect();
177        let field_names: Vec<_> = (1..=n).map(field_ident).collect();
178
179        // where T1: Column + Default, T1::DType: Clone, Vec<T1::DType>: Into<T1>, ...
180        let where_clauses = type_params.iter().map(|t| {
181            quote! { #t: Column + Default }
182        });
183        let clone_clauses = type_params.iter().map(|t| {
184            quote! { #t::DType: Clone }
185        });
186        let into_clauses = type_params.iter().map(|t| {
187            quote! { Vec<#t::DType>: Into<#t> }
188        });
189
190        // head: self.col_j.to_vec()[..n].to_vec().into()
191        let head_fields = field_names.iter().zip(type_params.iter()).map(|(f, _t)| {
192            quote! {
193                #f: self.#f.to_vec()[..take].to_vec().into()
194            }
195        });
196
197        let tail_fields = field_names.iter().zip(type_params.iter()).map(|(f, _t)| {
198            quote! {
199                #f: self.#f.to_vec()[start..].to_vec().into()
200            }
201        });
202
203        let slice_fields = field_names.iter().zip(type_params.iter()).map(|(f, _t)| {
204            quote! {
205                #f: self.#f.to_vec()[start..end].to_vec().into()
206            }
207        });
208
209        let filter_fields = field_names.iter().zip(type_params.iter()).map(|(f, _t)| {
210            quote! {
211                #f: {
212                    let v = self.#f.to_vec();
213                    indices.iter().map(|&i| v[i].clone()).collect::<Vec<_>>().into()
214                }
215            }
216        });
217
218        // push_row parameters and body
219        let push_params = (1..=n).map(|j| {
220            let vname = Ident::new(&format!("v{}", j), Span::call_site());
221            let ty = type_ident(j);
222            quote! { #vname: #ty::DType }
223        });
224        let push_body = (1..=n).map(|j| {
225            let vname = Ident::new(&format!("v{}", j), Span::call_site());
226            let field = field_ident(j);
227            quote! { self.#field.push(#vname); }
228        });
229
230        // append body
231        let append_body = (1..=n).map(|j| {
232            let field = field_ident(j);
233            quote! {
234                for i in 0..other.#field.row() {
235                    self.#field.push(other.#field.to_vec()[i].clone());
236                }
237            }
238        });
239
240        // concat fields
241        let concat_fields = field_names.iter().map(|f| {
242            quote! {
243                #f: {
244                    let mut v: Vec<_> = self.#f.to_vec().clone();
245                    v.extend(other.#f.to_vec().iter().cloned());
246                    v.into()
247                }
248            }
249        });
250
251        // reindex fields
252        let reindex_fields = field_names.iter().map(|f| {
253            quote! {
254                #f: {
255                    let v = self.#f.to_vec();
256                    indices.iter().map(|&i| v[i].clone()).collect::<Vec<_>>().into()
257                }
258            }
259        });
260
261        // sort_by_cN methods — one per column
262        let sort_methods = (1..=n).map(|j| {
263            let method_name = sort_by_ident(j);
264            let field = field_ident(j);
265            let ty = type_ident(j);
266            quote! {
267                pub fn #method_name(&self) -> Self where #ty::DType: Ord {
268                    let v = self.#field.to_vec();
269                    let mut indices: Vec<usize> = (0..v.len()).collect();
270                    indices.sort_by(|&a, &b| v[a].cmp(&v[b]));
271                    self.reindex(&indices)
272                }
273            }
274        });
275
276        let block = quote! {
277            impl<#(#type_params),*> #col_name<#(#type_params),*>
278            where
279                #(#where_clauses,)*
280                #(#clone_clauses,)*
281                #(#into_clauses,)*
282            {
283                pub fn head(&self, n: usize) -> Self {
284                    let take = n.min(self.nrows());
285                    Self {
286                        header: self.header.clone(),
287                        #(#head_fields),*
288                    }
289                }
290
291                pub fn tail(&self, n: usize) -> Self {
292                    let rows = self.nrows();
293                    let start = rows.saturating_sub(n);
294                    Self {
295                        header: self.header.clone(),
296                        #(#tail_fields),*
297                    }
298                }
299
300                pub fn slice(&self, start: usize, end: usize) -> Self {
301                    let end = end.min(self.nrows());
302                    Self {
303                        header: self.header.clone(),
304                        #(#slice_fields),*
305                    }
306                }
307
308                pub fn filter<F: Fn(usize) -> bool>(&self, predicate: F) -> Self {
309                    let indices: Vec<usize> = (0..self.nrows())
310                        .filter(|&i| predicate(i))
311                        .collect();
312                    Self {
313                        header: self.header.clone(),
314                        #(#filter_fields),*
315                    }
316                }
317
318                pub fn push_row(&mut self, #(#push_params),*) {
319                    #(#push_body)*
320                }
321
322                pub fn append(&mut self, other: &Self) {
323                    #(#append_body)*
324                }
325
326                pub fn concat(self, other: Self) -> Self {
327                    Self {
328                        header: self.header.clone(),
329                        #(#concat_fields),*
330                    }
331                }
332
333                pub fn reindex(&self, indices: &[usize]) -> Self {
334                    Self {
335                        header: self.header.clone(),
336                        #(#reindex_fields),*
337                    }
338                }
339
340                #(#sort_methods)*
341            }
342        };
343        all.extend(block);
344    }
345
346    all.into()
347}
348
349// =============================================================================
350// multi_col_display_impl!() — Display trait for ColN
351// =============================================================================
352#[proc_macro]
353pub fn multi_col_display_impl(_item: TokenStream) -> TokenStream {
354    let mut all = proc_macro2::TokenStream::new();
355
356    for n in 1..=32 {
357        let col_name = col_ident(n);
358        let type_params: Vec<_> = (1..=n).map(type_ident).collect();
359
360        let where_clauses = type_params.iter().map(|t| {
361            quote! { #t: Column }
362        });
363        let display_clauses = type_params.iter().map(|t| {
364            quote! { #t::DType: std::fmt::Display }
365        });
366
367        let col_indices: Vec<usize> = (1..=n).collect();
368        let field_names_for_width: Vec<_> = (1..=n).map(field_ident).collect();
369
370        // Build width calculations and row formatting
371        let width_calcs = field_names_for_width.iter().enumerate().map(|(idx, f)| {
372            let idx_lit = proc_macro2::Literal::usize_unsuffixed(idx);
373            quote! {
374                let mut w = if #idx_lit < self.header.len() {
375                    self.header[#idx_lit].len()
376                } else {
377                    0
378                };
379                for i in 0..rows {
380                    let s = format!("{}", self.#f.idx(i));
381                    if s.len() > w { w = s.len(); }
382                }
383                widths.push(w);
384            }
385        });
386
387        let header_cells = col_indices.iter().map(|&j| {
388            let idx = j - 1;
389            let idx_lit = proc_macro2::Literal::usize_unsuffixed(idx);
390            quote! {
391                let name = if #idx_lit < self.header.len() {
392                    &self.header[#idx_lit]
393                } else {
394                    ""
395                };
396                write!(f, "{:>width$}", name, width = widths[#idx_lit])?;
397                if #idx_lit < widths.len() - 1 {
398                    write!(f, "  ")?;
399                }
400            }
401        });
402
403        let sep_cells = col_indices.iter().map(|&j| {
404            let idx = j - 1;
405            let idx_lit = proc_macro2::Literal::usize_unsuffixed(idx);
406            quote! {
407                write!(f, "{:->width$}", "", width = widths[#idx_lit])?;
408                if #idx_lit < widths.len() - 1 {
409                    write!(f, "  ")?;
410                }
411            }
412        });
413
414        let data_cells = field_names_for_width.iter().enumerate().map(|(idx, f)| {
415            let idx_lit = proc_macro2::Literal::usize_unsuffixed(idx);
416            let n_lit = proc_macro2::Literal::usize_unsuffixed(n);
417            quote! {
418                write!(f, "{:>width$}", format!("{}", self.#f.idx(i)), width = widths[#idx_lit])?;
419                if #idx_lit < #n_lit - 1 {
420                    write!(f, "  ")?;
421                }
422            }
423        });
424
425        let block = quote! {
426            impl<#(#type_params),*> std::fmt::Display for #col_name<#(#type_params),*>
427            where
428                #(#where_clauses,)*
429                #(#display_clauses,)*
430            {
431                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432                    let rows = self.col_1.row();
433                    let mut widths: Vec<usize> = Vec::new();
434                    #(#width_calcs)*
435
436                    // Header
437                    #(#header_cells)*
438                    writeln!(f)?;
439
440                    // Separator
441                    #(#sep_cells)*
442                    writeln!(f)?;
443
444                    // Data rows
445                    for i in 0..rows {
446                        #(#data_cells)*
447                        if i < rows - 1 {
448                            writeln!(f)?;
449                        }
450                    }
451
452                    Ok(())
453                }
454            }
455        };
456        all.extend(block);
457    }
458
459    all.into()
460}
461
462// =============================================================================
463// col_vec_impl!() — Column trait for Vec<T>
464// =============================================================================
465#[proc_macro]
466pub fn col_vec_impl(item: TokenStream) -> TokenStream {
467    let ty: proc_macro2::TokenStream = item.into();
468
469    let expanded = quote! {
470        impl Column for Vec<#ty> {
471            type DType = #ty;
472
473            fn row(&self) -> usize {
474                self.len()
475            }
476
477            fn idx(&self, n: usize) -> &Self::DType {
478                &self[n]
479            }
480
481            fn idx_mut(&mut self, n: usize) -> &mut Self::DType {
482                &mut self[n]
483            }
484
485            fn to_vec(&self) -> &Vec<Self::DType> {
486                &self
487            }
488
489            fn push(&mut self, val: Self::DType) {
490                Vec::push(self, val);
491            }
492        }
493
494        impl ColumnApply for Vec<#ty> {
495            fn apply<F: FnMut(&mut Self::DType)>(&mut self, mut f: F) {
496                for item in self.iter_mut() {
497                    f(item);
498                }
499            }
500        }
501    };
502
503    expanded.into()
504}
505
506// =============================================================================
507// multi_col_csv_impl!() — CSV trait implementation for ColN
508// =============================================================================
509#[proc_macro]
510pub fn multi_col_csv_impl(_item: TokenStream) -> TokenStream {
511    let mut all = proc_macro2::TokenStream::new();
512
513    for n in 1..=32 {
514        let col_name = col_ident(n);
515        let type_params: Vec<_> = (1..=n).map(type_ident).collect();
516
517        let where_default = type_params.iter().map(|t| {
518            quote! { #t: Column + Default }
519        });
520        let where_tostr = type_params.iter().map(|t| {
521            quote! { #t::DType: ToString + FromStr }
522        });
523        let where_err = type_params.iter().map(|t| {
524            quote! { <#t::DType as FromStr>::Err: std::fmt::Debug + Error }
525        });
526        let where_into = type_params.iter().map(|t| {
527            quote! { Vec<#t::DType>: Into<#t> }
528        });
529
530        // write_csv: let c1 = self.c1(); ...
531        let write_lets = (1..=n).map(|j| {
532            let acc = accessor_ident(j);
533            let var = Ident::new(&format!("c{}", j), Span::call_site());
534            quote! { let #var = self.#acc(); }
535        });
536
537        // write_csv: record[0] = c1.idx(i).to_string(); ...
538        let write_records = (1..=n).map(|j| {
539            let var = Ident::new(&format!("c{}", j), Span::call_site());
540            let idx_lit = proc_macro2::Literal::usize_unsuffixed(j - 1);
541            quote! { record[#idx_lit] = #var.idx(i).to_string(); }
542        });
543
544        // read_csv: let mut c1: Vec<T1::DType> = vec![]; ...
545        let read_decls = (1..=n).map(|j| {
546            let var = Ident::new(&format!("c{}", j), Span::call_site());
547            let ty = type_ident(j);
548            quote! { let mut #var: Vec<#ty::DType> = vec![]; }
549        });
550
551        // read_csv: c1.push(rec[0].parse().unwrap()); ...
552        let read_pushes = (1..=n).map(|j| {
553            let var = Ident::new(&format!("c{}", j), Span::call_site());
554            let idx_lit = proc_macro2::Literal::usize_unsuffixed(j - 1);
555            quote! { #var.push(rec[#idx_lit].parse().unwrap()); }
556        });
557
558        // from_cols args: c1.into(), c2.into(), ...
559        let from_cols_args = (1..=n).map(|j| {
560            let var = Ident::new(&format!("c{}", j), Span::call_site());
561            quote! { #var.into() }
562        });
563
564        // Default header names: "c1", "c2", ...
565        let header_names = (1..=n).map(|j| {
566            let s = format!("c{}", j);
567            quote! { #s }
568        });
569
570        let n_lit = proc_macro2::Literal::usize_unsuffixed(n);
571
572        let block = quote! {
573            impl<#(#type_params),*> CSV for #col_name<#(#type_params),*>
574            where
575                #(#where_default,)*
576                #(#where_tostr,)*
577                #(#where_err,)*
578                #(#where_into,)*
579            {
580                fn write_csv(&self, file_path: &str, delimiter: char) -> Result<(), Box<dyn Error>> {
581                    let mut wtr = WriterBuilder::new()
582                        .delimiter(delimiter as u8)
583                        .from_path(file_path)?;
584                    #(#write_lets)*
585                    let r: usize = self.col_1.row();
586                    let c: usize = #n_lit;
587
588                    wtr.write_record(self.header())?;
589
590                    for i in 0..r {
591                        let mut record: Vec<String> = vec!["".to_string(); c];
592                        #(#write_records)*
593                        wtr.write_record(record)?;
594                    }
595                    wtr.flush()?;
596                    Ok(())
597                }
598
599                fn read_csv(file_path: &str, delimiter: char) -> Result<Self, Box<dyn Error>> {
600                    let mut rdr = ReaderBuilder::new()
601                        .has_headers(true)
602                        .delimiter(delimiter as u8)
603                        .trim(Trim::All)
604                        .from_path(file_path)?;
605
606                    #(#read_decls)*
607
608                    for rec in rdr.records() {
609                        let rec = rec?;
610                        #(#read_pushes)*
611                    }
612
613                    let mut col = #col_name::from_cols(#(#from_cols_args),*);
614                    col.set_header(vec![#(#header_names),*]);
615
616                    Ok(col)
617                }
618            }
619        };
620        all.extend(block);
621    }
622
623    all.into()
624}
625
626// =============================================================================
627// multi_col_json_impl!() — JSON I/O for ColN
628// =============================================================================
629#[proc_macro]
630pub fn multi_col_json_impl(_item: TokenStream) -> TokenStream {
631    let mut all = proc_macro2::TokenStream::new();
632
633    for n in 1..=32 {
634        let col_name = col_ident(n);
635        let type_params: Vec<_> = (1..=n).map(type_ident).collect();
636        let field_names: Vec<_> = (1..=n).map(field_ident).collect();
637
638        let where_default = type_params.iter().map(|t| {
639            quote! { #t: Column + Default }
640        });
641        let where_display = type_params.iter().map(|t| {
642            quote! { #t::DType: std::fmt::Display + FromStr }
643        });
644        let where_err = type_params.iter().map(|t| {
645            quote! { <#t::DType as FromStr>::Err: std::fmt::Debug + Error }
646        });
647        let where_into = type_params.iter().map(|t| {
648            quote! { Vec<#t::DType>: Into<#t> }
649        });
650
651        // to_json_string: serialize each column as JSON array
652        let json_col_writes = field_names.iter().enumerate().map(|(idx, f)| {
653            let idx_lit = proc_macro2::Literal::usize_unsuffixed(idx);
654            let n_lit = proc_macro2::Literal::usize_unsuffixed(n);
655            quote! {
656                {
657                    let name = if #idx_lit < self.header.len() {
658                        &self.header[#idx_lit]
659                    } else {
660                        ""
661                    };
662                    s.push_str(&format!("    \"{}\": [", name));
663                    let v = self.#f.to_vec();
664                    for i in 0..v.len() {
665                        let val_str = format!("{}", v[i]);
666                        // Try to parse as number; if not, quote it
667                        if val_str.parse::<f64>().is_ok() {
668                            s.push_str(&val_str);
669                        } else if val_str == "true" || val_str == "false" {
670                            s.push_str(&val_str);
671                        } else {
672                            s.push_str(&format!("\"{}\"", val_str));
673                        }
674                        if i < v.len() - 1 {
675                            s.push_str(", ");
676                        }
677                    }
678                    s.push(']');
679                    if #idx_lit < #n_lit - 1 {
680                        s.push(',');
681                    }
682                    s.push('\n');
683                }
684            }
685        });
686
687        // read_json: parse each column
688        let read_decls = (1..=n).map(|j| {
689            let var = Ident::new(&format!("c{}", j), Span::call_site());
690            let ty = type_ident(j);
691            quote! { let mut #var: Vec<#ty::DType> = vec![]; }
692        });
693
694        let read_fills = field_names.iter().enumerate().map(|(idx, _f)| {
695            let idx_lit = proc_macro2::Literal::usize_unsuffixed(idx);
696            let var = Ident::new(&format!("c{}", idx + 1), Span::call_site());
697            quote! {
698                if #idx_lit < headers.len() {
699                    if let Some(arr) = data.get(&headers[#idx_lit]) {
700                        for val in arr {
701                            #var.push(val.parse().map_err(|e| format!("Parse error: {:?}", e))?);
702                        }
703                    }
704                }
705            }
706        });
707
708        let from_cols_args = (1..=n).map(|j| {
709            let var = Ident::new(&format!("c{}", j), Span::call_site());
710            quote! { #var.into() }
711        });
712
713        let block = quote! {
714            impl<#(#type_params),*> JsonIO for #col_name<#(#type_params),*>
715            where
716                #(#where_default,)*
717                #(#where_display,)*
718                #(#where_err,)*
719                #(#where_into,)*
720            {
721                fn to_json_string(&self) -> String {
722                    let mut s = String::from("{\n");
723
724                    // headers
725                    s.push_str("  \"headers\": [");
726                    for (i, h) in self.header.iter().enumerate() {
727                        s.push_str(&format!("\"{}\"", h));
728                        if i < self.header.len() - 1 {
729                            s.push_str(", ");
730                        }
731                    }
732                    s.push_str("],\n");
733
734                    // data
735                    s.push_str("  \"data\": {\n");
736                    #(#json_col_writes)*
737                    s.push_str("  }\n");
738                    s.push('}');
739                    s
740                }
741
742                fn write_json(&self, file_path: &str) -> Result<(), Box<dyn Error>> {
743                    std::fs::write(file_path, self.to_json_string())?;
744                    Ok(())
745                }
746
747                fn read_json(file_path: &str) -> Result<Self, Box<dyn Error>> {
748                    let content = std::fs::read_to_string(file_path)?;
749                    Self::from_json_string(&content)
750                }
751
752                fn from_json_string(s: &str) -> Result<Self, Box<dyn Error>> {
753                    let (headers, data) = parse_puruda_json(s)?;
754
755                    #(#read_decls)*
756
757                    #(#read_fills)*
758
759                    let mut col = #col_name::from_cols(#(#from_cols_args),*);
760                    let header_refs: Vec<&str> = headers.iter().map(|s| s.as_str()).collect();
761                    col.set_header(header_refs);
762
763                    Ok(col)
764                }
765            }
766        };
767        all.extend(block);
768    }
769
770    all.into()
771}
772
773// =============================================================================
774// multi_col_describe_impl!() — describe() method for ColN
775// =============================================================================
776#[proc_macro]
777pub fn multi_col_describe_impl(_item: TokenStream) -> TokenStream {
778    let mut all = proc_macro2::TokenStream::new();
779
780    for n in 1..=32 {
781        let col_name = col_ident(n);
782        let type_params: Vec<_> = (1..=n).map(type_ident).collect();
783
784        let where_clauses = type_params.iter().map(|t| {
785            quote! { #t: Column + Default }
786        });
787        let display_clauses = type_params.iter().map(|t| {
788            quote! { #t::DType: std::fmt::Display }
789        });
790
791        let describe_cols = (1..=n).map(|j| {
792            let field = field_ident(j);
793            let idx_lit = proc_macro2::Literal::usize_unsuffixed(j - 1);
794            quote! {
795                {
796                    let name = if #idx_lit < self.header.len() {
797                        self.header[#idx_lit].clone()
798                    } else {
799                        format!("col_{}", #idx_lit + 1)
800                    };
801                    let count = self.#field.row();
802                    summaries.push((name, count));
803                }
804            }
805        });
806
807        let block = quote! {
808            impl<#(#type_params),*> #col_name<#(#type_params),*>
809            where
810                #(#where_clauses,)*
811                #(#display_clauses,)*
812            {
813                pub fn describe(&self) {
814                    let mut summaries: Vec<(String, usize)> = Vec::new();
815                    #(#describe_cols)*
816
817                    // Print describe table
818                    let mut max_name = 4usize; // "Name"
819                    for (name, _) in &summaries {
820                        if name.len() > max_name { max_name = name.len(); }
821                    }
822                    let count_width = 5; // "Count"
823
824                    println!("{:>nw$}  {:>cw$}", "Name", "Count", nw = max_name, cw = count_width);
825                    println!("{:->nw$}  {:->cw$}", "", "", nw = max_name, cw = count_width);
826                    for (name, count) in &summaries {
827                        println!("{:>nw$}  {:>cw$}", name, count, nw = max_name, cw = count_width);
828                    }
829                }
830            }
831        };
832        all.extend(block);
833    }
834
835    all.into()
836}