1#![recursion_limit="256"]
2
3extern crate proc_macro;
4extern crate proc_macro2;
5extern crate syn;
6#[macro_use]
7extern crate quote;
8extern crate heck;
9extern crate susyabi;
10
11mod constructor;
12mod contract;
13mod event;
14mod function;
15
16use std::{env, fs};
17use std::path::PathBuf;
18use heck::SnakeCase;
19use syn::export::Span;
20use susyabi::{Result, ResultExt, Contract, Param, ParamType};
21
22const ERROR_MSG: &'static str = "`derive(SabiContract)` failed";
23
24#[proc_macro_derive(SabiContract, attributes(susyabi_contract_options))]
25pub fn susyabi_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
26 let ast = syn::parse(input).expect(ERROR_MSG);
27 let gen = impl_susyabi_derive(&ast).expect(ERROR_MSG);
28 gen.into()
29}
30
31fn impl_susyabi_derive(ast: &syn::DeriveInput) -> Result<proc_macro2::TokenStream> {
32 let options = get_options(&ast.attrs, "susyabi_contract_options")?;
33 let path = get_option(&options, "path")?;
34 let normalized_path = normalize_path(&path)?;
35 let source_file = fs::File::open(&normalized_path)
36 .chain_err(|| format!("Cannot load contract abi from `{}`", normalized_path.display()))?;
37 let contract = Contract::load(source_file)?;
38 let c = contract::Contract::from(&contract);
39 Ok(c.generate())
40}
41
42fn get_options(attrs: &[syn::Attribute], name: &str) -> Result<Vec<syn::NestedMeta>> {
43 let options = attrs.iter()
44 .flat_map(syn::Attribute::interpret_meta)
45 .find(|meta| meta.name() == name);
46
47
48 match options {
49 Some(syn::Meta::List(list)) => Ok(list.nested.into_iter().collect()),
50 _ => Err("Unexpected meta item".into())
51 }
52}
53
54fn get_option(options: &[syn::NestedMeta], name: &str) -> Result<String> {
55 let item = options.iter()
56 .flat_map(|nested| match *nested {
57 syn::NestedMeta::Meta(ref meta) => Some(meta),
58 _ => None,
59 })
60 .find(|meta| meta.name() == name)
61 .chain_err(|| format!("Expected to find option {}", name))?;
62 str_value_of_meta_item(item, name)
63}
64
65fn str_value_of_meta_item(item: &syn::Meta, name: &str) -> Result<String> {
66 if let syn::Meta::NameValue(ref name_value) = *item {
67 if let syn::Lit::Str(ref value) = name_value.lit {
68 return Ok(value.value());
69 }
70 }
71
72 Err(format!(r#"`{}` must be in the form `#[{}="somsing"]`"#, name, name).into())
73}
74
75fn normalize_path(relative_path: &str) -> Result<PathBuf> {
76 let cargo_toml_directory = env::var("CARGO_MANIFEST_DIR").chain_err(|| "Cannot find manifest file")?;
78 let mut path: PathBuf = cargo_toml_directory.into();
79 path.push(relative_path);
80 Ok(path)
81}
82
83fn to_syntax_string(param_type: &susyabi::ParamType) -> proc_macro2::TokenStream {
84 match *param_type {
85 ParamType::Address => quote! { susyabi::ParamType::Address },
86 ParamType::Bytes => quote! { susyabi::ParamType::Bytes },
87 ParamType::Int(x) => quote! { susyabi::ParamType::Int(#x) },
88 ParamType::Uint(x) => quote! { susyabi::ParamType::Uint(#x) },
89 ParamType::Bool => quote! { susyabi::ParamType::Bool },
90 ParamType::String => quote! { susyabi::ParamType::String },
91 ParamType::Array(ref param_type) => {
92 let param_type_quote = to_syntax_string(param_type);
93 quote! { susyabi::ParamType::Array(Box::new(#param_type_quote)) }
94 },
95 ParamType::FixedBytes(x) => quote! { susyabi::ParamType::FixedBytes(#x) },
96 ParamType::FixedArray(ref param_type, ref x) => {
97 let param_type_quote = to_syntax_string(param_type);
98 quote! { susyabi::ParamType::FixedArray(Box::new(#param_type_quote), #x) }
99 }
100 }
101}
102
103fn to_susyabi_param_vec<'a, P: 'a>(params: P) -> proc_macro2::TokenStream
104 where P: IntoIterator<Item = &'a Param>
105{
106 let p = params.into_iter().map(|x| {
107 let name = &x.name;
108 let kind = to_syntax_string(&x.kind);
109 quote! {
110 susyabi::Param {
111 name: #name.to_owned(),
112 kind: #kind
113 }
114 }
115 }).collect::<Vec<_>>();
116
117 quote! { vec![ #(#p),* ] }
118}
119
120fn rust_type(input: &ParamType) -> proc_macro2::TokenStream {
121 match *input {
122 ParamType::Address => quote! { susyabi::Address },
123 ParamType::Bytes => quote! { susyabi::Bytes },
124 ParamType::FixedBytes(32) => quote! { susyabi::Hash },
125 ParamType::FixedBytes(size) => quote! { [u8; #size] },
126 ParamType::Int(_) => quote! { susyabi::Int },
127 ParamType::Uint(_) => quote! { susyabi::Uint },
128 ParamType::Bool => quote! { bool },
129 ParamType::String => quote! { String },
130 ParamType::Array(ref kind) => {
131 let t = rust_type(&*kind);
132 quote! { Vec<#t> }
133 },
134 ParamType::FixedArray(ref kind, size) => {
135 let t = rust_type(&*kind);
136 quote! { [#t, #size] }
137 }
138 }
139}
140
141fn template_param_type(input: &ParamType, index: usize) -> proc_macro2::TokenStream {
142 let t_ident = syn::Ident::new(&format!("T{}", index), Span::call_site());
143 let u_ident = syn::Ident::new(&format!("U{}", index), Span::call_site());
144 match *input {
145 ParamType::Address => quote! { #t_ident: Into<susyabi::Address> },
146 ParamType::Bytes => quote! { #t_ident: Into<susyabi::Bytes> },
147 ParamType::FixedBytes(32) => quote! { #t_ident: Into<susyabi::Hash> },
148 ParamType::FixedBytes(size) => quote! { #t_ident: Into<[u8; #size]> },
149 ParamType::Int(_) => quote! { #t_ident: Into<susyabi::Int> },
150 ParamType::Uint(_) => quote! { #t_ident: Into<susyabi::Uint> },
151 ParamType::Bool => quote! { #t_ident: Into<bool> },
152 ParamType::String => quote! { #t_ident: Into<String> },
153 ParamType::Array(ref kind) => {
154 let t = rust_type(&*kind);
155 quote! {
156 #t_ident: IntoIterator<Item = #u_ident>, #u_ident: Into<#t>
157 }
158 },
159 ParamType::FixedArray(ref kind, size) => {
160 let t = rust_type(&*kind);
161 quote! {
162 #t_ident: Into<[#u_ident; #size]>, #u_ident: Into<#t>
163 }
164 }
165 }
166}
167
168fn from_template_param(input: &ParamType, name: &syn::Ident) -> proc_macro2::TokenStream {
169 match *input {
170 ParamType::Array(_) => quote! { #name.into_iter().map(Into::into).collect::<Vec<_>>() },
171 ParamType::FixedArray(_, _) => quote! { (Box::new(#name.into()) as Box<[_]>).into_vec().into_iter().map(Into::into).collect::<Vec<_>>() },
172 _ => quote! {#name.into() },
173 }
174}
175
176fn to_token(name: &proc_macro2::TokenStream, kind: &ParamType) -> proc_macro2::TokenStream {
177 match *kind {
178 ParamType::Address => quote! { susyabi::Token::Address(#name) },
179 ParamType::Bytes => quote! { susyabi::Token::Bytes(#name) },
180 ParamType::FixedBytes(_) => quote! { susyabi::Token::FixedBytes(#name.to_vec()) },
181 ParamType::Int(_) => quote! { susyabi::Token::Int(#name) },
182 ParamType::Uint(_) => quote! { susyabi::Token::Uint(#name) },
183 ParamType::Bool => quote! { susyabi::Token::Bool(#name) },
184 ParamType::String => quote! { susyabi::Token::String(#name) },
185 ParamType::Array(ref kind) => {
186 let inner_name = quote! { inner };
187 let inner_loop = to_token(&inner_name, kind);
188 quote! {
189 {
191 let v = #name.into_iter().map(|#inner_name| #inner_loop).collect();
192 susyabi::Token::Array(v)
193 }
194 }
195 }
196 ParamType::FixedArray(ref kind, _) => {
197 let inner_name = quote! { inner };
198 let inner_loop = to_token(&inner_name, kind);
199 quote! {
200 {
202 let v = #name.into_iter().map(|#inner_name| #inner_loop).collect();
203 susyabi::Token::FixedArray(v)
204 }
205 }
206 },
207 }
208}
209
210fn from_token(kind: &ParamType, token: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
211 match *kind {
212 ParamType::Address => quote! { #token.to_address().expect(INTERNAL_ERR) },
213 ParamType::Bytes => quote! { #token.to_bytes().expect(INTERNAL_ERR) },
214 ParamType::FixedBytes(32) => quote! {
215 {
216 let mut result = [0u8; 32];
217 let v = #token.to_fixed_bytes().expect(INTERNAL_ERR);
218 result.copy_from_slice(&v);
219 susyabi::Hash::from(result)
220 }
221 },
222 ParamType::FixedBytes(size) => {
223 let size: syn::Index = size.into();
224 quote! {
225 {
226 let mut result = [0u8; #size];
227 let v = #token.to_fixed_bytes().expect(INTERNAL_ERR);
228 result.copy_from_slice(&v);
229 result
230 }
231 }
232 },
233 ParamType::Int(_) => quote! { #token.to_int().expect(INTERNAL_ERR) },
234 ParamType::Uint(_) => quote! { #token.to_uint().expect(INTERNAL_ERR) },
235 ParamType::Bool => quote! { #token.to_bool().expect(INTERNAL_ERR) },
236 ParamType::String => quote! { #token.to_string().expect(INTERNAL_ERR) },
237 ParamType::Array(ref kind) => {
238 let inner = quote! { inner };
239 let inner_loop = from_token(kind, &inner);
240 quote! {
241 #token.to_array().expect(INTERNAL_ERR).into_iter()
242 .map(|#inner| #inner_loop)
243 .collect()
244 }
245 },
246 ParamType::FixedArray(ref kind, size) => {
247 let inner = quote! { inner };
248 let inner_loop = from_token(kind, &inner);
249 let to_array = vec![quote! { iter.next() }; size];
250 quote! {
251 {
252 let iter = #token.to_array().expect(INTERNAL_ERR).into_iter()
253 .map(|#inner| #inner_loop);
254 [#(#to_array),*]
255 }
256 }
257 },
258 }
259}
260
261fn input_names(inputs: &Vec<Param>) -> Vec<syn::Ident> {
262 inputs
263 .iter()
264 .enumerate()
265 .map(|(index, param)| if param.name.is_empty() {
266 syn::Ident::new(&format!("param{}", index), Span::call_site())
267 } else {
268 syn::Ident::new(&rust_variable(¶m.name), Span::call_site())
269 })
270 .collect()
271}
272
273fn get_template_names(kinds: &Vec<proc_macro2::TokenStream>) -> Vec<syn::Ident> {
274 kinds.iter().enumerate()
275 .map(|(index, _)| syn::Ident::new(&format!("T{}", index), Span::call_site()))
276 .collect()
277}
278
279fn get_output_kinds(outputs: &Vec<Param>) -> proc_macro2::TokenStream {
280 match outputs.len() {
281 0 => quote! {()},
282 1 => {
283 let t = rust_type(&outputs[0].kind);
284 quote! { #t }
285 },
286 _ => {
287 let outs: Vec<_> = outputs
288 .iter()
289 .map(|param| rust_type(¶m.kind))
290 .collect();
291 quote! { (#(#outs),*) }
292 }
293 }
294}
295
296fn rust_variable(name: &str) -> String {
300 match name {
302 "self" => "_self".to_string(),
303 other => other.to_snake_case(),
304 }
305}