Skip to content

Commit

Permalink
ocaml_export! macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Lupus committed Jan 17, 2025
1 parent 11598a0 commit acac6c7
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 0 deletions.
89 changes: 89 additions & 0 deletions src/ocaml_gen_extras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,95 @@ where
}
}

#[macro_export]
macro_rules! ocaml_export {
($inner_type:ty, $new_type:ident, $ocaml_path:expr) => {
#[allow(dead_code)]
pub struct $new_type($inner_type);

impl ::std::ops::Deref for $new_type {
type Target = $inner_type;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl ::std::ops::DerefMut for $new_type {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl ::std::convert::From<$inner_type> for $new_type {
fn from(inner: $inner_type) -> Self {
Self(inner)
}
}

unsafe impl ocaml::ToValue for $new_type {
fn to_value(&self, rt: &ocaml::Runtime) -> ocaml::Value {
self.0.to_value(rt)
}
}

unsafe impl ocaml::FromValue for $new_type {
fn from_value(v: ocaml::Value) -> Self {
$new_type::from_value(v)
}
}

impl ::ocaml_gen::OCamlDesc for $new_type {
fn ocaml_desc(env: &::ocaml_gen::Env, generics: &[&str]) -> String {
// We clone an env
let mut env = env.clone();
// Ask our inner type to produce ocaml binding for a new type in
// the cloned env under desired name, we ignore the actial
// binding code returned by `ocaml_binding` as we don't need it
<$inner_type as ::ocaml_gen::OCamlBinding>::ocaml_binding(
&mut env,
Some($ocaml_path),
true,
);
// Call ocaml_desc for our inner type in this new env with
// defined binding
let res =
<$inner_type as ::ocaml_gen::OCamlDesc>::ocaml_desc(&env, generics);
// Discard the env to avoid panics on drop as we're still nested
// in some module etc
env.discard();
// Return the ocaml_desc produced in fake env
res
}

fn unique_id() -> u128 {
<$inner_type as ::ocaml_gen::OCamlDesc>::unique_id()
}
}

impl ::ocaml_gen::OCamlBinding for $new_type {
fn ocaml_binding(
env: &mut ::ocaml_gen::Env,
rename: Option<&'static str>,
new_type: bool,
) -> String {
let ty_id = <Self as ::ocaml_gen::OCamlDesc>::unique_id();
let name = <Self as ::ocaml_gen::OCamlDesc>::ocaml_desc(env, &[]);

if new_type {
panic!("can't declare a new type for {}, as it's exported from other lib, \
you can declare an alias for it if you really want to", stringify!($new_type));
} else {
let ty_name =
rename.expect("bug in ocaml-gen: rename should be Some");
env.add_alias(ty_id, ty_name);
format!("type nonrec {} = {}", ty_name, name)
}
}
}
};
}

/// Represents a plugin for generating OCaml bindings.
/// It contains a generator function and the name of the crate.
pub struct OcamlGenPlugin {
Expand Down
13 changes: 13 additions & 0 deletions test/Some_other_lib.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Barn = struct
type t
end

module Animal = struct
type tags =
[ `Ocaml_rs_smartptr_test_stubs_animal_proxy
| `Core_marker_send
]

type 'a t' = ([> tags ] as 'a) Ocaml_rs_smartptr.Rusty_obj.t
type t = tags t'
end
13 changes: 13 additions & 0 deletions test/Stubs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,16 @@ module Animal_alias = struct

external create_random_animal : string -> _ animal' = "animal_create_random"
end

module Export_import = struct
external barn_create : int32 -> Some_other_lib.Barn.t = "barn_create"

type nonrec barn = Some_other_lib.Barn.t

external barn_create_with_alias : int32 -> barn = "barn_create"

external dynbox_with_animal_create
: string
-> _ Some_other_lib.Animal.t'
= "dynbox_with_animal_create"
end
39 changes: 39 additions & 0 deletions test/src/stubs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,38 @@ pub fn call_cb(
res
}

// ocaml_export! bindings

#[derive(ocaml::ToValue, ocaml::FromValue, ocaml_gen::CustomType)]
pub struct Barn {
size: u32,
}

type DynBoxWithAnimal = DynBox<dyn AnimalProxy + Send + Sync>;

pub mod exports {
ocaml_rs_smartptr::ocaml_export!(crate::stubs::Barn, Barn, "Some_other_lib.Barn.t");
ocaml_rs_smartptr::ocaml_export!(
crate::stubs::DynBoxWithAnimal,
DynBoxWithAnimal,
"Some_other_lib.Animal.t"
);
}

#[ocaml_gen::func]
#[ocaml::func]
pub fn barn_create(size: u32) -> exports::Barn {
Barn { size }.into()
}

#[ocaml_gen::func]
#[ocaml::func]
pub fn dynbox_with_animal_create(name: String) -> exports::DynBoxWithAnimal {
let wolf: Wolf = animals::Animal::new(name);
let animal: Box<dyn AnimalProxy + Send + Sync> = Box::new(wolf);
DynBox::new_exclusive_boxed(animal).into()
}

// Register types & traits
register_rtti! {
register_trait!(
Expand Down Expand Up @@ -194,4 +226,11 @@ ocaml_gen_bindings! {
decl_type_alias!("animal" => DynBox<Animal>);
decl_func!(animal_create_random => "create_random_animal");
});

decl_module!("Export_import", {
decl_func!(barn_create => "barn_create");
decl_type_alias!("barn" => exports::Barn);
decl_func!(barn_create => "barn_create_with_alias");
decl_func!(dynbox_with_animal_create => "dynbox_with_animal_create");
});
}

0 comments on commit acac6c7

Please sign in to comment.