use iref::IriBuf;
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use quote::quote;
use std::collections::HashMap;
macro_rules! error {
( $( $x:expr ),* ) => {
{
let msg = format!($($x),*);
let tokens: TokenStream = format!("compile_error!(\"{}\");", msg).parse().unwrap();
tokens
}
};
}
fn filter_attribute(
attr: syn::Attribute,
name: &str,
) -> Result<Option<proc_macro2::TokenStream>, TokenStream> {
if let Some(attr_id) = attr.path.get_ident() {
if attr_id == name {
if let Some(TokenTree::Group(group)) = attr.tokens.into_iter().next() {
Ok(Some(group.stream()))
} else {
Err(error!("malformed `{}` attribute", name))
}
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn expand_iri(value: &str, prefixes: &HashMap<String, IriBuf>) -> Result<IriBuf, ()> {
if let Some(index) = value.find(':') {
if index > 0 {
let (prefix, suffix) = value.split_at(index);
let suffix = &suffix[1..suffix.len()];
if !suffix.starts_with("//") {
if let Some(base_iri) = prefixes.get(prefix) {
let concat = base_iri.as_str().to_string() + suffix;
if let Ok(iri) = IriBuf::new(concat) {
return Ok(iri);
} else {
return Err(());
}
}
}
}
}
if let Ok(iri) = IriBuf::new(value.to_owned()) {
Ok(iri)
} else {
Err(())
}
}
#[proc_macro_derive(IriEnum, attributes(iri_prefix, iri))]
pub fn iri_enum_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let mut prefixes = HashMap::new();
for attr in ast.attrs {
match filter_attribute(attr, "iri_prefix") {
Ok(Some(tokens)) => {
let mut tokens = tokens.into_iter();
if let Some(token) = tokens.next() {
if let Ok(prefix) = string_literal_token(token) {
if tokens.next().is_some() {
if let Some(token) = tokens.next() {
if let Ok(iri) = string_literal_token(token) {
match IriBuf::new(iri) {
Ok(iri) => {
prefixes.insert(prefix, iri);
}
Err(e) => {
return error!(
"invalid IRI `{}` for prefix `{}`",
e.0, prefix
);
}
}
} else {
return error!("expected a string literal");
}
} else {
return error!("expected a string literal");
}
} else {
return error!("expected `=` literal");
}
} else {
return error!("expected a string literal");
}
} else {
return error!("expected a string literal");
}
}
Ok(None) => (),
Err(tokens) => return tokens,
}
}
match ast.data {
syn::Data::Enum(e) => {
let type_id = ast.ident;
let mut try_from = proc_macro2::TokenStream::new();
let mut try_from_default = quote! { Err(()) };
let mut into = proc_macro2::TokenStream::new();
for variant in e.variants {
let variant_ident = variant.ident;
let mut variant_iri: Option<IriBuf> = None;
for attr in variant.attrs {
match filter_attribute(attr, "iri") {
Ok(Some(tokens)) => match string_literal(tokens) {
Ok(str) => {
if let Ok(iri) = expand_iri(str.as_str(), &prefixes) {
variant_iri = Some(iri)
} else {
return error!(
"invalid IRI `{}` for variant `{}`",
str, variant_ident
);
}
}
Err(_) => return error!("malformed `iri` attribute"),
},
Ok(None) => (),
Err(tokens) => return tokens,
}
}
match variant.fields {
syn::Fields::Unit => {
if let Some(iri) = variant_iri {
let iri = iri.as_str();
try_from.extend(quote! {
_ if iri == static_iref::iri!(#iri) => Ok(#type_id::#variant_ident),
});
into.extend(quote! {
#type_id::#variant_ident => static_iref::iri!(#iri),
});
} else {
return error!("missing IRI for enum variant `{}`", variant_ident);
}
}
syn::Fields::Named(_) => {
return error!("variants with named fields are unsupported")
}
syn::Fields::Unnamed(fields) => {
if fields.unnamed.len() == 1 {
let field = fields.unnamed.into_iter().next().unwrap();
let ty = field.ty;
try_from_default = quote! {
match #ty::try_from(iri) {
Ok(value) => Ok(#type_id::#variant_ident(value)),
Err(_) => {
#try_from_default
}
}
};
into.extend(quote! {
#type_id::#variant_ident(v) => v.into(),
});
} else {
return error!(
"variants with named more than one field are unsupported"
);
}
}
}
}
let output = quote! {
impl<'a> ::std::convert::TryFrom<&'a ::iref::Iri> for #type_id {
type Error = ();
#[inline]
fn try_from(iri: &'a ::iref::Iri) -> ::std::result::Result<#type_id, ()> {
match iri {
#try_from
_ => #try_from_default
}
}
}
impl<'a, 'i> From<&'a #type_id> for &'i ::iref::Iri {
#[inline]
fn from(vocab: &'a #type_id) -> &'i ::iref::Iri {
match vocab {
#into
}
}
}
impl<'i> From<#type_id> for &'i ::iref::Iri {
#[inline]
fn from(vocab: #type_id) -> &'i ::iref::Iri {
<&::iref::Iri as From<&#type_id>>::from(&vocab)
}
}
impl<'a, 'i> From<&'a #type_id> for &'i ::iref::IriRef {
#[inline]
fn from(vocab: &'a #type_id) -> &'i ::iref::IriRef {
<&::iref::Iri as From<&#type_id>>::from(vocab).as_iri_ref()
}
}
impl<'i> From<#type_id> for &'i ::iref::IriRef {
#[inline]
fn from(vocab: #type_id) -> &'i ::iref::IriRef {
<&::iref::Iri as From<#type_id>>::from(vocab).as_iri_ref()
}
}
impl AsRef<iref::Iri> for #type_id {
#[inline]
fn as_ref(&self) -> &::iref::Iri {
<&::iref::Iri as From<&#type_id>>::from(self)
}
}
impl AsRef<iref::IriRef> for #type_id {
#[inline]
fn as_ref(&self) -> &::iref::IriRef {
<&::iref::IriRef as From<&#type_id>>::from(self)
}
}
};
output.into()
}
_ => {
error!("only enums are handled by IriEnum")
}
}
}
fn string_literal(tokens: proc_macro2::TokenStream) -> Result<String, &'static str> {
if let Some(token) = tokens.into_iter().next() {
string_literal_token(token)
} else {
Err("expected one string parameter")
}
}
fn string_literal_token(token: proc_macro2::TokenTree) -> Result<String, &'static str> {
if let TokenTree::Literal(lit) = token {
let str = lit.to_string();
if str.len() >= 2 {
let mut buffer = String::with_capacity(str.len() - 2);
for (i, c) in str.chars().enumerate() {
if i == 0 || i == str.len() - 1 {
if c != '"' {
return Err("expected string literal");
}
} else {
buffer.push(c)
}
}
Ok(buffer)
} else {
Err("expected string literal")
}
} else {
Err("expected string literal")
}
}