├── lua ├── .gitignore ├── test │ ├── .gitignore │ ├── run.lua │ └── tests.lua ├── bootstrap.lua └── example.lua ├── rust-unit ├── src │ ├── lib.rs │ ├── build.rs │ └── ffi.rs └── Cargo.toml ├── rust-example ├── src │ ├── lib.rs │ ├── build.rs │ └── ffi.rs └── Cargo.toml ├── .gitignore ├── c-marshalling ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml ├── parser ├── Cargo.toml └── src │ └── lib.rs ├── generator ├── Cargo.toml └── src │ └── lib.rs ├── derive-c-marshalling-library ├── Cargo.toml └── src │ └── lib.rs ├── lua-marshalling ├── Cargo.toml └── src │ └── lib.rs ├── derive-lua-marshalling ├── Cargo.toml └── src │ └── lib.rs ├── Makefile ├── Cargo.lock ├── README.md └── LICENSE /lua/.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | -------------------------------------------------------------------------------- /lua/test/.gitignore: -------------------------------------------------------------------------------- 1 | luaunit.lua 2 | -------------------------------------------------------------------------------- /rust-unit/src/lib.rs: -------------------------------------------------------------------------------- 1 | // ffi *must* be pub to expose the C API 2 | pub mod ffi; 3 | -------------------------------------------------------------------------------- /rust-example/src/lib.rs: -------------------------------------------------------------------------------- 1 | // ffi *must* be pub to expose the C API 2 | pub mod ffi; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | target/ 6 | -------------------------------------------------------------------------------- /c-marshalling/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "c-marshalling" 3 | version = "0.2.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | 7 | [dependencies] 8 | libc = "0.2.20" 9 | quick-error = "1.2.1" 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "c-marshalling", 4 | "derive-c-marshalling-library", 5 | "derive-lua-marshalling", 6 | "generator", 7 | "lua-marshalling", 8 | "parser", 9 | "rust-example", 10 | "rust-unit", 11 | ] 12 | -------------------------------------------------------------------------------- /parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parser" 3 | version = "0.2.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | 7 | [dependencies] 8 | quote = "0.4" 9 | syn = { version = "0.12", features = ["full"] } 10 | -------------------------------------------------------------------------------- /generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generator" 3 | version = "2.0.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | 7 | [dependencies] 8 | quote = "0.4" 9 | syn = "0.12" 10 | parser = { path = "../parser" } 11 | -------------------------------------------------------------------------------- /derive-c-marshalling-library/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive-c-marshalling-library" 3 | version = "0.1.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | 7 | [dependencies] 8 | libc = "0.2.20" 9 | syn = "0.12" 10 | quote = "0.4" 11 | -------------------------------------------------------------------------------- /lua-marshalling/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lua-marshalling" 3 | version = "0.3.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | 7 | [dependencies] 8 | libc = "0.2.20" 9 | c-marshalling = { path = "../c-marshalling" } 10 | derive-lua-marshalling = { path = "../derive-lua-marshalling" } 11 | lazy_static = "1" 12 | -------------------------------------------------------------------------------- /lua/bootstrap.lua: -------------------------------------------------------------------------------- 1 | local ffi = require("ffi") 2 | 3 | ffi.cdef[[ 4 | char *__lua_bootstrap(); 5 | void __free_lua_bootstrap(char *); 6 | ]] 7 | local rust = ffi.load(arg[1]) 8 | 9 | local bootstrap = rust.__lua_bootstrap() 10 | if bootstrap == nil then 11 | error("lua_bootstrap failed") 12 | end 13 | ffi.gc(bootstrap, rust.__free_lua_bootstrap) 14 | print(ffi.string(bootstrap)) 15 | -------------------------------------------------------------------------------- /derive-lua-marshalling/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive-lua-marshalling" 3 | version = "0.2.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | 7 | [dependencies] 8 | libc = "0.2.20" 9 | syn = "0.12" 10 | quote = "0.4" 11 | derive-c-marshalling-library = { path = "../derive-c-marshalling-library" } 12 | 13 | [lib] 14 | proc-macro = true 15 | -------------------------------------------------------------------------------- /rust-unit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-unit" 3 | version = "0.1.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | build = "src/build.rs" 7 | 8 | [dependencies] 9 | libc = "0.2.20" 10 | c-marshalling = { path = "../c-marshalling" } 11 | lua-marshalling = { path = "../lua-marshalling" } 12 | 13 | [build-dependencies] 14 | generator = { path = "../generator" } 15 | 16 | [lib] 17 | name = "rust_unit" 18 | crate-type = ["cdylib"] 19 | -------------------------------------------------------------------------------- /rust-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-example" 3 | version = "0.1.0" 4 | edition = "2018" 5 | authors = ["Johan Gardell <736172+gardell@users.noreply.github.com>"] 6 | build = "src/build.rs" 7 | 8 | [dependencies] 9 | libc = "0.2.20" 10 | c-marshalling = { path = "../c-marshalling" } 11 | lua-marshalling = { path = "../lua-marshalling" } 12 | 13 | [build-dependencies] 14 | generator = { path = "../generator" } 15 | 16 | [lib] 17 | name = "rust_example" 18 | crate-type = ["cdylib"] 19 | -------------------------------------------------------------------------------- /rust-unit/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let rust_output = ::std::path::Path::new(&env::var("OUT_DIR").unwrap()).join("ffi.rs"); 5 | 6 | let output = generator::generate( 7 | &env::current_dir().unwrap().as_path().join("src/ffi.rs"), 8 | "rust_unit", 9 | false, 10 | ); 11 | 12 | use std::io::Write; 13 | std::fs::File::create(rust_output.clone()) 14 | .unwrap() 15 | .write_all(output.as_bytes()) 16 | .unwrap(); 17 | 18 | let _ = ::std::process::Command::new("rustfmt") 19 | .arg(rust_output.display().to_string()) 20 | .spawn(); 21 | 22 | assert!(rust_output.exists()); 23 | } 24 | -------------------------------------------------------------------------------- /rust-example/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let rust_output = ::std::path::Path::new(&env::var("OUT_DIR").unwrap()).join("ffi.rs"); 5 | 6 | let output = generator::generate( 7 | &env::current_dir().unwrap().as_path().join("src/ffi.rs"), 8 | "rust_example", 9 | false, 10 | ); 11 | 12 | use std::io::Write; 13 | std::fs::File::create(rust_output.clone()) 14 | .unwrap() 15 | .write_all(output.as_bytes()) 16 | .unwrap(); 17 | 18 | let _ = ::std::process::Command::new("rustfmt") 19 | .arg(rust_output.display().to_string()) 20 | .spawn(); 21 | 22 | assert!(rust_output.exists()); 23 | } 24 | -------------------------------------------------------------------------------- /lua/test/run.lua: -------------------------------------------------------------------------------- 1 | local luaunit = require('luaunit') 2 | 3 | 4 | --[[ 5 | Recursively discover all files prefixed with 'test' and load them. 6 | 7 | @param string directory 8 | @return table tests 9 | --]] 10 | 11 | local function discoverTests(directory) 12 | for testName in io.popen("find "..directory.." -name \"test*.lua\""):lines() do 13 | print(testName) 14 | local keyName = testName:sub(string.len(directory) + 2, -5) 15 | print(keyName) 16 | local importName = keyName:gsub("/", ".") 17 | 18 | _G[keyName] = require(importName) 19 | end 20 | end 21 | 22 | --[[ 23 | Discovers all tests and runs them 24 | --]] 25 | 26 | local function run(entrypoint) 27 | local lu = luaunit.LuaUnit.new() 28 | luaunit:setVerbosity(luaunit.VERBOSITY_VERBOSE) 29 | 30 | discoverTests(entrypoint) 31 | lu:runSuite() 32 | 33 | os.exit((lu.result.failureCount > 0 and 1 or 0) 34 | + (lu.result.errorCount > 0 and 2 or 0)) 35 | end 36 | 37 | 38 | run("lua/test") 39 | -------------------------------------------------------------------------------- /lua/example.lua: -------------------------------------------------------------------------------- 1 | local example = require('rust-example') 2 | 3 | local a = example.make_a("Bilbo Baggins", 111) 4 | print("a", a.string, a.integer) 5 | 6 | local b_nil = example.make_b(nil, nil) 7 | print("b_nil", b_nil.string, b_nil.integer) 8 | 9 | local b = example.make_b("Frodo Baggins", 0) 10 | print("b", b.string, b.integer) 11 | 12 | local c_empty = example.make_c(nil, {}) 13 | print("c_empty") 14 | if c_empty.a ~= nil then 15 | print(c_empty.a.string, c_empty.a.integer) 16 | end 17 | print(c_empty.b.string, c_empty.b.integer) 18 | 19 | local c = example.make_c(a, { b }) 20 | io.write("c") 21 | if c.a ~= nil then 22 | io.write(c.a.string, c.a.integer) 23 | end 24 | print() 25 | print(c.b.string, c.b.integer) 26 | 27 | local d = example.make_d({ 1, 2, 3, 4, 5, 6 }) 28 | for _, value in ipairs(d.integers) do 29 | io.write(tostring(value).." ") 30 | end 31 | print() 32 | 33 | local e_nil = example.make_e(nil, { nil }) 34 | io.write("e_nil".." "..tostring(e_nil.integers).." ") 35 | for _, value in ipairs(e_nil.ds) do 36 | io.write(tostring(value).." ") 37 | end 38 | print() 39 | 40 | local e = example.make_e(nil, { d }) 41 | io.write("e".." "..tostring(e.integers).." ") 42 | for _, d in ipairs(e.ds) do 43 | for _, value in ipairs(d.integers) do 44 | io.write(tostring(value).." ") 45 | end 46 | print() 47 | end 48 | 49 | local random_short = example.random_short() 50 | print("random_short", random_short) 51 | 52 | -- Commented out due to bug in Lua FFI on Mac causing anything to print to stderr/stdout in native to cause a segfault 53 | --local status, msg = pcall(example.i_like_to_panic) 54 | --print("i_like_to_panic", status, msg) 55 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | rust-projects := \ 2 | c-marshalling \ 3 | derive-c-marshalling-library \ 4 | derive-lua-marshalling \ 5 | generator \ 6 | lua-marshalling \ 7 | parser \ 8 | rust-example \ 9 | rust-unit 10 | 11 | .PHONY: all 12 | all: build test run 13 | 14 | .PHONY: build-debug-rust 15 | build-debug-rust: 16 | for project in $(rust-projects); do \ 17 | cargo build --package $$project; \ 18 | done 19 | 20 | .PHONY: build-release-rust 21 | build-release-rust: 22 | for project in $(rust-projects); do \ 23 | cargo build --release --package $$project; \ 24 | done 25 | 26 | .PHONY: test-debug-rust 27 | test-debug-rust: 28 | for project in $(rust-projects); do \ 29 | cargo test --package $$project; \ 30 | done 31 | 32 | .PHONY: test-release-rust 33 | test-release-rust: 34 | for project in $(rust-projects); do \ 35 | cargo test --release --package $$project; \ 36 | done 37 | 38 | .PHONY: clean-rust 39 | clean-rust: 40 | for project in $(rust-projects); do \ 41 | cargo clean --package $$project; \ 42 | done 43 | 44 | lua/test/luaunit.lua: 45 | curl https://raw.githubusercontent.com/bluebird75/luaunit/master/luaunit.lua \ 46 | > lua/test/luaunit.lua 47 | 48 | .PHONY: luaunit 49 | luaunit: lua/test/luaunit.lua 50 | 51 | .PHONY: build-debug-example-lua 52 | build-debug-example-lua: build-debug-rust 53 | mkdir -p lua/output 54 | LD_LIBRARY_PATH=target/debug/ \ 55 | luajit lua/bootstrap.lua rust_example > lua/output/rust-example.lua 56 | 57 | .PHONY: build-release-example-lua 58 | build-release-example-lua: build-release-rust 59 | mkdir -p lua/output 60 | LD_LIBRARY_PATH=target/release/ \ 61 | luajit lua/bootstrap.lua rust_example > lua/output/rust-example.lua 62 | 63 | .PHONY: build-debug-unit-lua 64 | build-debug-unit-lua: build-debug-rust 65 | mkdir -p lua/output 66 | LD_LIBRARY_PATH=target/debug/ \ 67 | luajit lua/bootstrap.lua rust_unit > lua/output/rust-unit.lua 68 | 69 | .PHONY: build-release-unit-lua 70 | build-release-unit-lua: build-release-rust 71 | mkdir -p lua/output 72 | LD_LIBRARY_PATH=target/release/ \ 73 | luajit lua/bootstrap.lua rust_unit > lua/output/rust-unit.lua 74 | 75 | .PHONY: build-debug-lua 76 | build-debug-lua: build-debug-example-lua build-debug-unit-lua 77 | 78 | .PHONY: build-release-lua 79 | build-release-lua: build-release-example-lua build-release-unit-lua 80 | 81 | .PHONY: test-debug-lua 82 | test-debug-lua: build-debug-rust build-debug-lua luaunit 83 | LD_LIBRARY_PATH=target/debug/ \ 84 | LUA_PATH="lua/?.lua;lua/output/?.lua;lua/test/?.lua;;" \ 85 | luajit lua/test/run.lua 86 | 87 | .PHONY: test-release-lua 88 | test-release-lua: build-release-rust build-release-lua luaunit 89 | LD_LIBRARY_PATH=target/release/ \ 90 | LUA_PATH="lua/?.lua;lua/output/?.lua;lua/test/?.lua;;" \ 91 | luajit lua/test/run.lua 92 | 93 | .PHONY: run-debug-lua 94 | run-debug-lua: build-debug-rust build-debug-lua 95 | LD_LIBRARY_PATH=target/debug/ \ 96 | LUA_PATH="lua/?.lua;lua/output/?.lua;;" \ 97 | luajit lua/example.lua 98 | 99 | .PHONY: run-release-lua 100 | run-release-lua: build-release-rust build-release-lua 101 | LD_LIBRARY_PATH=target/release/ \ 102 | LUA_PATH="lua/?.lua;lua/output/?.lua;;" \ 103 | luajit lua/example.lua 104 | 105 | .PHONY: clean-lua 106 | clean-lua: 107 | rm -rf lua/output 108 | rm -f lua/test/luaunit.lua 109 | 110 | .PHONY: build-debug 111 | build-debug: build-debug-rust build-debug-lua 112 | 113 | .PHONY: build-release 114 | build-release: build-release-rust build-release-lua 115 | 116 | .PHONY: build 117 | build: build-debug 118 | 119 | .PHONY: test-debug 120 | test-debug: test-debug-rust test-debug-lua 121 | 122 | .PHONY: test-release 123 | test-release: test-release-rust test-release-lua 124 | 125 | .PHONY: test 126 | test: test-debug 127 | 128 | .PHONY: clean 129 | clean: clean-rust clean-lua 130 | 131 | .PHONY: run 132 | run: run-debug-lua 133 | -------------------------------------------------------------------------------- /rust-example/src/ffi.rs: -------------------------------------------------------------------------------- 1 | use lua_marshalling::LuaMarshalling; 2 | 3 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 4 | pub struct A { 5 | string: String, 6 | integer: i32, 7 | } 8 | 9 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 10 | pub struct B { 11 | string: Option, 12 | integer: Option, 13 | } 14 | 15 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 16 | pub struct C { 17 | a: Option, 18 | b: Vec, 19 | } 20 | 21 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 22 | pub struct D { 23 | integers: Vec, 24 | } 25 | 26 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 27 | pub struct E { 28 | integers: Option>, 29 | ds: Vec, 30 | } 31 | 32 | #[derive(Debug, Eq, PartialEq, Default, LuaMarshalling)] 33 | pub struct F { 34 | thing: Option>>, 35 | } 36 | 37 | impl F { 38 | fn describe(&self) -> String { 39 | self.thing 40 | .as_ref() 41 | .and_then(|things: &Vec>| things.first()) 42 | .and_then(|things: &Vec| things.first()) 43 | .map(|thing: &A| format!("{:#?}", thing)) 44 | .unwrap_or_else(|| "Nothing here".into()) 45 | } 46 | } 47 | 48 | #[derive(Debug, Eq, PartialEq, Default, LuaMarshalling)] 49 | pub struct G { 50 | thing: Option>, 51 | } 52 | 53 | impl G { 54 | fn a(&self) -> Option<&A> { 55 | self.thing.as_ref().and_then(|inner| inner.as_ref()) 56 | } 57 | 58 | fn string(&self) -> &str { 59 | self.a().map(|a| a.string.as_ref()).unwrap_or_default() 60 | } 61 | 62 | fn integer(&self) -> i32 { 63 | self.a().map(|a| a.integer).unwrap_or_default() 64 | } 65 | } 66 | 67 | pub mod extern_ffi { 68 | use super::{A, D, F, G}; 69 | 70 | pub fn make_a(string: &str, integer: i32) -> A { 71 | A { 72 | string: string.to_owned(), 73 | integer, 74 | } 75 | } 76 | 77 | pub fn make_b(string: Option<&str>, integer: Option) -> super::B { 78 | super::B { 79 | string: string.map(|string| string.to_owned()), 80 | integer, 81 | } 82 | } 83 | 84 | pub fn make_c(a: Option, b: Vec) -> super::C { 85 | super::C { a, b } 86 | } 87 | 88 | pub fn make_d(integers: &[i32]) -> D { 89 | D { 90 | integers: integers.to_owned(), 91 | } 92 | } 93 | 94 | pub fn make_e(integers: Option>, ds: Vec) -> super::E { 95 | super::E { integers, ds } 96 | } 97 | 98 | pub fn describe(a: A, b: super::B, c: super::C, d: D, e: super::E) -> String { 99 | format!("A: {:?}, B: {:?}, C: {:?}, D: {:?}, E: {:?}", a, b, c, d, e) 100 | } 101 | 102 | pub fn random_short() -> i16 { 103 | 3 104 | } 105 | 106 | // Note: No support for return ! or () 107 | pub fn i_like_to_panic() -> i32 { 108 | panic!("p-p-p-p-p-anic!"); 109 | } 110 | 111 | pub fn make_f(a: Option) -> F { 112 | F { 113 | thing: match a { 114 | Some(A { string, integer }) => Some(vec![vec![A { string, integer }]]), 115 | None => None, 116 | }, 117 | } 118 | } 119 | 120 | pub fn length_f(f: F) -> usize { 121 | f.thing.map(|things| things.len()).unwrap_or_default() 122 | } 123 | 124 | pub fn describe_first_f(f: F) -> String { 125 | f.describe() 126 | } 127 | 128 | pub fn make_g(a: Option) -> G { 129 | match a { 130 | Some(A { string, integer }) => G { 131 | thing: Some(Some(A { string, integer })), 132 | }, 133 | None => G { thing: None }, 134 | } 135 | } 136 | 137 | pub fn g_get_a(g: G) -> Option { 138 | g.thing.and_then(|inner| inner) 139 | } 140 | 141 | pub fn string_g(g: G) -> String { 142 | g.string().into() 143 | } 144 | 145 | pub fn integer_g(g: G) -> i32 { 146 | g.integer() 147 | } 148 | } 149 | 150 | include!(concat!(env!("OUT_DIR"), "/ffi.rs")); 151 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "c-marshalling" 5 | version = "0.2.0" 6 | dependencies = [ 7 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "derive-c-marshalling-library" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 17 | "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", 18 | ] 19 | 20 | [[package]] 21 | name = "derive-lua-marshalling" 22 | version = "0.2.0" 23 | dependencies = [ 24 | "derive-c-marshalling-library 0.1.0", 25 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 27 | "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", 28 | ] 29 | 30 | [[package]] 31 | name = "generator" 32 | version = "2.0.0" 33 | dependencies = [ 34 | "parser 0.2.0", 35 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "lazy_static" 41 | version = "1.4.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | 44 | [[package]] 45 | name = "libc" 46 | version = "0.2.62" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "lua-marshalling" 51 | version = "0.3.0" 52 | dependencies = [ 53 | "c-marshalling 0.2.0", 54 | "derive-lua-marshalling 0.2.0", 55 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 57 | ] 58 | 59 | [[package]] 60 | name = "parser" 61 | version = "0.2.0" 62 | dependencies = [ 63 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "proc-macro2" 69 | version = "0.2.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | dependencies = [ 72 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 73 | ] 74 | 75 | [[package]] 76 | name = "quick-error" 77 | version = "1.2.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | 80 | [[package]] 81 | name = "quote" 82 | version = "0.4.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | dependencies = [ 85 | "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "rust-example" 90 | version = "0.1.0" 91 | dependencies = [ 92 | "c-marshalling 0.2.0", 93 | "generator 2.0.0", 94 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "lua-marshalling 0.3.0", 96 | ] 97 | 98 | [[package]] 99 | name = "rust-unit" 100 | version = "0.1.0" 101 | dependencies = [ 102 | "c-marshalling 0.2.0", 103 | "generator 2.0.0", 104 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "lua-marshalling 0.3.0", 106 | ] 107 | 108 | [[package]] 109 | name = "syn" 110 | version = "0.12.15" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | dependencies = [ 113 | "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "unicode-xid" 120 | version = "0.1.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | 123 | [metadata] 124 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 125 | "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" 126 | "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" 127 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 128 | "checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" 129 | "checksum syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97c05b8ebc34ddd6b967994d5c6e9852fa92f8b82b3858c39451f97346dcce5" 130 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 131 | -------------------------------------------------------------------------------- /derive-c-marshalling-library/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | use quote::*; 3 | 4 | pub fn c_marshalling(derive_input: &syn::DeriveInput) -> quote::Tokens { 5 | let ident = &derive_input.ident; 6 | let marshal_typename: syn::Ident = syn::parse_str(&format!("__c_{}", ident)).unwrap(); 7 | let mut_marshal_typename: syn::Ident = syn::parse_str(&format!("__c_mut_{}", ident)).unwrap(); 8 | 9 | match derive_input.data { 10 | syn::Data::Struct(syn::DataStruct { 11 | fields: syn::Fields::Named(ref fields), 12 | .. 13 | }) => { 14 | let marshal_type_field_declarations = fields.named.iter().map(|field| { 15 | let ident = &field.ident.as_ref().unwrap(); 16 | let ty = &field.ty; 17 | quote! { #ident: <#ty as c_marshalling::PtrAsReference>::Raw } 18 | }); 19 | let mut_marshal_type_field_declarations = fields.named.iter().map(|field| { 20 | let ident = &field.ident.as_ref().unwrap(); 21 | let ty = &field.ty; 22 | quote! { #ident: <#ty as c_marshalling::FromRawConversion>::Raw } 23 | }); 24 | let into_raw_field_initializers = fields.named.iter().map(|field| { 25 | let ident = &field.ident.as_ref().unwrap(); 26 | quote! { #ident: self.#ident.into_raw()? } 27 | }); 28 | let from_raw_field_initializers = fields.named.iter().map(|field| { 29 | let ident = &field.ident.as_ref().unwrap(); 30 | quote! { #ident: c_marshalling::FromRawConversion::from_raw(raw.#ident)? } 31 | }); 32 | let raw_as_ref_field_initializers = fields.named.iter().map(|field| { 33 | let ident = &field.ident.as_ref().unwrap(); 34 | quote! { #ident: c_marshalling::PtrAsReference::raw_as_ref(&raw.#ident)? } 35 | }); 36 | 37 | quote! { 38 | 39 | #[doc(hidden)] 40 | #[allow(non_snake_case)] 41 | #[repr(C)] 42 | pub struct #marshal_typename { 43 | #(#marshal_type_field_declarations),* 44 | } 45 | 46 | #[doc(hidden)] 47 | #[allow(non_snake_case)] 48 | #[repr(C)] 49 | pub struct #mut_marshal_typename { 50 | #(#mut_marshal_type_field_declarations),* 51 | } 52 | 53 | impl c_marshalling::IntoRawConversion for #ident { 54 | type Raw = #mut_marshal_typename; 55 | type Ptr = *mut Self::Raw; 56 | 57 | fn into_raw(self) -> Result { 58 | Ok(Self::Raw { 59 | #(#into_raw_field_initializers),* 60 | }) 61 | } 62 | 63 | fn into_ptr(self) -> Result { 64 | c_marshalling::box_into_ptr(self) 65 | } 66 | } 67 | 68 | impl c_marshalling::FromRawConversion for #ident { 69 | type Raw = #mut_marshal_typename; 70 | type Ptr = *mut Self::Raw; 71 | 72 | unsafe fn from_raw(raw: #mut_marshal_typename) 73 | -> Result { 74 | Ok(Self { 75 | #(#from_raw_field_initializers),* 76 | }) 77 | } 78 | 79 | unsafe fn from_ptr(raw: Self::Ptr) -> Result { 80 | c_marshalling::box_from_ptr(raw) 81 | } 82 | } 83 | 84 | impl c_marshalling::PtrAsReference for #ident { 85 | type Raw = #marshal_typename; 86 | type Ptr = *const Self::Raw; 87 | 88 | unsafe fn raw_as_ref(raw: &#marshal_typename) 89 | -> Result { 90 | Ok(Self { 91 | #(#raw_as_ref_field_initializers),* 92 | }) 93 | } 94 | 95 | unsafe fn ptr_as_ref(raw: Self::Ptr) -> Result { 96 | Self::raw_as_ref(&*raw) 97 | } 98 | } 99 | } 100 | } 101 | syn::Data::Struct(::syn::DataStruct { 102 | fields: syn::Fields::Unnamed(..), 103 | .. 104 | }) => panic!("Tuple-struct type not supported"), 105 | syn::Data::Struct(syn::DataStruct { 106 | fields: syn::Fields::Unit, 107 | .. 108 | }) => panic!("Unit type not supported"), 109 | syn::Data::Enum(_) => panic!("Enum type not supported"), 110 | syn::Data::Union(_) => panic!("Union type not supported"), 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua to Rust FFI code generation 2 | ## Motivating example 3 | Rust 4 | ```Rust 5 | #[derive(LuaMarshalling)] 6 | pub struct A { 7 | string: String, 8 | integer: i32, 9 | } 10 | 11 | pub mod extern_ffi { 12 | pub fn make_a(string: &str, integer: i32) -> A { 13 | A { 14 | string: string.to_owned(), 15 | integer, 16 | } 17 | } 18 | 19 | pub fn describe(a: A) -> String { 20 | format!("A: {:?}", a) 21 | } 22 | } 23 | ``` 24 | 25 | Lua 26 | ```Lua 27 | local example = require('rust-example') 28 | 29 | local a = example.make_a("Test string", 42) 30 | print("a", a.string, a.integer) 31 | 32 | print("describe", example.describe(a)) 33 | ``` 34 | ## Implementation details 35 | ### Types 36 | * Supported Rust types include primitives, `Vec`, `Option`, `String` and custom `struct` with `derive(LuaMarshalling)` and any combination of those. 37 | `&str` is supported only as an argument but is faster than `String`. `&[]` is supported only for primitive types. 38 | `Result` is supported only as a return argument. 39 | * `Option`s `None` is `nil` in Lua. 40 | * Only `&str` and `&[]` of primitive types are passed as references to Rust, all other types are copied. 41 | * A Rust `struct` is converted to a Lua `table`, but can still be used as an argument. 42 | For this to work, the Lua table also keeps a reference to the native object pointer. 43 | * The native object pointer is garbage collected by calling back to Rust. 44 | To keep the native pointer and the table consistent, the `table` is immutable. 45 | 46 | ### `panic` and `error` 47 | * Passing a Lua string to Rust as `&str` or `String` **may** **fail** with an `error` due to UTF-8 requirements. 48 | However, passing a Lua string to Rust as a `&[u8]` or `Vec` will not. 49 | * Returning a string from Rust **may** **fail** as well with an `error` due to strings containing the zero-byte. 50 | * A Rust `panic` will cause an `error` in Lua. 51 | 52 | ### Known Issues 53 | * `Vec>` have been disabled. 54 | Lua arrays generally do not handle `null` values well. 55 | See [www.lua.org/pil/19.1.html](https://www.lua.org/pil/19.1.html) for more information. 56 | * `struct` typenames must be unique. Separate modules are not enough. 57 | * Identifiers can not be Lua or C reserved keywords. For example, a variable cannot be called `short`. 58 | * The `__` prefix is reserved for hidden identifiers and should not be used as field names or function arguments. 59 | 60 | ## Setup 61 | ### Configuration 62 | ```Sh 63 | cargo new --lib example_setup 64 | ``` 65 | * In `example_setup` create the file `src/build.rs` with the following content 66 | 67 | ```Rust 68 | use std::env; 69 | use std::fs::File; 70 | use std::io::Write; 71 | use std::path::Path; 72 | 73 | fn main() { 74 | let rust_output = Path::new(&env::var("OUT_DIR").unwrap()).join("ffi.rs"); 75 | 76 | let output = generator::generate( 77 | &env::current_dir().unwrap().as_path().join("src/lib.rs"), 78 | "fuzzy_filter_lua_ffi", 79 | false, 80 | ); 81 | 82 | File::create(rust_output.clone()) 83 | .unwrap() 84 | .write_all(output.as_bytes()) 85 | .unwrap(); 86 | 87 | assert!(rust_output.exists()); 88 | } 89 | ``` 90 | 91 | **Note** the `library_name` parameter to `generator::generator` must be equal to the library name of the crate. 92 | 93 | Add the following to the `Cargo.toml` under `[package]` 94 | ```Toml 95 | build = "src/build.rs" 96 | ``` 97 | 98 | Under `[dependencies]` add the following 99 | ```Toml 100 | libc = "0.2.20" 101 | c-marshalling = { git = "https://github.com/distil/rust_lua_ffi" } 102 | lua-marshalling = { git = "https://github.com/distil/rust_lua_ffi" } 103 | ``` 104 | 105 | Add the following section to the `Cargo.toml` as well 106 | ```Toml 107 | [build-dependencies] 108 | generator = { git = "https://github.com/distil/rust_lua_ffi" } 109 | 110 | [lib] 111 | crate-type = ["cdylib"] 112 | ``` 113 | 114 | In `src/lib.rs` add the following 115 | ```Rust 116 | pub mod extern_ffi { 117 | pub fn hello_world() -> String { 118 | "Hello World!".to_owned() 119 | } 120 | } 121 | 122 | include!(concat!(env!("OUT_DIR"), "/ffi.rs")); 123 | ``` 124 | 125 | ### Building 126 | After the library has been built, the Lua interface code can be generated using the following command 127 | ```Sh 128 | LD_LIBRARY_PATH=..path-to-example_setup/target/debug/ \ 129 | RUST_LUA_FFI_TYPE_PREFIX=example_setup \ 130 | luajit ..path-to-rust_lua_ffi/lua/bootstrap.lua example_setup > api.lua 131 | ``` 132 | Note the setting of `RUST_LUA_FFI_TYPE_PREFIX` to the module name. This is 133 | optional unless you need to use separately generated bindings for multiple Rust 134 | libraries in the same Lua instance. 135 | 136 | ### Usage 137 | To use the `api.lua` file generated in the *Building* step, create a Lua file called `example.lua` in the same directory as the Lua interface code containing 138 | ```Lua 139 | local api = require('api') 140 | 141 | print(api.hello_world()) 142 | ``` 143 | 144 | Execute the file using the following command 145 | ```Bash 146 | LD_LIBRARY_PATH=..path-to-example_setup/target/debug/ luajit example.lua 147 | ``` 148 | -------------------------------------------------------------------------------- /derive-lua-marshalling/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | extern crate proc_macro; 3 | use quote::*; 4 | 5 | fn lua_marshalling(derive_input: &syn::DeriveInput) -> quote::Tokens { 6 | let ident = &derive_input.ident; 7 | 8 | match derive_input.data { 9 | syn::Data::Struct(syn::DataStruct { 10 | fields: syn::Fields::Named(ref fields), 11 | .. 12 | }) => { 13 | let lua_c_struct_fields = fields.named.iter().map(|field| { 14 | let ident = &field.ident.as_ref().unwrap().to_string(); 15 | let ty = &field.ty; 16 | quote! { 17 | format!(" const {typename} {ident};", 18 | typename=<#ty as ::lua_marshalling::Type>::prefixed_c_typename(), 19 | ident=#ident) 20 | } 21 | }); 22 | let lua_table_field_initializers = fields.named.iter().map(|field| { 23 | let ident = &field.ident.as_ref().unwrap().to_string(); 24 | let ty = &field.ty; 25 | quote! { 26 | format!("{ident} = ({function})(value.{ident})", 27 | ident = #ident, 28 | function = <#ty as lua_marshalling::FromRawConversion>::function()) 29 | } 30 | }); 31 | let lua_c_struct_field_initializers = fields.named.iter().map(|field| { 32 | let ident = &field.ident.as_ref().unwrap().to_string(); 33 | let ty = &field.ty; 34 | quote! { format!("({function})(value.{ident})", 35 | ident = #ident, 36 | function = <#ty as lua_marshalling::IntoRawConversion>::function()) 37 | } 38 | }); 39 | let lua_dependencies = fields.named.iter().map(|field| { 40 | let ty = &field.ty; 41 | quote! { 42 | dependencies.extend(lua_marshalling::make_dependencies::<#ty>()); 43 | } 44 | }); 45 | 46 | quote! { 47 | impl lua_marshalling::Type for #ident { 48 | fn typename() -> String { 49 | stringify!(#ident).to_string() 50 | } 51 | fn typedeclaration() -> String { 52 | let fields: &[String] = &[ 53 | #(#lua_c_struct_fields),* 54 | ]; 55 | format!(r#" 56 | typedef struct {{ 57 | {fields} 58 | }} {self_typename}; 59 | "#, 60 | fields = fields.join("\n"), 61 | self_typename = Self::prefixed_typename()) 62 | } 63 | fn dependencies() -> lua_marshalling::Dependencies { 64 | let mut dependencies = lua_marshalling::Dependencies::new(); 65 | #(#lua_dependencies)* 66 | dependencies 67 | } 68 | fn c_function_argument() -> String { 69 | format!("const {}*", Self::prefixed_c_typename()) 70 | } 71 | fn c_mut_function_argument() -> String { 72 | format!("{}*", Self::prefixed_typename()) 73 | } 74 | fn metatype() -> String { 75 | lua_marshalling::ptr_type_metatype::() 76 | } 77 | } 78 | 79 | impl lua_marshalling::FromRawConversion for #ident { 80 | fn function() -> String { 81 | format!( 82 | r#"function(value) 83 | return {{ 84 | {} 85 | }} 86 | end"#, 87 | &[ 88 | #(#lua_table_field_initializers),* 89 | ].join(", ")) 90 | } 91 | fn gc() -> bool { 92 | true 93 | } 94 | } 95 | 96 | impl lua_marshalling::IntoRawConversion for #ident { 97 | fn function() -> String { 98 | let fields: &[String] = &[ 99 | #(#lua_c_struct_field_initializers),* 100 | ]; 101 | format!(r#"function(value) 102 | return __typename_{self_typename}( 103 | {fields} 104 | ) 105 | end"#, 106 | self_typename = ::typename(), 107 | fields = fields.join(",\n ")) 108 | } 109 | fn create_pointer() -> String { 110 | lua_marshalling::ptr_type_create_pointer::() 111 | } 112 | fn create_array() -> String { 113 | lua_marshalling::immediate_type_create_array::() 114 | } 115 | } 116 | } 117 | } 118 | _ => panic!("Only non-tuple struct supported"), 119 | } 120 | } 121 | 122 | #[proc_macro_derive(LuaMarshalling)] 123 | pub fn derive_lua_marshalling(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 124 | let c = derive_c_marshalling_library::c_marshalling(&syn::parse(input.clone()).unwrap()); 125 | let lua = lua_marshalling(&syn::parse(input).unwrap()); 126 | let quote = quote! { 127 | #lua 128 | 129 | #c 130 | }; 131 | quote.into() 132 | } 133 | -------------------------------------------------------------------------------- /parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | use quote::*; 2 | 3 | pub fn extern_ffi_mod(file: &syn::File) -> Option<&[syn::Item]> { 4 | file.items 5 | .iter() 6 | .filter_map(|item| match *item { 7 | syn::Item::Mod(ref m) if AsRef::::as_ref(&m.ident) == stringify!(extern_ffi) => { 8 | m.content.as_ref().map(|t| &t.1[..]) 9 | } 10 | _ => None, 11 | }) 12 | .next() 13 | } 14 | 15 | pub fn uses(items: &[syn::Item]) -> Vec { 16 | items 17 | .iter() 18 | .filter_map(|item| { 19 | if let syn::Item::Use(ref view_path) = *item { 20 | Some(quote!(#view_path)) 21 | } else { 22 | None 23 | } 24 | }) 25 | .collect() 26 | } 27 | 28 | pub struct Argument { 29 | pub ident: syn::Ident, 30 | pub typ: quote::Tokens, 31 | } 32 | 33 | pub struct Function { 34 | pub ident: syn::Ident, 35 | pub args: Vec, 36 | pub ret: quote::Tokens, 37 | } 38 | 39 | pub fn functions(items: &[::syn::Item]) -> Vec { 40 | items 41 | .iter() 42 | .filter_map(|item| { 43 | if let syn::Item::Fn(ref fn_decl) = *item { 44 | Some((&fn_decl.ident, &fn_decl.decl.inputs, &fn_decl.decl.output)) 45 | } else { 46 | None 47 | } 48 | }) 49 | .map(|(ident, args, output)| { 50 | let args: Vec<_> = args 51 | .iter() 52 | .map(|arg| { 53 | let (name, ty_arg) = match *arg { 54 | syn::FnArg::Captured(ref cap) => match cap.pat { 55 | syn::Pat::Ident(ref pat) => (&pat.ident, &cap.ty), 56 | _ => panic!("Unknown identifier"), 57 | }, 58 | _ => panic!("Unknown identifier"), 59 | }; 60 | let typ = match *ty_arg { 61 | syn::Type::Reference(::syn::TypeReference { 62 | elem: ref ty, 63 | mutability: None, 64 | .. 65 | }) => match **ty { 66 | syn::Type::Path(ref path) => { 67 | quote! { &#path } 68 | } 69 | syn::Type::Slice(ref ty) => { 70 | if let syn::Type::Path(ref path) = *ty.elem { 71 | quote! { &[#path] } 72 | } else { 73 | panic!( 74 | "Slice: Function arguments can only be immutable \ 75 | reference or immediate" 76 | ) 77 | } 78 | } 79 | _ => panic!( 80 | "Reference: Function arguments can only be immutable \ 81 | reference or immediate" 82 | ), 83 | }, 84 | ::syn::Type::Path(ref path) => { 85 | quote! { #path } 86 | } 87 | _ => panic!( 88 | "Function arguments can only be immutable reference or immediate" 89 | ), 90 | }; 91 | Argument { ident: *name, typ } 92 | }) 93 | .collect(); 94 | Function { 95 | ident: *ident, 96 | args, 97 | ret: match *output { 98 | syn::ReturnType::Default => quote! { () }, 99 | syn::ReturnType::Type(_, ref ty) => { 100 | if let syn::Type::Path(ref path) = **ty { 101 | quote! { #path } 102 | } else { 103 | panic!("Function return type can only be immediate") 104 | } 105 | } 106 | }, 107 | } 108 | }) 109 | .collect() 110 | } 111 | 112 | pub fn function_declarations(functions: &[Function], uses: &[quote::Tokens]) -> quote::Tokens { 113 | let extern_c_ffi_functions = functions.iter().map(|function| { 114 | let argument_declaration = function.args.iter().map(|arg| { 115 | let ident = &arg.ident; 116 | let typ = &arg.typ; 117 | quote! { #ident: <#typ as c_marshalling::PtrAsReference>::Ptr } 118 | }); 119 | let argument_passing = function.args.iter().map(|arg| { 120 | let ident = &arg.ident; 121 | let typ = &arg.typ; 122 | quote! { 123 | <#typ as c_marshalling::PtrAsReference>::ptr_as_ref(#ident)? 124 | } 125 | }); 126 | let gc_ident = syn::parse_str::(&format!("__gc_{}", function.ident)).unwrap(); 127 | let ret = &function.ret; 128 | let ident = &function.ident; 129 | quote! { 130 | /// # Safety 131 | /// 132 | /// Only called in an auto-generated context. Should not be called directly. 133 | #[no_mangle] 134 | pub unsafe extern "C" fn #ident( 135 | #(#argument_declaration,)* 136 | __output: *mut <#ret as c_marshalling::IntoRawConversion>::Ptr) -> u32 { 137 | std::panic::catch_unwind(|| -> Result { 138 | *__output = <#ret as c_marshalling::IntoRawConversion >::into_ptr( 139 | super::extern_ffi::#ident(#(#argument_passing),*) 140 | )?; 141 | Ok(0) 142 | }).unwrap_or(Ok(2)).unwrap_or(1) 143 | } 144 | 145 | /// # Safety 146 | /// 147 | /// Only called in an auto-generated context. Should not be called directly. 148 | #[no_mangle] 149 | pub unsafe extern "C" fn #gc_ident( 150 | output: <#ret as c_marshalling::IntoRawConversion>::Ptr) -> u32 { 151 | <#ret as c_marshalling::FromRawConversion >::from_ptr(output) 152 | .is_err() as u32 153 | } 154 | } 155 | }); 156 | 157 | quote! { 158 | #[doc(hidden)] 159 | pub mod extern_c_ffi { 160 | #(#uses)* 161 | 162 | #(#extern_c_ffi_functions) * 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /rust-unit/src/ffi.rs: -------------------------------------------------------------------------------- 1 | use lua_marshalling::LuaMarshalling; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq, LuaMarshalling)] 4 | pub struct A { 5 | string: String, 6 | integer: i32, 7 | } 8 | 9 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 10 | pub struct B { 11 | string: Option, 12 | integer: Option, 13 | } 14 | 15 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 16 | pub struct C { 17 | a: Option, 18 | b: Vec, 19 | } 20 | 21 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 22 | pub struct D { 23 | integers: Vec, 24 | } 25 | 26 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 27 | pub struct E { 28 | integers: Option>, 29 | ds: Vec, 30 | } 31 | 32 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 33 | pub struct F { 34 | as_: Option>, 35 | strings: Option>, 36 | } 37 | 38 | #[derive(Debug, Eq, PartialEq, LuaMarshalling)] 39 | pub struct G { 40 | b: bool, 41 | option_b: Option, 42 | vec_b: Vec, 43 | } 44 | 45 | pub mod extern_ffi { 46 | // Intentionally not `use` all structs to test relative names 47 | use super::{A, D}; 48 | 49 | pub fn square_i8(value: i8) -> i8 { 50 | value * value 51 | } 52 | pub fn square_i16(value: i16) -> i16 { 53 | value * value 54 | } 55 | pub fn square_i32(value: i32) -> i32 { 56 | value * value 57 | } 58 | pub fn square_u8(value: u8) -> u8 { 59 | value * value 60 | } 61 | pub fn square_u16(value: u16) -> u16 { 62 | value * value 63 | } 64 | pub fn square_u32(value: u32) -> u32 { 65 | value * value 66 | } 67 | pub fn square_f32(value: f32) -> f32 { 68 | value * value 69 | } 70 | pub fn square_f64(value: f64) -> f64 { 71 | value * value 72 | } 73 | pub fn concatenate_strings(string1: String, string2: String, separator: &str) -> String { 74 | [string1, string2].join(separator) 75 | } 76 | pub fn concatenate_u16_slices(slice1: &[u16], slice2: &[u16]) -> Vec { 77 | slice1.iter().chain(slice2).cloned().collect() 78 | } 79 | pub fn concatenate_a(a1: A, a2: A, separator: &str) -> A { 80 | A { 81 | string: concatenate_strings(a1.string, a2.string, separator), 82 | integer: a1.integer + a2.integer, 83 | } 84 | } 85 | pub fn concatenate_vec_i32(vec1: Vec, vec2: Vec) -> Vec { 86 | vec1.into_iter().chain(vec2).collect() 87 | } 88 | pub fn concatenate_vec_a(vec1: Vec, vec2: Vec) -> Vec { 89 | vec1.into_iter().chain(vec2).collect() 90 | } 91 | pub fn concatenate_vec_string(vec1: Vec, vec2: Vec) -> Vec { 92 | vec1.into_iter().chain(vec2).collect() 93 | } 94 | pub fn concatenate_vec_vec_i32(vec1: Vec>, vec2: Vec>) -> Vec> { 95 | vec1.into_iter().chain(vec2).collect() 96 | } 97 | pub fn concatenate_vec_vec_string( 98 | vec1: Vec>, 99 | vec2: Vec>, 100 | ) -> Vec> { 101 | vec1.into_iter().chain(vec2).collect() 102 | } 103 | pub fn option_i32_or(option1: Option, option2: Option) -> Option { 104 | option1.or(option2) 105 | } 106 | pub fn option_a_or(option1: Option, option2: Option) -> Option { 107 | option1.or(option2) 108 | } 109 | pub fn option_string_or(option1: Option, option2: Option) -> Option { 110 | option1.or(option2) 111 | } 112 | pub fn option_vec_i32_or( 113 | option1: Option>, 114 | option2: Option>, 115 | ) -> Option> { 116 | option1.or(option2) 117 | } 118 | pub fn option_vec_string_or( 119 | option1: Option>, 120 | option2: Option>, 121 | ) -> Option> { 122 | option1.or(option2) 123 | } 124 | pub fn option_vec_a_or(option1: Option>, option2: Option>) -> Option> { 125 | option1.or(option2) 126 | } 127 | pub fn option_option_i32_or( 128 | option1: Option>, 129 | option2: Option>, 130 | ) -> Option> { 131 | option1.or(option2) 132 | } 133 | pub fn option_option_string_or( 134 | option1: Option>, 135 | option2: Option>, 136 | ) -> Option> { 137 | option1.or(option2) 138 | } 139 | pub fn option_option_a_or( 140 | option1: Option>, 141 | option2: Option>, 142 | ) -> Option> { 143 | option1.or(option2) 144 | } 145 | 146 | pub fn make_a(string: &str, integer: i32) -> A { 147 | A { 148 | string: string.to_owned(), 149 | integer, 150 | } 151 | } 152 | pub fn make_b(string: Option<&str>, integer: Option) -> super::B { 153 | super::B { 154 | string: string.map(|string| string.to_owned()), 155 | integer, 156 | } 157 | } 158 | pub fn make_c(a: Option, b: Vec) -> super::C { 159 | super::C { a, b } 160 | } 161 | pub fn make_d(integers: &[i32]) -> D { 162 | D { 163 | integers: integers.to_owned(), 164 | } 165 | } 166 | pub fn make_e(integers: Option>, ds: Vec) -> super::E { 167 | super::E { integers, ds } 168 | } 169 | pub fn make_f(as_: Option>, strings: Option>) -> super::F { 170 | super::F { as_, strings } 171 | } 172 | 173 | pub fn make_g(b: bool, option_b: Option, vec_b: Vec) -> super::G { 174 | println!("option_b: {:?}", option_b); 175 | super::G { b, option_b, vec_b } 176 | } 177 | 178 | pub fn describe(a: A, b: super::B, c: super::C, d: D, e: super::E, f: super::F) -> String { 179 | format!( 180 | "A: {:?}, B: {:?}, C: {:?}, D: {:?}, E: {:?}, F: {:?}", 181 | a, b, c, d, e, f 182 | ) 183 | } 184 | 185 | // Note: No support for return ! or () 186 | pub fn i_like_to_panic() -> i32 { 187 | panic!("p-p-p-p-p-anic!"); 188 | } 189 | 190 | pub fn u8_slice_to_string(slice: &[u8]) -> String { 191 | String::from_utf8(slice.to_owned()).unwrap() 192 | } 193 | 194 | pub fn u8_vec_to_string(vec: Vec) -> String { 195 | String::from_utf8(vec).unwrap() 196 | } 197 | 198 | pub fn string_with_byte_zeros() -> String { 199 | println!("string_with_byte_zeros"); 200 | "String\0containing\0null\0bytes".to_owned() 201 | } 202 | 203 | pub fn maybe_make_a(string: Option<&str>, integer: Option) -> Result { 204 | match (string, integer) { 205 | (Some(string), Some(integer)) => Ok(make_a(string, integer)), 206 | (None, Some(_)) => Err("No string".to_owned()), 207 | (Some(_), None) => Err("No integer".to_owned()), 208 | (None, None) => Err("Neither".to_owned()), 209 | } 210 | } 211 | 212 | pub fn make_b_or_a(string: Option<&str>, integer: Option) -> Result { 213 | match (string, integer) { 214 | (Some(string), Some(integer)) => Ok(make_a(string, integer)), 215 | (string, integer) => Err(make_b(string, integer)), 216 | } 217 | } 218 | 219 | pub fn ok_none() -> Result, i32> { 220 | Ok(None) 221 | } 222 | 223 | pub fn err_none() -> Result> { 224 | Err(None) 225 | } 226 | } 227 | 228 | include!(concat!(env!("OUT_DIR"), "/ffi.rs")); 229 | -------------------------------------------------------------------------------- /generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "256"] 2 | use quote::*; 3 | use std::fs::File; 4 | use std::io::Read; 5 | 6 | fn function_declarations( 7 | functions: &[parser::Function], 8 | uses: &[quote::Tokens], 9 | library_name: &str, 10 | ffi_load_using_cpath: bool, 11 | ) -> quote::Tokens { 12 | let extern_lua_ffi_c_header_functions = functions.iter().map(|function| { 13 | let ident = function.ident.to_string(); 14 | let mut argument_declaration: Vec<_> = function 15 | .args 16 | .iter() 17 | .map(|arg| { 18 | let typ = &arg.typ; 19 | quote! { 20 | <#typ as lua_marshalling::Type>::c_function_argument() 21 | } 22 | }) 23 | .collect(); 24 | let ret = &function.ret; 25 | argument_declaration.push(quote! { 26 | format!("{}*", <#ret as lua_marshalling::Type>::c_mut_function_argument()) 27 | }); 28 | quote! { 29 | format!(r#"int32_t {ident}( 30 | {argument_declaration});"#, 31 | ident=#ident, 32 | argument_declaration=[#(#argument_declaration),*].join(",\n ")), 33 | format!("int32_t __gc_{ident}( 34 | {argument_declaration});", 35 | ident=#ident, 36 | argument_declaration=<#ret as lua_marshalling::Type>::c_mut_function_argument()) 37 | } 38 | }); 39 | 40 | let extern_lua_function_wrappers = functions.iter().map(|function| { 41 | let ident = function.ident.to_string(); 42 | let argument_declaration: Vec<_> = function 43 | .args 44 | .iter() 45 | .map(|arg| arg.ident.to_string()) 46 | .collect(); 47 | let argument_passing: Vec<_> = function 48 | .args 49 | .iter() 50 | .map(|arg| { 51 | let ident = arg.ident.to_string(); 52 | let typ = &arg.typ; 53 | quote! { 54 | format!( 55 | "({function})({ident})", 56 | ident=#ident, 57 | function=<#typ as lua_marshalling::IntoRawConversion>::function()) 58 | } 59 | }) 60 | .collect(); 61 | 62 | let ret = &function.ret; 63 | let argument_declaration = argument_declaration.join(",\n "); 64 | 65 | quote! { 66 | format!(r#"function M.{ident}( 67 | {argument_declaration}) 68 | local __typeof = __c_mut_function_argument_{typename} 69 | local __ret_ptr = __typeof(1, {{}}) 70 | local status = rust.{ident}( 71 | {argument_passing} 72 | ) 73 | if status ~= 0 then 74 | error("{ident} failed with status "..status) 75 | end 76 | local __ret = __ret_ptr[0] 77 | {gc} 78 | local f = {function} 79 | return f(__ret) 80 | end 81 | "#, 82 | ident = #ident, 83 | argument_declaration = #argument_declaration, 84 | typename = <#ret as lua_marshalling::Type>::typename(), 85 | argument_passing = { 86 | let mut argument_passing: Vec = [#(#argument_passing),*].to_vec(); 87 | argument_passing.push("__ret_ptr".to_owned()); 88 | argument_passing 89 | }.join(",\n "), 90 | gc = if <#ret as lua_marshalling::FromRawConversion>::gc() { 91 | format!("ffi.gc(__ret, rust.__gc_{})", #ident) 92 | } else { 93 | "".to_owned() 94 | }, 95 | function = <#ret as lua_marshalling::FromRawConversion>::function() 96 | ) 97 | } 98 | }); 99 | 100 | let extern_lua_unique_types = functions.iter().map(|function| { 101 | let args = function.args.iter().map(|arg| { 102 | let typ = &arg.typ; 103 | quote! { 104 | lua_marshalling::make_dependencies::<#typ>() 105 | } 106 | }); 107 | 108 | let ret = &function.ret; 109 | quote! { 110 | #(#args,)* 111 | lua_marshalling::make_dependencies::<#ret>(), 112 | } 113 | }); 114 | 115 | let ffi_load_expression = if ffi_load_using_cpath { 116 | format!( 117 | "ffi.load( 118 | package.searchpath('lib{library_name}', package.cpath) 119 | or package.searchpath('{library_name}', package.cpath) 120 | or '{library_name}')", 121 | library_name = library_name, 122 | ) 123 | } else { 124 | format!("ffi.load('{library_name}')", library_name = library_name) 125 | }; 126 | 127 | quote! { 128 | #[doc(hidden)] 129 | pub mod lua_bootstrap { 130 | #(#uses)* 131 | 132 | #[no_mangle] 133 | pub extern "C" fn __lua_bootstrap() -> *mut libc::c_char { 134 | let unique_types: lua_marshalling::Dependencies = 135 | [ #(#extern_lua_unique_types)* ] 136 | .iter() 137 | .flat_map(|value| value.into_iter() 138 | .map(|(k, v)| (k.clone(), v.clone()))) 139 | .collect(); 140 | let sorted_types = 141 | lua_marshalling::dependency_sorted_type_descriptions(&unique_types); 142 | 143 | std::ffi::CString::new( 144 | [ 145 | r#"-- Code generated by Rust Lua interface. DO NOT EDIT. 146 | 147 | local ffi = require("ffi") 148 | 149 | ffi.cdef[[ 150 | "#.to_owned(), 151 | sorted_types 152 | .iter() 153 | .map(|dependencies| (dependencies.typedeclaration)()) 154 | .collect::>() 155 | .join("\n"), 156 | { 157 | let functions: Vec = 158 | vec![#(#extern_lua_ffi_c_header_functions),*]; 159 | functions 160 | }.join("\n"), 161 | format!(r#" 162 | ]] 163 | 164 | local rust = {ffi_load_expression} 165 | 166 | local M = {{}} 167 | 168 | "#, ffi_load_expression = #ffi_load_expression), 169 | sorted_types 170 | .iter() 171 | .map(|dependencies| (dependencies.metatype)()) 172 | .collect::>() 173 | .join("\n"), 174 | #(#extern_lua_function_wrappers,)* 175 | r#" 176 | return M 177 | "#.to_owned() 178 | ].join("\n")) 179 | .ok() 180 | .map(std::ffi::CString::into_raw) 181 | .unwrap_or_else(std::ptr::null_mut) 182 | } 183 | 184 | /// # Safety 185 | /// 186 | /// Only called in an auto-generated context. Should not be called directly. 187 | #[no_mangle] 188 | pub unsafe extern "C" fn __free_lua_bootstrap(bootstrap: *mut ::libc::c_char) { 189 | if bootstrap != std::ptr::null_mut() { 190 | std::ffi::CString::from_raw(bootstrap); 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | pub fn generate( 198 | file_name: &std::path::Path, 199 | library_name: &str, 200 | ffi_load_using_cpath: bool, 201 | ) -> String { 202 | let mut input = String::new(); 203 | let input = { 204 | let mut file = File::open(file_name).unwrap(); 205 | file.read_to_string(&mut input).unwrap(); 206 | &input 207 | }; 208 | let file = syn::parse_file(input).unwrap(); 209 | let items = parser::extern_ffi_mod(&file).expect("ffi module"); 210 | let uses = parser::uses(items); 211 | let functions = parser::functions(items); 212 | 213 | format!( 214 | r#"// Code generated by Rust Lua interface. DO NOT EDIT. 215 | {} 216 | {} 217 | "#, 218 | parser::function_declarations(&functions, &uses).to_string(), 219 | function_declarations(&functions, &uses, library_name, ffi_load_using_cpath).to_string() 220 | ) 221 | } 222 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lua/test/tests.lua: -------------------------------------------------------------------------------- 1 | local luaunit = require('luaunit') 2 | local unit = require('rust-unit') 3 | 4 | local M = {} 5 | 6 | function M.testIntegers() 7 | luaunit.assertEquals(unit.square_i8(2), 2 * 2) 8 | luaunit.assertEquals(unit.square_i16(3), 3 * 3) 9 | luaunit.assertEquals(unit.square_i32(4), 4 * 4) 10 | luaunit.assertEquals(unit.square_u8(2), 2 * 2) 11 | luaunit.assertEquals(unit.square_u16(3), 3 * 3) 12 | luaunit.assertEquals(unit.square_u32(4), 4 * 4) 13 | luaunit.assertAlmostEquals(unit.square_f64(5.3), 5.3 * 5.3) 14 | end 15 | 16 | function M.testConcatenateStrings() 17 | luaunit.assertEquals(unit.concatenate_strings("Bilbo", "Baggins", " "), "Bilbo Baggins") 18 | luaunit.assertEquals(unit.concatenate_strings("", "", ""), "") 19 | end 20 | 21 | function M.testConcatenateSlices() 22 | luaunit.assertEquals( 23 | unit.concatenate_u16_slices( 24 | { 1, 2, 3, 4 }, 25 | { 5, 6, 7 }), 26 | { 1, 2, 3, 4, 5, 6, 7 }) 27 | luaunit.assertEquals( 28 | unit.concatenate_u16_slices( 29 | { 1, 2, 3, 4 }, 30 | {}), 31 | { 1, 2, 3, 4 }) 32 | luaunit.assertEquals(unit.concatenate_u16_slices({}, {}), {}) 33 | end 34 | 35 | function M.testConcatenateStructs() 36 | local a = unit.concatenate_a(unit.make_a("Dead", 0xDEAD), unit.make_a("beef", 0xBEEF), " ") 37 | luaunit.assertEquals(a.string, "Dead beef") 38 | luaunit.assertEquals(a.integer, 0xDEAD + 0xBEEF) 39 | end 40 | 41 | function M.testConcatenateVecs() 42 | luaunit.assertEquals( 43 | unit.concatenate_vec_i32({ 1, 2, 3, 4 }, { 5, 6, 7 }), 44 | { 1, 2, 3, 4, 5, 6, 7 }) 45 | luaunit.assertEquals( 46 | unit.concatenate_vec_string({"Red", "Green"}, {"Blue"}), 47 | { "Red", "Green", "Blue" }) 48 | luaunit.assertEquals( 49 | unit.concatenate_vec_vec_i32({ { 1, 2, 3 }, { 4, 5 } }, { { 6 }, {} }), 50 | { { 1, 2, 3 }, { 4, 5 }, { 6 }, {} }) 51 | luaunit.assertEquals( 52 | unit.concatenate_vec_vec_string({ {}, {"Red", "Green"}, {"Blue"} }, { {}, {} }), 53 | { {}, {"Red", "Green"}, {"Blue"}, {}, {} }) 54 | end 55 | 56 | function M.testOptionOr() 57 | luaunit.assertEquals(unit.option_i32_or(nil, nil), nil) 58 | luaunit.assertEquals(unit.option_i32_or(42, nil), 42) 59 | luaunit.assertEquals(unit.option_i32_or(nil, 42), 42) 60 | luaunit.assertEquals(unit.option_i32_or(42, 43), 42) 61 | 62 | luaunit.assertEquals(unit.option_string_or(nil, nil), nil) 63 | luaunit.assertEquals(unit.option_string_or("Bilbo", nil), "Bilbo") 64 | luaunit.assertEquals(unit.option_string_or(nil, "Frodo"), "Frodo") 65 | luaunit.assertEquals(unit.option_string_or("Bilbo", "Frodo"), "Bilbo") 66 | 67 | luaunit.assertEquals(unit.option_vec_i32_or(nil, nil), nil) 68 | luaunit.assertEquals(unit.option_vec_i32_or({ 1, 2, 3 }, nil), { 1, 2, 3 }) 69 | luaunit.assertEquals(unit.option_vec_i32_or(nil, { 1, 2, 3 }), { 1, 2, 3 }) 70 | luaunit.assertEquals(unit.option_vec_i32_or({ 1, 2, 3 }, { 4, 5 }), { 1, 2, 3 }) 71 | 72 | luaunit.assertEquals(unit.option_vec_string_or(nil, nil), nil) 73 | luaunit.assertEquals(unit.option_vec_string_or({ "Red", "Green", "Blue" }, nil), { "Red", "Green", "Blue" }) 74 | luaunit.assertEquals(unit.option_vec_string_or(nil, { "Red", "Green", "Blue" }), { "Red", "Green", "Blue" }) 75 | luaunit.assertEquals(unit.option_vec_string_or({ "Red", "Green" }, { "Blue" }), { "Red", "Green" }) 76 | 77 | luaunit.assertEquals(unit.option_option_i32_or(nil, nil), nil) 78 | luaunit.assertEquals(unit.option_option_i32_or(42, nil), 42) 79 | luaunit.assertEquals(unit.option_option_i32_or(nil, 42), 42) 80 | luaunit.assertEquals(unit.option_option_i32_or(42, 43), 42) 81 | 82 | luaunit.assertEquals(unit.option_option_string_or(nil, nil), nil) 83 | luaunit.assertEquals(unit.option_option_string_or("Bilbo", nil), "Bilbo") 84 | luaunit.assertEquals(unit.option_option_string_or(nil, "Frodo"), "Frodo") 85 | luaunit.assertEquals(unit.option_option_string_or("Bilbo", "Frodo"), "Bilbo") 86 | end 87 | 88 | function M.testOptionStructOr() 89 | luaunit.assertEquals(unit.option_a_or(nil, nil), nil) 90 | luaunit.assertEquals(unit.option_a_or(unit.make_a("Bilbo Baggins", 42), nil).string, "Bilbo Baggins") 91 | luaunit.assertEquals(unit.option_a_or(nil, unit.make_a("Bilbo Baggins", 42)).string, "Bilbo Baggins") 92 | luaunit.assertEquals(unit.option_a_or(unit.make_a("Bilbo", 42), unit.make_a("Frodo", 42)).string, "Bilbo") 93 | 94 | luaunit.assertEquals(unit.option_option_a_or(nil, nil), nil) 95 | luaunit.assertEquals(unit.option_option_a_or(unit.make_a("Bilbo Baggins", 42), nil).string, "Bilbo Baggins") 96 | luaunit.assertEquals(unit.option_option_a_or(nil, unit.make_a("Bilbo Baggins", 42)).string, "Bilbo Baggins") 97 | luaunit.assertEquals(unit.option_option_a_or(unit.make_a("Bilbo", 42), unit.make_a("Frodo", 42)).string, "Bilbo") 98 | end 99 | 100 | function M.testOptionVecStructOr() 101 | luaunit.assertEquals(unit.option_vec_a_or(nil, nil), nil) 102 | local as = unit.option_vec_a_or({ unit.make_a("Bilbo", 42), unit.make_a("Baggins", 43) }, nil) 103 | luaunit.assertEquals(#as, 2) 104 | luaunit.assertEquals(as[1].string, "Bilbo") 105 | luaunit.assertEquals(as[1].integer, 42) 106 | luaunit.assertEquals(as[2].string, "Baggins") 107 | luaunit.assertEquals(as[2].integer, 43) 108 | local as = unit.option_vec_a_or(nil, { unit.make_a("Bilbo", 42), unit.make_a("Baggins", 43) }) 109 | luaunit.assertEquals(#as, 2) 110 | luaunit.assertEquals(as[1].string, "Bilbo") 111 | luaunit.assertEquals(as[1].integer, 42) 112 | luaunit.assertEquals(as[2].string, "Baggins") 113 | luaunit.assertEquals(as[2].integer, 43) 114 | local as = unit.option_vec_a_or({ unit.make_a("Bilbo", 42), unit.make_a("Baggins", 43) }, {}) 115 | luaunit.assertEquals(#as, 2) 116 | luaunit.assertEquals(as[1].string, "Bilbo") 117 | luaunit.assertEquals(as[1].integer, 42) 118 | luaunit.assertEquals(as[2].string, "Baggins") 119 | luaunit.assertEquals(as[2].integer, 43) 120 | end 121 | 122 | function M.testConcatenateVecStructs() 123 | local as = unit.concatenate_vec_a( 124 | { 125 | unit.make_a("Dead", 0xDEAD), 126 | unit.make_a("Beef", 0xBEEF) 127 | }, 128 | { 129 | unit.make_a("Covfefe", 0xC0FEFE) 130 | }) 131 | luaunit.assertEquals(#as, 3) 132 | luaunit.assertEquals(as[1].string, "Dead") 133 | luaunit.assertEquals(as[1].integer, 0xDEAD) 134 | luaunit.assertEquals(as[2].string, "Beef") 135 | luaunit.assertEquals(as[2].integer, 0xBEEF) 136 | luaunit.assertEquals(as[3].string, "Covfefe") 137 | luaunit.assertEquals(as[3].integer, 0xC0FEFE) 138 | end 139 | 140 | function M.testMakeStruct() 141 | local a = unit.make_a("Covfefe", 0xC0FEFE) 142 | luaunit.assertEquals(a.string, "Covfefe") 143 | luaunit.assertEquals(a.integer, 0xC0FEFE) 144 | 145 | local b = unit.make_b(nil, nil) 146 | luaunit.assertEquals(b.string, nil) 147 | luaunit.assertEquals(b.integer, nil) 148 | local b = unit.make_b("Covfefe", nil) 149 | luaunit.assertEquals(b.string, "Covfefe") 150 | luaunit.assertEquals(b.integer, nil) 151 | local b = unit.make_b(nil, 0xC0FEFE) 152 | luaunit.assertEquals(b.string, nil) 153 | luaunit.assertEquals(b.integer, 0xC0FEFE) 154 | local b = unit.make_b("Beef", 0xBEEF) 155 | luaunit.assertEquals(b.string, "Beef") 156 | luaunit.assertEquals(b.integer, 0xBEEF) 157 | 158 | local c = unit.make_c(nil, {}) 159 | luaunit.assertEquals(c.a, nil) 160 | luaunit.assertEquals(c.b, {}) 161 | local c = unit.make_c(unit.make_a("Bilbo Baggins", 111), {}) 162 | luaunit.assertEquals(c.a.string, "Bilbo Baggins") 163 | luaunit.assertEquals(c.a.integer, 111) 164 | luaunit.assertEquals(c.b, {}) 165 | local c = unit.make_c(unit.make_a("Bilbo Baggins", 111), { unit.make_b("Frodo Baggins", nil) }) 166 | luaunit.assertEquals(c.a.string, "Bilbo Baggins") 167 | luaunit.assertEquals(c.a.integer, 111) 168 | luaunit.assertEquals(#c.b, 1) 169 | luaunit.assertEquals(c.b[1].string, "Frodo Baggins") 170 | luaunit.assertEquals(c.b[1].integer, nil) 171 | 172 | luaunit.assertEquals(unit.make_d({ 1, 2, 3, 4 }).integers, { 1, 2, 3, 4 }) 173 | luaunit.assertEquals(unit.make_d({}).integers, {}) 174 | 175 | local f = unit.make_f(nil, nil) 176 | luaunit.assertEquals(f.as_, nil) 177 | luaunit.assertEquals(f.strings, nil) 178 | local f = unit.make_f({}, nil) 179 | luaunit.assertEquals(f.as_, {}) 180 | luaunit.assertEquals(f.strings, nil) 181 | local f = unit.make_f(nil, {}) 182 | luaunit.assertEquals(f.as_, nil) 183 | luaunit.assertEquals(f.strings, {}) 184 | local f = unit.make_f({ unit.make_a("Covfefe", 0xC0FEFE) }, nil) 185 | luaunit.assertEquals(#f.as_, 1) 186 | luaunit.assertEquals(f.as_[1].string, "Covfefe") 187 | luaunit.assertEquals(f.strings, nil) 188 | local f = unit.make_f(nil, { "Bilbo", "Baggins"}) 189 | luaunit.assertEquals(f.as_, nil) 190 | luaunit.assertEquals(f.strings, { "Bilbo", "Baggins" }) 191 | end 192 | 193 | function M.testConsumeStructs() 194 | luaunit.assertEquals( 195 | unit.describe( 196 | unit.make_a("A", 1), 197 | unit.make_b("B", 2), 198 | unit.make_c(unit.make_a("CA", 3), { unit.make_b("CB", 4) }), 199 | unit.make_d({ 5, 6, 7 }), 200 | unit.make_e({ 8, 9 }, { unit.make_d({ 10, 11 }) }), 201 | unit.make_f({ unit.make_a("FA1", 12), unit.make_a("FA2", 13) }, { "S1", "S2" }) 202 | ), 203 | 'A: A { string: "A", integer: 1 }, ' 204 | ..'B: B { string: Some("B"), integer: Some(2) }, ' 205 | ..'C: C { a: Some(A { string: "CA", integer: 3 }), b: [B { string: Some("CB"), integer: Some(4) }] }, ' 206 | ..'D: D { integers: [5, 6, 7] }, ' 207 | ..'E: E { integers: Some([8, 9]), ds: [D { integers: [10, 11] }] }, ' 208 | ..'F: F { as_: Some([A { string: "FA1", integer: 12 }, A { string: "FA2", integer: 13 }]), strings: Some(["S1", "S2"]) }' 209 | ) 210 | end 211 | 212 | function M.testPanic() 213 | local status, msg = pcall(unit.i_like_to_panic) 214 | luaunit.assertFalse(status) 215 | luaunit.assertNotNil(msg) 216 | end 217 | 218 | function M.testU8s() 219 | local string = "Hello World!" 220 | luaunit.assertEquals(unit.u8_slice_to_string(string), string) 221 | luaunit.assertEquals(unit.u8_vec_to_string(string), string) 222 | end 223 | 224 | function M.testInvalidUtf8() 225 | local string = "\0\x9f" 226 | local status, _ = pcall(unit.u8_slice_to_string, string) 227 | luaunit.assertFalse(status) 228 | local status, string = pcall(unit.u8_vec_to_string, string) 229 | luaunit.assertFalse(status) 230 | end 231 | 232 | function M.testStringWithByteZeros() 233 | local status, _ = pcall(unit.string_with_byte_zeros) 234 | luaunit.assertFalse(status) 235 | end 236 | 237 | function M.testBooleans() 238 | local g1 = unit.make_g(true, nil, {}) 239 | local g2 = unit.make_g(false, true, { true }) 240 | local g3 = unit.make_g(true, false, { true, false }) 241 | 242 | luaunit.assertTrue(g1.b) 243 | luaunit.assertNil(g1.option_b) 244 | luaunit.assertEquals(#g1.vec_b, 0) 245 | 246 | luaunit.assertFalse(g2.b) 247 | luaunit.assertTrue(g2.option_b) 248 | luaunit.assertEquals(#g2.vec_b, 1) 249 | luaunit.assertTrue(g2.vec_b[1]) 250 | 251 | luaunit.assertTrue(g3.b) 252 | luaunit.assertFalse(g3.option_b) 253 | luaunit.assertEquals(#g3.vec_b, 2) 254 | luaunit.assertTrue(g3.vec_b[1]) 255 | luaunit.assertFalse(g3.vec_b[2]) 256 | end 257 | 258 | function M.testResult() 259 | local ok, err = unit.maybe_make_a(nil, nil) 260 | luaunit.assertNil(ok) 261 | luaunit.assertEquals(err, "Neither") 262 | 263 | local ok, err = unit.maybe_make_a("Bilbo Baggins", nil) 264 | luaunit.assertNil(ok) 265 | luaunit.assertEquals(err, "No integer") 266 | 267 | local ok, err = unit.maybe_make_a(nil, 42) 268 | luaunit.assertNil(ok) 269 | luaunit.assertEquals(err, "No string") 270 | 271 | local ok, err = unit.maybe_make_a("Bilbo Baggins", 111) 272 | luaunit.assertNil(err) 273 | luaunit.assertEquals(ok.string, "Bilbo Baggins") 274 | luaunit.assertEquals(ok.integer, 111) 275 | 276 | local ok, err = unit.make_b_or_a("Bilbo Baggins", 111) 277 | luaunit.assertNil(err) 278 | luaunit.assertEquals(ok.string, "Bilbo Baggins") 279 | luaunit.assertEquals(ok.integer, 111) 280 | 281 | local ok, err = unit.make_b_or_a("Bilbo Baggins", nil) 282 | luaunit.assertNil(ok) 283 | luaunit.assertEquals(err.string, "Bilbo Baggins") 284 | luaunit.assertNil(err.integer) 285 | 286 | local ok, err = unit.make_b_or_a(nil, 42) 287 | luaunit.assertNil(ok) 288 | luaunit.assertNil(err.string) 289 | luaunit.assertEquals(err.integer, 42) 290 | 291 | local ok, err = unit.ok_none() 292 | luaunit.assertNil(ok) 293 | luaunit.assertNil(err) 294 | 295 | local ok, err = unit.ok_none() 296 | luaunit.assertNil(ok) 297 | luaunit.assertNil(err) 298 | end 299 | 300 | return M 301 | -------------------------------------------------------------------------------- /c-marshalling/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | use quick_error::quick_error; 3 | 4 | quick_error! { 5 | #[derive(Debug)] 6 | pub enum Error { 7 | NulError(err: std::ffi::NulError) { 8 | display("{}", err) 9 | from() 10 | } 11 | IntoStringError(err: std::ffi::IntoStringError) { 12 | display("{}", err) 13 | from() 14 | } 15 | Utf8Error(err: std::str::Utf8Error) { 16 | display("{}", err) 17 | from() 18 | } 19 | } 20 | } 21 | 22 | // Types with #[derive(CMarshalling)] implement this trait. 23 | pub trait IntoRawConversion: Sized { 24 | type Raw: Sized; 25 | type Ptr: Sized; 26 | 27 | /// This method releases ownership `self`. 28 | /// A successfully returned type *must* be free'd using 29 | /// `FromRawConversion::from_raw` and said method only. 30 | /// 31 | /// `PtrAsReference::raw_as_ref` can be used to access the type 32 | /// but it will *not* return ownership. 33 | fn into_raw(self) -> Result; 34 | 35 | fn into_ptr(self) -> Result; 36 | } 37 | 38 | // Types with #[derive(CMarshalling)] implement this trait. 39 | pub trait FromRawConversion: Sized { 40 | type Raw: Sized; 41 | type Ptr: Sized; 42 | 43 | /// This method takes ownership of the `raw` object. 44 | /// Use `PtrAsReference::raw_as_ref` to *not* take ownership of the object. 45 | unsafe fn from_raw(raw: Self::Raw) -> Result; 46 | 47 | unsafe fn from_ptr(ptr: Self::Ptr) -> Result; 48 | } 49 | 50 | // Types with #[derive(CMarshalling)] implement this trait. 51 | pub trait PtrAsReference: Sized { 52 | type Raw: Sized; 53 | type Ptr: Sized; 54 | 55 | /// This method does not take ownership of the object pointed to by `raw`. 56 | /// Use `FromRawConversion::from_raw` to take ownership of the pointer. 57 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result; 58 | 59 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result; 60 | } 61 | 62 | pub fn box_into_ptr>(value: T) -> Result<*mut T::Raw, Error> { 63 | value.into_raw().map(Box::new).map(Box::into_raw) 64 | } 65 | 66 | /// # Safety 67 | /// 68 | /// Only called in an auto-generated context. Should not be called directly. 69 | pub unsafe fn box_from_ptr>(raw: *mut T::Raw) -> Result { 70 | T::from_raw(*Box::from_raw(raw)) 71 | } 72 | 73 | #[repr(C)] 74 | pub struct CVec { 75 | pub ptr: *const T, 76 | pub len: usize, 77 | pub capacity: usize, 78 | } 79 | 80 | #[repr(C)] 81 | pub struct CMutVec { 82 | pub ptr: *mut T, 83 | pub len: usize, 84 | pub capacity: usize, 85 | } 86 | 87 | impl IntoRawConversion for Vec { 88 | type Raw = CMutVec; 89 | type Ptr = *mut Self::Raw; 90 | 91 | fn into_raw(self) -> Result { 92 | let mut vec = self 93 | .into_iter() 94 | .map(T::into_raw) 95 | .collect::, Error>>()?; 96 | let mut_vec = CMutVec { 97 | ptr: vec.as_mut_ptr(), 98 | len: vec.len(), 99 | capacity: vec.capacity(), 100 | }; 101 | std::mem::forget(vec); 102 | Ok(mut_vec) 103 | } 104 | 105 | fn into_ptr(self) -> Result { 106 | box_into_ptr(self) 107 | } 108 | } 109 | 110 | impl FromRawConversion for Vec { 111 | type Raw = CMutVec; 112 | type Ptr = *mut Self::Raw; 113 | 114 | unsafe fn from_raw(raw: Self::Raw) -> Result { 115 | Vec::from_raw_parts(raw.ptr, raw.len as usize, raw.capacity as usize) 116 | .into_iter() 117 | .map(|value| T::from_raw(value)) 118 | .collect() 119 | } 120 | 121 | unsafe fn from_ptr(ptr: Self::Ptr) -> Result { 122 | box_from_ptr(ptr) 123 | } 124 | } 125 | 126 | impl PtrAsReference for Vec { 127 | type Raw = CVec; 128 | type Ptr = *const Self::Raw; 129 | 130 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 131 | std::slice::from_raw_parts(raw.ptr, raw.len as usize) 132 | .iter() 133 | .map(|value| T::raw_as_ref(value)) 134 | .collect() 135 | } 136 | 137 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 138 | Self::raw_as_ref(&*ptr) 139 | } 140 | } 141 | 142 | impl IntoRawConversion for String { 143 | type Raw = *mut ::libc::c_char; 144 | type Ptr = Self::Raw; 145 | 146 | fn into_raw(self) -> Result { 147 | Ok(std::ffi::CString::new(self)?.into_raw()) 148 | } 149 | 150 | fn into_ptr(self) -> Result { 151 | self.into_raw() 152 | } 153 | } 154 | 155 | impl FromRawConversion for String { 156 | type Raw = *mut ::libc::c_char; 157 | type Ptr = Self::Raw; 158 | 159 | unsafe fn from_raw(raw: Self::Raw) -> Result { 160 | Ok(std::ffi::CString::from_raw(raw).into_string()?) 161 | } 162 | 163 | unsafe fn from_ptr(ptr: Self::Ptr) -> Result { 164 | Self::from_raw(ptr) 165 | } 166 | } 167 | 168 | impl PtrAsReference for String { 169 | type Raw = *mut ::libc::c_char; 170 | type Ptr = Self::Raw; 171 | 172 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 173 | Ok(::std::ffi::CStr::from_ptr(*raw).to_str()?.to_owned()) 174 | } 175 | 176 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 177 | Self::raw_as_ref(&ptr) 178 | } 179 | } 180 | 181 | impl<'a> PtrAsReference for &'a str { 182 | type Raw = *mut ::libc::c_char; 183 | type Ptr = Self::Raw; 184 | 185 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 186 | Ok(std::ffi::CStr::from_ptr(*raw).to_str()?) 187 | } 188 | 189 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 190 | Self::raw_as_ref(&ptr) 191 | } 192 | } 193 | 194 | #[repr(C)] 195 | pub struct COption { 196 | pub ptr: *const T, 197 | } 198 | 199 | #[repr(C)] 200 | pub struct CMutOption { 201 | pub ptr: *mut T, 202 | } 203 | 204 | impl IntoRawConversion for Option { 205 | type Raw = CMutOption; 206 | type Ptr = *mut Self::Raw; 207 | 208 | fn into_raw(self) -> Result { 209 | Ok(CMutOption { 210 | ptr: if let Some(value) = self { 211 | box_into_ptr(value)? 212 | } else { 213 | ::std::ptr::null_mut() 214 | }, 215 | }) 216 | } 217 | 218 | fn into_ptr(self) -> Result { 219 | box_into_ptr(self) 220 | } 221 | } 222 | 223 | impl FromRawConversion for Option { 224 | type Raw = CMutOption; 225 | type Ptr = *mut Self::Raw; 226 | 227 | unsafe fn from_raw(raw: Self::Raw) -> Result { 228 | Ok(if !raw.ptr.is_null() { 229 | Some(box_from_ptr(raw.ptr)?) 230 | } else { 231 | None 232 | }) 233 | } 234 | 235 | unsafe fn from_ptr(ptr: Self::Ptr) -> Result { 236 | box_from_ptr(ptr) 237 | } 238 | } 239 | 240 | impl PtrAsReference for Option { 241 | type Raw = COption; 242 | type Ptr = *const Self::Raw; 243 | 244 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 245 | if let Some(value) = raw.ptr.as_ref() { 246 | Ok(Some(T::raw_as_ref(value)?)) 247 | } else { 248 | Ok(None) 249 | } 250 | } 251 | 252 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 253 | Self::raw_as_ref(&*ptr) 254 | } 255 | } 256 | 257 | #[repr(C)] 258 | pub struct CResult { 259 | pub ok: *const T, 260 | pub err: *const E, 261 | } 262 | 263 | #[repr(C)] 264 | pub struct CMutResult { 265 | pub ok: *mut T, 266 | pub err: *mut E, 267 | } 268 | 269 | impl IntoRawConversion for Result { 270 | type Raw = CMutResult; 271 | type Ptr = *mut Self::Raw; 272 | 273 | fn into_raw(self) -> Result { 274 | Ok(match self { 275 | Ok(value) => CMutResult { 276 | ok: box_into_ptr(value)?, 277 | err: std::ptr::null_mut(), 278 | }, 279 | Err(value) => CMutResult { 280 | ok: std::ptr::null_mut(), 281 | err: box_into_ptr(value)?, 282 | }, 283 | }) 284 | } 285 | 286 | fn into_ptr(self) -> Result { 287 | box_into_ptr(self) 288 | } 289 | } 290 | 291 | impl FromRawConversion for Result { 292 | type Raw = CMutResult; 293 | type Ptr = *mut Self::Raw; 294 | 295 | unsafe fn from_raw(raw: Self::Raw) -> Result { 296 | Ok(if !raw.ok.is_null() { 297 | Ok(box_from_ptr(raw.ok)?) 298 | } else { 299 | Err(box_from_ptr(raw.err)?) 300 | }) 301 | } 302 | 303 | unsafe fn from_ptr(ptr: Self::Ptr) -> Result { 304 | box_from_ptr(ptr) 305 | } 306 | } 307 | 308 | impl PtrAsReference for Result { 309 | type Raw = CResult; 310 | type Ptr = *const Self::Raw; 311 | 312 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 313 | if let Some(value) = raw.ok.as_ref() { 314 | Ok(Ok(T::raw_as_ref(value)?)) 315 | } else if let Some(value) = raw.err.as_ref() { 316 | Ok(Err(E::raw_as_ref(value)?)) 317 | } else { 318 | unreachable!() 319 | } 320 | } 321 | 322 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 323 | Self::raw_as_ref(&*ptr) 324 | } 325 | } 326 | 327 | #[repr(C)] 328 | pub struct CSlice { 329 | pub ptr: *const T, 330 | pub len: usize, 331 | } 332 | 333 | macro_rules! primitive_marshalled_type { 334 | ($($typ:ty )*) => { 335 | $( 336 | impl IntoRawConversion for $typ { 337 | type Raw = Self; 338 | type Ptr = Self::Raw; 339 | 340 | fn into_raw(self) -> Result { 341 | Ok(self) 342 | } 343 | 344 | fn into_ptr(self) -> Result { 345 | Ok(self) 346 | } 347 | } 348 | 349 | impl FromRawConversion for $typ { 350 | type Raw = Self; 351 | type Ptr = Self::Raw; 352 | 353 | unsafe fn from_raw(raw: Self::Raw) -> Result { 354 | Ok(raw) 355 | } 356 | 357 | unsafe fn from_ptr(ptr: Self::Ptr) -> Result { 358 | Ok(ptr) 359 | } 360 | } 361 | 362 | impl PtrAsReference for $typ { 363 | type Raw = Self; 364 | type Ptr = Self::Raw; 365 | 366 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 367 | Ok(raw.clone()) 368 | } 369 | 370 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 371 | Ok(ptr) 372 | } 373 | } 374 | 375 | impl<'a> PtrAsReference for &'a [$typ] { 376 | type Raw = CSlice<$typ>; 377 | type Ptr = *const Self::Raw; 378 | 379 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 380 | Ok(std::slice::from_raw_parts(raw.ptr, raw.len as usize)) 381 | } 382 | 383 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 384 | Self::raw_as_ref(&*ptr) 385 | } 386 | } 387 | )* 388 | 389 | }; 390 | } 391 | 392 | primitive_marshalled_type!( 393 | i8 394 | i16 395 | i32 396 | i64 397 | u8 398 | u16 399 | u32 400 | u64 401 | f32 402 | f64 403 | isize 404 | usize 405 | ); 406 | 407 | impl IntoRawConversion for bool { 408 | type Raw = i8; 409 | type Ptr = Self::Raw; 410 | 411 | fn into_raw(self) -> Result { 412 | Ok(self as Self::Raw) 413 | } 414 | 415 | fn into_ptr(self) -> Result { 416 | Ok(self as Self::Ptr) 417 | } 418 | } 419 | 420 | impl FromRawConversion for bool { 421 | type Raw = i8; 422 | type Ptr = Self::Raw; 423 | 424 | unsafe fn from_raw(raw: Self::Raw) -> Result { 425 | Ok(raw != 0) 426 | } 427 | 428 | unsafe fn from_ptr(ptr: Self::Ptr) -> Result { 429 | Ok(ptr != 0) 430 | } 431 | } 432 | 433 | impl PtrAsReference for bool { 434 | type Raw = i8; 435 | type Ptr = Self::Raw; 436 | 437 | unsafe fn raw_as_ref(raw: &Self::Raw) -> Result { 438 | Ok(*raw != 0) 439 | } 440 | 441 | unsafe fn ptr_as_ref(ptr: Self::Ptr) -> Result { 442 | Ok(ptr != 0) 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /lua-marshalling/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | use lazy_static::lazy_static; 3 | 4 | // procedural crates do not allow exporting code themselves, so re-export the crate and 5 | // implement the library here. 6 | pub use derive_lua_marshalling::*; 7 | 8 | fn is_valid_type_prefix(string: &str) -> bool { 9 | string.is_empty() || { 10 | let mut bytes = string.as_bytes().iter(); 11 | (match bytes.next().unwrap() { 12 | #[rustfmt::skip] 13 | | b'a'..=b'z' 14 | | b'A'..=b'Z' 15 | | b'_' 16 | => true, 17 | _ => false, 18 | }) && bytes.all(|byte| match *byte { 19 | #[rustfmt::skip] 20 | | b'a'..=b'z' 21 | | b'A'..=b'Z' 22 | | b'0'..=b'9' 23 | | b'_' 24 | => true, 25 | _ => false, 26 | }) 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_is_valid_type_prefix() { 32 | assert!(is_valid_type_prefix("")); 33 | assert!(is_valid_type_prefix("__abc__")); 34 | assert!(is_valid_type_prefix("__abc_3_")); 35 | assert!(is_valid_type_prefix("F__abc_3_")); 36 | assert!(is_valid_type_prefix("F")); 37 | assert!(!is_valid_type_prefix("4F__abc_3_")); 38 | assert!(!is_valid_type_prefix("F_-abc_3_")); 39 | } 40 | 41 | fn prefixed(string: &str) -> String { 42 | lazy_static! { 43 | static ref PACKAGE_PREFIX: String = { 44 | std::env::var("RUST_LUA_FFI_TYPE_PREFIX") 45 | .map(|name| { 46 | assert!( 47 | is_valid_type_prefix(&name), 48 | format!( 49 | "Invalid value ({:#?}) supplied for \ 50 | RUST_LUA_FFI_TYPE_PREFIX. Value must contain only \ 51 | ASCII alpha-numeric characters or underscores and \ 52 | cannot start with a number", 53 | name, 54 | ) 55 | ); 56 | format!("{}__", name) 57 | }) 58 | .unwrap_or_default() 59 | }; 60 | } 61 | 62 | format!("{}{}", *PACKAGE_PREFIX, string) 63 | } 64 | 65 | #[derive(Debug, Clone)] 66 | pub struct TypeDescription { 67 | pub typeid: std::any::TypeId, 68 | pub dependencies: std::collections::HashSet, 69 | pub typedeclaration: fn() -> String, 70 | pub metatype: fn() -> String, 71 | } 72 | 73 | pub trait Type { 74 | // Defaults to false since all derived types outside of this module _won't_ 75 | // be primitive. 76 | const IS_PRIMITIVE: bool = false; 77 | 78 | fn typename() -> String; 79 | fn c_typename() -> String { 80 | Self::typename() 81 | } 82 | fn typedeclaration() -> String { 83 | "".to_owned() 84 | } 85 | fn metatype() -> String { 86 | if Self::IS_PRIMITIVE { 87 | primitive_type_metatype::() 88 | } else { 89 | ptr_type_metatype::() 90 | } 91 | } 92 | fn dependencies() -> Dependencies { 93 | vec![].into_iter().collect() 94 | } 95 | fn c_function_argument() -> String; 96 | fn c_mut_function_argument() -> String; 97 | fn prefixed_typename() -> String { 98 | if Self::IS_PRIMITIVE { 99 | Self::typename() 100 | } else { 101 | prefixed(&Self::typename()) 102 | } 103 | } 104 | fn prefixed_c_typename() -> String { 105 | if Self::IS_PRIMITIVE { 106 | Self::c_typename() 107 | } else { 108 | prefixed(&Self::c_typename()) 109 | } 110 | } 111 | } 112 | 113 | pub trait FromRawConversion: Type { 114 | fn function() -> String; 115 | fn gc() -> bool; 116 | } 117 | 118 | pub trait IntoRawConversion: Type { 119 | fn function() -> String; 120 | fn create_pointer() -> String; 121 | fn create_array() -> String; 122 | } 123 | 124 | pub type Dependencies = std::collections::HashMap; 125 | 126 | pub fn make_dependencies() -> Dependencies { 127 | let typeid = std::any::TypeId::of::(); 128 | let mut dependencies = T::dependencies(); 129 | let type_dependencies = dependencies.keys().cloned().collect(); 130 | dependencies.insert( 131 | typeid, 132 | TypeDescription { 133 | typeid: std::any::TypeId::of::(), 134 | dependencies: type_dependencies, 135 | typedeclaration: T::typedeclaration, 136 | metatype: T::metatype, 137 | }, 138 | ); 139 | dependencies 140 | } 141 | 142 | pub fn dependency_sorted_type_descriptions<'a>( 143 | dependencies: &'a Dependencies, 144 | ) -> Vec<&'a TypeDescription> { 145 | let mut remaining: std::collections::HashSet<_> = dependencies.keys().cloned().collect(); 146 | let mut sorted_dependencies = Vec::new(); 147 | while !remaining.is_empty() { 148 | let typ = { 149 | let (typ, dependencies) = remaining 150 | .iter() 151 | .map(|typ| (typ, dependencies.get(typ).unwrap())) 152 | .find(|&(_, dependencies)| dependencies.dependencies.is_disjoint(&remaining)) 153 | .unwrap(); 154 | sorted_dependencies.push(dependencies); 155 | *typ 156 | }; 157 | assert!(remaining.remove(&typ)); 158 | } 159 | sorted_dependencies 160 | } 161 | 162 | pub fn ptr_type_metatype() -> String { 163 | format!( 164 | r#" 165 | local __typename_{self_typename} = ffi.metatype("{c_typename}", {{}}) 166 | local __const_c_typename_{self_typename} = ffi.typeof("const {c_typename}[?]") 167 | local __c_function_argument_{self_typename} = ffi.typeof("{c_function_argument}[?]") 168 | local __c_mut_function_argument_{self_typename} = ffi.typeof("{c_mut_function_argument}[?]") 169 | "#, 170 | self_typename = T::typename(), 171 | c_typename = T::prefixed_c_typename(), 172 | c_function_argument = T::c_function_argument(), 173 | c_mut_function_argument = T::c_mut_function_argument() 174 | ) 175 | } 176 | 177 | pub fn primitive_type_metatype() -> String { 178 | format!( 179 | r#" 180 | local __const_c_typename_{self_typename} = ffi.typeof("const {c_typename}[?]") 181 | local __c_function_argument_{self_typename} = ffi.typeof("{c_function_argument}[?]") 182 | local __c_mut_function_argument_{self_typename} = ffi.typeof("{c_mut_function_argument}[?]") 183 | "#, 184 | self_typename = T::typename(), 185 | c_typename = T::c_typename(), 186 | c_function_argument = T::c_function_argument(), 187 | c_mut_function_argument = T::c_mut_function_argument() 188 | ) 189 | } 190 | 191 | pub fn ptr_type_create_pointer() -> String { 192 | T::function() 193 | } 194 | 195 | pub fn ptr_type_create_array() -> String { 196 | format!( 197 | r#"function(value) 198 | local result = {{}} 199 | local f = {function} 200 | for i, value in pairs(value) do 201 | local tmp = f(value) 202 | result[i] = tmp[0] 203 | end 204 | return __const_c_typename_{typename}(#result, result) 205 | end"#, 206 | function = T::function(), 207 | typename = ::typename() 208 | ) 209 | } 210 | 211 | pub fn immediate_type_create_array() -> String { 212 | format!( 213 | r#"function(value) 214 | local result = {{}} 215 | local f = {function} 216 | for i, value in pairs(value) do 217 | result[i] = f(value) 218 | end 219 | return __const_c_typename_{typename}(#result, result) 220 | end"#, 221 | function = T::function(), 222 | typename = ::typename() 223 | ) 224 | } 225 | 226 | fn primitive_type_create_pointer() -> String { 227 | format!( 228 | r#"function(value) 229 | return __const_c_typename_{typename}(1, {{ value }}) 230 | end"#, 231 | typename = ::typename() 232 | ) 233 | } 234 | 235 | fn primitive_type_create_array() -> String { 236 | format!( 237 | r#"function(value) 238 | return __const_c_typename_{typename}(#value, value) 239 | end"#, 240 | typename = T::typename() 241 | ) 242 | } 243 | 244 | impl Type for Option { 245 | const IS_PRIMITIVE: bool = false; 246 | fn typename() -> String { 247 | format!("Option_{}", T::typename()) 248 | } 249 | fn typedeclaration() -> String { 250 | format!( 251 | r#"typedef struct {{ 252 | const {c_typename} *ptr; 253 | }} {self_typename};"#, 254 | c_typename = ::prefixed_c_typename(), 255 | self_typename = Self::prefixed_typename() 256 | ) 257 | } 258 | fn dependencies() -> Dependencies { 259 | make_dependencies::() 260 | } 261 | fn c_function_argument() -> String { 262 | format!("const {}*", Self::prefixed_c_typename()) 263 | } 264 | fn c_mut_function_argument() -> String { 265 | format!("{}*", Self::prefixed_c_typename()) 266 | } 267 | } 268 | 269 | impl FromRawConversion for Option { 270 | fn function() -> String { 271 | format!( 272 | r#"function(value) 273 | if value.ptr ~= nil then 274 | local f = {function} 275 | return f(value.ptr[0]) 276 | else 277 | return nil 278 | end 279 | end"#, 280 | function = T::function() 281 | ) 282 | } 283 | fn gc() -> bool { 284 | true 285 | } 286 | } 287 | 288 | impl IntoRawConversion for Option { 289 | fn function() -> String { 290 | format!( 291 | r#" 292 | function(value) 293 | local f = {create_pointer} 294 | if value ~= nil then 295 | return __typename_{self_typename}(f(value)) 296 | else 297 | return __typename_{self_typename}(nil) 298 | end 299 | end 300 | "#, 301 | self_typename = ::typename(), 302 | create_pointer = ::create_pointer() 303 | ) 304 | } 305 | fn create_pointer() -> String { 306 | ptr_type_create_pointer::() 307 | } 308 | fn create_array() -> String { 309 | panic!("Array of Option are unreliable and have been disabled"); 310 | } 311 | } 312 | 313 | impl Type for Result { 314 | const IS_PRIMITIVE: bool = false; 315 | 316 | fn typename() -> String { 317 | format!( 318 | "Result_{T_typename}_{E_typename}", 319 | T_typename = T::typename(), 320 | E_typename = E::typename() 321 | ) 322 | } 323 | fn typedeclaration() -> String { 324 | format!( 325 | r#"typedef struct {{ 326 | const {T_c_typename} *ok; 327 | const {E_c_typename} *err; 328 | }} {self_typename};"#, 329 | T_c_typename = T::prefixed_c_typename(), 330 | E_c_typename = E::prefixed_c_typename(), 331 | self_typename = Self::prefixed_typename() 332 | ) 333 | } 334 | fn dependencies() -> Dependencies { 335 | let mut dependencies = make_dependencies::(); 336 | dependencies.extend(make_dependencies::()); 337 | dependencies 338 | } 339 | fn c_function_argument() -> String { 340 | format!("const {}*", Self::prefixed_c_typename()) 341 | } 342 | fn c_mut_function_argument() -> String { 343 | format!("{}*", Self::prefixed_c_typename()) 344 | } 345 | } 346 | 347 | impl FromRawConversion 348 | for Result 349 | { 350 | fn function() -> String { 351 | format!( 352 | r#"function(value) 353 | if value.ok ~= nil then 354 | local f = {T_function} 355 | return f(value.ok[0]) 356 | else 357 | local f = {E_function} 358 | return nil, f(value.err[0]) 359 | end 360 | end"#, 361 | T_function = T::function(), 362 | E_function = E::function() 363 | ) 364 | } 365 | fn gc() -> bool { 366 | true 367 | } 368 | } 369 | 370 | impl Type for Vec { 371 | const IS_PRIMITIVE: bool = false; 372 | 373 | fn typename() -> String { 374 | format!("Vec_{}", T::typename()) 375 | } 376 | fn typedeclaration() -> String { 377 | format!( 378 | r#"typedef struct {{ 379 | const {c_typename} *ptr; 380 | size_t len; 381 | size_t capacity; 382 | }} {self_typename};"#, 383 | c_typename = ::prefixed_c_typename(), 384 | self_typename = Self::prefixed_typename() 385 | ) 386 | } 387 | fn dependencies() -> Dependencies { 388 | make_dependencies::() 389 | } 390 | fn c_function_argument() -> String { 391 | format!("const {}*", ::prefixed_c_typename()) 392 | } 393 | fn c_mut_function_argument() -> String { 394 | format!("{}*", ::prefixed_c_typename()) 395 | } 396 | } 397 | 398 | impl FromRawConversion for Vec { 399 | fn function() -> String { 400 | format!( 401 | r#"function(value) 402 | local ret = {{}} 403 | local len = tonumber(value.len) 404 | local f = {function} 405 | for i = 1,len do 406 | ret[i] = f(value.ptr[i - 1]) 407 | end 408 | return ret 409 | end"#, 410 | function = T::function() 411 | ) 412 | } 413 | fn gc() -> bool { 414 | true 415 | } 416 | } 417 | 418 | impl IntoRawConversion for Vec { 419 | fn function() -> String { 420 | format!( 421 | r#" 422 | function(value) 423 | if type(value) == "string" then 424 | return __typename_{self_typename}(value, #value) 425 | else 426 | local f = {create_array} 427 | return __typename_{self_typename}(f(value), #value, 0) 428 | end 429 | end 430 | "#, 431 | self_typename = ::typename(), 432 | create_array = ::create_array() 433 | ) 434 | } 435 | fn create_pointer() -> String { 436 | ptr_type_create_pointer::() 437 | } 438 | fn create_array() -> String { 439 | immediate_type_create_array::() 440 | } 441 | } 442 | 443 | impl Type for String { 444 | const IS_PRIMITIVE: bool = true; 445 | 446 | fn c_typename() -> String { 447 | "char *".to_owned() 448 | } 449 | fn typename() -> String { 450 | "__string_ptr".to_owned() 451 | } 452 | fn c_function_argument() -> String { 453 | format!("const {}", Self::c_typename()) 454 | } 455 | fn c_mut_function_argument() -> String { 456 | ::c_typename() 457 | } 458 | } 459 | 460 | impl FromRawConversion for String { 461 | fn function() -> String { 462 | "ffi.string".to_owned() 463 | } 464 | fn gc() -> bool { 465 | true 466 | } 467 | } 468 | 469 | impl IntoRawConversion for String { 470 | fn function() -> String { 471 | "function(value) return value end".to_owned() 472 | } 473 | fn create_pointer() -> String { 474 | primitive_type_create_pointer::() 475 | } 476 | fn create_array() -> String { 477 | primitive_type_create_array::() 478 | } 479 | } 480 | 481 | macro_rules! primitive_lua_from_native { 482 | ($($typ:ty)*) => { 483 | $( 484 | impl Type for $typ { 485 | const IS_PRIMITIVE: bool = true; 486 | 487 | fn typename() -> String { 488 | stringify!($typ).to_owned() 489 | } 490 | fn c_typename() -> String { 491 | stringify!($typ).to_owned() 492 | } 493 | fn c_function_argument() -> String { 494 | ::c_typename() 495 | } 496 | fn c_mut_function_argument() -> String { 497 | ::c_typename() 498 | } 499 | } 500 | 501 | impl FromRawConversion for $typ { 502 | fn function() -> String { 503 | "function(value) return value end".to_owned() 504 | } 505 | fn gc() -> bool { 506 | false 507 | } 508 | } 509 | 510 | impl IntoRawConversion for $typ { 511 | 512 | fn function() -> String { 513 | "function(value) return value end".to_owned() 514 | } 515 | fn create_pointer() -> String { 516 | primitive_type_create_pointer::() 517 | } 518 | fn create_array() -> String { 519 | primitive_type_create_array::() 520 | } 521 | } 522 | )* 523 | }; 524 | } 525 | 526 | macro_rules! primitive_slice_lua_native { 527 | ($($typ:ty)*) => { 528 | $( 529 | impl<'a> Type for &'a [$typ] { 530 | const IS_PRIMITIVE: bool = false; 531 | 532 | fn typename() -> String { 533 | format!("Slice_{}", stringify!($typ)) 534 | } 535 | 536 | fn typedeclaration() -> String { 537 | format!(r#"typedef struct {{ 538 | const {c_name} *ptr; 539 | size_t len; 540 | }} {self_typename};"#, 541 | c_name = stringify!($typ), 542 | self_typename = Self::prefixed_typename()) 543 | } 544 | fn c_function_argument() -> String { 545 | format!("const {}*", Self::prefixed_c_typename()) 546 | } 547 | fn c_mut_function_argument() -> String { 548 | // Mutable not supported 549 | Self::c_function_argument() 550 | } 551 | } 552 | )* 553 | }; 554 | } 555 | 556 | macro_rules! primitive_slice_lua_to_native { 557 | ($($typ:ty)*) => { 558 | $( 559 | impl<'a> IntoRawConversion for &'a [$typ] { 560 | 561 | fn function() -> String { 562 | format!( 563 | r#"function(value) 564 | local result = {{}} 565 | for i, value in pairs(value) do 566 | result[i] = value 567 | end 568 | return __typename_{self_typename}(__c_function_argument_{typename}(#result, result), #result) 569 | end"#, 570 | self_typename = Self::typename(), 571 | typename = <$typ as Type>::typename()) 572 | } 573 | fn create_pointer() -> String { 574 | ptr_type_create_pointer::() 575 | } 576 | fn create_array() -> String { 577 | ptr_type_create_array::() 578 | } 579 | } 580 | )* 581 | }; 582 | } 583 | 584 | #[allow(non_camel_case_types)] 585 | type int16_t = i16; 586 | #[allow(non_camel_case_types)] 587 | type int32_t = i32; 588 | #[allow(non_camel_case_types)] 589 | type int64_t = i64; 590 | #[allow(non_camel_case_types)] 591 | type int8_t = i8; 592 | #[allow(non_camel_case_types)] 593 | type uint16_t = u16; 594 | #[allow(non_camel_case_types)] 595 | type uint32_t = u32; 596 | #[allow(non_camel_case_types)] 597 | type uint64_t = u64; 598 | #[allow(non_camel_case_types)] 599 | type uint8_t = u8; 600 | #[allow(non_camel_case_types)] 601 | type size_t = usize; 602 | #[allow(non_camel_case_types)] 603 | type ssize_t = isize; 604 | 605 | #[allow(non_camel_case_types)] 606 | type float = f32; 607 | #[allow(non_camel_case_types)] 608 | type double = f64; 609 | 610 | primitive_lua_from_native!( 611 | int8_t 612 | int16_t 613 | int32_t 614 | int64_t 615 | uint8_t 616 | uint16_t 617 | uint32_t 618 | uint64_t 619 | ssize_t 620 | size_t 621 | float 622 | double 623 | ); 624 | 625 | primitive_slice_lua_native!( 626 | int8_t 627 | int16_t 628 | int32_t 629 | int64_t 630 | uint8_t 631 | uint16_t 632 | uint32_t 633 | uint64_t 634 | ssize_t 635 | size_t 636 | float 637 | double 638 | ); 639 | 640 | primitive_slice_lua_to_native!( 641 | int8_t 642 | int16_t 643 | int32_t 644 | int64_t 645 | uint16_t 646 | uint32_t 647 | uint64_t 648 | ssize_t 649 | size_t 650 | float 651 | double 652 | ); 653 | 654 | impl<'a> IntoRawConversion for &'a [u8] { 655 | fn function() -> String { 656 | format!( 657 | r#"function(value) 658 | if type(value) == "string" then 659 | return __typename_{self_typename}(value, #value) 660 | else 661 | local result = {{}} 662 | for i, value in pairs(value) do 663 | result[i] = value 664 | end 665 | return __typename_{self_typename}( 666 | __c_function_argument_{typename}(#result, result), #result) 667 | end 668 | end"#, 669 | self_typename = ::typename(), 670 | typename = ::typename() 671 | ) 672 | } 673 | fn create_pointer() -> String { 674 | ptr_type_create_pointer::() 675 | } 676 | fn create_array() -> String { 677 | ptr_type_create_array::() 678 | } 679 | } 680 | 681 | impl<'a> Type for &'a str { 682 | const IS_PRIMITIVE: bool = true; 683 | 684 | fn typename() -> String { 685 | "_str_ptr__".to_owned() 686 | } 687 | fn c_typename() -> String { 688 | "char *".to_owned() 689 | } 690 | fn c_function_argument() -> String { 691 | format!("const {}", Self::c_typename()) 692 | } 693 | fn c_mut_function_argument() -> String { 694 | // Mutable not supported 695 | Self::c_function_argument() 696 | } 697 | } 698 | 699 | impl<'a> IntoRawConversion for &'a str { 700 | fn function() -> String { 701 | "function(value) return value end".to_owned() 702 | } 703 | fn create_pointer() -> String { 704 | primitive_type_create_pointer::() 705 | } 706 | fn create_array() -> String { 707 | primitive_type_create_array::() 708 | } 709 | } 710 | 711 | impl Type for bool { 712 | const IS_PRIMITIVE: bool = true; 713 | 714 | fn typename() -> String { 715 | stringify!(bool).to_owned() 716 | } 717 | fn c_typename() -> String { 718 | stringify!(int8_t).to_owned() 719 | } 720 | fn c_function_argument() -> String { 721 | Self::c_typename() 722 | } 723 | fn c_mut_function_argument() -> String { 724 | Self::c_typename() 725 | } 726 | } 727 | 728 | impl FromRawConversion for bool { 729 | fn function() -> String { 730 | "function(value) return value ~= 0 end".to_owned() 731 | } 732 | fn gc() -> bool { 733 | false 734 | } 735 | } 736 | 737 | impl IntoRawConversion for bool { 738 | fn function() -> String { 739 | "function(value) return value and 1 or 0 end".to_owned() 740 | } 741 | fn create_pointer() -> String { 742 | primitive_type_create_pointer::() 743 | } 744 | fn create_array() -> String { 745 | primitive_type_create_array::() 746 | } 747 | } 748 | --------------------------------------------------------------------------------