Skip to content

Instantly share code, notes, and snippets.

@jmsdnns
Created April 24, 2025 18:12
Show Gist options
  • Save jmsdnns/7923459f7f24022c3710da88636bb888 to your computer and use it in GitHub Desktop.
Save jmsdnns/7923459f7f24022c3710da88636bb888 to your computer and use it in GitHub Desktop.
Using Rust library to parse yaml from Ocaml
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[repr(C)]
pub struct KeyValue {
pub key: *const c_char,
pub value: *const c_char,
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[unsafe(no_mangle)]
pub extern "C" fn parse_yaml(input: *const u8, len: usize) -> *mut KeyValue {
if input.is_null() {
return std::ptr::null_mut();
}
let slice = unsafe { std::slice::from_raw_parts(input, len) };
let yaml_str = match std::str::from_utf8(slice) {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let parsed: Result<Value, serde_yaml::Error> = serde_yaml::from_str(yaml_str);
match parsed {
Ok(Value::Mapping(mapping)) => {
if let Some((key, value)) = mapping.into_iter().next() {
let key_str = match key.as_str() {
Some(s) => s,
None => return std::ptr::null_mut(),
};
let value_str = match value.as_str() {
Some(s) => s,
None => return std::ptr::null_mut(),
};
let key_cstr = CString::new(key_str).unwrap();
let value_cstr = CString::new(value_str).unwrap();
let kv = KeyValue {
key: key_cstr.into_raw(),
value: value_cstr.into_raw(),
};
Box::into_raw(Box::new(kv))
} else {
std::ptr::null_mut()
}
}
_ => std::ptr::null_mut(),
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[unsafe(no_mangle)]
pub extern "C" fn free_key_value(kv: *mut KeyValue) {
if kv.is_null() {
return;
}
unsafe {
// frees the pointer
let kv_box = Box::from_raw(kv);
if !kv_box.key.is_null() {
let _ = CString::from_raw(kv_box.key as *mut c_char);
}
if !kv_box.value.is_null() {
let _ = CString::from_raw(kv_box.value as *mut c_char);
}
}
}
(*
ocamlfind ocamlopt -linkpkg -package ctypes.foreign yoml.ml -o main
LD_LIBRARY_PATH=../yamlrs/target/debug ./main
*)
open Ctypes
open Foreign
let lib =
Dl.dlopen ~filename:"../yamlrs/target/debug/libyamlrs.so"
~flags:[ Dl.RTLD_NOW ]
(* matches rust struct *)
type key_value
let key_value : key_value structure typ = structure "KeyValue"
let key = field key_value "key" (ptr char)
let value = field key_value "value" (ptr char)
let () = seal key_value
(* bindings for both rust functions *)
let parse_yaml =
foreign ~from:lib "parse_yaml"
(ptr uint8_t @-> size_t @-> returning (ptr_opt key_value))
let free_key_value =
foreign ~from:lib "free_key_value" (ptr key_value @-> returning void)
(* converts c-string to ocaml string *)
let string_from_c_ptr (p : char ptr) : string =
if p = from_voidp char null then "" else coerce (ptr char) string p
(* the parse function *)
let parse str =
let len = String.length str in
let buf = CArray.of_string str in
let input_ptr = coerce (ptr char) (ptr uint8_t) (CArray.start buf) in
match parse_yaml input_ptr (Unsigned.Size_t.of_int len) with
| None -> None
| Some kv_ptr ->
let kv = !@kv_ptr in
let k = string_from_c_ptr (getf kv key) in
let v = string_from_c_ptr (getf kv value) in
free_key_value kv_ptr;
Some (k, v)
let () =
let yaml = "name: ocaml" in
match parse yaml with
| Some (k, v) -> Printf.printf "Parsed: %s => %s\n" k v
| None -> Printf.printf "Failed to parse YAML\n"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment