lvbitfile2rust/
lib.rs

1use quote::quote;
2use quote::ToTokens;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum CodegenError {
7    #[error("Failed to read bitfile {0}: {1}")]
8    BitfileRead(String, std::io::Error),
9    #[error("Failed to parse bitfile {0}: {1}")]
10    BitfileParse(String, roxmltree::Error),
11    #[error("<{0}> node has no <{1}> child")]
12    MissingChild(String, String),
13    #[error("<{0}> node has no children")]
14    NoChildren(String),
15    #[error("<{0}> node has no text")]
16    NoText(String),
17    #[error("Unknown bitfile type: {0}")]
18    UnknownBitfileType(String),
19}
20
21struct HashableTokenStream(proc_macro2::TokenStream);
22
23impl std::cmp::PartialEq for HashableTokenStream {
24    fn eq(&self, other: &Self) -> bool {
25        self.0.to_string().eq(&other.0.to_string())
26    }
27}
28
29impl std::cmp::Eq for HashableTokenStream {}
30
31impl std::cmp::PartialOrd for HashableTokenStream {
32    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33        Some(self.cmp(other))
34    }
35}
36
37impl std::cmp::Ord for HashableTokenStream {
38    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
39        self.0.to_string().cmp(&other.0.to_string())
40    }
41}
42
43impl std::hash::Hash for HashableTokenStream {
44    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
45        self.0.to_string().hash(state);
46    }
47}
48
49impl ToTokens for HashableTokenStream {
50    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
51        self.0.to_tokens(tokens)
52    }
53}
54
55fn sanitize_ident(ident: &str) -> String {
56    ident.replace(".", "_").replace(" ", "_")
57}
58
59fn first_child_by_tag<'a, 'b>(
60    node: &'a roxmltree::Node<'b, 'b>,
61    child_tag: &'a str,
62) -> Result<roxmltree::Node<'b, 'b>, CodegenError> {
63    Ok(node
64        .children()
65        .find(|child| child.tag_name().name() == child_tag)
66        .ok_or_else(|| {
67            CodegenError::MissingChild(node.tag_name().name().to_owned(), child_tag.to_owned())
68        })?)
69}
70
71fn node_text<'a>(node: &'a roxmltree::Node) -> Result<&'a str, CodegenError> {
72    node.text()
73        .ok_or_else(|| CodegenError::NoText(node.tag_name().name().to_owned()))
74}
75
76fn type_node_to_rust_typedecl(
77    type_node: &roxmltree::Node,
78) -> Result<proc_macro2::TokenStream, CodegenError> {
79    match type_node.tag_name().name() {
80        "Boolean" => Ok(quote! {bool}),
81        "U8" => Ok(quote! {u8}),
82        "U16" => Ok(quote! {u16}),
83        "U32" => Ok(quote! {u32}),
84        "U64" => Ok(quote! {u64}),
85        "I8" => Ok(quote! {i8}),
86        "I16" => Ok(quote! {i16}),
87        "I32" => Ok(quote! {i32}),
88        "I64" => Ok(quote! {i64}),
89        "SGL" => Ok(quote! {f32}),
90        "DBL" => Ok(quote! {f64}),
91        "Array" => {
92            let size = syn::LitInt::new(
93                node_text(&first_child_by_tag(type_node, "Size")?)?,
94                proc_macro2::Span::call_site(),
95            );
96            let inner_type_node = first_child_by_tag(type_node, "Type")?
97                .first_element_child()
98                .ok_or_else(|| CodegenError::NoChildren("Type".to_owned()))?;
99            let inner_typedecl = type_node_to_rust_typedecl(&inner_type_node)?;
100            Ok(quote! {
101                [#inner_typedecl; #size]
102            })
103        }
104        "Cluster" | "EnumU8" | "EnumU16" | "EnumU32" | "EnumU64" => {
105            let ident = syn::Ident::new(
106                &sanitize_ident(node_text(&first_child_by_tag(type_node, "Name")?)?),
107                proc_macro2::Span::call_site(),
108            );
109            Ok(quote! {
110                types::#ident
111            })
112        }
113        "FXP" => {
114            let signed = syn::LitBool {
115                value: node_text(&first_child_by_tag(type_node, "Signed")?)? == "true",
116                span: proc_macro2::Span::call_site(),
117            };
118            let word_length = syn::LitInt::new(
119                node_text(&first_child_by_tag(type_node, "WordLength")?)?,
120                proc_macro2::Span::call_site(),
121            );
122            let integer_word_length = syn::LitInt::new(
123                node_text(&first_child_by_tag(type_node, "IntegerWordLength")?)?,
124                proc_macro2::Span::call_site(),
125            );
126            Ok(quote! {
127                ni_fpga::fxp::FXP<#word_length, #integer_word_length, #signed>
128            })
129        }
130        _ => Err(CodegenError::UnknownBitfileType(
131            type_node.tag_name().name().to_owned(),
132        )),
133    }
134}
135
136pub fn codegen(bitfile_path: String) -> Result<proc_macro2::TokenStream, CodegenError> {
137    let bitfile_contents = match std::fs::read_to_string(&bitfile_path) {
138        Ok(contents) => contents,
139        Err(e) => return Err(CodegenError::BitfileRead(bitfile_path, e)),
140    };
141    let bitfile = match roxmltree::Document::parse(&bitfile_contents) {
142        Ok(doc) => doc,
143        Err(e) => return Err(CodegenError::BitfileParse(bitfile_path, e)),
144    };
145
146    let mut typedefs = std::collections::HashSet::<HashableTokenStream>::new();
147    let mut register_defs = Vec::<proc_macro2::TokenStream>::new();
148    let mut register_fields = Vec::<proc_macro2::TokenStream>::new();
149    let mut register_inits = Vec::<proc_macro2::TokenStream>::new();
150    let mut vi_signature = String::new();
151
152    for node in bitfile.root().descendants() {
153        match node.tag_name().name() {
154            "Cluster" => {
155                let ident = syn::Ident::new(
156                    &sanitize_ident(node_text(&first_child_by_tag(&node, "Name")?)?),
157                    proc_macro2::Span::call_site(),
158                );
159                let mut fields = Vec::<proc_macro2::TokenStream>::new();
160                let type_list_node = first_child_by_tag(&node, "TypeList")?;
161                for field_node in type_list_node.children().filter(|child| child.is_element()) {
162                    let field_name = syn::Ident::new(
163                        node_text(&first_child_by_tag(&field_node, "Name")?)?,
164                        proc_macro2::Span::call_site(),
165                    );
166                    let field_typedecl = type_node_to_rust_typedecl(&field_node)?;
167                    fields.push(quote! {
168                        pub #field_name: #field_typedecl
169                    });
170                }
171                typedefs.insert(HashableTokenStream(quote! {
172                    #[derive(Cluster, Debug)]
173                    pub struct #ident {
174                        #(#fields),*
175                    }
176                }));
177            }
178            _name if _name.starts_with("Enum") => {
179                let ident = syn::Ident::new(
180                    &sanitize_ident(node_text(&first_child_by_tag(&node, "Name")?)?),
181                    proc_macro2::Span::call_site(),
182                );
183                let mut variants = Vec::<proc_macro2::TokenStream>::new();
184                let string_list_node = first_child_by_tag(&node, "StringList")?;
185                for variant_node in string_list_node
186                    .children()
187                    .filter(|child| child.is_element())
188                {
189                    let variant_ident = syn::Ident::new(
190                        &sanitize_ident(node_text(&variant_node)?),
191                        proc_macro2::Span::call_site(),
192                    );
193                    variants.push(quote! {
194                        #variant_ident
195                    });
196                }
197                typedefs.insert(HashableTokenStream(quote! {
198                    #[derive(Debug, Enum)]
199                    pub enum #ident {
200                        #(#variants),*
201                    }
202                }));
203            }
204            "Register" => {
205                if node_text(&first_child_by_tag(&node, "Hidden")?)? == "false" {
206                    let ident = syn::Ident::new(
207                        &sanitize_ident(node_text(&first_child_by_tag(&node, "Name")?)?),
208                        proc_macro2::Span::call_site(),
209                    );
210                    let offset = syn::LitInt::new(
211                        &sanitize_ident(node_text(&first_child_by_tag(&node, "Offset")?)?),
212                        proc_macro2::Span::call_site(),
213                    );
214                    let type_node = first_child_by_tag(&node, "Datatype")?
215                        .first_element_child()
216                        .ok_or_else(|| CodegenError::NoChildren("Datatype".to_owned()))?;
217                    let typedecl = type_node_to_rust_typedecl(&type_node)?;
218                    let write_fn = match node_text(&first_child_by_tag(&node, "Indicator")?)? {
219                        "false" => quote! {
220                            pub fn write(&self, value: &#typedecl) -> Result<(), ni_fpga::Error> {
221                                unsafe { SESSION.as_ref() }.unwrap().write(#offset, value)
222                            }
223                        },
224                        _ => quote! {},
225                    };
226                    register_defs.push(quote! {
227                        pub struct #ident {
228                            _marker: PhantomData<*const ()>,
229                        }
230
231                        impl #ident {
232                            pub fn read(&self) -> Result<#typedecl, ni_fpga::Error> {
233                                unsafe { SESSION.as_ref() }.unwrap().read(#offset)
234                            }
235                            #write_fn
236                        }
237                    });
238                    register_fields.push(quote! {
239                        pub #ident: #ident
240                    });
241                    register_inits.push(quote! {
242                        #ident: #ident{ _marker: PhantomData }
243                    });
244                }
245            }
246            "SignatureRegister" => {
247                vi_signature = node_text(&node)?.to_owned();
248            }
249            _ => {}
250        }
251    }
252
253    let mut typedefs_sorted = typedefs.iter().collect::<Vec<_>>();
254    typedefs_sorted.sort();
255
256    Ok(quote! {
257        use std::marker::PhantomData;
258
259        #[no_mangle]
260        static mut SESSION: *const ni_fpga::Session = std::ptr::null();
261
262        mod types {
263            use ni_fpga_macros::{Cluster, Enum};
264            use super::types;
265            #(#typedefs_sorted)*
266        }
267
268        #(#register_defs)*
269
270        pub struct Peripherals {
271            _marker: PhantomData<*const ()>,
272            #(#register_fields),*
273        }
274
275        impl Peripherals {
276            pub fn take(resource: &str) -> Result<Self, Box<dyn std::error::Error>> {
277                if unsafe{ !SESSION.is_null() } {
278                    Err("Peripherals already in use")?
279                } else {
280                    let session = ni_fpga::Session::open(
281                        #bitfile_path,
282                        #vi_signature,
283                        resource,
284                    )?;
285                    unsafe { SESSION = &session as *const ni_fpga::Session; }
286                    std::mem::forget(session);
287                    Ok(
288                        Self{
289                            _marker: PhantomData,
290                            #(#register_inits),*
291                        },
292                    )
293                }
294            }
295        }
296
297        impl Drop for Peripherals {
298            fn drop(&mut self) {
299                std::mem::drop(unsafe { SESSION.as_ref() }.unwrap())
300            }
301        }
302    })
303}