use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
struct Uniform {
uniform_name: Option<(syn::Token![=], syn::LitStr)>,
}
impl syn::parse::Parse for Uniform{
fn parse(input: syn::parse::ParseStream) -> syn::Result<Uniform>{
let uniform_name = if input.peek(syn::Token![=]) {
Some((input.parse()?, input.parse()?))
}else{
None
};
Ok(Uniform{
uniform_name,
})
}
}
impl Uniform {
fn name(&self) -> Option<String> {
self.uniform_name.as_ref().map(|(_, name)| name.value())
}
}
struct Name {
_equal_token: syn::Token![=],
value: syn::LitStr,
}
impl syn::parse::Parse for Name{
fn parse(input: syn::parse::ParseStream) -> syn::Result<Name>{
Ok(Name{
_equal_token: input.parse()?,
value: input.parse()?,
})
}
}
struct Default {
_equal_token: syn::Token![=],
value: syn::Lit,
}
impl syn::parse::Parse for Default {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Default>{
Ok(Default{
_equal_token: input.parse()?,
value: input.parse()?,
})
}
}
enum MaterialAttrTts {
Skip,
Uniform(Uniform),
Name(Name),
Default(Default),
Data,
NotData,
}
impl syn::parse::Parse for MaterialAttrTts{
fn parse(input: syn::parse::ParseStream) -> syn::Result<MaterialAttrTts>{
let path: syn::Ident = input.parse()?;
if path == "skip" {
Ok(MaterialAttrTts::Skip)
}else if path == "uniform" {
let uniform = input.parse()?;
Ok(MaterialAttrTts::Uniform(uniform))
}else if path == "name" {
let name = input.parse()?;
Ok(MaterialAttrTts::Name(name))
}else if path == "default" {
let default = input.parse()?;
Ok(MaterialAttrTts::Default(default))
}else if path == "data" {
Ok(MaterialAttrTts::Data)
}else if path == "not_data" {
Ok(MaterialAttrTts::NotData)
}else{
Err(input.error(format!("Error parsing Material attribute {} not supported", path)))
}
}
}
impl MaterialAttrTts {
fn is_skip(&self) -> bool {
match self {
MaterialAttrTts::Skip => true,
_ => false,
}
}
fn as_name(&self) -> Option<&syn::LitStr> {
match self {
MaterialAttrTts::Name(Name{value, ..}) => Some(value),
_ => None,
}
}
fn as_uniform(&self) -> Option<&Uniform> {
match self {
MaterialAttrTts::Uniform(uniform) => Some(uniform),
_ => None,
}
}
fn is_uniform(&self) -> bool {
match self {
MaterialAttrTts::Uniform(_) => true,
_ => false,
}
}
fn is_data(&self) -> bool {
match self {
MaterialAttrTts::Data => true,
_ => false,
}
}
fn is_not_data(&self) -> bool {
match self {
MaterialAttrTts::NotData => true,
_ => false,
}
}
fn as_default_value(&self) -> Option<&syn::Lit> {
match self {
MaterialAttrTts::Default(Default{value, ..}) => Some(value),
_ => None,
}
}
}
struct MaterialAttr{
_pound_token: syn::token::Pound,
_bracket_token: syn::token::Bracket,
_path: syn::Path,
_paren: Option<syn::token::Paren>,
attributes: Punctuated<MaterialAttrTts, syn::Token![,]>,
}
impl syn::parse::Parse for MaterialAttr{
fn parse(input: syn::parse::ParseStream) -> syn::Result<MaterialAttr>{
let content;
let _pound_token = input.parse()?;
let _bracket_token = syn::bracketed!(content in input);
let _path: syn::Path = content.call(syn::Path::parse_mod_style)?;
let attr_content;
let _paren = Some(syn::parenthesized!(attr_content in content));
let attributes = Punctuated::parse_terminated(&attr_content)?;
Ok(MaterialAttr{
_pound_token,
_bracket_token,
_path,
_paren,
attributes,
})
}
}
impl MaterialAttr {
fn is_skip(&self) -> bool {
self.attributes.iter().any(|tts| tts.is_skip())
}
fn as_name(&self) -> Option<&syn::LitStr> {
self.attributes.iter().filter_map(|tts| tts.as_name()).next()
}
fn as_uniform(&self) -> Option<&Uniform> {
self.attributes.iter().filter_map(|tts| tts.as_uniform()).next()
}
fn is_uniform(&self) -> bool {
self.attributes.iter().any(|tts| tts.is_uniform())
}
fn is_data(&self) -> bool {
self.attributes.iter().any(|tts| tts.is_data())
}
fn is_not_data(&self) -> bool {
self.attributes.iter().any(|tts| tts.is_not_data())
}
fn as_default_value(&self) -> Option<&syn::Lit> {
self.attributes.iter().filter_map(|tts| tts.as_default_value()).next()
}
fn parse_outer(input: syn::parse::ParseStream) -> syn::Result<Vec<Self>>{
let attrs = syn::Attribute::parse_outer(input)?
.into_iter()
.filter(|attr| attr.path.is_ident("material"))
.collect::<Vec<_>>();
let mut mat_attrs = Vec::new();
for attr in attrs {
let input = quote!(#attr);
mat_attrs.push(syn::parse2(input)?);
}
Ok(mat_attrs)
}
}
struct Parameter{
attrs: Vec<MaterialAttr>,
_vis: syn::Visibility,
ident: syn::Ident,
_colon_token: syn::Token![:],
ty: syn::Type
}
impl syn::parse::Parse for Parameter{
fn parse(input: syn::parse::ParseStream) -> syn::Result<Parameter>{
Ok(Parameter{
attrs: input.call(MaterialAttr::parse_outer)?,
_vis: input.parse()?,
ident: input.parse()?,
_colon_token: input.parse()?,
ty: input.parse()?,
})
}
}
fn bracketed_first_argument(ty: &syn::PathSegment) -> Option<&syn::PathSegment>{
if let syn::PathArguments::AngleBracketed(args) = &ty.arguments {
args.args.first().and_then(|args| {
if let syn::GenericArgument::Type(ty) = args {
if let syn::Type::Path(path) = ty{
path.path.segments.last().map(|ty| ty )
}else{
None
}
}else{
None
}
})
}else{
None
}
}
impl Parameter {
fn is_skip(&self) -> bool {
self.attrs.iter().any(|attr| attr.is_skip())
}
fn name(&self) -> String {
self.attrs.iter()
.filter_map(|attr| attr.as_name())
.next()
.map(|name| name.value())
.unwrap_or_else(|| self.ident.to_string())
}
fn uniform_name(&self) -> Option<String> {
self.attrs.iter()
.filter_map(|attr| attr.as_uniform())
.next()
.map(|uniform| uniform.name().unwrap_or_else(|| self.name()))
}
fn is_uniform(&self) -> bool {
self.attrs.iter().any(|attr| attr.is_uniform())
}
fn is_option_parameter(&self) -> bool{
self.is_parameter_type( "Option")
}
fn parameter_type(&self) -> Option<&syn::PathSegment> {
match &self.ty{
syn::Type::Path(path) => path.path.segments.last().and_then(|ty|{
if ty.ident == "Parameter" {
bracketed_first_argument(ty)
}else{
None
}
}),
_ => None,
}
}
fn is_parameter_type<I>(&self, param_ty: I) -> bool
where syn::Ident: PartialEq<I>
{
if let Some(parameter_type) = self.parameter_type(){
parameter_type.ident == param_ty
}else{
false
}
}
fn is_parameter_type_or_opt<I>(&self, param_ty: I) -> bool
where syn::Ident: PartialEq<I> + PartialEq<&'static str>
{
if let Some(parameter_type) = self.parameter_type(){
if parameter_type.ident == param_ty {
true
}else if parameter_type.ident == "Option" {
if let Some(ty) = bracketed_first_argument(parameter_type){
ty.ident == param_ty
}else{
false
}
}else{
false
}
}else{
false
}
}
fn is_texture(&self) -> bool {
self.is_parameter_type_or_opt("TextureSampler")
}
fn is_cubemap(&self) -> bool {
self.is_parameter_type_or_opt("CubemapSampler")
}
fn is_alpha_type(&self) -> bool {
self.is_parameter_type_or_opt("AlphaType")
}
fn is_properties_vec(&self) -> bool{
if let Some(param_ty) = self.parameter_type(){
if param_ty.ident == "Vec"{
if let Some(arg) = bracketed_first_argument(param_ty){
arg.ident == "Property"
}else{
false
}
}else{
false
}
}else{
false
}
}
fn is_data(&self) -> Result<bool, syn::Error> {
let has_data_attr = self.attrs.iter().any(|attr| attr.is_data());
let has_not_data_attr = self.attrs.iter().any(|attr| attr.is_not_data());
if has_data_attr && has_not_data_attr {
return Err(syn::Error::new(
self.ident.span(),
"A field can't have the data and no_data attributes simultaneously"
))
}
if has_data_attr {
return Ok(true)
}
if has_not_data_attr {
return Ok(false)
}
let is_data = !self.is_texture()
&& !self.is_alpha_type()
&& !self.is_properties_vec()
&& !self.is_uniform();
Ok(is_data)
}
fn parameter_name(&self) -> proc_macro2::TokenStream {
let parameter_name = self.name();
quote! {
#parameter_name,
}
}
fn parameter_ref(&self) -> proc_macro2::TokenStream {
let field_name = &self.ident;
let parameter_name = self.name();
quote! {
self.#field_name.as_any(#parameter_name),
}
}
fn parameter_mut(&self) -> proc_macro2::TokenStream {
let field_name = &self.ident;
let parameter_name = self.name();
quote! {
self.#field_name.as_mut_any(#parameter_name),
}
}
fn parameter_ref_match(&self) -> proc_macro2::TokenStream{
if self.is_parameter() {
let field_name = &self.ident;
let parameter_name = self.name();
quote! {
#parameter_name => Some(self.#field_name.as_any(#parameter_name)),
}
}else{
quote!()
}
}
fn parameter_mut_match(&self) -> proc_macro2::TokenStream{
if self.is_parameter() {
let field_name = &self.ident;
let parameter_name = self.name();
quote! {
#parameter_name => Some(self.#field_name.as_mut_any(#parameter_name)),
}
}else{
quote!()
}
}
fn parameter_type_name_match(&self) -> proc_macro2::TokenStream{
if self.is_parameter() {
let parameter_name = self.name();
let parameter_type = self.parameter_type().unwrap();
quote! {
#parameter_name => Some(stringify!(#parameter_type)),
}
}else{
quote!()
}
}
fn is_parameter(&self) -> bool{
match &self.ty{
syn::Type::Path(path) => path.path.segments.last().map(|ty|{
if ty.ident == "Parameter" {
true
}else{
false
}
}).unwrap_or(false),
_ => false,
}
}
fn default_value(&self) -> Option<proc_macro2::TokenStream>{
self.attrs.iter()
.filter_map(|attr| attr.as_default_value())
.next()
.map(|default_value| match default_value{
syn::Lit::Str(litstr) => match syn::parse_str(&litstr.value()){
Ok(default_value) => default_value,
Err(err) => err.to_compile_error()
}
syn::Lit::ByteStr(litstr) => match String::from_utf8(litstr.value()){
Ok(default_value) => match syn::parse_str(&default_value){
Ok(default_value) => default_value,
Err(err) => err.to_compile_error()
},
Err(err) => syn::Error::new(
litstr.span(),
format!("{}", err)
).to_compile_error()
}
syn::Lit::Int(litint) => match litint.base10_parse::<u64>(){
Ok(default_value) => quote!(#default_value as _),
Err(err) => err.to_compile_error()
}
syn::Lit::Float(litint) => match litint.base10_parse::<f64>(){
Ok(default_value) => quote!(#default_value as _),
Err(err) => err.to_compile_error()
}
syn::Lit::Bool(litbool) => quote!(#litbool),
syn::Lit::Byte(litbyte) => syn::Error::new(
litbyte.span(),
"Byte literal not supported in this position"
).to_compile_error(),
syn::Lit::Char(litchar) => syn::Error::new(
litchar.span(),
"Char literal not supported in this position"
).to_compile_error(),
syn::Lit::Verbatim(lit) => syn::Error::new(
lit.span(),
"Verbatim literal not supported in this position"
).to_compile_error(),
})
}
}
pub struct Material{
_vis: syn::Visibility,
_struct_token: syn::Token![struct],
ident: syn::Ident,
generics: syn::Generics,
_brace_token: syn::token::Brace,
fields: Punctuated<Parameter, syn::Token![,]>,
}
impl syn::parse::Parse for Material{
fn parse(input: syn::parse::ParseStream) -> syn::Result<Material>{
let content;
let _vis = input.parse()?;
let _struct_token = input.parse()?;
let ident = input.parse()?;
let generics = input.parse()?;
let _brace_token = syn::braced!(content in input);
let fields = Punctuated::parse_terminated(&content)?;
Ok(Material{
_vis,
_struct_token,
ident,
generics,
_brace_token,
fields,
})
}
}
impl Material {
pub fn impl_material(&self, attributes: Vec<syn::Attribute>, module_name: &syn::Path) -> proc_macro2::TokenStream {
let name = &self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let mut impl_defualt = false;
for attr in attributes.iter() {
match attr.parse_args::<syn::Path>(){
Ok(tts) => if tts.is_ident("default") {
impl_defualt = true
}else{
return syn::Error::new(
attr.tokens.span(),
"Material attribute not supported, only default suppoted in this position"
).to_compile_error()
},
Err(err) => return err.to_compile_error()
}
}
let non_skip_params = self.fields.iter()
.filter(|parameter| !parameter.is_skip())
.collect::<Vec<_>>();
let parameters_name = non_skip_params.iter()
.map(|p| p.parameter_name());
let parameters_ref = non_skip_params.iter()
.map(|p| p.parameter_ref());
let parameters_mut = non_skip_params.iter()
.map(|p| p.parameter_mut());
let parameters_ref_match = non_skip_params.iter()
.map(|p| p.parameter_ref_match());
let parameters_mut_match = non_skip_params.iter()
.map(|p| p.parameter_mut_match());
let parameters_type_name_match = non_skip_params.iter()
.map(|p| p.parameter_type_name_match());
let field_reset_name = self.fields.iter()
.filter(|parameter| !parameter.is_skip() || parameter.is_parameter())
.map(|p| &p.ident);
let uniforms = non_skip_params.iter()
.filter_map(|parameter| {
let field_name = ¶meter.ident;
parameter.uniform_name().map(|name|
quote!(self.#field_name.as_uniform_ref(#name))
)
});
let uniform_fields = non_skip_params.iter()
.filter_map(|parameter| {
if parameter.is_uniform() {
Some(¶meter.ident)
}else{
None
}
});
let option_uniform_fields = non_skip_params.iter()
.filter(|parameter| parameter.is_option_parameter())
.filter_map(|parameter|
if parameter.is_uniform() {
Some(¶meter.ident)
}else{
None
}
);
let has_uniforms = non_skip_params.iter().any(|p| p.is_uniform());
let textures = non_skip_params.iter()
.filter_map(|parameter| {
if parameter.is_texture() {
let field_name = ¶meter.ident;
let name = parameter.name();
Some(quote!(self.#field_name.as_uniform_ref(#name)))
}else{
None
}
});
let texture_fields = non_skip_params.iter()
.filter_map(|parameter|
if parameter.is_texture() {
Some(¶meter.ident)
}else{
None
});
let option_texture_fields = non_skip_params.iter()
.filter(|parameter| parameter.is_option_parameter())
.filter_map(|parameter|
if parameter.is_texture() {
Some(¶meter.ident)
}else{
None
});
let has_textures = non_skip_params.iter().any(|p| p.is_texture());
let cubemaps = non_skip_params.iter()
.filter_map(|parameter| {
if parameter.is_cubemap() {
let field_name = ¶meter.ident;
let name = parameter.name();
Some(quote!(self.#field_name.as_uniform_ref(#name)))
}else{
None
}
});
let cubemap_fields = non_skip_params.iter()
.filter_map(|parameter|
if parameter.is_cubemap() {
Some(¶meter.ident)
}else{
None
});
let option_cubemap_fields = non_skip_params.iter()
.filter(|parameter| parameter.is_option_parameter())
.filter_map(|parameter|
if parameter.is_cubemap() {
Some(¶meter.ident)
}else{
None
});
let has_cubemaps = non_skip_params.iter().any(|p| p.is_cubemap());
let data_fields = non_skip_params.iter()
.filter_map(|parameter|
match parameter.is_data() {
Ok(is_data) => if is_data {
let field_name = ¶meter.ident;
Some(quote!(#field_name))
}else{
None
}
Err(err) => Some(err.to_compile_error())
}
);
let data_parameters = non_skip_params.iter()
.filter_map(|parameter| {
match parameter.is_data() {
Ok(is_data) => if is_data {
let field_name = ¶meter.ident;
Some(quote!(self.#field_name))
}else{
None
}
Err(err) => Some(err.to_compile_error())
}
});
let has_data = non_skip_params.iter().any(|p| if let Ok(is_data) = p.is_data() {
is_data
}else{
false
});
let option_data_fields = non_skip_params.iter()
.filter(|parameter| parameter.is_option_parameter())
.filter_map(|parameter|
match parameter.is_data() {
Ok(is_data) => if is_data {
let field_name = ¶meter.ident;
Some(quote!(#field_name))
}else{
None
}
Err(err) => Some(err.to_compile_error())
}
);
let alpha_type = non_skip_params.iter()
.find(|parameter| parameter.is_alpha_type())
.map(|parameter| {
let alpha_ty_field = ¶meter.ident;
quote!(Some(&self.#alpha_ty_field))
}).unwrap_or_else(|| quote!(None));
let properties = non_skip_params.iter()
.find(|parameter| parameter.is_properties_vec())
.map(|parameter| {
let properties_field = ¶meter.ident;
quote!(Some(&self.#properties_field))
}).unwrap_or_else(|| quote!(None));
let material_impl = quote!{
impl #impl_generics #module_name::Material for #name #ty_generics #where_clause {
fn type_name(&self) -> &str {
stringify!(#name #ty_generics)
}
fn parameter_names(&self) -> Vec<&str>{
vec![#(#parameters_name)*]
}
fn parameters(&self) -> Vec<#module_name::ParameterAny>{
vec![#(#parameters_ref)*]
}
fn parameters_mut(&mut self) -> Vec<#module_name::ParameterMutAny>{
vec![#(#parameters_mut)*]
}
fn parameter(&self, name: &str) -> Option<#module_name::ParameterAny>{
match name{
#(#parameters_ref_match)*
_ => None
}
}
fn parameter_mut(&mut self, name: &str) -> Option<#module_name::ParameterMutAny>{
match name{
#(#parameters_mut_match)*
_ => None
}
}
fn parameter_type_name(&self, name: &str) -> Option<&str>{
match name{
#(#parameters_type_name_match)*
_ => None
}
}
fn reset_changed(&mut self){
#(
self.#field_reset_name.reset_changed();
)*
}
}
impl #impl_generics #module_name::MaterialParameterTypes for #name #ty_generics #where_clause {
fn uniform_parameters(&self) -> Vec<#module_name::UniformRef>{
vec![#(
#uniforms,
)*]
}
fn texture_parameters(&self) -> Vec<#module_name::UniformRef>{
vec![#(
#textures,
)*]
}
fn cubemap_parameters(&self) -> Vec<#module_name::UniformRef>{
vec![#(
#cubemaps,
)*]
}
fn properties_parameter(&self) -> Option<&#module_name::Parameter<Vec<#module_name::Property>>>{
#properties
}
fn alpha_type_parameter(&self) -> Option<&#module_name::Parameter<#module_name::AlphaType>>{
#alpha_type
}
fn any_uniform_changed(&self) -> bool{
#(self.#uniform_fields.has_changed() || )* false
}
fn any_texture_changed(&self) -> bool{
#(self.#texture_fields.has_changed() || ) * false
}
fn any_cubemap_changed(&self) -> bool{
#(self.#cubemap_fields.has_changed() || ) * false
}
fn any_data_changed(&self) -> bool{
#(self.#data_fields.has_changed() || ) * false
}
fn has_uniforms(&self) -> bool{
#has_uniforms
}
fn has_data(&self) -> bool{
#has_data
}
fn has_textures(&self) -> bool{
#has_textures
}
fn has_cubemaps(&self) -> bool{
#has_cubemaps
}
fn any_uniform_option_changed(&self) -> bool{
#(self.#option_uniform_fields.option_changed() || ) * false
}
fn any_texture_option_changed(&self) -> bool{
#(self.#option_texture_fields.option_changed() || ) * false
}
fn any_cubemap_option_changed(&self) -> bool{
#(self.#option_cubemap_fields.option_changed() || ) * false
}
fn any_data_option_changed(&self) -> bool{
#(self.#option_data_fields.option_changed() || ) * false
}
}
impl #impl_generics std140_data::WriteStd140 for #name #ty_generics #where_clause {
fn write_std140(&self, data: &mut std140_data::Data){
if #has_data {
let mut material_data = data.start_struct();
#(
material_data.push(&#data_parameters);
)*
}
}
}
};
if impl_defualt {
let default_fields: proc_macro2::TokenStream = self.fields.iter()
.map(|parameter| {
let field_name = ¶meter.ident;
if let Some(default_value) = parameter.default_value() {
quote! {
#field_name: #module_name::Parameter::new(#default_value),
}
}else{
quote! {
#field_name: Default::default(),
}
}
}).collect();
let default_impl = quote!{
impl #impl_generics Default for #name #ty_generics #where_clause{
fn default() -> #name #ty_generics {
#name {
#default_fields
}
}
}
impl #impl_generics #name #ty_generics #where_clause {
pub fn new() -> #name #ty_generics {
#name::default()
}
}
};
material_impl.into_iter().chain(default_impl).collect()
}else{
material_impl
}
}
}