diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index be57730bb4..cb46800afb 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -173,7 +173,15 @@ jobs: command: test args: --features docs - - name: FFI test (C & C#?) + - name: Install LuaJIT and LuaRocks (Ubuntu) + run: sudo apt-get install -y luajit + if: matrix.os == 'ubuntu-latest' + + - name: Install LuaJIT and LuaRocks (macOS) + run: brew install luajit + if: matrix.os == 'macos-latest' + + - name: FFI test (C & C# & Lua?) run: make -C ffi_tests if: runner.os != 'Windows' env: diff --git a/ffi_tests/generated.cffi b/ffi_tests/generated.cffi index 2a28e32bb4..620daa3bde 100644 --- a/ffi_tests/generated.cffi +++ b/ffi_tests/generated.cffi @@ -20,6 +20,20 @@ typedef struct AnUnusedStruct { Wow_t are_you_still_there; } AnUnusedStruct_t; +typedef struct { + float idx[3]; +} float_3_array_t; + +typedef struct { + size_t idx[5]; +} size_5_array_t; + +typedef struct ArraysStruct { + float_3_array_t floats; + + size_5_array_t sizes; +} ArraysStruct_t; + #define FOO ... typedef enum Bar { @@ -36,6 +50,38 @@ typedef struct next_generation { void * (*cb)(bool); } next_generation_t; +typedef struct { + uint8_t idx[1]; +} uint8_1_array_t; + +typedef struct ConstGenericStruct_uint8_1 { + uint8_1_array_t data; +} ConstGenericStruct_uint8_1_t; + +typedef struct { + uint8_t idx[2]; +} uint8_2_array_t; + +typedef struct ConstGenericStruct_uint8_2 { + uint8_2_array_t data; +} ConstGenericStruct_uint8_2_t; + +typedef struct { + uint16_t idx[3]; +} uint16_3_array_t; + +typedef struct ConstGenericStruct_uint16_3 { + uint16_3_array_t data; +} ConstGenericStruct_uint16_3_t; + +typedef struct SpecificConstGenericContainer { + ConstGenericStruct_uint8_1_t field1; + + ConstGenericStruct_uint8_2_t field2; + + ConstGenericStruct_uint16_3_t field3; +} SpecificConstGenericContainer_t; + typedef enum triforce { TRIFORCE_DIN, TRIFORCE_FARORE, diff --git a/ffi_tests/generated.cs b/ffi_tests/generated.cs index 0d89dde0b6..3d0864e7c7 100644 --- a/ffi_tests/generated.cs +++ b/ffi_tests/generated.cs @@ -33,6 +33,25 @@ public unsafe struct AnUnusedStruct_t { public Wow_t are_you_still_there; } +[StructLayout(LayoutKind.Sequential, Size = 12)] +public unsafe struct float_3_array_t { + public float _0; + public float _1; + public float _2; +} + +[StructLayout(LayoutKind.Sequential, Size = 40)] +public unsafe struct size_5_array_t { + public fixed UIntPtr arr[5]; +} + +[StructLayout(LayoutKind.Sequential, Size = 56)] +public unsafe struct ArraysStruct_t { + public float_3_array_t floats; + + public size_5_array_t sizes; +} + public unsafe partial class Ffi { public const Int32 FOO = 42; } @@ -66,6 +85,45 @@ public unsafe struct next_generation_t { public void_ptr_bool_fptr cb; } +[StructLayout(LayoutKind.Sequential, Size = 1)] +public unsafe struct uint8_1_array_t { + public fixed byte arr[1]; +} + +[StructLayout(LayoutKind.Sequential, Size = 1)] +public unsafe struct ConstGenericStruct_uint8_1_t { + public uint8_1_array_t data; +} + +[StructLayout(LayoutKind.Sequential, Size = 2)] +public unsafe struct uint8_2_array_t { + public fixed byte arr[2]; +} + +[StructLayout(LayoutKind.Sequential, Size = 2)] +public unsafe struct ConstGenericStruct_uint8_2_t { + public uint8_2_array_t data; +} + +[StructLayout(LayoutKind.Sequential, Size = 6)] +public unsafe struct uint16_3_array_t { + public fixed UInt16 arr[3]; +} + +[StructLayout(LayoutKind.Sequential, Size = 6)] +public unsafe struct ConstGenericStruct_uint16_3_t { + public uint16_3_array_t data; +} + +[StructLayout(LayoutKind.Sequential, Size = 10)] +public unsafe struct SpecificConstGenericContainer_t { + public ConstGenericStruct_uint8_1_t field1; + + public ConstGenericStruct_uint8_2_t field2; + + public ConstGenericStruct_uint16_3_t field3; +} + /// /// Hello, World! /// diff --git a/ffi_tests/generated.h b/ffi_tests/generated.h index 65b74c4136..b1898a457d 100644 --- a/ffi_tests/generated.h +++ b/ffi_tests/generated.h @@ -39,6 +39,23 @@ typedef struct AnUnusedStruct { Wow_t are_you_still_there; } AnUnusedStruct_t; +typedef struct { + float idx[3]; +} float_3_array_t; + +typedef struct { + size_t idx[5]; +} size_5_array_t; + +/** */ +typedef struct ArraysStruct { + /** */ + float_3_array_t floats; + + /** */ + size_5_array_t sizes; +} ArraysStruct_t; + /** */ #define FOO ((int32_t) 42) @@ -76,6 +93,48 @@ typedef struct next_generation { void * (*cb)(bool); } next_generation_t; +typedef struct { + uint8_t idx[1]; +} uint8_1_array_t; + +/** */ +typedef struct ConstGenericStruct_uint8_1 { + /** */ + uint8_1_array_t data; +} ConstGenericStruct_uint8_1_t; + +typedef struct { + uint8_t idx[2]; +} uint8_2_array_t; + +/** */ +typedef struct ConstGenericStruct_uint8_2 { + /** */ + uint8_2_array_t data; +} ConstGenericStruct_uint8_2_t; + +typedef struct { + uint16_t idx[3]; +} uint16_3_array_t; + +/** */ +typedef struct ConstGenericStruct_uint16_3 { + /** */ + uint16_3_array_t data; +} ConstGenericStruct_uint16_3_t; + +/** */ +typedef struct SpecificConstGenericContainer { + /** */ + ConstGenericStruct_uint8_1_t field1; + + /** */ + ConstGenericStruct_uint8_2_t field2; + + /** */ + ConstGenericStruct_uint16_3_t field3; +} SpecificConstGenericContainer_t; + /** \brief * Hello, `World`! */ diff --git a/ffi_tests/generated.lua b/ffi_tests/generated.lua new file mode 100644 index 0000000000..19468fd97b --- /dev/null +++ b/ffi_tests/generated.lua @@ -0,0 +1,332 @@ +-- File auto-generated by `::safer_ffi`. +-- +-- Do not manually edit this file. + +local ffi = require "ffi" + +ffi.cdef [[ + +// +// enum has the same ABI as `uint8_t` +typedef enum Wow { + // + WOW_LEROY, + // + WOW_JENKINS, +}; typedef uint8_t Wow_t; + +// +typedef struct AnUnusedStruct { + // + Wow_t are_you_still_there; +} AnUnusedStruct_t; + +// +typedef struct ArraysStruct { + // + float floats[3]; + + // + size_t sizes[5]; +} ArraysStruct_t; + +// +static const int32_t FOO = 42; + +// +// enum has the same ABI as `int8_t` +typedef enum Bar { + // + BAR_A = 43, + // + BAR_B = 42, +}; typedef int8_t Bar_t; + +// Hello, `World`! +typedef struct next_generation { + // I test some `gen`-eration. + Bar_t gen; + + // with function pointers and everything! + void * (*cb)(bool); +} next_generation_t; + +// +typedef struct ConstGenericStruct_uint8_1 { + // + uint8_t data[1]; +} ConstGenericStruct_uint8_1_t; + +// +typedef struct ConstGenericStruct_uint8_2 { + // + uint8_t data[2]; +} ConstGenericStruct_uint8_2_t; + +// +typedef struct ConstGenericStruct_uint16_3 { + // + uint16_t data[3]; +} ConstGenericStruct_uint16_3_t; + +// +typedef struct SpecificConstGenericContainer { + // + ConstGenericStruct_uint8_1_t field1; + + // + ConstGenericStruct_uint8_2_t field2; + + // + ConstGenericStruct_uint16_3_t field3; +} SpecificConstGenericContainer_t; + +// Hello, `World`! +// enum has the same ABI as `uint8_t` +typedef enum triforce { + // + TRIFORCE_DIN = 3, + // + TRIFORCE_FARORE = 1, + // + TRIFORCE_NARYU, +}; typedef uint8_t triforce_t; + +// https://github.com/getditto/safer_ffi/issues/45 +int32_t +_issue_45 ( + int32_t __arg_0); + +// +int32_t +async_get_ft (void); + +// `Arc Ret>` +typedef struct ArcDynFn0_void { + // + void * env_ptr; + + // + void (*call)(void *); + + // + void (*release)(void *); + + // + void (*retain)(void *); +} ArcDynFn0_void_t; + +// +void +call_in_the_background ( + ArcDynFn0_void_t f); + +// This is a `#[repr(C)]` enum, which leads to a classic enum def. +typedef enum SomeReprCEnum { + // This is some variant. + SOME_REPR_C_ENUM_SOME_VARIANT, +} SomeReprCEnum_t; + +// +void +check_SomeReprCEnum ( + SomeReprCEnum_t _baz); + +// +void +check_bar ( + Bar_t _bar); + +// Concatenate the two input strings into a new one. +// +// The returned string must be freed using `free_char_p`. +char * +concat ( + char const * fst, + char const * snd); + +// Frees a string created by `concat`. +void +free_char_p ( + char * _string); + +// +typedef struct foo foo_t; + +// +void +free_foo ( + foo_t * foo); + +// `&'lt [T]` but with a guaranteed `#[repr(C)]` layout. +// +// # C layout (for some given type T) +// +// ```c +// typedef struct { +// // Cannot be NULL +// T * ptr; +// size_t len; +// } slice_T; +// ``` +// +// # Nullable pointer? +// +// If you want to support the above typedef, but where the `ptr` field is +// allowed to be `NULL` (with the contents of `len` then being undefined) +// use the `Option< slice_ptr<_> >` type. +typedef struct slice_ref_int32 { + // Pointer to the first element (if any). + int32_t const * ptr; + + // Element count + size_t len; +} slice_ref_int32_t; + +// Returns a pointer to the maximum integer of the input slice, or `NULL` if +// it is empty. +int32_t const * +max ( + slice_ref_int32_t xs); + +// +foo_t * +new_foo (void); + +// +int32_t +read_foo ( + foo_t const * foo); + +// +uint16_t (* +returns_a_fn_ptr (void))(uint8_t); + +// The layout of `core::task::wake::Context` is opaque/subject to changes. +typedef struct Opaque_Context Opaque_Context_t; + +// +ArcDynFn0_void_t +rust_future_task_context_get_waker ( + Opaque_Context_t const * task_context); + +// +void +rust_future_task_context_wake ( + Opaque_Context_t const * task_context); + +// +typedef struct Erased Erased_t; + +// An FFI-safe `Poll<()>`. +// enum has the same ABI as `int8_t` +typedef enum PollFuture { + // + POLL_FUTURE_COMPLETED = 0, + // + POLL_FUTURE_PENDING = -1, +}; typedef int8_t PollFuture_t; + +// +typedef struct FfiFutureVTable { + // + void (*release_vptr)(Erased_t *); + + // + PollFuture_t (*dyn_poll)(Erased_t *, Opaque_Context_t *); +} FfiFutureVTable_t; + +// +typedef struct VirtualPtr__Erased_ptr_FfiFutureVTable { + // + Erased_t * ptr; + + // + FfiFutureVTable_t vtable; +} VirtualPtr__Erased_ptr_FfiFutureVTable_t; + +// `Box Ret>` +typedef struct BoxDynFnMut0_void { + // + void * env_ptr; + + // + void (*call)(void *); + + // + void (*free)(void *); +} BoxDynFnMut0_void_t; + +// +typedef struct DropGlueVTable { + // + void (*release_vptr)(Erased_t *); +} DropGlueVTable_t; + +// +typedef struct VirtualPtr__Erased_ptr_DropGlueVTable { + // + Erased_t * ptr; + + // + DropGlueVTable_t vtable; +} VirtualPtr__Erased_ptr_DropGlueVTable_t; + +// +typedef struct FfiFutureExecutorVTable { + // + void (*release_vptr)(Erased_t *); + + // + Erased_t * (*retain_vptr)(Erased_t const *); + + // + VirtualPtr__Erased_ptr_FfiFutureVTable_t (*dyn_spawn)(Erased_t const *, VirtualPtr__Erased_ptr_FfiFutureVTable_t); + + // + VirtualPtr__Erased_ptr_FfiFutureVTable_t (*dyn_spawn_blocking)(Erased_t const *, BoxDynFnMut0_void_t); + + // + void (*dyn_block_on)(Erased_t const *, VirtualPtr__Erased_ptr_FfiFutureVTable_t); + + // + VirtualPtr__Erased_ptr_DropGlueVTable_t (*dyn_enter)(Erased_t const *); +} FfiFutureExecutorVTable_t; + +// +typedef struct VirtualPtr__Erased_ptr_FfiFutureExecutorVTable { + // + Erased_t * ptr; + + // + FfiFutureExecutorVTable_t vtable; +} VirtualPtr__Erased_ptr_FfiFutureExecutorVTable_t; + +// +int32_t +test_spawner ( + VirtualPtr__Erased_ptr_FfiFutureExecutorVTable_t executor); + +// `&'lt mut (dyn 'lt + Send + FnMut(A1) -> Ret)` +typedef struct RefDynFnMut1_void_char_const_ptr { + // + void * env_ptr; + + // + void (*call)(void *, char const *); +} RefDynFnMut1_void_char_const_ptr_t; + +// Same as `concat`, but with a callback-based API to auto-free the created +// string. +void +with_concat ( + char const * fst, + char const * snd, + RefDynFnMut1_void_char_const_ptr_t cb); + +// +bool +with_foo ( + void (*cb)(foo_t *)); + +]] \ No newline at end of file diff --git a/ffi_tests/src/lib.rs b/ffi_tests/src/lib.rs index 74ddc58a4e..e869ca262d 100644 --- a/ffi_tests/src/lib.rs +++ b/ffi_tests/src/lib.rs @@ -211,13 +211,44 @@ pub struct AnUnusedStruct { are_you_still_there: Wow, } +#[ffi_export] +#[derive_ReprC] +#[repr(C)] +pub struct ArraysStruct { + floats: [f32; 3], + sizes: [usize; 5], +} + +#[derive_ReprC] +#[repr(C)] +pub struct ConstGenericStruct { + data: [T; M] +} + +#[ffi_export] +#[derive_ReprC] +#[repr(C)] +pub struct SpecificConstGenericContainer { + field1: ConstGenericStruct<1, u8>, + field2: ConstGenericStruct<2, u8>, + field3: ConstGenericStruct<3, u16>, +} + #[safer_ffi::cfg_headers] #[test] fn generate_headers () -> ::std::io::Result<()> { use ::safer_ffi::headers::Language::*; - for &(language, ext) in &[(C, "h"), (CSharp, "cs"), (Python, "cffi")] { + + let languages = &[ + (C, "h"), + (CSharp, "cs"), + (Lua, "lua"), + (Python, "cffi"), + ]; + + for &(language, ext) in languages { let builder = ::safer_ffi::headers::builder() .with_language(language) diff --git a/ffi_tests/tests/lua/generated.lua b/ffi_tests/tests/lua/generated.lua new file mode 120000 index 0000000000..301e861214 --- /dev/null +++ b/ffi_tests/tests/lua/generated.lua @@ -0,0 +1 @@ +../../generated.lua \ No newline at end of file diff --git a/ffi_tests/tests/lua/tests.lua b/ffi_tests/tests/lua/tests.lua new file mode 100644 index 0000000000..eae4ba7681 --- /dev/null +++ b/ffi_tests/tests/lua/tests.lua @@ -0,0 +1,120 @@ +local ffi = require("ffi") +local generated = require("generated") +local lib = ffi.load "libffi_tests" + +function test_concat() + local s1 = "Hello, " + local s2 = "World!" + local p = lib.concat(s1, s2); + local res = ffi.string(p) + lib.free_char_p(p); + assert(res == s1 .. s2) +end + +function test_max() + local arr = { -27, -42, 9, -8 } + local c_arr = ffi.new("int32_t[?]", #arr, arr) + local c_slice = ffi.new("slice_ref_int32_t", c_arr, #arr) + local p = lib.max(c_slice); + assert(p ~= ffi.NULL) + assert(p[0] == 9) +end + +function test_max_empty() + local arr = {} + local c_arr = ffi.new("int32_t[?]", #arr, arr) + local c_slice = ffi.new("slice_ref_int32_t", c_arr, #arr) + local p = lib.max(c_slice); + assert(p == ffi.NULL) +end + +function test_foo() + local foo = lib.new_foo() + assert(lib.read_foo(foo) == 42) + local called = false; + lib.with_foo(function() + assert(lib.read_foo(foo) == 42) + called = true + end) + assert(called == true) + lib.free_foo(foo) + lib.free_foo(nil) +end + +function test_constant() + assert(lib.FOO == 42) +end + +function test_currified_thing() + assert(lib.returns_a_fn_ptr()(0x42) == 0x4200) +end + +function test_enum_int_constant() + assert(lib.TRIFORCE_DIN == 3) + assert(lib.TRIFORCE_FARORE == 1) + assert(lib.TRIFORCE_NARYU == 2) +end + +function test_arrays_struct() + local a = ffi.new("ArraysStruct_t") + a.floats = ffi.new("float[3]", {7, 8, 9}) + a.sizes = ffi.new("size_t[5]", {5, 4, 3, 2, 1}) + + assert(a.floats[0] == 7) + assert(a.floats[1] == 8) + assert(a.floats[2] == 9) + + assert(a.sizes[0] == 5) + assert(a.sizes[1] == 4) + assert(a.sizes[2] == 3) + assert(a.sizes[3] == 2) + assert(a.sizes[4] == 1) +end + +function test_const_generics_struct() + local a = ffi.new("SpecificConstGenericContainer_t") + a.field1 = ffi.new("ConstGenericStruct_uint8_1_t", ffi.new("uint8_t[1]", {1})) + a.field2 = ffi.new("ConstGenericStruct_uint8_2_t", ffi.new("uint8_t[2]", {1, 2})) + a.field3 = ffi.new("ConstGenericStruct_uint16_3_t", ffi.new("uint16_t[3]", {1, 2, 3})) + + assert(a.field1.data[0] == 1) + assert(a.field2.data[0] == 1) + assert(a.field2.data[1] == 2) + assert(a.field3.data[0] == 1) + assert(a.field3.data[1] == 2) + assert(a.field3.data[2] == 3) +end + +function run_tests() + local tests = { + test_concat, + test_max, + test_max_empty, + test_foo, + test_constant, + test_currified_thing, + test_enum_int_constant, + test_arrays_struct, + test_const_generics_struct, + } + + local passed = 0 + local failed = 0 + for _, test in ipairs(tests) do + local status, err = pcall(test) + if status then + passed = passed + 1 + else + failed = failed + 1 + print("Failed: " .. err) + end + end + + print(string.format("Passed: %d, Failed: %d", passed, failed)) + + if failed > 0 then + os.exit(1) + end +end + +run_tests() \ No newline at end of file diff --git a/ffi_tests/tests/main.rs b/ffi_tests/tests/main.rs index abb8bd4b21..e644be0820 100644 --- a/ffi_tests/tests/main.rs +++ b/ffi_tests/tests/main.rs @@ -81,3 +81,20 @@ fn test_csharp_code () .success() ); } + +#[test] +fn test_lua_code() +{ + let output = ::std::process::Command::new("luajit") + .current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/lua")) + .arg("tests.lua") + .output() + .expect("Failed to run Lua tests"); + + assert!( + output.status.success(), + "Lua tests failed with output:\n\nSTDOUT:\n{}\n\nSTDERR:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} diff --git a/src/c_char.rs b/src/c_char.rs index 404ef10d29..8bb88841e3 100644 --- a/src/c_char.rs +++ b/src/c_char.rs @@ -87,6 +87,15 @@ impl LegacyCType "byte".into() } } + + __cfg_lua__! { + fn lua_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + } } type OPAQUE_KIND = crate::layout::OpaqueKind::Concrete; } from_CType_impl_ReprC! { diff --git a/src/headers/_mod.rs b/src/headers/_mod.rs index c1f7cc6c9b..675f3b3d17 100644 --- a/src/headers/_mod.rs +++ b/src/headers/_mod.rs @@ -343,7 +343,15 @@ impl Builder<'_, WhereTo> { fn write_banner (&'_ self, definer: &'_ mut dyn Definer) -> io::Result<()> { - let banner: &'_ str = self.banner.unwrap_or(concat!( + let lang = self.language.unwrap_or(Language::C); + + let banner: &'_ str = self.banner.unwrap_or(match lang { + Language::Lua => concat!( + "-- File auto-generated by `::safer_ffi`.\n", + "--\n", + "-- Do not manually edit this file.\n", + ), + _ => concat!( "/*! \\file */\n", "/*******************************************\n", " * *\n", @@ -352,7 +360,9 @@ impl Builder<'_, WhereTo> { " * Do not manually edit this file. *\n", " * *\n", " *******************************************/\n", - )); + ), + }); + writeln!(definer.out(), "{}", banner) } @@ -375,6 +385,8 @@ impl Builder<'_, WhereTo> { RustLib = Self::lib_name(), ), + | Language::Lua => writeln!(definer.out(), include_str!("templates/lua/_prelude.lua")), + #[cfg(feature = "python-headers")] // CHECKME | Language::Python => Ok(()), @@ -387,6 +399,13 @@ impl Builder<'_, WhereTo> { { let stable_header = self.stable_header.unwrap_or(true); let lang = self.language.unwrap_or(Language::C); + + // skip adding int/bool headers for Lua + if lang == Language::Lua { + definer.insert("__int_headers__"); + definer.insert("bool"); + } + let _naming_convention = self.naming_convention .as_ref() @@ -434,6 +453,11 @@ impl Builder<'_, WhereTo> { PkgName = pkg_name, ) }, + + | Language::Lua => { + write!(definer.out(), include_str!("templates/lua/epilogue.lua")) + }, + #[cfg(feature = "python-headers")] // CHECKME | Language::Python => Ok(()), @@ -502,6 +526,10 @@ enum Language { /// C# CSharp, + + /// Lua + Lua, + /// Python (experimental). #[cfg(feature = "python-headers")] Python, @@ -530,6 +558,9 @@ hidden_export! { | Language::CSharp => { ::define_self(&crate::headers::languages::CSharp, definer) }, + | Language::Lua => { + ::define_self(&crate::headers::languages::Lua, definer) + }, #[cfg(feature = "python-headers")] | Language::Python => { ::define_self(&crate::headers::languages::Python, definer) @@ -557,6 +588,7 @@ fn __define_fn__ ( let dyn_lang: &dyn HeaderLanguage = match lang { | Language::C => &languages::C, | Language::CSharp => &languages::CSharp, + | Language::Lua => &languages::Lua, #[cfg(feature = "python-headers")] | Language::Python => &languages::Python, }; @@ -593,6 +625,11 @@ hidden_export! { | Language::CSharp => write!(out, "{} (", f_name.trim(), ), + + | Language::Lua => write!(out, + "{} (", f_name.trim(), + ), + #[cfg(feature = "python-headers")] | Language::Python => write!(out, "{} (", f_name.trim(), @@ -627,6 +664,12 @@ hidden_export! { .unwrap_or("") , ), + + | Language::Lua => write!(out, + "\n {}", + Arg::CLayout::name_wrapping_var(&crate::headers::languages::Lua, arg_name), + ), + #[cfg(feature = "python-headers")] | Language::Python => write!(out, "\n {}", @@ -673,6 +716,17 @@ hidden_export! { , ) }, + + | Language::Lua => { + if fname_and_args.ends_with("(") { + fname_and_args.push_str("void"); + } + writeln!(out, + "{});\n", + Ret::CLayout::name_wrapping_var(&crate::headers::languages::Lua, &fname_and_args), + ) + }, + #[cfg(feature = "python-headers")] | Language::Python => { if fname_and_args.ends_with("(") { diff --git a/src/headers/languages/lua.rs b/src/headers/languages/lua.rs new file mode 100644 index 0000000000..88f939e05c --- /dev/null +++ b/src/headers/languages/lua.rs @@ -0,0 +1,226 @@ +#![cfg_attr(rustfmt, rustfmt::skip)] + +use super::*; + +pub struct Lua; + +impl HeaderLanguage for Lua { + fn emit_docs ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + indent: &'_ Indentation, + ) -> io::Result<()> + { + mk_out!(indent, ctx.out()); + + if docs.is_empty() { + out!(("// ")); + return Ok(()); + } + + for line in docs.iter().copied().map(str::trim) { + let sep = if line.is_empty() { "" } else { " " }; + out!(("//{sep}{line}")); + } + + Ok(()) + } + + fn emit_simple_enum ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + backing_integer: Option<&dyn PhantomCType>, + variants: &'_ [EnumVariant<'_>], + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, ctx.out()); + + let ref intn_t = + backing_integer.map(|it| it.name(self)) + ; + + self.emit_docs(ctx, docs, indent)?; + + let ref short_name = self_ty.short_name(); + let ref full_ty_name = self_ty.name(self); + + if let Some(intn_t) = intn_t { + out!(( + "// enum has the same ABI as `{intn_t}`" + "typedef enum {short_name} {{" + )); + } else { + out!(("typedef enum {short_name} {{")); + } + + if let _ = indent.scope() { + for v in variants { + self.emit_docs(ctx, v.docs, indent)?; + let variant_name = crate::utils::screaming_case(short_name, v.name) /* ctx.adjust_variant_name( + Language::C, + enum_name, + v.name, + ) */; + if let Some(value) = v.discriminant { + out!(("{variant_name} = {value:?},")); + } else { + out!(("{variant_name},")); + } + } + } + + if let Some(intn_t) = intn_t { + out!(("}}; typedef {intn_t} {full_ty_name};")); + } else { + out!(("}} {full_ty_name};")); + } + + out!("\n"); + + Ok(()) + } + + fn emit_struct ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + fields: &'_ [StructField<'_>] + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, ctx.out()); + let short_name = self_ty.short_name(); + let full_ty_name = self_ty.name(self); + + if self_ty.size() == 0 { + panic!("C does not support zero-sized structs!") + } + + self.emit_docs(ctx, docs, indent)?; + out!(("typedef struct {short_name} {{")); + if let _ = indent.scope() { + let ref mut first = true; + for &StructField { docs, name, ty } in fields { + // Skip ZSTs + if ty.size() == 0 { + if ty.align() > 1 { + panic!("Zero-sized fields must have an alignment of `1`"); + } else { + continue; + } + } + if mem::take(first).not() { + out!("\n"); + } + self.emit_docs(ctx, docs, indent)?; + out!( + ("{};"), + ty.name_wrapping_var(self, name) + ); + } + } + out!(("}} {full_ty_name};")); + + out!("\n"); + Ok(()) + } + + fn emit_opaque_type ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, ctx.out()); + let short_name = self_ty.short_name(); + let full_ty_name = self_ty.name(self); + + self.emit_docs(ctx, docs, indent)?; + out!(("typedef struct {short_name} {full_ty_name};")); + + out!("\n"); + Ok(()) + } + + fn emit_function ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + fname: &'_ str, + args: &'_ [FunctionArg<'_>], + ret_ty: &'_ dyn PhantomCType, + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + + self.emit_docs(ctx, docs, indent)?; + + let ref fn_sig_but_for_ret_type: String = { + let mut buf = Vec::::new(); + mk_out!(indent, buf); + + out!( + "\n{indent}{fn}{fname} (", + fn = if cfg!(feature = "c-headers-with-fn-style") { + "/* fn */ " + } else { + "" + }, + ); + let mut first = true; + if let _ = indent.scope() { + for arg in args { + if mem::take(&mut first).not() { + out!(","); + } + out!("\n{indent}{}", arg.ty.name_wrapping_var(self, arg.name)) + } + if first { + out!("void"); + } + } + out!(")"); + String::from_utf8(buf).unwrap() + }; + + mk_out!(indent, ctx.out()); + out!( + ("{};"), ret_ty.name_wrapping_var(self, fn_sig_but_for_ret_type) + ); + + out!("\n"); + Ok(()) + } + + fn emit_constant ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + name: &'_ str, + ty: &'_ dyn PhantomCType, + value: &'_ dyn ::core::fmt::Debug, + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, ctx.out()); + + self.emit_docs(ctx, docs, indent)?; + let ty = ty.name(self); + match ty.as_str() { + "int32_t" | "uint32_t" | "int16_t" | "uint16_t" | "int8_t" | "uint8_t" => { + out!(("static const {ty} {name} = {value:?};")); + }, + _ => panic!("Lua doesn't support this const type: {}", ty), + } + + out!("\n"); + Ok(()) + } +} \ No newline at end of file diff --git a/src/headers/languages/mod.rs b/src/headers/languages/mod.rs index f73c51fbea..6026b8aaa9 100644 --- a/src/headers/languages/mod.rs +++ b/src/headers/languages/mod.rs @@ -25,6 +25,11 @@ __cfg_python__! { mod python; } +__cfg_lua__! { + pub use lua::Lua; + mod lua; +} + pub struct Indentation { depth: ::core::cell::Cell, diff --git a/src/headers/templates/lua/_prelude.lua b/src/headers/templates/lua/_prelude.lua new file mode 100644 index 0000000000..b899289df3 --- /dev/null +++ b/src/headers/templates/lua/_prelude.lua @@ -0,0 +1,3 @@ +local ffi = require "ffi" + +ffi.cdef [[ diff --git a/src/headers/templates/lua/epilogue.lua b/src/headers/templates/lua/epilogue.lua new file mode 100644 index 0000000000..cdf229759f --- /dev/null +++ b/src/headers/templates/lua/epilogue.lua @@ -0,0 +1 @@ +]] \ No newline at end of file diff --git a/src/layout/_mod.rs b/src/layout/_mod.rs index c46a586c80..206aeb0f74 100644 --- a/src/layout/_mod.rs +++ b/src/layout/_mod.rs @@ -119,6 +119,9 @@ impl CType for T { | _case if language.is::() => { ::csharp_define_self(definer) }, + | _case if language.is::() => { + ::lua_define_self(definer) + }, #[cfg(feature = "python-headers")] | _case if language.is::() => { ::c_define_self(definer) @@ -149,6 +152,9 @@ impl CType for T { let sep = if var_name.is_empty() { "" } else { " " }; format!("{}{sep}{var_name}", Self::csharp_ty()) }, + | _case if language.is::() => { + ::lua_var(var_name) + }, #[cfg(feature = "python-headers")] | _case if language.is::() => { ::c_var(var_name).to_string() @@ -561,6 +567,21 @@ unsafe trait LegacyCType ) } } + + + __cfg_lua__! { + /// Extra typedef code (_e.g._ `[LayoutKind.Sequential] struct ...`) + fn lua_define_self (definer: &'_ mut dyn Definer) + -> io::Result<()> + ; + + /// Convenience function for formatting `{ty} {var}` in Lua. + fn lua_var (var_name: &'_ str) + -> rust::String + { + Self::c_var(var_name).to_string() + } + } } } diff --git a/src/layout/impls.rs b/src/layout/impls.rs index d02646d07d..25ce54aeb7 100644 --- a/src/layout/impls.rs +++ b/src/layout/impls.rs @@ -81,150 +81,8 @@ const _: () = { macro_rules! impl_CTypes { impl_CTypes! { @fns (A9, A8, A7, A6, A5, A4, A3, A2, A1) } - #[cfg(docs)] impl_CTypes! { @arrays 1 2 } #[cfg(not(docs))] - impl_CTypes! { @arrays - // 0 - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - 26 27 28 29 30 31 32 40 48 50 60 64 70 75 80 90 96 100 125 128 192 - 200 250 256 300 400 500 512 600 700 750 800 900 1000 1024 - } ); - ( - @arrays - $($N:tt)* - ) => ($( - // LegacyCType - /// Simplified for lighter documentation, but the actual impls - /// range **from `1` up to `32`, plus a bunch of significant - /// lengths up to `1024`**. - unsafe // Safety: Rust arrays _are_ `#[repr(C)]` - impl LegacyCType - for [Item; $N] - { __cfg_headers__! { - fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) - -> fmt::Result - { - // item_N_array - write!(fmt, - concat!("{}_", stringify!($N), "_array"), - Item::short_name(), - ) - } - - fn c_define_self (definer: &'_ mut dyn Definer) - -> io::Result<()> - { - let ref me = Self::c_var("").to_string(); - definer.define_once( - me, - &mut |definer| { - Item::define_self(&crate::headers::languages::C, definer)?; - writeln!(definer.out(), - concat!( - "typedef struct {{\n", - " {inline_array};\n", - "}} {me};\n", - ), - inline_array = Item::name_wrapping_var(&crate::headers::languages::C, concat!( - "idx[", stringify!($N), "]", - )), - me = me, - ) - } - ) - } - - fn c_var_fmt ( - fmt: &'_ mut fmt::Formatter<'_>, - var_name: &'_ str, - ) -> fmt::Result - { - // _e.g._, item_N_array_t - write!(fmt, - "{}_t{sep}{}", - Self::c_short_name(), - var_name, - sep = if var_name.is_empty() { "" } else { " " }, - ) - } - - __cfg_csharp__! { - fn csharp_define_self (definer: &'_ mut dyn Definer) - -> io::Result<()> - { - let ref me = Self::csharp_ty(); - Item::define_self(&crate::headers::languages::CSharp, definer)?; - definer.define_once(me, &mut |definer| { - let array_items = { - // Poor man's specialization to use `fixed` arrays. - if [ - "bool", - "u8", "u16", "u32", "u64", "usize", - "i8", "i16", "i32", "i64", "isize", - "float", "double", - ].contains(&::core::any::type_name::()) - { - format!( - " public fixed {ItemTy} arr[{N}];\n", - ItemTy = Item::name(&crate::headers::languages::CSharp), - N = $N, - // no need for a marshaler here - ) - } else { - // Sadly for the general case fixed arrays are - // not supported. - (0 .. $N) - .map(|i| format!( - " \ - {marshaler}\ - public {ItemTy} _{i};\n", - ItemTy = Item::name(&crate::headers::languages::CSharp), - i = i, - marshaler = - Item::csharp_marshaler() - .map(|m| format!("[MarshalAs({})]\n ", m)) - .as_deref() - .unwrap_or("") - , - )) - .collect::() - } - }; - writeln!(definer.out(), - concat!( - "[StructLayout(LayoutKind.Sequential, Size = {size})]\n", - "public unsafe struct {me} {{\n", - "{array_items}", - "}}\n", - ), - me = me, - array_items = array_items, - size = mem::size_of::(), - ) - }) - } - } - } type OPAQUE_KIND = OpaqueKind::Concrete; } - - // ReprC - /// Simplified for lighter documentation, but the actual impls - /// range **from `1` up to `32`, plus a bunch of significant - /// lengths up to `1024`**. - unsafe - impl ReprC - for [Item; $N] - { - type CLayout = [Item::CLayout; $N]; - - #[inline] - fn is_valid (it: &'_ Self::CLayout) - -> bool - { - it.iter().all(Item::is_valid) - } - } - )*); (@fns ( @@ -358,6 +216,17 @@ const _: () = { macro_rules! impl_CTypes { Some("UnmanagedType.FunctionPtr".into()) } } + + __cfg_lua__! { + fn lua_define_self (definer: &'_ mut dyn Definer) + -> io::Result<()> + { + Ret::define_self(&crate::headers::languages::Lua, definer)?; $( + $An::define_self(&crate::headers::languages::Lua, definer)?; $( + $Ai::define_self(&crate::headers::languages::Lua, definer)?; )*)? + Ok(()) + } + } } type OPAQUE_KIND = OpaqueKind::Concrete; } /// Simplified for lighter documentation, but the actual impls include @@ -555,6 +424,15 @@ const _: () = { macro_rules! impl_CTypes { $CSharpInt.into() } } + + __cfg_lua__! { + fn lua_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + } } type OPAQUE_KIND = OpaqueKind::Concrete; } from_CType_impl_ReprC! { $RustInt } )*); @@ -608,6 +486,15 @@ const _: () = { macro_rules! impl_CTypes { $Cty.into() } } + + __cfg_lua__! { + fn lua_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + } } type OPAQUE_KIND = OpaqueKind::Concrete; } from_CType_impl_ReprC! { $fN } )*); @@ -666,6 +553,15 @@ const _: () = { macro_rules! impl_CTypes { format!("{} /*const*/ *", T::name(&crate::headers::languages::CSharp)) } } + + __cfg_lua__! { + fn lua_define_self (definer: &'_ mut dyn $crate::headers::Definer) + -> $crate::ඞ::io::Result<()> + { + T::define_self(&crate::headers::languages::Lua, definer)?; + Ok(()) + } + } } type OPAQUE_KIND = OpaqueKind::Concrete; } unsafe @@ -724,6 +620,14 @@ const _: () = { macro_rules! impl_CTypes { format!("{} *", T::name(&crate::headers::languages::CSharp)) } } + + __cfg_lua__! { + fn lua_define_self (definer: &'_ mut dyn $crate::headers::Definer) + -> $crate::ඞ::io::Result<()> + { + T::define_self(&crate::headers::languages::Lua, definer) + } + } } type OPAQUE_KIND = OpaqueKind::Concrete; } unsafe impl ReprC @@ -907,6 +811,15 @@ unsafe "bool".into() } } + + __cfg_lua__! { + fn lua_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + } } type OPAQUE_KIND = OpaqueKind::Concrete; @@ -984,6 +897,15 @@ unsafe Some("UnmanagedType.SysInt".into()) } } + + __cfg_lua__! { + fn lua_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + } } type OPAQUE_KIND = OpaqueKind::Concrete; @@ -1201,3 +1123,146 @@ match_! {( } )* )}} + + +/// Arrays of const size `N` +unsafe // Safety: Rust arrays _are_ `#[repr(C)]` +impl LegacyCType + for [Item; N] +{ __cfg_headers__! { + fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) + -> fmt::Result + { + // item_N_array + write!(fmt, "{}_{}_array", Item::short_name(), N) + } + + fn c_define_self (definer: &'_ mut dyn Definer) + -> io::Result<()> + { + let ref me = Self::c_var("").to_string(); + definer.define_once( + me, + &mut |definer| { + Item::define_self(&crate::headers::languages::C, definer)?; + writeln!(definer.out(), + concat!( + "typedef struct {{\n", + " {inline_array};\n", + "}} {me};\n", + ), + inline_array = Item::name_wrapping_var( + &crate::headers::languages::C, + &format!("idx[{}]", N) + ), + me = me, + ) + } + ) + } + + fn c_var_fmt ( + fmt: &'_ mut fmt::Formatter<'_>, + var_name: &'_ str, + ) -> fmt::Result + { + // _e.g._, item_N_array_t + write!(fmt, + "{}_t{sep}{}", + Self::c_short_name(), + var_name, + sep = if var_name.is_empty() { "" } else { " " }, + ) + } + + __cfg_csharp__! { + fn csharp_define_self (definer: &'_ mut dyn Definer) + -> io::Result<()> + { + let ref me = Self::csharp_ty(); + Item::define_self(&crate::headers::languages::CSharp, definer)?; + definer.define_once(me, &mut |definer| { + let array_items = { + // Poor man's specialization to use `fixed` arrays. + if [ + "bool", + "u8", "u16", "u32", "u64", "usize", + "i8", "i16", "i32", "i64", "isize", + "float", "double", + ].contains(&::core::any::type_name::()) + { + format!( + " public fixed {ItemTy} arr[{N}];\n", + ItemTy = Item::name(&crate::headers::languages::CSharp), + N = N, + // no need for a marshaler here + ) + } else { + // Sadly for the general case fixed arrays are + // not supported. + (0 .. N) + .map(|i| format!( + " \ + {marshaler}\ + public {ItemTy} _{i};\n", + ItemTy = Item::name(&crate::headers::languages::CSharp), + i = i, + marshaler = + Item::csharp_marshaler() + .map(|m| format!("[MarshalAs({})]\n ", m)) + .as_deref() + .unwrap_or("") + , + )) + .collect::() + } + }; + writeln!(definer.out(), + concat!( + "[StructLayout(LayoutKind.Sequential, Size = {size})]\n", + "public unsafe struct {me} {{\n", + "{array_items}", + "}}\n", + ), + me = me, + array_items = array_items, + size = mem::size_of::(), + ) + }) + } + } + + __cfg_lua__! { + fn lua_define_self (definer: &'_ mut dyn Definer) + -> io::Result<()> + { + Item::define_self(&crate::headers::languages::Lua, definer) + } + + fn lua_var (var_name: &'_ str) + -> rust::String + { + format!( + "{}{sep}{}[{N}]", + Item::name(&crate::headers::languages::Lua), + var_name, + sep = if var_name.is_empty() { "" } else { " " }, + N = N, + ) + } + } +} type OPAQUE_KIND = OpaqueKind::Concrete; } + +unsafe +impl ReprC + for [Item; N] +{ + type CLayout = [Item::CLayout; N]; + + #[inline] + fn is_valid (it: &'_ Self::CLayout) + -> bool + { + it.iter().all(Item::is_valid) + } +} \ No newline at end of file diff --git a/src/layout/macros.rs b/src/layout/macros.rs index 1638d523ad..d40088f636 100644 --- a/src/layout/macros.rs +++ b/src/layout/macros.rs @@ -61,6 +61,22 @@ macro_rules! __cfg_python__ {( // Nothing )} +#[cfg(feature = "headers")] +#[macro_export] #[doc(hidden)] +macro_rules! __cfg_lua__ {( + $($item:item)* +) => ( + $($item)* +)} + +#[cfg(not(feature = "headers"))] +#[macro_export] #[doc(hidden)] +macro_rules! __cfg_lua__ {( + $($item:item)* +) => ( + // Nothing +)} + /// Safely implement [`CType`][`trait@crate::layout::LegacyCType`] /// for a `#[repr(C)]` struct **when all its fields are `CType`**. /// diff --git a/src/proc_macro/derives/c_type/struct_.rs b/src/proc_macro/derives/c_type/struct_.rs index 655971138c..a5249486fa 100644 --- a/src/proc_macro/derives/c_type/struct_.rs +++ b/src/proc_macro/derives/c_type/struct_.rs @@ -56,6 +56,9 @@ fn derive ( let EachGenericTy = generics.type_params().map(|it| &it.ident) ; + let EachConstParam = + generics.const_params().map(|param| ¶m.ident) + ; let ref EachFieldTy = fields.iter().vmap(|Field { ty, .. }| ty) ; @@ -70,17 +73,12 @@ fn derive ( fn short_name () -> #ඞ::String { - let mut _ret = - <#ඞ::String as #ඞ::From<_>>::from(#StructName_str) - ; + let mut _ret = #ඞ::format!("{}", #StructName_str); + #( + _ret.push_str(&#ඞ::format!("_{}", <#CLayoutOf<#EachGenericTy> as #CType>::short_name())); + )* #( - #ඞ::fmt::Write::write_fmt( - &mut _ret, - #ඞ::format_args!( - "_{}", - <#CLayoutOf<#EachGenericTy> as #CType>::short_name(), - ), - ).unwrap(); + _ret.push_str(&#ඞ::format!("_{}", #EachConstParam)); )* _ret } diff --git a/src/proc_macro/ffi_export/const_.rs b/src/proc_macro/ffi_export/const_.rs index 18744e5c17..6624174741 100644 --- a/src/proc_macro/ffi_export/const_.rs +++ b/src/proc_macro/ffi_export/const_.rs @@ -49,6 +49,7 @@ fn handle ( match lang { | Language::C => &languages::C, | Language::CSharp => &languages::CSharp, + | Language::Lua => &languages::Lua, | Language::Python => &languages::Python, } ; diff --git a/src/string/slice.rs b/src/string/slice.rs index 10dbace571..1043237236 100644 --- a/src/string/slice.rs +++ b/src/string/slice.rs @@ -155,6 +155,15 @@ impl<'lt> str_ref<'lt> { ) } } + + #[inline] + pub + fn try_as_str(self: str_ref<'lt>) -> Result<&'lt str, std::str::Utf8Error> { + std::str::from_utf8(unsafe { slice::from_raw_parts( + self.0.as_ptr(), + self.0.len(), + )}) + } } impl<'lt> Deref @@ -189,4 +198,4 @@ impl fmt::Debug { ::fmt(self, fmt) } -} +} \ No newline at end of file diff --git a/src/tuple.rs b/src/tuple.rs index d7ee1709a8..d29344cd83 100644 --- a/src/tuple.rs +++ b/src/tuple.rs @@ -60,6 +60,15 @@ impl LegacyCType "void".into() } } + + __cfg_lua__! { + fn lua_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + } } type OPAQUE_KIND = crate::layout::OpaqueKind::Concrete; } from_CType_impl_ReprC! { CVoid } diff --git a/tests/layout_macros.rs b/tests/layout_macros.rs index a6f7046ea0..d0ee46c084 100644 --- a/tests/layout_macros.rs +++ b/tests/layout_macros.rs @@ -320,6 +320,7 @@ fn generate_headers () in &[ C, CSharp, + Lua, ] { ::safer_ffi::headers::builder()