use proc_macro2::TokenStream;
use quote::quote;
use std::path::Path;
use std::{fs, io::Read};
use syn;
use walkdir::WalkDir;
const SKIPPED_ENTRIES: &[&str] =
&["metadata.rs", "iter.rs", "compiler_directives"];
fn main() {
println!("cargo:rerun-if-changed=src");
println!("cargo:rerun-if-changed=build.rs");
let mut node_names = Vec::new();
let mut node_structs = Vec::new();
let mut node_enums = Vec::new();
for entry in WalkDir::new("src")
.into_iter()
.filter_entry(|e| {
!SKIPPED_ENTRIES.contains(&e.file_name().to_string_lossy().as_ref())
})
.filter_map(Result::ok)
.filter(|e| {
e.file_type().is_file()
&& e.path().extension().and_then(|s| s.to_str()) == Some("rs")
})
{
let mut file =
fs::File::open(entry.path()).expect("Unable to open source file");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Unable to read source file");
let syntax_tree =
syn::parse_file(&contents).expect("Unable to parse source file");
for item in syntax_tree.items {
match item {
syn::Item::Struct(item_struct) => {
node_names.push(item_struct.ident.clone());
node_structs.push(item_struct);
}
syn::Item::Enum(item_enum) => {
node_names.push(item_enum.ident.clone());
node_enums.push(item_enum);
}
_ => (),
}
}
}
let output_path =
Path::new(&std::env::var("OUT_DIR").unwrap()).join("nodes.rs");
let node_enum_def = quote! {
#[derive(Debug, Clone)]
pub enum Node<'a: 'b, 'b> {
#( #node_names(&'b #node_names<'a>) ),*
}
impl<'a: 'b, 'b> Nodes<'a, 'b> for Node<'a, 'b> {
fn nodes(&'b self) -> NodeIter<'a, 'b> {
self.iter()
}
fn add_nodes(&'b self, dest: &mut Vec<Node<'a, 'b>>, pred: fn(Node<'a, 'b>) -> bool)
{
match self {
#( Node::#node_names(inner_ref) => { inner_ref.add_nodes(dest, pred) } )*
}
}
}
impl<'a: 'b, 'b> IntoIterator for Node<'a, 'b> {
type Item = Node<'a, 'b>;
type IntoIter = NodeIter<'a, 'b>;
fn into_iter(self) -> Self::IntoIter {
self.into()
}
}
impl<'a: 'b, 'b> Node<'a, 'b> {
pub fn iter(&self) -> NodeIter<'a, 'b> {
self.clone().into()
}
fn children(&self) -> Vec<Node<'a, 'b>> {
match self {
#( Node::#node_names(inner_ref) => { inner_ref.children() } )*
}
}
pub fn name(&self) -> &str {
match self {
#( Node::#node_names(_) => { stringify!(#node_names) } )*
}
}
}
};
let iter_doc = node_names.iter().map(|ident| format!{"Iterate across the [`{}`] and its children", ident.to_string()});
let node_defs = quote! {
#(
impl<'a: 'b, 'b> From<&'b #node_names<'a>> for Node<'a, 'b> {
fn from(value: &'b #node_names<'a>) -> Self {
Node::#node_names(value)
}
}
impl<'a: 'b, 'b> IntoIterator for &'b #node_names<'a> {
type Item = Node<'a, 'b>;
type IntoIter = NodeIter<'a, 'b>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a: 'b, 'b> #node_names<'a> {
#[doc = #iter_doc]
pub fn iter(&'b self) -> NodeIter<'a, 'b> {
Into::<Node<'a, 'b>>::into(self).iter()
}
}
)*
};
let mut node_impls = TokenStream::default();
for item_enum in node_enums {
let ident = item_enum.ident;
let variants = item_enum.variants.iter().map(|v| {
let name = &v.ident;
quote! {#name}
});
let variant_expansions = item_enum.variants.iter().map(|v| {
let syn::Fields::Unnamed(ref unnamed_fields) = v.fields else {
panic!(
"Syntax tree enum with named fields: {}",
ident.to_string()
);
};
let expansion_string = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(i, _)| format!("v{}", i))
.collect::<Vec<_>>()
.join(", ");
let res: TokenStream = expansion_string
.parse()
.expect("Unable to parse enum expansion");
res
});
let variant_children = item_enum.variants.iter().map(|v| {
let syn::Fields::Unnamed(ref unnamed_fields) = v.fields else {
panic!(
"Syntax tree enum with named fields: {}",
ident.to_string()
);
};
let expansion_string = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(i, _)| format!("v{}.nodes()", i))
.collect::<Vec<_>>()
.join(" + ");
if expansion_string.is_empty() {
quote!(NodeIter::default())
} else {
let res: TokenStream = expansion_string
.parse()
.expect("Unable to parse enum expansion");
res
}
});
let variant_add_nodes = item_enum.variants.iter().map(|v| {
let syn::Fields::Unnamed(ref unnamed_fields) = v.fields else {
panic!(
"Syntax tree enum with named fields: {}",
ident.to_string()
);
};
let expansion_string = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(i, _)| format!("v{}.add_nodes(dest, pred)", i))
.collect::<Vec<_>>()
.join("; ");
if expansion_string.is_empty() {
quote!(NodeIter::default())
} else {
let res: TokenStream = expansion_string
.parse()
.expect("Unable to parse enum expansion");
res
}
});
let variants_clone = variants.clone();
let variant_expansions_clone = variant_expansions.clone();
node_impls.extend(quote! {
impl<'a: 'b, 'b> Nodes<'a, 'b> for #ident<'a> {
fn nodes(&'b self) -> NodeIter<'a, 'b> {
Into::<NodeIter<'a, 'b>>::into(Into::<Node<'a, 'b>>::into(self))
}
fn add_nodes(&'b self, dest: &mut Vec<Node<'a, 'b>>, pred: fn(Node<'a, 'b>) -> bool)
{
if pred(Into::<Node<'a, 'b>>::into(self)) {
dest.push(Into::<Node<'a, 'b>>::into(self))
}
match self {
#( #ident::#variants(#variant_expansions) => { #variant_add_nodes; } )*
}
}
}
impl<'a: 'b, 'b> #ident<'a> {
fn children(&'b self) -> Vec<Node<'a, 'b>> {
match self {
#( #ident::#variants_clone(#variant_expansions_clone) => { (#variant_children).raw() } )*
}
}
}
})
}
for item_struct in node_structs {
let ident = item_struct.ident;
let syn::Fields::Unnamed(unnamed_fields) = item_struct.fields else {
panic!(
"Syntax tree struct with named fields: {}",
ident.to_string()
);
};
let indices =
unnamed_fields.unnamed.iter().enumerate().map(|(i, _)| {
let index = syn::Index::from(i);
quote! {#index}
});
let indices_clone = indices.clone();
node_impls.extend(quote! {
impl<'a: 'b, 'b> Nodes<'a, 'b> for #ident<'a> {
fn nodes(&'b self) -> NodeIter<'a, 'b> {
Into::<NodeIter<'a, 'b>>::into(Into::<Node<'a, 'b>>::into(self))
}
fn add_nodes(&'b self, dest: &mut Vec<Node<'a, 'b>>, pred: fn(Node<'a, 'b>) -> bool)
{
if pred(Into::<Node<'a, 'b>>::into(self)) {
dest.push(Into::<Node<'a, 'b>>::into(self))
}
#( self.#indices.add_nodes(dest, pred); )*
}
}
impl<'a: 'b, 'b> #ident<'a> {
fn children(&'b self) -> Vec<Node<'a, 'b>> {
(#( self.#indices_clone.nodes() )+*).raw()
}
}
})
}
fs::write(
&output_path,
node_enum_def.to_string()
+ &node_defs.to_string()
+ &node_impls.to_string(),
)
.expect("Unable to write generated file");
}