From 92b8593be9edb1fdf1550c3beb6821790c2431cb Mon Sep 17 00:00:00 2001 From: Linus Vogel Date: Sun, 26 Jul 2026 23:25:48 +0200 Subject: [PATCH] some work on implementing the structures --- .idea/lfcm.iml | 1 + Cargo.toml | 2 + src/lib.rs | 243 +++++++++++++++++++++++++++++++------ tests/integration_tests.rs | 17 +++ 4 files changed, 227 insertions(+), 36 deletions(-) create mode 100644 tests/integration_tests.rs diff --git a/.idea/lfcm.iml b/.idea/lfcm.iml index cf84ae4..bbe0a70 100644 --- a/.idea/lfcm.iml +++ b/.idea/lfcm.iml @@ -3,6 +3,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index 8b435bb..5de5e20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ edition = "2018" [lib] proc-macro = true + + [dependencies] quote = "1" proc-macro2 = "1.0" diff --git a/src/lib.rs b/src/lib.rs index c99da4d..1653857 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,59 +1,230 @@ + extern crate proc_macro; -use proc_macro::TokenStream; - - +extern crate proc_macro2; +use proc_macro2::TokenStream; +use std::fmt::{Debug, Formatter}; +use proc_macro2::{Group, TokenTree}; use quote::quote; use syn::parse::{Parse, ParseStream}; -use syn::{parse_macro_input}; - -struct ParseConfigDeclaration { +use syn::{parse_macro_input, Expr, Type, Ident, braced}; +use syn::spanned::Spanned; +use syn::token::{Brace, Semi, Eq, Colon}; +mod kw { + use syn::custom_keyword; + custom_keyword!(section); + custom_keyword!(var); + // params and their names + custom_keyword!(param); + custom_keyword!(name); } -impl Parse for ParseConfigDeclaration { +#[derive(Clone)] +struct ParseConfigDefinition { + pub name: Ident, + pub top_section: ParseConfigSection +} + +#[derive(Clone)] +struct ParseConfigVariable { + pub ty: Type, + pub name: Ident, + pub default: Expr, +} + +#[derive(Clone)] +struct ParseConfigSection { + pub name: Ident, + pub vars: Vec, + pub subsections: Vec +} + +impl Parse for ParseConfigVariable { fn parse(input: ParseStream) -> syn::Result { - todo!() + let _var: kw::var = input.parse()?; + let name: Ident = input.parse()?; + let _colon: Colon = input.parse()?; + let ty: Type = input.parse()?; + let _eq: Eq = input.parse()?; + let default: Expr = input.parse()?; + let _semi: Semi = input.parse()?; + + Ok (ParseConfigVariable{ ty, name, default }) + } +} + +impl Parse for ParseConfigSection { + fn parse(input: ParseStream) -> syn::Result { + let content; + let _section: kw::section = input.parse()?; + let name: Ident = input.parse()?; + let _brace: Brace = braced!(content in input); + let mut vars: Vec = Vec::new(); + let mut subsections: Vec = Vec::new(); + + while !content.is_empty() { + if content.peek(kw::section) { + subsections.push(content.parse()?); + } else { + vars.push(content.parse()?); + } + } + + Ok (ParseConfigSection{ name, vars, subsections }) + } +} + +impl Parse for ParseConfigDefinition { + fn parse(input: ParseStream) -> syn::Result { + // parse variables and default section + let mut top_section: Option = None; + let mut name: Option = None; + + while !input.is_empty() { + if input.peek(kw::section) { + if top_section.is_some() { + return Err(syn::Error::new(input.span(), "Duplicate section")); + } + top_section = Some(input.parse::()?); + } else if input.peek(kw::param) { + let _param: kw::param = input.parse()?; + if input.peek(kw::name) { + let _name: kw::name = input.parse()?; + let _eq: Eq = input.parse()?; + name = Some(input.parse()?); + let _semi: Semi = input.parse()?; + } else { + return Err(syn::Error::new(input.span(), format!("Unexpected token: {:?}", input.span().source_text()))); + } + } else { + return Err(syn::Error::new(input.span(), format!("Invalid token: {:?}", input.span().source_text()))); + } + } + + if name.is_none() { + return Err(syn::Error::new(input.span(), "Config has no name specified")); + } + + if top_section.is_none() { + return Err (input.error("No top section found")); + } + + Ok (Self {name: name.unwrap(), top_section: top_section.unwrap()}) + } +} + +impl Debug for ParseConfigVariable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let ty_str = self.ty.span().source_text().unwrap_or("no-type".to_string()); + let id_str = self.name.to_string(); + let expr_str = self.default.span().source_text().unwrap_or("no-default".to_string()); + + f.write_fmt(format_args!("{} {} = {};", ty_str, id_str, expr_str)) + } +} + +impl Debug for ParseConfigSection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let name_str = self.name.to_string(); + + f.write_fmt(format_args!("section {name_str} {{\n"))?; + for var in &self.vars { + f.write_fmt(format_args!("{var:?}\n"))?; + } + for section in &self.subsections { + f.write_fmt(format_args!("{section:?}\n"))?; + } + f.write_fmt(format_args!("}}")) + } +} + +impl Debug for ParseConfigDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("config! {{\n"))?; + f.write_fmt(format_args!("name = {:?};\n", self.name))?; + f.write_fmt(format_args!("{:?}\n", self.top_section))?; + f.write_fmt(format_args!("}}\n")) } } #[proc_macro] -pub fn my_macro(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ParseConfigDeclaration); +pub fn config(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // parse the input and prepare the output + let definition: ParseConfigDefinition = parse_macro_input!(input as ParseConfigDefinition); + let mut out = proc_macro2::TokenStream::new(); - let tokens = quote! { - #input + // generate the class Structure + let mut config_sections: Vec = Vec::new(); + walk_config_sections(&definition, &mut config_sections); - struct Hello; - }; + for config_section in config_sections.iter() { + println!("Section: {}", config_section.name.span().source_text().unwrap_or("".to_string())); + } - tokens.into() + for section in config_sections.iter() { + emit_section_struct(&mut out, section) + } + + // TODO: generate implementations for necessary output + + println!("Seems done"); + + proc_macro::TokenStream::from(out) } -/// Example of user-defined [derive mode macro][1] -/// -/// [1]: https://doc.rust-lang.org/reference/procedural-macros.html#derive-mode-macros -#[proc_macro_derive(MyDerive)] -pub fn my_derive(_input: TokenStream) -> TokenStream { - - let tokens = quote! { - struct Hello; - }; - - tokens.into() +fn capitalize_word(word: &str) -> String { + word[0..1].to_uppercase() + word[1..].as_ref() } -/// Example of user-defined [procedural macro attribute][1]. -/// -/// [1]: https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros -#[proc_macro_attribute] -pub fn my_attribute(_args: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); +fn capitalize_name(name: &str) -> String { + name.split("_").map(capitalize_word).collect::>().join("") +} - let tokens = quote! { - #input +fn walk_config_sections(definition: &ParseConfigDefinition, sections: &mut Vec) { + fn recurse(section: &ParseConfigSection, sections: &mut Vec) { + for section in §ion.subsections { + sections.push(section.clone()); + recurse(section, sections); + } + } - struct Hello; + let top_level_section = ParseConfigSection { + name: definition.clone().name.clone(), + vars: definition.top_section.vars.clone(), + subsections: definition.top_section.subsections.clone(), }; - tokens.into() + sections.push(top_level_section); + recurse(&definition.top_section, sections); } + +fn emit_section_struct(out: &mut proc_macro2::TokenStream, section: &ParseConfigSection) { + + let vars = §ion.vars.iter().map(|var| { + let name = var.name.clone(); + let ty = var.ty.clone(); + quote! { + pub #name: #ty, + } + }).collect::>(); + let subsections = §ion.subsections.iter().map(|subsection| { + let type_name = capitalize_name(&subsection.name.to_string()); + let type_name_ident = Ident::new(type_name.as_str(), subsection.name.span()); + let var_name = subsection.name.clone(); + quote! { + pub #var_name: #type_name_ident + } + }).collect::>(); + + // create the token stream producing the struct in question. + let type_name = capitalize_name(§ion.name.to_string()); + let type_name_ident = Ident::new(type_name.as_str(), section.name.span()); + let struct_stream = quote! { + pub struct #type_name_ident { + #(#vars),* + #(#subsections),* + } + }; + + out.extend(struct_stream.into_iter()); +} \ No newline at end of file diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..5d54e24 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,17 @@ +use lfcm::config; + +config! { + param name = Config; + section top { + var rand: u64 = 42; + section database { + var seed: u64 = 42; + } + } +} + +#[test] +fn test_parser() { + //println!("{:?}"); + +} \ No newline at end of file