hs_bindgen_attribute/
lib.rs

1//! # `hs-bindgen-attribute`
2//!
3//! This library define the `#[hs_bindgen]` procedural macro used by
4//! [`hs-bindgen`](https://github.com/yvan-sraka/hs-bindgen) library.
5//!
6//! ## Acknowledgments
7//!
8//! ⚠️ This is still a working experiment, not yet production ready.
9//!
10//! This project was part of a work assignment as an
11//! [IOG](https://github.com/input-output-hk) contractor.
12//!
13//! ## License
14//!
15//! Licensed under either of [Apache License](LICENSE-APACHE), Version 2.0 or
16//! [MIT license](LICENSE-MIT) at your option.
17//!
18//! Unless you explicitly state otherwise, any contribution intentionally submitted
19//! for inclusion in this project by you, as defined in the Apache-2.0 license,
20//! shall be dual licensed as above, without any additional terms or conditions.
21
22#![forbid(unsafe_code)]
23#![cfg_attr(DIAGNOSTICS, feature(proc_macro_diagnostic))]
24
25use proc_macro::TokenStream;
26use std::{fs, path::Path, sync::Mutex};
27
28mod haskell;
29mod reflexive;
30mod rust;
31mod toml;
32
33#[proc_macro_attribute]
34pub fn hs_bindgen(attrs: TokenStream, input: TokenStream) -> TokenStream {
35    let mut output = input.clone();
36    let item_fn: syn::ItemFn = syn::parse(input)
37        .expect("failed to parse as Rust code the content of `#[hs_bindgen]` macro");
38
39    // Generate extra Rust code that wrap our exposed function ...
40    let (signature, extern_c_wrapper) = rust::generate(attrs, item_fn);
41
42    // Neat hack to keep track of all exposed functions ...
43    static SIGNATURES: Mutex<Vec<haskell::Signature>> = Mutex::new(vec![]);
44    let signatures = &mut *SIGNATURES.lock().unwrap();
45    signatures.push(signature);
46
47    // Generate Haskell bindings into module defined in `hsbindgen.toml` config ...
48    let module = toml::config()
49        .default
50        .expect("your `hsbindgen.toml` file should contain a `default` field");
51    let cargo_manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
52        .expect("environment variable `CARGO_MANIFEST_DIR` must be set");
53    let path = Path::new(&cargo_manifest_dir).join(format!("src/{}.hs", module));
54    fs::write(&path, haskell::template(&module, signatures))
55        .unwrap_or_else(|_| panic!("fail to write `{}` file", path.display()));
56
57    output.extend(extern_c_wrapper);
58    output
59}