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!(), }
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}