Initial push
This commit is contained in:
parent
d1327c45f4
commit
c46977354d
17 changed files with 1205 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target/
|
||||
Cargo.lock
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[workspace]
|
||||
|
||||
members = [
|
||||
"haskell-ffi",
|
||||
"haskell-ffi-derive",
|
||||
]
|
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Rust library for easy interop with Haskell
|
||||
|
12
haskell-ffi-derive/Cargo.toml
Normal file
12
haskell-ffi-derive/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "haskell-ffi-derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
85
haskell-ffi-derive/src/lib.rs
Normal file
85
haskell-ffi-derive/src/lib.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
//! Macro for deriving `HaskellSize` instances for structs
|
||||
//!
|
||||
//! Implementation is adapted from the `heapsize` example in the `syn` crate.
|
||||
//! The implementation is not identical, however: `haskell_size` does not take
|
||||
//! any value as input, but is entirely type-based.
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse_macro_input, parse_quote, punctuated::Iter, Data, DeriveInput, Field, Fields,
|
||||
GenericParam, Generics,
|
||||
};
|
||||
|
||||
/// Derive `HaskellSize` instance
|
||||
///
|
||||
/// NOTE: Only structs are currently supported.
|
||||
#[proc_macro_derive(HaskellSize)]
|
||||
pub fn haskell_size_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
// Parse the input tokens into a syntax tree.
|
||||
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
// Used in the quasi-quotation below as `#name`.
|
||||
let name = &input.ident;
|
||||
|
||||
// Add a bound `T: HaskellSize` to every type parameter T.
|
||||
let without_tag: Generics = add_trait_bounds(input.generics);
|
||||
|
||||
// The instance itself must get an additional `Tag` argument
|
||||
//
|
||||
// NOTE: Things will go badly if one of the user's parameters is also named `Tag`.
|
||||
let mut including_tag: Generics = without_tag.clone();
|
||||
including_tag
|
||||
.params
|
||||
.push(GenericParam::Type(parse_quote!(Tag)));
|
||||
|
||||
let (including_tag_impl, _, _) = including_tag.split_for_impl();
|
||||
let (_, without_tag_tys, without_tag_where) = without_tag.split_for_impl();
|
||||
|
||||
// Generate an expression to sum up the size of each field.
|
||||
let sum = haskell_size_sum(&input.data);
|
||||
|
||||
let expanded = quote! {
|
||||
impl #including_tag_impl HaskellSize<Tag> for #name #without_tag_tys #without_tag_where {
|
||||
fn haskell_size(tag: PhantomData<Tag>) -> usize {
|
||||
#sum
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Hand the output tokens back to the compiler.
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
/// Add a bound `T: HaskellSize<Tag>` to every type parameter T.
|
||||
fn add_trait_bounds(mut generics: Generics) -> Generics {
|
||||
for param in &mut generics.params {
|
||||
if let GenericParam::Type(ref mut type_param) = *param {
|
||||
type_param.bounds.push(parse_quote!(HaskellSize<Tag>));
|
||||
}
|
||||
}
|
||||
generics
|
||||
}
|
||||
|
||||
/// Generate an expression to sum up the size of each field.
|
||||
fn haskell_size_sum(data: &Data) -> TokenStream {
|
||||
match data {
|
||||
Data::Struct(ref data) => match &data.fields {
|
||||
Fields::Named(fields) => haskell_size_fields(fields.named.iter()),
|
||||
Fields::Unnamed(fields) => haskell_size_fields(fields.unnamed.iter()),
|
||||
Fields::Unit => quote!(0),
|
||||
},
|
||||
Data::Enum(_) | Data::Union(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Auxiliary to `haskell_size_sum`
|
||||
fn haskell_size_fields(fields: Iter<Field>) -> TokenStream {
|
||||
let recurse = fields.map(|f| {
|
||||
let t = &f.ty;
|
||||
quote! { <#t> :: haskell_size(tag) }
|
||||
});
|
||||
quote! {
|
||||
0 #(+ #recurse)*
|
||||
}
|
||||
}
|
13
haskell-ffi/Cargo.toml
Normal file
13
haskell-ffi/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "haskell-ffi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3"
|
||||
borsh = "0.9"
|
||||
haskell-ffi-derive = { path = "../haskell-ffi-derive" }
|
||||
ref-cast = "1.0"
|
||||
serde = "1.0"
|
36
haskell-ffi/src/bincode.rs
Normal file
36
haskell-ffi/src/bincode.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use std::{
|
||||
io::{Error, ErrorKind, Write},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
/// Implement `to_haskell` using `bincode`
|
||||
///
|
||||
/// The result will be length-prefixed ("bincode-in-Borsh").
|
||||
pub fn bincode_to_haskell<Tag, T, W>(
|
||||
t: &T,
|
||||
writer: &mut W,
|
||||
_: PhantomData<Tag>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: serde::ser::Serialize,
|
||||
W: Write,
|
||||
{
|
||||
match bincode::serialize(t) {
|
||||
Ok(vec) => borsh::BorshSerialize::serialize(&vec, writer),
|
||||
Err(e) => Err(Error::new(ErrorKind::InvalidData, e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `from_haskell` using `bincode`
|
||||
///
|
||||
/// See als `bincode_to_haskell`
|
||||
pub fn bincode_from_haskell<Tag, T>(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<T, Error>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let vec: Vec<u8> = borsh::BorshDeserialize::deserialize(buf)?;
|
||||
match bincode::deserialize(vec.as_ref()) {
|
||||
Ok(x) => Ok(x),
|
||||
Err(e) => Err(Error::new(ErrorKind::InvalidData, e)),
|
||||
}
|
||||
}
|
113
haskell-ffi/src/deriving_via.rs
Normal file
113
haskell-ffi/src/deriving_via.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use ref_cast::RefCast;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::Debug,
|
||||
hash::{Hash, Hasher},
|
||||
io::{Error, Write},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use crate::{from_haskell::FromHaskell, to_haskell::ToHaskell};
|
||||
|
||||
/*******************************************************************************
|
||||
Deriving-via support
|
||||
*******************************************************************************/
|
||||
|
||||
#[derive(RefCast)]
|
||||
#[repr(transparent)]
|
||||
/// Newtype for "deriving-via" instances
|
||||
///
|
||||
/// The purpose of this newtype is best illustrated through its instances:
|
||||
///
|
||||
/// ```ignore
|
||||
/// impl<Tag, T: ToHaskell<Tag>> BorshSerialize for Haskell<Tag, T>
|
||||
/// impl<Tag, T: FromHaskell<Tag>> BorshDeserialize for Haskell<Tag, T>
|
||||
/// ```
|
||||
///
|
||||
/// This is primarily used internally: when deriving `ToHaskell`/`FromHaskell`
|
||||
/// instances for standard types, we want to re-use the logic from `borsh`,
|
||||
/// rather than re-implement everything here. We do this by turning say a
|
||||
/// `Vec<T>` into a `Vec<Haskell<Tag, T>>`, and then call functions from
|
||||
/// `borsh`. The use of the newtype wrapper then ensures that the constraint
|
||||
/// on `T` will be in terms of `ToHaskell`/`FromHaskell` again.
|
||||
pub struct Haskell<Tag, T>(pub T, PhantomData<Tag>);
|
||||
|
||||
pub fn tag_val<Tag, T>(t: T) -> Haskell<Tag, T> {
|
||||
Haskell(t, PhantomData)
|
||||
}
|
||||
|
||||
pub fn tag_ref<Tag, T>(t: &T) -> &Haskell<Tag, T> {
|
||||
RefCast::ref_cast(t)
|
||||
}
|
||||
|
||||
pub fn untag_val<Tag, T>(tagged: Haskell<Tag, T>) -> T {
|
||||
tagged.0
|
||||
}
|
||||
|
||||
pub fn untag_ref<Tag, T>(tagged: &Haskell<Tag, T>) -> &T {
|
||||
&tagged.0
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Standard instances
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, T: Debug> Debug for Haskell<Tag, T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: PartialEq> PartialEq for Haskell<Tag, T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: Eq> Eq for Haskell<Tag, T> {}
|
||||
|
||||
impl<Tag, T: PartialOrd> PartialOrd for Haskell<Tag, T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: Hash> Hash for Haskell<Tag, T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: Default> Default for Haskell<Tag, T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: Clone> Clone for Haskell<Tag, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: Copy> Copy for Haskell<Tag, T> {}
|
||||
|
||||
/*******************************************************************************
|
||||
Forwarding instances
|
||||
|
||||
NOTE: We do not expect _additional_ forwarding instances to be defined.
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, T: ToHaskell<Tag>> BorshSerialize for Haskell<Tag, T> {
|
||||
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.0.to_haskell(writer, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: FromHaskell<Tag>> BorshDeserialize for Haskell<Tag, T> {
|
||||
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
|
||||
let tag: PhantomData<Tag> = PhantomData;
|
||||
T::from_haskell(buf, tag).map(tag_val)
|
||||
}
|
||||
}
|
76
haskell-ffi/src/from_haskell.rs
Normal file
76
haskell-ffi/src/from_haskell.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use std::{
|
||||
io::{Error, ErrorKind},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use crate::HaskellSize;
|
||||
|
||||
/*******************************************************************************
|
||||
Main class definition
|
||||
*******************************************************************************/
|
||||
|
||||
const ERROR_NOT_ALL_BYTES_READ: &str = "Not all bytes read";
|
||||
|
||||
pub trait FromHaskell<Tag>: Sized {
|
||||
/// Deserialize data sent from Haskell
|
||||
///
|
||||
/// This is the analogue of `BorshDeserialize::deserialize`.
|
||||
//
|
||||
/// See `ToHaskell` for a detailed discussion of the `tag` argument.
|
||||
fn from_haskell(buf: &mut &[u8], tag: PhantomData<Tag>) -> Result<Self, Error>;
|
||||
|
||||
fn from_haskell_slice(slice: &[u8], tag: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let mut slice_mut = slice;
|
||||
let result = Self::from_haskell(&mut slice_mut, tag)?;
|
||||
if !slice_mut.is_empty() {
|
||||
return Err(Error::new(ErrorKind::InvalidData, ERROR_NOT_ALL_BYTES_READ));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Derived functionality
|
||||
|
||||
See comments in `to_haskell` for why these functions do not live inside the
|
||||
trait.
|
||||
*******************************************************************************/
|
||||
|
||||
/// Marshall value with variable-sized encoding
|
||||
pub fn marshall_from_haskell_var<Tag, T>(inp: *const u8, len: usize, tag: PhantomData<Tag>) -> T
|
||||
where
|
||||
T: FromHaskell<Tag>,
|
||||
{
|
||||
let mut vec: Vec<u8> = vec![0; len];
|
||||
unsafe {
|
||||
std::ptr::copy(inp, vec.as_mut_ptr(), len);
|
||||
}
|
||||
match T::from_haskell_slice(vec.as_ref(), tag) {
|
||||
Ok(t) => t,
|
||||
Err(e) => panic!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Marshall value with fixed-size encoding
|
||||
///
|
||||
/// The `len` argument here is only to verify that the Haskell-side and
|
||||
/// Rust-side agree on the size of the encoding.
|
||||
pub fn marshall_from_haskell_fixed<Tag, T>(
|
||||
inp: *const u8,
|
||||
inp_len: usize,
|
||||
tag: PhantomData<Tag>,
|
||||
) -> T
|
||||
where
|
||||
T: FromHaskell<Tag> + HaskellSize<Tag>,
|
||||
{
|
||||
let expected_len = T::haskell_size(tag);
|
||||
|
||||
if inp_len != expected_len {
|
||||
panic!(
|
||||
"expected buffer of size {}, but got {}",
|
||||
expected_len, inp_len
|
||||
)
|
||||
} else {
|
||||
marshall_from_haskell_var(inp, inp_len, tag)
|
||||
}
|
||||
}
|
223
haskell-ffi/src/haskell_size.rs
Normal file
223
haskell-ffi/src/haskell_size.rs
Normal file
|
@ -0,0 +1,223 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{derive_size_tuple_instance, fold_types};
|
||||
|
||||
pub use haskell_ffi_derive::HaskellSize;
|
||||
|
||||
/*******************************************************************************
|
||||
Main class definition
|
||||
*******************************************************************************/
|
||||
|
||||
pub trait HaskellSize<Tag> {
|
||||
/// Statically known size (in bytes)
|
||||
fn haskell_size(tag: PhantomData<Tag>) -> usize;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Simple instances
|
||||
|
||||
Note: the following types in the Borsh spec do _not_ have statically known sizes:
|
||||
|
||||
- Vec<T>
|
||||
- HashMap<K, V>
|
||||
- HashSet<T>
|
||||
- Option<T>
|
||||
- String
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for u8 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for u16 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for u32 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for u64 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for u128 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for i8 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for i16 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for i32 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for i64 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for i128 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for f32 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for f64 {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for () {
|
||||
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: HaskellSize<Tag>, const N: usize> HaskellSize<Tag> for [T; N] {
|
||||
fn haskell_size(tag: PhantomData<Tag>) -> usize {
|
||||
T::haskell_size(tag) * N
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Tuples
|
||||
|
||||
We support the same sizes of tuples as `borsh` does.
|
||||
*******************************************************************************/
|
||||
|
||||
derive_size_tuple_instance!(T0, T1);
|
||||
derive_size_tuple_instance!(T0, T1, T2);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
|
||||
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
|
||||
derive_size_tuple_instance!(
|
||||
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
|
||||
);
|
||||
derive_size_tuple_instance!(
|
||||
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17
|
||||
);
|
||||
derive_size_tuple_instance!(
|
||||
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18
|
||||
);
|
||||
derive_size_tuple_instance!(
|
||||
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19
|
||||
);
|
||||
|
||||
/*******************************************************************************
|
||||
Sanity checks
|
||||
*******************************************************************************/
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Error;
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
|
||||
use super::*;
|
||||
|
||||
enum ExampleTag {}
|
||||
|
||||
#[derive(HaskellSize, BorshSerialize)]
|
||||
struct EmptyStruct;
|
||||
|
||||
#[derive(HaskellSize, BorshSerialize)]
|
||||
struct UnnamedStruct(u16, (u8, u32));
|
||||
|
||||
#[derive(HaskellSize, BorshSerialize)]
|
||||
struct NamedStruct {
|
||||
a: u8,
|
||||
b: u16,
|
||||
c: (u32, u64),
|
||||
}
|
||||
|
||||
#[derive(HaskellSize, BorshSerialize)]
|
||||
struct ParamStruct<T> {
|
||||
a: u8,
|
||||
b: (T, T, T),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() -> Result<(), Error> {
|
||||
let tag: PhantomData<ExampleTag> = PhantomData;
|
||||
assert_eq!(EmptyStruct::haskell_size(tag), 0);
|
||||
let encoded = EmptyStruct.try_to_vec()?;
|
||||
assert_eq!(encoded.len(), EmptyStruct::haskell_size(tag));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnamed() -> Result<(), Error> {
|
||||
let tag: PhantomData<ExampleTag> = PhantomData;
|
||||
assert_eq!(UnnamedStruct::haskell_size(tag), 7);
|
||||
let encoded = UnnamedStruct(1, (2, 3)).try_to_vec()?;
|
||||
assert_eq!(encoded.len(), UnnamedStruct::haskell_size(tag));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named() -> Result<(), Error> {
|
||||
let tag: PhantomData<ExampleTag> = PhantomData;
|
||||
assert_eq!(NamedStruct::haskell_size(tag), 15);
|
||||
let encoded = NamedStruct {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: (3, 4),
|
||||
}
|
||||
.try_to_vec()?;
|
||||
assert_eq!(encoded.len(), NamedStruct::haskell_size(tag));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param() -> Result<(), Error> {
|
||||
let tag: PhantomData<ExampleTag> = PhantomData;
|
||||
assert_eq!(<ParamStruct<f64>>::haskell_size(tag), 25);
|
||||
let encoded = ParamStruct {
|
||||
a: 1,
|
||||
b: (1.0, 2.0, 3.0),
|
||||
}
|
||||
.try_to_vec()?;
|
||||
assert_eq!(encoded.len(), <ParamStruct<f64>>::haskell_size(tag));
|
||||
Ok(())
|
||||
}
|
||||
}
|
262
haskell-ffi/src/instances.rs
Normal file
262
haskell-ffi/src/instances.rs
Normal file
|
@ -0,0 +1,262 @@
|
|||
//! ToHaskell and FromHaskell instances for the various standard types mandated
|
||||
//! by the [Borsh spec](https://borsh.io/), piggy-backing on the implementation
|
||||
//! in the `borsh` crate. The only spec-described types _not_ provided are
|
||||
//! user-defined structs and enums.
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
hash::Hash,
|
||||
io::{Error, ErrorKind, Write},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
derive_array_instances, derive_simple_instances, derive_tuple_instances,
|
||||
deriving_via::{tag_ref, untag_val, Haskell},
|
||||
from_haskell::FromHaskell,
|
||||
map_tuple, map_tuple_ref,
|
||||
to_haskell::ToHaskell,
|
||||
HaskellSize,
|
||||
};
|
||||
|
||||
/*******************************************************************************
|
||||
Simple (non-composite) instances
|
||||
*******************************************************************************/
|
||||
|
||||
derive_simple_instances!(u8);
|
||||
derive_simple_instances!(u16);
|
||||
derive_simple_instances!(u32);
|
||||
derive_simple_instances!(u64);
|
||||
derive_simple_instances!(u128);
|
||||
derive_simple_instances!(i8);
|
||||
derive_simple_instances!(i16);
|
||||
derive_simple_instances!(i32);
|
||||
derive_simple_instances!(i64);
|
||||
derive_simple_instances!(i128);
|
||||
derive_simple_instances!(f32);
|
||||
derive_simple_instances!(f64);
|
||||
derive_simple_instances!(());
|
||||
derive_simple_instances!(String);
|
||||
|
||||
/*******************************************************************************
|
||||
Array instances
|
||||
|
||||
This is the same set of sizes as supported by borsh.
|
||||
*******************************************************************************/
|
||||
|
||||
derive_array_instances!(0);
|
||||
derive_array_instances!(1);
|
||||
derive_array_instances!(2);
|
||||
derive_array_instances!(3);
|
||||
derive_array_instances!(4);
|
||||
derive_array_instances!(5);
|
||||
derive_array_instances!(6);
|
||||
derive_array_instances!(7);
|
||||
derive_array_instances!(8);
|
||||
derive_array_instances!(9);
|
||||
derive_array_instances!(10);
|
||||
derive_array_instances!(11);
|
||||
derive_array_instances!(12);
|
||||
derive_array_instances!(13);
|
||||
derive_array_instances!(14);
|
||||
derive_array_instances!(15);
|
||||
derive_array_instances!(16);
|
||||
derive_array_instances!(17);
|
||||
derive_array_instances!(18);
|
||||
derive_array_instances!(19);
|
||||
derive_array_instances!(20);
|
||||
derive_array_instances!(21);
|
||||
derive_array_instances!(22);
|
||||
derive_array_instances!(23);
|
||||
derive_array_instances!(24);
|
||||
derive_array_instances!(25);
|
||||
derive_array_instances!(26);
|
||||
derive_array_instances!(27);
|
||||
derive_array_instances!(28);
|
||||
derive_array_instances!(29);
|
||||
derive_array_instances!(30);
|
||||
derive_array_instances!(31);
|
||||
derive_array_instances!(32);
|
||||
|
||||
derive_array_instances!(64);
|
||||
derive_array_instances!(65);
|
||||
|
||||
derive_array_instances!(128);
|
||||
derive_array_instances!(256);
|
||||
derive_array_instances!(512);
|
||||
derive_array_instances!(1024);
|
||||
derive_array_instances!(2048);
|
||||
|
||||
/*******************************************************************************
|
||||
Composite instances
|
||||
|
||||
This is the same set of tuple sizes as supported by `borsh.`
|
||||
*******************************************************************************/
|
||||
|
||||
derive_tuple_instances!(T0, T1);
|
||||
derive_tuple_instances!(T0, T1, T2);
|
||||
derive_tuple_instances!(T0, T1, T2, T3);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
|
||||
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
|
||||
derive_tuple_instances!(
|
||||
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17
|
||||
);
|
||||
derive_tuple_instances!(
|
||||
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18
|
||||
);
|
||||
derive_tuple_instances!(
|
||||
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19
|
||||
);
|
||||
|
||||
/*******************************************************************************
|
||||
Vec
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for Vec<T> {
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
|
||||
let tagged: Vec<&Haskell<Tag, T>> = self.iter().map(tag_ref).collect();
|
||||
tagged.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: FromHaskell<Tag>> FromHaskell<Tag> for Vec<T> {
|
||||
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let tagged: Vec<Haskell<Tag, T>> = BorshDeserialize::deserialize(buf)?;
|
||||
Ok(tagged.into_iter().map(untag_val).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
HashMap
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, K, V> ToHaskell<Tag> for HashMap<K, V>
|
||||
where
|
||||
K: Eq + PartialOrd + Hash + ToHaskell<Tag>,
|
||||
V: ToHaskell<Tag>,
|
||||
{
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
|
||||
let tagged: HashMap<&Haskell<Tag, K>, &Haskell<Tag, V>> =
|
||||
self.iter().map(|(k, v)| (tag_ref(k), tag_ref(v))).collect();
|
||||
tagged.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, K, V> FromHaskell<Tag> for HashMap<K, V>
|
||||
where
|
||||
K: Eq + Hash + FromHaskell<Tag>,
|
||||
V: FromHaskell<Tag>,
|
||||
{
|
||||
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let tagged: HashMap<Haskell<Tag, K>, Haskell<Tag, V>> = BorshDeserialize::deserialize(buf)?;
|
||||
Ok(tagged
|
||||
.into_iter()
|
||||
.map(|(k, v)| (untag_val(k), untag_val(v)))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
HashSet
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, T> ToHaskell<Tag> for HashSet<T>
|
||||
where
|
||||
T: Eq + PartialOrd + Hash + ToHaskell<Tag>,
|
||||
{
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
|
||||
let tagged: HashSet<&Haskell<Tag, T>> = self.iter().map(tag_ref).collect();
|
||||
tagged.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T> FromHaskell<Tag> for HashSet<T>
|
||||
where
|
||||
T: Eq + Hash + FromHaskell<Tag>,
|
||||
{
|
||||
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let tagged: HashSet<Haskell<Tag, T>> = BorshDeserialize::deserialize(buf)?;
|
||||
Ok(tagged.into_iter().map(untag_val).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Option
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for Option<T> {
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
|
||||
let tagged: Option<&Haskell<Tag, T>> = self.as_ref().map(tag_ref);
|
||||
tagged.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: FromHaskell<Tag>> FromHaskell<Tag> for Option<T> {
|
||||
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let tagged: Option<Haskell<Tag, T>> = BorshDeserialize::deserialize(buf)?;
|
||||
Ok(tagged.map(untag_val))
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Result
|
||||
|
||||
`Result` is not explicitly mentioned by the Borsh spec, but it's ubiquitous
|
||||
and so we provide an instance for it, following the standard rule for enum.
|
||||
|
||||
There is no need for an instance of `FromHaskell`, since this is indicating
|
||||
the result of some Rust-side operation.
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, T: ToHaskell<Tag>, E: ToHaskell<Tag>> ToHaskell<Tag> for Result<T, E> {
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
|
||||
let tagged: Result<&Haskell<Tag, T>, &Haskell<Tag, E>> = match self {
|
||||
Ok(t) => Ok(tag_ref(t)),
|
||||
Err(e) => Err(tag_ref(e)),
|
||||
};
|
||||
tagged.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Bool
|
||||
|
||||
The Borsh spec does not mention Bool; we encode `true` as 1 and `false` as 0;
|
||||
this matches what the Haskell `borsh` library does.
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag> HaskellSize<Tag> for bool {
|
||||
fn haskell_size(tag: PhantomData<Tag>) -> usize {
|
||||
u8::haskell_size(tag)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> ToHaskell<Tag> for bool {
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, tag: PhantomData<Tag>) -> Result<(), Error> {
|
||||
let as_u8: u8 = if *self { 1 } else { 0 };
|
||||
as_u8.to_haskell(writer, tag)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> FromHaskell<Tag> for bool {
|
||||
fn from_haskell(buf: &mut &[u8], tag: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let as_u8 = u8::from_haskell(buf, tag)?;
|
||||
match as_u8 {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => Err(Error::new(ErrorKind::InvalidData, "Invalid bool")),
|
||||
}
|
||||
}
|
||||
}
|
16
haskell-ffi/src/lib.rs
Normal file
16
haskell-ffi/src/lib.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
#![feature(array_methods)]
|
||||
#![feature(trace_macros)]
|
||||
|
||||
mod instances;
|
||||
mod macros;
|
||||
|
||||
pub mod bincode;
|
||||
pub mod deriving_via;
|
||||
pub mod from_haskell;
|
||||
pub mod haskell_size;
|
||||
pub mod to_haskell;
|
||||
pub mod use_borsh;
|
||||
|
||||
pub use from_haskell::FromHaskell;
|
||||
pub use haskell_size::HaskellSize;
|
||||
pub use to_haskell::ToHaskell;
|
194
haskell-ffi/src/macros.rs
Normal file
194
haskell-ffi/src/macros.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*******************************************************************************
|
||||
Auxiliary general-purpose macros
|
||||
|
||||
The `map_tuple` macro is adapted from
|
||||
https://stackoverflow.com/questions/66396814/generating-tuple-indices-based-on-macro-rules-repetition-expansion .
|
||||
*******************************************************************************/
|
||||
|
||||
/// Map function across all elements of a tuple
|
||||
///
|
||||
/// ```ignore
|
||||
/// map_tuple( [T0, T1], tuple, f )
|
||||
/// ```
|
||||
///
|
||||
/// will become
|
||||
///
|
||||
/// ```ignore
|
||||
/// ( f(tuple.0) , f(tuple.1) )
|
||||
/// ```
|
||||
///
|
||||
/// See also `map_tuple_ref`.
|
||||
#[macro_export]
|
||||
macro_rules! map_tuple {
|
||||
// Base-case: we are done. Return the accumulator
|
||||
//
|
||||
// We explicitly allow the list of indices to be non-empty (not all indices might be used)
|
||||
( @, $tuple:ident, $fn:ident, [], [ $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
|
||||
( $($acc),* )
|
||||
};
|
||||
|
||||
// Recursive-case: add entry to accumulator
|
||||
( @, $tuple:ident, $fn:ident, [ $t:ident $(,$ts:ident)* ], [ ($ix:tt) $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
|
||||
map_tuple!(@, $tuple, $fn, [ $($ts),* ], [ $($ixs)* ], [ $($acc)* ($fn($tuple . $ix)) ])
|
||||
};
|
||||
|
||||
// Entry-point into the macro
|
||||
( [ $($ts:ident),* ], $tuple:ident, $fn:ident ) => {
|
||||
map_tuple!(@, $tuple, $fn,
|
||||
// Pass original list of identifiers (only used to determine tuple length)
|
||||
[ $($ts),* ]
|
||||
|
||||
// Pre-defined list of tuple indices
|
||||
, [(0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19)]
|
||||
|
||||
// Empty accumulator
|
||||
, []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Variation on `map_tuple` that uses a _reference_ to a tuple
|
||||
///
|
||||
/// TODO: It seems I cannot unify these two macros, because `&self.0` and `(&self).0` are not
|
||||
/// equivalent expressions. Is that true..?
|
||||
#[macro_export]
|
||||
macro_rules! map_tuple_ref {
|
||||
// Base-case: we are done. Return the accumulator
|
||||
//
|
||||
// We explicitly allow the list of indices to be non-empty (not all indices might be used)
|
||||
( @, $tuple:ident, $fn:ident, [], [ $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
|
||||
( $($acc),* )
|
||||
};
|
||||
|
||||
// Recursive-case: add entry to accumulator
|
||||
( @, $tuple:ident, $fn:ident, [ $t:ident $(,$ts:ident)* ], [ ($ix:tt) $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
|
||||
map_tuple_ref!(@, $tuple, $fn, [ $($ts),* ], [ $($ixs)* ], [ $($acc)* ($fn(&$tuple . $ix)) ])
|
||||
};
|
||||
|
||||
// Entry-point into the macro
|
||||
( [ $($ts:ident),* ], $tuple:ident, $fn:ident ) => {
|
||||
map_tuple_ref!(@, $tuple, $fn,
|
||||
// Pass original list of identifiers (only used to determine tuple length)
|
||||
[ $($ts),* ]
|
||||
|
||||
// Pre-defined list of tuple indices
|
||||
, [(0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19)]
|
||||
|
||||
// Empty accumulator
|
||||
, []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold a list of types
|
||||
///
|
||||
/// ```ignore
|
||||
/// fold_types!( [T0, T1], haskell_size, tag, +, 0);
|
||||
/// ```
|
||||
///
|
||||
/// expands to
|
||||
///
|
||||
/// ```ignore
|
||||
/// 0 + <T0>::haskell_size(tag) + <T1>::haskell_size(tag)
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! fold_types {
|
||||
// Base-case: we are done. Return the accumulator
|
||||
( @, $f:ident, $arg:ident, $op:tt, [], $acc:tt ) => {
|
||||
$acc
|
||||
};
|
||||
|
||||
// Recursive-case: add entry to accumulator
|
||||
( @, $f:ident, $arg:ident, $op:tt, [ $t:ty $(,$ts:ty)* ], $acc:tt ) => {
|
||||
fold_types!(@, $f, $arg, $op, [ $($ts),* ], ( $acc $op (<$t> :: $f($arg)) ))
|
||||
};
|
||||
|
||||
// Entry-point into the macro
|
||||
( [ $($ts:ty),* ], $f:ident, $arg:ident, $op:tt, $e:tt ) => {
|
||||
fold_types!(@, $f, $arg, $op, [ $($ts),* ], $e)
|
||||
};
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Macros for deriving specific kinds of instances
|
||||
*******************************************************************************/
|
||||
|
||||
/// Derive `ToHaskell` and `FromHaskell` instances for simple types: types with
|
||||
/// no type arguments.
|
||||
#[macro_export]
|
||||
macro_rules! derive_simple_instances {
|
||||
($t:ty) => {
|
||||
impl<Tag> ToHaskell<Tag> for $t {
|
||||
fn to_haskell<W: Write>(
|
||||
&self,
|
||||
writer: &mut W,
|
||||
_: PhantomData<Tag>,
|
||||
) -> Result<(), Error> {
|
||||
self.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> FromHaskell<Tag> for $t {
|
||||
fn from_haskell(buf: &mut &[u8], _tag: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
<$t>::deserialize(buf)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Derive `ToHaskell` and `FromHaskell` instances for arrays of the specified size.
|
||||
#[macro_export]
|
||||
macro_rules! derive_array_instances {
|
||||
($sz : literal) => {
|
||||
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for [T; $sz] {
|
||||
fn to_haskell<W: Write>(
|
||||
&self,
|
||||
writer: &mut W,
|
||||
_: PhantomData<Tag>,
|
||||
) -> Result<(), Error> {
|
||||
let tagged: [&Haskell<Tag, T>; $sz] = self.each_ref().map(tag_ref);
|
||||
tagged.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: FromHaskell<Tag> + Default + Copy> FromHaskell<Tag> for [T; $sz] {
|
||||
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let tagged: [Haskell<Tag, T>; $sz] = BorshDeserialize::deserialize(buf)?;
|
||||
Ok(tagged.map(untag_val))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Derive `ToHaskell` and `FromHaskell` for tuples with the specified number of type arguments
|
||||
/// (i.e., for tuples of the specified size).
|
||||
#[macro_export]
|
||||
macro_rules! derive_tuple_instances {
|
||||
($($ts:ident),*) => {
|
||||
impl<Tag, $($ts: ToHaskell<Tag> ),* > ToHaskell<Tag> for ( $($ts ),* ) {
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W,_: PhantomData<Tag>) -> Result<(), Error> {
|
||||
let tagged: ( $(&Haskell<Tag, $ts> ),* ) = map_tuple_ref!( [ $($ts),* ], self, tag_ref );
|
||||
tagged.serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, $($ts: FromHaskell<Tag> ),* > FromHaskell<Tag> for ( $($ts ),* ) {
|
||||
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
let tagged: ( $(Haskell<Tag, $ts> ),* ) = BorshDeserialize::deserialize(buf)?;
|
||||
Ok( map_tuple!( [ $($ts),* ], tagged, untag_val ) )
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Derive `HaskellSize` instance for tuple with the specified type arguments.
|
||||
#[macro_export]
|
||||
macro_rules! derive_size_tuple_instance {
|
||||
($($ts:ident),*) => {
|
||||
impl<Tag, $($ts: HaskellSize<Tag> ),* > HaskellSize<Tag> for ( $($ts),* ) {
|
||||
fn haskell_size(tag: PhantomData<Tag>) -> usize {
|
||||
fold_types!( [ $($ts),* ], haskell_size, tag, +, 0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
16
haskell-ffi/src/tagged.rs
Normal file
16
haskell-ffi/src/tagged.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
pub mod borsh_instances;
|
||||
mod macros;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Tagged<Tag, T> {
|
||||
pub value: T,
|
||||
pub tag: PhantomData<Tag>,
|
||||
}
|
||||
|
||||
pub fn tag<Tag, T>(t: T) -> Tagged<Tag, T> {
|
||||
Tagged {
|
||||
value: t,
|
||||
tag: PhantomData,
|
||||
}
|
||||
}
|
93
haskell-ffi/src/to_haskell.rs
Normal file
93
haskell-ffi/src/to_haskell.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use std::{
|
||||
io::{Error, Write},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use crate::HaskellSize;
|
||||
|
||||
/*******************************************************************************
|
||||
Main class definition
|
||||
*******************************************************************************/
|
||||
|
||||
// Copied from `borsh`
|
||||
const DEFAULT_SERIALIZER_CAPACITY: usize = 1024;
|
||||
|
||||
pub trait ToHaskell<Tag> {
|
||||
/// Serialize data to be sent to Haskell
|
||||
///
|
||||
/// This is the analogue of `BorshSerialize::serialize`.
|
||||
///
|
||||
/// The `tag` argument allows client libraries to define additional
|
||||
/// instances of `ToHaskell` for foreign (non-local) types. For example, the
|
||||
/// `solana-sdk-haskell` library can define a `ToHaskell` instance for
|
||||
/// `Keypair`, defined in `solana-sdk`, as long as it uses a tag `Solana`
|
||||
/// defined locally in the `solana-haskell-sdk` package.
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, tag: PhantomData<Tag>) -> Result<(), Error>;
|
||||
|
||||
fn to_haskell_vec(&self, tag: PhantomData<Tag>) -> Result<Vec<u8>, Error> {
|
||||
let mut result = Vec::with_capacity(DEFAULT_SERIALIZER_CAPACITY);
|
||||
self.to_haskell(&mut result, tag)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for &T {
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, tag: PhantomData<Tag>) -> Result<(), Error> {
|
||||
(*self).to_haskell(writer, tag)
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Derived functionality
|
||||
|
||||
These functions are not defined in the trait itself, to make it clear that
|
||||
they only exist at top-level calls, and will not be recursively called
|
||||
in various `ToHaskell` instances. This is important, because the `len`
|
||||
parameter that gives the length of the buffer only applies to the _overall_
|
||||
buffer.
|
||||
*******************************************************************************/
|
||||
|
||||
/// Marshall value with fixed-sized encoding
|
||||
///
|
||||
/// The `out_len` parameter is only used to verify that the Haskell-side and
|
||||
/// the Rust side agree on the length of the encoding.
|
||||
pub fn marshall_to_haskell_fixed<Tag, T>(t: &T, out: *mut u8, out_len: usize, tag: PhantomData<Tag>)
|
||||
where
|
||||
T: HaskellSize<Tag> + ToHaskell<Tag>,
|
||||
{
|
||||
let expected_len: usize = T::haskell_size(tag);
|
||||
if out_len != expected_len {
|
||||
panic!(
|
||||
"marshall_to_haskell_fixed: expected buffer of size {}, but got {}",
|
||||
expected_len, out_len
|
||||
)
|
||||
} else {
|
||||
let mut out_len_copy = out_len;
|
||||
marshall_to_haskell_var(t, out, &mut out_len_copy, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// Marshall value with variable-sized encoding
|
||||
pub fn marshall_to_haskell_var<Tag, T>(
|
||||
t: &T,
|
||||
out: *mut u8,
|
||||
out_len: &mut usize,
|
||||
tag: PhantomData<Tag>,
|
||||
) where
|
||||
T: ToHaskell<Tag>,
|
||||
{
|
||||
match t.to_haskell_vec(tag) {
|
||||
Ok(vec) => {
|
||||
let slice: &[u8] = vec.as_ref();
|
||||
|
||||
if slice.len() <= *out_len {
|
||||
unsafe {
|
||||
std::ptr::copy(slice.as_ptr(), out, slice.len());
|
||||
}
|
||||
}
|
||||
|
||||
*out_len = slice.len();
|
||||
}
|
||||
Err(e) => panic!("{}", e),
|
||||
}
|
||||
}
|
54
haskell-ffi/src/use_borsh.rs
Normal file
54
haskell-ffi/src/use_borsh.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use std::{
|
||||
io::{Error, Write},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use crate::{FromHaskell, ToHaskell};
|
||||
|
||||
/// Newtype wrapper for defaulting to `borsh` for `ToHaskell`/`FromHaskell`
|
||||
///
|
||||
/// `ToHaskell`/`FromHaskell` have instances for types such as `Vec<T>`, but
|
||||
/// those instances depend on `ToHaskell`/`FromHaskell` for `T`. This
|
||||
/// indirection is not always necessary, and may be expensive. The `UseBorsh`
|
||||
/// newtype wrapper can be used to mark values where `ToHaskell`/`FromHaskell`
|
||||
/// should just piggy-back on Borsh.
|
||||
pub struct UseBorsh<T>(pub T);
|
||||
|
||||
pub fn unwrap_use_borsh<T>(use_borsh: UseBorsh<T>) -> T {
|
||||
let UseBorsh(t) = use_borsh;
|
||||
t
|
||||
}
|
||||
|
||||
pub fn unwrap_use_borsh_ref<T>(use_borsh: &UseBorsh<T>) -> &T {
|
||||
let UseBorsh(t) = use_borsh;
|
||||
t
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Forwarding instances
|
||||
|
||||
These instances _define_ the `UseBorsh` type
|
||||
*******************************************************************************/
|
||||
|
||||
impl<Tag, T: BorshSerialize> ToHaskell<Tag> for UseBorsh<T> {
|
||||
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
|
||||
unwrap_use_borsh_ref(self).serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag, T: BorshDeserialize> FromHaskell<Tag> for UseBorsh<T> {
|
||||
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
|
||||
T::deserialize(buf).map(UseBorsh)
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Additional standard instances
|
||||
*******************************************************************************/
|
||||
|
||||
impl<T: AsRef<T>> AsRef<T> for UseBorsh<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
unwrap_use_borsh_ref(self).as_ref()
|
||||
}
|
||||
}
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel ="nightly"
|
Loading…
Reference in a new issue