pyrs_macros/
lib.rs

1// pyrs/pyrs-macros/src/lib.rs
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, ItemFn, visit_mut::{self, VisitMut}, Expr, ExprCall, ExprPath, Lit, ExprLit};
5use regex::Regex;
6
7struct FStringTransformer;
8
9impl VisitMut for FStringTransformer {
10    fn visit_expr_mut(&mut self, expr: &mut Expr) {
11        if let Expr::Call(ExprCall { func, args, .. }) = expr {
12            if let Expr::Path(ExprPath { path, .. }) = func.as_ref() {
13                if path.segments.len() == 1 &&
14                    (path.segments[0].ident == "println" || path.segments[0].ident == "print") {
15                    if let Some(Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. })) = args.first() {
16                        let template = lit_str.value();
17                        let transformations = extract_and_process_variables(&template);
18                        if !transformations.is_empty() {
19                            let func_name = &path.segments[0].ident;
20                            let mut replacement_code = quote! {
21                                let mut result = String::from(#template);
22                            };
23                            for (original, expr_str, format_spec) in transformations {
24                                // Essayer de parser l'expression, si ça échoue, utiliser une chaîne
25                                let expr_tokens = match expr_str.parse::<proc_macro2::TokenStream>() {
26                                    Ok(tokens) => tokens,
27                                    Err(_) => {
28                                        // Si le parsing échoue, traiter comme une chaîne littérale
29                                        quote! { #expr_str }
30                                    }
31                                };
32
33                                let format_expr = match format_spec.as_deref() {
34                                    Some(":c") => quote! { format!("{:?}", #expr_tokens) },
35                                    Some(":j") => quote! { 
36                                        {
37                                            let debug_str = format!("{:#?}", #expr_tokens);
38                                            pyrs_format_json_clean(&debug_str)
39                                        }
40                                    },
41                                    Some(":.2") => quote! { format!("{:.2}", #expr_tokens) },
42                                    Some(":.6") => quote! { format!("{:.6}", #expr_tokens) },
43                                    Some(":e") => quote! { format!("{:e}", #expr_tokens) },
44                                    Some(":.0") => quote! { format!("{:.0}", #expr_tokens) },
45                                    Some(":04") => quote! { format!("{:04}", #expr_tokens) },
46                                    Some(":x") => quote! { format!("{:x}", #expr_tokens) },
47                                    Some(":X") => quote! { format!("{:X}", #expr_tokens) },
48                                    Some(":b") => quote! { format!("{:b}", #expr_tokens) },
49                                    Some(":o") => quote! { format!("{:o}", #expr_tokens) },
50                                    None => quote! { format!("{}", #expr_tokens) },
51                                    _ => quote! { format!("{}", #expr_tokens) },
52                                };
53                                replacement_code = quote! {
54                                    #replacement_code
55                                    result = result.replace(#original, &#format_expr);
56                                };
57                            }
58                            let new_expr = quote! {
59                                #func_name({
60                                    #replacement_code
61                                    result
62                                })
63                            };
64                            *expr = syn::parse2(new_expr).unwrap();
65                            return;
66                        }
67                    }
68                }
69            }
70        }
71        visit_mut::visit_expr_mut(self, expr);
72    }
73}
74
75fn extract_and_process_variables(template: &str) -> Vec<(String, String, Option<String>)> {
76    // Regex modifiée pour capturer des expressions complexes, pas seulement des identifiants simples
77    let re = Regex::new(r"\{([^:}]+)(:[^}]*)?}").unwrap();
78    let mut transformations = Vec::new();
79    for cap in re.captures_iter(template) {
80        if let Some(full_match) = cap.get(0) {
81            if let Some(expr_match) = cap.get(1) {
82                let original = full_match.as_str().to_string();
83                let expr_str = expr_match.as_str().trim().to_string();
84                let format_spec = cap.get(2).map(|m| m.as_str().to_string());
85                // Éviter les doublons
86                if !transformations.iter().any(|(orig, _, _)| orig == &original) {
87                    transformations.push((original, expr_str, format_spec));
88                }
89            }
90        }
91    }
92    transformations
93}
94
95#[proc_macro_attribute]
96pub fn gui(_args: TokenStream, input: TokenStream) -> TokenStream {
97    let mut input_fn = parse_macro_input!(input as ItemFn);
98    FStringTransformer.visit_item_fn_mut(&mut input_fn);
99    let fn_name = &input_fn.sig.ident;
100    let fn_block = &input_fn.block;
101    let expanded = quote! {
102        #[allow(unused_variables)]
103        fn #fn_name() {
104            fn pyrs_format_json_clean(debug_str: &str) -> String {
105                use regex::Regex;
106                lazy_static::lazy_static! {
107                    static ref SIMPLE_VALUES: Regex = Regex::new(r"^\s*(\d+,?\s*)+$").unwrap();
108                }
109                let lines: Vec<&str> = debug_str.lines().collect();
110                let mut result = Vec::new();
111                let mut i = 0;
112                while i < lines.len() {
113                    let line = lines[i];
114                    let trimmed = line.trim();
115                    if SIMPLE_VALUES.is_match(trimmed) && i + 1 < lines.len() {
116                        let next_line = lines[i + 1].trim();
117                        if SIMPLE_VALUES.is_match(next_line) {
118                            let indent = " ".repeat(line.len() - trimmed.len());
119                            let combined = format!("{}[{} {}]", indent, 
120                                trimmed.replace(",", "").trim(), 
121                                next_line.replace(",", "").trim());
122                            result.push(combined);
123                            i += 2;
124                            continue;
125                        }
126                    }
127                    let cleaned_line = line.replace(",", "");
128                    result.push(cleaned_line);
129                    i += 1;
130                }
131                result.join("\n")
132            }
133            fn println<T: std::fmt::Display>(text: T) { pyrs::print::println_str(text); }
134            fn print<T: std::fmt::Display>(text: T) { pyrs::print::print_str(text); }
135            use pyrs::input::input_with_validation as input;
136            use pyrs::latex::{latex, latex_display, latex_inline};
137            pyrs::gui::start_gui_server(|| { #fn_block });
138        }
139    };
140    TokenStream::from(expanded)
141}