mirror of
https://github.com/lune-org/lune.git
synced 2025-04-04 10:30:54 +01:00
Add lune-std-ffi crate (#243)
This commit is contained in:
parent
1d4d1635eb
commit
af08c59e3b
23 changed files with 1191 additions and 1 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -23,5 +23,9 @@
|
|||
},
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
},
|
||||
"files.associations": {
|
||||
"*.inc": "c",
|
||||
"random": "c"
|
||||
}
|
||||
}
|
||||
|
|
54
Cargo.lock
generated
54
Cargo.lock
generated
|
@ -786,6 +786,29 @@ dependencies = [
|
|||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bc2c7ed06fd72a8513ded8d0d2f6fd2655a85d6885c48cae8625d80faf28c03"
|
||||
dependencies = [
|
||||
"dlopen2_derive",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2_derive"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
|
@ -1426,6 +1449,25 @@ version = "0.2.155"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libffi"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce826c243048e3d5cec441799724de52e2d42f820468431fc3fceee2341871e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libffi-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libffi-sys"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f36115160c57e8529781b4183c2bb51fdc1f6d6d1ed345591d84be7703befb3c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
|
@ -1557,6 +1599,7 @@ name = "lune-std"
|
|||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"lune-std-datetime",
|
||||
"lune-std-ffi",
|
||||
"lune-std-fs",
|
||||
"lune-std-luau",
|
||||
"lune-std-net",
|
||||
|
@ -1585,6 +1628,17 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lune-std-ffi"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"dlopen2",
|
||||
"libffi",
|
||||
"lune-utils",
|
||||
"mlua",
|
||||
"mlua-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lune-std-fs"
|
||||
version = "0.1.2"
|
||||
|
|
|
@ -15,6 +15,7 @@ members = [
|
|||
"crates/lune-std-serde",
|
||||
"crates/lune-std-stdio",
|
||||
"crates/lune-std-task",
|
||||
"crates/lune-std-ffi",
|
||||
"crates/lune-utils",
|
||||
"crates/mlua-luau-scheduler",
|
||||
]
|
||||
|
|
22
crates/lune-std-ffi/Cargo.toml
Normal file
22
crates/lune-std-ffi/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "lune-std-ffi"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - FFI"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
mlua-sys = { version = "0.6.2", features = ["luau"] }
|
||||
dlopen2 = "0.6"
|
||||
|
||||
libffi = "3.2.0"
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
129
crates/lune-std-ffi/readme.md
Normal file
129
crates/lune-std-ffi/readme.md
Normal file
|
@ -0,0 +1,129 @@
|
|||
TODO: rewrite docs
|
||||
|
||||
# Raw
|
||||
|
||||
Data received from external. You can move this data into a box, use it as a ref, or change it directly to a Lua value.
|
||||
The raw data is not on Lua's heap.
|
||||
|
||||
Raw:toRef()
|
||||
Convert data into ref. it allocate new lua userdata
|
||||
|
||||
Raw:toBox()
|
||||
Convert data into box. it allocate new lua userdata
|
||||
|
||||
Raw:intoBox()
|
||||
Raw:intoRef()
|
||||
|
||||
See type:fromRaw()
|
||||
|
||||
# Box
|
||||
|
||||
`ffi.box(size)`
|
||||
|
||||
Create new userdata with sized by `size` argument. Box is untyped, and have no ABI information. You can write some data into box with `type`
|
||||
|
||||
All operation with box will boundary checked. GC will free heap well.
|
||||
|
||||
일반적으로 포인터를 넘겨주기 위해서 사용됩니다. 박스의 공간은 ref 할 수 있으며. 함수를 수행한 후 루아에서 읽어볼 수 있습니다.
|
||||
|
||||
## :zero()
|
||||
박스를 0 으로 채워넣습니다. 기본적으로 박스는 초기화될 때 0 으로 채워지기 때문에 박스를 다시 0 으로 초기화하고 싶을 경우에 사용하십시오.
|
||||
|
||||
## :copy(targetbox,size,offset?=0,targetoffset?=0)
|
||||
박스 안의 값을 다른 박스에 복사합니다. 바운더리가 확인되어지므로 안전합니다.
|
||||
|
||||
## .size
|
||||
이 박스의 크기입니다.
|
||||
|
||||
## :ref(offset?=0) => ref
|
||||
이 박스를 참조합니다. 참조가 살아있는 동안 박스는 수거되지 않습니다. 일반적으로 외부의 함수에 포인터를 넘겨주기 위해서 사용됩니다.
|
||||
|
||||
## more stuffs (not planned at this time)
|
||||
|
||||
ref=>buffer conversion, or bit/byte related?
|
||||
|
||||
# Ref (Unsafe)
|
||||
|
||||
바운더리를 처리하지 않는 포인터입니다. 외부에서 받은 포인터, 또는 박스로부터 만들어진 포인터입니다.
|
||||
ref 는 바운더리를 검사하지 않으므로 안전하지 않습니다.
|
||||
|
||||
## :offset(bytes)
|
||||
|
||||
이 ref 와 상대적인 위치에 있는 ref 를 구합니다.
|
||||
|
||||
## :writefromRef()
|
||||
다른 ref 안의 값을 읽어와 이 ref 안에 씁니다. 아래와 비슷한 연산을 합니다
|
||||
```c
|
||||
int a = 100,b;
|
||||
```
|
||||
|
||||
## :writefromBox()
|
||||
box 값을 읽어와서 쓰기
|
||||
|
||||
# Type
|
||||
|
||||
`type` is abstract class that helps encoding data into `box` or decode data from `box`
|
||||
|
||||
## :toBox(luavalue)
|
||||
Convert lua value to box. box will sized with `type.size`
|
||||
|
||||
## :fromBox(box,offset?=0)
|
||||
Read data from box, and convert into lua value.
|
||||
Boundary will checked
|
||||
|
||||
## :intoBox(luavalue,box,offset?=0)
|
||||
Convert lua value, and write into box
|
||||
Boundary will checked
|
||||
|
||||
## :fromRef(ref,offset?=0)
|
||||
포인터가 가르키는 곳의 데이터를 읽어서 루아의 데이터로 변환합니다.
|
||||
|
||||
## :intoRef(luavalue,ref,offset?=0)
|
||||
포인터가 가르키는 곳에 데이터를 작성합니다.
|
||||
|
||||
## :fromRaw(raw,offset?=0)
|
||||
|
||||
|
||||
## :ptr() -> Ptr
|
||||
Get pointer type
|
||||
|
||||
## :arr(len) -> Arr
|
||||
Get array type
|
||||
|
||||
## .size
|
||||
|
||||
Byte size of this type. you can initialize box with
|
||||
|
||||
## :cast(box,type) TODO
|
||||
|
||||
# Ptr
|
||||
Pointer type of some type.
|
||||
|
||||
Ptr is not data converter. It only works for type hint of `struct` or `fn`
|
||||
|
||||
## .inner
|
||||
Inner type
|
||||
|
||||
## .size
|
||||
Size of `usize`
|
||||
|
||||
:ptr()
|
||||
:arr()
|
||||
|
||||
## Arr
|
||||
|
||||
## Void
|
||||
|
||||
`ffi.void`
|
||||
|
||||
Zero sized type.
|
||||
|
||||
## Fn
|
||||
Prototype type of some function. converts lua function into native function pointer or native function pointer into lua function.
|
||||
|
||||
`ffi.fn({ type }, type) -> fn`
|
||||
|
||||
:toLua( ref ) -> luafunction
|
||||
:toBox( luafunction ) -> ref
|
||||
|
||||
> TODO: rust, and another ABI support
|
84
crates/lune-std-ffi/src/association.rs
Normal file
84
crates/lune-std-ffi/src/association.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
// This is a small library that helps you set the dependencies of data in Lua.
|
||||
// In FFI, there is often data that is dependent on other data.
|
||||
// However, if you use user_value to inform Lua of the dependency,
|
||||
// a table will be created for each userdata.
|
||||
// To prevent this, we place a weak reference table in the registry
|
||||
// and simulate what mlua does.
|
||||
// Since mlua does not provide Lua state (private),
|
||||
// uservalue operations cannot be performed directly,
|
||||
// so this is the best solution for now.
|
||||
// If the dependency is deep, the value may be completely destroyed when
|
||||
// gc is performed multiple times. As an example, there is the following case:
|
||||
//
|
||||
// ffi.i32:ptr():ptr()
|
||||
// box:ref():ref()
|
||||
//
|
||||
// Since the outermost pointer holds the definition for the pointer
|
||||
// type inside it, only the outermost type will be removed on the first gc.
|
||||
// It doesn't matter much. But if there is a cleaner way, we should choose it
|
||||
use mlua::prelude::*;
|
||||
|
||||
// Forces 'associated' to persist as long as 'value' is alive.
|
||||
// 'value' can only hold one value. If you want to keep something else,
|
||||
// use a table with a different name.
|
||||
// You can delete the relationship by changing 'associated' to nil
|
||||
pub fn set_association<'lua, T, U>(
|
||||
lua: &'lua Lua,
|
||||
regname: &str,
|
||||
value: T,
|
||||
associated: U,
|
||||
) -> LuaResult<()>
|
||||
where
|
||||
T: IntoLua<'lua>,
|
||||
U: IntoLua<'lua>,
|
||||
{
|
||||
let table = match lua.named_registry_value::<LuaValue>(regname)? {
|
||||
LuaValue::Nil => {
|
||||
let table = lua.create_table()?;
|
||||
lua.set_named_registry_value(regname, table.clone())?;
|
||||
let meta = lua.create_table()?;
|
||||
meta.set("__mode", "k")?;
|
||||
table.set_metatable(Some(meta));
|
||||
table
|
||||
}
|
||||
LuaValue::Table(t) => t,
|
||||
_ => panic!(""),
|
||||
};
|
||||
|
||||
table.set(value, associated)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// returns the Lua value that 'value' keeps.
|
||||
// If there is no table in registry, it returns None.
|
||||
// If there is no value in table, it returns LuaNil.
|
||||
pub fn get_association<'lua, T>(
|
||||
lua: &'lua Lua,
|
||||
regname: &str,
|
||||
value: T,
|
||||
) -> LuaResult<Option<LuaValue<'lua>>>
|
||||
where
|
||||
T: IntoLua<'lua>,
|
||||
{
|
||||
match lua.named_registry_value::<LuaValue>(regname)? {
|
||||
LuaValue::Nil => Ok(None),
|
||||
LuaValue::Table(t) => Ok(Some(t.get(value)?)),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Allows reading of registry tables for debugging.
|
||||
// This helps keep track of data being gc'd.
|
||||
// However, for security and safety reasons,
|
||||
// this will not be allowed unless it is a debug build.
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn get_table<'lua>(lua: &'lua Lua, regname: &str) -> LuaResult<Option<LuaTable<'lua>>> {
|
||||
match lua.named_registry_value::<LuaValue>(regname)? {
|
||||
LuaValue::Nil => Ok(None),
|
||||
LuaValue::Table(t) => Ok(Some(t)),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
24
crates/lune-std-ffi/src/carr.rs
Normal file
24
crates/lune-std-ffi/src/carr.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use libffi::middle::Type;
|
||||
use mlua::prelude::*;
|
||||
|
||||
// This is a series of some type.
|
||||
// It provides the final size and the offset of the index,
|
||||
// but does not allow multidimensional arrays because of API complexity.
|
||||
// However, multidimensional arrays are not impossible to implement
|
||||
// because they are a series of transcribed one-dimensional arrays.
|
||||
|
||||
// See: https://stackoverflow.com/a/43525176
|
||||
|
||||
struct CArr {
|
||||
libffi_type: Type,
|
||||
length: usize,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl CArr {
|
||||
fn new(libffi_type: Type, length: usize) {
|
||||
Self { libffi_type }
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for CArr {}
|
45
crates/lune-std-ffi/src/cfn.rs
Normal file
45
crates/lune-std-ffi/src/cfn.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use libffi::middle::{Cif, Type};
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::ctype::{libffi_type_from_userdata, libffi_types_from_table};
|
||||
|
||||
// cfn is a type declaration for a function.
|
||||
// Basically, when calling an external function, this type declaration
|
||||
// is referred to and type conversion is automatically assisted.
|
||||
|
||||
// However, in order to save on type conversion costs,
|
||||
// users keep values they will use continuously in a box and use them multiple times.
|
||||
// Alternatively, if the types are the same,you can save the cost of creating
|
||||
// a new space by directly passing FfiRaw,
|
||||
// the result value of another function or the argument value of the callback.
|
||||
|
||||
// Defining cfn simply lists the function's actual argument positions and conversions.
|
||||
// You must decide how to process the data in Lua.
|
||||
|
||||
// The name cfn is intentional. This is because any *c_void is
|
||||
// moved to a Lua function or vice versa.
|
||||
|
||||
pub struct CFn {
|
||||
libffi_cif: Cif,
|
||||
args: Vec<Type>,
|
||||
ret: Type,
|
||||
}
|
||||
|
||||
impl CFn {
|
||||
pub fn new(args: Vec<Type>, ret: Type) -> Self {
|
||||
let libffi_cif = Cif::new(args.clone(), ret.clone());
|
||||
Self {
|
||||
libffi_cif,
|
||||
args,
|
||||
ret,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_lua_table(args: LuaTable, ret: LuaAnyUserData) -> LuaResult<Self> {
|
||||
let args = libffi_types_from_table(&args)?;
|
||||
let ret = libffi_type_from_userdata(&ret)?;
|
||||
Ok(Self::new(args, ret))
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for CFn {}
|
6
crates/lune-std-ffi/src/cstring.rs
Normal file
6
crates/lune-std-ffi/src/cstring.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
// This is a string type that can be given to an external function.
|
||||
// To be exact, it converts the Lua string into a c_char array and puts it in the box.
|
||||
// For this part, initially, i wanted to allow box("lua string"),
|
||||
// but separated it for clarity.
|
||||
// This also allows operations such as ffi.string:intoBox().
|
||||
// (Write a string to an already existing box)
|
127
crates/lune-std-ffi/src/cstruct.rs
Normal file
127
crates/lune-std-ffi/src/cstruct.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use libffi::low::ffi_abi_FFI_DEFAULT_ABI;
|
||||
use libffi::middle::{Cif, Type};
|
||||
use libffi::raw::ffi_get_struct_offsets;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::association::{get_association, set_association};
|
||||
use crate::ctype::{libffi_types_from_table, type_name_from_userdata};
|
||||
|
||||
use super::ctype::CType;
|
||||
|
||||
pub struct CStruct {
|
||||
libffi_cif: Cif,
|
||||
libffi_type: Type,
|
||||
fields: Vec<Type>,
|
||||
offsets: Vec<usize>,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
const CSTRUCT_INNER: &str = "__cstruct_inner";
|
||||
|
||||
impl CStruct {
|
||||
pub fn new(fields: Vec<Type>) -> Self {
|
||||
let libffi_type = Type::structure(fields.clone());
|
||||
let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void());
|
||||
let size = unsafe { (*libffi_type.as_raw_ptr()).size };
|
||||
let mut offsets = Vec::<usize>::with_capacity(fields.len());
|
||||
unsafe {
|
||||
ffi_get_struct_offsets(
|
||||
ffi_abi_FFI_DEFAULT_ABI,
|
||||
libffi_type.as_raw_ptr(),
|
||||
offsets.as_mut_ptr(),
|
||||
);
|
||||
offsets.set_len(offsets.capacity());
|
||||
}
|
||||
|
||||
Self {
|
||||
libffi_cif: libffi_cfi,
|
||||
libffi_type,
|
||||
fields,
|
||||
offsets,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_lua_table<'lua>(
|
||||
lua: &'lua Lua,
|
||||
table: LuaTable<'lua>,
|
||||
) -> LuaResult<LuaAnyUserData<'lua>> {
|
||||
let fields = libffi_types_from_table(&table)?;
|
||||
let cstruct = lua.create_userdata(Self::new(fields))?;
|
||||
table.set_readonly(true);
|
||||
set_association(lua, CSTRUCT_INNER, cstruct.clone(), table)?;
|
||||
Ok(cstruct)
|
||||
}
|
||||
|
||||
// Stringify cstruct for pretty printing something like:
|
||||
// <CStruct( u8, i32, size = 8 )>
|
||||
pub fn stringify(userdata: &LuaAnyUserData) -> LuaResult<String> {
|
||||
let field: LuaValue = userdata.get("inner")?;
|
||||
if field.is_table() {
|
||||
let table = field
|
||||
.as_table()
|
||||
.ok_or(LuaError::external("failed to get inner table."))?;
|
||||
|
||||
// iterate for field
|
||||
let mut result = String::from(" ");
|
||||
for i in 0..table.raw_len() {
|
||||
let child: LuaAnyUserData = table.raw_get(i + 1)?;
|
||||
result.push_str(format!("{}, ", type_name_from_userdata(&child)?).as_str());
|
||||
}
|
||||
|
||||
// size of
|
||||
result.push_str(format!("size = {} ", userdata.borrow::<CStruct>()?.size).as_str());
|
||||
Ok(result)
|
||||
} else {
|
||||
Ok(String::from("unnamed"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> Type {
|
||||
self.libffi_type.clone()
|
||||
}
|
||||
|
||||
// Get byte offset of nth field
|
||||
pub fn offset(&self, index: usize) -> LuaResult<usize> {
|
||||
let offset = self
|
||||
.offsets
|
||||
.get(index)
|
||||
.ok_or(LuaError::external("Out of index"))?
|
||||
.to_owned();
|
||||
Ok(offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for CStruct {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("size", |_, this| Ok(this.size));
|
||||
|
||||
// Simply pass in the locked table used when first creating this object.
|
||||
// By strongly referencing the table, the types inside do not disappear
|
||||
// and the user can read the contents as needed. (good recycling!)
|
||||
fields.add_field_function_get("inner", |lua, this: LuaAnyUserData| {
|
||||
let table: LuaValue = get_association(lua, CSTRUCT_INNER, this)?
|
||||
// It shouldn't happen.
|
||||
.ok_or(LuaError::external("inner field not found"))?;
|
||||
Ok(table)
|
||||
});
|
||||
}
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("offset", |_, this, index: usize| {
|
||||
let offset = this.offset(index)?;
|
||||
Ok(offset)
|
||||
});
|
||||
methods.add_function("ptr", |lua, this: LuaAnyUserData| {
|
||||
let pointer = CType::pointer(lua, &this)?;
|
||||
Ok(pointer)
|
||||
});
|
||||
methods.add_meta_function(LuaMetaMethod::ToString, |_, this: LuaAnyUserData| {
|
||||
let result = CStruct::stringify(&this)?;
|
||||
Ok(result)
|
||||
});
|
||||
}
|
||||
}
|
203
crates/lune-std-ffi/src/ctype.rs
Normal file
203
crates/lune-std-ffi/src/ctype.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use super::association::{get_association, set_association};
|
||||
use super::cstruct::CStruct;
|
||||
use libffi::middle::{Cif, Type};
|
||||
use lune_utils::fmt::{pretty_format_value, ValueFormatConfig};
|
||||
use mlua::prelude::*;
|
||||
// use libffi::raw::{ffi_cif, ffi_ptrarray_to_raw};
|
||||
|
||||
const POINTER_INNER: &str = "__pointer_inner";
|
||||
|
||||
pub struct CType {
|
||||
libffi_cif: Cif,
|
||||
libffi_type: Type,
|
||||
size: usize,
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
// TODO: ARR
|
||||
// TODO: convert
|
||||
|
||||
impl CType {
|
||||
pub fn new(libffi_type: Type, name: Option<String>) -> Self {
|
||||
let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void());
|
||||
let size = unsafe { (*libffi_type.as_raw_ptr()).size };
|
||||
Self {
|
||||
libffi_cif: libffi_cfi,
|
||||
libffi_type,
|
||||
size,
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> Type {
|
||||
self.libffi_type.clone()
|
||||
}
|
||||
|
||||
pub fn pointer<'lua>(lua: &'lua Lua, inner: &LuaAnyUserData) -> LuaResult<LuaValue<'lua>> {
|
||||
let value = Self {
|
||||
libffi_cif: Cif::new(vec![Type::pointer()], Type::void()),
|
||||
libffi_type: Type::pointer(),
|
||||
size: size_of::<usize>(),
|
||||
name: Some(format!(
|
||||
"Ptr<{}({})>",
|
||||
{
|
||||
if inner.is::<CStruct>() {
|
||||
"CStruct"
|
||||
} else if inner.is::<CType>() {
|
||||
"CType"
|
||||
} else {
|
||||
"unnamed"
|
||||
}
|
||||
},
|
||||
type_name_from_userdata(inner)?
|
||||
)),
|
||||
}
|
||||
.into_lua(lua)?;
|
||||
|
||||
set_association(lua, POINTER_INNER, value.borrow(), inner)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn stringify(&self) -> String {
|
||||
match &self.name {
|
||||
Some(t) => t.to_owned(),
|
||||
None => String::from("unnamed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for CType {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("size", |_, this| Ok(this.size));
|
||||
fields.add_field_function_get("inner", |lua, this| {
|
||||
let inner = get_association(lua, POINTER_INNER, this)?;
|
||||
match inner {
|
||||
Some(t) => Ok(t),
|
||||
None => Ok(LuaNil),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("ptr", |lua, this: LuaAnyUserData| {
|
||||
let pointer = CType::pointer(lua, &this)?;
|
||||
Ok(pointer)
|
||||
});
|
||||
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||
let name = this.stringify();
|
||||
Ok(name)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// export all default c-types
|
||||
pub fn create_all_types(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
||||
Ok(vec![
|
||||
(
|
||||
"u8",
|
||||
CType::new(Type::u8(), Some(String::from("u8"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"u16",
|
||||
CType::new(Type::u16(), Some(String::from("u16"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"u32",
|
||||
CType::new(Type::u32(), Some(String::from("u32"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"u64",
|
||||
CType::new(Type::u64(), Some(String::from("u64"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"i8",
|
||||
CType::new(Type::i8(), Some(String::from("i8"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"i16",
|
||||
CType::new(Type::i16(), Some(String::from("i16"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"i32",
|
||||
CType::new(Type::i32(), Some(String::from("i32"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"i64",
|
||||
CType::new(Type::i64(), Some(String::from("i64"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"f32",
|
||||
CType::new(Type::f32(), Some(String::from("f32"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"f64",
|
||||
CType::new(Type::f64(), Some(String::from("f64"))).into_lua(lua)?,
|
||||
),
|
||||
(
|
||||
"void",
|
||||
CType::new(Type::void(), Some(String::from("void"))).into_lua(lua)?,
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
// get Vec<libffi_type> from table(array) of c-types userdata
|
||||
pub fn libffi_types_from_table(table: &LuaTable) -> LuaResult<Vec<Type>> {
|
||||
let len: usize = table.raw_len();
|
||||
let mut fields = Vec::with_capacity(len);
|
||||
|
||||
for i in 0..len {
|
||||
// Test required
|
||||
let value = table.raw_get(i + 1)?;
|
||||
match value {
|
||||
LuaValue::UserData(field_type) => {
|
||||
fields.push(libffi_type_from_userdata(&field_type)?);
|
||||
}
|
||||
_ => {
|
||||
return Err(LuaError::external(format!(
|
||||
"Unexpected field. CStruct, CType or CArr is required for element but got {}",
|
||||
pretty_format_value(&value, &ValueFormatConfig::new())
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(fields)
|
||||
}
|
||||
|
||||
// get libffi_type from any c-types userdata
|
||||
pub fn libffi_type_from_userdata(userdata: &LuaAnyUserData) -> LuaResult<Type> {
|
||||
if userdata.is::<CStruct>() {
|
||||
Ok(userdata.borrow::<CStruct>()?.get_type())
|
||||
} else if userdata.is::<CType>() {
|
||||
Ok(userdata.borrow::<CType>()?.get_type())
|
||||
} else {
|
||||
Err(LuaError::external(format!(
|
||||
"Unexpected field. CStruct, CType or CArr is required for element but got {}",
|
||||
pretty_format_value(
|
||||
// Since the data is in the Lua location,
|
||||
// there is no problem with the clone.
|
||||
&LuaValue::UserData(userdata.to_owned()),
|
||||
&ValueFormatConfig::new()
|
||||
)
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
// stringify any c-types userdata (for recursive)
|
||||
pub fn type_name_from_userdata(userdata: &LuaAnyUserData) -> LuaResult<String> {
|
||||
if userdata.is::<CType>() {
|
||||
let name = userdata.borrow::<CType>()?.stringify();
|
||||
Ok(name)
|
||||
} else if userdata.is::<CStruct>() {
|
||||
let name = CStruct::stringify(userdata)?;
|
||||
Ok(name)
|
||||
} else {
|
||||
Ok(String::from("unnamed"))
|
||||
}
|
||||
}
|
85
crates/lune-std-ffi/src/ffibox.rs
Normal file
85
crates/lune-std-ffi/src/ffibox.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
// It is an untyped, sized memory area that Lua can manage.
|
||||
// This area is safe within Lua. Operations have their boundaries checked.
|
||||
// It is basically intended to implement passing a pointed space to the outside.
|
||||
// It also helps you handle data that Lua cannot handle.
|
||||
// Depending on the type, operations such as sum, mul, and mod may be implemented.
|
||||
// There is no need to enclose all data in a box;
|
||||
// rather, it creates more heap space, so it should be used appropriately
|
||||
// where necessary.
|
||||
|
||||
use super::association::set_association;
|
||||
use super::ffiref::FfiRef;
|
||||
use core::ffi::c_void;
|
||||
use mlua::prelude::*;
|
||||
use std::boxed::Box;
|
||||
|
||||
const BOX_REF_INNER: &str = "__box_ref";
|
||||
|
||||
pub struct FfiBox(Box<[u8]>);
|
||||
|
||||
impl FfiBox {
|
||||
pub fn new(size: usize) -> Self {
|
||||
Self(vec![0u8; size].into_boxed_slice())
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
// pub fn copy(&self, target: &mut FfiBox) {}
|
||||
|
||||
pub fn get_ptr(&self) -> *mut c_void {
|
||||
self.0.as_ptr() as *mut c_void
|
||||
}
|
||||
|
||||
// bad naming. i have no idea what should i use
|
||||
pub fn luaref<'lua>(
|
||||
lua: &'lua Lua,
|
||||
this: LuaAnyUserData<'lua>,
|
||||
) -> LuaResult<LuaAnyUserData<'lua>> {
|
||||
let target = this.borrow::<FfiBox>()?;
|
||||
|
||||
let luaref = lua.create_userdata(FfiRef::new(target.get_ptr()))?;
|
||||
|
||||
set_association(lua, BOX_REF_INNER, luaref.clone(), this.clone())?;
|
||||
|
||||
Ok(luaref)
|
||||
}
|
||||
|
||||
pub fn zero(&mut self) {
|
||||
self.0.fill(0u8);
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for FfiBox {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("size", |_, this| Ok(this.size()));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method_mut("zero", |_, this, ()| {
|
||||
this.zero();
|
||||
Ok(())
|
||||
});
|
||||
methods.add_function("ref", |lua, this: LuaAnyUserData| {
|
||||
let luaref = FfiBox::luaref(lua, this)?;
|
||||
Ok(luaref)
|
||||
});
|
||||
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.size()));
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
||||
dbg!(&this.0.len());
|
||||
let mut buff = String::from("[ ");
|
||||
for i in &this.0 {
|
||||
buff.push_str(i.to_owned().to_string().as_str());
|
||||
buff.push_str(", ");
|
||||
}
|
||||
buff.pop();
|
||||
buff.pop();
|
||||
buff.push_str(" ]");
|
||||
let luastr = lua.create_string(buff.as_bytes())?;
|
||||
Ok(luastr)
|
||||
});
|
||||
}
|
||||
}
|
57
crates/lune-std-ffi/src/ffilib.rs
Normal file
57
crates/lune-std-ffi/src/ffilib.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use std::ffi::c_void;
|
||||
|
||||
use super::association::set_association;
|
||||
use dlopen2::symbor::Library;
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::ffiref::FfiRef;
|
||||
|
||||
pub struct FfiLib(Library);
|
||||
|
||||
const SYM_INNER: &str = "__syn_inner";
|
||||
|
||||
// COMMENT HERE
|
||||
// For convenience, it would be nice to provide a way to get
|
||||
// symbols from a table with type and field names specified.
|
||||
// But right now, we are starting from the lowest level, so we will make it later.
|
||||
|
||||
// I wanted to provide something like cdef,
|
||||
// but that is beyond the scope of lune's support.
|
||||
// Higher-level bindings for convenience are much preferable written in Lua.
|
||||
|
||||
impl FfiLib {
|
||||
pub fn new(libname: String) -> LuaResult<Self> {
|
||||
match Library::open(libname) {
|
||||
Ok(t) => Ok(Self(t)),
|
||||
Err(err) => Err(LuaError::external(format!("{err}"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sym<'lua>(
|
||||
lua: &'lua Lua,
|
||||
this: LuaAnyUserData<'lua>,
|
||||
name: String,
|
||||
) -> LuaResult<LuaAnyUserData<'lua>> {
|
||||
let lib = this.borrow::<FfiLib>()?;
|
||||
let sym = unsafe {
|
||||
lib.0
|
||||
.symbol::<*mut c_void>(name.as_str())
|
||||
.map_err(|err| LuaError::external(format!("{err}")))?
|
||||
};
|
||||
|
||||
let luasym = lua.create_userdata(FfiRef::new(*sym))?;
|
||||
|
||||
set_association(lua, SYM_INNER, luasym.clone(), this.clone())?;
|
||||
|
||||
Ok(luasym)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for FfiLib {
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("dlsym", |lua, (this, name): (LuaAnyUserData, String)| {
|
||||
let luasym = FfiLib::get_sym(lua, this, name)?;
|
||||
Ok(luasym)
|
||||
});
|
||||
}
|
||||
}
|
8
crates/lune-std-ffi/src/ffiraw.rs
Normal file
8
crates/lune-std-ffi/src/ffiraw.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
// This is raw data coming from outside.
|
||||
// Users must convert it to a Lua value, reference, or box to use it.
|
||||
// The biggest reason for providing this is to allow the user to
|
||||
// decide whether to move the data to a heap that Lua can manage (box),
|
||||
// move it directly to Lua's data, or think of it as a pointer.
|
||||
// This will help you distinguish between safe operations and
|
||||
// relatively insecure operations, and help ensure that as little
|
||||
// data copy as possible occurs, while allowing you to do little restrictions.
|
59
crates/lune-std-ffi/src/ffiref.rs
Normal file
59
crates/lune-std-ffi/src/ffiref.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use super::association::set_association;
|
||||
use core::ffi::c_void;
|
||||
use mlua::prelude::*;
|
||||
use std::ptr;
|
||||
|
||||
// A referenced space. It is possible to read and write through types.
|
||||
// This operation is not safe. This may cause a memory error in Lua
|
||||
// if use it incorrectly.
|
||||
// If it references an area managed by Lua,
|
||||
// the box will remain as long as this reference is alive.
|
||||
|
||||
pub struct FfiRef(*mut c_void);
|
||||
|
||||
const REF_INNER: &str = "__ref_inner";
|
||||
|
||||
impl FfiRef {
|
||||
pub fn new(target: *mut c_void) -> Self {
|
||||
Self(target)
|
||||
}
|
||||
|
||||
// bad naming. i have no idea what should i use
|
||||
pub fn luaref<'lua>(
|
||||
lua: &'lua Lua,
|
||||
this: LuaAnyUserData<'lua>,
|
||||
) -> LuaResult<LuaAnyUserData<'lua>> {
|
||||
let target = this.borrow::<FfiRef>()?;
|
||||
|
||||
let luaref = lua.create_userdata(FfiRef::new(ptr::from_ref(&target.0) as *mut c_void))?;
|
||||
|
||||
set_association(lua, REF_INNER, luaref.clone(), this.clone())?;
|
||||
|
||||
Ok(luaref)
|
||||
}
|
||||
|
||||
pub unsafe fn deref(&self) -> Self {
|
||||
Self::new(*self.0.cast::<*mut c_void>())
|
||||
}
|
||||
|
||||
pub unsafe fn offset(&self, offset: isize) -> Self {
|
||||
Self::new(self.0.offset(offset))
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for FfiRef {
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("deref", |_, this, ()| {
|
||||
let ffiref = unsafe { this.deref() };
|
||||
Ok(ffiref)
|
||||
});
|
||||
methods.add_method("offset", |_, this, offset: isize| {
|
||||
let ffiref = unsafe { this.offset(offset) };
|
||||
Ok(ffiref)
|
||||
});
|
||||
methods.add_function("ref", |lua, this: LuaAnyUserData| {
|
||||
let ffiref = FfiRef::luaref(lua, this)?;
|
||||
Ok(ffiref)
|
||||
});
|
||||
}
|
||||
}
|
56
crates/lune-std-ffi/src/lib.rs
Normal file
56
crates/lune-std-ffi/src/lib.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
use mlua::prelude::*;
|
||||
|
||||
mod association;
|
||||
mod carr;
|
||||
mod cfn;
|
||||
mod cstring;
|
||||
mod cstruct;
|
||||
mod ctype;
|
||||
mod ffibox;
|
||||
mod ffilib;
|
||||
mod ffiraw;
|
||||
mod ffiref;
|
||||
|
||||
use self::association::get_table;
|
||||
use self::cfn::CFn;
|
||||
use self::cstruct::CStruct;
|
||||
use self::ctype::create_all_types;
|
||||
use self::ffibox::FfiBox;
|
||||
use self::ffilib::FfiLib;
|
||||
|
||||
/**
|
||||
Creates the `ffi` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let ctypes = create_all_types(lua)?;
|
||||
let result = TableBuilder::new(lua)?
|
||||
.with_values(ctypes)?
|
||||
.with_function("box", |_, size: usize| Ok(FfiBox::new(size)))?
|
||||
// TODO: discuss about function name. matching with io.open is better?
|
||||
.with_function("dlopen", |_, name: String| {
|
||||
let lib = FfiLib::new(name)?;
|
||||
Ok(lib)
|
||||
})?
|
||||
.with_function("struct", |lua, types: LuaTable| {
|
||||
let cstruct = CStruct::from_lua_table(lua, types)?;
|
||||
Ok(cstruct)
|
||||
})?
|
||||
.with_function("fn", |_, (args, ret): (LuaTable, LuaAnyUserData)| {
|
||||
let cfn = CFn::from_lua_table(args, ret)?;
|
||||
Ok(cfn)
|
||||
})?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let result = result.with_function("debug_associate", |lua, str: String| {
|
||||
get_table(lua, str.as_ref())
|
||||
})?;
|
||||
|
||||
result.build_readonly()
|
||||
}
|
102
crates/lune-std-ffi/todo.md
Normal file
102
crates/lune-std-ffi/todo.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
|
||||
use libffi::raw::{ffi_cif, ffi_ptrarray_to_raw};
|
||||
|
||||
// pub fn ffi_get_struct_offsets(
|
||||
// abi: ffi_abi,
|
||||
// struct_type: *mut ffi_type,
|
||||
// offsets: *mut usize,
|
||||
// ) -> ffi_status;
|
||||
|
||||
- last thing to do
|
||||
- [ ] Add tests
|
||||
- [ ] Add docs
|
||||
- [ ] Typing
|
||||
|
||||
# Raw
|
||||
|
||||
- [ ] Raw:toRef()
|
||||
- [ ] Raw:toBox()
|
||||
- [ ] Raw:intoBox()
|
||||
- [ ] Raw:intoRef()
|
||||
|
||||
# Box
|
||||
|
||||
- [x] ffi.box(size)
|
||||
- [x] .size
|
||||
- [x] :zero()
|
||||
- [?] :ref(offset?=0) => ref
|
||||
- offset is not impled
|
||||
- [~] :copy(box,size?=-1,offset?=0)
|
||||
- working on it
|
||||
|
||||
# Ref (Unsafe)
|
||||
|
||||
- [x] ref:deref() -> ref
|
||||
- [x] ref:offset(bytes) -> ref
|
||||
- [x] ref:ref() -> ref
|
||||
|
||||
~~- [ ] ref:fromRef(size,offset?=0) ?? what is this~~
|
||||
~~- [ ] ref:fromBox(size,offset?=0) ?? what is this~~
|
||||
|
||||
# Struct
|
||||
|
||||
- [x] :offset(index)
|
||||
- [x] :ptr()
|
||||
- [x] .inner[n]
|
||||
- [!] .size
|
||||
- [ ] #
|
||||
- [x] tostring
|
||||
|
||||
size, offset is strange. maybe related to cif state.
|
||||
|
||||
# Type
|
||||
|
||||
- [ ] :toBox(luavalue)
|
||||
|
||||
Very stupid idea.
|
||||
from(box|ref|raw, offset) is better idea i think.
|
||||
|
||||
- [ ] :fromBox(box,offset?=0)
|
||||
- [ ] :intoBox(luavalue,box,offset?=0)
|
||||
- [ ] :fromRef(ref,offset?=0)
|
||||
- [ ] :intoRef(luavalue,ref,offset?=0)
|
||||
- [ ] :fromRaw(raw,offset?=0)
|
||||
|
||||
- [ ] :castBox(box,type) TODO
|
||||
- [ ]
|
||||
|
||||
- [ ] :sum
|
||||
- [ ] :mul
|
||||
- [ ] :sub
|
||||
|
||||
## subtype
|
||||
- [x] :ptr() -> Ptr
|
||||
- [~] :arr(len) -> Arr
|
||||
- [x] .size
|
||||
|
||||
|
||||
|
||||
# Ptr
|
||||
|
||||
- [x] .inner
|
||||
- [x] .size
|
||||
- [x] :ptr()
|
||||
- [~] :arr()
|
||||
|
||||
## Arr
|
||||
|
||||
## Void
|
||||
|
||||
`ffi.void`
|
||||
|
||||
Zero sized type.
|
||||
|
||||
## Fn
|
||||
Prototype type of some function. converts lua function into native function pointer or native function pointer into lua function.
|
||||
|
||||
`ffi.fn({ type }, type) -> fn`
|
||||
|
||||
:toLua( ref ) -> luafunction
|
||||
:toBox( luafunction ) -> ref
|
||||
|
||||
> TODO: rust, and another ABI support
|
|
@ -24,6 +24,7 @@ default = [
|
|||
"serde",
|
||||
"stdio",
|
||||
"task",
|
||||
"ffi",
|
||||
]
|
||||
|
||||
datetime = ["dep:lune-std-datetime"]
|
||||
|
@ -36,6 +37,7 @@ roblox = ["dep:lune-std-roblox"]
|
|||
serde = ["dep:lune-std-serde"]
|
||||
stdio = ["dep:lune-std-stdio"]
|
||||
task = ["dep:lune-std-task"]
|
||||
ffi = ["dep:lune-std-ffi"]
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
@ -57,3 +59,4 @@ lune-std-roblox = { optional = true, version = "0.1.3", path = "../lune-std-robl
|
|||
lune-std-serde = { optional = true, version = "0.1.2", path = "../lune-std-serde" }
|
||||
lune-std-stdio = { optional = true, version = "0.1.2", path = "../lune-std-stdio" }
|
||||
lune-std-task = { optional = true, version = "0.1.2", path = "../lune-std-task" }
|
||||
lune-std-ffi = { optional = true, version = "0.1.1", path = "../lune-std-ffi" }
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
|||
use mlua::prelude::*;
|
||||
|
||||
/**
|
||||
A standard library provided by Lune.
|
||||
A standard library probloxrovided by Lune.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[rustfmt::skip]
|
||||
|
@ -18,6 +18,7 @@ pub enum LuneStandardLibrary {
|
|||
#[cfg(feature = "serde")] Serde,
|
||||
#[cfg(feature = "stdio")] Stdio,
|
||||
#[cfg(feature = "roblox")] Roblox,
|
||||
#[cfg(feature = "ffi")] Ffi,
|
||||
}
|
||||
|
||||
impl LuneStandardLibrary {
|
||||
|
@ -36,6 +37,7 @@ impl LuneStandardLibrary {
|
|||
#[cfg(feature = "serde")] Self::Serde,
|
||||
#[cfg(feature = "stdio")] Self::Stdio,
|
||||
#[cfg(feature = "roblox")] Self::Roblox,
|
||||
#[cfg(feature = "ffi")] Self::Ffi,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -56,6 +58,7 @@ impl LuneStandardLibrary {
|
|||
#[cfg(feature = "serde")] Self::Serde => "serde",
|
||||
#[cfg(feature = "stdio")] Self::Stdio => "stdio",
|
||||
#[cfg(feature = "roblox")] Self::Roblox => "roblox",
|
||||
#[cfg(feature = "ffi")] Self::Ffi => "ffi",
|
||||
|
||||
_ => unreachable!("no standard library enabled"),
|
||||
}
|
||||
|
@ -82,6 +85,7 @@ impl LuneStandardLibrary {
|
|||
#[cfg(feature = "serde")] Self::Serde => lune_std_serde::module(lua),
|
||||
#[cfg(feature = "stdio")] Self::Stdio => lune_std_stdio::module(lua),
|
||||
#[cfg(feature = "roblox")] Self::Roblox => lune_std_roblox::module(lua),
|
||||
#[cfg(feature = "ffi")] Self::Ffi => lune_std_ffi::module(lua),
|
||||
|
||||
_ => unreachable!("no standard library enabled"),
|
||||
};
|
||||
|
@ -111,6 +115,7 @@ impl FromStr for LuneStandardLibrary {
|
|||
#[cfg(feature = "serde")] "serde" => Self::Serde,
|
||||
#[cfg(feature = "stdio")] "stdio" => Self::Stdio,
|
||||
#[cfg(feature = "roblox")] "roblox" => Self::Roblox,
|
||||
#[cfg(feature = "ffi")] "ffi" => Self::Ffi,
|
||||
|
||||
_ => {
|
||||
return Err(format!(
|
||||
|
|
|
@ -30,6 +30,7 @@ std-roblox = ["dep:lune-std", "lune-std/roblox", "dep:lune-roblox"]
|
|||
std-serde = ["dep:lune-std", "lune-std/serde"]
|
||||
std-stdio = ["dep:lune-std", "lune-std/stdio"]
|
||||
std-task = ["dep:lune-std", "lune-std/task"]
|
||||
std-ffi = ["dep:lune-std", "lune-std/ffi"]
|
||||
|
||||
std = [
|
||||
"std-datetime",
|
||||
|
@ -42,6 +43,7 @@ std = [
|
|||
"std-serde",
|
||||
"std-stdio",
|
||||
"std-task",
|
||||
"std-ffi",
|
||||
]
|
||||
|
||||
cli = ["dep:clap", "dep:include_dir", "dep:rustyline", "dep:zip_next"]
|
||||
|
|
33
tests/ffi/ptr.luau
Normal file
33
tests/ffi/ptr.luau
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
local ffi = require("@lune/ffi")
|
||||
|
||||
-- ptr size test
|
||||
assert(
|
||||
ffi.i32:ptr().size == ffi.i64:ptr().size,
|
||||
"All of Ptr.size must be same.\n"..
|
||||
"ffi.i32:ptr().size == ffi.i64:ptr().size failed"
|
||||
)
|
||||
|
||||
-- inner test
|
||||
local i32ptr = ffi.i32:ptr()
|
||||
assert(
|
||||
rawequal(ffi.i32, i32ptr.inner),
|
||||
"Ptr.inner must be same with their parent\n"..
|
||||
"raweq ffi.i32 == ffi.i32:ptr().inner failed"
|
||||
)
|
||||
assert(
|
||||
rawequal(i32ptr, i32ptr:ptr().inner),
|
||||
"Ptr.inner must be same with their parent\n"..
|
||||
"raweq i32ptr == i32ptr:ptr().inner failed"
|
||||
)
|
||||
assert(
|
||||
rawequal(i32ptr, i32ptr:ptr().inner:ptr().inner:ptr().inner),
|
||||
"Ptr.inner must be same with their parent\n"..
|
||||
"raweq i32ptr == i32ptr:ptr().inner:ptr().inner:ptr().inner failed"
|
||||
)
|
||||
|
||||
-- deep ptr test
|
||||
local ok,err = pcall(function()
|
||||
i32ptr:ptr():ptr():ptr():ptr():ptr():ptr():ptr()
|
||||
end)
|
||||
assert(ok,`Deep ptr test failed.\n{err}`)
|
2
tests/ffi/struct.luau
Normal file
2
tests/ffi/struct.luau
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
local ffi = require("@lune/ffi")
|
79
types/ffi.luau
Normal file
79
types/ffi.luau
Normal file
|
@ -0,0 +1,79 @@
|
|||
--[=[
|
||||
@interface Box
|
||||
@within FFI
|
||||
|
||||
Box is an untyped, sized memory area that Lua can manage.
|
||||
This area is safe within Lua. Operations have their boundaries checked.
|
||||
|
||||
You can passing box as raw arguments or as pointer to outside.
|
||||
It also helps you handle data that Lua cannot handle. or you can reuse box to save cost from convertsion.
|
||||
Depending on the type, operations such as sum, mul, and mod may be implemented. See Types
|
||||
|
||||
```lua
|
||||
ffi.box(size)
|
||||
```
|
||||
This is a dictionary that will contain the following values:
|
||||
|
||||
* `readOnly` - If the target path is read-only or not
|
||||
]=]
|
||||
|
||||
export type Box = {
|
||||
size: number,
|
||||
ref: (self: Box)->Ref,
|
||||
}
|
||||
export type BoxConstructor = (size: number)->Box
|
||||
|
||||
export type Type = {}
|
||||
|
||||
---! FIXME: better typing for PointerSize
|
||||
export type PointerSize = number -- typeof(5) | typeof(8)
|
||||
|
||||
export type Arr<T> = {
|
||||
inner: T,
|
||||
size: number,
|
||||
ptr: (self: Arr<T>) -> any,
|
||||
}
|
||||
|
||||
--[=[
|
||||
@interface Ptr
|
||||
@within FFI
|
||||
|
||||
]=]
|
||||
---! FIXME: due to recursive type limition. hardcoded 6 depth. better idea?
|
||||
export type Ptr<T> = {
|
||||
inner: T,
|
||||
size: PointerSize,
|
||||
ptr: (self: Ptr<T>)->PtrPtr<Ptr<T>>,
|
||||
arr: (self: Ptr<T>, size: number) -> Arr<Ptr<T>>,
|
||||
}
|
||||
export type PtrPtr<T> = {
|
||||
inner: T,
|
||||
size: PointerSize,
|
||||
ptr: (self: PtrPtr<T>)->PtrPtrPtr<PtrPtr<T>>,
|
||||
arr: (self: PtrPtr<T>, size: number) -> Arr<PtrPtr<T>>,
|
||||
}
|
||||
export type PtrPtrPtr<T> = {
|
||||
inner: T,
|
||||
size: PointerSize,
|
||||
ptr: (self: PtrPtrPtr<T>)->PtrPtrPtrPtr<PtrPtrPtr<T>>,
|
||||
arr: (self: PtrPtrPtr<T>, size: number) -> Arr<PtrPtrPtr<T>>,
|
||||
}
|
||||
export type PtrPtrPtrPtr<T> = {
|
||||
inner: T,
|
||||
size: PointerSize,
|
||||
ptr: (self: PtrPtrPtrPtr<T>)->PtrPtrPtrPtrPtr<PtrPtrPtrPtr<T>>,
|
||||
arr: (self: PtrPtrPtrPtr<T>, size: number) -> Arr<PtrPtrPtrPtr<T>>,
|
||||
}
|
||||
export type PtrPtrPtrPtrPtr<T> = {
|
||||
inner: T,
|
||||
size: PointerSize,
|
||||
ptr: (self: PtrPtrPtrPtrPtr<T>)->PtrPtrPtrPtrPtrPtr<PtrPtrPtrPtrPtr<T>>,
|
||||
arr: (self: PtrPtrPtrPtrPtr<T>, size: number) -> Arr<PtrPtrPtrPtrPtr<T>>,
|
||||
}
|
||||
export type PtrPtrPtrPtrPtrPtr<T> = {
|
||||
inner: T,
|
||||
size: PointerSize,
|
||||
ptr: (self: PtrPtrPtrPtrPtrPtr<T>)->any, -- Yes. At this point. more type is useless.
|
||||
arr: (self: PtrPtrPtrPtrPtrPtr<T>, size: number) -> Arr<PtrPtrPtrPtrPtrPtr<T>>,
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue