some work on implementing the structures

This commit is contained in:
Linus Vogel 2026-07-26 23:25:48 +02:00
parent 03014508ba
commit 92b8593be9
4 changed files with 227 additions and 36 deletions

1
.idea/lfcm.iml generated
View File

@ -3,6 +3,7 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />

View File

@ -9,6 +9,8 @@ edition = "2018"
[lib]
proc-macro = true
[dependencies]
quote = "1"
proc-macro2 = "1.0"

View File

@ -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<ParseConfigVariable>,
pub subsections: Vec<ParseConfigSection>
}
impl Parse for ParseConfigVariable {
fn parse(input: ParseStream) -> syn::Result<Self> {
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<Self> {
let content;
let _section: kw::section = input.parse()?;
let name: Ident = input.parse()?;
let _brace: Brace = braced!(content in input);
let mut vars: Vec<ParseConfigVariable> = Vec::new();
let mut subsections: Vec<ParseConfigSection> = 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<Self> {
// parse variables and default section
let mut top_section: Option<ParseConfigSection> = None;
let mut name: Option<Ident> = 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::<ParseConfigSection>()?);
} 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<ParseConfigSection> = Vec::new();
walk_config_sections(&definition, &mut config_sections);
struct Hello;
};
tokens.into()
for config_section in config_sections.iter() {
println!("Section: {}", config_section.name.span().source_text().unwrap_or("<no-name?>".to_string()));
}
/// 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()
for section in config_sections.iter() {
emit_section_struct(&mut out, section)
}
/// 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);
// TODO: generate implementations for necessary output
let tokens = quote! {
#input
println!("Seems done");
struct Hello;
proc_macro::TokenStream::from(out)
}
fn capitalize_word(word: &str) -> String {
word[0..1].to_uppercase() + word[1..].as_ref()
}
fn capitalize_name(name: &str) -> String {
name.split("_").map(capitalize_word).collect::<Vec<String>>().join("")
}
fn walk_config_sections(definition: &ParseConfigDefinition, sections: &mut Vec<ParseConfigSection>) {
fn recurse(section: &ParseConfigSection, sections: &mut Vec<ParseConfigSection>) {
for section in &section.subsections {
sections.push(section.clone());
recurse(section, sections);
}
}
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 = &section.vars.iter().map(|var| {
let name = var.name.clone();
let ty = var.ty.clone();
quote! {
pub #name: #ty,
}
}).collect::<Vec<_>>();
let subsections = &section.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::<Vec<_>>();
// create the token stream producing the struct in question.
let type_name = capitalize_name(&section.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());
}

View File

@ -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!("{:?}");
}