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