hs_bindgen_types/
lib.rs

1use cfg_if::cfg_if;
2use core::ffi::*;
3use displaydoc::Display;
4use proc_macro2::TokenStream;
5use quote::quote;
6use thiserror::Error;
7
8/// Enumeration of all Haskell C-FFI safe types as the string representation of
9/// their token in Haskell.
10///
11/// FIXME: `Errno(c_int)` should be implemented as a Rust `enum` ...
12/// https://hackage.haskell.org/package/base/docs/Foreign-C-Error.html
13/// ... using `#[repr(i32)]` https://doc.rust-lang.org/nomicon/other-reprs.html
14#[non_exhaustive]
15pub enum HsType {
16    /// `Int32`
17    CInt,
18    /// `Int8`
19    CChar,
20    /// `Int8`
21    CSChar,
22    /// `Word8`
23    CUChar,
24    /// `Int16`
25    CShort,
26    /// `Word16`
27    CUShort,
28    /// `Word32`
29    CUInt,
30    /// `Int64`
31    CLong,
32    /// `Word64`
33    CULong,
34    /// `Int64`
35    CLLong,
36    /// `Word64`
37    CULLong,
38    /// `Word8`
39    CBool,
40    /// `Ptr CChar`
41    CString,
42    /// `Double`
43    CDouble,
44    /// `Float`
45    CFloat,
46    /// `()`
47    Empty,
48    /// `Ptr T`
49    Ptr(Box<HsType>),
50    /// `IO T`
51    IO(Box<HsType>),
52    /// FunPtr (S -> T)
53    FunPtr(Vec<HsType>),
54}
55
56impl std::fmt::Display for HsType {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(
59            f,
60            "{}",
61            match self {
62                HsType::CBool => "CBool".to_string(),
63                HsType::CChar => "CChar".to_string(),
64                HsType::CDouble => "CDouble".to_string(),
65                HsType::CFloat => "CFloat".to_string(),
66                HsType::CInt => "CInt".to_string(),
67                HsType::CLLong => "CLLong".to_string(),
68                HsType::CLong => "CLong".to_string(),
69                HsType::CSChar => "CSChar".to_string(),
70                HsType::CShort => "CShort".to_string(),
71                HsType::CString => "CString".to_string(),
72                HsType::CUChar => "CUChar".to_string(),
73                HsType::CUInt => "CUInt".to_string(),
74                HsType::CULLong => "CULLong".to_string(),
75                HsType::CULong => "CULong".to_string(),
76                HsType::CUShort => "CUShort".to_string(),
77                HsType::Empty => "()".to_string(),
78                HsType::Ptr(x) => format!("Ptr ({x})"),
79                HsType::IO(x) => format!("IO ({x})"),
80                HsType::FunPtr(types) => {
81                    let args: Vec<String> = types.iter().map(|arg| format!("{arg}")).collect();
82                    format!("FunPtr({})", args.join(" -> "))
83                }
84            }
85        )
86    }
87}
88
89#[derive(Debug, Display, Error)]
90pub enum Error {
91    /** type `{0}` isn't in the list of supported Haskell C-FFI types.
92     * Consider opening an issue https://github.com/yvan-sraka/hs-bindgen-types
93     *
94     * The list of available Haskell C-FFI types could be found here:
95     * https://hackage.haskell.org/package/base/docs/Foreign-C.html
96     */
97    UnsupportedHsType(String),
98    /// found an open `(` without the matching closing `)`
99    UnmatchedParenthesis,
100    /// FunPtr is missing type parameter
101    FunPtrWithoutTypeArgument,
102}
103
104pub struct ArrowIter<'a> {
105    remaining: &'a str,
106}
107
108impl<'a> Iterator for ArrowIter<'a> {
109    type Item = &'a str;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        let ArrowIter { remaining } = self;
113
114        let mut open = 0;
115        let mut offset = 0;
116
117        if remaining.trim().is_empty() {
118            return None;
119        }
120
121        let mut matched: &str = "";
122
123        for c in remaining.chars() {
124            if c == '(' {
125                open += 1;
126            } else if c == ')' {
127                open -= 1;
128            } else if open == 0 && remaining[offset..].starts_with("->") {
129                matched = &remaining[..offset];
130                offset += "->".len();
131                break;
132            }
133
134            offset += c.len_utf8();
135            matched = &remaining[..offset];
136        }
137
138        *remaining = &remaining[offset..];
139        Some(matched)
140    }
141}
142
143impl<'a> From<&'a str> for ArrowIter<'a> {
144    fn from(value: &'a str) -> Self {
145        Self { remaining: value }
146    }
147}
148
149impl std::str::FromStr for HsType {
150    type Err = Error;
151
152    fn from_str(s: &str) -> Result<Self, Self::Err> {
153        let s = s.trim();
154        if s == "()" {
155            Ok(HsType::Empty)
156        } else if !s.is_empty() && &s[..1] == "(" {
157            Ok(s[1..]
158                .strip_suffix(')')
159                .ok_or(Error::UnmatchedParenthesis)?
160                .parse()?)
161        } else if s.len() >= 2 && &s[..2] == "IO" {
162            Ok(HsType::IO(Box::new(s[2..].parse()?)))
163        } else if s.len() >= 3 && &s[..3] == "Ptr" {
164            Ok(HsType::Ptr(Box::new(s[3..].parse()?)))
165        } else if s.len() >= 6 && &s[..6] == "FunPtr" {
166            let mut s = s[6..].trim();
167
168            if let Some('(') = s.chars().next() {
169                s = s[1..]
170                    .strip_suffix(')')
171                    .ok_or(Error::UnmatchedParenthesis)?;
172            }
173
174            let types: Vec<_> = ArrowIter { remaining: s }
175                .map(|s| s.parse::<Self>())
176                .collect::<Result<_, _>>()?;
177
178            if types.is_empty() {
179                return Err(Error::FunPtrWithoutTypeArgument);
180            }
181
182            Ok(HsType::FunPtr(types))
183        } else {
184            match s {
185                "CBool" => Ok(HsType::CBool),
186                "CChar" => Ok(HsType::CChar),
187                "CDouble" => Ok(HsType::CDouble),
188                "CFloat" => Ok(HsType::CFloat),
189                "CInt" => Ok(HsType::CInt),
190                "CLLong" => Ok(HsType::CLLong),
191                "CLong" => Ok(HsType::CLong),
192                "CSChar" => Ok(HsType::CSChar),
193                "CShort" => Ok(HsType::CShort),
194                "CString" => Ok(HsType::CString),
195                "CUChar" => Ok(HsType::CUChar),
196                "CUInt" => Ok(HsType::CUInt),
197                "CULLong" => Ok(HsType::CULLong),
198                "CULong" => Ok(HsType::CULong),
199                "CUShort" => Ok(HsType::CUShort),
200                ty => Err(Error::UnsupportedHsType(ty.to_string())),
201            }
202        }
203    }
204}
205
206impl HsType {
207    /// Get the C-FFI Rust type that match the memory layout of a given HsType.
208    ///
209    /// This function return a `OUTPUT: proc_macro2::TokenStream` that should
210    /// be valid (considered as FFI-safe by `rustc`) in the context of a block
211    /// of form: `quote! { extern C fn _(_: #OUTPUT) {} }`
212    ///
213    /// c.f. https://doc.rust-lang.org/core/ffi/
214    pub fn quote(&self) -> TokenStream {
215        match self {
216            // FIXME: add https://doc.rust-lang.org/core/ffi/enum.c_void.html
217            HsType::CBool => quote! { bool },
218            HsType::CChar => quote! { core::ffi::c_char },
219            HsType::CDouble => quote! { core::ffi::c_double },
220            HsType::CFloat => quote! { core::ffi::c_float },
221            HsType::CInt => quote! { core::ffi::c_int },
222            HsType::CLLong => quote! { core::ffi::c_longlong },
223            HsType::CLong => quote! { core::ffi::c_long },
224            HsType::CSChar => quote! { core::ffi::c_schar },
225            HsType::CShort => quote! { core::ffi::c_short },
226            HsType::CString => HsType::Ptr(Box::new(HsType::CChar)).quote(),
227            HsType::CUChar => quote! { core::ffi::c_uchar },
228            HsType::CUInt => quote! { core::ffi::c_uint },
229            HsType::CULLong => quote! { core::ffi::c_ulonglong },
230            HsType::CULong => quote! { core::ffi::c_ulong },
231            HsType::CUShort => quote! { core::ffi::c_ushort },
232            HsType::Empty => quote! { () },
233            HsType::Ptr(x) => {
234                let ty = x.quote();
235                quote! { *const #ty }
236            }
237            HsType::IO(x) => x.quote(),
238            HsType::FunPtr(types) => {
239                let ret = types.last().unwrap().quote();
240                let args: Vec<_> = types[..types.len() - 1]
241                    .iter()
242                    .map(|arg| arg.quote())
243                    .collect();
244                quote!(unsafe extern "C" fn(#(#args),*) -> #ret)
245            }
246        }
247    }
248}
249
250/// Turn a given Rust type into his `HsType` target.
251///
252/// Deducing what's the right Haskell type target given an arbitrary Rust type
253/// is provided by `antlion` feature of `hs-bingen-derive` and rely mostly on
254/// Rust type inference through this trait.
255pub trait ReprHs {
256    fn into() -> HsType;
257}
258
259macro_rules! repr_hs {
260    ($($ty:ty => $ident:ident,)*) => {$(
261        impl ReprHs for $ty {
262            fn into() -> HsType {
263                HsType::$ident
264            }
265        }
266    )*};
267}
268pub(crate) use repr_hs;
269
270repr_hs! {
271    c_char   => CChar,
272    c_double => CDouble,
273    c_float  => CFloat,
274    c_int    => CInt,
275    c_short  => CShort,
276    c_uchar  => CUChar,
277    c_uint   => CUInt,
278    c_ushort => CUShort,
279    ()       => Empty,
280}
281
282cfg_if! {
283    if #[cfg(all(target_pointer_width = "64", not(windows)))] {
284        repr_hs! {
285            c_long  => CLong,
286            c_ulong => CULong,
287        }
288    } else {
289        repr_hs! {
290            c_longlong  => CLLong,
291            c_ulonglong => CULLong,
292        }
293    }
294}
295
296impl<T> ReprHs for *const T
297where
298    T: ReprHs,
299{
300    fn into() -> HsType {
301        HsType::Ptr(Box::new(T::into()))
302    }
303}
304
305impl<T> ReprHs for *mut T
306where
307    T: ReprHs,
308{
309    fn into() -> HsType {
310        HsType::Ptr(Box::new(T::into()))
311    }
312}
313
314/* ********** Vector & Slices ********** */
315
316impl<T> ReprHs for Vec<T>
317where
318    T: ReprHs,
319{
320    fn into() -> HsType {
321        HsType::Ptr(Box::new(T::into()))
322    }
323}
324
325impl<T, const N: usize> ReprHs for &[T; N]
326where
327    T: ReprHs,
328{
329    fn into() -> HsType {
330        HsType::Ptr(Box::new(T::into()))
331    }
332}
333
334/* ********** Strings ********** */
335
336use std::ffi::CString;
337
338repr_hs! {
339    CString => CString,
340    &CStr   => CString,
341    String  => CString,
342    &str    => CString,
343}