Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Use nom-locate to add span information in the AST #129

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

members = [
"glsl",
"glsl-tree",
"glsl-quasiquote"
"glsl-impl",
"glsl-quasiquote",
"glsl-tree"
]

[patch.crates-io]
glsl = { path = "./glsl" }
glsl-impl = { path = "./glsl-impl" }
14 changes: 14 additions & 0 deletions glsl-impl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "glsl-impl"
version = "6.0.0"
license = "BSD-3-Clause"
authors = ["Dimitri Sabadie <[email protected]>", "Vincent Tavernier <[email protected]>"]

edition = "2018"

[lib]
proc-macro = true

[dependencies]
syn = "1.0"
quote = "1.0"
169 changes: 169 additions & 0 deletions glsl-impl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput};

#[proc_macro_derive(NodeContents)]
pub fn node_contents(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);

// Add anonymous lifetimes as needed
let lifetimes: Vec<_> = input.generics.lifetimes().map(|_| quote! { '_ }).collect();

// Build the contents_eq method
let contents_eq_body = match input.data {
Data::Struct(ds) => {
let mut current_expr = None;

for (id, field) in ds.fields.iter().enumerate() {
let field_id = field
.ident
.as_ref()
.map(|id| quote! { #id })
.unwrap_or_else(|| {
let id = syn::Index::from(id);
quote! { #id }
});

let this_field_expr = quote! { self.#field_id.contents_eq(&other.#field_id) };

current_expr = Some(match current_expr {
Some(before) => quote! { #before && #this_field_expr },
None => this_field_expr,
});
}

current_expr.unwrap_or_else(|| quote! { true })
}
Data::Enum(de) => {
let mut arms = Vec::new();

for variant in de.variants {
let variant_name = &variant.ident;

// Build the result expression
let mut expr = None;
// The left variant binding
let mut bind_left = Vec::new();
// The right variant binding
let mut bind_right = Vec::new();

let (bind_left, bind_right) = match variant.fields {
// Enum variant with named fields: Enum::X { .. }
syn::Fields::Named(named) => {
for (id, field) in named.named.iter().enumerate() {
let field_id = &field.ident;
let left_field_id = format_ident!("a{}", id + 1);
let right_field_id = format_ident!("b{}", id + 1);

bind_left.push(quote! {
#field_id: #left_field_id
});
bind_right.push(quote! {
#field_id: #right_field_id
});

let this_field_expr = quote! { #left_field_id.contents_eq(&#right_field_id) };

expr = Some(match expr {
Some(before) => quote! { #before && #this_field_expr },
None => this_field_expr,
});
}

(
quote! { { #(#bind_left),* } },
quote! { { #(#bind_right),* } },
)
}
// Enum variant with unnamed fields: Enum::X(..)
syn::Fields::Unnamed(unnamed) => {
for (id, _) in unnamed.unnamed.iter().enumerate() {
let left_field_id = format_ident!("a{}", id + 1);
let right_field_id = format_ident!("b{}", id + 1);

bind_left.push(quote! { #left_field_id });
bind_right.push(quote! { #right_field_id });

let this_field_expr = quote! { #left_field_id.contents_eq(&#right_field_id) };

expr = Some(match expr {
Some(before) => quote! { #before && #this_field_expr },
None => this_field_expr,
});
}

(quote! { (#(#bind_left),*) }, quote! { (#(#bind_right),*) })
}
// Enum with no fields
syn::Fields::Unit => {
arms.push(quote! { (Self::#variant_name, Self::#variant_name) => true });
continue;
}
};

let expr = expr.unwrap_or_else(|| quote! { true });
arms.push(
quote! { (Self::#variant_name #bind_left, Self::#variant_name #bind_right) => #expr },
);
}

quote! { match (self, other) {
#(#arms),*,
_ => false
} }
}
_ => panic!("unsupported type for NodeContents derive"),
};

// Generate the name of the target for usage in impl targets
let base_ident = input.ident;
let struct_name = if lifetimes.is_empty() {
quote! { #base_ident }
} else {
quote! { #base_ident<#(#lifetimes),*> }
};

// Build the output, possibly using quasi-quotation
let expanded = quote! {
#[automatically_derived]
impl NodeContents for #struct_name {}
#[automatically_derived]
impl NodeContentsEq for #struct_name {
fn contents_eq(&self, other: &Self) -> bool {
#contents_eq_body
}
}
};

// Is this a "Data" node?
let raw_name = base_ident
.to_string()
.strip_suffix("Data")
.map(|id| format_ident!("{}", id));
let expanded = if let Some(raw_name) = raw_name {
let lifetimes: Vec<_> = input.generics.lifetimes().collect();
let type_name = if lifetimes.is_empty() {
quote! { #raw_name }
} else {
quote! { #raw_name<#(#lifetimes),*> }
};

quote! {
#expanded

pub type #type_name = Node<#struct_name>;

impl<U> From<U> for #type_name
where U: Into<#base_ident> {
fn from(u: U) -> Self {
Node::new(u.into(), None)
}
}
}
} else {
expanded
};

TokenStream::from(expanded)
}
2 changes: 1 addition & 1 deletion glsl-quasiquote/src/quoted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ where
impl Quoted for Identifier {
fn quote(&self) -> TokenStream {
let s = &self.0;
quote! { glsl::syntax::Identifier(#s.to_owned()) }
quote! { glsl::syntax::IdentifierData(#s.to_owned()) }
}
}

Expand Down
Loading