probe_rs_t2rust/
lib.rs

1use std::fs;
2use std::fs::{read_dir, read_to_string};
3use std::io;
4use std::path::{Path, PathBuf};
5
6/// Parse all target description files in the input directory and create
7/// a single output file with the Rust source code
8/// for all targets.
9pub fn run(input_dir: impl AsRef<Path>, output_file: impl AsRef<Path>) {
10    // Determine all config files to parse.
11    let mut files = vec![];
12    visit_dirs(input_dir.as_ref(), &mut files).unwrap();
13
14    let output_file = output_file.as_ref();
15
16    let output_dir = output_file.parent().unwrap();
17
18    let mut configs: Vec<proc_macro2::TokenStream> = vec![];
19    for file in files {
20        let string = read_to_string(&file).expect(
21            "Algorithm definition file could not be read. This is a bug. Please report it.",
22        );
23
24        let yaml: Result<serde_yaml::Value, _> = serde_yaml::from_str(&string);
25
26        match yaml {
27            Ok(chip) => {
28                let chip = extract_chip_family(&chip, output_dir);
29                configs.push(chip);
30            }
31            Err(e) => {
32                panic!("Failed to parse target file: {:?} because:\n{}", file, e);
33            }
34        }
35    }
36
37    let include_stream = if configs.is_empty() {
38        quote::quote! {}
39    } else {
40        quote::quote! {
41            #[allow(unused_imports)]
42            use jep106::JEP106Code;
43            use crate::config::{Chip, RawFlashAlgorithm, FlashRegion, MemoryRegion, RamRegion, SectorDescription, FlashProperties};
44
45            use std::borrow::Cow;
46        }
47    };
48
49    let target_count = configs.len();
50
51    let stream = quote::quote! {
52        #include_stream
53        use crate::config::ChipFamily;
54
55        #[allow(clippy::unreadable_literal)]
56        pub const TARGETS: [ChipFamily;#target_count] = [
57            #(#configs,)*
58        ];
59
60        pub fn get_targets() -> &'static [ChipFamily] {
61            &TARGETS
62        }
63    };
64
65    fs::write(output_file, stream.to_string()).expect("Writing build.rs output failed.");
66}
67
68// one possible implementation of walking a directory only visiting files
69fn visit_dirs(dir: &Path, targets: &mut Vec<PathBuf>) -> io::Result<()> {
70    if dir.is_dir() {
71        for entry in read_dir(dir)? {
72            let entry = entry?;
73            let path = entry.path();
74            if path.is_dir() {
75                visit_dirs(&path, targets)?;
76            } else {
77                targets.push(path.to_owned());
78            }
79        }
80    }
81    Ok(())
82}
83
84/// Creates a properly quoted Option<T>` `TokenStream` from an `Option<T>`.
85fn quote_option<T: quote::ToTokens>(option: Option<T>) -> proc_macro2::TokenStream {
86    if let Some(value) = option {
87        quote::quote! {
88            Some(#value)
89        }
90    } else {
91        quote::quote! {
92            None
93        }
94    }
95}
96
97/// Extracts a list of algorithm token streams from a yaml value.
98fn extract_algorithms(
99    chip: &serde_yaml::Value,
100    output_dir: &Path,
101) -> Vec<proc_macro2::TokenStream> {
102    // Get an iterator over all the algorithms contained in the chip value obtained from the yaml file.
103    let algorithm_iter = chip
104        .get("flash_algorithms")
105        .unwrap()
106        .as_mapping()
107        .unwrap()
108        .iter();
109
110    algorithm_iter
111        .map(|(_name, algorithm)| {
112            // Extract all values and form them into a struct.
113            let name = algorithm
114                .get("name")
115                .unwrap()
116                .as_str()
117                .unwrap()
118                .to_ascii_lowercase();
119            let description = algorithm
120                .get("description")
121                .unwrap()
122                .as_str()
123                .unwrap()
124                .to_ascii_lowercase();
125            let default = algorithm.get("default").unwrap().as_bool().unwrap();
126            let instructions: Vec<u8> =
127                base64::decode(algorithm.get("instructions").unwrap().as_str().unwrap()).unwrap();
128            let pc_init =
129                quote_option(algorithm.get("pc_init").unwrap().as_u64().map(|v| v as u32));
130            let pc_uninit = quote_option(
131                algorithm
132                    .get("pc_uninit")
133                    .unwrap()
134                    .as_u64()
135                    .map(|v| v as u32),
136            );
137            let pc_program_page =
138                algorithm.get("pc_program_page").unwrap().as_u64().unwrap() as u32;
139            let pc_erase_sector =
140                algorithm.get("pc_erase_sector").unwrap().as_u64().unwrap() as u32;
141            let pc_erase_all = quote_option(
142                algorithm
143                    .get("pc_erase_all")
144                    .unwrap()
145                    .as_u64()
146                    .map(|v| v as u32),
147            );
148            let data_section_offset = algorithm
149                .get("data_section_offset")
150                .unwrap()
151                .as_u64()
152                .unwrap() as u32;
153
154            let flash_properties = algorithm.get("flash_properties").unwrap();
155
156            let range = flash_properties.get("address_range").unwrap();
157            let start = range.get("start").unwrap().as_u64().unwrap() as u32;
158            let end = range.get("end").unwrap().as_u64().unwrap() as u32;
159            let page_size = flash_properties.get("page_size").unwrap().as_u64().unwrap() as u32;
160            let erased_byte_value = flash_properties
161                .get("erased_byte_value")
162                .unwrap()
163                .as_u64()
164                .unwrap() as u8;
165            let program_page_timeout = flash_properties
166                .get("program_page_timeout")
167                .unwrap()
168                .as_u64()
169                .unwrap() as u32;
170            let erase_sector_timeout = flash_properties
171                .get("erase_sector_timeout")
172                .unwrap()
173                .as_u64()
174                .unwrap() as u32;
175
176            // get all sectors
177            let sectors = extract_sectors(&flash_properties);
178
179            // write flash algorithm into separate file
180
181            let mut algorithm_file_name = name.replace(" ", "_");
182
183            algorithm_file_name.push_str(".bin");
184
185            let algorithm_path = output_dir.join(&algorithm_file_name);
186
187            fs::write(&algorithm_path, &instructions).unwrap();
188
189            // Quote the algorithm struct.
190            let algorithm = quote::quote! {
191                RawFlashAlgorithm {
192                    name: Cow::Borrowed(#name),
193                    description: Cow::Borrowed(#description),
194                    default: #default,
195                    instructions: Cow::Borrowed(include_bytes!(#algorithm_file_name)),
196                    pc_init: #pc_init,
197                    pc_uninit: #pc_uninit,
198                    pc_program_page: #pc_program_page,
199                    pc_erase_sector: #pc_erase_sector,
200                    pc_erase_all: #pc_erase_all,
201                    data_section_offset: #data_section_offset,
202                    flash_properties: FlashProperties {
203                        address_range: #start..#end,
204                        page_size: #page_size,
205                        erased_byte_value: #erased_byte_value,
206                        program_page_timeout: #program_page_timeout,
207                        erase_sector_timeout: #erase_sector_timeout,
208                        sectors: Cow::Borrowed(&[
209                            #(#sectors,)*
210                        ])
211                    },
212                }
213            };
214
215            algorithm
216        })
217        .collect()
218}
219
220fn extract_sectors(region: &serde_yaml::Value) -> Vec<proc_macro2::TokenStream> {
221    match region.get("sectors") {
222        Some(sectors) => {
223            let iter = sectors.as_sequence().unwrap().iter();
224
225            iter.map(|sector| {
226                let size = sector.get("size").unwrap().as_u64().unwrap() as u32;
227                let address = sector.get("address").unwrap().as_u64().unwrap() as u32;
228
229                quote::quote! {
230                    SectorDescription {
231                        size: #size,
232                        address: #address,
233                    }
234                }
235            })
236            .collect()
237        }
238        // Currently, sectors might be missing due to the old target generation code
239        // For that case, just create a single entry based on the old values
240        None => vec![],
241    }
242}
243
244/// Extracts a list of algorithm token streams from a yaml value.
245fn extract_memory_map(chip: &serde_yaml::Value) -> Vec<proc_macro2::TokenStream> {
246    // Get an iterator over all the algorithms contained in the chip value obtained from the yaml file.
247    let memory_map_iter = chip
248        .get("memory_map")
249        .unwrap()
250        .as_sequence()
251        .unwrap()
252        .iter();
253
254    memory_map_iter
255        .filter_map(|memory_region| {
256            // Check if it's a RAM region. If yes, parse it into a TokenStream.
257            memory_region
258                .get("Ram")
259                .map(|region| {
260                    let range = region.get("range").unwrap();
261                    let start = range.get("start").unwrap().as_u64().unwrap() as u32;
262                    let end = range.get("end").unwrap().as_u64().unwrap() as u32;
263                    let is_boot_memory = region.get("is_boot_memory").unwrap().as_bool().unwrap();
264
265                    quote::quote! {
266                        MemoryRegion::Ram(RamRegion {
267                            range: #start..#end,
268                            is_boot_memory: #is_boot_memory,
269                        })
270                    }
271                })
272                .or_else(|| {
273                    memory_region.get("Flash").map(|region| {
274                        let range = region.get("range").unwrap();
275                        let start = range.get("start").unwrap().as_u64().unwrap() as u32;
276                        let end = range.get("end").unwrap().as_u64().unwrap() as u32;
277                        let is_boot_memory =
278                            region.get("is_boot_memory").unwrap().as_bool().unwrap();
279
280                        quote::quote! {
281                            MemoryRegion::Flash(FlashRegion {
282                                range: #start..#end,
283                                is_boot_memory: #is_boot_memory,
284                            })
285                        }
286                    })
287                })
288        })
289        .collect()
290}
291
292/// Extracts a list of algorithm token streams from a yaml value.
293fn extract_variants(chip_family: &serde_yaml::Value) -> Vec<proc_macro2::TokenStream> {
294    // Get an iterator over all the algorithms contained in the chip value obtained from the yaml file.
295    let variants_iter = chip_family
296        .get("variants")
297        .unwrap()
298        .as_sequence()
299        .unwrap()
300        .iter();
301
302    variants_iter
303        .map(|variant| {
304            let name = variant.get("name").unwrap().as_str().unwrap();
305            let part = quote_option(
306                variant
307                    .get("part")
308                    .and_then(|v| v.as_u64().map(|v| v as u16)),
309            );
310
311            // Extract all the memory regions into a Vec of TookenStreams.
312            let memory_map = extract_memory_map(&variant);
313
314            let flash_algorithms = variant
315                .get("flash_algorithms")
316                .unwrap()
317                .as_sequence()
318                .unwrap();
319            let flash_algorithm_names = flash_algorithms.iter().map(|a| a.as_str().unwrap());
320            quote::quote! {
321                Chip {
322                    name: Cow::Borrowed(#name),
323                    part: #part,
324                    memory_map: Cow::Borrowed(&[
325                        #(#memory_map,)*
326                    ]),
327                    flash_algorithms: Cow::Borrowed(&[
328                        #(Cow::Borrowed(#flash_algorithm_names),)*
329                    ]),
330                }
331            }
332        })
333        .collect()
334}
335
336/// Extracts a chip family token stream from a yaml value.
337fn extract_chip_family(
338    chip_family: &serde_yaml::Value,
339    output_dir: &Path,
340) -> proc_macro2::TokenStream {
341    // Extract all the algorithms into a Vec of TokenStreams.
342    let algorithms = extract_algorithms(&chip_family, output_dir);
343
344    // Extract all the available variants into a Vec of TokenStreams.
345    let variants = extract_variants(&chip_family);
346
347    let name = chip_family
348        .get("name")
349        .unwrap()
350        .as_str()
351        .unwrap()
352        .to_ascii_lowercase();
353    let core = chip_family
354        .get("core")
355        .unwrap()
356        .as_str()
357        .unwrap()
358        .to_ascii_lowercase();
359    let manufacturer = quote_option(extract_manufacturer(&chip_family));
360
361    // Quote the chip.
362    let chip_family = quote::quote! {
363        ChipFamily {
364            name: Cow::Borrowed(#name),
365            manufacturer: #manufacturer,
366            flash_algorithms: Cow::Borrowed(&[
367                #(#algorithms,)*
368            ]),
369            variants: Cow::Borrowed(&[
370                #(#variants,)*
371            ]),
372            core: Cow::Borrowed(#core),
373        }
374    };
375
376    chip_family
377}
378
379/// Extracts the jep code token stream from a yaml value.
380fn extract_manufacturer(chip: &serde_yaml::Value) -> Option<proc_macro2::TokenStream> {
381    chip.get("manufacturer").and_then(|manufacturer| {
382        let cc = manufacturer.get("cc").map(|v| v.as_u64().unwrap() as u8);
383        let id = manufacturer.get("id").map(|v| v.as_u64().unwrap() as u8);
384
385        // For a valid JEP106 Code we need both cc and id
386        if cc.is_some() && id.is_some() {
387            Some(quote::quote! {
388                JEP106Code {
389                    cc: #cc,
390                    id: #id,
391                }
392            })
393        } else {
394            None
395        }
396    })
397}