Skip to content
Merged
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Cargo.lock
/target/
target
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ license = "MIT / Apache-2.0"
unstable = []

[dependencies]
euclid_macros = { path = "./macros", version = "0.1" }
num-traits = { version = "0.2" }
serde = { version = "1.0", features = ["serde_derive"], optional = true }
mint = {version = "0.5.1", optional = true}
Expand Down
13 changes: 13 additions & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "euclid_macros"
version = "0.1.0"
authors = ["Emilio Cobos Álvarez <emilio@crisal.io>", "The Servo project developers"]

[lib]
path = "lib.rs"
proc-macro = true

[dependencies]
proc-macro2 = "0.4"
quote = "0.6"
syn = { version = "0.15", features = ["visit"] }
251 changes: 251 additions & 0 deletions macros/euclid_matrix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use proc_macro2::TokenStream;
use syn::{self, DeriveInput};

type Fields = syn::punctuated::Punctuated<syn::Field, syn::token::Comma>;

fn derive_trait<F>(
input: &DeriveInput,
trait_name: TokenStream,
generics: &syn::Generics,
body: F
) -> TokenStream
where
F: FnOnce() -> TokenStream,
{
let struct_name = &input.ident;

let (impl_generics, _, where_clause) = generics.split_for_impl();
let (_, ty_generics, _) = input.generics.split_for_impl();

let body = body();
quote! {
impl #impl_generics #trait_name for #struct_name #ty_generics #where_clause {
#body
}
}
}

fn derive_simple_trait<F>(
input: &DeriveInput,
trait_name: TokenStream,
t: &syn::TypeParam,
body: F,
) -> TokenStream
where
F: FnOnce() -> TokenStream,
{
let mut generics = input.generics.clone();
generics
.make_where_clause()
.predicates
.push(parse_quote!(#t: #trait_name));
derive_trait(input, trait_name, &generics, body)
}

fn each_field_except_unit<F>(
fields: &Fields,
unit: &syn::Field,
mut field_expr: F,
) -> TokenStream
where
F: FnMut(&syn::Ident) -> TokenStream,
{
fields.iter().filter(|f| f.ident != unit.ident).fold(quote! {}, |body, field| {
let name = field.ident.as_ref().unwrap();
let expr = field_expr(name);
quote! {
#body
#expr
}
})
}


fn derive_struct_body<F>(
fields: &Fields,
unit: &syn::Field,
mut field_expr: F,
) -> TokenStream
where
F: FnMut(&syn::Ident) -> TokenStream,
{
let body = each_field_except_unit(fields, unit, |name| {
let expr = field_expr(name);
quote! {
#name: #expr,
}
});

let unit_name = unit.ident.as_ref().unwrap();
quote! {
Self {
#body
#unit_name: PhantomData,
}
}
}

fn clone_impl(input: &DeriveInput, fields: &Fields, unit: &syn::Field, t: &syn::TypeParam) -> TokenStream {
derive_simple_trait(input, quote! { Clone }, t, || {
let body = derive_struct_body(fields, unit, |name| {
quote! { self.#name.clone() }
});
quote! {
fn clone(&self) -> Self {
#body
}
}
})
}

fn copy_impl(input: &DeriveInput, t: &syn::TypeParam) -> TokenStream {
derive_simple_trait(input, quote!{ Copy }, t, || quote! {})
}

fn eq_impl(input: &DeriveInput, t: &syn::TypeParam) -> TokenStream {
derive_simple_trait(input, quote!{ ::core::cmp::Eq }, t, || quote! {})
}

fn partialeq_impl(input: &DeriveInput, fields: &Fields, unit: &syn::Field, t: &syn::TypeParam) -> TokenStream {
derive_simple_trait(input, quote!{ ::core::cmp::PartialEq }, t, || {
let body = each_field_except_unit(fields, unit, |name| {
quote! { && self.#name == other.#name }
});

quote! {
fn eq(&self, other: &Self) -> bool {
true #body
}
}
})
}

fn hash_impl(input: &DeriveInput, fields: &Fields, unit: &syn::Field, t: &syn::TypeParam) -> TokenStream {
derive_simple_trait(input, quote!{ ::core::hash::Hash }, t, || {
let body = each_field_except_unit(fields, unit, |name| {
quote! { self.#name.hash(h); }
});

quote! {
fn hash<H: ::core::hash::Hasher>(&self, h: &mut H) {
#body
}
}
})
}

fn serde_impl(
input: &DeriveInput,
fields: &Fields,
unit: &syn::Field,
t: &syn::TypeParam,
) -> TokenStream {
let deserialize_impl = {
let mut generics = input.generics.clone();
generics.params.insert(0, parse_quote!('de));
generics
.make_where_clause()
.predicates
.push(parse_quote!(#t: ::serde::Deserialize<'de>));
derive_trait(input, quote!{ ::serde::Deserialize<'de> }, &generics, || {
let tuple = each_field_except_unit(fields, unit, |name| {
quote! { #name, }
});
let body = derive_struct_body(fields, unit, |name| quote! { #name });
quote! {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
let (#tuple) = ::serde::Deserialize::deserialize(deserializer)?;
Ok(#body)
}
}
})
};

let serialize_impl = derive_simple_trait(input, quote! { ::serde::Serialize }, t, || {
let tuple = each_field_except_unit(fields, unit, |name| {
quote! { &self.#name, }
});
quote! {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
(#tuple).serialize(serializer)
}
}
});

quote! {
#[cfg(feature = "serde")]
#serialize_impl
#[cfg(feature = "serde")]
#deserialize_impl
}
}

pub fn derive(input: DeriveInput) -> TokenStream {
let s = match input.data {
syn::Data::Struct(ref s) => s,
_ => panic!("Need to derive this on a struct"),
};

let fields = match s.fields {
syn::Fields::Named(ref named) => &named.named,
_ => panic!("Need to use named fields"),
};

assert!(!fields.is_empty());

let unit_field = fields.last().unwrap();
assert_eq!(
unit_field.value().ident.as_ref().unwrap().to_string(),
"_unit",
"You need to have a _unit field to derive this trait",
);

assert!(match unit_field.value().vis {
syn::Visibility::Public(..) => true,
_ => false,
}, "Unit field should be public");

assert!(input.attrs.iter().filter_map(|attr| attr.interpret_meta()).any(|attr| {
match attr {
syn::Meta::Word(..) |
syn::Meta::NameValue(..) => false,
syn::Meta::List(ref list) => {
list.ident == "repr" && list.nested.iter().any(|meta| {
match *meta {
syn::NestedMeta::Meta(syn::Meta::Word(ref w)) => w == "C",
_ => false,
}
})
}
}
}), "struct should be #[repr(C)]");

let type_param =
input.generics.type_params().next().cloned().expect("Need a T");

let clone = clone_impl(&input, fields, unit_field.value(), &type_param);
let copy = copy_impl(&input, &type_param);
let serde = serde_impl(&input, fields, unit_field.value(), &type_param);
let eq = eq_impl(&input, &type_param);
let partialeq = partialeq_impl(&input, fields, unit_field.value(), &type_param);
let hash = hash_impl(&input, fields, unit_field.value(), &type_param);

quote! {
#clone
#copy
#serde
#eq
#partialeq
#hash
}
}
20 changes: 20 additions & 0 deletions macros/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

use proc_macro::TokenStream;

mod euclid_matrix;

#[proc_macro_derive(EuclidMatrix)]
pub fn derive_euclid_matrix(input: TokenStream) -> TokenStream {
let input = syn::parse(input).unwrap();
euclid_matrix::derive(input).into()
}
18 changes: 10 additions & 8 deletions src/homogen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ use core::marker::PhantomData;
use core::ops::Div;


define_matrix! {
/// Homogeneous vector in 3D space.
pub struct HomogeneousVector<T, U> {
pub x: T,
pub y: T,
pub z: T,
pub w: T,
}
/// Homogeneous vector in 3D space.
#[derive(EuclidMatrix)]
#[repr(C)]
pub struct HomogeneousVector<T, U> {
pub x: T,
pub y: T,
pub z: T,
pub w: T,
#[doc(hidden)]
pub _unit: PhantomData<U>,
}


Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ extern crate serde;

#[cfg(feature = "mint")]
pub extern crate mint;
#[macro_use]
extern crate euclid_macros;
extern crate num_traits;
#[cfg(test)]
extern crate rand;
Expand Down
Loading