some work on implementing the structures
This commit is contained in:
parent
03014508ba
commit
92b8593be9
1
.idea/lfcm.iml
generated
1
.idea/lfcm.iml
generated
@ -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" />
|
||||
|
||||
@ -9,6 +9,8 @@ edition = "2018"
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
|
||||
|
||||
[dependencies]
|
||||
quote = "1"
|
||||
proc-macro2 = "1.0"
|
||||
|
||||
243
src/lib.rs
243
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<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;
|
||||
};
|
||||
for config_section in config_sections.iter() {
|
||||
println!("Section: {}", config_section.name.span().source_text().unwrap_or("<no-name?>".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::<Vec<String>>().join("")
|
||||
}
|
||||
|
||||
let tokens = quote! {
|
||||
#input
|
||||
fn walk_config_sections(definition: &ParseConfigDefinition, sections: &mut Vec<ParseConfigSection>) {
|
||||
fn recurse(section: &ParseConfigSection, sections: &mut Vec<ParseConfigSection>) {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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());
|
||||
}
|
||||
17
tests/integration_tests.rs
Normal file
17
tests/integration_tests.rs
Normal 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!("{:?}");
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user