summaryrefslogtreecommitdiff
path: root/systems/_constants/macros
diff options
context:
space:
mode:
Diffstat (limited to 'systems/_constants/macros')
-rw-r--r--systems/_constants/macros/Cargo.toml15
-rw-r--r--systems/_constants/macros/src/lib.rs189
2 files changed, 204 insertions, 0 deletions
diff --git a/systems/_constants/macros/Cargo.toml b/systems/_constants/macros/Cargo.toml
new file mode 100644
index 0000000..04ad4fc
--- /dev/null
+++ b/systems/_constants/macros/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "macros"
+version = "0.1.0"
+edition = "2024"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { version = "2.0", features = ["full", "extra-traits"] }
+quote = "1.0"
+proc-macro2 = "1.0"
+regex = "1.12"
+
+string_proc = { path = "../../../utils/string_proc" }
diff --git a/systems/_constants/macros/src/lib.rs b/systems/_constants/macros/src/lib.rs
new file mode 100644
index 0000000..ebcc31f
--- /dev/null
+++ b/systems/_constants/macros/src/lib.rs
@@ -0,0 +1,189 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{Expr, ExprLit, Ident, Item, ItemMod, Lit, LitStr, parse_macro_input};
+
+#[proc_macro_attribute]
+pub fn constants(attr: TokenStream, item: TokenStream) -> TokenStream {
+ let prefix = parse_macro_input!(attr as LitStr).value();
+ let mut input_mod = parse_macro_input!(item as ItemMod);
+
+ if let Some((_, items)) = &mut input_mod.content {
+ // Collect functions to generate
+ let mut generated_functions = Vec::new();
+
+ for item in items.iter_mut() {
+ if let Item::Macro(macro_item) = item {
+ // Check if it's a c! macro call
+ if macro_item.mac.path.is_ident("c") {
+ // Parse macro content
+ let macro_tokens = macro_item.mac.tokens.clone();
+ let parsed: syn::Result<Expr> = syn::parse2(macro_tokens);
+
+ if let Ok(Expr::Assign(assign)) = parsed {
+ // Get constant name and value
+ if let Some((const_name, const_value)) =
+ extract_const_name_and_value(&assign)
+ {
+ // Process constant and generate function
+ if let Some(func) = process_constant(&prefix, const_name, const_value) {
+ generated_functions.push(func);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for func in generated_functions {
+ items.push(Item::Verbatim(func));
+ }
+ }
+
+ TokenStream::from(quote! { #input_mod })
+}
+
+fn extract_const_name_and_value(assign: &syn::ExprAssign) -> Option<(Ident, Box<Expr>)> {
+ if let Expr::Path(path) = &*assign.left {
+ if let Some(ident) = path.path.get_ident() {
+ let const_name = ident.clone();
+ let const_value = assign.right.clone();
+ return Some((const_name, const_value));
+ }
+ }
+ None
+}
+
+fn process_constant(
+ prefix: &str,
+ const_name: Ident,
+ const_value: Box<Expr>,
+) -> Option<proc_macro2::TokenStream> {
+ if let Expr::Lit(ExprLit {
+ lit: Lit::Str(lit_str),
+ ..
+ }) = *const_value
+ {
+ let value_str = lit_str.value();
+ let value_span = lit_str.span();
+
+ if value_str.contains('{') && value_str.contains('}') {
+ let params = extract_placeholder_params(&value_str);
+
+ if !params.is_empty() {
+ // With parameters
+ Some(generate_function_with_params(
+ prefix,
+ &const_name,
+ &value_str,
+ &params,
+ value_span,
+ ))
+ } else {
+ // Without parameters
+ Some(generate_function_without_params(
+ prefix,
+ &const_name,
+ &value_str,
+ ))
+ }
+ } else {
+ // Without parameters
+ Some(generate_function_without_params(
+ prefix,
+ &const_name,
+ &value_str,
+ ))
+ }
+ } else {
+ None
+ }
+}
+
+fn extract_placeholder_params(value_str: &str) -> Vec<String> {
+ let mut params = Vec::new();
+ let chars = value_str.chars().collect::<Vec<_>>();
+ let mut i = 0;
+
+ while i < chars.len() {
+ if chars[i] == '{' {
+ let start = i;
+ i += 1;
+
+ while i < chars.len() && chars[i] != '}' {
+ i += 1;
+ }
+
+ if i < chars.len() && chars[i] == '}' {
+ let param: String = chars[start + 1..i].iter().collect();
+ if !param.is_empty() {
+ params.push(param);
+ }
+ }
+ }
+ i += 1;
+ }
+
+ params
+}
+
+fn generate_function_with_params(
+ prefix: &str,
+ const_name: &Ident,
+ value_str: &str,
+ params: &[String],
+ value_span: proc_macro2::Span,
+) -> proc_macro2::TokenStream {
+ let fn_name = format!("{}_{}", prefix, const_name.to_string().to_lowercase());
+ let fn_ident = Ident::new(&fn_name, const_name.span());
+
+ // Generate parameter list
+ let param_idents: Vec<Ident> = params.iter().map(|p| Ident::new(p, value_span)).collect();
+ let format_args = param_idents.iter().map(|ident| quote! { #ident });
+
+ // Generate format! code
+ let doc_format_vaule = params.join(", ");
+
+ let mut doc_format_template = value_str.to_string();
+ for param in params {
+ let placeholder = format!("{{{}}}", param);
+ doc_format_template = doc_format_template.replace(&placeholder, "{}");
+ }
+
+ let doc_format_code = format!(
+ "format!(\"{}\", {});",
+ doc_format_template, doc_format_vaule
+ );
+
+ // Replace {xxx} with {} format
+ let mut format_str = value_str.to_string();
+ for param in params {
+ format_str = format_str.replace(&format!("{{{}}}", param), "{}");
+ }
+
+ quote! {
+ #[doc = "```ignore"]
+ #[doc = #doc_format_code]
+ #[doc = "```"]
+ pub fn #fn_ident(#(#param_idents: &str),*) -> String {
+ format!(#format_str, #(#format_args),*)
+ }
+ }
+}
+
+fn generate_function_without_params(
+ prefix: &str,
+ const_name: &Ident,
+ value_str: &str,
+) -> proc_macro2::TokenStream {
+ let fn_name = format!("{}_{}", prefix, const_name.to_string().to_lowercase());
+ let fn_ident = Ident::new(&fn_name, const_name.span());
+
+ quote! {
+ #[doc = "`"]
+ #[doc = #value_str]
+ #[doc = "`"]
+ pub fn #fn_ident() -> &'static str {
+ #value_str
+ }
+ }
+}