// 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::ffi::CString;
use std::panic::UnwindSafe;

pub use bumpalo::Bump;
use ocamlrep::Allocator;
use ocamlrep::BlockBuilder;
pub use ocamlrep::FromOcamlRep;
pub use ocamlrep::FromOcamlRepIn;
use ocamlrep::MemoizationCache;
use ocamlrep::ToOcamlRep;
pub use ocamlrep::Value;

extern "C" {
    fn ocamlpool_enter();
    fn ocamlpool_leave();
    fn ocamlpool_reserve_block(tag: u8, size: usize) -> usize;
    fn caml_failwith(msg: *const i8);
    fn caml_initialize(addr: *mut usize, value: usize);
    static ocamlpool_generation: usize;

    pub fn caml_named_value(name: *const std::ffi::c_char) -> *mut usize;
    pub fn caml_callbackN_exn(closure: usize, n: std::ffi::c_int, args: *const usize) -> usize;
}

pub struct Pool {
    cache: MemoizationCache,
}

impl Pool {
    /// Prepare the ocamlpool library to allocate values directly on the OCaml
    /// runtime's garbage-collected heap.
    ///
    /// # Safety
    ///
    /// The OCaml runtime is not thread-safe, and this function will interact
    /// with it. If any other thread interacts with the OCaml runtime or
    /// ocamlpool library during the lifetime of the `Pool`, undefined behavior
    /// will result.
    #[inline(always)]
    pub unsafe fn new() -> Self {
        ocamlpool_enter();
        Self {
            cache: MemoizationCache::new(),
        }
    }

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

impl Drop for Pool {
    #[inline(always)]
    fn drop(&mut self) {
        unsafe { ocamlpool_leave() };
    }
}

impl Allocator for Pool {
    #[inline(always)]
    fn generation(&self) -> usize {
        unsafe { ocamlpool_generation }
    }

    #[inline(always)]
    fn block_with_size_and_tag(&self, size: usize, tag: u8) -> BlockBuilder<'_> {
        let ptr = unsafe { ocamlpool_reserve_block(tag, size) as *mut Value<'_> };
        BlockBuilder::new(unsafe { std::slice::from_raw_parts_mut(ptr, size) })
    }

    #[inline(always)]
    fn set_field<'a>(&self, block: &mut BlockBuilder<'a>, index: usize, value: Value<'a>) {
        assert!(index < block.size());
        unsafe {
            caml_initialize(
                (self.block_ptr_mut(block) as *mut usize).add(index),
                value.to_bits(),
            )
        };
    }

    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))
    }
}

/// Convert the given value to an OCaml value on the OCaml runtime's
/// garbage-collected heap.
///
/// # Safety
///
/// The OCaml runtime is not thread-safe, and this function will interact with
/// it. If any other thread interacts with the OCaml runtime or ocamlpool
/// library during the execution of `to_ocaml`, undefined behavior will result.
///
/// # Panics
///
/// Panics upon attempts to re-enter `to_ocaml`.
#[inline(always)]
pub unsafe fn to_ocaml<T: ToOcamlRep + ?Sized>(value: &T) -> usize {
    let pool = Pool::new();
    let result = pool.add_root(value);
    result.to_bits()
}

/// Catches panics in `f` and raises a OCaml exception of type Failure
/// with the panic message (if the panic was raised with a `&str` or `String`).
pub fn catch_unwind(f: impl FnOnce() -> usize + UnwindSafe) -> usize {
    catch_unwind_with_handler(f, |msg: &str| -> Result<usize, String> { Err(msg.into()) })
}

/// Catches panics in `f` and raises a OCaml exception of type Failure
/// with the panic message (if the panic was raised with a `&str` or `String`).
/// `h` handles panic msg, it may re-raise by returning Err.
pub fn catch_unwind_with_handler(
    f: impl FnOnce() -> usize + UnwindSafe,
    h: impl FnOnce(&str) -> Result<usize, String>,
) -> usize {
    let err = match std::panic::catch_unwind(f) {
        Ok(value) => return value,
        Err(err) => err,
    };
    let msg: &str = if let Some(s) = err.downcast_ref::<&str>() {
        s
    } else if let Some(s) = err.downcast_ref::<String>() {
        s.as_str()
    } else {
        // TODO: Build a smarter message in this case (using panic::set_hook?)
        "Panicked with non-string object"
    };
    match h(msg) {
        Ok(value) => return value,
        Err(err) => unsafe {
            let msg = CString::new(err).unwrap();
            caml_failwith(msg.as_ptr().cast());
        },
    }
    unreachable!();
}

/// Assume that some Pool exists in some parent scope. Since ocamlpool is
/// implemented with statics, we don't need a reference to that pool to write to
/// it.
///
/// Does not preserve sharing of values referred to by multiple references or
/// Rcs (but sharing is preserved for `ocamlrep::rc::RcOc`).
///
/// # Safety
///
/// The OCaml runtime is not thread-safe, and this function will interact with
/// it. If any other thread interacts with the OCaml runtime or ocamlpool
/// library during the execution of this function, undefined behavior will
/// result.
#[inline(always)]
pub unsafe fn add_to_ambient_pool<T: ToOcamlRep>(value: &T) -> usize {
    let fake_pool = Pool {
        cache: MemoizationCache::new(),
    };
    let result = value.to_ocamlrep(&fake_pool).to_bits();
    std::mem::forget(fake_pool);
    result
}

/// Check if an OCaml value is an exception.
///
/// For internal purposes.
pub fn is_exception_result(v: usize) -> bool {
    v & 3 == 2
}

#[macro_export]
macro_rules! ocaml_ffi_fn {
    (fn $name:ident($($param:ident: $ty:ty),+  $(,)?) -> $ret:ty $code:block) => {
        #[no_mangle]
        pub unsafe extern "C" fn $name ($($param: usize,)*) -> usize {
            $crate::catch_unwind(|| {
                fn inner($($param: $ty,)*) -> $ret { $code }
                use $crate::FromOcamlRep;
                $(let $param = <$ty>::from_ocaml($param).unwrap();)*
                let result = inner($($param,)*);
                $crate::to_ocaml(&result)
            })
        }
    };

    (fn $name:ident() -> $ret:ty $code:block) => {
        #[no_mangle]
        pub unsafe extern "C" fn $name (_unit: usize) -> usize {
            $crate::catch_unwind(|| {
                fn inner() -> $ret { $code }
                let result = inner();
                $crate::to_ocaml(&result)
            })
        }
    };

    (fn $name:ident($($param:ident: $ty:ty),*  $(,)?) $code:block) => {
        $crate::ocaml_ffi_fn! {
            fn $name($($param: $ty),*) -> () $code
        }
    };
}

/// Convenience macro for declaring OCaml FFI wrappers.
///
/// Each parameter will be converted from OCaml using `ocamlrep` and the result
/// will be converted to OCaml using `ocamlrep` and allocated on the OCaml GC
/// heap using `ocamlpool`.
///
/// Panics in the function body will be caught and converted to an OCaml
/// exception of type Failure.
#[macro_export]
macro_rules! ocaml_ffi {
    ($(fn $name:ident($($param:ident: $ty:ty),*  $(,)?) $(-> $ret:ty)? $code:block)*) => {
        $($crate::ocaml_ffi_fn! {
            fn $name($($param: $ty),*) $(-> $ret)* $code
        })*
    };
}

#[macro_export]
macro_rules! ocaml_ffi_with_arena_fn {
    (fn $name:ident<$lifetime:lifetime>($arena:ident: $arena_ty:ty, $($param:ident: $ty:ty),+ $(,)?) -> $ret:ty $code:block) => {
        #[no_mangle]
        pub unsafe extern "C" fn $name ($($param: usize,)*) -> usize {
            $crate::catch_unwind(|| {
                use $crate::FromOcamlRepIn;
                let arena = &$crate::Bump::new();
                fn inner<$lifetime>($arena: $arena_ty, $($param: usize,)*) -> $ret {
                    $(let $param = unsafe {
                        <$ty>::from_ocamlrep_in($crate::Value::from_bits($param), $arena).unwrap()
                    };)*
                    $code
                }
                let result = inner(arena, $($param,)*);
                $crate::to_ocaml(&result)
            })
        }
    };

    (fn $name:ident<$lifetime:lifetime>($arena:ident: $arena_ty:ty $(,)?) -> $ret:ty $code:block) => {
        #[no_mangle]
        pub unsafe extern "C" fn $name (_unit: usize) -> usize {
            $crate::catch_unwind(|| {
                fn inner<$lifetime>($arena: $arena_ty) -> $ret { $code }
                let arena = &$crate::Bump::new();
                let result = inner(arena);
                $crate::to_ocaml(&result)
            })
        }
    };

    (fn $name:ident<$lifetime:lifetime>($($param:ident: $ty:ty),* $(,)?) $code:block) => {
        $crate::ocaml_ffi_with_arena_fn! {
            fn $name<$lifetime>($($param: $ty),*) -> () $code
        }
    };
}

/// Convenience macro for declaring OCaml FFI wrappers which use an arena to
/// allocate the arguments and return value.
///
/// FFI functions declared with this macro must declare exactly one lifetime
/// parameter. The function's first value parameter must be a reference to a
/// `bumpalo::Bump` arena with that lifetime:
///
/// ```
/// ocaml_ffi_with_arena! {
///     fn swap_str_pair<'a>(arena: &'a Bump, pair: (&'a str, &'a str)) -> (&'a str, &'a str) {
///         (pair.1, pair.0)
///     }
/// }
/// ```
///
/// An OCaml extern declaration for this function would look like this:
///
/// ```
/// external swap_str_pair : string * string -> string * string = "swap_str_pair"
/// ```
///
/// Note that no parameter for the arena appears on the OCaml side--it is
/// constructed on the Rust side and lives only for the duration of one FFI
/// call.
///
/// Each (non-arena) parameter will be converted from OCaml using
/// `ocamlrep::FromOcamlRepIn`, and allocated in the given arena (if its
/// `FromOcamlRepIn` implementation makes use of the arena).
///
/// The return value (which may be allocated in the given arena, if convenient)
/// will be converted to OCaml using `ocamlrep::ToOcamlRep`. The converted OCaml
/// value will be allocated on the OCaml heap using `ocamlpool`.
///
/// Panics in the function body will be caught and converted to an OCaml
/// exception of type `Failure`.
#[macro_export]
macro_rules! ocaml_ffi_with_arena {
    ($(fn $name:ident<$lifetime:lifetime>($($param:ident: $ty:ty),* $(,)?) $(-> $ret:ty)? $code:block)*) => {
        $($crate::ocaml_ffi_with_arena_fn! {
            fn $name<$lifetime>($($param: $ty),*) $(-> $ret)* $code
        })*
    };
}

#[macro_export]
macro_rules! ocaml_ffi_arena_result_fn {
    (fn $name:ident<$lifetime:lifetime>($arena:ident: $arena_ty:ty, $($param:ident: $ty:ty),+ $(,)?) -> $ret:ty $code:block) => {
        #[no_mangle]
        pub unsafe extern "C" fn $name ($($param: usize,)*) -> usize {
            $crate::catch_unwind(|| {
                fn inner<$lifetime>($arena: $arena_ty, $($param: $ty,)*) -> $ret {
                    $code
                }
                use $crate::FromOcamlRep;
                $(let $param = <$ty>::from_ocaml($param).unwrap();)*
                let arena = &$crate::Bump::new();
                let result = inner(arena, $($param,)*);
                $crate::to_ocaml(&result)
            })
        }
    };
}

/// Convenience macro for declaring OCaml FFI wrappers which use an arena to
/// just the return value.
///
/// FFI functions declared with this macro must declare exactly one lifetime
/// parameter. The function's first value parameter must be a reference to a
/// `bumpalo::Bump` arena with that lifetime:
///
/// ```
/// ocaml_ffi_arena_result! {
///     fn parse_example<'a>(arena: &'a Bump, text: String) -> ('a str, &'a str) {
///         /* copy parts of text into arena while parsing */
///         (pair.1, pair.0)
///     }
/// }
/// ```
///
/// An OCaml extern declaration for this function would look like this:
///
/// ```
/// external parse_example : string -> string * string = "parse_example"
/// ```
///
/// Note that no parameter for the arena appears on the OCaml side--it is
/// constructed on the Rust side and lives only for the duration of one FFI
/// call.
///
/// Each parameter after the arena parameter will be converted from OCaml using
/// `ocamlrep` and passed in as an owned value.
///
/// The return value (which may be allocated in the given arena, if convenient)
/// will be converted to OCaml using `ocamlrep::ToOcamlRep`. The converted OCaml
/// value will be allocated on the OCaml heap using `ocamlpool`.
///
/// Panics in the function body will be caught and converted to an OCaml
/// exception of type `Failure`.
#[macro_export]
macro_rules! ocaml_ffi_arena_result {
    ($(fn $name:ident<$lifetime:lifetime>($($param:ident: $ty:ty),* $(,)?) $(-> $ret:ty)? $code:block)*) => {
        $($crate::ocaml_ffi_arena_result_fn! {
            fn $name<$lifetime>($($param: $ty),*) $(-> $ret)* $code
        })*
    };
}

#[macro_export]
macro_rules! ocaml_registered_function_fn {
    // This needs to be first, as macro matching is linear.
    //
    // caml_callback_exn works as it directly calls into the implemented OCaml functions.
    //
    // caml_callback{2,3,N}_exn don't work, as they go through caml_apply2,
    // caml_apply3 etc. which for some reason crashes!
    //
    // TODO: FIgure out how to make caml_apply2 and friends not crash, and remove the below rule.
    ($ocaml_name:expr, fn $name:ident($param1:ident: $ty1:ty, $($params:ident: $ty:ty),+  $(,)?) -> $ret:ty) => {
        compile_error!("We don't support functions with more than one parameter.");
    };

    ($ocaml_name:expr, fn $name:ident($($param:ident: $ty:ty),+  $(,)?) -> $ret:ty) => {
        #[no_mangle]
        pub unsafe fn $name ($($param: $ty,)*) -> $ret {
            static FN: once_cell::sync::OnceCell<usize> = once_cell::sync::OnceCell::new();
            let the_function_to_call = *FN.get_or_init(|| {
                let the_function_to_call_name = std::ffi::CString::new($ocaml_name).expect("string contained null byte");
                let the_function_to_call = $crate::caml_named_value(the_function_to_call_name.as_ptr());
                if the_function_to_call.is_null() {
                    panic!("Could not find function. Use Callback.register");
                }
                *the_function_to_call
            });
            let args_to_function: Vec<usize> = vec![$($crate::to_ocaml(&$param),)*];
            let args_to_function_ptr: *const usize = args_to_function.as_ptr();
            let result = $crate::caml_callbackN_exn(the_function_to_call, args_to_function.len().try_into().unwrap(), args_to_function_ptr);
            if $crate::is_exception_result(result) {
                panic!("OCaml function threw an unknown exception");
            }
            let result = <$ret>::from_ocaml(result).unwrap();
            result
        }
    };

    ($ocaml_name:expr, fn $name:ident() -> $ret:ty) => {
        unsafe fn $name() -> $ret {
            $crate::ocaml_registered_function_fn!(
                $ocaml_name,
                fn inner(_unit: ()) -> $ret
            );
            inner(())
        }
    };

    ($ocaml_name:expr, fn $name:ident($($param:ident: $ty:ty),*  $(,)?)) => {
        $crate::ocaml_registered_function_fn!(
            $ocaml_name,
            fn $name($($param: $ty),*) -> ()
        );
    };
}

/// Convenience macro for declaring Rust FFI wrappers around OCaml-defined functions.
///
/// Each parameter will be converted to OCaml using `ocamlrep` and allocated on
/// the OCaml GC heap using `ocamlpool`. The result will be converted from OCaml
/// using `ocamlrep`.
///
/// Exceptions in OCaml will be caught and converted to a Rust panic. The panic
/// will not contain useful information due to the limitations of deserializing
/// arbitrary OCaml exceptions.
#[macro_export]
macro_rules! ocaml_registered_function {
    ($(fn $name:ident($($param:ident: $ty:ty),*  $(,)?) $(-> $ret:ty)?;)*) => {
        $($crate::ocaml_registered_function_fn!(
            stringify!($name),
            fn $name($($param: $ty),*) $(-> $ret)*
        );)*
    };
}


// 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.

#![allow(unused_crate_dependencies)]
#![feature(exit_status_error)]

use ocamlrep_ocamlpool::ocaml_ffi;
use ocamlrep_ocamlpool::ocaml_registered_function;
use ocamlrep_ocamlpool::FromOcamlRep;

extern "C" {
    fn ocamlpool_enter();
    fn ocamlpool_reserve_block(tag: u8, size: usize) -> usize;
    fn ocamlpool_leave();
}

// This test attempts to catch off by one issues in ocamlpool.c

// Magic constant needs to fulfill two requirements:
// Needs to be above the OCAMLPOOL_DEFAULT_SIZE constant in ocamlpool.h
//   This requirement is easy to fulfill
// Needs to be the exact size of memory block allocated by ocamlpool_reserve_block
//   which is given by the Chunk_size call in chunk_alloc in ocamlpool.c
//   This requirement requires some magic
const MAGIC_MEMORY_SIZE: usize = 1053183;

ocaml_registered_function! {
    fn f_unit_to_unit();
    fn f_one_arg_to_unit(x: i64);
    fn f_sum_tuple(args: (i64, i64)) -> i64;
}

ocaml_ffi! {
    fn test() {
        unsafe {
            ocamlpool_enter();
            // This line will crash on off by one error
            ocamlpool_reserve_block(0, MAGIC_MEMORY_SIZE);
            ocamlpool_leave();
        }
    }

    fn test_call_ocaml_from_rust() {
        for _ in 0..4 {
            unsafe {
                f_unit_to_unit();
                f_one_arg_to_unit(3);
                assert!(f_sum_tuple((3, 4)) == 7);
            }
        }
    }
}

// [Note: Test blocks for Cargo]
// -----------------------------
// With buck, where testing involves compiling OCaml we make use of
// `ocaml_binary` & `custom_unnittest` rules.
//
// When testing with cargo we instead use `#[cfg(test_blocks)]` within which we
// compile OCaml "manually" using the rust `command` crate. Thus, in these cases
// the `#[cfg(test)]` blocks are for cargo only & not buck. We express that
// according to the following schema:
// ```
//  rust_library(
//     name = "foo_test",
//     ...
//     autocargo = {
//       ...
//       "test": True, // Yes, unittests for Cargo...
//       ...
//     },
//     ...
//     unittests=False, // No! No, no unittests for Buck!
//     ...
//  )
//```
//
// If in such a `#[cfg(test)]` block we now wish to use a crate not otherwise
// dependend upon & put it in the target's `deps` section in `TARGETS` there
// will be an unsused-crate error. If we put it in the target's `test_deps`
// section in `TARGETS` buck will rightly complain that `unittests=False` (so
// how can there be `tests_deps`?).
//
// The workaround I employ is to add `allow(unused_crate_dependencies)` to this
// module. That way they can be enumerated in the `deps` and are thereby
// availabe for use in the `#[cfg(test)]` blocks.

#[cfg(test)]
mod tests {
    use anyhow::Result;
    use cargo_test_utils::*;
    use tempdir::TempDir;

    #[test]
    fn ocamlpool_test() -> Result<()> {
        let tmp_dir = TempDir::new("ocamlpool_test")?;
        std::fs::copy(
            "ocamlpool_test.ml",
            tmp_dir.path().join("ocamlpool_test.ml"),
        )?;
        let compile_cmd = cmd(
            "ocamlopt.opt",
            &[
                "-verbose",
                "-c",
                "ocamlpool_test.ml",
                "-o",
                "ocamlpool_test_ml.cmx",
            ],
            Some(tmp_dir.path()),
        );
        assert_eq!(run(compile_cmd).map_err(fmt_exit_status_err), Ok(()));
        let link_cmd = cmd(
            "ocamlopt.opt",
            &[
                "-verbose",
                "-o",
                "ocamlpool_test",
                "ocamlpool_test_ml.cmx",
                "-ccopt",
                &("-L".to_owned() + workspace_dir(&["target", build_flavor()]).to_str().unwrap()),
                "-cclib",
                "-locamlpool_test",
                "-cclib",
                "-locamlrep_ocamlpool",
            ],
            Some(tmp_dir.path()),
        );
        assert_eq!(run(link_cmd).map_err(fmt_exit_status_err), Ok(()));
        let ocamlpool_test_cmd = cmd(
            tmp_dir
                .path()
                .join("ocamlpool_test")
                .as_path()
                .to_str()
                .unwrap(),
            &[],
            None,
        );
        assert_eq!(run(ocamlpool_test_cmd).map_err(fmt_exit_status_err), Ok(()));
        tmp_dir.close()?;
        Ok(())
    }
}


// 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 proc_macro::TokenStream;

/// This attribute macro is intended to be consumed by the rust_to_ocaml codegen
/// tool, so this proc macro doesn't need to do anything other than return the
/// item (with the rust_to_ocaml attribute stripped by rustc).
///
/// Use of the rust_to_ocaml attribute in positions other than items (like field
/// definitions) are stripped by ocamlrep_derive macros (which is simpler than
/// filtering them from the `item` in this crate).
///
/// We may want to add validation later so that incorrect use of the attribute
/// emits errors at compile time, but stripping is good enough for now.
#[proc_macro_attribute]
pub fn rust_to_ocaml(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}


// 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 syn::Meta::List;
use syn::Meta::NameValue;
use syn::Meta::Path;
use syn::NestedMeta::Lit;
use syn::NestedMeta::Meta;

static DOC: &str = "doc";
static RUST_TO_OCAML: &str = "rust_to_ocaml";
static PREFIX: &str = "prefix";
static AND: &str = "and";
static ATTR: &str = "attr";
static NAME: &str = "name";
static INLINE_TUPLE: &str = "inline_tuple";

/// The attributes understood by `rust_to_ocaml`.
#[derive(Clone, Debug)]
pub struct Attrs {
    /// Doc comments (and their desugared form, the `#[doc]` attribute) are
    /// picked up by rust_to_ocaml and included as ocamldoc comments.
    ///
    ///     /// Type A
    ///     pub type A = X;
    ///
    /// is converted to:
    ///
    ///     (** Type A *)
    ///     type a = x
    pub doc: Vec<String>,

    /// Sometimes OCaml programs use prefixes to distinguish fields of the same
    /// name in different records (avoiding OCaml warning 30). The `prefix`
    /// attribute (on a declaration of a struct or a struct-like enum variant)
    /// indicates the prefix that should be added to each field name.
    ///
    ///     #[rust_to_ocaml(prefix = "a_")]
    ///     pub struct StructA { pub foo: isize, pub bar: isize }
    ///     #[rust_to_ocaml(prefix = "b_")]
    ///     pub struct StructB { pub foo: isize, pub bar: isize }
    ///
    /// is converted to:
    ///
    ///     type struct_a = { a_foo: int; a_bar: int; }
    ///     type struct_b = { b_foo: int; b_bar: int; }
    pub prefix: Option<String>,

    /// OCaml attributes (in OCaml syntax) to annotate a type or field
    /// declaration with in the generated OCaml.
    ///
    ///     #[rust_to_ocaml(attr = "deriving show")]
    ///     pub type X = A;
    ///
    /// is converted to:
    ///
    ///     type x = a [@@deriving show]
    pub attrs: Vec<String>,

    /// Mutual recursion in type definitions is opt-in in OCaml; one writes
    /// `type x = y list and y = x list` rather than `type x = y list ;; type y
    /// = x list` (which is an error because the type name `y` is not bound in
    /// the declaration of `x`). Use the `#[rust_to_ocaml(and)]` attribute to
    /// indicate when the `and` keyword should be used to continue a mutually
    /// recursive type declaration.
    ///
    ///     pub struct Foo(pub Bar, pub Bar);
    ///     #[rust_to_ocaml(and)]
    ///     pub struct Bar(pub Option<Foo>, pub Option<Foo>);
    ///
    /// is converted to:
    ///
    ///     type foo = bar * bar
    ///     and bar = foo option * foo option
    pub mutual_rec: bool,

    /// Normally, rust_to_ocaml will convert the names of fields and enum
    /// variants by attempting to convert idiomatic Rust casing to idiomatic
    /// OCaml casing. Use the `#[rust_to_ocaml(name = "my_name")]` attribute to
    /// override this behavior and provide some other name. This attribute takes
    /// precedence over the `prefix` attribute (no prefix will be applied to the
    /// given name). This attribute cannot be used to rename types (use
    /// rust_to_ocaml_config.toml instead).
    pub name: Option<String>,

    /// In OCaml, a variant declared as `Foo of (a * b)` is a variant with one
    /// field which is a pointer to a heap-allocated tuple. A variant declared
    /// as `Baz of a * b` is a variant with two fields of type `a` and `b`.
    ///
    /// By default, rust_to_ocaml will produce variants with a single field. But
    /// this behavior can be overridden with the `inline_tuple` attribute,
    /// converting the fields of a tuple (possibly behind a reference, `Box`, or
    /// any other wrapper type declared in the `types.transparent` section in
    /// rust_to_ocaml_config.toml) to fields of the OCaml variant.
    ///
    ///     pub enum E {
    ///         Foo((A, B)),
    ///         Bar(Box<(A, B)>),
    ///         #[rust_to_ocaml(inline_tuple)]
    ///         Baz((A, B)),
    ///         #[rust_to_ocaml(inline_tuple)]
    ///         Qux(Box<(A, B)>),
    ///     }
    ///
    /// is converted to:
    ///
    ///     type e =
    ///       | Foo of (a * b)
    ///       | Bar of (a * b)
    ///       | Baz of a * b
    ///       | Qux of a * b
    pub inline_tuple: bool,
}

impl Attrs {
    pub fn from_type(item: &syn::ItemType) -> Self {
        Self::from_attributes(&item.attrs, AttrKind::Container)
    }
    pub fn from_struct(item: &syn::ItemStruct) -> Self {
        Self::from_attributes(&item.attrs, AttrKind::Container)
    }
    pub fn from_enum(item: &syn::ItemEnum) -> Self {
        Self::from_attributes(&item.attrs, AttrKind::Container)
    }

    pub fn from_variant(variant: &syn::Variant) -> Self {
        Self::from_attributes(&variant.attrs, AttrKind::Variant)
    }
    pub fn from_field(field: &syn::Field) -> Self {
        Self::from_attributes(&field.attrs, AttrKind::Field)
    }

    fn from_attributes(attrs: &[syn::Attribute], kind: AttrKind) -> Self {
        let doc = get_doc_comment(attrs);
        let mut prefix = None;
        let mut ocaml_attrs = vec![];
        let mut mutual_rec = false;
        let mut name = None;
        let mut inline_tuple = false;

        for meta_item in attrs
            .iter()
            .flat_map(get_rust_to_ocaml_meta_items)
            .flatten()
        {
            match &meta_item {
                // Parse `#[rust_to_ocaml(prefix = "foo")]`
                Meta(NameValue(m)) if m.path.is_ident(PREFIX) => {
                    // TODO: emit error for AttrKind::Field (should use the
                    // `name` meta item instead)
                    if let Ok(s) = get_lit_str(PREFIX, &m.lit) {
                        prefix = Some(s.value());
                    }
                }
                // Parse `#[rust_to_ocaml(attr = "deriving eq")]`
                Meta(NameValue(m)) if m.path.is_ident(ATTR) => {
                    if let Ok(s) = get_lit_str(ATTR, &m.lit) {
                        ocaml_attrs.push(s.value());
                    }
                }
                // Parse `#[rust_to_ocaml(and)]`
                Meta(Path(word)) if word.is_ident(AND) => {
                    // TODO: emit an error instead
                    assert_eq!(kind, AttrKind::Container);
                    mutual_rec = true;
                }
                // Parse `#[rust_to_ocaml(name = "foo")]`
                Meta(NameValue(m)) if m.path.is_ident(NAME) => {
                    // TODO: emit error for AttrKind::Container (should add to
                    // types.rename config instead)
                    if let Ok(s) = get_lit_str(NAME, &m.lit) {
                        name = Some(s.value());
                    }
                }
                // Parse `#[rust_to_ocaml(inline_tuple)]`
                Meta(Path(word)) if word.is_ident(INLINE_TUPLE) => {
                    // TODO: emit an error instead
                    assert_eq!(kind, AttrKind::Variant);
                    inline_tuple = true;
                }
                Meta(_meta_item) => {
                    // let path = meta_item
                    //     .path()
                    //     .into_token_stream()
                    //     .to_string()
                    //     .replace(' ', "");
                    // cx.error_spanned_by(
                    //     meta_item.path(),
                    //     format!("unknown rust_to_ocaml {} attribute `{}`", kind, path),
                    // );
                }
                Lit(_lit) => {
                    // cx.error_spanned_by(lit, format!("unexpected literal in rust_to_ocaml {} attribute", kind));
                }
            }
        }

        Self {
            doc,
            prefix,
            attrs: ocaml_attrs,
            mutual_rec,
            name,
            inline_tuple,
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum AttrKind {
    Container,
    Variant,
    Field,
}

impl std::fmt::Display for AttrKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Container => write!(f, "container"),
            Self::Variant => write!(f, "variant"),
            Self::Field => write!(f, "field"),
        }
    }
}

pub fn get_doc_comment(attrs: &[syn::Attribute]) -> Vec<String> {
    attrs
        .iter()
        .filter_map(|attr| {
            if !attr.path.is_ident(DOC) {
                return None;
            }
            match attr.parse_meta() {
                Ok(syn::Meta::NameValue(meta)) => {
                    if let syn::Lit::Str(s) = meta.lit {
                        Some(s.value())
                    } else {
                        None
                    }
                }
                _ => None,
            }
        })
        .collect()
}

fn get_rust_to_ocaml_meta_items(attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
    if !attr.path.is_ident(RUST_TO_OCAML) {
        return Ok(vec![]);
    }

    match attr.parse_meta() {
        Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
        Ok(_other) => {
            // cx.error_spanned_by(other, "expected #[rust_to_ocaml(...)]");
            Err(())
        }
        Err(_err) => {
            // cx.syn_error(err);
            Err(())
        }
    }
}

fn get_lit_str<'a>(_attr_name: &'static str, lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> {
    if let syn::Lit::Str(lit) = lit {
        Ok(lit)
    } else {
        // cx.error_spanned_by(
        //     lit,
        //     format!(
        //         "expected rust_to_ocaml {} attribute to be a string: `{} = \"...\"`",
        //         attr_name, attr_name
        //     ),
        // );
        Err(())
    }
}


// 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::str::FromStr;

use indexmap::indexmap;
use indexmap::indexset;
use indexmap::IndexMap;
use indexmap::IndexSet;
use serde::Deserialize;
use serde::Serialize;

use crate::ir;
use crate::ir::ModuleName;
use crate::ir::TypeName;

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Config {
    #[serde(default)]
    modules: ModulesConfig,
    #[serde(default)]
    types: TypesConfig,
}

#[derive(Debug, Default, Serialize, Deserialize)]
struct ModulesConfig {
    #[serde(default, with = "indexmap::serde_seq")]
    rename: IndexMap<ModuleName, ModuleName>,
}

#[derive(Debug, Serialize, Deserialize)]
struct TypesConfig {
    transparent: IndexSet<RustTypePath>,
    #[serde(with = "indexmap::serde_seq")]
    rename: IndexMap<RustTypePath, OcamlTypePath>,
}

#[derive(Clone, PartialEq, Eq, Hash)]
pub struct RustTypePath {
    pub modules: Vec<ModuleName>,
    pub ty: TypeName,
}

#[derive(Clone, PartialEq, Eq, Hash)]
pub struct OcamlTypePath {
    pub modules: Vec<ModuleName>,
    pub ty: TypeName,
}

impl Config {
    pub fn get_renamed_module(&self, name: &ModuleName) -> Option<ModuleName> {
        self.modules.rename.get(name).cloned()
    }
    pub fn is_transparent_type(&self, path: &ir::TypePath) -> bool {
        let rust_path = RustTypePath::from(path);
        self.types.transparent.contains(&rust_path)
    }
    pub fn get_renamed_type(&self, path: &ir::TypePath) -> Option<OcamlTypePath> {
        let rust_path = RustTypePath::from(path);
        self.types.rename.get(&rust_path).cloned()
    }
}

impl Default for TypesConfig {
    fn default() -> Self {
        let r = |s| RustTypePath::from_str(s).unwrap();
        let o = |s| OcamlTypePath::from_str(s).unwrap();
        Self {
            transparent: indexset! {
                r("Box"),
                r("std::boxed::Box"),
                r("Rc"),
                r("std::rc::Rc"),
                r("Arc"),
                r("std::sync::Arc"),
            },
            rename: indexmap! {
                r("Vec") => o("list"),
                r("std::vec::Vec") => o("list"),
            },
        }
    }
}

impl From<&ir::TypePath> for RustTypePath {
    fn from(path: &ir::TypePath) -> Self {
        Self {
            modules: path.modules.clone(),
            ty: path.ty.clone(),
        }
    }
}

impl std::fmt::Display for RustTypePath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for m in self.modules.iter() {
            write!(f, "{}", m.as_str())?;
            write!(f, "::")?;
        }
        write!(f, "{}", self.ty.as_str())
    }
}
impl std::fmt::Display for OcamlTypePath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for m in self.modules.iter() {
            write!(f, "{}", m.as_str())?;
            write!(f, ".")?;
        }
        write!(f, "{}", self.ty.as_str())
    }
}

impl std::fmt::Debug for RustTypePath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        format!("{self}").fmt(f)
    }
}
impl std::fmt::Debug for OcamlTypePath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        format!("{self}").fmt(f)
    }
}

impl FromStr for RustTypePath {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> anyhow::Result<Self> {
        let (modules, ty) = parse_type_path(s, "::")?;
        Ok(Self { modules, ty })
    }
}
impl FromStr for OcamlTypePath {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> anyhow::Result<Self> {
        let (modules, ty) = parse_type_path(s, ".")?;
        Ok(Self { modules, ty })
    }
}

fn parse_type_path(s: &str, sep: &str) -> anyhow::Result<(Vec<ModuleName>, TypeName)> {
    let mut split = s.rsplit(sep);
    let ty = match split.next() {
        None | Some("") => anyhow::bail!("Invalid type name: {:?}", s),
        Some(ty) => TypeName(ty.to_owned()),
    };
    let mut modules = split.map(ModuleName::new).collect::<Result<Vec<_>, _>>()?;
    modules.reverse();
    Ok((modules, ty))
}

serde_from_display!(RustTypePath, "a valid Rust type path string");
serde_from_display!(OcamlTypePath, "a valid OCaml type path string");


// 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 anyhow::bail;
use anyhow::ensure;
use anyhow::Context;
use anyhow::Result;
use convert_case::Case;
use convert_case::Casing;

use crate::ir;
use crate::ir::Def;
use crate::ir::FieldName;
use crate::ir::File;
use crate::ir::TypeName;
use crate::ir::VariantName;
use crate::Config;

pub fn convert_file(
    config: &'static Config,
    filename: &std::path::Path,
    file: &syn::File,
) -> Result<String> {
    let defs = (file.items.iter())
        .filter_map(|item| ItemConverter::convert_item(config, item).transpose())
        .collect::<Result<_>>()?;
    let file_stem = filename.file_stem().context("expected nonempty filename")?;
    let module_name = file_stem.to_str().context("non-UTF8 filename")?.to_owned();
    let mut file = File {
        root: ir::Module {
            name: ir::ModuleName::new(module_name)?,
            defs,
        },
    };
    crate::rewrite_types::rewrite_file(config, &mut file);
    crate::rewrite_module_names::rewrite_file(config, &mut file);
    Ok(file.to_string())
}

struct ItemConverter {
    config: &'static Config,
    tparams: Vec<String>,
}

impl ItemConverter {
    fn convert_item(config: &'static Config, item: &syn::Item) -> Result<Option<Def>> {
        use syn::Item;
        match item {
            Item::Type(item) => {
                let this = ItemConverter::new(config, &item.generics);
                Ok(Some(this.convert_item_type(item).with_context(|| {
                    format!("Failed to convert type {}", item.ident)
                })?))
            }
            Item::Struct(item) => {
                let this = ItemConverter::new(config, &item.generics);
                Ok(Some(this.convert_item_struct(item).with_context(|| {
                    format!("Failed to convert type {}", item.ident)
                })?))
            }
            Item::Enum(item) => {
                let this = ItemConverter::new(config, &item.generics);
                Ok(Some(this.convert_item_enum(item).with_context(|| {
                    format!("Failed to convert type {}", item.ident)
                })?))
            }
            Item::Mod(item) => {
                if let Some((_brace, items)) = &item.content {
                    let defs = items
                        .iter()
                        .filter_map(|item| Self::convert_item(config, item).transpose())
                        .collect::<Result<_>>()
                        .with_context(|| format!("Failed to convert module {}", item.ident))?;
                    Ok(Some(Def::Module(ir::Module {
                        name: ir::ModuleName::new(item.ident.to_string())?,
                        defs,
                    })))
                } else {
                    Ok(None)
                }
            }
            _ => Ok(None),
        }
    }

    fn new(config: &'static Config, generics: &syn::Generics) -> Self {
        let tparams = generics
            .type_params()
            .map(|tparam| tparam.ident.to_string())
            .collect();
        Self { config, tparams }
    }

    fn convert_item_type(self, item: &syn::ItemType) -> Result<Def> {
        let name = TypeName(item.ident.to_string().to_case(Case::Snake));
        let attrs = attr_parser::Attrs::from_type(item);
        let ty = self.convert_type(&item.ty)?;
        Ok(Def::Alias {
            doc: attrs.doc,
            attrs: attrs.attrs,
            mutual_rec: attrs.mutual_rec,
            tparams: self.tparams,
            name,
            ty,
        })
    }

    fn convert_item_struct(self, item: &syn::ItemStruct) -> Result<Def> {
        let name = TypeName(item.ident.to_string().to_case(Case::Snake));
        let container_attrs = attr_parser::Attrs::from_struct(item);
        match &item.fields {
            syn::Fields::Unit => Ok(Def::Alias {
                doc: container_attrs.doc,
                attrs: container_attrs.attrs,
                mutual_rec: container_attrs.mutual_rec,
                tparams: self.tparams,
                name,
                ty: ir::Type::Path(ir::TypePath::simple("unit")),
            }),
            syn::Fields::Unnamed(fields) => {
                let elems = (fields.unnamed.iter())
                    .map(|field| self.convert_type(&field.ty))
                    .collect::<Result<Vec<_>>>()?;
                Ok(Def::Alias {
                    doc: container_attrs.doc,
                    attrs: container_attrs.attrs,
                    mutual_rec: container_attrs.mutual_rec,
                    tparams: self.tparams,
                    name,
                    ty: if elems.is_empty() {
                        ir::Type::Path(ir::TypePath::simple("unit"))
                    } else {
                        ir::Type::Tuple(ir::TypeTuple { elems })
                    },
                })
            }
            syn::Fields::Named(fields) => {
                let fields = (fields.named.iter())
                    .map(|field| {
                        let field_attrs = attr_parser::Attrs::from_field(field);
                        let name = if let Some(name) = field_attrs.name {
                            FieldName(name)
                        } else {
                            field_name(field.ident.as_ref(), container_attrs.prefix.as_deref())
                        };
                        let ty = self.convert_type(&field.ty)?;
                        Ok(ir::Field {
                            name,
                            ty,
                            doc: field_attrs.doc,
                            attrs: field_attrs.attrs,
                        })
                    })
                    .collect::<Result<Vec<_>>>()?;
                Ok(Def::Record {
                    doc: container_attrs.doc,
                    attrs: container_attrs.attrs,
                    mutual_rec: container_attrs.mutual_rec,
                    tparams: self.tparams,
                    name,
                    fields,
                })
            }
        }
    }

    fn convert_item_enum(self, item: &syn::ItemEnum) -> Result<Def> {
        let name = TypeName(item.ident.to_string().to_case(Case::Snake));
        let container_attrs = attr_parser::Attrs::from_enum(item);
        let variants = item
            .variants
            .iter()
            .map(|variant| {
                let variant_attrs = attr_parser::Attrs::from_variant(variant);
                let name = if let Some(name) = variant_attrs.name {
                    VariantName(name)
                } else {
                    variant_name(&variant.ident, container_attrs.prefix.as_deref())
                };
                let fields = match &variant.fields {
                    syn::Fields::Unit => None,
                    syn::Fields::Unnamed(fields) => {
                        let mut fields = (fields.unnamed.iter())
                            .map(|field| self.convert_type(&field.ty))
                            .collect::<Result<Vec<_>>>()?;
                        if variant_attrs.inline_tuple {
                            assert_eq!(fields.len(), 1);
                            let field = fields.pop().unwrap();
                            match field {
                                ir::Type::Path(mut path) => {
                                    if path.targs.len() == 1
                                        && self.config.is_transparent_type(&path)
                                        && matches!(path.targs[0], ir::Type::Tuple(..))
                                    {
                                        if let Some(ir::Type::Tuple(tuple)) = path.targs.pop() {
                                            Some(ir::VariantFields::Unnamed(tuple.elems))
                                        } else {
                                            unreachable!()
                                        }
                                    } else {
                                        anyhow::bail!(
                                            "Variant {} must have a single argument which is a tuple",
                                            variant.ident.to_string()
                                        )
                                    }
                                }
                                ir::Type::Tuple(tuple) => {
                                    Some(ir::VariantFields::Unnamed(tuple.elems))
                                }
                            }
                        } else {
                            Some(ir::VariantFields::Unnamed(fields))
                        }
                    }
                    syn::Fields::Named(fields) => Some(ir::VariantFields::Named(
                        (fields.named.iter())
                            .map(|field| {
                                let field_attrs = attr_parser::Attrs::from_field(field);
                                let name = if let Some(name) = field_attrs.name {
                                    FieldName(name)
                                } else {
                                    field_name(
                                        field.ident.as_ref(),
                                        variant_attrs.prefix.as_deref(),
                                    )
                                };
                                let ty = self.convert_type(&field.ty)?;
                                Ok(ir::Field {
                                    name,
                                    ty,
                                    doc: field_attrs.doc,
                                    attrs: field_attrs.attrs,
                                })
                            })
                            .collect::<Result<_>>()?,
                    )),
                };
                Ok(ir::Variant {
                    name,
                    fields,
                    doc: variant_attrs.doc,
                    attrs: variant_attrs.attrs,
                })
            })
            .collect::<Result<Vec<_>>>()?;
        Ok(Def::Variant {
            doc: container_attrs.doc,
            attrs: container_attrs.attrs,
            mutual_rec: container_attrs.mutual_rec,
            tparams: self.tparams,
            name,
            variants,
        })
    }

    fn convert_type(&self, ty: &syn::Type) -> Result<ir::Type> {
        match ty {
            syn::Type::Path(ty) => Ok(ir::Type::Path(self.convert_type_path(ty)?)),
            syn::Type::Tuple(ty) => {
                if ty.elems.is_empty() {
                    Ok(ir::Type::Path(ir::TypePath::simple("unit")))
                } else {
                    Ok(ir::Type::Tuple(ir::TypeTuple {
                        elems: (ty.elems.iter())
                            .map(|e| self.convert_type(e))
                            .collect::<Result<_>>()?,
                    }))
                }
            }
            syn::Type::Reference(ty) => Ok(self.convert_type(&ty.elem)?),
            syn::Type::Slice(ty) => Ok(ir::Type::Path(ir::TypePath {
                modules: vec![],
                targs: vec![self.convert_type(&ty.elem)?],
                ty: ir::TypeName(String::from("list")),
            })),
            _ => bail!("Not supported: {:?}", ty),
        }
    }

    fn convert_type_path(&self, ty: &syn::TypePath) -> Result<ir::TypePath> {
        ensure!(ty.qself.is_none(), "Qualified self in paths not supported");
        let last_seg = ty.path.segments.last().unwrap();
        if ty.path.segments.len() == 1 && last_seg.arguments.is_empty() {
            let ident = last_seg.ident.to_string();
            if self.tparams.contains(&ident) {
                let tparam = format!("'{}", ident.to_case(Case::Snake));
                return Ok(ir::TypePath::simple(tparam));
            }
        }
        let segments_except_last = ty.path.segments.iter().rev().skip(1).rev();
        Ok(ir::TypePath {
            modules: segments_except_last
                .map(|seg| {
                    ensure!(
                        seg.arguments.is_empty(),
                        "Type args only supported in last path segment"
                    );
                    ir::ModuleName::new(seg.ident.to_string())
                })
                .collect::<Result<_>>()?,
            ty: TypeName(last_seg.ident.to_string()),
            targs: match &last_seg.arguments {
                syn::PathArguments::AngleBracketed(args) => (args.args.iter())
                    .filter_map(|arg| match arg {
                        syn::GenericArgument::Type(arg) => Some(self.convert_type(arg)),
                        _ => None,
                    })
                    .collect::<Result<_>>()?,
                _ => vec![],
            },
        })
    }
}

fn field_name(ident: Option<&syn::Ident>, prefix: Option<&str>) -> FieldName {
    FieldName(format!("{}{}", prefix.unwrap_or_default(), ident.unwrap()))
}

fn variant_name(ident: &syn::Ident, prefix: Option<&str>) -> VariantName {
    VariantName(format!("{}{}", prefix.unwrap_or_default(), ident))
}


// 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.

mod display;

use derive_more::Display;

#[derive(Debug)]
pub struct File {
    pub root: Module,
}

#[derive(Debug)]
pub struct Module {
    pub name: ModuleName,
    pub defs: Vec<Def>,
}

#[derive(Debug)]
pub enum Def {
    Module(Module),
    Alias {
        doc: Vec<String>,
        attrs: Vec<String>,
        mutual_rec: bool,
        tparams: Vec<String>,
        name: TypeName,
        ty: Type,
    },
    Record {
        doc: Vec<String>,
        attrs: Vec<String>,
        mutual_rec: bool,
        tparams: Vec<String>,
        name: TypeName,
        fields: Vec<Field>,
    },
    Variant {
        doc: Vec<String>,
        attrs: Vec<String>,
        mutual_rec: bool,
        tparams: Vec<String>,
        name: TypeName,
        variants: Vec<Variant>,
    },
}

#[derive(Debug)]
pub struct Variant {
    pub name: VariantName,
    pub fields: Option<VariantFields>,
    pub doc: Vec<String>,
    pub attrs: Vec<String>,
}

#[derive(Debug)]
pub enum VariantFields {
    Unnamed(Vec<Type>),
    Named(Vec<Field>),
}

#[derive(Debug)]
pub struct Field {
    pub name: FieldName,
    pub ty: Type,
    pub doc: Vec<String>,
    pub attrs: Vec<String>,
}

#[derive(Debug)]
pub enum Type {
    Path(TypePath),
    Tuple(TypeTuple),
}

#[derive(Debug)]
pub struct TypePath {
    pub targs: Vec<Type>,
    pub modules: Vec<ModuleName>,
    pub ty: TypeName,
}

impl TypePath {
    pub fn simple(id: impl Into<String>) -> Self {
        Self {
            modules: vec![],
            ty: TypeName(id.into()),
            targs: vec![],
        }
    }
}

#[derive(Debug)]
pub struct TypeTuple {
    pub elems: Vec<Type>,
}

#[derive(Clone, Hash, PartialEq, Eq)]
pub struct ModuleName(String);

impl ModuleName {
    pub fn new(name: impl Into<String>) -> anyhow::Result<Self> {
        let name: String = name.into();
        anyhow::ensure!(!name.is_empty(), "Module names must not be empty");
        let first_char = name.chars().next().unwrap();
        anyhow::ensure!(
            first_char.is_ascii(),
            "Module names must start with an ASCII character: {}",
            name
        );
        anyhow::ensure!(
            first_char.to_ascii_uppercase().is_ascii_uppercase(),
            "Module names must start with a character which can be converted to uppercase: {}",
            name
        );
        Ok(Self(name))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

impl std::str::FromStr for ModuleName {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> anyhow::Result<Self> {
        Self::new(s)
    }
}

serde_from_display!(ModuleName, "a valid Rust or OCaml module name");

#[derive(Clone, Hash, PartialEq, Eq)]
pub struct TypeName(pub String);

impl TypeName {
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

#[derive(Clone, Hash, PartialEq, Eq)]
pub struct FieldName(pub String);

#[derive(Clone, Hash, PartialEq, Eq, Display)]
pub struct VariantName(pub String);

impl std::fmt::Debug for ModuleName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl std::fmt::Debug for TypeName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl std::fmt::Debug for FieldName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl std::fmt::Debug for VariantName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}


// 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.

/// Provide impls of `Serialize` and `Deserialize` which delegate to the impls
/// of `std::fmt::Display` and `std::str::FromStr` respectively.
macro_rules! serde_from_display {
    ($name:ident, $expecting:expr) => {
        impl ::serde::Serialize for $name {
            fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
                serializer.serialize_str(&self.to_string())
            }
        }
        impl<'de> ::serde::Deserialize<'de> for $name {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: ::serde::Deserializer<'de>,
            {
                struct Visitor;
                impl<'de> ::serde::de::Visitor<'de> for Visitor {
                    type Value = $name;
                    fn expecting(
                        &self,
                        formatter: &mut ::std::fmt::Formatter<'_>,
                    ) -> ::std::fmt::Result {
                        formatter.write_str($expecting)
                    }
                    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
                    where
                        E: ::serde::de::Error,
                    {
                        value.parse().map_err(|e| {
                            E::invalid_value(::serde::de::Unexpected::Other(&format!("{e}")), &self)
                        })
                    }
                }
                deserializer.deserialize_str(Visitor)
            }
        }
    };
}


// 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 crate::ir;
use crate::Config;

pub fn rewrite_file(config: &'static Config, file: &mut ir::File) {
    let rewriter = Rewriter { config };
    rewriter.rewrite_module(&mut file.root)
}

struct Rewriter {
    config: &'static Config,
}

impl Rewriter {
    fn rewrite_module(&self, module: &mut ir::Module) {
        module.defs.iter_mut().for_each(|def| self.rewrite_def(def))
    }

    fn rewrite_def(&self, def: &mut ir::Def) {
        match def {
            ir::Def::Module(module) => self.rewrite_module(module),
            ir::Def::Alias { ty, .. } => self.rewrite_type(ty),
            ir::Def::Record { fields, .. } => fields.iter_mut().for_each(|f| self.rewrite_field(f)),
            ir::Def::Variant { variants, .. } => {
                variants.iter_mut().for_each(|v| self.rewrite_variant(v))
            }
        }
    }

    fn rewrite_field(&self, field: &mut ir::Field) {
        self.rewrite_type(&mut field.ty)
    }

    fn rewrite_variant(&self, variant: &mut ir::Variant) {
        variant
            .fields
            .iter_mut()
            .for_each(|f| self.rewrite_variant_fields(f))
    }

    fn rewrite_variant_fields(&self, fields: &mut ir::VariantFields) {
        match fields {
            ir::VariantFields::Unnamed(tys) => tys.iter_mut().for_each(|ty| self.rewrite_type(ty)),
            ir::VariantFields::Named(fields) => {
                fields.iter_mut().for_each(|f| self.rewrite_field(f))
            }
        }
    }

    fn rewrite_type(&self, ty: &mut ir::Type) {
        match ty {
            ir::Type::Path(path) => self.rewrite_type_path(path),
            ir::Type::Tuple(tuple) => self.rewrite_type_tuple(tuple),
        }
    }

    fn rewrite_type_path(&self, path: &mut ir::TypePath) {
        match path.modules.get(0).map(ir::ModuleName::as_str) {
            Some("crate" | "super") => {
                path.modules.remove(0);
            }
            _ => {}
        }
        path.modules.iter_mut().for_each(|m| {
            if let Some(name) = self.config.get_renamed_module(m) {
                *m = name;
            }
        });
        path.targs.iter_mut().for_each(|ty| self.rewrite_type(ty))
    }

    fn rewrite_type_tuple(&self, tuple: &mut ir::TypeTuple) {
        tuple.elems.iter_mut().for_each(|ty| self.rewrite_type(ty))
    }
}


// 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 convert_case::Case;
use convert_case::Casing;

use crate::ir;
use crate::Config;

pub fn rewrite_file(config: &'static Config, file: &mut ir::File) {
    Rewriter::rewrite_module(config, &mut file.root)
}

struct Rewriter {
    config: &'static Config,
    module_name: ir::ModuleName,
}

impl Rewriter {
    fn rewrite_module(config: &'static Config, module: &mut ir::Module) {
        let this = Self {
            config,
            module_name: module.name.clone(),
        };
        module.defs.iter_mut().for_each(|def| this.rewrite_def(def))
    }

    fn rewrite_def(&self, def: &mut ir::Def) {
        let rewrite_name = |name: &mut ir::TypeName| {
            if name.as_str() == self.module_name.as_str() {
                *name = ir::TypeName(String::from("t"));
            }
        };
        match def {
            ir::Def::Module(module) => Self::rewrite_module(self.config, module),
            ir::Def::Alias { name, ty, .. } => {
                rewrite_name(name);
                self.rewrite_type(ty)
            }
            ir::Def::Record { name, fields, .. } => {
                rewrite_name(name);
                fields.iter_mut().for_each(|f| self.rewrite_field(f))
            }
            ir::Def::Variant { name, variants, .. } => {
                rewrite_name(name);
                variants.iter_mut().for_each(|v| self.rewrite_variant(v))
            }
        }
    }

    fn rewrite_field(&self, field: &mut ir::Field) {
        self.rewrite_type(&mut field.ty)
    }

    fn rewrite_variant(&self, variant: &mut ir::Variant) {
        variant
            .fields
            .iter_mut()
            .for_each(|f| self.rewrite_variant_fields(f))
    }

    fn rewrite_variant_fields(&self, fields: &mut ir::VariantFields) {
        match fields {
            ir::VariantFields::Unnamed(tys) => tys.iter_mut().for_each(|ty| self.rewrite_type(ty)),
            ir::VariantFields::Named(fields) => {
                fields.iter_mut().for_each(|f| self.rewrite_field(f))
            }
        }
    }

    fn rewrite_type(&self, ty: &mut ir::Type) {
        match ty {
            ir::Type::Path(path) => {
                if path.targs.len() == 1 && self.config.is_transparent_type(path) {
                    *ty = path.targs.pop().unwrap();
                    self.rewrite_type(ty);
                } else {
                    self.rewrite_type_path(path);
                }
            }
            ir::Type::Tuple(tuple) => self.rewrite_type_tuple(tuple),
        }
    }

    fn rewrite_type_path(&self, path: &mut ir::TypePath) {
        // Convert all integer types to `int`. The impls of ToOcamlRep
        // and FromOcamlRep for integer types do checked conversions, so
        // we'll fail at runtime if our int value doesn't fit into
        // OCaml's integer width.
        if path.modules.is_empty() && path.targs.is_empty() {
            match path.ty.as_str() {
                "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "i128" | "u128"
                | "isize" | "usize" => {
                    path.ty = ir::TypeName(String::from("int"));
                    return;
                }
                _ => {}
            }
        }
        if let Some(renamed_path) = self.config.get_renamed_type(path) {
            path.ty = renamed_path.ty;
            path.modules = renamed_path.modules;
        }
        let ty = path.ty.as_str().to_case(Case::Snake);
        let ty_matches_last_module_in_path =
            (path.modules.last()).map_or(false, |module| ty == module.as_str());
        if ty_matches_last_module_in_path || ty == self.module_name.as_str() {
            path.ty = ir::TypeName(String::from("t"));
        }
        path.targs.iter_mut().for_each(|ty| self.rewrite_type(ty))
    }

    fn rewrite_type_tuple(&self, tuple: &mut ir::TypeTuple) {
        tuple.elems.iter_mut().for_each(|ty| self.rewrite_type(ty))
    }
}


// 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.

#[macro_use]
mod macros;

mod config;
mod convert;
mod ir;
mod rewrite_module_names;
mod rewrite_types;

use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

use anyhow::Context;
use anyhow::Result;

use crate::config::Config;

#[derive(Debug, clap::Parser)]
struct Opts {
    /// The Hack source file to convert.
    #[clap(value_name("FILEPATH"))]
    filename: PathBuf,

    /// The OCaml source file to generate.
    #[clap(value_name("OUTPATH"))]
    out_path: Option<PathBuf>,

    /// Path to a configuration file.
    #[clap(long)]
    config: Option<PathBuf>,

    /// Command to regenerate the output. This text will be included in generated file headers.
    #[clap(long)]
    regen_cmd: Option<String>,

    /// Do not add copyright header and generated tag (for tests).
    #[clap(long)]
    no_header: bool,

    /// Path to an OCaml formatter binary which will be used on the generated output.
    #[clap(long)]
    formatter: Option<String>,
}

fn main() -> Result<()> {
    let opts = <Opts as clap::Parser>::from_args();

    let config = Box::leak(Box::new(match opts.config {
        Some(path) => {
            let contents = std::fs::read_to_string(&path)
                .with_context(|| format!("Failed to read config file at {}", path.display()))?;
            toml::from_str(&contents)
                .with_context(|| format!("Failed to parse config file at {}", path.display()))?
        }
        None => Config::default(),
    }));

    let src = std::fs::read_to_string(&opts.filename)
        .with_context(|| format!("Failed to read input file {}", opts.filename.display()))?;
    let file = syn::parse_file(&src)?;
    let mut ocaml_src = convert::convert_file(config, &opts.filename, &file)?;

    if !opts.no_header {
        ocaml_src = attach_header(opts.regen_cmd.as_deref(), &ocaml_src);
    }
    let absolute_filename = opts.filename.canonicalize()?;
    let mut ocaml_src = ocamlformat(
        opts.formatter.as_deref(),
        opts.out_path
            .as_deref()
            .and_then(Path::parent)
            .or_else(|| absolute_filename.parent()),
        ocaml_src.into_bytes(),
    )
    .context("failed to run ocamlformat")?;
    if !opts.no_header {
        ocaml_src = signed_source::sign_file(&ocaml_src)?;
    }

    if let Some(out_path) = &opts.out_path {
        write_file(out_path, &ocaml_src)?;
    } else {
        let mut stdout = std::io::stdout().lock();
        stdout.write_all(&ocaml_src)?;
    }

    Ok(())
}

fn attach_header(regen_cmd: Option<&str>, contents: &str) -> String {
    let regen_cmd = regen_cmd.map_or_else(String::new, |regen_cmd| {
        format!(" *\n * To regenerate this file, run:\n *   {}\n", regen_cmd)
    });
    format!(
        r#"(*
 * 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.
 *
 * {}
{} *)

{}"#,
        signed_source::SIGNING_TOKEN,
        regen_cmd,
        contents
    )
}

fn ocamlformat(
    formatter: Option<&str>,
    out_dir: Option<&Path>,
    contents: Vec<u8>,
) -> Result<Vec<u8>> {
    let formatter = match formatter {
        None => return Ok(contents),
        Some(f) => f,
    };
    // Even if we format the file on disk (i.e., at `opts.out_path`),
    // ocamlformat won't look for an .ocamlformat file in the directory
    // containing the file. It only looks up from the current working directory.
    // There's a --root arg, but it doesn't seem to produce the same behavior.
    let prev_dir = std::env::current_dir()?;
    if let Some(out_dir) = out_dir {
        std::env::set_current_dir(out_dir)?;
    }
    let mut child = std::process::Command::new(formatter)
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        // In the event that an .ocamlformat file is still not available, tell
        // ocamlformat to please format it anyway.
        .arg("--enable-outside-detected-project")
        .arg("--impl")
        .arg("-")
        .spawn()?;
    let child_stdin = child.stdin.as_mut().unwrap();
    child_stdin.write_all(&contents)?;
    let output = child.wait_with_output()?;
    if !output.status.success() {
        anyhow::bail!("Formatter failed:\n{:#?}", output);
    }
    if out_dir.is_some() {
        std::env::set_current_dir(prev_dir)?;
    }
    Ok(output.stdout)
}

fn write_file(path: &Path, contents: &[u8]) -> Result<()> {
    let mut file = std::fs::File::create(path)?;
    file.write_all(contents)?;
    Ok(())
}


// 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 std::fmt::Formatter;
use std::fmt::Result;

use convert_case::Case;
use convert_case::Casing;

use crate::ir;

impl Display for ir::File {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        self.root.fmt(f)
    }
}

impl Display for ir::Module {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        for def in self.defs.iter() {
            def.fmt(f)?
        }
        Ok(())
    }
}

impl Display for ir::Def {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match self {
            Self::Module(module) => {
                writeln!(f, "module {} = struct", module.name)?;
                module.fmt(f)?;
                writeln!(f, "end")?
            }
            Self::Alias {
                doc,
                attrs,
                mutual_rec,
                tparams,
                name,
                ty,
            } => {
                write_toplevel_doc_comment(f, doc)?;
                if *mutual_rec {
                    write!(f, "and ")?;
                } else {
                    write!(f, "type ")?;
                }
                write_type_parameters(f, tparams)?;
                write!(f, "{name} = {ty}")?;
                for attr in attrs {
                    write!(f, " [@@{}]", attr)?;
                }
                writeln!(f)?;
            }
            Self::Record {
                doc,
                attrs,
                mutual_rec,
                tparams,
                name,
                fields,
            } => {
                write_toplevel_doc_comment(f, doc)?;
                if *mutual_rec {
                    write!(f, "and ")?;
                } else {
                    write!(f, "type ")?;
                }
                write_type_parameters(f, tparams)?;
                writeln!(f, "{name} = {{")?;
                for field in fields {
                    writeln!(f, "  {field}")?;
                }
                write!(f, "}}")?;
                for attr in attrs {
                    write!(f, " [@@{}]", attr)?;
                }
                writeln!(f)?;
            }
            Self::Variant {
                doc,
                attrs,
                mutual_rec,
                tparams,
                name,
                variants,
            } => {
                write_toplevel_doc_comment(f, doc)?;
                if *mutual_rec {
                    write!(f, "and ")?;
                } else {
                    write!(f, "type ")?;
                }
                write_type_parameters(f, tparams)?;
                writeln!(f, "{name} =")?;
                for variant in variants {
                    writeln!(f, "  | {variant}")?;
                }
                for attr in attrs {
                    writeln!(f)?;
                    write!(f, "[@@{}]", attr)?;
                }
                writeln!(f)?;
            }
        }
        writeln!(f)?;
        Ok(())
    }
}

impl Display for ir::Variant {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        let Self {
            name,
            fields,
            doc,
            attrs,
        } = self;
        write!(f, "{name}")?;
        if let Some(fields) = fields {
            write!(f, " of {fields}")?;
        }
        for attr in attrs {
            write!(f, " [@{}]", attr)?;
        }
        write_field_or_variant_doc_comment(f, doc)?;
        Ok(())
    }
}

impl Display for ir::VariantFields {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match self {
            Self::Unnamed(fields) => {
                let mut iter = fields.iter();
                let ty = iter.next().expect("empty VariantFields::Unnamed");
                ty.fmt(f)?;
                for ty in iter {
                    write!(f, " * {ty}")?;
                }
                Ok(())
            }
            Self::Named(fields) => {
                writeln!(f, "{{")?;
                for field in fields {
                    writeln!(f, "    {field}")?;
                }
                write!(f, "}}")
            }
        }
    }
}

impl Display for ir::Field {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        let Self {
            name,
            ty,
            doc,
            attrs,
        } = self;
        write!(f, "{name}: {ty};")?;
        for attr in attrs {
            write!(f, " [@{}]", attr)?;
        }
        write_field_or_variant_doc_comment(f, doc)?;
        Ok(())
    }
}

impl Display for ir::Type {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match self {
            Self::Path(ty) => ty.fmt(f),
            Self::Tuple(ty) => ty.fmt(f),
        }
    }
}

impl Display for ir::TypePath {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match self.targs.as_slice() {
            [] => {}
            [targ] => write!(f, "{} ", targ)?,
            [first, rest @ ..] => {
                write!(f, "({}", first)?;
                for targ in rest {
                    write!(f, ", {}", targ)?;
                }
                write!(f, ") ")?;
            }
        }
        for module in self.modules.iter() {
            write!(f, "{}.", module)?;
        }
        write!(f, "{}", self.ty)
    }
}

impl Display for ir::TypeTuple {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "(")?;
        let mut elems = self.elems.iter();
        let elem = elems.next().expect("empty TypeTuple");
        write!(f, "{elem}")?;
        for elem in elems {
            write!(f, " * {elem}")?;
        }
        write!(f, ")")
    }
}

impl Display for ir::ModuleName {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        let name = &self.0;
        let mut first_char = name.chars().next().unwrap(); // Invariant: self.0 is nonempty
        // OCaml modules _must_ start with an uppercase letter (the OCaml parser
        // depends on this). We ensure in `ModuleName`'s constructor that the
        // first character is ASCII, so we can use `make_ascii_uppercase`.
        first_char.make_ascii_uppercase();
        assert!(first_char.is_ascii_uppercase());
        write!(f, "{}", first_char)?;
        write!(f, "{}", &name[1..])
    }
}

fn is_ocaml_keyword(name: &str) -> bool {
    match name {
        "and" | "as" | "assert" | "asr" | "begin" | "class" | "constraint" | "do" | "done"
        | "downto" | "else" | "end" | "exception" | "external" | "false" | "for" | "fun"
        | "function" | "functor" | "if" | "in" | "include" | "inherit" | "initializer" | "land"
        | "lazy" | "let" | "lor" | "lsl" | "lsr" | "lxor" | "match" | "method" | "mod"
        | "module" | "mutable" | "new" | "nonrec" | "object" | "of" | "open" | "or" | "private"
        | "rec" | "sig" | "struct" | "then" | "to" | "true" | "try" | "type" | "val"
        | "virtual" | "when" | "while" | "with" => true,
        _ => false,
    }
}

impl Display for ir::TypeName {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        let name = self.0.to_case(Case::Snake);
        if is_ocaml_keyword(name.as_str()) {
            write!(f, "{}_", name)
        } else {
            name.fmt(f)
        }
    }
}

impl Display for ir::FieldName {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        let name = self.0.to_case(Case::Snake);
        if is_ocaml_keyword(name.as_str()) {
            write!(f, "{}_", name)
        } else {
            name.fmt(f)
        }
    }
}

fn write_toplevel_doc_comment(
    f: &mut std::fmt::Formatter<'_>,
    doc: &Vec<String>,
) -> std::fmt::Result {
    if doc.is_empty() {
        return Ok(());
    }
    write!(f, "(**{}", doc.join("\n *"))?;
    if doc.len() == 1 {
        if !doc[0].contains('\n') {
            write!(f, " ")?;
        }
    } else {
        write!(f, "\n ")?;
    }
    writeln!(f, "*)")?;
    Ok(())
}

fn write_field_or_variant_doc_comment(
    f: &mut std::fmt::Formatter<'_>,
    doc: &Vec<String>,
) -> std::fmt::Result {
    if doc.is_empty() {
        return Ok(());
    }
    let joined = doc.join("\n       *");
    write!(f, "(**{}", joined)?;
    if !joined.ends_with(' ') {
        write!(f, " ")?;
    }
    writeln!(f, "*)")?;
    Ok(())
}

fn write_type_parameters(f: &mut std::fmt::Formatter<'_>, tparams: &[String]) -> std::fmt::Result {
    match tparams {
        [] => {}
        [tparam] => write!(f, "'{} ", tparam.to_case(Case::Snake))?,
        [first, rest @ ..] => {
            write!(f, "('{}", first.to_case(Case::Snake))?;
            for tparam in rest {
                write!(f, ", '{} ", tparam.to_case(Case::Snake))?;
            }
            write!(f, ") ")?;
        }
    }
    Ok(())
}


// 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::ffi::OsStr;
use std::path::Path;
use std::path::PathBuf;

#[derive(Debug, clap::Parser)]
struct Opts {
    /// The directory containing snapshot test cases (.rs) and expected output
    /// files (.rs.exp).
    cases: PathBuf,

    /// The rust_to_ocaml binary.
    rust_to_ocaml_bin: PathBuf,

    /// Update the expected output files instead of testing that the actual
    /// output matches the expected.
    #[clap(long)]
    update_snapshots: bool,

    /// Arguments which will be passed to the rust_to_ocaml binary.
    rust_to_ocaml_args: Vec<PathBuf>,
}

const IN_EXT: &str = "rs";
const EXP_EXT: &str = "exp";

fn main() -> anyhow::Result<()> {
    let opts = <Opts as clap::Parser>::from_args();
    if opts.update_snapshots {
        update_snapshots(&opts)?;
    } else {
        run_test_cases(&opts)?;
    }
    Ok(())
}

fn get_test_cases(dir: &Path) -> impl Iterator<Item = PathBuf> + '_ {
    walkdir::WalkDir::new(dir)
        .sort_by_file_name()
        .into_iter()
        .map(|e| e.unwrap())
        .filter(|e| e.file_type().is_file())
        .filter(|e| e.path().extension().and_then(OsStr::to_str) == Some(IN_EXT))
        .map(|e| e.path().to_owned())
}

fn expected_output_path(test_case: &Path) -> PathBuf {
    assert_eq!(test_case.extension().and_then(OsStr::to_str), Some(IN_EXT));
    let mut path = test_case.to_owned();
    path.set_extension(format!("{IN_EXT}.{EXP_EXT}"));
    path
}

fn run_test_cases(opts: &Opts) -> anyhow::Result<()> {
    let mut failures = vec![];
    for path in get_test_cases(&opts.cases) {
        if let Err(diff) = run_test_case(opts, &path)? {
            let rel_path = path.strip_prefix(&opts.cases).unwrap_or(&path).to_owned();
            eprintln!("=== TEST FAILED: {} ==================", rel_path.display());
            eprintln!("{diff}");
            failures.push(rel_path);
        }
    }
    if !failures.is_empty() {
        anyhow::bail!("{} test cases failed: {:?}", failures.len(), failures);
    }
    Ok(())
}

fn run_test_case(opts: &Opts, test_case: &Path) -> anyhow::Result<Result<(), String>> {
    let exp_path = expected_output_path(test_case);
    let expected = std::fs::read_to_string(exp_path)?;
    let actual = rust_to_ocaml(opts, test_case)?;
    if expected == actual {
        Ok(Ok(()))
    } else {
        Ok(Err(similar::TextDiff::from_lines(&expected, &actual)
            .unified_diff()
            .context_radius(10)
            .header("expected", "actual")
            .to_string()))
    }
}

fn rust_to_ocaml(opts: &Opts, rust_file: &Path) -> anyhow::Result<String> {
    let mut cmd = std::process::Command::new(&opts.rust_to_ocaml_bin);
    for arg in &opts.rust_to_ocaml_args {
        cmd.arg(arg);
    }
    cmd.arg(rust_file);
    let output = cmd.output()?;
    let mut output_str = String::from_utf8(output.stdout)?;
    output_str.push_str(std::str::from_utf8(&output.stderr)?);
    Ok(output_str)
}

fn update_snapshots(opts: &Opts) -> anyhow::Result<()> {
    let test_cases: Vec<PathBuf> = get_test_cases(&opts.cases).collect();
    println!(
        "Updating snapshots for {} test cases in {}",
        test_cases.len(),
        opts.cases
            .canonicalize()
            .unwrap_or_else(|_| opts.cases.clone())
            .display(),
    );
    let mut num_updated = 0;
    for test_case in test_cases {
        let exp_path = expected_output_path(&test_case);
        let actual = rust_to_ocaml(opts, &test_case)?;
        if let Ok(expected) = std::fs::read_to_string(&exp_path) {
            if expected == actual {
                continue;
            }
        }
        let mut expected = std::fs::File::create(&exp_path)?;
        std::io::Write::write_all(&mut expected, actual.as_bytes())?;
        num_updated += 1;
    }
    println!("Updated {num_updated} snapshots.");
    Ok(())
}


// 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.

/// type X
#[rust_to_ocaml(attr = "deriving show")]
pub type X = A;

/// type Y
#[rust_to_ocaml(
    prefix = "y_",
    attr = r#"deriving visitors {
        variety = "iter";
        ancestors = ["iter_ab"];
    }"#
)]
pub struct Y {
    /// foo
    #[rust_to_ocaml(attr = "opaque")]
    #[rust_to_ocaml(attr = "visitors.opaque")]
    pub foo: A,
    /// bar
    pub bar: B,
}

/// type Visibility
#[rust_to_ocaml(prefix = "V", attr = "deriving eq, ord, show { with_path = false }")]
enum Visibility {
    /// Private
    #[rust_to_ocaml(attr = "visitors.name \"visibility_VPrivate\"")]
    Private,
    /// Public
    #[rust_to_ocaml(attr = r#"visitors.name "visibility_VPublic""#)]
    Public,
}


// 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.

pub type A = bool;


// 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.

/// Type A
pub type A = X;

/// Type B
/// is int
pub type B = X;

/// Type C has a fenced code block:
///
/// ```
/// function f(): int {
///   return 0;
/// }
/// ```
///
/// And an unfenced code block:
///
///     function g(): int {
///       return 0;
///     }
///
/// It should stay indented.
pub type C = X;

/** Type D has a multiline delimited comment:

```
function f(): int {
  return 0;
}
```

And an indented code block:

    ```
    function g(): int {
      return 0;
    }
    ```
*/
pub type D = X;

/// Records can have comments on the fields.
pub struct Record {
    /// The comments come after the field declaration in OCaml.
    pub foo: X,
    /// bar comment
    pub bar: X,
}

/// Variant types can have comments on each variant.
pub enum Variant {
    /// Again, the comments come after the variant declaration.
    /// Multiline comments are understood.
    Foo,
    /** Bar has a multiline delimited comment, even though it's
    unusual in Rust source. */
    Bar,
    /// Baz comment
    Baz { a: X, b: X },
    /// Qux is a struct-like variant with a long comment spanning
    /// multiple lines.
    #[rust_to_ocaml(prefix = "q_")]
    Qux { a: X, b: X },
}


// 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.

pub enum E {
    Foo((A, B)),
    Bar(Box<(A, B)>),
    #[rust_to_ocaml(inline_tuple)]
    Baz((A, B)),
    #[rust_to_ocaml(inline_tuple)]
    Qux(Box<(A, B)>),
}


// 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.

pub enum E {
    #[rust_to_ocaml(inline_tuple)]
    Foo(Box<A>),
}


// 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.

pub type A = i8;

pub type B = u8;

pub type C = i16;

pub type D = u16;

pub type E = i32;

pub type F = u32;

pub type G = i64;

pub type H = u64;

pub type I = i128;

pub type J = u128;

pub type K = isize;

pub type L = 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.

pub struct Foo {
    and: A,
    assert: A,
    asr: A,
    begin: A,
    class: A,
    constraint: A,
    done: A,
    downto: A,
    end: A,
    exception: A,
    external: A,
    fun: A,
    function: A,
    functor: A,
    include: A,
    inherit: A,
    initializer: A,
    land: A,
    lazy: A,
    lor: A,
    lsl: A,
    lsr: A,
    lxor: A,
    method: A,
    module: A,
    mutable: A,
    new: A,
    nonrec: A,
    object: A,
    of: A,
    open: A,
    or: A,
    private: A,
    rec: A,
    sig: A,
    then: A,
    to: A,
    val: A,
    when: A,
    with: A,
}

type A = And;
type A = As;
type A = Assert;
type A = Asr;
type A = Begin;
type A = Class;
type A = Constraint;
type A = Do;
type A = Done;
type A = Downto;
type A = Else;
type A = End;
type A = Exception;
type A = External;
type A = False;
type A = For;
type A = Fun;
type A = Function;
type A = Functor;
type A = If;
type A = In;
type A = Include;
type A = Inherit;
type A = Initializer;
type A = Land;
type A = Lazy;
type A = Let;
type A = Lor;
type A = Lsl;
type A = Lsr;
type A = Lxor;
type A = Match;
type A = Method;
type A = Mod;
type A = Module;
type A = Mutable;
type A = New;
type A = Nonrec;
type A = Object;
type A = Of;
type A = Open;
type A = Or;
type A = Private;
type A = Rec;
type A = Sig;
type A = Struct;
type A = Then;
type A = To;
type A = True;
type A = Try;
type A = Type;
type A = Val;
type A = Virtual;
type A = When;
type A = While;
type A = With;


// 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.

pub type MyVec = Vec<X>;
pub type BoxedSlice = Box<[X]>;
pub type Slice<'a> = &'a [X];

pub type StdVec = std::vec::Vec<X>;
pub type StdBoxedSlice = std::boxed::Box<[X]>;


// 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.

pub struct Foo(pub Bar, pub Bar);

#[rust_to_ocaml(and)]
pub struct Bar(pub Option<Foo>, pub Option<Foo>);


// 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.

pub enum Result<T, E> {
    Ok(T),
    Err(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.

#[rust_to_ocaml(name = "aa")] // ignored
type A = X;

#[rust_to_ocaml(name = "bb")] // ignored
struct B {
    #[rust_to_ocaml(name = "bb_x")]
    foo: x,
    #[rust_to_ocaml(name = "bb_y")]
    bar: y,
}

#[rust_to_ocaml(name = "cc")] // ignored
#[rust_to_ocaml(prefix = "C")]
enum C {
    #[rust_to_ocaml(name = "C_foo")]
    Foo,
    #[rust_to_ocaml(name = "Bar")]
    Bar {
        #[rust_to_ocaml(name = "bar_x")]
        foo: x,
        #[rust_to_ocaml(name = "bar_y")]
        bar: y,
    },
    Baz,
}

type a_alias = a;

type b_alias = b;

type c_alias = c;


// 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.

pub enum MyOption<A> {
    None,
    Some(A),
}


// 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.

pub type BoxA = Box<A>;
pub type RcA = Rc<A>;
pub type ArcA = Arc<A>;
pub type RcOcA = RcOc<A>;

pub type StdBoxA = std::boxed::Box<A>;
pub type StdRcA = std::rc::Rc<A>;
pub type StdArcA = std::sync::Arc<A>;
pub type OcamlrepRcOcA = ocamlrep::rc::RcOc<A>;

pub type BoxedTuple = Box<(Box<A>, Box<B>)>;


// 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.

pub type A = x::Foo;

pub type B = y::z::Foo;

pub type C = my_module::some_submodule::Foo;

pub type D = i_map::IMap<X>;


// 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.

pub struct MyStruct {
    pub foo: isize,
    pub bar: isize,
}

#[rust_to_ocaml(prefix = "a_")]
pub struct StructA {
    pub foo: isize,
    pub bar: isize,
}

#[rust_to_ocaml(prefix = "b_")]
pub struct StructB {
    pub foo: isize,
    pub bar: isize,
}


// 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.

type TupleA = (A, B);

type TupleB = (A, (B, C));


// 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.

pub struct A;

pub struct B();

pub struct C(());

pub struct D(pub X);

pub struct E(pub Y, pub Z);


// 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.

pub type MaybeA = Option<A>;

pub type MyResult = Result<B, C>;

pub type Bar = foo::Bar<D, E, F>;


// 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.

pub type A = pos::Pos;

pub type B = crate::relative_path::RelativePath;

pub type C = collections::s_set::SSet;

pub type TypeNameMatchesModuleName = D;

pub mod foo {
    pub type Foo = E;
    pub type Maybe = Option<Foo>;
}


// 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.

pub enum A {
    I,
    J(isize),
    K(isize, isize),
    L((isize, isize)),
    M { x: isize, y: isize },
}

#[rust_to_ocaml(prefix = "P")]
pub enum Prefixed {
    I,
    J(isize),
    K(isize, isize),
    L((isize, isize)),
    #[rust_to_ocaml(prefix = "m_")]
    M {
        x: isize,
        y: isize,
    },
}


// 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.

#![allow(unused_crate_dependencies)]

fn main() {}


// 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 bstr::ByteSlice;
use once_cell::sync::Lazy;
use regex::bytes::Regex;

/// This crate is a port of hphp/hack/src/utils/signed_source.ml, which was
/// based on a historical version of fbsource/tools/signedsource.py.

/// The signing token, which you must embed in the file you wish to sign.
/// Generally, you should put this in a header comment.
pub static SIGNING_TOKEN: &str = concat!(
    "@",
    "generated",
    " ",
    "<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>"
);

/// Sign a source file into which you have previously embedded a signing token.
/// Signing modifies only the signing token, so the semantics of the file will
/// not change if the token is put in a comment.
///
/// Returns `TokenNotFoundError` if no signing token is present.
pub fn sign_file(data: &[u8]) -> Result<Vec<u8>, TokenNotFoundError> {
    let data = SIGN_OR_OLD_TOKEN.replace_all(data, TOKEN.as_bytes());
    if !data.contains_str(TOKEN) {
        return Err(TokenNotFoundError);
    }
    let signature = format!("SignedSource<<{}>>", hash(&data));
    Ok(TOKEN_REGEX
        .replace_all(&data, signature.as_bytes())
        .into_owned())
}

/// Sign a UTF-8 source file into which you have previously embedded a signing
/// token. Signing modifies only the signing token, so the semantics of the file
/// will not change if the token is put in a comment.
///
/// Returns `TokenNotFoundError` if no signing token is present.
pub fn sign_utf8_file(data: &str) -> Result<String, TokenNotFoundError> {
    let data = sign_file(data.as_bytes())?;
    // SAFETY: `data` was a valid `&str` before signing, and signing only
    // replaces ASCII characters with other ASCII characters.
    unsafe { Ok(String::from_utf8_unchecked(data)) }
}

/// Determine whether a file is signed. This does NOT verify the signature.
pub fn is_signed(data: &[u8]) -> bool {
    SIGNING_REGEX.is_match(data)
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SignCheckResponse {
    Ok,
    Unsigned,
    Invalid,
}

/// Verify a file's signature.
pub fn verify_signature(data: &[u8]) -> SignCheckResponse {
    let expected_md5 = match SIGNING_REGEX.captures(data) {
        None => return SignCheckResponse::Unsigned,
        Some(caps) => match caps.get(1) {
            None => return SignCheckResponse::Unsigned,
            Some(cap) => cap.as_bytes(),
        },
    };
    for tok in [TOKEN, OLD_TOKEN] {
        let replacement = make_signing_token(tok);
        let unsigned_data = SIGNING_REGEX.replace_all(data, replacement.as_bytes());
        let actual_md5 = hash(&unsigned_data);
        if expected_md5 == actual_md5.as_bytes() {
            return SignCheckResponse::Ok;
        }
    }
    SignCheckResponse::Invalid
}

static TOKEN: &str = "<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>";

/// This old token was historically used as the signing token. It was replaced
/// because it is 2 characters shorter than the final signature, and as a result,
/// signing data with the old token forced the entire string to be rewritten
/// (everything after the token needs to be shifted forwards 2 bytes).
/// In this implementation, we rewrite the entire string anyway.
static OLD_TOKEN: &str = "<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@I>>";

fn make_signing_token(token: &str) -> String {
    format!("@{} {}", "generated", token)
}

static SIGNATURE_RE: &str = r"SignedSource<<([a-f0-9]+)>>";

static SIGN_OR_OLD_TOKEN: Lazy<Regex> =
    Lazy::new(|| Regex::new(&format!("{}|{}", SIGNATURE_RE, regex::escape(OLD_TOKEN))).unwrap());

static SIGNING_REGEX: Lazy<Regex> =
    Lazy::new(|| Regex::new(&make_signing_token(SIGNATURE_RE)).unwrap());

static TOKEN_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(&regex::escape(TOKEN)).unwrap());

fn hash(data: &[u8]) -> String {
    use md5::Digest;
    let mut digest = md5::Md5::new();
    digest.update(data);
    hex::encode(digest.finalize())
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("Failed to sign file: input does not contain signing token")]
pub struct TokenNotFoundError;

#[cfg(test)]
mod test {
    use super::is_signed;
    use super::make_signing_token;
    use super::sign_utf8_file;
    use super::verify_signature;
    use super::SignCheckResponse;
    use super::TokenNotFoundError;
    use super::SIGNING_TOKEN;
    use super::TOKEN;

    static NO_TOKEN: &str = concat!("// @", "generated\nfn foo() {}");
    static INVALID: &str = concat!(
        "// @",
        "generated SignedSource<<48ab1081d9394843f184debf0b251a18>>\nfn foo() {}"
    );
    static UNSIGNED: &str = concat!(
        "// @",
        "generated <<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>\nfn foo() {}"
    );
    // Below signature was manually verified to be equal to the OCaml
    // Signed_source output for `UNSIGNED`.
    static SIGNED: &str = concat!(
        "// @",
        "generated SignedSource<<38ab1081d9394843f184debf0b251a18>>\nfn foo() {}"
    );

    #[test]
    fn test_signing_token() {
        // We use `concat!` so that `SIGNING_TOKEN` can be a `&str` rather than
        // a `Lazy`, since `make_signing_token` can't be a `const fn` yet.
        // Verify that we're producing the same result.
        assert_eq!(SIGNING_TOKEN, make_signing_token(TOKEN))
    }

    #[test]
    fn test_sign_utf8_file() {
        assert_eq!(sign_utf8_file(UNSIGNED), Ok(SIGNED.to_owned()));
        assert_eq!(sign_utf8_file(SIGNED), Ok(SIGNED.to_owned()));
        assert_eq!(sign_utf8_file(NO_TOKEN), Err(TokenNotFoundError));
    }

    #[test]
    fn test_is_signed() {
        assert!(is_signed(SIGNED.as_bytes()));
        assert!(is_signed(INVALID.as_bytes())); // `is_signed` doesn't validate
        assert!(!is_signed(NO_TOKEN.as_bytes()));
        assert!(!is_signed(UNSIGNED.as_bytes()));
    }

    #[test]
    fn test_verify_signature() {
        assert_eq!(verify_signature(SIGNED.as_bytes()), SignCheckResponse::Ok);
        assert_eq!(
            verify_signature(INVALID.as_bytes()),
            SignCheckResponse::Invalid
        );
        assert_eq!(
            verify_signature(NO_TOKEN.as_bytes()),
            SignCheckResponse::Unsigned
        );
        assert_eq!(
            verify_signature(UNSIGNED.as_bytes()),
            SignCheckResponse::Unsigned
        );
    }
}


use std::path::Path;
extern crate cc;

fn main() {
    let src_dir = Path::new("src");

    let mut c_config = cc::Build::new();
    c_config.include(&src_dir);
    c_config
        .flag_if_supported("-Wno-unused-parameter")
        .flag_if_supported("-Wno-unused-but-set-variable")
        .flag_if_supported("-Wno-trigraphs");
    let parser_path = src_dir.join("parser.c");
    c_config.file(&parser_path);
    println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
    c_config.compile("parser");

    let mut cpp_config = cc::Build::new();
    cpp_config.cpp(true);
    cpp_config.include(&src_dir);
    cpp_config
        .flag_if_supported("-Wno-unused-parameter")
        .flag_if_supported("-Wno-unused-but-set-variable");
    let scanner_path = src_dir.join("scanner.cc");
    cpp_config.file(&scanner_path);
    println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
    cpp_config.compile("scanner");
}


// -*- coding: utf-8 -*-
// ------------------------------------------------------------------------------------------------
// Copyright Â© 2020, tree-sitter-python authors.
// See the LICENSE file in this repo for license details.
// ------------------------------------------------------------------------------------------------

//! This crate provides a Python grammar for the [tree-sitter][] parsing library.
//!
//! Typically, you will use the [language][language func] function to add this grammar to a
//! tree-sitter [Parser][], and then use the parser to parse some code:
//!
//! ```
//! use tree_sitter::Parser;
//!
//! let code = r#"
//!     def double(x):
//!         return x * 2
//! "#;
//! let mut parser = Parser::new();
//! parser.set_language(tree_sitter_python::language()).expect("Error loading Python grammar");
//! let parsed = parser.parse(code, None);
//! # let parsed = parsed.unwrap();
//! # let root = parsed.root_node();
//! # assert!(!root.has_error());
//! ```
//!
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
//! [language func]: fn.language.html
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/

use tree_sitter::Language;

extern "C" {
    fn tree_sitter_python() -> Language;
}

/// Returns the tree-sitter [Language][] for this grammar.
///
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
pub fn language() -> Language {
    unsafe { tree_sitter_python() }
}

/// The source of the Python tree-sitter grammar description.
pub const GRAMMAR: &'static str = include_str!("../../grammar.js");

/// The syntax highlighting query for this language.
pub const HIGHLIGHT_QUERY: &'static str = include_str!("../../queries/highlights.scm");

/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json");

/// The symbol tagging query for this language.
pub const TAGGING_QUERY: &'static str = include_str!("../../queries/tags.scm");

#[cfg(test)]
mod tests {
    #[test]
    fn can_load_grammar() {
        let mut parser = tree_sitter::Parser::new();
        parser
            .set_language(super::language())
            .expect("Error loading Python grammar");
    }
}