1use iref::IriBuf;
48use proc_macro::TokenStream;
49use proc_macro2::TokenTree;
50use quote::quote;
51use std::collections::HashMap;
52
53macro_rules! error {
54 ( $( $x:expr ),* ) => {
55 {
56 let msg = format!($($x),*);
57 let tokens: TokenStream = format!("compile_error!(\"{}\");", msg).parse().unwrap();
58 tokens
59 }
60 };
61}
62
63fn filter_attribute(
64 attr: syn::Attribute,
65 name: &str,
66) -> Result<Option<proc_macro2::TokenStream>, TokenStream> {
67 if let Some(attr_id) = attr.path.get_ident() {
68 if attr_id == name {
69 if let Some(TokenTree::Group(group)) = attr.tokens.into_iter().next() {
70 Ok(Some(group.stream()))
71 } else {
72 Err(error!("malformed `{}` attribute", name))
73 }
74 } else {
75 Ok(None)
76 }
77 } else {
78 Ok(None)
79 }
80}
81
82fn expand_iri(value: &str, prefixes: &HashMap<String, IriBuf>) -> Result<IriBuf, ()> {
83 if let Some(index) = value.find(':') {
84 if index > 0 {
85 let (prefix, suffix) = value.split_at(index);
86 let suffix = &suffix[1..suffix.len()];
87
88 if !suffix.starts_with("//") {
89 if let Some(base_iri) = prefixes.get(prefix) {
90 let concat = base_iri.as_str().to_string() + suffix;
91 if let Ok(iri) = IriBuf::new(concat) {
92 return Ok(iri);
93 } else {
94 return Err(());
95 }
96 }
97 }
98 }
99 }
100
101 if let Ok(iri) = IriBuf::new(value.to_owned()) {
102 Ok(iri)
103 } else {
104 Err(())
105 }
106}
107
108#[proc_macro_derive(IriEnum, attributes(iri_prefix, iri))]
109pub fn iri_enum_derive(input: TokenStream) -> TokenStream {
110 let ast: syn::DeriveInput = syn::parse(input).unwrap();
111
112 let mut prefixes = HashMap::new();
113 for attr in ast.attrs {
114 match filter_attribute(attr, "iri_prefix") {
115 Ok(Some(tokens)) => {
116 let mut tokens = tokens.into_iter();
117 if let Some(token) = tokens.next() {
118 if let Ok(prefix) = string_literal_token(token) {
119 if tokens.next().is_some() {
120 if let Some(token) = tokens.next() {
121 if let Ok(iri) = string_literal_token(token) {
122 match IriBuf::new(iri) {
123 Ok(iri) => {
124 prefixes.insert(prefix, iri);
125 }
126 Err(e) => {
127 return error!(
128 "invalid IRI `{}` for prefix `{}`",
129 e.0, prefix
130 );
131 }
132 }
133 } else {
134 return error!("expected a string literal");
135 }
136 } else {
137 return error!("expected a string literal");
138 }
139 } else {
140 return error!("expected `=` literal");
141 }
142 } else {
143 return error!("expected a string literal");
144 }
145 } else {
146 return error!("expected a string literal");
147 }
148 }
149 Ok(None) => (),
150 Err(tokens) => return tokens,
151 }
152 }
153
154 match ast.data {
155 syn::Data::Enum(e) => {
156 let type_id = ast.ident;
157 let mut try_from = proc_macro2::TokenStream::new();
158 let mut try_from_default = quote! { Err(()) };
159 let mut into = proc_macro2::TokenStream::new();
160
161 for variant in e.variants {
162 let variant_ident = variant.ident;
163 let mut variant_iri: Option<IriBuf> = None;
164
165 for attr in variant.attrs {
166 match filter_attribute(attr, "iri") {
167 Ok(Some(tokens)) => match string_literal(tokens) {
168 Ok(str) => {
169 if let Ok(iri) = expand_iri(str.as_str(), &prefixes) {
170 variant_iri = Some(iri)
171 } else {
172 return error!(
173 "invalid IRI `{}` for variant `{}`",
174 str, variant_ident
175 );
176 }
177 }
178 Err(_) => return error!("malformed `iri` attribute"),
179 },
180 Ok(None) => (),
181 Err(tokens) => return tokens,
182 }
183 }
184
185 match variant.fields {
186 syn::Fields::Unit => {
187 if let Some(iri) = variant_iri {
188 let iri = iri.as_str();
189
190 try_from.extend(quote! {
191 _ if iri == static_iref::iri!(#iri) => Ok(#type_id::#variant_ident),
192 });
193
194 into.extend(quote! {
195 #type_id::#variant_ident => static_iref::iri!(#iri),
196 });
197 } else {
198 return error!("missing IRI for enum variant `{}`", variant_ident);
199 }
200 }
201 syn::Fields::Named(_) => {
202 return error!("variants with named fields are unsupported")
203 }
204 syn::Fields::Unnamed(fields) => {
205 if fields.unnamed.len() == 1 {
206 let field = fields.unnamed.into_iter().next().unwrap();
207 let ty = field.ty;
208
209 try_from_default = quote! {
210 match #ty::try_from(iri) {
211 Ok(value) => Ok(#type_id::#variant_ident(value)),
212 Err(_) => {
213 #try_from_default
214 }
215 }
216 };
217
218 into.extend(quote! {
219 #type_id::#variant_ident(v) => v.into(),
220 });
221 } else {
222 return error!(
223 "variants with named more than one field are unsupported"
224 );
225 }
226 }
227 }
228 }
229
230 let output = quote! {
231 impl<'a> ::std::convert::TryFrom<&'a ::iref::Iri> for #type_id {
232 type Error = ();
233
234 #[inline]
235 fn try_from(iri: &'a ::iref::Iri) -> ::std::result::Result<#type_id, ()> {
236 match iri {
237 #try_from
238 _ => #try_from_default
239 }
240 }
241 }
242
243 impl<'a, 'i> From<&'a #type_id> for &'i ::iref::Iri {
244 #[inline]
245 fn from(vocab: &'a #type_id) -> &'i ::iref::Iri {
246 match vocab {
247 #into
248 }
249 }
250 }
251
252 impl<'i> From<#type_id> for &'i ::iref::Iri {
253 #[inline]
254 fn from(vocab: #type_id) -> &'i ::iref::Iri {
255 <&::iref::Iri as From<&#type_id>>::from(&vocab)
256 }
257 }
258
259 impl<'a, 'i> From<&'a #type_id> for &'i ::iref::IriRef {
260 #[inline]
261 fn from(vocab: &'a #type_id) -> &'i ::iref::IriRef {
262 <&::iref::Iri as From<&#type_id>>::from(vocab).as_iri_ref()
263 }
264 }
265
266 impl<'i> From<#type_id> for &'i ::iref::IriRef {
267 #[inline]
268 fn from(vocab: #type_id) -> &'i ::iref::IriRef {
269 <&::iref::Iri as From<#type_id>>::from(vocab).as_iri_ref()
270 }
271 }
272
273 impl AsRef<iref::Iri> for #type_id {
274 #[inline]
275 fn as_ref(&self) -> &::iref::Iri {
276 <&::iref::Iri as From<&#type_id>>::from(self)
277 }
278 }
279
280 impl AsRef<iref::IriRef> for #type_id {
281 #[inline]
282 fn as_ref(&self) -> &::iref::IriRef {
283 <&::iref::IriRef as From<&#type_id>>::from(self)
284 }
285 }
286 };
287
288 output.into()
289 }
290 _ => {
291 error!("only enums are handled by IriEnum")
292 }
293 }
294}
295
296fn string_literal(tokens: proc_macro2::TokenStream) -> Result<String, &'static str> {
297 if let Some(token) = tokens.into_iter().next() {
298 string_literal_token(token)
299 } else {
300 Err("expected one string parameter")
301 }
302}
303
304fn string_literal_token(token: proc_macro2::TokenTree) -> Result<String, &'static str> {
305 if let TokenTree::Literal(lit) = token {
306 let str = lit.to_string();
307
308 if str.len() >= 2 {
309 let mut buffer = String::with_capacity(str.len() - 2);
310 for (i, c) in str.chars().enumerate() {
311 if i == 0 || i == str.len() - 1 {
312 if c != '"' {
313 return Err("expected string literal");
314 }
315 } else {
316 buffer.push(c)
317 }
318 }
319
320 Ok(buffer)
321 } else {
322 Err("expected string literal")
323 }
324 } else {
325 Err("expected string literal")
326 }
327}