use displaydoc::Display;
use hs_bindgen_types::{ArrowIter, HsType};
use std::str::FromStr;
use thiserror::Error;
pub(crate) fn template(module: &str, signatures: &[Signature]) -> String {
let modulename = module.replace("/", ".");
let names = signatures
.iter()
.map(|x| x.fn_name.clone())
.collect::<Vec<String>>()
.join(", ");
let imports = signatures
.iter()
.map(|sig| {
format!(
"foreign import ccall {} \"__c_{}\" {sig}",
if sig.fn_safe {
"safe"
} else {
warning(sig);
"unsafe"
},
sig.fn_name
)
})
.collect::<Vec<String>>()
.join("\n");
format!(
"-- This file was generated by `hs-bindgen` crate and contains C FFI bindings
-- wrappers for every Rust function annotated with `#[hs_bindgen]`
{{-# LANGUAGE ForeignFunctionInterface #-}}
-- Why not rather using `{{-# LANGUAGE CApiFFI #-}}` language extension?
--
-- * Because it's GHC specific and not part of the Haskell standard:
-- https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/ffi.html ;
--
-- * Because the capabilities it gave (by rather works on top of symbols of a C
-- header file) can't work in our case. Maybe we want a future with an
-- {{-# LANGUAGE RustApiFFI #-}} language extension that would enable us to
-- work on top of a `.rs` source file (or a `.rlib`, but this is unlikely as
-- this format has purposely no public specification).
{{-# OPTIONS_GHC -Wno-unused-imports #-}}
module {modulename} ({names}) where
import Data.Int
import Data.Word
import Foreign.C.String
import Foreign.C.Types
import Foreign.Ptr
{imports}"
)
}
pub(crate) fn warning(_sig: &Signature) {
#[cfg(DIAGNOSTICS)]
proc_macro::Diagnostic::spanned(
[proc_macro::Span::call_site()].as_ref(),
proc_macro::Level::Warning,
format!(
"Using: `foreign import ccall unsafe __c_, {} {_sig}`
means that Haskell Garbage-Collector will be locked during the foreign call.
/!\\ Do not use it for long computations in a multithreaded application or
it will slow down a lot your whole program ...",
_sig.fn_name
),
)
.emit();
}
#[derive(Display, Error, Debug)]
pub enum Error {
MissingSig,
MalformedSig(String),
HsType(String),
}
pub(crate) struct Signature {
pub(crate) fn_name: String,
pub(crate) fn_safe: bool,
pub(crate) fn_type: Vec<HsType>,
}
impl std::fmt::Display for Signature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} :: {}",
self.fn_name,
self.fn_type
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join(" -> ")
)
}
}
impl FromStr for Signature {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
(!s.is_empty()).then_some(()).ok_or(Error::MissingSig)?;
let mut x = s.split("::");
let fn_name = x.next().ok_or(Error::MissingSig)?.trim();
let fn_safe = !fn_name.starts_with("unsafe ");
let fn_name = if fn_safe {
fn_name.trim_start_matches("safe ")
} else {
fn_name.trim_start_matches("unsafe ")
}
.trim_start()
.to_string();
let fn_type = ArrowIter::from(x.next().ok_or_else(|| Error::MalformedSig(s.to_string()))?)
.map(|ty| {
ty.parse::<HsType>()
.map_err(|ty| Error::HsType(ty.to_string()))
})
.collect::<Result<Vec<HsType>, Error>>()?;
assert!(x.next().is_none(), "{}", Error::MalformedSig(s.to_string()));
Ok(Signature {
fn_name,
fn_safe,
fn_type,
})
}
}