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}