cargo_autobindings/
lib.rs

1use convert_case::{Case, Casing};
2use proc_macro2::TokenTree;
3use std::time::Instant;
4use std::{
5    collections::HashMap,
6    fs::File,
7    io::{Read, Write},
8};
9use syn::{
10    punctuated::Punctuated, token::Comma, Attribute, Expr, ExprLit, Field, Fields, FieldsNamed,
11    Item, ItemEnum, ItemStruct, Lit, Path, Type, TypeArray, TypePath, TypeReference, Variant,
12};
13
14use crate::js_generate::js_process_file;
15use crate::py_generate::py_process_file;
16
17pub mod js_generate;
18pub mod py_generate;
19
20#[derive(Debug, Clone, Copy)]
21pub enum TargetLang {
22    Javascript,
23    Python,
24}
25
26pub fn generate(
27    instructions_path: &str,
28    instructions_enum_path: &str,
29    target_lang: TargetLang,
30    output_path: &str,
31) {
32    let now = Instant::now();
33    let path = std::path::Path::new(instructions_path);
34    let (instruction_tags, use_casting) = parse_instructions_enum(instructions_enum_path);
35    let directory = std::fs::read_dir(path).unwrap();
36    let mut output = get_header(target_lang);
37    for d in directory {
38        let file = d.unwrap();
39        let module_name = std::path::Path::new(&file.file_name())
40            .file_stem()
41            .unwrap()
42            .to_str()
43            .unwrap()
44            .to_owned();
45        let instruction_tag = instruction_tags
46            .get(&module_name)
47            .unwrap_or_else(|| panic!("Instruction not found for {}", module_name));
48        let s = match target_lang {
49            TargetLang::Javascript => js_process_file(
50                &module_name,
51                *instruction_tag,
52                file.path().to_str().unwrap(),
53                use_casting,
54            ),
55            TargetLang::Python => py_process_file(
56                &module_name,
57                *instruction_tag,
58                file.path().to_str().unwrap(),
59                use_casting,
60            ),
61        };
62        output.push_str(&s);
63    }
64
65    let mut out_file = File::create(output_path).unwrap();
66    out_file.write_all(output.as_bytes()).unwrap();
67
68    let elapsed = now.elapsed();
69    println!("✨  Done in {:.2?}", elapsed);
70}
71
72pub fn parse_instructions_enum(instructions_enum_path: &str) -> (HashMap<String, usize>, bool) {
73    let mut f = File::open(instructions_enum_path).unwrap();
74    let mut result_map = HashMap::new();
75    let mut raw_string = String::new();
76    f.read_to_string(&mut raw_string).unwrap();
77    let use_casting = raw_string.contains("get_instruction_cast");
78    let ast: syn::File = syn::parse_str(&raw_string).unwrap();
79    let instructions_enum = find_enum(&ast);
80    let enum_variants = get_enum_variants(instructions_enum);
81    for (
82        i,
83        Variant {
84            attrs: _,
85            ident,
86            fields: _,
87            discriminant: _,
88        },
89    ) in enum_variants.into_iter().enumerate()
90    {
91        let module_name = pascal_to_snake(&ident.to_string());
92        result_map.insert(module_name, i);
93    }
94    (result_map, use_casting)
95}
96
97pub fn get_header(target_lang: TargetLang) -> String {
98    match target_lang {
99        TargetLang::Javascript => include_str!("templates/template.ts").to_string(),
100        TargetLang::Python => include_str!("templates/template.py").to_string(),
101    }
102}
103
104fn get_simple_type(ty: &Type) -> String {
105    match ty {
106        Type::Path(TypePath {
107            qself: _,
108            path: Path {
109                leading_colon: _,
110                segments,
111            },
112        }) => segments.iter().next().unwrap().ident.to_string(),
113        _ => unimplemented!(),
114    }
115}
116
117fn padding_len(ty: &Type) -> u8 {
118    match ty {
119        Type::Path(TypePath {
120            qself: _,
121            path: Path {
122                leading_colon: _,
123                segments,
124            },
125        }) => {
126            let simple_type = segments.iter().next().unwrap().ident.to_string();
127            match simple_type.as_ref() {
128                "u8" => 1,
129                "u16" => 2,
130                "u32" => 4,
131                "u64" => 8,
132                "u128" => 16,
133                _ => unimplemented!(), // padding should be of types given above
134            }
135        }
136        Type::Array(TypeArray {
137            bracket_token: _,
138            elem,
139            semi_token: _,
140            len:
141                Expr::Lit(ExprLit {
142                    attrs: _,
143                    lit: Lit::Int(l),
144                }),
145        }) => padding_len(elem) * l.base10_parse::<u8>().unwrap(),
146        _ => unimplemented!(),
147    }
148}
149
150fn snake_to_camel(s: &str) -> String {
151    s.from_case(Case::Snake).to_case(Case::Camel)
152}
153fn snake_to_pascal(s: &str) -> String {
154    s.from_case(Case::Snake).to_case(Case::Pascal)
155}
156fn pascal_to_snake(s: &str) -> String {
157    s.from_case(Case::Pascal).to_case(Case::Snake)
158}
159fn lower_to_upper(s: &str) -> String {
160    s.from_case(Case::Lower).to_case(Case::Upper)
161}
162
163fn find_struct(ident_str: &str, file_ast: &syn::File) -> Item {
164    file_ast
165        .items
166        .iter()
167        .find(|a| {
168            if let Item::Struct(ItemStruct {
169                ident,
170                attrs: _,
171                vis: _,
172                struct_token: _,
173                generics: _,
174                fields: _,
175                semi_token: _,
176            }) = a
177            {
178                *ident == ident_str
179            } else {
180                false
181            }
182        })
183        .unwrap()
184        .clone()
185}
186
187fn find_enum(file_ast: &syn::File) -> Item {
188    file_ast
189        .items
190        .iter()
191        .find(|a| matches!(a, Item::Enum(_)))
192        .unwrap()
193        .clone()
194}
195
196fn get_enum_variants(s: Item) -> Punctuated<Variant, Comma> {
197    if let Item::Enum(ItemEnum {
198        attrs: _,
199        vis: _,
200        enum_token: _,
201        ident: _,
202        generics: _,
203        brace_token: _,
204        variants,
205    }) = s
206    {
207        variants
208    } else {
209        unreachable!()
210    }
211}
212
213fn get_struct_fields(s: Item) -> Punctuated<Field, Comma> {
214    if let Item::Struct(ItemStruct {
215        ident: _,
216        attrs: _,
217        vis: _,
218        struct_token: _,
219        generics: _,
220        fields:
221            Fields::Named(FieldsNamed {
222                named,
223                brace_token: _,
224            }),
225        semi_token: _,
226    }) = s
227    {
228        named
229    } else {
230        unreachable!()
231    }
232}
233
234fn get_constraints(attrs: &[Attribute]) -> (bool, bool) {
235    let mut writable = false;
236    let mut signer = false;
237    for a in attrs {
238        if a.path.is_ident("cons") {
239            let t = if let TokenTree::Group(g) = a.tokens.clone().into_iter().next().unwrap() {
240                g.stream()
241            } else {
242                panic!()
243            };
244
245            for constraint in t.into_iter() {
246                match constraint {
247                    TokenTree::Ident(i) => {
248                        if &i.to_string() == "writable" {
249                            writable = true;
250                        }
251                        if &i.to_string() == "signer" {
252                            signer = true;
253                        }
254                    }
255                    TokenTree::Punct(p) if p.as_char() == ',' => {}
256                    _ => {}
257                }
258            }
259            break;
260        }
261    }
262    (writable, signer)
263}
264
265fn is_slice(ty: &Type) -> bool {
266    if let Type::Reference(TypeReference {
267        and_token: _,
268        lifetime: _,
269        mutability: _,
270        elem,
271    }) = ty
272    {
273        let ty = *elem.clone();
274        if let Type::Slice(_) = ty {
275            return true;
276        }
277    }
278    false
279}
280
281fn is_option(ty: &Type) -> bool {
282    if let Type::Path(TypePath { qself: _, path }) = ty {
283        let seg = path.segments.iter().next().unwrap();
284        if seg.ident != "Option" {
285            unimplemented!()
286        }
287        return true;
288    }
289    false
290}