// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#![feature(exit_status_error)]

use std::process::Command;
use std::process::ExitStatusError;

pub fn cmd(prog: &str, args: &[&str], dir: Option<&std::path::Path>) -> Command {
    let mut prog_cmd = Command::new(prog);
    if let Some(path) = dir {
        prog_cmd.current_dir(path);
    }
    prog_cmd.args(args);
    prog_cmd
}

pub fn workspace_dir(ds: &[&str]) -> std::path::PathBuf {
    let mut cargo_cmd = cmd(
        "cargo",
        &["locate-project", "--workspace", "--message-format=plain"],
        None,
    );
    let output = cargo_cmd.output().unwrap().stdout;
    let root_cargo_toml = std::path::Path::new(std::str::from_utf8(&output).unwrap().trim());
    let mut p = root_cargo_toml.parent().unwrap().to_path_buf();
    for d in ds {
        p.push(d);
    }
    p
}

pub fn run(mut cmd: Command) -> Result<(), ExitStatusError> {
    cmd.spawn().unwrap().wait().ok().unwrap().exit_ok()
}

pub fn fmt_exit_status_err(err: ExitStatusError) -> String {
    format!("error status: {err}")
}

pub fn build_flavor() -> &'static str {
    if cfg!(debug_assertions) {
        "debug"
    } else {
        "release"
    }
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use std::fmt::Display;

use proc_macro2::TokenStream;
use proc_macro2::TokenTree;

fn mismatch(ta: Option<TokenTree>, tb: Option<TokenTree>, ax: &TokenStream, bx: &TokenStream) -> ! {
    panic!(
        "Mismatch!\nLeft:  {}\nRight: {}\nwhen comparing\nLeft:  {}\nRight: {}\n",
        ta.map_or("None".into(), |t| t.to_string()),
        tb.map_or("None".into(), |t| t.to_string()),
        ax,
        bx
    );
}

pub fn assert_pat_eq<E: Display>(a: Result<TokenStream, E>, b: TokenStream) {
    let a = match a {
        Err(err) => {
            panic!("Unexpected error '{}'", err);
        }
        Ok(ok) => ok,
    };

    fn inner_cmp(a: TokenStream, b: TokenStream, ax: &TokenStream, bx: &TokenStream) {
        let mut ia = a.into_iter();
        let mut ib = b.into_iter();

        loop {
            let t_a = ia.next();
            let t_b = ib.next();
            if t_a.is_none() && t_b.is_none() {
                break;
            }
            if t_a.is_none() || t_b.is_none() {
                mismatch(t_a, t_b, ax, bx);
            }
            let t_a = t_a.unwrap();
            let t_b = t_b.unwrap();

            match (&t_a, &t_b) {
                (TokenTree::Ident(a), TokenTree::Ident(b)) if a == b => {}
                (TokenTree::Literal(a), TokenTree::Literal(b))
                    if a.to_string() == b.to_string() => {}
                (TokenTree::Punct(a), TokenTree::Punct(b)) if a.to_string() == b.to_string() => {}
                (TokenTree::Group(ga), TokenTree::Group(gb))
                    if ga.delimiter() == gb.delimiter() =>
                {
                    inner_cmp(ga.stream(), gb.stream(), ax, bx);
                }
                (TokenTree::Group(_), _)
                | (TokenTree::Ident(_), _)
                | (TokenTree::Punct(_), _)
                | (TokenTree::Literal(_), _) => mismatch(Some(t_a), Some(t_b), ax, bx),
            }
        }
    }

    inner_cmp(a.clone(), b.clone(), &a, &b);
}

pub fn assert_error<E: Display>(a: Result<TokenStream, E>, b: &str) {
    match a {
        Ok(a) => panic!("Expected error but got:\n{}", a),
        Err(e) => {
            let a = format!("{}", e);
            assert_eq!(a, b, "Incorrect error")
        }
    }
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use std::cell::RefCell;
use std::cmp::max;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

use crate::block::Header;
use crate::Allocator;
use crate::BlockBuilder;
use crate::MemoizationCache;
use crate::ToOcamlRep;
use crate::Value;

struct Chunk {
    data: Box<[Value<'static>]>,
    index: usize,

    /// Pointer to the prev arena segment.
    prev: Option<Box<Chunk>>,
}

impl Chunk {
    fn with_capacity(capacity: usize) -> Self {
        Self {
            index: 0,
            data: vec![Value::int(0); capacity].into_boxed_slice(),
            prev: None,
        }
    }

    fn capacity(&self) -> usize {
        self.data.len()
    }

    fn can_fit(&self, requested_size: usize) -> bool {
        self.index + requested_size <= self.data.len()
    }

    #[inline]
    pub fn alloc(&mut self, requested_size: usize) -> &mut [Value<'static>] {
        let previous_index = self.index;
        self.index += requested_size;
        &mut self.data[previous_index..self.index]
    }
}

// The generation number is used solely to identify which arena a cached value
// belongs to in `RcOc`.
//
// We use usize::max_value() / 2 here to avoid colliding with ocamlpool and
// SlabAllocator generation numbers (ocamlpool starts at 0, and SlabAllocator
// starts at usize::max_value() / 4). This generation trick isn't sound with the
// use of multiple generation counters, but this mitigation should make it
// extremely difficult to mix up values allocated with ocamlpool, Arena, and
// SlabAllocator in practice (one would have to serialize the same value with
// multiple Allocators, and only after increasing the generation of one by an
// absurd amount).
//
// If we add more allocators, we might want to rethink this strategy.
static NEXT_GENERATION: AtomicUsize = AtomicUsize::new(usize::max_value() / 2);

/// An [`Allocator`](trait.Allocator.html) which builds values in Rust-managed
/// memory. The memory is freed when the Arena is dropped.
pub struct Arena {
    generation: usize,
    current_chunk: RefCell<Chunk>,
    cache: MemoizationCache,
}

impl Arena {
    /// Create a new Arena with 4KB of capacity preallocated.
    pub fn new() -> Self {
        Self::with_capacity(1024 * 4)
    }

    /// Create a new Arena with `capacity_in_bytes` preallocated.
    pub fn with_capacity(capacity_in_bytes: usize) -> Self {
        let generation = NEXT_GENERATION.fetch_add(1, Ordering::SeqCst);
        let capacity_in_words = max(2, capacity_in_bytes / std::mem::size_of::<Value<'_>>());
        Self {
            generation,
            current_chunk: RefCell::new(Chunk::with_capacity(capacity_in_words)),
            cache: MemoizationCache::new(),
        }
    }

    #[inline]
    #[allow(clippy::mut_from_ref)]
    fn alloc<'a>(&'a self, requested_size: usize) -> &'a mut [Value<'a>] {
        if !self.current_chunk.borrow().can_fit(requested_size) {
            let prev_chunk_capacity = self.current_chunk.borrow().capacity();
            let prev_chunk = self.current_chunk.replace(Chunk::with_capacity(max(
                requested_size * 2,
                prev_chunk_capacity,
            )));
            self.current_chunk.borrow_mut().prev = Some(Box::new(prev_chunk));
        }
        let mut chunk = self.current_chunk.borrow_mut();
        let slice = chunk.alloc(requested_size);
        // Transmute the 'static lifetime to 'a, to allow Values which point to
        // blocks allocated using this Arena to be stored in other such blocks.
        // The lifetime ensures that callers cannot allow such Values to outlive
        // the arena (and therefore outlive the block they point to). This
        // transmute violates the 'static lifetime in Chunk, so it is critical
        // for safety that we never expose a view of those Values to code
        // outside this module (using the type `Value<'static>`).
        // Also transmute the unnamed lifetime referring to the mutable borrow
        // of `chunk` to 'a. This allows callers to hold multiple mutable blocks
        // at once. This is safe because the blocks handed out by Chunk::alloc
        // are non-overlapping, so there is no aliasing.
        unsafe { std::mem::transmute::<&'_ mut [Value<'static>], &'a mut [Value<'a>]>(slice) }
    }

    #[inline(always)]
    pub fn add<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a> {
        value.to_ocamlrep(self)
    }

    #[inline(always)]
    pub fn add_root<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a> {
        Allocator::add_root(self, value)
    }
}

impl Allocator for Arena {
    #[inline(always)]
    fn generation(&self) -> usize {
        self.generation
    }

    fn block_with_size_and_tag(&self, size: usize, tag: u8) -> BlockBuilder<'_> {
        let block = self.alloc(size + 1);
        let header = Header::new(size, tag);
        // Safety: We need to make sure that the Header written to index 0 of
        // this slice is never observed as a Value. We guarantee that by not
        // exposing raw Chunk memory--only allocated Values.
        block[0] = unsafe { Value::from_bits(header.to_bits()) };
        let slice = &mut block[1..];
        BlockBuilder::new(slice)
    }

    #[inline(always)]
    fn set_field<'a>(&self, block: &mut BlockBuilder<'a>, index: usize, value: Value<'a>) {
        unsafe { *self.block_ptr_mut(block).add(index) = value }
    }

    unsafe fn block_ptr_mut<'a>(&self, block: &mut BlockBuilder<'a>) -> *mut Value<'a> {
        block.address() as *mut _
    }

    fn memoized<'a>(
        &'a self,
        ptr: usize,
        size: usize,
        f: impl FnOnce(&'a Self) -> Value<'a>,
    ) -> Value<'a> {
        let bits = self.cache.memoized(ptr, size, || f(self).to_bits());
        // SAFETY: The only memoized values in the cache are those computed in
        // the closure on the previous line. Since f returns Value<'a>, any
        // cached bits must represent a valid Value<'a>,
        unsafe { Value::from_bits(bits) }
    }

    fn add_root<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a> {
        self.cache.with_cache(|| value.to_ocamlrep(self))
    }
}

#[cfg(test)]
mod tests {
    use std::io::Write;
    use std::time::Instant;

    use super::*;

    #[test]
    fn test_alloc_byte_string_with_len() {
        let arena = Arena::with_capacity(1000);

        let msg: &[u8] = b"Hello world!";
        let mut w = arena.byte_string_with_len(msg.len());
        let bytes_written = w.write(msg).ok().unwrap();
        assert!(bytes_written == msg.len());

        let value: Value<'_> = w.build();
        assert_eq!(value.as_byte_string().unwrap(), msg);
    }

    #[test]
    fn test_alloc_block_of_three_fields() {
        let arena = Arena::with_capacity(1000);

        let mut block = arena.block_with_size(3);
        arena.set_field(&mut block, 0, Value::int(1));
        arena.set_field(&mut block, 1, Value::int(2));
        arena.set_field(&mut block, 2, Value::int(3));
        let block = block.build().as_block().unwrap();

        assert_eq!(block.size(), 3);
        assert_eq!(block[0].as_int().unwrap(), 1);
        assert_eq!(block[1].as_int().unwrap(), 2);
        assert_eq!(block[2].as_int().unwrap(), 3);
    }

    #[test]
    fn test_large_allocs() {
        let arena = Arena::with_capacity(1000);

        let alloc_block = |size| arena.block_with_size(size).build().as_block().unwrap();

        let max = alloc_block(1000);
        assert_eq!(max.size(), 1000);

        let two_thousand = alloc_block(2000);
        assert_eq!(two_thousand.size(), 2000);

        let four_thousand = alloc_block(4000);
        assert_eq!(four_thousand.size(), 4000);
    }

    #[test]
    fn perf_test() {
        let arena = Arena::with_capacity(10_000);

        let alloc_block = |size| arena.block_with_size(size).build().as_block().unwrap();

        println!("Benchmarks for allocating [1] 200,000 times");
        let now = Instant::now();
        for _ in 0..200_000 {
            vec![0; 1].into_boxed_slice();
        }
        println!("Alloc: {:?}", now.elapsed());

        let now = Instant::now();
        for _ in 0..200_000 {
            alloc_block(1);
        }
        println!("Arena: {:?}", now.elapsed());

        println!("Benchmarks for allocating [5] 200,000 times");
        let now = Instant::now();
        for _ in 0..200_000 {
            vec![0; 5].into_boxed_slice();
        }
        println!("Alloc: {:?}", now.elapsed());

        let now = Instant::now();
        for _ in 0..200_000 {
            alloc_block(5);
        }
        println!("Arena: {:?}", now.elapsed());

        println!("Benchmarks for allocating [10] 200,000 times");
        let now = Instant::now();
        for _ in 0..200_000 {
            vec![0; 10].into_boxed_slice();
        }
        println!("Alloc: {:?}", now.elapsed());

        let now = Instant::now();
        for _ in 0..200_000 {
            alloc_block(10);
        }
        println!("Arena: {:?}", now.elapsed());
    }
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::ops::Index;

use crate::Allocator;
use crate::Value;

/// Blocks with tags greater than or equal to NO_SCAN_TAG contain binary data,
/// and are not scanned by the garbage collector. Likewise, we must avoid
/// interpreting the fields of blocks with such tags as Values.
pub const NO_SCAN_TAG: u8 = 251;
pub const FORWARD_TAG: u8 = 250;
pub const INFIX_TAG: u8 = 249;
pub const OBJECT_TAG: u8 = 248;
pub const CLOSURE_TAG: u8 = 247;
pub const LAZY_TAG: u8 = 246;
pub const CONT_TAG: u8 = 245;
pub const FORCING_TAG: u8 = 244;
pub const ABSTRACT_TAG: u8 = 251;
pub const STRING_TAG: u8 = 252;
pub const DOUBLE_TAG: u8 = 253;
pub const DOUBLE_ARRAY_TAG: u8 = 254;
pub const CUSTOM_TAG: u8 = 255;

/// A recently-allocated, not-yet-finalized Block.
#[repr(transparent)]
pub struct BlockBuilder<'a> {
    fields: &'a mut [Value<'a>],
}

impl<'a> BlockBuilder<'a> {
    /// `address` may be a pointer or an offset (the `Allocator` which invokes
    /// `BlockBuilder::new` determines the meaning of `BlockBuilder` addresses).
    /// `size` must be greater than 0 and denotes the number of fields in the
    /// block.
    ///
    /// # Panics
    ///
    /// Panics if `fields.is_empty()`.
    #[inline(always)]
    pub fn new(fields: &'a mut [Value<'a>]) -> Self {
        if fields.is_empty() {
            panic!()
        }
        Self { fields }
    }

    /// The address of the field slice passed to `BlockBuilder::new`.
    #[inline(always)]
    pub fn address(&self) -> usize {
        self.fields.as_ptr() as _
    }

    /// The number of fields in this block.
    #[inline(always)]
    pub fn size(&self) -> usize {
        self.fields.len()
    }

    // TODO(jakebailey): This needs to be marked unsafe. The caller must
    // initialize all of the fields.
    #[inline(always)]
    pub fn build(self) -> Value<'a> {
        unsafe { Value::from_ptr(self.fields.as_ptr()) }
    }
}

/// The contents of an OCaml block, consisting of a header and one or more
/// fields of type [`Value`](struct.Value.html).
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Block<'arena>(pub(crate) &'arena [Value<'arena>]);

impl<'a> Block<'a> {
    #[inline(always)]
    pub fn header(self) -> Header {
        Header(self.0[0].0)
    }

    #[inline(always)]
    pub fn size(self) -> usize {
        self.header().size()
    }

    #[inline(always)]
    pub fn tag(self) -> u8 {
        self.header().tag()
    }

    #[inline(always)]
    pub fn as_value(self) -> Value<'a> {
        unsafe { Value::from_ptr(&self.0[1]) }
    }

    #[inline(always)]
    pub fn as_values(self) -> Option<&'a [Value<'a>]> {
        if self.tag() >= NO_SCAN_TAG {
            return None;
        }
        Some(&self.0[1..])
    }

    #[inline(always)]
    pub fn as_int_slice(self) -> &'a [usize] {
        let slice = &self.0[1..];
        unsafe { std::slice::from_raw_parts(slice.as_ptr().cast(), slice.len()) }
    }

    /// Helper for `Value::clone_with_allocator`.
    pub(crate) fn clone_with<'b, A: Allocator>(
        self,
        alloc: &'b A,
        seen: &mut HashMap<usize, Value<'b>>,
    ) -> Value<'b> {
        let mut block = alloc.block_with_size_and_tag(self.size(), self.tag());
        match self.as_values() {
            Some(fields) => {
                for (i, field) in fields.iter().enumerate() {
                    let field = field.clone_with(alloc, seen);
                    alloc.set_field(&mut block, i, field)
                }
            }
            None => {
                // Safety: Both pointers must be valid, aligned, and
                // non-overlapping. Both pointers are the heads of blocks which
                // came from some Allocator. Allocators are required to allocate
                // blocks with usize-aligned pointers, and those blocks are
                // required to be valid for reads and writes for the number of
                // usize-sized fields reported in the size in their header.
                // Allocators are also required to allocate non-overlapping
                // blocks.
                unsafe {
                    std::ptr::copy_nonoverlapping(
                        self.0.as_ptr().offset(1) as *const usize,
                        alloc.block_ptr_mut(&mut block) as *mut usize,
                        self.size(),
                    )
                }
            }
        }
        block.build()
    }
}

impl<'a> Index<usize> for Block<'a> {
    type Output = Value<'a>;

    #[inline(always)]
    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index + 1]
    }
}

impl Debug for Block<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.tag() == STRING_TAG {
            write!(f, "{:?}", self.as_value().as_str().unwrap())
        } else if self.tag() == DOUBLE_TAG {
            write!(f, "{:?}", self.as_value().as_float().unwrap())
        } else {
            write!(f, "{}{:?}", self.tag(), self.as_values().unwrap())
        }
    }
}

// values from ocaml 'gc.h'
#[repr(usize)]
#[derive(Clone, Copy)]
pub enum Color {
    White = crate::CAML_WHITE,
    Gray = crate::CAML_GRAY,
    Blue = crate::CAML_BLUE,
    Black = crate::CAML_BLACK,
}

#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Header(usize);

impl Header {
    #[inline(always)]
    pub const fn new(size: usize, tag: u8) -> Self {
        Self::with_color(size, tag, Color::White)
    }

    #[inline(always)]
    pub const fn with_color(size: usize, tag: u8, color: Color) -> Self {
        let bits = size << 10 | (color as usize) | (tag as usize);
        Header(bits)
    }

    #[inline(always)]
    pub const fn size(self) -> usize {
        self.0 >> 10
    }

    #[inline(always)]
    pub const fn tag(self) -> u8 {
        self.0 as u8
    }

    #[inline(always)]
    pub const fn color(self) -> Color {
        match self.0 & Color::Black as usize {
            crate::CAML_WHITE => Color::White,
            crate::CAML_GRAY => Color::Gray,
            crate::CAML_BLUE => Color::Blue,
            crate::CAML_BLACK => Color::Black,
            _ => unreachable!(),
        }
    }

    #[inline(always)]
    pub const fn from_bits(bits: usize) -> Self {
        Header(bits)
    }

    #[inline(always)]
    pub const fn to_bits(self) -> usize {
        self.0
    }
}

impl Debug for Header {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Header")
            .field("size", &self.size())
            .field("tag", &self.tag())
            .finish()
    }
}

pub struct BlockBytes<'a>(&'a mut [u8], *const u8);

impl<'a> BlockBytes<'a> {
    // SAFETY: `bytes` must be the data segment of a valid block with
    // `STRING_TAG`, excluding the trailing padding bytes; see
    // `Allocator::byte_string_with_len`
    pub(crate) unsafe fn new(bytes: &'a mut [u8]) -> Self {
        BlockBytes(bytes, bytes.as_ptr())
    }
}

impl std::io::Write for BlockBytes<'_> {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        // If the number of bytes to be written exceeds the buffer size, error,
        // don't do short writes!
        self.0.write_all(buf)?;
        Ok(buf.len())
    }
    fn flush(&mut self) -> std::io::Result<()> {
        self.0.flush()
    }
}

impl<'a> BlockBytes<'a> {
    pub fn build(self) -> Value<'a> {
        let BlockBytes(slice, block) = self;
        if !slice.is_empty() {
            panic!("ocamlrep: BlockBytes invariant violation: not all bytes written to.");
        }
        unsafe { Value::from_bits(block as usize) }
    }
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

//! Provides `MemoizationCache`, a simple cache designed to aid implementation
//! of the `Allocator` trait.

use std::cell::RefCell;

type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;

/// A simple scoped cache for memoizing conversions from one pointer-sized value
/// to another. Useful for memoizing conversions between OCaml values and Rust
/// references.
pub struct MemoizationCache {
    /// Maps from input (address,size_in_bytes) -> output
    cache: RefCell<Option<HashMap<(usize, usize), usize>>>,
}

impl MemoizationCache {
    #[inline(always)]
    pub fn new() -> Self {
        Self {
            cache: RefCell::new(None),
        }
    }

    #[inline(always)]
    pub fn with_cache<T>(&self, f: impl FnOnce() -> T) -> T {
        // The `replace` below should not panic because the only borrows of
        // `self.cache` are in this function and in `memoized`. In both
        // functions, we do not hold a `Ref` or `RefMut` while calling into code
        // which might attempt to re-enter `memoized` or `with_cache`.
        let prev_value = self.cache.replace(Some(Default::default()));
        if prev_value.is_some() {
            panic!(
                "Attempted to re-enter MemoizationCache::with_cache \
                (probably via ocamlrep::Allocator::add_root, which is not re-entrant)"
            );
        }
        let result = f();
        // As above, this `replace` should not panic.
        self.cache.replace(None);
        result
    }

    #[inline(always)]
    pub fn memoized(&self, input: usize, size_in_bytes: usize, f: impl FnOnce() -> usize) -> usize {
        if size_in_bytes == 0 {
            return f();
        }
        let memoized_output = match (self.cache.borrow().as_ref())
            .map(|cache| cache.get(&(input, size_in_bytes)).copied())
        {
            None => return f(),
            Some(output) => output,
        };
        match memoized_output {
            Some(output) => output,
            None => {
                let output = f();
                // The `borrow_mut` below should not panic because the only
                // borrows of `self.cache` are in this function and in
                // `with_cache`. In this function, we do not hold a `Ref` or
                // `RefMut` while calling into `f` (or any other function which
                // might attempt to re-enter this function or `with_cache`).
                let mut cache = self.cache.borrow_mut();
                // The `unwrap` below should not panic. We know `self.cache` was
                // not None upon entering this function because we looked up
                // `memoized_output`. The only function which can replace the
                // cache with None is `with_cache`, which would have panicked if
                // `f` attempted to re-enter it.
                (cache.as_mut().unwrap()).insert((input, size_in_bytes), output);
                output
            }
        }
    }
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use std::error::Error;
use std::fmt;
use std::num::TryFromIntError;
use std::str::Utf8Error;

/// Returned by
/// [`OcamlRep::from_ocamlrep`](trait.OcamlRep.html#tymethod.from_ocamlrep) when
/// the given [`Value`](struct.Value.html) cannot be converted to a Rust value
/// of the expected type.
#[derive(Debug, PartialEq)]
pub enum FromError {
    BadUtf8(Utf8Error),
    BlockTagOutOfRange { max: u8, actual: u8 },
    ErrorInField(usize, Box<FromError>),
    ExpectedBlock(isize),
    ExpectedBlockTag { expected: u8, actual: u8 },
    ExpectedBool(isize),
    ExpectedChar(isize),
    ExpectedInt(usize),
    ExpectedUnit(isize),
    ExpectedZeroTag(u8),
    IntOutOfRange(TryFromIntError),
    NullaryVariantTagOutOfRange { max: usize, actual: isize },
    WrongBlockSize { expected: usize, actual: usize },
    UnexpectedCustomOps { expected: usize, actual: usize },
}

impl std::convert::From<TryFromIntError> for FromError {
    fn from(error: TryFromIntError) -> Self {
        FromError::IntOutOfRange(error)
    }
}

impl std::convert::From<Utf8Error> for FromError {
    fn from(error: Utf8Error) -> Self {
        FromError::BadUtf8(error)
    }
}

impl fmt::Display for FromError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use FromError::*;
        match self {
            BadUtf8(_) => write!(f, "Invalid UTF-8"),
            BlockTagOutOfRange { max, actual } => {
                write!(f, "Expected tag value <= {}, but got {}", max, actual)
            }
            ErrorInField(idx, _) => write!(f, "Failed to convert field {}", idx),
            ExpectedBlock(x) => write!(f, "Expected block, but got integer value {}", x),
            ExpectedBlockTag { expected, actual } => write!(
                f,
                "Expected block with tag {}, but got {}",
                expected, actual
            ),
            ExpectedBool(x) => write!(f, "Expected bool, but got {}", x),
            ExpectedChar(x) => write!(f, "Expected char, but got {}", x),
            ExpectedInt(x) => {
                write!(f, "Expected integer value, but got block pointer {:p}", x)
            }
            ExpectedUnit(x) => write!(f, "Expected (), but got {}", x),
            ExpectedZeroTag(x) => write!(
                f,
                "Expected block with tag 0 (tuple, record, cons cell, etc), but got tag value {}",
                x
            ),
            IntOutOfRange(_) => write!(f, "Integer value out of range"),
            NullaryVariantTagOutOfRange { max, actual } => write!(
                f,
                "Expected nullary variant tag, where 0 <= tag <= {}, but got {}",
                max, actual
            ),
            WrongBlockSize { expected, actual } => write!(
                f,
                "Expected block of size {}, but got size {}",
                expected, actual
            ),
            UnexpectedCustomOps { expected, actual } => write!(
                f,
                "Expected custom operations struct address 0x{:x}, but got address 0x{:x}",
                expected, actual
            ),
        }
    }
}

impl Error for FromError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        use FromError::*;
        match self {
            BadUtf8(err) => Some(err),
            ErrorInField(_, err) => Some(err),
            IntOutOfRange(err) => Some(err),
            BlockTagOutOfRange { .. }
            | ExpectedBlock(..)
            | ExpectedBlockTag { .. }
            | ExpectedBool(..)
            | ExpectedChar(..)
            | ExpectedInt(..)
            | ExpectedUnit(..)
            | ExpectedZeroTag(..)
            | NullaryVariantTagOutOfRange { .. }
            | WrongBlockSize { .. }
            | UnexpectedCustomOps { .. } => None,
        }
    }
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

//! Helpers for implementing `FromOcamlRep::from_ocamlrep` or
//! `FromOcamlRepIn::from_ocamlrep_in`.

use bumpalo::Bump;

use crate::Block;
use crate::FromError;
use crate::FromOcamlRep;
use crate::FromOcamlRepIn;
use crate::Value;

pub fn expect_int(value: Value<'_>) -> Result<isize, FromError> {
    match value.as_int() {
        Some(value) => Ok(value),
        None => Err(FromError::ExpectedInt(value.to_bits())),
    }
}

pub fn expect_nullary_variant(value: Value<'_>, max: usize) -> Result<isize, FromError> {
    let value = expect_int(value)?;
    let max_as_isize: isize = max.try_into().unwrap();
    if 0 <= value && value <= max_as_isize {
        Ok(value)
    } else {
        Err(FromError::NullaryVariantTagOutOfRange { max, actual: value })
    }
}

pub fn expect_block<'a>(value: Value<'a>) -> Result<Block<'a>, FromError> {
    match value.as_block() {
        Some(block) => Ok(block),
        None => Err(FromError::ExpectedBlock(value.as_int().unwrap())),
    }
}

pub fn expect_block_size(block: Block<'_>, size: usize) -> Result<(), FromError> {
    if block.size() != size {
        return Err(FromError::WrongBlockSize {
            expected: size,
            actual: block.size(),
        });
    }
    Ok(())
}

pub fn expect_block_tag(block: Block<'_>, tag: u8) -> Result<(), FromError> {
    if block.tag() != tag {
        return Err(FromError::ExpectedBlockTag {
            expected: tag,
            actual: block.tag(),
        });
    }
    Ok(())
}

pub fn expect_block_with_size_and_tag(
    value: Value<'_>,
    size: usize,
    tag: u8,
) -> Result<Block<'_>, FromError> {
    let block = expect_block(value)?;
    expect_block_size(block, size)?;
    expect_block_tag(block, tag)?;
    Ok(block)
}

pub fn expect_tuple<'a>(value: Value<'a>, size: usize) -> Result<Block<'a>, FromError> {
    let block = expect_block(value)?;
    expect_block_size(block, size)?;
    if block.tag() != 0 {
        return Err(FromError::ExpectedZeroTag(block.tag()));
    }
    Ok(block)
}

pub fn field<T: FromOcamlRep>(block: Block<'_>, field: usize) -> Result<T, FromError> {
    T::from_ocamlrep(block[field]).map_err(|e| FromError::ErrorInField(field, Box::new(e)))
}

pub fn field_in<'a, T: FromOcamlRepIn<'a>>(
    block: Block<'_>,
    field: usize,
    alloc: &'a Bump,
) -> Result<T, FromError> {
    T::from_ocamlrep_in(block[field], alloc)
        .map_err(|e| FromError::ErrorInField(field, Box::new(e)))
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use std::borrow::Borrow;
use std::borrow::Cow;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::hash::BuildHasher;
use std::hash::Hash;
use std::mem::size_of;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;

use bstr::BStr;
use bstr::BString;
use bumpalo::Bump;
use indexmap::IndexMap;
use indexmap::IndexSet;

use crate::block;
use crate::from;
use crate::Allocator;
use crate::FromError;
use crate::FromOcamlRep;
use crate::FromOcamlRepIn;
use crate::ToOcamlRep;
use crate::Value;

macro_rules! trivial_from_in_impl {
    ($ty:ty) => {
        impl<'a> FromOcamlRepIn<'a> for $ty {
            fn from_ocamlrep_in(value: Value<'_>, _alloc: &'a Bump) -> Result<Self, FromError> {
                Self::from_ocamlrep(value)
            }
        }
    };
}

impl ToOcamlRep for () {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int(0)
    }
}

impl FromOcamlRep for () {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        match from::expect_int(value)? {
            0 => Ok(()),
            x => Err(FromError::ExpectedUnit(x)),
        }
    }
}

trivial_from_in_impl!(());

impl ToOcamlRep for isize {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int(*self)
    }
}

impl FromOcamlRep for isize {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        from::expect_int(value)
    }
}

trivial_from_in_impl!(isize);

impl ToOcamlRep for usize {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int((*self).try_into().unwrap())
    }
}

impl FromOcamlRep for usize {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(from::expect_int(value)?.try_into()?)
    }
}

trivial_from_in_impl!(usize);

impl ToOcamlRep for i64 {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int((*self).try_into().unwrap())
    }
}

impl FromOcamlRep for i64 {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(from::expect_int(value)?.try_into()?)
    }
}

trivial_from_in_impl!(i64);

impl ToOcamlRep for u64 {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int((*self).try_into().unwrap())
    }
}

impl FromOcamlRep for u64 {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(from::expect_int(value)?.try_into()?)
    }
}

trivial_from_in_impl!(u64);

impl ToOcamlRep for i32 {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int((*self).try_into().unwrap())
    }
}

impl FromOcamlRep for i32 {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(from::expect_int(value)?.try_into()?)
    }
}

trivial_from_in_impl!(i32);

impl ToOcamlRep for u32 {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int((*self).try_into().unwrap())
    }
}

impl FromOcamlRep for u32 {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(from::expect_int(value)?.try_into()?)
    }
}

trivial_from_in_impl!(u32);

impl ToOcamlRep for bool {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        Value::int((*self).into())
    }
}

impl FromOcamlRep for bool {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        match from::expect_int(value)? {
            0 => Ok(false),
            1 => Ok(true),
            x => Err(FromError::ExpectedBool(x)),
        }
    }
}

trivial_from_in_impl!(bool);

impl ToOcamlRep for char {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, _alloc: &'a A) -> Value<'a> {
        assert!(*self as u32 <= 255, "char out of range: {}", self);
        Value::int(*self as isize)
    }
}

impl FromOcamlRep for char {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let c = from::expect_int(value)?;
        if (0..=255).contains(&c) {
            Ok(c as u8 as char)
        } else {
            Err(FromError::ExpectedChar(c))
        }
    }
}

trivial_from_in_impl!(char);

impl ToOcamlRep for f64 {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size_and_tag(1, block::DOUBLE_TAG);
        alloc.set_field(&mut block, 0, unsafe {
            Value::from_bits(self.to_bits() as usize)
        });
        block.build()
    }
}

impl FromOcamlRep for f64 {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_block_with_size_and_tag(value, 1, block::DOUBLE_TAG)?;
        Ok(f64::from_bits(block[0].0 as u64))
    }
}

trivial_from_in_impl!(f64);

impl<T: ToOcamlRep + Sized> ToOcamlRep for Box<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(&**self)
    }
}

impl<T: FromOcamlRep + Sized> FromOcamlRep for Box<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(Box::new(T::from_ocamlrep(value)?))
    }
}

impl<T: ToOcamlRep + Sized> ToOcamlRep for &'_ T {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.memoized(
            *self as *const T as *const usize as usize,
            size_of::<T>(),
            |alloc| (**self).to_ocamlrep(alloc),
        )
    }
}

impl<'a, T: FromOcamlRepIn<'a>> FromOcamlRepIn<'a> for &'a T {
    fn from_ocamlrep_in(value: Value<'_>, alloc: &'a Bump) -> Result<Self, FromError> {
        // NB: We don't get any sharing this way.
        Ok(alloc.alloc(T::from_ocamlrep_in(value, alloc)?))
    }
}

impl<T: ToOcamlRep + Sized> ToOcamlRep for Rc<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.memoized(
            self.as_ref() as *const T as usize,
            size_of::<T>(),
            |alloc| alloc.add(self.as_ref()),
        )
    }
}

impl<T: FromOcamlRep> FromOcamlRep for Rc<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        // NB: We don't get any sharing this way.
        Ok(Rc::new(T::from_ocamlrep(value)?))
    }
}

impl<T: ToOcamlRep + Sized> ToOcamlRep for Arc<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.memoized(
            self.as_ref() as *const T as usize,
            size_of::<T>(),
            |alloc| alloc.add(self.as_ref()),
        )
    }
}

impl<T: FromOcamlRep> FromOcamlRep for Arc<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        // NB: We don't get any sharing this way.
        Ok(Arc::new(T::from_ocamlrep(value)?))
    }
}

impl<T: ToOcamlRep> ToOcamlRep for RefCell<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(1);
        let value_ref: std::cell::Ref<'a, T> = self.borrow();
        alloc.set_field(&mut block, 0, alloc.add(&*value_ref));
        // SAFETY: the `&'a self` lifetime is intended to ensure that our `T` is
        // not mutated or dropped during the to-OCaml conversion, in order to
        // ensure that the allocator's memoization table isn't invalidated. We
        // can't guarantee that statically for types with internal mutability,
        // so the `ToOcamlRep` docs ask the caller to promise not to mutate or
        // drop these values. If they violate that requirement, the allocator
        // may give stale results in the event of aliasing, which is definitely
        // undesirable, but does not break type safety on the Rust side. The
        // allocator ties the lifetime of the Value we're returning to our
        // local variable `value_ref`, but it doesn't actually reference that
        // local, so it's safe to cast the lifetime away.
        unsafe { std::mem::transmute::<Value<'_>, Value<'a>>(block.build()) }
    }
}

impl<T: Copy + ToOcamlRep> ToOcamlRep for Cell<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(1);
        let value_copy = self.get();
        alloc.set_field(&mut block, 0, alloc.add(&value_copy));
        // SAFETY: as above with RefCell, we need to cast away the lifetime to
        // deal with internal mutability.
        unsafe { std::mem::transmute::<Value<'_>, Value<'a>>(block.build()) }
    }
}

impl<T: FromOcamlRep> FromOcamlRep for Cell<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 1)?;
        let value: T = from::field(block, 0)?;
        Ok(Cell::new(value))
    }
}

impl<'a, T: FromOcamlRepIn<'a>> FromOcamlRepIn<'a> for Cell<T> {
    fn from_ocamlrep_in(value: Value<'_>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 1)?;
        let value: T = from::field_in(block, 0, alloc)?;
        Ok(Cell::new(value))
    }
}

impl<T: FromOcamlRep> FromOcamlRep for RefCell<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 1)?;
        let value: T = from::field(block, 0)?;
        Ok(RefCell::new(value))
    }
}

impl<'a, T: FromOcamlRepIn<'a>> FromOcamlRepIn<'a> for RefCell<T> {
    fn from_ocamlrep_in(value: Value<'_>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 1)?;
        let value: T = from::field_in(block, 0, alloc)?;
        Ok(RefCell::new(value))
    }
}

impl<T: ToOcamlRep> ToOcamlRep for Option<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        match self {
            None => Value::int(0),
            Some(val) => {
                let mut block = alloc.block_with_size(1);
                alloc.set_field(&mut block, 0, alloc.add(val));
                block.build()
            }
        }
    }
}

impl<T: FromOcamlRep> FromOcamlRep for Option<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        if value.is_int() {
            let _ = from::expect_nullary_variant(value, 0)?;
            Ok(None)
        } else {
            let block = from::expect_block_with_size_and_tag(value, 1, 0)?;
            Ok(Some(from::field(block, 0)?))
        }
    }
}

impl<'a, T: FromOcamlRepIn<'a>> FromOcamlRepIn<'a> for Option<T> {
    fn from_ocamlrep_in(value: Value<'_>, alloc: &'a Bump) -> Result<Self, FromError> {
        if value.is_int() {
            let _ = from::expect_nullary_variant(value, 0)?;
            Ok(None)
        } else {
            let block = from::expect_block_with_size_and_tag(value, 1, 0)?;
            Ok(Some(from::field_in(block, 0, alloc)?))
        }
    }
}

impl<T: ToOcamlRep, E: ToOcamlRep> ToOcamlRep for Result<T, E> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        match self {
            Ok(val) => {
                let mut block = alloc.block_with_size(1);
                alloc.set_field(&mut block, 0, alloc.add(val));
                block.build()
            }
            Err(val) => {
                let mut block = alloc.block_with_size_and_tag(1, 1);
                alloc.set_field(&mut block, 0, alloc.add(val));
                block.build()
            }
        }
    }
}

impl<T: FromOcamlRep, E: FromOcamlRep> FromOcamlRep for Result<T, E> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_block(value)?;
        match block.tag() {
            0 => Ok(Ok(from::field(block, 0)?)),
            1 => Ok(Err(from::field(block, 0)?)),
            t => Err(FromError::BlockTagOutOfRange { max: 1, actual: t }),
        }
    }
}

impl<'a, T: FromOcamlRepIn<'a>, E: FromOcamlRepIn<'a>> FromOcamlRepIn<'a> for Result<T, E> {
    fn from_ocamlrep_in(value: Value<'_>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_block(value)?;
        match block.tag() {
            0 => Ok(Ok(from::field_in(block, 0, alloc)?)),
            1 => Ok(Err(from::field_in(block, 0, alloc)?)),
            t => Err(FromError::BlockTagOutOfRange { max: 1, actual: t }),
        }
    }
}

impl<T: ToOcamlRep> ToOcamlRep for [T] {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut hd = alloc.add(&());
        for val in self.iter().rev() {
            let mut block = alloc.block_with_size(2);
            alloc.set_field(&mut block, 0, alloc.add(val));
            alloc.set_field(&mut block, 1, hd);
            hd = block.build();
        }
        hd
    }
}

impl<T: ToOcamlRep> ToOcamlRep for &'_ [T] {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.memoized(
            self.as_ptr() as usize,
            self.len() * size_of::<T>(),
            |alloc| (**self).to_ocamlrep(alloc),
        )
    }
}

impl<'a, T: FromOcamlRepIn<'a>> FromOcamlRepIn<'a> for &'a [T] {
    fn from_ocamlrep_in(value: Value<'_>, alloc: &'a Bump) -> Result<Self, FromError> {
        let mut len = 0usize;
        let mut hd = value;
        while !hd.is_int() {
            let block = from::expect_tuple(hd, 2)?;
            len += 1;
            hd = block[1];
        }
        let hd = hd.as_int().unwrap();
        if hd != 0 {
            return Err(FromError::ExpectedUnit(hd));
        }

        let mut vec = bumpalo::collections::Vec::with_capacity_in(len, alloc);
        let mut hd = value;
        while !hd.is_int() {
            let block = from::expect_tuple(hd, 2).unwrap();
            vec.push(from::field_in(block, 0, alloc)?);
            hd = block[1];
        }
        Ok(vec.into_bump_slice())
    }
}

impl<T: ToOcamlRep> ToOcamlRep for Box<[T]> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        (**self).to_ocamlrep(alloc)
    }
}

impl<T: FromOcamlRep> FromOcamlRep for Box<[T]> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let vec = <Vec<T>>::from_ocamlrep(value)?;
        Ok(vec.into_boxed_slice())
    }
}

impl<T: ToOcamlRep> ToOcamlRep for Vec<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_slice())
    }
}

impl<T: FromOcamlRep> FromOcamlRep for Vec<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let mut vec = vec![];
        let mut hd = value;
        while !hd.is_int() {
            let block = from::expect_tuple(hd, 2)?;
            vec.push(from::field(block, 0)?);
            hd = block[1];
        }
        let hd = hd.as_int().unwrap();
        if hd != 0 {
            return Err(FromError::ExpectedUnit(hd));
        }
        Ok(vec)
    }
}

impl<'a, T: FromOcamlRep> FromOcamlRepIn<'a> for Vec<T> {
    fn from_ocamlrep_in(value: Value<'_>, _alloc: &'a Bump) -> Result<Self, FromError> {
        Self::from_ocamlrep(value)
    }
}

impl<K: ToOcamlRep + Ord, V: ToOcamlRep> ToOcamlRep for BTreeMap<K, V> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        if self.is_empty() {
            return Value::int(0);
        }
        let len = self.len();
        let mut iter = self
            .iter()
            .map(|(k, v)| (k.to_ocamlrep(alloc), v.to_ocamlrep(alloc)));
        let (res, _) = sorted_iter_to_ocaml_map(&mut iter, alloc, len);
        res
    }
}

impl<K: FromOcamlRep + Ord, V: FromOcamlRep> FromOcamlRep for BTreeMap<K, V> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let mut map = BTreeMap::new();
        btree_map_from_ocamlrep(&mut map, value)?;
        Ok(map)
    }
}

impl<'a, K: FromOcamlRep + Ord, V: FromOcamlRep> FromOcamlRepIn<'a> for BTreeMap<K, V> {
    fn from_ocamlrep_in(value: Value<'_>, _alloc: &'a Bump) -> Result<Self, FromError> {
        Self::from_ocamlrep(value)
    }
}

/// Given an iterator which emits key-value pairs (already converted to OCaml
/// values), build an OCaml Map containing those bindings. The iterator must
/// emit each key only once. The key-value pairs must be emitted in ascending
/// order, sorted by key. The iterator must emit exactly `size` pairs.
pub fn sorted_iter_to_ocaml_map<'a, A: Allocator>(
    iter: &mut impl Iterator<Item = (Value<'a>, Value<'a>)>,
    alloc: &'a A,
    size: usize,
) -> (Value<'a>, usize) {
    if size == 0 {
        return (Value::int(0), 0);
    }
    let (left, left_height) = sorted_iter_to_ocaml_map(iter, alloc, size / 2);
    let (key, val) = iter.next().unwrap();
    let (right, right_height) = sorted_iter_to_ocaml_map(iter, alloc, size - 1 - size / 2);
    let height = std::cmp::max(left_height, right_height) + 1;
    let mut block = alloc.block_with_size(5);
    alloc.set_field(&mut block, 0, left);
    alloc.set_field(&mut block, 1, key);
    alloc.set_field(&mut block, 2, val);
    alloc.set_field(&mut block, 3, right);
    alloc.set_field(&mut block, 4, alloc.add_copy(height));
    (block.build(), height)
}

fn btree_map_from_ocamlrep<K: FromOcamlRep + Ord, V: FromOcamlRep>(
    map: &mut BTreeMap<K, V>,
    value: Value<'_>,
) -> Result<(), FromError> {
    if value.is_int() {
        let _ = from::expect_nullary_variant(value, 0)?;
        return Ok(());
    }
    let block = from::expect_block_with_size_and_tag(value, 5, 0)?;
    btree_map_from_ocamlrep(map, block[0])?;
    let key: K = from::field(block, 1)?;
    let val: V = from::field(block, 2)?;
    map.insert(key, val);
    btree_map_from_ocamlrep(map, block[3])?;
    Ok(())
}

fn vec_from_ocaml_map_impl<K: FromOcamlRep, V: FromOcamlRep>(
    vec: &mut Vec<(K, V)>,
    value: Value<'_>,
) -> Result<(), FromError> {
    if value.is_int() {
        let _ = from::expect_nullary_variant(value, 0)?;
        return Ok(());
    }
    let block = from::expect_block_with_size_and_tag(value, 5, 0)?;
    vec_from_ocaml_map_impl(vec, block[0])?;
    let key: K = from::field(block, 1)?;
    let val: V = from::field(block, 2)?;
    vec.push((key, val));
    vec_from_ocaml_map_impl(vec, block[3])?;
    Ok(())
}

pub fn vec_from_ocaml_map<K: FromOcamlRep, V: FromOcamlRep>(
    value: Value<'_>,
) -> Result<Vec<(K, V)>, FromError> {
    let mut vec = vec![];
    vec_from_ocaml_map_impl(&mut vec, value)?;
    Ok(vec)
}

pub fn vec_from_ocaml_map_in<'a, K, V>(
    value: Value<'_>,
    vec: &mut bumpalo::collections::Vec<'a, (K, V)>,
    alloc: &'a Bump,
) -> Result<(), FromError>
where
    K: FromOcamlRepIn<'a> + Ord,
    V: FromOcamlRepIn<'a>,
{
    if value.is_int() {
        let _ = from::expect_nullary_variant(value, 0)?;
        return Ok(());
    }
    let block = from::expect_block_with_size_and_tag(value, 5, 0)?;
    vec_from_ocaml_map_in(block[0], vec, alloc)?;
    let key: K = from::field_in(block, 1, alloc)?;
    let val: V = from::field_in(block, 2, alloc)?;
    vec.push((key, val));
    vec_from_ocaml_map_in(block[3], vec, alloc)?;
    Ok(())
}

impl<T: ToOcamlRep + Ord> ToOcamlRep for BTreeSet<T> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        if self.is_empty() {
            return Value::int(0);
        }
        let len = self.len();
        let mut iter = self.iter().map(|x| x.to_ocamlrep(alloc));
        let (res, _) = sorted_iter_to_ocaml_set(&mut iter, alloc, len);
        res
    }
}

impl<T: FromOcamlRep + Ord> FromOcamlRep for BTreeSet<T> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let mut set = BTreeSet::new();
        btree_set_from_ocamlrep(&mut set, value)?;
        Ok(set)
    }
}

impl<'a, T: FromOcamlRep + Ord> FromOcamlRepIn<'a> for BTreeSet<T> {
    fn from_ocamlrep_in(value: Value<'_>, _alloc: &'a Bump) -> Result<Self, FromError> {
        Self::from_ocamlrep(value)
    }
}

/// Build an OCaml Set containing all items emitted by the given iterator. The
/// iterator must emit each item only once. The items must be emitted in
/// ascending order. The iterator must emit exactly `size` items.
pub fn sorted_iter_to_ocaml_set<'a, A: Allocator>(
    iter: &mut impl Iterator<Item = Value<'a>>,
    alloc: &'a A,
    size: usize,
) -> (Value<'a>, usize) {
    if size == 0 {
        return (Value::int(0), 0);
    }
    let (left, left_height) = sorted_iter_to_ocaml_set(iter, alloc, size / 2);
    let val = iter.next().unwrap();
    let (right, right_height) = sorted_iter_to_ocaml_set(iter, alloc, size - 1 - size / 2);
    let height = std::cmp::max(left_height, right_height) + 1;
    let mut block = alloc.block_with_size(4);
    alloc.set_field(&mut block, 0, left);
    alloc.set_field(&mut block, 1, val);
    alloc.set_field(&mut block, 2, right);
    alloc.set_field(&mut block, 3, alloc.add_copy(height));
    (block.build(), height)
}

fn btree_set_from_ocamlrep<T: FromOcamlRep + Ord>(
    set: &mut BTreeSet<T>,
    value: Value<'_>,
) -> Result<(), FromError> {
    if value.is_int() {
        let _ = from::expect_nullary_variant(value, 0)?;
        return Ok(());
    }
    let block = from::expect_block_with_size_and_tag(value, 4, 0)?;
    btree_set_from_ocamlrep(set, block[0])?;
    set.insert(from::field(block, 1)?);
    btree_set_from_ocamlrep(set, block[2])?;
    Ok(())
}

fn vec_from_ocaml_set_impl<T: FromOcamlRep>(
    value: Value<'_>,
    vec: &mut Vec<T>,
) -> Result<(), FromError> {
    if value.is_int() {
        let _ = from::expect_nullary_variant(value, 0)?;
        return Ok(());
    }
    let block = from::expect_block_with_size_and_tag(value, 4, 0)?;
    vec_from_ocaml_set_impl(block[0], vec)?;
    vec.push(from::field(block, 1)?);
    vec_from_ocaml_set_impl(block[2], vec)?;
    Ok(())
}

pub fn vec_from_ocaml_set<T: FromOcamlRep>(value: Value<'_>) -> Result<Vec<T>, FromError> {
    let mut vec = vec![];
    vec_from_ocaml_set_impl(value, &mut vec)?;
    Ok(vec)
}

pub fn vec_from_ocaml_set_in<'a, T: FromOcamlRepIn<'a> + Ord>(
    value: Value<'_>,
    vec: &mut bumpalo::collections::Vec<'a, T>,
    alloc: &'a Bump,
) -> Result<(), FromError> {
    if value.is_int() {
        let _ = from::expect_nullary_variant(value, 0)?;
        return Ok(());
    }
    let block = from::expect_block_with_size_and_tag(value, 4, 0)?;
    vec_from_ocaml_set_in(block[0], vec, alloc)?;
    vec.push(from::field_in(block, 1, alloc)?);
    vec_from_ocaml_set_in(block[2], vec, alloc)?;
    Ok(())
}

impl<K: ToOcamlRep + Ord, V: ToOcamlRep, S: BuildHasher + Default> ToOcamlRep
    for IndexMap<K, V, S>
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        if self.is_empty() {
            return Value::int(0);
        }
        let mut vec: Vec<(&'a K, &'a V)> = self.iter().collect();
        vec.sort_unstable_by_key(|&(k, _)| k);
        let len = vec.len();
        let mut iter = vec.iter().map(|(k, v)| {
            let k: &'a K = k;
            let v: &'a V = v;
            (k.to_ocamlrep(alloc), v.to_ocamlrep(alloc))
        });
        let (res, _) = sorted_iter_to_ocaml_map(&mut iter, alloc, len);
        res
    }
}

impl<K: FromOcamlRep + Ord + Hash, V: FromOcamlRep, S: BuildHasher + Default> FromOcamlRep
    for IndexMap<K, V, S>
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let vec = vec_from_ocaml_map(value)?;
        Ok(vec.into_iter().collect())
    }
}

impl<T: ToOcamlRep + Ord, S: BuildHasher + Default> ToOcamlRep for IndexSet<T, S> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        if self.is_empty() {
            return Value::int(0);
        }
        let mut vec: Vec<&'a T> = self.iter().collect();
        vec.sort_unstable();
        let len = vec.len();
        let mut iter = vec.iter().copied().map(|x| x.to_ocamlrep(alloc));
        let (res, _) = sorted_iter_to_ocaml_set(&mut iter, alloc, len);
        res
    }
}

impl<T: FromOcamlRep + Ord + Hash, S: BuildHasher + Default> FromOcamlRep for IndexSet<T, S> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let set = <BTreeSet<T>>::from_ocamlrep(value)?;
        Ok(set.into_iter().collect())
    }
}

impl ToOcamlRep for OsStr {
    // TODO: A Windows implementation would be nice, but what does the OCaml
    // runtime do? If we need Windows support, we'll have to find out.
    #[cfg(unix)]
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        use std::os::unix::ffi::OsStrExt;
        alloc.add(self.as_bytes())
    }

    #[cfg(target_arch = "wasm32")]
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        panic!()
    }
}

impl ToOcamlRep for &'_ OsStr {
    #[cfg(unix)]
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        use std::os::unix::ffi::OsStrExt;
        alloc.add(self.as_bytes())
    }

    #[cfg(target_arch = "wasm32")]
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        panic!()
    }
}

impl<'a> FromOcamlRepIn<'a> for &'a OsStr {
    #[cfg(unix)]
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        use std::os::unix::ffi::OsStrExt;
        Ok(std::ffi::OsStr::from_bytes(<&'a [u8]>::from_ocamlrep_in(
            value, alloc,
        )?))
    }

    #[cfg(target_arch = "wasm32")]
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        panic!()
    }
}

impl ToOcamlRep for OsString {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_os_str())
    }
}

impl FromOcamlRep for OsString {
    #[cfg(unix)]
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        use std::os::unix::ffi::OsStrExt;
        Ok(OsString::from(std::ffi::OsStr::from_bytes(
            bytes_from_ocamlrep(value)?,
        )))
    }

    #[cfg(target_arch = "wasm32")]
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        panic!()
    }
}

impl ToOcamlRep for Path {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_os_str())
    }
}

impl ToOcamlRep for &'_ Path {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_os_str())
    }
}

impl<'a> FromOcamlRepIn<'a> for &'a Path {
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        Ok(Path::new(<&'a OsStr>::from_ocamlrep_in(value, alloc)?))
    }
}

impl ToOcamlRep for PathBuf {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_os_str())
    }
}

impl FromOcamlRep for PathBuf {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(PathBuf::from(OsString::from_ocamlrep(value)?))
    }
}

impl ToOcamlRep for String {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_str())
    }
}

impl FromOcamlRep for String {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(String::from(str_from_ocamlrep(value)?))
    }
}

trivial_from_in_impl!(String);

impl ToOcamlRep for Cow<'_, str> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let s: &str = self.borrow();
        alloc.add(s)
    }
}

impl FromOcamlRep for Cow<'_, str> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(Cow::Owned(String::from(str_from_ocamlrep(value)?)))
    }
}

impl ToOcamlRep for str {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        str_to_ocamlrep(self, alloc)
    }
}

impl ToOcamlRep for &'_ str {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.memoized(self.as_bytes().as_ptr() as usize, self.len(), |alloc| {
            (**self).to_ocamlrep(alloc)
        })
    }
}

impl<'a> FromOcamlRepIn<'a> for &'a str {
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        Ok(alloc.alloc_str(str_from_ocamlrep(value)?))
    }
}

/// Allocate an OCaml string using the given allocator and copy the given string
/// slice into it.
pub fn str_to_ocamlrep<'a, A: Allocator>(s: &str, alloc: &'a A) -> Value<'a> {
    bytes_to_ocamlrep(s.as_bytes(), alloc)
}

/// Given an OCaml string, return a string slice pointing to its contents, if
/// they are valid UTF-8.
pub fn str_from_ocamlrep<'a>(value: Value<'a>) -> Result<&'a str, FromError> {
    Ok(std::str::from_utf8(bytes_from_ocamlrep(value)?)?)
}

impl ToOcamlRep for Vec<u8> {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_slice())
    }
}

impl FromOcamlRep for Vec<u8> {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(Vec::from(bytes_from_ocamlrep(value)?))
    }
}

impl ToOcamlRep for BString {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.add(self.as_slice())
    }
}

impl FromOcamlRep for BString {
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        Ok(Vec::from_ocamlrep(value)?.into())
    }
}

impl ToOcamlRep for BStr {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        bytes_to_ocamlrep(self, alloc)
    }
}

impl ToOcamlRep for &'_ BStr {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.memoized(self.as_ptr() as usize, self.len(), |alloc| {
            (**self).to_ocamlrep(alloc)
        })
    }
}

impl<'a> FromOcamlRepIn<'a> for &'a BStr {
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let slice: &[u8] = alloc.alloc_slice_copy(bytes_from_ocamlrep(value)?);
        Ok(slice.into())
    }
}

impl ToOcamlRep for [u8] {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        bytes_to_ocamlrep(self, alloc)
    }
}

impl ToOcamlRep for &'_ [u8] {
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        alloc.memoized(self.as_ptr() as usize, self.len(), |alloc| {
            (**self).to_ocamlrep(alloc)
        })
    }
}

impl<'a> FromOcamlRepIn<'a> for &'a [u8] {
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        Ok(alloc.alloc_slice_copy(bytes_from_ocamlrep(value)?))
    }
}

/// Allocate an OCaml string using the given allocator and copy the given byte
/// slice into it.
pub fn bytes_to_ocamlrep<'a, A: Allocator>(bytes: &[u8], alloc: &'a A) -> Value<'a> {
    use std::io::Write;
    let mut w = alloc.byte_string_with_len(bytes.len());
    let _ = w.write(bytes).unwrap();
    w.build()
}

/// Given an OCaml string, return a byte slice pointing to its contents.
pub fn bytes_from_ocamlrep<'a>(value: Value<'a>) -> Result<&'a [u8], FromError> {
    let block = from::expect_block(value)?;
    from::expect_block_tag(block, block::STRING_TAG)?;
    let block_size_in_bytes = block.size() * std::mem::size_of::<Value<'_>>();
    let slice = unsafe {
        let ptr = block.0.as_ptr().add(1) as *const u8;
        let padding = *ptr.add(block_size_in_bytes - 1);
        let len = block_size_in_bytes - padding as usize - 1;
        std::slice::from_raw_parts(ptr, len)
    };
    Ok(slice)
}

impl<T0, T1> ToOcamlRep for (T0, T1)
where
    T0: ToOcamlRep,
    T1: ToOcamlRep,
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(2);
        alloc.set_field(&mut block, 0, alloc.add(&self.0));
        alloc.set_field(&mut block, 1, alloc.add(&self.1));
        block.build()
    }
}

impl<T0, T1> FromOcamlRep for (T0, T1)
where
    T0: FromOcamlRep,
    T1: FromOcamlRep,
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 2)?;
        let f0: T0 = from::field(block, 0)?;
        let f1: T1 = from::field(block, 1)?;
        Ok((f0, f1))
    }
}

impl<'a, T0, T1> FromOcamlRepIn<'a> for (T0, T1)
where
    T0: FromOcamlRepIn<'a>,
    T1: FromOcamlRepIn<'a>,
{
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 2)?;
        let f0: T0 = from::field_in(block, 0, alloc)?;
        let f1: T1 = from::field_in(block, 1, alloc)?;
        Ok((f0, f1))
    }
}

impl<T0, T1, T2> ToOcamlRep for (T0, T1, T2)
where
    T0: ToOcamlRep,
    T1: ToOcamlRep,
    T2: ToOcamlRep,
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(3);
        alloc.set_field(&mut block, 0, alloc.add(&self.0));
        alloc.set_field(&mut block, 1, alloc.add(&self.1));
        alloc.set_field(&mut block, 2, alloc.add(&self.2));
        block.build()
    }
}

impl<T0, T1, T2> FromOcamlRep for (T0, T1, T2)
where
    T0: FromOcamlRep,
    T1: FromOcamlRep,
    T2: FromOcamlRep,
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 3)?;
        let f0: T0 = from::field(block, 0)?;
        let f1: T1 = from::field(block, 1)?;
        let f2: T2 = from::field(block, 2)?;
        Ok((f0, f1, f2))
    }
}

impl<'a, T0, T1, T2> FromOcamlRepIn<'a> for (T0, T1, T2)
where
    T0: FromOcamlRepIn<'a>,
    T1: FromOcamlRepIn<'a>,
    T2: FromOcamlRepIn<'a>,
{
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 3)?;
        let f0: T0 = from::field_in(block, 0, alloc)?;
        let f1: T1 = from::field_in(block, 1, alloc)?;
        let f2: T2 = from::field_in(block, 2, alloc)?;
        Ok((f0, f1, f2))
    }
}

impl<T0, T1, T2, T3> ToOcamlRep for (T0, T1, T2, T3)
where
    T0: ToOcamlRep,
    T1: ToOcamlRep,
    T2: ToOcamlRep,
    T3: ToOcamlRep,
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(4);
        alloc.set_field(&mut block, 0, alloc.add(&self.0));
        alloc.set_field(&mut block, 1, alloc.add(&self.1));
        alloc.set_field(&mut block, 2, alloc.add(&self.2));
        alloc.set_field(&mut block, 3, alloc.add(&self.3));
        block.build()
    }
}

impl<T0, T1, T2, T3> FromOcamlRep for (T0, T1, T2, T3)
where
    T0: FromOcamlRep,
    T1: FromOcamlRep,
    T2: FromOcamlRep,
    T3: FromOcamlRep,
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 4)?;
        let f0: T0 = from::field(block, 0)?;
        let f1: T1 = from::field(block, 1)?;
        let f2: T2 = from::field(block, 2)?;
        let f3: T3 = from::field(block, 3)?;
        Ok((f0, f1, f2, f3))
    }
}

impl<'a, T0, T1, T2, T3> FromOcamlRepIn<'a> for (T0, T1, T2, T3)
where
    T0: FromOcamlRepIn<'a>,
    T1: FromOcamlRepIn<'a>,
    T2: FromOcamlRepIn<'a>,
    T3: FromOcamlRepIn<'a>,
{
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 4)?;
        let f0: T0 = from::field_in(block, 0, alloc)?;
        let f1: T1 = from::field_in(block, 1, alloc)?;
        let f2: T2 = from::field_in(block, 2, alloc)?;
        let f3: T3 = from::field_in(block, 3, alloc)?;
        Ok((f0, f1, f2, f3))
    }
}

impl<T0, T1, T2, T3, T4> ToOcamlRep for (T0, T1, T2, T3, T4)
where
    T0: ToOcamlRep,
    T1: ToOcamlRep,
    T2: ToOcamlRep,
    T3: ToOcamlRep,
    T4: ToOcamlRep,
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(5);
        alloc.set_field(&mut block, 0, alloc.add(&self.0));
        alloc.set_field(&mut block, 1, alloc.add(&self.1));
        alloc.set_field(&mut block, 2, alloc.add(&self.2));
        alloc.set_field(&mut block, 3, alloc.add(&self.3));
        alloc.set_field(&mut block, 4, alloc.add(&self.4));
        block.build()
    }
}

impl<T0, T1, T2, T3, T4> FromOcamlRep for (T0, T1, T2, T3, T4)
where
    T0: FromOcamlRep,
    T1: FromOcamlRep,
    T2: FromOcamlRep,
    T3: FromOcamlRep,
    T4: FromOcamlRep,
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 5)?;
        let f0: T0 = from::field(block, 0)?;
        let f1: T1 = from::field(block, 1)?;
        let f2: T2 = from::field(block, 2)?;
        let f3: T3 = from::field(block, 3)?;
        let f4: T4 = from::field(block, 4)?;
        Ok((f0, f1, f2, f3, f4))
    }
}

impl<'a, T0, T1, T2, T3, T4> FromOcamlRepIn<'a> for (T0, T1, T2, T3, T4)
where
    T0: FromOcamlRepIn<'a>,
    T1: FromOcamlRepIn<'a>,
    T2: FromOcamlRepIn<'a>,
    T3: FromOcamlRepIn<'a>,
    T4: FromOcamlRepIn<'a>,
{
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 5)?;
        let f0: T0 = from::field_in(block, 0, alloc)?;
        let f1: T1 = from::field_in(block, 1, alloc)?;
        let f2: T2 = from::field_in(block, 2, alloc)?;
        let f3: T3 = from::field_in(block, 3, alloc)?;
        let f4: T4 = from::field_in(block, 4, alloc)?;
        Ok((f0, f1, f2, f3, f4))
    }
}

impl<T0, T1, T2, T3, T4, T5> ToOcamlRep for (T0, T1, T2, T3, T4, T5)
where
    T0: ToOcamlRep,
    T1: ToOcamlRep,
    T2: ToOcamlRep,
    T3: ToOcamlRep,
    T4: ToOcamlRep,
    T5: ToOcamlRep,
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(6);
        alloc.set_field(&mut block, 0, alloc.add(&self.0));
        alloc.set_field(&mut block, 1, alloc.add(&self.1));
        alloc.set_field(&mut block, 2, alloc.add(&self.2));
        alloc.set_field(&mut block, 3, alloc.add(&self.3));
        alloc.set_field(&mut block, 4, alloc.add(&self.4));
        alloc.set_field(&mut block, 5, alloc.add(&self.5));
        block.build()
    }
}

impl<T0, T1, T2, T3, T4, T5> FromOcamlRep for (T0, T1, T2, T3, T4, T5)
where
    T0: FromOcamlRep,
    T1: FromOcamlRep,
    T2: FromOcamlRep,
    T3: FromOcamlRep,
    T4: FromOcamlRep,
    T5: FromOcamlRep,
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 6)?;
        let f0: T0 = from::field(block, 0)?;
        let f1: T1 = from::field(block, 1)?;
        let f2: T2 = from::field(block, 2)?;
        let f3: T3 = from::field(block, 3)?;
        let f4: T4 = from::field(block, 4)?;
        let f5: T5 = from::field(block, 5)?;
        Ok((f0, f1, f2, f3, f4, f5))
    }
}

impl<'a, T0, T1, T2, T3, T4, T5> FromOcamlRepIn<'a> for (T0, T1, T2, T3, T4, T5)
where
    T0: FromOcamlRepIn<'a>,
    T1: FromOcamlRepIn<'a>,
    T2: FromOcamlRepIn<'a>,
    T3: FromOcamlRepIn<'a>,
    T4: FromOcamlRepIn<'a>,
    T5: FromOcamlRepIn<'a>,
{
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 6)?;
        let f0: T0 = from::field_in(block, 0, alloc)?;
        let f1: T1 = from::field_in(block, 1, alloc)?;
        let f2: T2 = from::field_in(block, 2, alloc)?;
        let f3: T3 = from::field_in(block, 3, alloc)?;
        let f4: T4 = from::field_in(block, 4, alloc)?;
        let f5: T5 = from::field_in(block, 5, alloc)?;
        Ok((f0, f1, f2, f3, f4, f5))
    }
}

impl<T0, T1, T2, T3, T4, T5, T6> ToOcamlRep for (T0, T1, T2, T3, T4, T5, T6)
where
    T0: ToOcamlRep,
    T1: ToOcamlRep,
    T2: ToOcamlRep,
    T3: ToOcamlRep,
    T4: ToOcamlRep,
    T5: ToOcamlRep,
    T6: ToOcamlRep,
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(7);
        alloc.set_field(&mut block, 0, alloc.add(&self.0));
        alloc.set_field(&mut block, 1, alloc.add(&self.1));
        alloc.set_field(&mut block, 2, alloc.add(&self.2));
        alloc.set_field(&mut block, 3, alloc.add(&self.3));
        alloc.set_field(&mut block, 4, alloc.add(&self.4));
        alloc.set_field(&mut block, 5, alloc.add(&self.5));
        alloc.set_field(&mut block, 6, alloc.add(&self.6));
        block.build()
    }
}

impl<T0, T1, T2, T3, T4, T5, T6> FromOcamlRep for (T0, T1, T2, T3, T4, T5, T6)
where
    T0: FromOcamlRep,
    T1: FromOcamlRep,
    T2: FromOcamlRep,
    T3: FromOcamlRep,
    T4: FromOcamlRep,
    T5: FromOcamlRep,
    T6: FromOcamlRep,
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 7)?;
        let f0: T0 = from::field(block, 0)?;
        let f1: T1 = from::field(block, 1)?;
        let f2: T2 = from::field(block, 2)?;
        let f3: T3 = from::field(block, 3)?;
        let f4: T4 = from::field(block, 4)?;
        let f5: T5 = from::field(block, 5)?;
        let f6: T6 = from::field(block, 6)?;
        Ok((f0, f1, f2, f3, f4, f5, f6))
    }
}

impl<'a, T0, T1, T2, T3, T4, T5, T6> FromOcamlRepIn<'a> for (T0, T1, T2, T3, T4, T5, T6)
where
    T0: FromOcamlRepIn<'a>,
    T1: FromOcamlRepIn<'a>,
    T2: FromOcamlRepIn<'a>,
    T3: FromOcamlRepIn<'a>,
    T4: FromOcamlRepIn<'a>,
    T5: FromOcamlRepIn<'a>,
    T6: FromOcamlRepIn<'a>,
{
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 7)?;
        let f0: T0 = from::field_in(block, 0, alloc)?;
        let f1: T1 = from::field_in(block, 1, alloc)?;
        let f2: T2 = from::field_in(block, 2, alloc)?;
        let f3: T3 = from::field_in(block, 3, alloc)?;
        let f4: T4 = from::field_in(block, 4, alloc)?;
        let f5: T5 = from::field_in(block, 5, alloc)?;
        let f6: T6 = from::field_in(block, 6, alloc)?;
        Ok((f0, f1, f2, f3, f4, f5, f6))
    }
}

impl<T0, T1, T2, T3, T4, T5, T6, T7> ToOcamlRep for (T0, T1, T2, T3, T4, T5, T6, T7)
where
    T0: ToOcamlRep,
    T1: ToOcamlRep,
    T2: ToOcamlRep,
    T3: ToOcamlRep,
    T4: ToOcamlRep,
    T5: ToOcamlRep,
    T6: ToOcamlRep,
    T7: ToOcamlRep,
{
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a> {
        let mut block = alloc.block_with_size(8);
        alloc.set_field(&mut block, 0, alloc.add(&self.0));
        alloc.set_field(&mut block, 1, alloc.add(&self.1));
        alloc.set_field(&mut block, 2, alloc.add(&self.2));
        alloc.set_field(&mut block, 3, alloc.add(&self.3));
        alloc.set_field(&mut block, 4, alloc.add(&self.4));
        alloc.set_field(&mut block, 5, alloc.add(&self.5));
        alloc.set_field(&mut block, 6, alloc.add(&self.6));
        alloc.set_field(&mut block, 7, alloc.add(&self.7));
        block.build()
    }
}

impl<T0, T1, T2, T3, T4, T5, T6, T7> FromOcamlRep for (T0, T1, T2, T3, T4, T5, T6, T7)
where
    T0: FromOcamlRep,
    T1: FromOcamlRep,
    T2: FromOcamlRep,
    T3: FromOcamlRep,
    T4: FromOcamlRep,
    T5: FromOcamlRep,
    T6: FromOcamlRep,
    T7: FromOcamlRep,
{
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 8)?;
        let f0: T0 = from::field(block, 0)?;
        let f1: T1 = from::field(block, 1)?;
        let f2: T2 = from::field(block, 2)?;
        let f3: T3 = from::field(block, 3)?;
        let f4: T4 = from::field(block, 4)?;
        let f5: T5 = from::field(block, 5)?;
        let f6: T6 = from::field(block, 6)?;
        let f7: T7 = from::field(block, 7)?;
        Ok((f0, f1, f2, f3, f4, f5, f6, f7))
    }
}

impl<'a, T0, T1, T2, T3, T4, T5, T6, T7> FromOcamlRepIn<'a> for (T0, T1, T2, T3, T4, T5, T6, T7)
where
    T0: FromOcamlRepIn<'a>,
    T1: FromOcamlRepIn<'a>,
    T2: FromOcamlRepIn<'a>,
    T3: FromOcamlRepIn<'a>,
    T4: FromOcamlRepIn<'a>,
    T5: FromOcamlRepIn<'a>,
    T6: FromOcamlRepIn<'a>,
    T7: FromOcamlRepIn<'a>,
{
    fn from_ocamlrep_in<'b>(value: Value<'b>, alloc: &'a Bump) -> Result<Self, FromError> {
        let block = from::expect_tuple(value, 8)?;
        let f0: T0 = from::field_in(block, 0, alloc)?;
        let f1: T1 = from::field_in(block, 1, alloc)?;
        let f2: T2 = from::field_in(block, 2, alloc)?;
        let f3: T3 = from::field_in(block, 3, alloc)?;
        let f4: T4 = from::field_in(block, 4, alloc)?;
        let f5: T5 = from::field_in(block, 5, alloc)?;
        let f6: T6 = from::field_in(block, 6, alloc)?;
        let f7: T7 = from::field_in(block, 7, alloc)?;
        Ok((f0, f1, f2, f3, f4, f5, f6, f7))
    }
}


// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

/*!
OcamlRep is a framework for building and interpreting the in-memory
representation of OCaml values. This is useful for converting Rust values to
OCaml values and vice-versa, and for building and storing OCaml values off of
the OCaml runtime's garbage-collected heap.

OcamlRep provides a generic interface abstracting over the allocation of OCaml
values, allowing custom allocators to choose where values are allocated
(including directly onto the OCaml heap).

# Example: build an OCaml value ###############################################

This crate provides an arena-allocator which manages the memory in which
converted OCaml values are stored. When the arena is dropped, the values are
freed.

```rust
// Import the Allocator trait for access to its `add` method, which builds an
// OCaml-value representation of the argument (using `OcamlRep::to_ocamlrep`)
// and returns that value.
use ocamlrep::{Allocator, Arena, Value};

// The `ocamlrep` crate provides implementations of `OcamlRep` for builtin types
// like `Option`, `String`, integers, and tuples. This allows them to be
// converted to OCaml values using the Allocator trait (which Arena implements).
let tuple = (Some(42), String::from("a"));

// Allocate a chunk of Rust-managed backing storage for our OCaml value.
let arena = Arena::new();

// This value borrows the Arena, to ensure that we cannot continue to use it
// after the Arena has been freed. OcamlRep values do not borrow the Rust values
// they were converted from--the string "a" is copied into the Arena.
let ocamlrep_value: Value<'_> = arena.add(&tuple);

// We must now convert the value to a usize which can be handed over to the
// OCaml runtime. We must take care when doing this to ensure that our OCaml
// program doesn't use the value after the Arena is freed.
let ocaml_value: usize = ocamlrep_value.to_bits();
```

# Example: return an OCaml value to the OCaml runtime #########################

The value `ocaml_value` from the previous example is suitable to be handed to
the OCaml runtime. We might do so with an extern declaration like this:

```ocaml
external get_tuple : unit -> int option * string = "get_tuple"

let use_rust_tuple () =
  match get_tuple () with
  | (Some i, s) -> Printf.printf "%d, %s\n" i s
  | (None, s) -> Printf.printf "None, %s\n" s
```

We could provide this symbol from the Rust side like this:

```rust
#[no_mangle]
pub extern "C" fn get_tuple(_unit: usize) -> usize {
    use ocamlrep::{Allocator, Arena};
    let arena = Box::leak(Box::new(Arena::new()));
    let ocaml_tuple = arena.add(&(Some(42), String::from("a")));
    // This is safe because we leaked the Arena--no matter what we do with this
    // value on the OCaml side, we'll never use-after-free.
    ocaml_tuple.to_bits()
}
```

But this leaks memory. For small amounts of memory, or a short-lived process,
this might be fine. If not, we might choose another strategy...

# Example: lend an OCaml value to the OCaml runtime ###########################

Instead, we could register an OCaml callback which uses the value:

```ocaml
let use_tuple (tuple : int option * string) : unit =
  match tuple with
  | (Some i, s) -> Printf.printf "%d, %s\n" i s
  | (None, s) -> Printf.printf "None, %s\n" s

external make_and_use_tuple : unit -> unit = "make_and_use_tuple"

let () =
  Callback.register "use_tuple" use_tuple;
  make_and_use_tuple ()
```

And call into OCaml from Rust (using the `ocaml` crate) to hand over the value:

```rust
#[no_mangle]
pub extern "C" fn make_and_use_tuple(_unit: usize) -> usize {
    use ocamlrep::{Allocator, Arena};
    let arena = Arena::new();
    let tuple_ocamlrep = arena.add(&(Some(42), String::from("a")));

    let use_tuple = ocaml::named_value("use_tuple")
        .expect("use_tuple must be registered using Callback.register");

    // This is safe because we are passing the value to `use_tuple`, which
    // doesn't store the value and returns before we free the Arena.
    let ocaml_tuple: usize = tuple_ocamlrep.to_bits();
    use_tuple
        .call(ocaml::Value::new(ocaml_tuple))
        .expect("use_tuple must be a function");

    // Free the arena and return unit.
    ocaml::core::mlvalues::UNIT
}
```

# Example: pass an OCaml value to the OCaml runtime ###########################

The `ocamlrep_ocamlpool` crate provides an `Allocator` which builds values on
the OCaml runtime's garbage-collected heap. We can replace the memory-leaking
implementation from the [second example ("return an OCaml value to the OCaml
runtime")](#example-return-an-ocaml-value-to-the-ocaml-runtime) with one that
allows the OCaml value to be garbage-collected when no longer used:

```rust
#[no_mangle]
pub extern "C" fn get_tuple(_unit: usize) -> usize {
    ocamlrep_ocamlpool::to_ocaml(&(Some(42), String::from("a")))
}
```

The `ocamlrep_ocamlpool` crate provides a convenience macro for this use case:

```rust
use ocamlrep_ocamlpool::ocaml_ffi;

ocaml_ffi! {
    // This expands to code similar to the above definition of get_tuple.
    fn get_tuple() -> (Option<i32>, String) {
        (Some(42), String::from("a"))
    }
}
```

# Example: pass an OCaml value to Rust ########################################

An `Allocator` converts Rust values which implement the `OcamlRep` trait into
OCaml values using the method `OcamlRep::to_ocamlrep`. The `OcamlRep` trait also
provides a method `OcamlRep::from_ocamlrep` (and an FFI helper named
`OcamlRep::from_ocaml`) for conversion in the other direction.

If we call into Rust like this:

```ocaml
external use_tuple : int option * string -> unit = "use_tuple"

let () = use_tuple (Some 42, "a")
```

We could convert the tuple to a Rust value like this:

```rust
#[no_mangle]
pub extern "C" fn use_tuple(ocaml_tuple: usize) -> usize {
    // Import the OcamlRep trait to use its associated function `from_ocaml`.
    use ocamlrep::OcamlRep;
    // Safety: `ocaml_tuple` is a valid OCaml value allocated by the OCaml
    // runtime, all objects reachable from that value are also valid OCaml
    // values, and those objects cannot be concurrently modified while
    // `from_ocaml` runs. This is true because that graph of objects is owned by
    // the OCaml runtime, we didn't expose any of their pointers in any other
    // FFI functions, and the OCaml runtime is both single-threaded and
    // currently interrupted by an FFI call into this function.
    let tuple_result = unsafe { <(Option<i32>, String)>::from_ocaml(ocaml_tuple) };
    let tuple = tuple_result
        .expect("Expected a value of type `int option * string`, \
                 but got some other type or invalid UTF-8");
    println!("{:?}", tuple);
    ocaml::core::mlvalues::UNIT
}
```

The `ocaml_ffi!` macro in the `ocamlrep_ocamlpool` crate supports this use case:

```rust
use ocamlrep_ocamlpool::ocaml_ffi;

ocaml_ffi! {
    // This expands to code similar to the above definition of use_tuple.
    fn use_tuple(tuple: (Option<i32>, String)) {
        println!("{:?}", tuple)
    }
}
```

Note that the value returned by `from_ocaml` is owned--it is effectively a
deep clone of the OCaml value. For instance, the OCaml string "a" is copied into
a newly allocated Rust String--the Rust side does not need to worry about the
OCaml value being garbage collected.

Take care when using `from_ocaml`, `from_ocamlrep`, or `ocaml_ffi!` with types
containing `String`s. OCaml strings are not guaranteed to be UTF-8, so
`from_ocaml` may return an `Err` because a string was invalid UTF-8 rather than
because the OCaml code did not pass a value of the expected type (which should
be forbidden by the OCaml compiler, provided that the `external` declaration is
annotated with the correct type). If representing invalid UTF-8 is a
requirement, use `Vec<u8>` instead (an implementation of `OcamlRep` which
converts `Vec<u8>` to an OCaml `string` is provided).

# Example: implementing OcamlRep ##############################################

Writing a manual implementation of OcamlRep requires one to choose some type
with which their value should be represented in OCaml and write `to_ocamlrep`
and `from_ocamlrep` conversion functions which build and interpret the in-memory
representation of that type.

This crate provides implementations of OcamlRep for several std types, and
chooses these OCaml types to represent them with:

| Rust type              | OCaml type                              |
|------------------------|-----------------------------------------|
| `()`                   | `unit`                                  |
| `bool`                 | `bool`                                  |
| `usize`/`isize`        | `int`                                   |
| [`Option`][OptionRs]   | [`option`][OptionMl]                    |
| [`Result`][ResultRs]   | [`result`][ResultMl]                    |
| [`Vec`][Vec]           | [`list`][List]                          |
| [`String`][StringRs]   | [`string`][StringMl] (when valid UTF-8) |
| [`Vec<u8>`][Vec]       | [`string`][StringMl]                    |
| [`PathBuf`][PathBuf]   | [`string`][StringMl]                    |
| [`BTreeMap`][BTreeMap] | [`Map`][Map]                            |
| [`BTreeSet`][BTreeSet] | [`Set`][Set]                            |

[OptionRs]: https://doc.rust-lang.org/beta/std/option/enum.Option.html
[OptionMl]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Option.html
[ResultRs]: https://doc.rust-lang.org/std/result/enum.Result.html
[ResultMl]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Result.html
[Vec]: https://doc.rust-lang.org/std/vec/struct.Vec.html
[List]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html
[StringRs]: https://doc.rust-lang.org/std/string/struct.String.html
[StringMl]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/String.html
[PathBuf]: https://doc.rust-lang.org/std/path/struct.PathBuf.html
[BTreeMap]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
[Map]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Map.html
[BTreeSet]: https://doc.rust-lang.org/std/collections/struct.BTreeSet.html
[Set]: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Set.html

See the [`impls`](../src/ocamlrep/impls.rs.html) submodule for examples of
conversions for std types, and [Real World
OCaml](https://dev.realworldocaml.org/runtime-memory-layout.html) and the
[Interfacing C with OCaml](https://ocaml.org/manual/intfc.html) section of the
OCaml manual for description of the OCaml representation of values.

Since manually implementing OcamlRep is cumbersome and error-prone, the
`ocamlrep_derive` crate provides a procedural macro for deriving the OcamlRep
trait.

When derived for custom types like this:

```ocaml
use ocamlrep_derive::OcamlRep;

#[derive(OcamlRep)]
struct Foo {
    a: isize,
    b: Vec<bool>,
}

#[derive(OcamlRep)]
enum Fruit {
    Apple,
    Orange(isize),
    Pear { num: isize },
    Kiwi,
}
```

It produces implementations of OcamlRep which construct values belonging to
these roughly-equivalent OCaml types:

```ocaml
type foo = {
  a: int;
  b: bool list;
}

type fruit =
  | Apple
  | Orange of int
  | Pear of { num: int }
  | Kiwi
```

The `oxidize` program at hphp/hack/src/hh_oxidize will take OCaml source files
like the one above and generate Rust source files which define
"roughly-equivalent" types (like the Rust source above). These types will derive
an implementation of `OcamlRep` which will produce OCaml values belonging to the
corresponding type in the OCaml source file.
*/

mod arena;
mod block;
mod cache;
mod error;
mod impls;
mod value;

pub mod from;
pub mod ptr;
pub mod rc;

pub use arena::Arena;
pub use block::Block;
pub use block::BlockBuilder;
pub use block::BlockBytes;
pub use block::Color;
pub use block::Header;
pub use block::ABSTRACT_TAG;
pub use block::CLOSURE_TAG;
pub use block::CONT_TAG;
pub use block::CUSTOM_TAG;
pub use block::DOUBLE_ARRAY_TAG;
pub use block::DOUBLE_TAG;
pub use block::FORCING_TAG;
pub use block::FORWARD_TAG;
pub use block::INFIX_TAG;
pub use block::LAZY_TAG;
pub use block::NO_SCAN_TAG;
pub use block::OBJECT_TAG;
pub use block::STRING_TAG;
pub use bumpalo::Bump;
pub use cache::MemoizationCache;
pub use error::FromError;
pub use impls::bytes_from_ocamlrep;
pub use impls::bytes_to_ocamlrep;
pub use impls::sorted_iter_to_ocaml_map;
pub use impls::sorted_iter_to_ocaml_set;
pub use impls::str_from_ocamlrep;
pub use impls::str_to_ocamlrep;
pub use impls::vec_from_ocaml_map;
pub use impls::vec_from_ocaml_map_in;
pub use impls::vec_from_ocaml_set;
pub use impls::vec_from_ocaml_set_in;
pub use ocamlrep_derive::FromOcamlRep;
pub use ocamlrep_derive::FromOcamlRepIn;
pub use ocamlrep_derive::ToOcamlRep;
pub use value::Value;

// 'mlvalues.h'
pub const DOUBLE_WOSIZE: usize = std::mem::size_of::<f64>() / std::mem::size_of::<usize>();

// 'gc.h'
pub const CAML_WHITE: usize = 0 << 8;
pub const CAML_GRAY: usize = 1 << 8;
pub const CAML_BLUE: usize = 2 << 8;
pub const CAML_BLACK: usize = 3 << 8;

/// A data structure that can be converted to an OCaml value.
///
/// Types which implement both `ToOcamlRep` and `FromOcamlRep` (or
/// `FromOcamlRepIn`) should provide compatible implementations thereof.
/// In other words, it is expected that for any value with type `T`,
/// `T::from_ocamlrep(value.to_ocamlrep(alloc)) == Ok(value)`.
pub trait ToOcamlRep {
    /// Allocate an OCaml representation of `self` using the given Allocator.
    ///
    /// Implementors of this method must not mutate or drop any values after
    /// passing them to `Allocator::add` (or invoking `to_ocamlrep` on them),
    /// else `Allocator::memoized` may return incorrect results (this can
    /// generally only be done using internal-mutability types like `RefCell`,
    /// `Mutex`, or atomics, or by using `unsafe`).
    fn to_ocamlrep<'a, A: Allocator>(&'a self, alloc: &'a A) -> Value<'a>;
}

/// An interface for allocating OCaml values in some allocator-defined memory region.
pub trait Allocator: Sized {
    /// Return a token which uniquely identifies this Allocator. The token must
    /// be unique (among all instances of all implementors of the Allocator
    /// trait), and an instance of an implementor of Allocator must return the
    /// same token throughout its entire lifetime.
    fn generation(&self) -> usize;

    /// Allocate a block with enough space for `size` fields, write its header,
    /// and return it.
    fn block_with_size_and_tag(&self, size: usize, tag: u8) -> BlockBuilder<'_>;

    /// Write the given value to the `index`th field of `block`.
    ///
    /// # Panics
    ///
    /// Panics if `index` is out of bounds for `block` (i.e., greater than or
    /// equal to the block's size).
    fn set_field<'a>(&self, block: &mut BlockBuilder<'a>, index: usize, value: Value<'a>);

    /// # Safety
    ///
    /// Must be used only with values allocated by this `Allocator`. The caller
    /// may assume the returned pointer is valid only until some other
    /// `Allocator` method is called on `self` (since an allocation may
    /// invalidate the pointed-to memory).
    ///
    /// Intended to be used only in implementations of `Allocator::set_field`
    /// and in conversion-to-OCaml functions requiring access to the raw memory
    /// of a block (e.g., `bytes_to_ocamlrep`).
    unsafe fn block_ptr_mut<'a>(&self, block: &mut BlockBuilder<'a>) -> *mut Value<'a>;

    #[inline(always)]
    fn block_with_size(&self, size: usize) -> BlockBuilder<'_> {
        self.block_with_size_and_tag(size, 0u8)
    }

    /// Convert the given data structure to an OCaml value. Structural sharing
    /// (via references or `Rc`) will not be preserved unless `add` is invoked
    /// within an outer invocation of `add_root`.
    ///
    /// To preserve structural sharing without using `add_root` (and the
    /// overhead of maintaining a cache that comes with it), consider using
    /// `ocamlrep::rc::RcOc` instead of `Rc`.
    #[inline(always)]
    fn add<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a> {
        value.to_ocamlrep(self)
    }

    /// Convert the given `Copy` data structure to an OCaml value.
    #[inline(always)]
    fn add_copy<'a, T: ToOcamlRep + Copy + 'static>(&'a self, value: T) -> Value<'a> {
        let value_ref = &value;
        // SAFETY: add/to_ocamlrep cannot reference `value` in the `Value`
        // they return, and the `Copy + 'static` bounds ensure that we're not
        // working with a reference or Rc which might be invalidated.
        self.add(unsafe { std::mem::transmute::<&'_ T, &'a T>(value_ref) })
    }

    /// Given the address and size of some value, and a function to convert the
    /// value to OCaml (e.g., a closure `|alloc| (*slice).to_ocamlrep(alloc)`),
    /// either execute the function and return its result, or return a cached
    /// result for that address.
    ///
    /// If `memoized` is invoked without an outer invocation of `add_root`, it
    /// must never return a cached result. If `memoized` is invoked within an
    /// outer invocation of `add_root`, it must return a result computed within
    /// that invocation of `add_root`.
    fn memoized<'a>(
        &'a self,
        ptr: usize,
        size_in_bytes: usize,
        f: impl FnOnce(&'a Self) -> Value<'a>,
    ) -> Value<'a>;

    /// Convert the given data structure to an OCaml value. Structural sharing
    /// (via references or `Rc`) will be preserved.
    ///
    /// Note that sharing is preserved using a memoization cache keyed off of
    /// address and size only. If the given value contains multiple references
    /// or slices pointing to equal-sized views of the same data, but with
    /// different OCaml representations, e.g.:
    ///
    /// ```
    /// let x: &(u32, u32) = &(0u32, 1u32);
    /// let y: &u64 = unsafe { std::mem::transmute(x) };
    /// let value = (x, y);
    /// alloc.add_root(&value) // PROBLEM!
    /// ```
    ///
    /// Then the converted OCaml value will not have the intended type (i.e.,
    /// the one which would be produced by `Allocator::add`). In this example,
    /// the allocator will memoize the result of converting `x` (an OCaml
    /// tuple), and use it for both `x` and `y` in `value` (since they point to
    /// equal-sized types at the same address).
    ///
    /// # Panics
    ///
    /// `add_root` is not re-entrant, and panics upon attempts to do so.
    fn add_root<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a>;

    /// Allocate a block with tag `STRING_TAG` and enough space for a string of
    /// `len` bytes. Write its header and return a `BlockBytes` wrapping the
    /// buffer and the block.
    fn byte_string_with_len<'a>(&'a self, len: usize) -> BlockBytes<'a> {
        let word_size = std::mem::size_of::<*const u8>();
        let words = (len + 1 /*null-ending*/ + (word_size - 1)/*rounding*/) / word_size;
        let length = words * word_size;
        let mut block = self.block_with_size_and_tag(words, STRING_TAG);
        unsafe {
            let block = self.block_ptr_mut(&mut block);
            *block.add(words - 1) = Value::from_bits(0);
            let block_bytes = block as *mut u8;
            *block_bytes.add(length - 1) = (length - len - 1) as u8;
            BlockBytes::new(std::slice::from_raw_parts_mut(block_bytes, len))
        }
    }
}

/// A type which can be reconstructed from an OCaml value.
///
/// Types which implement both `ToOcamlRep` and `FromOcamlRep` should provide
/// compatible implementations thereof. In other words, it is expected that for
/// any value, `T::from_ocamlrep(value.to_ocamlrep(alloc)) == Ok(value)`.
pub trait FromOcamlRep: Sized {
    /// Convert the given ocamlrep Value to a value of type `Self`, if possible.
    fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError>;

    /// Convert the given OCaml value to a value of type `Self`, if possible.
    ///
    /// # Safety
    ///
    /// The given value must be a valid OCaml value. All values reachable from
    /// the given value must be valid OCaml values. None of these values may be
    /// naked pointers. None of these values may be modified while `from_ocaml`
    /// is running.
    unsafe fn from_ocaml(value: usize) -> Result<Self, FromError> {
        Self::from_ocamlrep(Value::from_bits(value))
    }
}

/// A type which can be reconstructed from an OCaml value.
///
/// Types which implement both `ToOcamlRep` and `FromOcamlRepIn` should provide
/// compatible implementations thereof. In other words, it is expected that for
/// any value, `T::from_ocamlrep_in(value.to_ocamlrep(alloc), bump) == Ok(value)`.
pub trait FromOcamlRepIn<'a>: Sized {
    /// Convert the given ocamlrep Value to a value of type `Self`, allocated in
    /// the given arena.
    fn from_ocamlrep_in(value: Value<'_>, arena: &'a Bump) -> Result<Self, FromError>;
}