Skip to content

Commit

Permalink
First pass at interface generation
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesMc86 committed Jul 5, 2023
1 parent d3b2f3e commit aff3be8
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ cc = "1.0"
bindgen = "0.66"
paste = "1.0"
libc = "0.2"
syn = { version = "2.0", features = ["visit", "parsing"] }
quote = "1.0"
prettyplease = "0.2"
112 changes: 112 additions & 0 deletions src/build/bindings_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Generates a new rust module which contains the interface to the FPGA.
//!
//! This is still in rough shape but seems to prove the basic concept.
use std::ffi::CString;

use quote::quote;
use syn::{
parse::{Parse, ParseStream, Result},
token::In,
visit::Visit,
ExprLit, LitByteStr,
};

pub struct InterfaceDescription {
pub signature: LitByteStr,
pub filename: LitByteStr,
}

impl InterfaceDescription {
pub fn parse_bindings(prefix: &str, content: &str) -> Self {
let file = syn::parse_file(content).unwrap();
let mut sig_visitor = ByteConstantVisitor::new(prefix, "Signature");
let mut bitfile_visitor = ByteConstantVisitor::new(prefix, "Bitfile");
sig_visitor.visit_file(&file);
bitfile_visitor.visit_file(&file);

InterfaceDescription {
signature: sig_visitor.value.unwrap(),
filename: bitfile_visitor.value.unwrap(),
}
}

pub fn generate_rust_output(&self) -> String {
let signature = &self.signature;
let signature_length = signature.value().len();
let bitfile = &self.filename;
let bitfile_length = bitfile.value().len();
let file = syn::parse2(quote! {
const SIGNATURE: [u8; #signature_length] = #signature;
const FILENAME: [u8; #bitfile_length] = #bitfile;
})
.unwrap();
prettyplease::unparse(&file)
}
}

struct ByteConstantVisitor {
name: String,
value: Option<LitByteStr>,
}

impl ByteConstantVisitor {
fn new(prefix: &str, suffix: &str) -> Self {
Self {
name: format!("NiFpga_{prefix}_{suffix}"),
value: None,
}
}
}

impl<'ast> Visit<'ast> for ByteConstantVisitor {
fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
if node.ident == self.name {
match node.expr.as_ref() {
syn::Expr::Lit(ExprLit {
attrs: _,
lit: syn::Lit::ByteStr(lit_byte_str),
}) => {
self.value = Some(lit_byte_str.clone());
}
_ => {
Visit::visit_item_const(self, node);
}
}
}
}
}

#[cfg(test)]
mod tests {
use super::InterfaceDescription;

#[test]
fn test_signature_extraction() {
let content = r#"
pub const NiFpga_Main_Bitfile: &[u8; 19] = b"NiFpga_Main.lvbitx\0";
#[doc = " The signature of the FPGA bitfile."]
pub const NiFpga_Main_Signature: &[u8; 33] = b"E3E0C23C5F01C0DBA61D947AB8A8F489\0";
"#;

let description = InterfaceDescription::parse_bindings("Main", content);

assert_eq!(
description.signature.value(),
b"E3E0C23C5F01C0DBA61D947AB8A8F489\0"
);
}

#[test]
fn test_filename_extraction() {
let content = r#"
pub const NiFpga_Main_Bitfile: &[u8; 19] = b"NiFpga_Main.lvbitx\0";
#[doc = " The signature of the FPGA bitfile."]
pub const NiFpga_Main_Signature: &[u8; 33] = b"E3E0C23C5F01C0DBA61D947AB8A8F489\0";
"#;

let description = InterfaceDescription::parse_bindings("Main", content);

assert_eq!(description.filename.value(), b"NiFpga_Main.lvbitx\0");
}
}
21 changes: 20 additions & 1 deletion src/build.rs → src/build/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod bindings_parser;

use std::{
env,
path::{Path, PathBuf},
Expand Down Expand Up @@ -61,17 +63,34 @@ impl FpgaCInterface {
}

fn build_rust_interface(&self) {
let allow_string = format!("NiFpga_{}\\w*", self.interface_name);
let bindings = bindgen::Builder::default()
.header(self.custom_h.as_os_str().to_str().unwrap())
.allowlist_function(&allow_string)
.allowlist_type(&allow_string)
.allowlist_var(&allow_string)
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.prepend_enum_name(false)
.generate()
.expect("Unable to generate bindings");

// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let bindings_file = out_path.join("bindings.rs");
let mod_path = out_path.join(format!("NiFpga_{}.rs", self.interface_name));
bindings
.write_to_file(out_path.join("bindings.rs"))
.write_to_file(&bindings_file)
.expect("Couldn't write bindings!");

let interface_description = bindings_parser::InterfaceDescription::parse_bindings(
&self.interface_name,
&std::fs::read_to_string(&bindings_file).unwrap(),
);

std::fs::write(&mod_path, interface_description.generate_rust_output()).unwrap();
}
}

Expand Down

0 comments on commit aff3be8

Please sign in to comment.