Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
zshipko committed Feb 15, 2018
0 parents commit 8580c24
Show file tree
Hide file tree
Showing 17 changed files with 837 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
target
.idea
*.iml
Cargo.lock
*~
*.bk
_build
*.o
*.cmx
*.cmi
*#*
examples/ocaml/test
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "ocaml"
version = "0.1.0"
authors = ["Zach Shipko <[email protected]>"]
readme = "README.md"
keywords = ["ocaml", "rust", "ffi"]
repository = "https://github.com/zshipko/ocaml-rs"
license = "ISC"
description = "OCaml bindings for Rust"
documentation = "https://docs.rs/raml"

[dependencies]
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SRC=$(wildcard src/*.rs)
ARGS=-- -Z unstable-options --pretty=expanded

pretty: $(SRC)
@cargo rustc $(ARGS)
@cd examples/rust && cargo rustc $(ARGS)
@cd examples/ocaml && make pretty
examples/ocaml/test

test: $(SRC)
@cargo test
@cd examples/rust && cargo build
@cd examples/ocaml && make
examples/ocaml/test

clean:
cargo clean
cd examples/rust && cargo clean
cd examples/ocaml && make clean

.PHONE: test pretty clean
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ocaml-rs - OCaml FFI for Rust

NOTE: `ocaml-rs` is based on `raml v0.1`

Direct OCaml bindings without ever leaving Rust - no C stubs!

(you still have to know how the C ffi bindings work; if you do, the macros are almost identical to the C ones in their naming and purpose)

Please see the example in `examples` for the Rust code in `rust` for the Rust code that OCaml code will call and the `ocaml` directory for the OCaml code that calls the Rust code.

Also, please bear with me as I'm trying to add more documentation and examples, but I am very busy; if you see something, don't hesitate to add a PR or issue, thanks :)

A basic example demonstrates their usage:

```rust
caml!(ml_beef, |parameter|, <local>, {
let i = int_val!(parameter);
let res = 0xbeef * i;
println!("about to return 0x{:x} to OCaml runtime", res);
local = val_int!(res);
} -> local);
```

The macro takes care of _automatically_ declaring `CAMLparam` et. al, as well as `CAMLlocal` and `CAMLreturn`.

If you need more fine grained control, `caml_body!` and others are available.

### Documentation

https://docs.rs/ocaml/

24 changes: 24 additions & 0 deletions examples/ocaml/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## example makefile for building ocaml binary with rust lib
## NB: assumes you use rustup with standard install

RUSTSRC=$(wildcard ../rust/src/*.rs)
CLIENT=../rust/target/debug/libclient.a
ARGS=-- -Z unstable-options --pretty=expanded

build: test $(CLIENT)

$(CLIENT): $(RUSTSRC)
cd ../rust && cargo build

test: test.ml $(CLIENT)
ocamlopt.opt -verbose -cclib -lclient -ccopt -L../rust/target/debug -ccopt '-L ~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/' -cclib -ldl -cclib -lpthread test.ml -o test

pretty: test
cd ../rust && cargo rustc $(ARGS)

clean:
rm test *.o *.cm*
rm -rf _build
cd ../rust && cargo clean

.PHONY: build clean pretty
19 changes: 19 additions & 0 deletions examples/ocaml/test.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
external send_int : int -> int = "ml_send_int"
external send_two : int -> string -> unit = "ml_send_two"
external send_tuple : (int * int) -> int = "ml_send_tuple"
external new_tuple : unit -> (int * int * int) = "ml_new_tuple"

let f x = x land 0x0000ffff

let _ =
let string = "string thing" in
let deadbeef = 0xdeadbeef in
let res = send_int 0xb1b1eb0b in
Printf.printf "send_int returned: 0x%x\n" res;
flush stdout;
send_two deadbeef string;
send_two (f deadbeef) string;
let res = send_tuple (1, 2) in
Printf.printf "%d\n" res;
let (a, b, c) = new_tuple () in
Printf.printf "%d %d %d\n" a b c
10 changes: 10 additions & 0 deletions examples/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "client"
version = "0.1.0"
authors = ["m4b <[email protected]>"]

[lib]
crate-type = ["staticlib"]

[dependencies.ocaml]
path = "../../"
41 changes: 41 additions & 0 deletions examples/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#[macro_use]
extern crate ocaml;

// use std::io;
// use std::io::Write;

caml!(ml_send_int, |v, v2|, <l>, {
let x = int_val!(v);
l = val_int!(0xbeef);
println!("send_int 0x{:x}", x);
// io::stdout().flush();
} -> l);

caml!(ml_send_two, |v, v2|, {
println!("local root addr: {:p} caml_local_roots: {:#?}, v: {:?}", &ocaml::memory::caml_local_roots, ocaml::memory::caml_local_roots, v);
let x = int_val!(v);
let len = ocaml::mlvalues::caml_string_length(v2);
let ptr = string_val!(v2);
let slice = ::std::slice::from_raw_parts(ptr, len);
let string = ::std::str::from_utf8_unchecked(slice);
println!("got 0x{:x}, {}", x, string);
});

caml!(ml_send_tuple, |t|, <dest>, {
let x = int_val!(*field!(t, 0));
let y = int_val!(*field!(t, 1));

dest = val_int!(x + y);
} -> dest);

caml!(ml_new_tuple, |unit|, <dest, a, b, c>, {
a = val_int!(0);
b = val_int!(1);
c = val_int!(2);

let mut tuple = ocaml::Tuple::new(3);
tuple.set(0, a).unwrap();
tuple.set(1, b).unwrap();
tuple.set(2, c).unwrap();
dest = ocaml::Value::from(tuple);
} -> dest);
21 changes: 21 additions & 0 deletions src/alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! External definitions for allocating values in the OCaml runtime
use mlvalues::{Size, Value, Tag};

extern "C" {
pub fn caml_alloc(size: Size, tag: Tag) -> Value;
pub fn caml_alloc_small(size: Size, tag: Tag) -> Value;
pub fn caml_alloc_tuple(size: Size) -> Value;
pub fn caml_alloc_string(size: Size) -> Value; // size in bytes
pub fn caml_copy_string(string: *const u8) -> Value;
pub fn caml_copy_string_array(arr: *const *const u8) -> Value;

pub fn caml_copy_double(double: f64) -> Value;
pub fn caml_copy_int32(int: i32) -> Value; // defined in [ints.c]
pub fn caml_copy_int64(int: i64) -> Value; // defined in [ints.c]
pub fn caml_copy_nativeint(int: isize) -> Value; // defined in [ints.c]
pub fn caml_alloc_array(value: (unsafe extern "C" fn(*const u8) -> Value),
array: *mut *mut u8)
-> Value;
// CAMLextern value caml_alloc_sprintf( const char * format, ... ); // this is going to be interesting
}
49 changes: 49 additions & 0 deletions src/callback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Callbacks from C to OCaml
//! This is also where you initialize the OCaml runtime system via `caml_startup` or `caml_main`
//!
use mlvalues::Value;

extern "C" {
pub fn caml_callback(closure: Value, arg: Value) -> Value;
pub fn caml_callback2(closure: Value, arg1: Value, arg2: Value) -> Value;
pub fn caml_callback3(closure: Value, arg1: Value, arg2: Value, arg3: Value) -> Value;
pub fn caml_callbackN(closure: Value, narg: usize, args: *mut Value) -> Value;

pub fn caml_callback_exn(closure: Value, arg1: Value) -> Value;
pub fn caml_callback2_exn(closure: Value, arg1: Value, arg2: Value) -> Value;
pub fn caml_callback3_exn(closure: Value, arg1: Value, arg2: Value, arg3: Value) -> Value;
pub fn caml_callbackN_exn(closure: Value, narg: usize, args: *mut Value) -> Value;

pub fn caml_main(argv: *mut *mut u8);
pub fn caml_startup(argv: *mut *mut u8);
pub fn caml_named_value(name: *const u8) -> *mut Value;

pub static mut caml_callback_depth: usize;
}

#[macro_export]
macro_rules! make_exception_result {
($v:ident) => {
(v as usize) | 2
}
}

#[macro_export]
macro_rules! is_exception_result {
($v:ident) => {
(v as usize) & 3 == 2
}
}

#[macro_export]
macro_rules! extract_exception {
($v:ident) => {
(v as usize) & !3
}
}

/*
typedef void (*caml_named_action) (value*, char *);
CAMLextern void caml_iterate_named_values(caml_named_action f);
*/
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[derive(Debug)]
pub enum Error {
OutOfBounds
}
94 changes: 94 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Raml is a library for directly interacting with the C OCaml runtime, in Rust.
//! Consquently, ocaml is designed for rust shared objects that expose raw C FFI bindings,
//! which are then either statically or dynamically linked against an OCaml binary, which calls into these raw FFI bindings as if they were
//! regular, so-called "C stubs". Similarly, any OCaml runtime functions, such as `caml_string_length`, will get their definition from the
//! final _OCaml_ binary, with its associated runtime.
//!
//! The benefit of this approach is that it removes any bridging C code, and allows in essence, a direct interface between Rust and OCaml.
//!
//! The macro has the format: `(name, |params...|, <local variables..>, { // code body }, -> optional return value listed in local variables`)
//!
//! A basic example demonstrates their usage:
//!
//! ```rust
//! #[macro_use] extern crate ocaml;
//!
//! // linking against this rust static lib, you can access this function in OCaml via:
//! // `external beef : int -> int = "ml_beef"`
//! caml!(ml_beef, |parameter|, <local>, {
//! let i = int_val!(parameter);
//! let res = 0xbeef * i;
//! println!("about to return 0x{:x} to OCaml runtime", res);
//! local = val_int!(res);
//! } -> local);
//!
//! // this is only here to satisfy docs
//! fn main() {}
//! ```
//!
//! The macro takes care of _automatically_ declaring `CAMLparam` et. al, as well as `CAMLlocal` and `CAMLreturn`.
//!
//! If you need more fine grained control, `caml_body!` and others are available.
//!
//! Some more examples:
//!
//! ```rust
//! #[macro_use] extern crate ocaml;
//!
//! // these are two functions that OCaml code can access via `external val` declarations
//! caml!(ml_send_int, |v, v2|, <l>, {
//! let x = int_val!(v);
//! l = val_int!(0xbeef);
//! println!("send_int 0x{:x}", x);
//! // io::stdout().flush();
//! } -> l);
//!
//! caml!(ml_send_two, |v, v2|, {
//! println!("local root addr: {:p} caml_local_roots: {:#?}, v: {:?}", &ocaml::memory::caml_local_roots, ocaml::memory::caml_local_roots, v);
//! let x = int_val!(v);
//! let len = ocaml::mlvalues::caml_string_length(v2);
//! let ptr = string_val!(v2);
//! let slice = ::std::slice::from_raw_parts(ptr, len);
//! let string = ::std::str::from_utf8_unchecked(slice);
//! println!("got 0x{:x}, {}", x, string);
//! });
//!
//! // this is only here to satisfy docs, you will likely want a staticlib, not a rust executable
//! fn main() {}
//! ```
//!
//! These will be accessible to an OCaml program via `external` declarations, e.g., for the above we could do something like:
//!
//! ```OCaml
//! external send_int : int -> int = "ml_send_int"
//! external send_two : int -> string -> unit = "ml_send_two"
//!
//! let f x = x land 0x0000ffff
//!
//! let _ =
//! let string = "string thing" in
//! let deadbeef = 0xdeadbeef in
//! let res = send_int 0xb1b1eb0b in
//! Printf.printf "send_int returned: 0x%x\n" res;
//! flush stdout;
//! send_two deadbeef string;
//! send_two (f deadbeef) string
//! ```
//!
mod tag;
mod types;
mod error;

#[macro_use]
pub mod mlvalues;
#[macro_use]
pub mod memory;
pub mod alloc;
pub mod callback;

pub use error::Error;
pub use mlvalues::Value;
pub use tag::Tag;
pub use types::{Array, Tuple};

Loading

0 comments on commit 8580c24

Please sign in to comment.