Compare commits
10 Commits
5ea532f1c6
...
27c40c3244
Author | SHA1 | Date | |
---|---|---|---|
27c40c3244 | |||
ccae0046fb | |||
e027623d40 | |||
8a49321110 | |||
c70b043281 | |||
f6b91cde10 | |||
e05e2f4cb3 | |||
6a4c726965 | |||
5f1f6dab7a | |||
a760beabc1 |
@ -31,7 +31,7 @@ dev.panic = "abort"
|
||||
release.panic = "abort"
|
||||
|
||||
[[test]]
|
||||
name = "test"
|
||||
name = "main"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
@ -44,7 +44,7 @@ tokio-console = ["dep:console-subscriber"]
|
||||
[dependencies]
|
||||
clap = { version = "4.5.40", features = ["derive", "env"] }
|
||||
console-subscriber = { version = "0.4.1", optional = true }
|
||||
lb = { path = "crates/lb" }
|
||||
lb = { path = "crates/lb", features = ["runtime"] }
|
||||
luajit = { path = "crates/luajit", features = ["runtime"] }
|
||||
mimalloc = "0.1.47"
|
||||
owo-colors = "4.2.1"
|
||||
|
@ -8,6 +8,7 @@ homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
runtime = ["tokio/rt"]
|
||||
task = ["tokio/rt", "tokio/time"]
|
||||
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
||||
net = ["tokio/net"]
|
||||
|
@ -32,7 +32,7 @@ impl lb_chanlib {
|
||||
(send, recv)
|
||||
}
|
||||
|
||||
extern "Lua" fn bounded(self, cap: number) {
|
||||
extern "Lua" fn bounded(self, cap: u32) {
|
||||
assert(cap >= 0, "channel capacity must be nonnegative");
|
||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
||||
self.__bounded(cap, send, recv);
|
||||
|
@ -38,7 +38,8 @@ type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Items exported by the `lb:fs` library.
|
||||
///
|
||||
/// This library can be acquired by calling `require` in Lua.
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local fs = require("lb:fs");
|
||||
@ -53,39 +54,39 @@ impl lb_fslib {
|
||||
Self
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn read(&self, path: &str) -> Result<Vec<u8>> {
|
||||
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
|
||||
Ok(tokio::fs::read(path).await?)
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn read_sync(&self, path: &str) -> Result<Vec<u8>> {
|
||||
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
|
||||
Ok(std::fs::read(path)?)
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn write(&self, path: &str, contents: &[u8]) -> Result<()> {
|
||||
pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> {
|
||||
Ok(tokio::fs::write(path, contents).await?)
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn write_sync(&self, path: &str, contents: &[u8]) -> Result<()> {
|
||||
pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> {
|
||||
Ok(std::fs::write(path, contents)?)
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn read_dir(&self, path: &str) -> Result<lb_read_dir> {
|
||||
pub async extern "Lua-C" fn read_dir(path: &str) -> Result<lb_read_dir> {
|
||||
Ok(tokio::fs::read_dir(path).await?.into())
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn read_dir_sync(&self, path: &str) -> Result<lb_read_dir_sync> {
|
||||
pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result<lb_read_dir_sync> {
|
||||
Ok(std::fs::read_dir(path)?.into())
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn walk_dir(&self, path: &str) -> lb_walk_dir {
|
||||
pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir {
|
||||
walkdir::WalkDir::new(path).into_iter().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn glob(&self, pattern: &str) -> Result<lb_glob_dir> {
|
||||
self.glob_dir(".", pattern)
|
||||
pub extern "Lua-C" fn glob(pattern: &str) -> Result<lb_glob_dir> {
|
||||
Self::glob_dir(".", pattern)
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn glob_dir(&self, path: &str, pattern: &str) -> Result<lb_glob_dir> {
|
||||
pub extern "Lua-C" fn glob_dir(path: &str, pattern: &str) -> Result<lb_glob_dir> {
|
||||
let prefix = PathBuf::from(path);
|
||||
let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter();
|
||||
let matcher = globset::GlobSet::builder()
|
||||
@ -99,11 +100,11 @@ impl lb_fslib {
|
||||
})
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn temp_dir(&self) -> Result<lb_temp_dir> {
|
||||
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
|
||||
Ok(tempfile::tempdir()?.into())
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn temp_dir_in(&self, path: &str) -> Result<lb_temp_dir> {
|
||||
pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result<lb_temp_dir> {
|
||||
Ok(tempfile::tempdir_in(path)?.into())
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
//! luby standard library
|
||||
#![warn(missing_docs)]
|
||||
pub mod runtime;
|
||||
|
||||
#[cfg(feature = "task")]
|
||||
pub mod chan;
|
||||
#[cfg(feature = "fs")]
|
||||
pub mod fs;
|
||||
#[cfg(feature = "net")]
|
||||
pub mod net;
|
||||
#[cfg(feature = "runtime")]
|
||||
pub mod runtime;
|
||||
#[cfg(feature = "task")]
|
||||
pub mod task;
|
||||
|
@ -7,7 +7,7 @@
|
||||
//!
|
||||
//! See [`lb_netlib`] for items exported by this library.
|
||||
use derive_more::{From, FromStr};
|
||||
use luaffi::{cdef, metatype};
|
||||
use luaffi::{cdef, marker::OneOf, metatype};
|
||||
use std::{
|
||||
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
time::Duration,
|
||||
@ -52,28 +52,28 @@ impl lb_netlib {
|
||||
Self
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::LOCALHOST`].
|
||||
pub extern "Lua-C" fn localhost_v4(&self) -> lb_ipaddr {
|
||||
/// An IPv4 address representing localhost: `127.0.0.1`
|
||||
pub extern "Lua-C" fn localhost_v4() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv6Addr::LOCALHOST`].
|
||||
pub extern "Lua-C" fn localhost_v6(&self) -> lb_ipaddr {
|
||||
/// An IPv6 address representing localhost: `::1`
|
||||
pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::UNSPECIFIED`].
|
||||
pub extern "Lua-C" fn unspecified_v4(&self) -> lb_ipaddr {
|
||||
/// An IPv4 address representing an unspecified address: `0.0.0.0`
|
||||
pub extern "Lua-C" fn unspecified_v4() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv6Addr::UNSPECIFIED`].
|
||||
pub extern "Lua-C" fn unspecified_v6(&self) -> lb_ipaddr {
|
||||
/// An IPv6 address representing an unspecified address: `::`
|
||||
pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::BROADCAST`].
|
||||
pub extern "Lua-C" fn broadcast_v4(&self) -> lb_ipaddr {
|
||||
/// An IPv4 address representing the broadcast address: `255.255.255.255`
|
||||
pub extern "Lua-C" fn broadcast_v4() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::BROADCAST.into())
|
||||
}
|
||||
|
||||
@ -82,17 +82,19 @@ impl lb_netlib {
|
||||
/// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an
|
||||
/// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses
|
||||
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
|
||||
pub extern "Lua" fn ipaddr(&self, addr: any) -> Result<lb_ipaddr> {
|
||||
pub extern "Lua" fn ipaddr(
|
||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||
) -> Result<lb_ipaddr> {
|
||||
if __istype(__ct.lb_ipaddr, addr) {
|
||||
__new(__ct.lb_ipaddr, addr) // copy constructor
|
||||
} else if __istype(__ct.lb_socketaddr, addr) {
|
||||
s.ip()
|
||||
} else {
|
||||
self.__parse_ipaddr(addr)
|
||||
Self::__parse_ipaddr(addr)
|
||||
}
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __parse_ipaddr(&self, addr: &str) -> Result<lb_ipaddr> {
|
||||
extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result<lb_ipaddr> {
|
||||
Ok(addr.parse()?)
|
||||
}
|
||||
|
||||
@ -105,25 +107,28 @@ impl lb_netlib {
|
||||
/// socket address string. Both IPv4 and IPv6 addresses are supported.
|
||||
///
|
||||
/// If `port` is not specified, `0` is used as the default.
|
||||
pub extern "Lua" fn socketaddr(&self, addr: any, port: any) -> Result<lb_socketaddr> {
|
||||
pub extern "Lua" fn socketaddr(
|
||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_socketaddr> {
|
||||
if port != () {
|
||||
self.__new_skaddr(self.ipaddr(addr), port)
|
||||
Self::__new_skaddr(Self::ipaddr(addr), port)
|
||||
} else {
|
||||
if __istype(__ct.lb_socketaddr, addr) {
|
||||
__new(__ct.lb_socketaddr, addr) // copy constructor
|
||||
} else if __istype(__ct.lb_ipaddr, addr) {
|
||||
self.__new_skaddr(addr, 0) // default port 0
|
||||
Self::__new_skaddr(addr, 0) // default port 0
|
||||
} else {
|
||||
self.__parse_skaddr(addr)
|
||||
Self::__parse_skaddr(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __new_skaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
||||
extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
||||
SocketAddr::new(ip.0, port).into()
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __parse_skaddr(&self, addr: &str) -> Result<lb_socketaddr> {
|
||||
extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result<lb_socketaddr> {
|
||||
Ok(if let Ok(addr) = addr.parse() {
|
||||
SocketAddr::new(addr, 0).into() // default port 0
|
||||
} else {
|
||||
@ -134,42 +139,51 @@ impl lb_netlib {
|
||||
/// Creates a new TCP socket configured for IPv4.
|
||||
///
|
||||
/// See [`TcpSocket::new_v4`].
|
||||
pub extern "Lua-C" fn tcp(&self) -> Result<lb_tcpsocket> {
|
||||
pub extern "Lua-C" fn tcp() -> Result<lb_tcpsocket> {
|
||||
Ok(Some(TcpSocket::new_v4()?).into())
|
||||
}
|
||||
|
||||
/// Creates a new TCP socket configured for IPv6.
|
||||
///
|
||||
/// See [`TcpSocket::new_v6`].
|
||||
pub extern "Lua-C" fn tcp6(&self) -> Result<lb_tcpsocket> {
|
||||
pub extern "Lua-C" fn tcp6() -> Result<lb_tcpsocket> {
|
||||
Ok(Some(TcpSocket::new_v6()?).into())
|
||||
}
|
||||
|
||||
pub async extern "Lua" fn bind_tcp(&self, addr: any, port: any) -> Result<lb_tcpsocket> {
|
||||
let addr = self.socketaddr(addr, port);
|
||||
pub async extern "Lua" fn bind_tcp(
|
||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_tcpsocket> {
|
||||
let addr = Self::socketaddr(addr, port);
|
||||
let socket;
|
||||
if addr.ip().is_v6() {
|
||||
socket = self.tcp6();
|
||||
socket = Self::tcp6();
|
||||
} else {
|
||||
socket = self.tcp();
|
||||
socket = Self::tcp();
|
||||
}
|
||||
socket.bind(addr);
|
||||
socket
|
||||
}
|
||||
|
||||
pub async extern "Lua" fn connect_tcp(&self, addr: any, port: any) -> Result<lb_tcpstream> {
|
||||
let addr = self.socketaddr(addr, port);
|
||||
pub async extern "Lua" fn connect_tcp(
|
||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_tcpstream> {
|
||||
let addr = Self::socketaddr(addr, port);
|
||||
let socket;
|
||||
if addr.ip().is_v6() {
|
||||
socket = self.tcp6();
|
||||
socket = Self::tcp6();
|
||||
} else {
|
||||
socket = self.tcp();
|
||||
socket = Self::tcp();
|
||||
}
|
||||
socket.connect(addr)
|
||||
}
|
||||
|
||||
pub async extern "Lua" fn listen_tcp(&self, addr: any, port: any) -> Result<lb_tcplistener> {
|
||||
self.bind_tcp(addr, port).listen(1024)
|
||||
pub async extern "Lua" fn listen_tcp(
|
||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_tcplistener> {
|
||||
Self::bind_tcp(addr, port).listen(1024)
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,7 +195,7 @@ impl lb_netlib {
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net");
|
||||
/// local addr = net:ipaddr("127.0.0.1"); -- ipv4 loopback address
|
||||
/// local addr = net.ipaddr("127.0.0.1"); -- ipv4 loopback address
|
||||
///
|
||||
/// assert(addr:is_v4());
|
||||
/// assert(addr:is_loopback());
|
||||
@ -208,7 +222,7 @@ impl lb_ipaddr {
|
||||
}
|
||||
|
||||
/// Returns the string `"v4"` if this is an IPv4 address or `"v6"` if this is an IPv6 address.
|
||||
pub extern "Lua" fn family(&self) -> string {
|
||||
pub extern "Lua" fn family(&self) -> String {
|
||||
if self.is_v6() { "v6" } else { "v4" }
|
||||
}
|
||||
|
||||
@ -313,7 +327,10 @@ impl lb_socketaddr {
|
||||
/// Sets the IP part of this address.
|
||||
///
|
||||
/// This function accepts the same arguments as [`ipaddr`](lb_netlib::ipaddr).
|
||||
pub extern "Lua" fn set_ip(&mut self, addr: any) -> &mut Self {
|
||||
pub extern "Lua" fn set_ip(
|
||||
&mut self,
|
||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||
) -> &mut Self {
|
||||
if __istype(__ct.lb_ipaddr, addr) {
|
||||
self.__set_ip(addr);
|
||||
} else if __istype(__ct.lb_socketaddr, addr) {
|
||||
@ -338,7 +355,7 @@ impl lb_socketaddr {
|
||||
}
|
||||
|
||||
/// Sets the port part of this address.
|
||||
pub extern "Lua" fn set_port(&mut self, port: integer) -> &mut Self {
|
||||
pub extern "Lua" fn set_port(&mut self, port: u16) -> &mut Self {
|
||||
self.__set_port(port);
|
||||
self
|
||||
}
|
||||
|
@ -1,10 +1,3 @@
|
||||
-- include task functions in the global scope
|
||||
local task = require("lb:task")
|
||||
|
||||
function sleep(ms)
|
||||
return task:sleep(ms)
|
||||
end
|
||||
|
||||
function spawn(f, ...)
|
||||
return task:spawn(f, ...)
|
||||
end
|
||||
sleep = task.sleep
|
||||
spawn = task.spawn
|
||||
|
@ -7,7 +7,11 @@
|
||||
//!
|
||||
//! See [`lb_tasklib`] for items exported by this library.
|
||||
use crate::runtime::spawn;
|
||||
use luaffi::{cdef, metatype};
|
||||
use luaffi::{
|
||||
cdef,
|
||||
marker::{function, many},
|
||||
metatype,
|
||||
};
|
||||
use luajit::{LUA_MULTRET, Type};
|
||||
use std::{ffi::c_int, time::Duration};
|
||||
use tokio::{task::JoinHandle, time::sleep};
|
||||
@ -31,20 +35,20 @@ impl lb_tasklib {
|
||||
Self
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn sleep(&self, ms: f64) {
|
||||
pub async extern "Lua-C" fn sleep(ms: f64) {
|
||||
sleep(Duration::from_secs_f64(ms / 1000.)).await;
|
||||
}
|
||||
|
||||
pub extern "Lua" fn spawn(&self, f: function, ...) -> lb_task {
|
||||
pub extern "Lua" fn spawn(f: function, ...) -> lb_task {
|
||||
// pack the function and its arguments into a table and pass its ref to rust.
|
||||
//
|
||||
// this table is used from rust-side to call the function with its args, and it's also
|
||||
// reused to store its return values that the task handle can return when awaited. the ref
|
||||
// is owned by the task handle and unref'ed when it's gc'ed.
|
||||
self.__spawn(__ref(__tpack(f, variadic!())))
|
||||
Self::__spawn(__ref(__tpack(f, variadic!())))
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __spawn(&self, key: c_int) -> lb_task {
|
||||
extern "Lua-C" fn __spawn(key: c_int) -> lb_task {
|
||||
let handle = spawn(async move |cx| {
|
||||
// SAFETY: key is always unique, created by __ref above.
|
||||
let arg = unsafe { cx.new_ref_unchecked(key) };
|
||||
|
@ -5,13 +5,13 @@ describe("temp files", function()
|
||||
test("temp_dir cleans itself", function()
|
||||
local path
|
||||
do
|
||||
local dir = fs:temp_dir()
|
||||
local dir = fs.temp_dir()
|
||||
path = dir:path()
|
||||
assert(path and path ~= "")
|
||||
fs:write(path .. "/test.txt", "test file")
|
||||
assert(fs:read(path .. "/test.txt") == "test file")
|
||||
fs.write(path .. "/test.txt", "test file")
|
||||
assert(fs.read(path .. "/test.txt") == "test file")
|
||||
end
|
||||
collectgarbage()
|
||||
assert(not pcall(fs.read, fs, path .. "/test.txt"))
|
||||
assert(not pcall(fs.read, path .. "/test.txt"))
|
||||
end)
|
||||
end)
|
||||
|
@ -4,14 +4,14 @@ if not ok then return end
|
||||
describe("tcp", function()
|
||||
describe("socket", function()
|
||||
test("bind", function()
|
||||
local socket = net:bind_tcp("127.0.0.1")
|
||||
local socket = net.bind_tcp("127.0.0.1")
|
||||
|
||||
-- binds to the correct port
|
||||
assert(tostring(socket:local_addr():ip()) == "127.0.0.1")
|
||||
assert(socket:local_addr():port() ~= 0)
|
||||
|
||||
-- should not be able to rebind socket
|
||||
assert(not pcall(socket.bind, socket, net:socketaddr("127.0.0.1")))
|
||||
assert(not pcall(socket.bind, socket, net.socketaddr("127.0.0.1")))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@ -74,7 +74,7 @@ describe("sleep", function()
|
||||
value = "value"
|
||||
end)
|
||||
assert(value == nil)
|
||||
task:sleep(100) -- implicit await: if it's synchronous, value wouldn't change
|
||||
task.sleep(100) -- implicit await: if it's synchronous, value wouldn't change
|
||||
assert(value == "value")
|
||||
end)
|
||||
end)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
__internal::{display, type_id},
|
||||
Cdef, CdefBuilder, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder,
|
||||
TypeType, UnsafeExternCFn,
|
||||
Cdef, CdefBuilder, ExternCFn, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type,
|
||||
TypeBuilder, TypeType,
|
||||
};
|
||||
use luaify::luaify;
|
||||
use std::{
|
||||
@ -70,6 +70,45 @@ enum State<F: Future> {
|
||||
Complete,
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
|
||||
fn name() -> impl Display {
|
||||
display!("__future_{:x}", type_id::<F>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct {} {name}", Self::name())
|
||||
}
|
||||
|
||||
fn build(s: &mut TypeBuilder) {
|
||||
s.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Cdef for lua_future<F> {
|
||||
fn build(s: &mut CdefBuilder) {
|
||||
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
|
||||
.field::<ExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
|
||||
.field::<ExternCFn<(&mut Self,), ()>>("__drop");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
|
||||
type Target = Self;
|
||||
|
||||
fn build(s: &mut MetatypeBuilder) {
|
||||
s.metatable_raw(
|
||||
"gc",
|
||||
luaify!(|self| {
|
||||
self.__drop();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future<Output: IntoFfi>> lua_future<F> {
|
||||
pub fn new(fut: F) -> Self {
|
||||
Self {
|
||||
@ -131,40 +170,6 @@ impl Future for lua_pollable {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
|
||||
fn name() -> impl Display {
|
||||
display!("future__{:x}", type_id::<F>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct {} {name}", Self::name())
|
||||
}
|
||||
|
||||
fn build(s: &mut TypeBuilder) {
|
||||
s.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Cdef for lua_future<F> {
|
||||
fn build(s: &mut CdefBuilder) {
|
||||
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
|
||||
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
|
||||
.field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
|
||||
type Target = Self;
|
||||
|
||||
fn build(s: &mut MetatypeBuilder) {
|
||||
s.metatable_raw("gc", luaify!(|self| self.__drop()));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
|
||||
type Into = lua_future<F>;
|
||||
|
||||
|
@ -6,24 +6,6 @@ use std::{
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub mod stub_types {
|
||||
pub struct any;
|
||||
pub struct many;
|
||||
pub struct nil;
|
||||
pub type boolean = bool;
|
||||
pub struct lightuserdata;
|
||||
pub struct number;
|
||||
pub struct integer;
|
||||
pub type string = String;
|
||||
pub struct table;
|
||||
pub struct function;
|
||||
pub struct userdata;
|
||||
pub struct thread;
|
||||
pub struct cdata;
|
||||
pub struct variadic;
|
||||
}
|
||||
|
||||
pub fn type_id<T: 'static>() -> u64 {
|
||||
let mut hash = FxHasher::default();
|
||||
TypeId::of::<T>().hash(&mut hash);
|
||||
|
@ -15,6 +15,7 @@ use std::{
|
||||
#[path = "./internal.rs"]
|
||||
pub mod __internal;
|
||||
pub mod future;
|
||||
pub mod marker;
|
||||
pub mod option;
|
||||
pub mod result;
|
||||
pub mod string;
|
||||
@ -128,7 +129,7 @@ fn cache_local(f: &mut Formatter, list: &[(&str, &str)]) -> fmt::Result {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Registry {
|
||||
types: HashSet<String>,
|
||||
funcs: HashSet<String>,
|
||||
decls: HashSet<String>,
|
||||
cdef: String,
|
||||
lua: String,
|
||||
}
|
||||
@ -136,25 +137,25 @@ pub struct Registry {
|
||||
impl Registry {
|
||||
pub fn new() -> Self {
|
||||
let mut s = Self::default();
|
||||
s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN);
|
||||
s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
|
||||
s.declare::<UnsafeExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
|
||||
s.declare::<ExternCFn<(*const c_void,), ()>>(KEEP_FN);
|
||||
s.declare::<ExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
|
||||
s.declare::<ExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
|
||||
s
|
||||
}
|
||||
|
||||
pub fn include<T: Type>(&mut self) -> &mut Self {
|
||||
self.types
|
||||
.insert(T::name().to_string())
|
||||
.then(|| T::build(&mut TypeBuilder::new::<T>(self)));
|
||||
.then(|| T::build(&mut TypeBuilder::new(self)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
||||
self.include::<T>()
|
||||
.funcs
|
||||
.insert(name.to_string())
|
||||
.then(|| writeln!(self.cdef, "{};", T::extern_cdecl(name)).unwrap());
|
||||
.decls
|
||||
.insert(T::extern_cdecl(&name).to_string())
|
||||
.then(|| writeln!(self.cdef, "{};", T::extern_cdecl(&name)).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
@ -183,7 +184,7 @@ impl Display for Registry {
|
||||
cache_local(f, CACHE_LIBS)?;
|
||||
cache_local(f, CACHE_LOCALS)?;
|
||||
writeln!(f, "{}", include_str!("./lib.lua"))?;
|
||||
writeln!(f, "__cdef [[\n{}\n]];", self.cdef.trim())?;
|
||||
writeln!(f, "__cdef [[\n{}\n]];", self.cdef.trim_end())?;
|
||||
write!(f, "{}", self.lua)
|
||||
}
|
||||
}
|
||||
@ -209,31 +210,23 @@ pub enum TypeType {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeBuilder<'r> {
|
||||
registry: &'r mut Registry,
|
||||
reg: &'r mut Registry,
|
||||
}
|
||||
|
||||
impl<'r> TypeBuilder<'r> {
|
||||
fn new<T: Type>(registry: &'r mut Registry) -> Self {
|
||||
let ct = T::name();
|
||||
let cdecl = T::cdecl("");
|
||||
writeln!(registry.lua, r#"__ct.{ct} = __typeof("{cdecl}");"#).unwrap();
|
||||
Self { registry }
|
||||
}
|
||||
|
||||
pub fn include<T: Type>(&mut self) -> &mut Self {
|
||||
self.registry.include::<T>();
|
||||
self
|
||||
fn new(reg: &'r mut Registry) -> Self {
|
||||
Self { reg }
|
||||
}
|
||||
|
||||
pub fn cdef<T: Cdef>(&mut self) -> &mut Self {
|
||||
let mut b = CdefBuilder::new::<T>(self.registry);
|
||||
let mut b = CdefBuilder::new::<T>(self.reg);
|
||||
<T as Cdef>::build(&mut b);
|
||||
drop(b);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metatype<T: Metatype>(&mut self) -> &mut Self {
|
||||
let mut b = MetatypeBuilder::new::<T>(self.registry);
|
||||
let mut b = MetatypeBuilder::new::<T>(self.reg);
|
||||
<T as Metatype>::build(&mut b);
|
||||
drop(b);
|
||||
self
|
||||
@ -250,16 +243,16 @@ pub unsafe trait Cdef: Type {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CdefBuilder<'r> {
|
||||
registry: &'r mut Registry,
|
||||
reg: &'r mut Registry,
|
||||
cdef: String,
|
||||
align: usize,
|
||||
opaque: usize,
|
||||
}
|
||||
|
||||
impl<'r> CdefBuilder<'r> {
|
||||
fn new<T: Cdef>(registry: &'r mut Registry) -> Self {
|
||||
fn new<T: Cdef>(reg: &'r mut Registry) -> Self {
|
||||
Self {
|
||||
registry,
|
||||
reg,
|
||||
cdef: format!("{} {{ ", T::cdecl("")),
|
||||
align: mem::align_of::<T>(),
|
||||
opaque: 0,
|
||||
@ -268,7 +261,7 @@ impl<'r> CdefBuilder<'r> {
|
||||
|
||||
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
assert!(T::ty() != TypeType::Void, "cannot declare void field");
|
||||
self.registry.include::<T>();
|
||||
self.reg.include::<T>();
|
||||
self.field_raw(T::cdecl(name))
|
||||
}
|
||||
|
||||
@ -302,14 +295,11 @@ impl<'r> CdefBuilder<'r> {
|
||||
impl<'r> Drop for CdefBuilder<'r> {
|
||||
fn drop(&mut self) {
|
||||
let Self {
|
||||
registry,
|
||||
cdef,
|
||||
align,
|
||||
..
|
||||
reg, cdef, align, ..
|
||||
} = self;
|
||||
|
||||
registry.cdef.push_str(cdef);
|
||||
writeln!(registry.cdef, "}} __attribute__((aligned({align})));").unwrap();
|
||||
reg.cdef.push_str(cdef);
|
||||
writeln!(reg.cdef, "}} __attribute__((aligned({align})));").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,26 +310,34 @@ pub unsafe trait Metatype {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MetatypeBuilder<'r> {
|
||||
registry: &'r mut Registry,
|
||||
reg: &'r mut Registry,
|
||||
ct: String,
|
||||
cdef: String,
|
||||
lua: String,
|
||||
lua_includes: Vec<&'static str>,
|
||||
has_index: bool,
|
||||
}
|
||||
|
||||
impl<'r> MetatypeBuilder<'r> {
|
||||
fn new<T: Metatype>(registry: &'r mut Registry) -> Self {
|
||||
fn new<T: Metatype>(reg: &'r mut Registry) -> Self {
|
||||
// NOTE: this needs to be written first, because recursively included dependency types might
|
||||
// need it
|
||||
let ct = T::Target::name();
|
||||
let cdecl = T::Target::cdecl("");
|
||||
writeln!(reg.lua, r#"__ct.{ct} = __typeof("{cdecl}");"#).unwrap();
|
||||
|
||||
Self {
|
||||
registry,
|
||||
reg,
|
||||
ct: T::Target::name().to_string(),
|
||||
cdef: String::new(),
|
||||
lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(),
|
||||
lua: String::new(),
|
||||
lua_includes: vec![],
|
||||
has_index: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
self.registry.declare::<T>(name);
|
||||
self.reg.declare::<T>(name);
|
||||
self
|
||||
}
|
||||
|
||||
@ -353,14 +351,16 @@ impl<'r> MetatypeBuilder<'r> {
|
||||
name: impl Display,
|
||||
f: impl FnOnce(&mut MetatypeFunctionBuilder),
|
||||
) -> &mut Self {
|
||||
write!(self.lua, "__idx.{name} = ").unwrap();
|
||||
write!(self.lua, "Self.{name} = ").unwrap();
|
||||
f(&mut MetatypeFunctionBuilder::new(self));
|
||||
writeln!(self.lua, ";").unwrap();
|
||||
self.has_index = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
|
||||
writeln!(self.lua, "__idx.{name} = {value};").unwrap();
|
||||
writeln!(self.lua, "Self.{name} = {value};").unwrap();
|
||||
self.has_index = true;
|
||||
self
|
||||
}
|
||||
|
||||
@ -384,18 +384,27 @@ impl<'r> MetatypeBuilder<'r> {
|
||||
impl<'r> Drop for MetatypeBuilder<'r> {
|
||||
fn drop(&mut self) {
|
||||
let Self {
|
||||
registry,
|
||||
reg,
|
||||
ct,
|
||||
cdef,
|
||||
lua,
|
||||
lua_includes: lua_postlude,
|
||||
lua_includes,
|
||||
has_index,
|
||||
} = self;
|
||||
|
||||
registry.cdef.push_str(cdef);
|
||||
registry.lua.push_str(lua);
|
||||
writeln!(registry.lua, r#"__metatype(__ct.{ct}, __mt); end;"#).unwrap();
|
||||
for lua in lua_postlude {
|
||||
writeln!(registry.lua, r#"do {lua} end;"#).unwrap();
|
||||
write!(reg.lua, r#"do local __mt = {{}}; "#).unwrap();
|
||||
|
||||
if *has_index {
|
||||
write!(reg.lua, r#"local Self = {{}}; __mt.__index = Self; "#).unwrap();
|
||||
}
|
||||
|
||||
reg.cdef.push_str(cdef);
|
||||
reg.lua.push_str(lua.trim_end());
|
||||
|
||||
writeln!(reg.lua, r#" __metatype(__ct.{ct}, __mt); end;"#).unwrap();
|
||||
|
||||
for lua in lua_includes {
|
||||
writeln!(reg.lua, "do {}\nend;", lua.trim_end()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -470,7 +479,7 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||
);
|
||||
|
||||
let Self {
|
||||
metatype: MetatypeBuilder { registry, .. },
|
||||
metatype: MetatypeBuilder { reg, .. },
|
||||
lparams,
|
||||
cparams,
|
||||
cargs,
|
||||
@ -479,7 +488,7 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||
..
|
||||
} = self;
|
||||
|
||||
registry.include::<T::From>();
|
||||
reg.include::<T::From>();
|
||||
|
||||
(!lparams.is_empty()).then(|| lparams.push_str(", "));
|
||||
(!cparams.is_empty()).then(|| cparams.push_str(", "));
|
||||
@ -551,13 +560,7 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||
|
||||
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
|
||||
let Self {
|
||||
metatype:
|
||||
MetatypeBuilder {
|
||||
registry,
|
||||
cdef,
|
||||
lua,
|
||||
..
|
||||
},
|
||||
metatype: MetatypeBuilder { reg, cdef, lua, .. },
|
||||
lparams,
|
||||
cparams,
|
||||
cargs,
|
||||
@ -566,7 +569,7 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||
..
|
||||
} = self;
|
||||
|
||||
registry.include::<T::Into>();
|
||||
reg.include::<T::Into>();
|
||||
write!(lua, "function({lparams}) {prelude}").unwrap();
|
||||
|
||||
match T::convention() {
|
||||
@ -603,6 +606,10 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Annotation {
|
||||
fn annotation() -> impl Display;
|
||||
}
|
||||
|
||||
//
|
||||
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
|
||||
// equivalent to passing a unit type as an argument in C.
|
||||
@ -636,7 +643,7 @@ impl_void!(());
|
||||
impl_void!(c_void);
|
||||
|
||||
macro_rules! impl_primitive {
|
||||
($rty:ty, $cty:expr) => {
|
||||
($rty:ty, $cty:expr, $lty:expr) => {
|
||||
unsafe impl Type for $rty {
|
||||
fn name() -> impl Display {
|
||||
$cty
|
||||
@ -652,22 +659,28 @@ macro_rules! impl_primitive {
|
||||
|
||||
fn build(_b: &mut TypeBuilder) {}
|
||||
}
|
||||
|
||||
impl Annotation for $rty {
|
||||
fn annotation() -> impl Display {
|
||||
$lty
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_primitive!(bool, "bool");
|
||||
impl_primitive!(u8, "uint8_t");
|
||||
impl_primitive!(u16, "uint16_t");
|
||||
impl_primitive!(u32, "uint32_t");
|
||||
impl_primitive!(u64, "uint64_t");
|
||||
impl_primitive!(usize, "uintptr_t");
|
||||
impl_primitive!(i8, "int8_t");
|
||||
impl_primitive!(i16, "int16_t");
|
||||
impl_primitive!(i32, "int32_t");
|
||||
impl_primitive!(i64, "int64_t");
|
||||
impl_primitive!(isize, "intptr_t");
|
||||
impl_primitive!(c_float, "float");
|
||||
impl_primitive!(c_double, "double");
|
||||
impl_primitive!(bool, "bool", "boolean");
|
||||
impl_primitive!(u8, "uint8_t", "number");
|
||||
impl_primitive!(u16, "uint16_t", "number");
|
||||
impl_primitive!(u32, "uint32_t", "number");
|
||||
impl_primitive!(u64, "uint64_t", "number");
|
||||
impl_primitive!(usize, "uintptr_t", "number");
|
||||
impl_primitive!(i8, "int8_t", "number");
|
||||
impl_primitive!(i16, "int16_t", "number");
|
||||
impl_primitive!(i32, "int32_t", "number");
|
||||
impl_primitive!(i64, "int64_t", "number");
|
||||
impl_primitive!(isize, "intptr_t", "number");
|
||||
impl_primitive!(c_float, "float", "number");
|
||||
impl_primitive!(c_double, "double", "number");
|
||||
|
||||
unsafe impl FromFfi for bool {
|
||||
type From = bool;
|
||||
@ -772,11 +785,19 @@ impl_bigint_intoabi!(usize);
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
impl_bigint_intoabi!(isize);
|
||||
|
||||
macro_rules! impl_const_ptr {
|
||||
($ty:ty) => {
|
||||
unsafe impl<T: Type> Type for $ty {
|
||||
macro_rules! impl_ptr {
|
||||
($ty:ty, $mutable:expr) => {
|
||||
unsafe impl<T> Type for $ty
|
||||
where
|
||||
T: Type,
|
||||
{
|
||||
fn name() -> impl Display {
|
||||
display!("const_{}_ptr", T::name())
|
||||
disp(|f| {
|
||||
if !$mutable {
|
||||
write!(f, "const_")?;
|
||||
}
|
||||
write!(f, "{}_ptr", T::name())
|
||||
})
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
@ -784,45 +805,59 @@ macro_rules! impl_const_ptr {
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("const *{name}"))
|
||||
T::cdecl(disp(move |f| {
|
||||
if !$mutable {
|
||||
write!(f, "const ")?;
|
||||
}
|
||||
write!(f, "*{name}")
|
||||
}))
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
b.reg.include::<T>();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_const_ptr!(*const T);
|
||||
impl_const_ptr!(&T);
|
||||
impl_const_ptr!(Option<&T>);
|
||||
impl_ptr!(&T, false);
|
||||
impl_ptr!(&mut T, true);
|
||||
impl_ptr!(*const T, false);
|
||||
impl_ptr!(*mut T, true);
|
||||
impl_ptr!(Option<&T>, false);
|
||||
impl_ptr!(Option<&mut T>, true);
|
||||
|
||||
macro_rules! impl_mut_ptr {
|
||||
macro_rules! impl_ptr_annotation {
|
||||
($ty:ty) => {
|
||||
unsafe impl<T: Type> Type for $ty {
|
||||
fn name() -> impl Display {
|
||||
display!("{}_ptr", T::name())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Primitive
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("*{name}"))
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
impl<T> Annotation for $ty
|
||||
where
|
||||
T: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
"lightuserdata"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_mut_ptr!(*mut T);
|
||||
impl_mut_ptr!(&mut T);
|
||||
impl_mut_ptr!(Option<&mut T>);
|
||||
impl_ptr_annotation!(*const T);
|
||||
impl_ptr_annotation!(*mut T);
|
||||
|
||||
macro_rules! impl_ref_annotation {
|
||||
($ty:ty) => {
|
||||
impl<T> Annotation for $ty
|
||||
where
|
||||
T: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}", T::annotation())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_ref_annotation!(&T);
|
||||
impl_ref_annotation!(&mut T);
|
||||
|
||||
//
|
||||
// SAFETY: Pass by value for pointers, which maps to a `cdata` argument in lua containing either:
|
||||
@ -835,7 +870,10 @@ impl_mut_ptr!(Option<&mut T>);
|
||||
//
|
||||
macro_rules! impl_ptr_fromabi {
|
||||
($ty:ty) => {
|
||||
unsafe impl<T: Type> FromFfi for $ty {
|
||||
unsafe impl<T> FromFfi for $ty
|
||||
where
|
||||
T: Type,
|
||||
{
|
||||
type From = Self;
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
@ -857,7 +895,10 @@ impl_ptr_fromabi!(Option<&mut T>);
|
||||
//
|
||||
macro_rules! impl_ptr_intoabi {
|
||||
($ty:ty) => {
|
||||
unsafe impl<T: Type> IntoFfi for $ty {
|
||||
unsafe impl<T> IntoFfi for $ty
|
||||
where
|
||||
T: Type,
|
||||
{
|
||||
type Into = Self;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
@ -898,7 +939,10 @@ impl_ptr_intoabi!(Option<&'static mut T>);
|
||||
//
|
||||
macro_rules! impl_ref_fromabi {
|
||||
($ty:ty) => {
|
||||
unsafe impl<'s, T: Type> FromFfi for $ty {
|
||||
unsafe impl<'s, T> FromFfi for $ty
|
||||
where
|
||||
T: Type,
|
||||
{
|
||||
type From = Option<$ty>;
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
@ -928,7 +972,10 @@ impl_ref_fromabi!(&'s mut T);
|
||||
//
|
||||
macro_rules! impl_ref_intoabi {
|
||||
($ty:ty) => {
|
||||
unsafe impl<T: Type> IntoFfi for $ty {
|
||||
unsafe impl<T> IntoFfi for $ty
|
||||
where
|
||||
T: Type,
|
||||
{
|
||||
type Into = Self;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
@ -947,7 +994,10 @@ impl_ref_intoabi!(&'static mut T);
|
||||
//
|
||||
// TODO: we could automatically convert them to tables and vice-versa
|
||||
//
|
||||
unsafe impl<T: Type> Type for [T] {
|
||||
unsafe impl<T> Type for [T]
|
||||
where
|
||||
T: Type,
|
||||
{
|
||||
fn name() -> impl Display {
|
||||
display!("{}_arr", T::name())
|
||||
}
|
||||
@ -961,11 +1011,23 @@ unsafe impl<T: Type> Type for [T] {
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
b.reg.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type, const N: usize> Type for [T; N] {
|
||||
impl<T> Annotation for [T]
|
||||
where
|
||||
T: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}[]", T::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T, const N: usize> Type for [T; N]
|
||||
where
|
||||
T: Type,
|
||||
{
|
||||
fn name() -> impl Display {
|
||||
display!("{}_arr{N}", T::name())
|
||||
}
|
||||
@ -979,18 +1041,27 @@ unsafe impl<T: Type, const N: usize> Type for [T; N] {
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.include::<T>();
|
||||
b.reg.include::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnsafeExternCFn<In, Out>(PhantomData<unsafe extern "C" fn(In) -> Out>);
|
||||
impl<T, const N: usize> Annotation for [T; N]
|
||||
where
|
||||
T: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}[]", T::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_function {
|
||||
(fn($($arg:tt),*) -> $ret:tt) => {
|
||||
impl_function!(UnsafeExternCFn, fn($($arg),*) -> $ret);
|
||||
pub struct ExternCFn<I, O>(PhantomData<extern "C" fn(I) -> O>);
|
||||
|
||||
macro_rules! impl_externcfn {
|
||||
(fn($($arg:ident),*) -> $ret:ident) => {
|
||||
impl_externcfn!(ExternCFn, fn($($arg),*) -> $ret);
|
||||
};
|
||||
|
||||
($ty:tt, fn($($arg:tt),*) -> $ret:tt) => {
|
||||
($ty:ident, fn($($arg:ident),*) -> $ret:ident) => {
|
||||
//
|
||||
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
|
||||
// in `&mut T`).
|
||||
@ -998,7 +1069,11 @@ macro_rules! impl_function {
|
||||
// We also can't implement `IntoFfi` because we can't call `FromFfi` and `IntoFfi` for the
|
||||
// function's respective argument and return values.
|
||||
//
|
||||
unsafe impl<$($arg: Type,)* $ret: Type> Type for $ty<($($arg,)*), $ret> {
|
||||
unsafe impl<$($arg,)* $ret> Type for $ty<($($arg,)*), $ret>
|
||||
where
|
||||
$($arg: Type,)*
|
||||
$ret: Type,
|
||||
{
|
||||
fn name() -> impl Display {
|
||||
disp(|f| Ok({
|
||||
write!(f, "fn_{}", $ret::name())?;
|
||||
@ -1012,8 +1087,8 @@ macro_rules! impl_function {
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
$ret::cdecl(disp(move |f| Ok({
|
||||
let mut _n = 0;
|
||||
write!(f, "(*{name})(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
|
||||
write!(f, ")")?;
|
||||
})))
|
||||
@ -1022,26 +1097,40 @@ macro_rules! impl_function {
|
||||
fn extern_cdecl(name: impl Display) -> impl Display {
|
||||
$ret::cdecl(disp(move |f| Ok({
|
||||
// for top-level function declarations in cdef
|
||||
let mut _n = 0;
|
||||
write!(f, "{name}(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
|
||||
write!(f, ")")?;
|
||||
})))
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
$(b.include::<$arg>();)*
|
||||
b.include::<$ret>();
|
||||
$(b.reg.include::<$arg>();)*
|
||||
b.reg.include::<$ret>();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_function!(fn() -> Z);
|
||||
impl_function!(fn(A) -> Z);
|
||||
impl_function!(fn(A, B) -> Z);
|
||||
impl_function!(fn(A, B, C) -> Z);
|
||||
impl_function!(fn(A, B, C, D) -> Z);
|
||||
impl_function!(fn(A, B, C, D, E) -> Z);
|
||||
impl_function!(fn(A, B, C, D, E, F) -> Z);
|
||||
impl_function!(fn(A, B, C, D, E, F, G) -> Z);
|
||||
impl_externcfn!(fn() -> A);
|
||||
impl_externcfn!(fn(A) -> B);
|
||||
impl_externcfn!(fn(A, B) -> C);
|
||||
impl_externcfn!(fn(A, B, C) -> D);
|
||||
impl_externcfn!(fn(A, B, C, D) -> E);
|
||||
impl_externcfn!(fn(A, B, C, D, E) -> F);
|
||||
impl_externcfn!(fn(A, B, C, D, E, F) -> G);
|
||||
impl_externcfn!(fn(A, B, C, D, E, F, G) -> H);
|
||||
impl_externcfn!(fn(A, B, C, D, E, F, G, H) -> I);
|
||||
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||
|
||||
impl<'s> Annotation for &'s str {
|
||||
fn annotation() -> impl Display {
|
||||
"string"
|
||||
}
|
||||
}
|
||||
|
||||
impl Annotation for String {
|
||||
fn annotation() -> impl Display {
|
||||
"string"
|
||||
}
|
||||
}
|
||||
|
193
crates/luaffi/src/marker.rs
Normal file
193
crates/luaffi/src/marker.rs
Normal file
@ -0,0 +1,193 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::{
|
||||
__internal::{disp, display},
|
||||
Annotation,
|
||||
};
|
||||
use std::{fmt::Display, marker::PhantomData};
|
||||
|
||||
enum Marker {}
|
||||
|
||||
pub struct any(Marker);
|
||||
|
||||
impl Annotation for any {
|
||||
fn annotation() -> impl Display {
|
||||
"any"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct many(Marker);
|
||||
|
||||
impl Annotation for many {
|
||||
fn annotation() -> impl Display {
|
||||
"..."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct nil(Marker);
|
||||
|
||||
impl Annotation for nil {
|
||||
fn annotation() -> impl Display {
|
||||
"nil"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct lightuserdata(Marker);
|
||||
|
||||
impl Annotation for lightuserdata {
|
||||
fn annotation() -> impl Display {
|
||||
"lightuserdata"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct table<K, V>(Marker, PhantomData<*mut [(K, V)]>);
|
||||
|
||||
impl<K, V> Annotation for table<K, V>
|
||||
where
|
||||
K: Annotation,
|
||||
V: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("table<{}, {}>", K::annotation(), V::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct function(Marker);
|
||||
|
||||
impl Annotation for function {
|
||||
fn annotation() -> impl Display {
|
||||
"function"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct fun<I, O>(Marker, PhantomData<fn(I) -> O>);
|
||||
|
||||
macro_rules! impl_fun {
|
||||
(fn($($arg:ident),*)) => {
|
||||
impl<$($arg,)*> Annotation for fun<($($arg,)*), ()>
|
||||
where
|
||||
$($arg: Annotation,)*
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
write!(f, "fun(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::annotation())?; _n += 1;)*
|
||||
write!(f, ")")
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(fn($($arg:ident),*) -> $ret:ident) => {
|
||||
impl<$($arg,)* $ret> Annotation for fun<($($arg,)*), $ret>
|
||||
where
|
||||
$($arg: Annotation,)*
|
||||
$ret: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
write!(f, "fun(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::annotation())?; _n += 1;)*
|
||||
write!(f, "): {}", $ret::annotation())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl_fun!(fn($($arg),*));
|
||||
};
|
||||
}
|
||||
|
||||
impl_fun!(fn() -> A);
|
||||
impl_fun!(fn(A) -> B);
|
||||
impl_fun!(fn(A, B) -> C);
|
||||
impl_fun!(fn(A, B, C) -> D);
|
||||
impl_fun!(fn(A, B, C, D) -> E);
|
||||
impl_fun!(fn(A, B, C, D, E) -> F);
|
||||
impl_fun!(fn(A, B, C, D, E, F) -> G);
|
||||
impl_fun!(fn(A, B, C, D, E, F, G) -> H);
|
||||
impl_fun!(fn(A, B, C, D, E, F, G, H) -> I);
|
||||
impl_fun!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||
|
||||
pub struct userdata(Marker);
|
||||
|
||||
impl Annotation for userdata {
|
||||
fn annotation() -> impl Display {
|
||||
"userdata"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct thread(Marker);
|
||||
|
||||
impl Annotation for thread {
|
||||
fn annotation() -> impl Display {
|
||||
"thread"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct cdata(Marker);
|
||||
|
||||
impl Annotation for cdata {
|
||||
fn annotation() -> impl Display {
|
||||
"cdata"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Either<T, U>(Marker, PhantomData<(T, U)>);
|
||||
|
||||
impl<T, U> Annotation for Either<T, U>
|
||||
where
|
||||
T: Annotation,
|
||||
U: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("({} | {})", T::annotation(), U::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OneOf<X>(Marker, PhantomData<X>);
|
||||
|
||||
macro_rules! impl_oneof {
|
||||
($($ty:ident),+) => {
|
||||
impl<$($ty),+> Annotation for OneOf<($($ty,)+)>
|
||||
where
|
||||
$($ty: Annotation),+
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
write!(f, "(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, " | ")?; } write!(f, "{}", $ty::annotation())?; _n += 1;)*
|
||||
write!(f, ")")
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_oneof!(A, B);
|
||||
impl_oneof!(A, B, C);
|
||||
impl_oneof!(A, B, C, D);
|
||||
impl_oneof!(A, B, C, D, E);
|
||||
impl_oneof!(A, B, C, D, E, F);
|
||||
impl_oneof!(A, B, C, D, E, F, G);
|
||||
impl_oneof!(A, B, C, D, E, F, G, H);
|
||||
impl_oneof!(A, B, C, D, E, F, G, H, I);
|
||||
|
||||
impl<T> Annotation for Option<T>
|
||||
where
|
||||
T: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}?", T::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Annotation for Result<T, E>
|
||||
where
|
||||
T: Annotation,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}", T::annotation())
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
__internal::{disp, display},
|
||||
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Type, TypeBuilder, TypeType,
|
||||
__internal::{disp, display, type_id},
|
||||
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
|
||||
};
|
||||
use std::{ffi::c_int, fmt::Display};
|
||||
|
||||
@ -11,9 +11,9 @@ pub enum lua_option<T> {
|
||||
Some(T), // __tag = 1
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Type for lua_option<T> {
|
||||
unsafe impl<T: Type + 'static> Type for lua_option<T> {
|
||||
fn name() -> impl Display {
|
||||
display!("option__{}", T::name())
|
||||
display!("__option_{:x}", type_id::<T>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
@ -25,18 +25,23 @@ unsafe impl<T: Type> Type for lua_option<T> {
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.cdef::<Self>();
|
||||
b.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Cdef for lua_option<T> {
|
||||
unsafe impl<T: Type + 'static> Cdef for lua_option<T> {
|
||||
fn build(b: &mut CdefBuilder) {
|
||||
b.field::<c_int>("__tag");
|
||||
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: IntoFfi> IntoFfi for Option<T> {
|
||||
unsafe impl<T: Type + 'static> Metatype for lua_option<T> {
|
||||
type Target = Self;
|
||||
fn build(_b: &mut MetatypeBuilder) {}
|
||||
}
|
||||
|
||||
unsafe impl<T: IntoFfi<Into: 'static>> IntoFfi for Option<T> {
|
||||
type Into = lua_option<T::Into>;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
__internal::{disp, display},
|
||||
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Type, TypeBuilder, TypeType,
|
||||
__internal::{disp, display, type_id},
|
||||
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
|
||||
string::{DROP_BUFFER_FN, lua_buffer},
|
||||
};
|
||||
use std::{ffi::c_int, fmt::Display};
|
||||
@ -12,9 +12,9 @@ pub enum lua_result<T> {
|
||||
Ok(T), // __tag = 1
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Type for lua_result<T> {
|
||||
unsafe impl<T: Type + 'static> Type for lua_result<T> {
|
||||
fn name() -> impl Display {
|
||||
display!("result__{}", T::name())
|
||||
display!("__result_{:x}", type_id::<T>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
@ -26,11 +26,11 @@ unsafe impl<T: Type> Type for lua_result<T> {
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.cdef::<Self>();
|
||||
b.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Cdef for lua_result<T> {
|
||||
unsafe impl<T: Type + 'static> Cdef for lua_result<T> {
|
||||
fn build(b: &mut CdefBuilder) {
|
||||
b.field::<c_int>("__tag").inner_union(|b| {
|
||||
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
||||
@ -39,7 +39,12 @@ unsafe impl<T: Type> Cdef for lua_result<T> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: IntoFfi, E: Display> IntoFfi for Result<T, E> {
|
||||
unsafe impl<T: Type + 'static> Metatype for lua_result<T> {
|
||||
type Target = Self;
|
||||
fn build(_b: &mut MetatypeBuilder) {}
|
||||
}
|
||||
|
||||
unsafe impl<T: IntoFfi<Into: 'static>, E: Display> IntoFfi for Result<T, E> {
|
||||
type Into = lua_result<T::Into>;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
|
@ -32,7 +32,7 @@ pub fn transform(args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
|
||||
let mod_name = format_ident!("__{name}_cdef");
|
||||
|
||||
Ok(quote_spanned!(name.span() =>
|
||||
Ok(quote!(
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#item
|
||||
@ -51,10 +51,9 @@ pub fn transform(args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
|
||||
fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let span = ty.span();
|
||||
let name = LitStr::new(&ty.unraw().to_string(), span);
|
||||
let name = ty.unraw().to_string();
|
||||
|
||||
Ok(quote_spanned!(span =>
|
||||
Ok(quote!(
|
||||
unsafe impl #ffi::Type for #ty {
|
||||
fn name() -> impl ::std::fmt::Display { #name }
|
||||
|
||||
@ -71,6 +70,10 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
impl #ffi::Annotation for #ty {
|
||||
fn annotation() -> impl ::std::fmt::Display { #name }
|
||||
}
|
||||
|
||||
// SAFETY: we can always implement `IntoFfi` because it transfers ownership from Rust to Lua
|
||||
unsafe impl #ffi::IntoFfi for #ty {
|
||||
type Into = Self;
|
||||
@ -82,7 +85,7 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
fn generate_module(name: &str, ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
|
||||
Ok(quote_spanned!(ty.span() =>
|
||||
Ok(quote!(
|
||||
impl #ffi::Module for #ty {
|
||||
fn name() -> impl ::std::fmt::Display { #name }
|
||||
}
|
||||
@ -98,10 +101,9 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &str.ident;
|
||||
let span = ty.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
||||
|
||||
Ok(quote_spanned!(span =>
|
||||
Ok(quote!(
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) { #build }
|
||||
}
|
||||
@ -117,18 +119,16 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &enu.ident;
|
||||
let span = ty.span();
|
||||
let build = enu
|
||||
.variants
|
||||
.iter_mut()
|
||||
.map(|variant| {
|
||||
let span = variant.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote_spanned!(span => b.inner_struct(|b| { #build })))
|
||||
Ok(quote!(b.inner_struct(|b| { #build })))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(quote_spanned!(span =>
|
||||
Ok(quote!(
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) {
|
||||
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build;)* });
|
||||
@ -201,7 +201,7 @@ fn generate_cdef_build(fields: &[CField]) -> Result<TokenStream> {
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let ty = &field.ty;
|
||||
let offset = offset(i);
|
||||
body.push(quote_spanned!(ty.span() =>
|
||||
body.push(quote!(
|
||||
// round up current offset to the alignment of field type for field offset
|
||||
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
|
||||
align = #max(align, #align_of::<#ty>());
|
||||
|
@ -21,7 +21,7 @@ pub fn transform(args: Args, mut imp: ItemImpl) -> Result<TokenStream> {
|
||||
let impls = generate_impls(&args, &mut imp)?;
|
||||
let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?);
|
||||
|
||||
Ok(quote_spanned!(imp.self_ty.span() =>
|
||||
Ok(quote!(
|
||||
#imp
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -119,7 +119,7 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
let build = ®istry.build;
|
||||
let exports = generate_ffi_exports(®istry)?;
|
||||
|
||||
Ok(quote_spanned!(ty.span() =>
|
||||
Ok(quote!(
|
||||
impl #ty { #(#shims)* }
|
||||
|
||||
unsafe impl #ffi::Metatype for #ty {
|
||||
@ -219,10 +219,10 @@ impl ToTokens for Metamethod {
|
||||
|
||||
struct FfiFunction {
|
||||
name: Ident,
|
||||
is_async: bool,
|
||||
params: Vec<PatType>,
|
||||
params: Vec<(Ident, Type)>,
|
||||
ret: Type,
|
||||
attrs: FfiFunctionAttrs,
|
||||
is_async: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -251,30 +251,31 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
FnArg::Receiver(recv) => {
|
||||
let ty = &recv.ty;
|
||||
parse_quote_spanned!(ty.span() => self: #ty)
|
||||
}
|
||||
FnArg::Typed(ty) => ty.clone(),
|
||||
.map(|arg| {
|
||||
Ok(match arg {
|
||||
FnArg::Receiver(recv) => {
|
||||
(Ident::new("self", recv.span()), (*recv.ty).clone())
|
||||
}
|
||||
FnArg::Typed(ty) => (pat_ident(&ty.pat)?.clone(), (*ty.ty).clone()),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let ret = match func.sig.output {
|
||||
ReturnType::Default => parse_quote!(()),
|
||||
ReturnType::Type(_, ref ty) => (**ty).clone(),
|
||||
};
|
||||
|
||||
for param in params.iter() {
|
||||
// double underscores are reserved for generated glue code
|
||||
for (name, ty) in params.iter() {
|
||||
// double underscores are reserved for glue code
|
||||
syn_assert!(
|
||||
!pat_ident(¶m.pat)?.to_string().starts_with("__"),
|
||||
param.pat,
|
||||
!name.to_string().starts_with("__"),
|
||||
name,
|
||||
"parameter names should not start with `__`"
|
||||
);
|
||||
|
||||
// lifetime should be determined by the caller (lua)
|
||||
if let Type::Reference(ref ty) = *param.ty {
|
||||
// lifetime should be determined by the caller (which is lua)
|
||||
if let Type::Reference(ty) = ty {
|
||||
syn_assert!(
|
||||
ty.lifetime.is_none(),
|
||||
ty.lifetime,
|
||||
@ -290,10 +291,10 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
|
||||
funcs.push(FfiFunction {
|
||||
name: func.sig.ident.clone(),
|
||||
is_async: func.sig.asyncness.is_some(),
|
||||
params,
|
||||
ret,
|
||||
attrs,
|
||||
is_async: func.sig.asyncness.is_some(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -387,16 +388,8 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
||||
let func_params = &func.params; // target function parameters
|
||||
let func_ret = &func.ret; // target function return type
|
||||
let mut func_args = vec![]; // target function arguments
|
||||
|
||||
let mut shim_params = vec![]; // shim function parameters
|
||||
let mut shim_ret = if func.is_async {
|
||||
// shim function return type
|
||||
quote_spanned!(func_ret.span() => #ffi::future::lua_future<impl ::std::future::Future<Output = #func_ret>>)
|
||||
} else {
|
||||
quote_spanned!(func_ret.span() => <#func_ret as #ffi::IntoFfi>::Into)
|
||||
};
|
||||
|
||||
let mut asserts = vec![]; // compile-time builder asserts
|
||||
let mut asserts = vec![]; // compile-time asserts
|
||||
let mut build = vec![]; // ffi builder body
|
||||
|
||||
// for __new metamethods, ignore the first argument (ctype of self, for which there is no
|
||||
@ -407,99 +400,102 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
||||
));
|
||||
}
|
||||
|
||||
for (i, param) in func_params.iter().enumerate() {
|
||||
let func_param = ¶m.ty;
|
||||
for (i, (name, func_param)) in func_params.iter().enumerate() {
|
||||
let span = func_param.span();
|
||||
let name = name.unraw().to_string();
|
||||
let shim_param = format_ident!("arg{i}");
|
||||
let name = pat_ident(¶m.pat)?.unraw().to_string();
|
||||
|
||||
match get_ffi_param_type(func_param) {
|
||||
FfiParameterType::Default => {
|
||||
shim_params.push(quote_spanned!(func_param.span() =>
|
||||
shim_params.push(quote_spanned!(span =>
|
||||
#shim_param: <#func_param as #ffi::FromFfi>::From
|
||||
));
|
||||
func_args.push(quote_spanned!(param.pat.span() =>
|
||||
func_args.push(quote_spanned!(span =>
|
||||
<#func_param as #ffi::FromFfi>::convert(#shim_param)
|
||||
));
|
||||
build.push(quote_spanned!(param.pat.span() =>
|
||||
build.push(quote_spanned!(span =>
|
||||
b.param::<#func_param>(#name);
|
||||
));
|
||||
}
|
||||
ty @ (FfiParameterType::StringLike(str) | FfiParameterType::OptionStringLike(str)) => {
|
||||
let shim_param_len = format_ident!("arg{i}_len");
|
||||
shim_params.push(quote_spanned!(func_param.span() =>
|
||||
shim_params.push(quote_spanned!(span =>
|
||||
#shim_param: ::std::option::Option<&::std::primitive::u8>,
|
||||
#shim_param_len: ::std::primitive::usize
|
||||
));
|
||||
let allow_nil = matches!(ty, FfiParameterType::OptionStringLike(_));
|
||||
let check_utf8 = matches!(str, StringLike::Str);
|
||||
let mut func_arg = quote_spanned!(func_param.span() =>
|
||||
let mut func_arg = quote_spanned!(span =>
|
||||
#shim_param.map(|s| ::std::slice::from_raw_parts(s, #shim_param_len))
|
||||
);
|
||||
func_arg = match str {
|
||||
StringLike::SliceU8 => func_arg,
|
||||
StringLike::BStr => {
|
||||
quote_spanned!(func_param.span() => #func_arg.map(::bstr::BStr::new))
|
||||
quote_spanned!(span => #func_arg.map(::bstr::BStr::new))
|
||||
}
|
||||
StringLike::Str => {
|
||||
quote_spanned!(func_param.span() => #func_arg.map(|s| {
|
||||
quote_spanned!(span => #func_arg.map(|s| {
|
||||
::std::debug_assert!(::std::str::from_utf8(s).is_ok());
|
||||
::std::str::from_utf8_unchecked(s)
|
||||
}))
|
||||
}
|
||||
};
|
||||
if !allow_nil {
|
||||
func_arg = quote_spanned!(func_param.span() => {
|
||||
func_arg = quote_spanned!(span => {
|
||||
let arg = #func_arg;
|
||||
::std::debug_assert!(arg.is_some());
|
||||
arg.unwrap_unchecked()
|
||||
});
|
||||
}
|
||||
func_args.push(func_arg);
|
||||
build.push(quote_spanned!(param.pat.span() =>
|
||||
build.push(quote_spanned!(span =>
|
||||
b.param_str(#name, #allow_nil, #check_utf8);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shim function body
|
||||
let mut shim_body = if func.is_async {
|
||||
// for async functions, wrapped the returned future in lua_future
|
||||
quote_spanned!(func_name.span() => #ffi::future::lua_future::new(Self::#func_name(#(#func_args),*)))
|
||||
} else {
|
||||
quote_spanned!(func_name.span() => <#func_ret as #ffi::IntoFfi>::convert(Self::#func_name(#(#func_args),*)))
|
||||
};
|
||||
let func_call = quote!(Self::#func_name(#(#func_args),*)); // target function call
|
||||
|
||||
if !func.is_async {
|
||||
// shim function body and return type
|
||||
let (shim_body, shim_ret) = if func.is_async {
|
||||
let span = func_ret.span();
|
||||
(
|
||||
// for async functions, wrapped the returned future in lua_future
|
||||
quote_spanned!(span => #ffi::future::lua_future::new(#func_call)),
|
||||
quote_spanned!(span => #ffi::future::lua_future<impl ::std::future::Future<Output = #func_ret>>),
|
||||
)
|
||||
} else {
|
||||
let span = func_ret.span();
|
||||
match get_ffi_ret_type(&func_ret) {
|
||||
FfiReturnType::Void => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
asserts.push(quote_spanned!(span =>
|
||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Void
|
||||
));
|
||||
(func_call, quote_spanned!(span => ()))
|
||||
}
|
||||
FfiReturnType::ByValue => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
asserts.push(quote_spanned!(span =>
|
||||
<#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByValue
|
||||
));
|
||||
(
|
||||
quote_spanned!(span => <#func_ret as #ffi::IntoFfi>::convert(#func_call)),
|
||||
quote_spanned!(span => <#func_ret as #ffi::IntoFfi>::Into),
|
||||
)
|
||||
}
|
||||
FfiReturnType::ByOutParam => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
asserts.push(quote_spanned!(span =>
|
||||
<#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByOutParam
|
||||
));
|
||||
|
||||
shim_params.insert(0, quote_spanned!(func_ret.span() => out: *mut #shim_ret));
|
||||
|
||||
(shim_body, shim_ret) = (
|
||||
quote_spanned!(func_ret.span() => ::std::ptr::write(out, #shim_body)),
|
||||
quote_spanned!(func_ret.span() => ()),
|
||||
);
|
||||
let out = quote_spanned!(span => out: *mut <#func_ret as #ffi::IntoFfi>::Into);
|
||||
shim_params.insert(0, out);
|
||||
(
|
||||
quote_spanned!(span => ::std::ptr::write(out, <#func_ret as #ffi::IntoFfi>::convert(#func_call))),
|
||||
quote_spanned!(span => ()),
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// build.push(quote_spanned!(func_name.span() =>
|
||||
// b.call_inferred(#c_name, Self::#func_name);
|
||||
// ));
|
||||
}
|
||||
};
|
||||
|
||||
build.push({
|
||||
let infer_args = iter::repeat_n(quote!(::std::unreachable!()), func_params.len());
|
||||
@ -508,23 +504,16 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
||||
} else {
|
||||
quote!(|| Self::#func_name(#(#infer_args),*))
|
||||
};
|
||||
quote_spanned!(func_name.span() => b.call_inferred(#c_name, #infer);)
|
||||
quote!(b.call_inferred(#c_name, #infer);)
|
||||
});
|
||||
|
||||
registry.build.push(quote_spanned!(func_name.span() =>
|
||||
#(::std::assert!(#asserts);)*
|
||||
));
|
||||
|
||||
registry.build.push(quote!(#(::std::assert!(#asserts);)*));
|
||||
registry.build.push(match func.attrs.metamethod {
|
||||
Some(ref mm) => quote_spanned!(func_name.span() =>
|
||||
b.metatable(#mm, |b| { #(#build)* });
|
||||
),
|
||||
None => quote_spanned!(func_name.span() =>
|
||||
b.index(#lua_name, |b| { #(#build)* });
|
||||
),
|
||||
Some(ref mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
||||
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
||||
});
|
||||
|
||||
registry.shims.push(parse_quote_spanned!(func_name.span() =>
|
||||
registry.shims.push(parse_quote!(
|
||||
#[unsafe(export_name = #c_name)]
|
||||
unsafe extern "C" fn #shim_name(#(#shim_params),*) -> #shim_ret { unsafe { #shim_body } }
|
||||
));
|
||||
@ -536,7 +525,7 @@ fn generate_ffi_exports(registry: &Registry) -> Result<TokenStream> {
|
||||
let ty = ®istry.ty;
|
||||
let names = registry.shims.iter().map(|f| &f.sig.ident);
|
||||
|
||||
Ok(quote_spanned!(ty.span() =>
|
||||
Ok(quote!(
|
||||
// this ensures ffi function symbol exports are actually present in the resulting binary,
|
||||
// otherwise they may get dead code-eliminated before it reaches the linker
|
||||
#[used]
|
||||
@ -583,18 +572,14 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
Ok(match arg {
|
||||
FnArg::Receiver(recv) => Pat::Type(parse_quote_spanned!(recv.span() =>
|
||||
self: cdata
|
||||
)),
|
||||
FnArg::Typed(ty) => Pat::Type(ty.clone()),
|
||||
})
|
||||
.map(|arg| match arg {
|
||||
FnArg::Receiver(recv) => parse_quote_spanned!(recv.span() => self),
|
||||
FnArg::Typed(ty) => (*ty.pat).clone(), // ignore parameter type (only used for documentation purposes)
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
.collect();
|
||||
|
||||
if let Some(ref variadic) = func.sig.variadic {
|
||||
params.push(parse_quote_spanned!(variadic.span() => variadic!()));
|
||||
params.push(parse_quote_spanned!(variadic.span() => variadic!())); // luaify builtin macro
|
||||
}
|
||||
|
||||
let attrs = parse_lua_function_attrs(&mut func.attrs)?;
|
||||
@ -625,62 +610,45 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
||||
func.attrs.push(parse_quote!(#[allow(unused)]));
|
||||
func.block.stmts.clear();
|
||||
func.block.stmts.push(parse_quote!(
|
||||
::std::unreachable!("cannot call lua function from rust");
|
||||
const fn has_annotation<T: #ffi::Annotation>() {}
|
||||
));
|
||||
|
||||
let inputs = &mut func.sig.inputs;
|
||||
let output = &mut func.sig.output;
|
||||
|
||||
for input in inputs.iter_mut() {
|
||||
if let FnArg::Typed(pat) = input
|
||||
&& let Ok(stub) = stub_lua_type(&pat.ty)
|
||||
{
|
||||
pat.ty = stub.into();
|
||||
}
|
||||
}
|
||||
|
||||
if let ReturnType::Type(_, ty) = output
|
||||
&& let Ok(stub) = stub_lua_type(ty)
|
||||
{
|
||||
*ty = stub.into();
|
||||
}
|
||||
|
||||
// convert `...` variadic to a `rest: luaffi::marker::Many` parameter
|
||||
if let Some(ref variadic) = func.sig.variadic {
|
||||
let ty = quote_spanned!(variadic.span() => variadic);
|
||||
inputs.push(parse_quote!(rest: #ffi::__internal::stub_types::#ty));
|
||||
let param = Ident::new("rest", variadic.span());
|
||||
let ty = quote_spanned!(variadic.span() => many);
|
||||
func.sig.variadic = None;
|
||||
func.sig
|
||||
.inputs
|
||||
.push(parse_quote!(#param: #ffi::marker::#ty));
|
||||
}
|
||||
|
||||
for param in func.sig.inputs.iter() {
|
||||
let ty = match param {
|
||||
FnArg::Receiver(recv) => &recv.ty,
|
||||
FnArg::Typed(ty) => &ty.ty,
|
||||
};
|
||||
|
||||
// temporary assertion until we implement annotation generation
|
||||
func.block.stmts.push(parse_quote!(
|
||||
has_annotation::<#ty>();
|
||||
));
|
||||
}
|
||||
|
||||
// temporary assertion until we implement annotation generation
|
||||
if let ReturnType::Type(_, ref ty) = func.sig.output {
|
||||
func.block.stmts.push(parse_quote!(
|
||||
has_annotation::<#ty>();
|
||||
));
|
||||
}
|
||||
|
||||
func.block.stmts.push(parse_quote!(
|
||||
::std::unreachable!(r#"cannot call extern "Lua" function from rust"#);
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stub_lua_type(ty: &Type) -> Result<Type> {
|
||||
let ffi = ffi_crate();
|
||||
let span = ty.span();
|
||||
let ty = if let Type::Infer(_) = ty {
|
||||
quote_spanned!(span => any)
|
||||
} else {
|
||||
match ty_name(ty)?.to_string().as_str() {
|
||||
"any" => quote_spanned!(span => any),
|
||||
"many" => quote_spanned!(span => many),
|
||||
"nil" => quote_spanned!(span => nil),
|
||||
"boolean" => quote_spanned!(span => boolean),
|
||||
"lightuserdata" => quote_spanned!(span => lightuserdata),
|
||||
"number" => quote_spanned!(span => number),
|
||||
"integer" => quote_spanned!(span => integer),
|
||||
"string" => quote_spanned!(span => string),
|
||||
"table" => quote_spanned!(span => table),
|
||||
"function" => quote_spanned!(span => function),
|
||||
"userdata" => quote_spanned!(span => userdata),
|
||||
"thread" => quote_spanned!(span => thread),
|
||||
"cdata" => quote_spanned!(span => cdata),
|
||||
_ => syn_error!(ty, "unknown lua type"),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(parse_quote!(#ffi::__internal::stub_types::#ty))
|
||||
}
|
||||
|
||||
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
||||
let mut parsed = LuaFunctionAttrs::default();
|
||||
let mut i = 0;
|
||||
@ -688,11 +656,6 @@ fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAtt
|
||||
if let Some(name) = attr.path().get_ident()
|
||||
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
||||
{
|
||||
match method {
|
||||
Metamethod::New => syn_error!(attr, r#"cannot be applied to a lua function"#),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
parsed.metamethod = Some(method);
|
||||
attrs.remove(i);
|
||||
} else {
|
||||
@ -712,12 +675,8 @@ fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
|
||||
let name = func_name.unraw().to_string();
|
||||
|
||||
registry.build.push(match func.attrs.metamethod {
|
||||
Some(ref mm) => quote_spanned!(func_name.span() =>
|
||||
b.metatable_raw(#mm, #luaify(|#(#params),*| #body));
|
||||
),
|
||||
None => quote_spanned!(func_name.span() =>
|
||||
b.index_raw(#name, #luaify(|#(#params),*| #body));
|
||||
),
|
||||
Some(ref mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
||||
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@ -752,46 +711,36 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
||||
"finaliser must take exactly one parameter"
|
||||
);
|
||||
|
||||
match lua.params[0] {
|
||||
// should be `self: cdata` PatType
|
||||
Pat::Type(ref ty) => {
|
||||
syn_assert!(
|
||||
pat_ident(&ty.pat)? == "self",
|
||||
lua.params[0],
|
||||
"finaliser parameter must be `self`"
|
||||
);
|
||||
}
|
||||
_ => syn_error!(lua.params[0], "finaliser parameter must be `self`"),
|
||||
}
|
||||
|
||||
let params = &lua.params;
|
||||
let param = pat_ident(&lua.params[0])?;
|
||||
let body = &lua.body;
|
||||
|
||||
registry.build.push(quote_spanned!(ty.span() =>
|
||||
syn_assert!(param == "self", param, "finaliser parameter must be `self`");
|
||||
|
||||
registry.build.push(quote!(
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
// if we have both a lua-side finaliser and a rust drop, then merge the finalisers
|
||||
// by doing the lua part first then drop rust
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.declare::<#ffi::ExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.metatable_raw("gc", #luaify(|self| {
|
||||
raw!(#luaify(#body)); // embed the lua part inside a do block
|
||||
__C::#c_name(self);
|
||||
}));
|
||||
} else {
|
||||
// we only have a lua-side finaliser
|
||||
b.metatable_raw("gc", #luaify(|#(#params),*| #body));
|
||||
b.metatable_raw("gc", #luaify(|self| #body));
|
||||
}
|
||||
));
|
||||
} else {
|
||||
registry.build.push(quote_spanned!(ty.span() =>
|
||||
registry.build.push(quote!(
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
// we only have a rust drop
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.declare::<#ffi::ExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.metatable_raw("gc", ::std::format_args!("__C.{}", #c_name_str));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
registry.shims.push(parse_quote_spanned!(ty.span() =>
|
||||
registry.shims.push(parse_quote!(
|
||||
#[unsafe(export_name = #c_name_str)]
|
||||
unsafe extern "C" fn #shim_name(ptr: *mut Self) {
|
||||
unsafe { ::std::ptr::drop_in_place(ptr) }
|
||||
@ -803,55 +752,73 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
||||
|
||||
fn document_ffi_function(func: &mut ImplItemFn) {
|
||||
func.attrs.insert(0, parse_quote!(#[doc =
|
||||
r#"<span class="stab" title="This function is implemented in Rust and called via FFI." style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">FFI</span>"#
|
||||
r#"<span
|
||||
class="stab"
|
||||
title="This function is implemented in Rust and called via FFI."
|
||||
style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||
>FFI</span>"#
|
||||
]));
|
||||
}
|
||||
|
||||
fn document_lua_function(func: &mut ImplItemFn) {
|
||||
func.attrs.insert(0, parse_quote!(#[doc =
|
||||
r#"<span class="stab" title="This function is implemented in Lua." style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Lua</span>"#
|
||||
r#"<span
|
||||
class="stab"
|
||||
title="This function is implemented in Lua."
|
||||
style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||
>Lua</span>"#
|
||||
]));
|
||||
}
|
||||
|
||||
fn document_async(func: &mut ImplItemFn) {
|
||||
func.attrs.insert(0, parse_quote!(#[doc =
|
||||
r#"<span class="stab" title="This function is asynchronous." style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Async</span>"#
|
||||
r#"<span
|
||||
class="stab"
|
||||
title="This function is asynchronous and will yield the calling thread."
|
||||
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||
>Async</span>"#
|
||||
]));
|
||||
}
|
||||
|
||||
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
|
||||
let s = match method {
|
||||
Metamethod::Eq => "This is a metamethod which is called by the `==` operator.",
|
||||
Metamethod::Len => "This is a metamethod which is called by the `#` operator.",
|
||||
Metamethod::Lt => "This is a metamethod which is called by the `<` operator.",
|
||||
Metamethod::Le => "This is a metamethod which is called by the `<=` operator.",
|
||||
Metamethod::Concat => "This is a metamethod which is called by the `..` operator.",
|
||||
Metamethod::Call => {
|
||||
"This is a metamethod which can be called by calling `(...)` on the value directly."
|
||||
}
|
||||
Metamethod::Add => "This is a metamethod which is called by the `+` operator.",
|
||||
Metamethod::Sub => "This is a metamethod which is called by the `-` operator.",
|
||||
Metamethod::Mul => "This is a metamethod which is called by the `*` operator.",
|
||||
Metamethod::Div => "This is a metamethod which is called by the `/` operator.",
|
||||
Metamethod::Mod => "This is a metamethod which is called by the `%` operator.",
|
||||
Metamethod::Pow => "This is a metamethod which is called by the `^` operator.",
|
||||
Metamethod::Unm => "This is a metamethod which is called by the `-` operator.",
|
||||
Metamethod::ToString => {
|
||||
"This is a metamethod which is called by the [`tostring(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-tostring) built-in function."
|
||||
}
|
||||
Metamethod::Pairs => {
|
||||
"This is a metamethod which is called by the [`pairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pairs) built-in function."
|
||||
}
|
||||
Metamethod::Ipairs => {
|
||||
"This is a metamethod which is called by the [`ipairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-ipairs) built-in function."
|
||||
}
|
||||
_ => "This is a metamethod and cannot be called directly.",
|
||||
};
|
||||
|
||||
func.attrs.insert(0, parse_quote!(#[doc =
|
||||
r#"<span class="stab" title="This function is a metamethod." style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Metamethod</span>"#
|
||||
r#"<span
|
||||
class="stab"
|
||||
title="This function is a metamethod."
|
||||
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||
>Metamethod</span>"#
|
||||
]));
|
||||
|
||||
let doc = match method {
|
||||
Metamethod::Eq => "This function is a metamethod which is called by the `==` operator.",
|
||||
Metamethod::Len => "This function is a metamethod which is called by the `#` operator.",
|
||||
Metamethod::Lt => "This function is a metamethod which is called by the `<` operator.",
|
||||
Metamethod::Le => "This function is a metamethod which is called by the `<=` operator.",
|
||||
Metamethod::Concat => "This function is a metamethod which is called by the `..` operator.",
|
||||
Metamethod::Call => {
|
||||
"This function is a metamethod which can be called by calling `(...)` on the value directly."
|
||||
}
|
||||
Metamethod::Add => "This function is a metamethod which is called by the `+` operator.",
|
||||
Metamethod::Sub => "This function is a metamethod which is called by the `-` operator.",
|
||||
Metamethod::Mul => "This function is a metamethod which is called by the `*` operator.",
|
||||
Metamethod::Div => "This function is a metamethod which is called by the `/` operator.",
|
||||
Metamethod::Mod => "This function is a metamethod which is called by the `%` operator.",
|
||||
Metamethod::Pow => "This function is a metamethod which is called by the `^` operator.",
|
||||
Metamethod::Unm => "This function is a metamethod which is called by the `-` operator.",
|
||||
Metamethod::ToString => {
|
||||
"This function is a metamethod which is called by the [`tostring(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-tostring) built-in function."
|
||||
}
|
||||
Metamethod::Pairs => {
|
||||
"This function is a metamethod which is called by the [`pairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pairs) built-in function."
|
||||
}
|
||||
Metamethod::Ipairs => {
|
||||
"This function is a metamethod which is called by the [`ipairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-ipairs) built-in function."
|
||||
}
|
||||
_ => "This function is a metamethod and cannot be called directly.",
|
||||
};
|
||||
|
||||
func.attrs.push(parse_quote!(#[doc = ""]));
|
||||
func.attrs.push(parse_quote!(#[doc = #s]));
|
||||
func.attrs.push(parse_quote!(#[doc = "# Metamethod"]));
|
||||
func.attrs.push(parse_quote!(#[doc = ""]));
|
||||
func.attrs.push(parse_quote!(#[doc = #doc]));
|
||||
}
|
||||
|
@ -916,7 +916,7 @@ fn generate_pat_typed(f: &mut Formatter, typed: &PatType, cx: PatContext) -> Res
|
||||
assert_no_attrs!(typed);
|
||||
match *typed.ty {
|
||||
Type::Infer(_) => generate_pat(f, &typed.pat, cx),
|
||||
ref ty => syn_error!(ty, "cannot have type"),
|
||||
ref ty => syn_error!(ty, "cannot specify type"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
use crate::utils::{LuaType, expr_ident, pat_ident, syn_error, wrap_expr_block};
|
||||
use quote::format_ident;
|
||||
use std::mem;
|
||||
use crate::utils::syn_error;
|
||||
use syn::{spanned::*, visit_mut::*, *};
|
||||
|
||||
pub fn transform(expr: &mut Expr) -> Result<()> {
|
||||
@ -27,27 +25,6 @@ impl Visitor {
|
||||
}
|
||||
|
||||
impl VisitMut for Visitor {
|
||||
fn visit_expr_closure_mut(&mut self, clo: &mut ExprClosure) {
|
||||
match self.transform_expr_closure(clo) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_closure_mut(self, clo),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_fn_mut(&mut self, func: &mut ItemFn) {
|
||||
match self.transform_function(func) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_item_fn_mut(self, func),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_mut(&mut self, expr: &mut Expr) {
|
||||
match self.transform_expr(expr) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_mut(self, expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_unary_mut(&mut self, un: &mut ExprUnary) {
|
||||
match self.transform_unary(un) {
|
||||
res @ Err(_) => self.result = res,
|
||||
@ -64,147 +41,9 @@ impl VisitMut for Visitor {
|
||||
}
|
||||
|
||||
impl Visitor {
|
||||
fn transform_expr_closure(&mut self, clo: &mut ExprClosure) -> Result<()> {
|
||||
//
|
||||
// transforms a closure expression with input type annotations by removing the annotations
|
||||
// and inserting `as` casts at the start.
|
||||
//
|
||||
// before:
|
||||
// |a: string, b: number| { ... }
|
||||
// after:
|
||||
// |a, b| { a as string; b as number; ... }
|
||||
//
|
||||
let mut checks: Vec<Stmt> = vec![];
|
||||
for input in clo.inputs.iter_mut() {
|
||||
match input {
|
||||
Pat::Ident(_) => {}
|
||||
Pat::Type(typed) => {
|
||||
let ident = pat_ident(&typed.pat)?;
|
||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
||||
match (&*ty).try_into()? {
|
||||
LuaType::Any => {}
|
||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !checks.is_empty() {
|
||||
let mut body = wrap_expr_block(&clo.body);
|
||||
body.stmts.splice(..0, checks);
|
||||
clo.body = Box::new(parse_quote! { #body });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_function(&mut self, func: &mut ItemFn) -> Result<()> {
|
||||
//
|
||||
// transforms a function item with input type annotations by removing the annotations
|
||||
// and inserting `as` casts at the start.
|
||||
//
|
||||
// before:
|
||||
// fn my_func(self: table, a: string) { ... }
|
||||
// after:
|
||||
// fn my_func(self: _, a: _) { self as table; a as string; ... }
|
||||
//
|
||||
let mut checks: Vec<Stmt> = vec![];
|
||||
for input in func.sig.inputs.iter_mut() {
|
||||
if let Some((ident, ty)) = match input {
|
||||
FnArg::Receiver(recv) if recv.colon_token.is_some() => {
|
||||
let ty = mem::replace(&mut recv.ty, parse_quote!(_));
|
||||
recv.colon_token = None;
|
||||
Some((Ident::new("self", recv.self_token.span()), ty))
|
||||
}
|
||||
FnArg::Typed(typed) => {
|
||||
let ident = pat_ident(&typed.pat)?;
|
||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
||||
Some((ident, ty))
|
||||
}
|
||||
_ => None,
|
||||
} {
|
||||
match (&*ty).try_into()? {
|
||||
LuaType::Any => {}
|
||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
func.block.stmts.splice(..0, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_expr(&mut self, expr: &mut Expr) -> Result<()> {
|
||||
self.transform_expr_cast(expr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_expr_cast(&mut self, expr: &mut Expr) -> Result<()> {
|
||||
//
|
||||
// transforms an `as` cast expression into a block expression containing a runtime
|
||||
// lua type check.
|
||||
//
|
||||
// before:
|
||||
// var as string
|
||||
// after:
|
||||
// { if type(var) != "string" { error(...) } }
|
||||
//
|
||||
if let Expr::Cast(cast) = expr {
|
||||
let arg = (*cast.expr).clone();
|
||||
let mut prelude: Option<Stmt> = None;
|
||||
let ty: LuaType = (&*cast.ty).try_into()?;
|
||||
let ty_str = format!("{ty}");
|
||||
let (ident, msg) = match expr_ident(&arg) {
|
||||
Ok(ident) => (ident.clone(), format!("{ty} expected in '{ident}', got ")),
|
||||
Err(_) => {
|
||||
let ident = Ident::new("_", arg.span());
|
||||
prelude = Some(parse_quote! { let #ident = #arg; });
|
||||
(ident, format!("{ty} expected, got "))
|
||||
}
|
||||
};
|
||||
|
||||
let tmp = format_ident!("__{ident}");
|
||||
let span = cast.span();
|
||||
*expr = match ty {
|
||||
LuaType::Any => parse_quote_spanned!(span => {}),
|
||||
LuaType::Nil => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
assert(#ident == (), concat!(#msg, r#type(#ident)));
|
||||
}),
|
||||
LuaType::Number => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
let #tmp = #ident;
|
||||
#ident = tonumber(#ident);
|
||||
assert(#ident != (), concat!(#msg, r#type(#tmp)));
|
||||
}),
|
||||
LuaType::Integer => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
let #tmp = #ident;
|
||||
#ident = tonumber(#ident);
|
||||
assert(#ident != () && math::floor(#ident) == #ident, concat!(#msg, r#type(#tmp)));
|
||||
}),
|
||||
LuaType::String => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
if r#type(#ident) == "number" {
|
||||
#ident = tostring(#ident);
|
||||
} else {
|
||||
assert(r#type(#ident) == "string", concat!(#msg, r#type(#ident)));
|
||||
}
|
||||
}),
|
||||
_ => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
assert(r#type(#ident) == #ty_str, concat!(#msg, r#type(#ident)));
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_unary(&mut self, un: &mut ExprUnary) -> Result<()> {
|
||||
//
|
||||
// separates a nested negation unary operator with parentheses, because double hyphen
|
||||
// separates a nested negation unary operator with parentheses, because the double hyphen
|
||||
// `--` indicates a comment in lua.
|
||||
//
|
||||
// before:
|
||||
@ -216,7 +55,7 @@ impl Visitor {
|
||||
&& let Expr::Unary(ref inner) = *un.expr
|
||||
&& let UnOp::Neg(_) = inner.op
|
||||
{
|
||||
un.expr = Box::new(parse_quote!((#inner)));
|
||||
un.expr = Box::new(parse_quote_spanned!(inner.span() => (#inner)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::fmt;
|
||||
use syn::{ext::*, spanned::*, *};
|
||||
use syn::*;
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
@ -24,90 +23,3 @@ pub fn wrap_expr_block(expr: &Expr) -> Block {
|
||||
expr => parse_quote!({ #expr }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expr_ident(expr: &Expr) -> Result<&Ident> {
|
||||
match expr {
|
||||
Expr::Path(path) => path.path.require_ident(),
|
||||
_ => syn_error!(expr, "expected ident"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pat_ident(pat: &Pat) -> Result<Ident> {
|
||||
Ok(match pat {
|
||||
Pat::Ident(ident) => match ident.subpat {
|
||||
Some((_, ref subpat)) => syn_error!(subpat, "unexpected subpattern"),
|
||||
None => ident.ident.clone(),
|
||||
},
|
||||
Pat::Wild(wild) => Ident::new("_", wild.span()),
|
||||
_ => syn_error!(pat, "expected ident"),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LuaType {
|
||||
Any,
|
||||
Nil,
|
||||
Boolean,
|
||||
Lightuserdata,
|
||||
Number,
|
||||
Integer,
|
||||
String,
|
||||
Table,
|
||||
Function,
|
||||
Userdata,
|
||||
Thread,
|
||||
Cdata,
|
||||
}
|
||||
|
||||
impl fmt::Display for LuaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
LuaType::Any => write!(f, "any"),
|
||||
LuaType::Nil => write!(f, "nil"),
|
||||
LuaType::Boolean => write!(f, "boolean"),
|
||||
LuaType::Lightuserdata => write!(f, "lightuserdata"),
|
||||
LuaType::Number => write!(f, "number"),
|
||||
LuaType::Integer => write!(f, "integer"),
|
||||
LuaType::String => write!(f, "string"),
|
||||
LuaType::Table => write!(f, "table"),
|
||||
LuaType::Function => write!(f, "function"),
|
||||
LuaType::Userdata => write!(f, "userdata"),
|
||||
LuaType::Thread => write!(f, "thread"),
|
||||
LuaType::Cdata => write!(f, "cdata"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Ident> for LuaType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Ident) -> Result<Self> {
|
||||
Ok(match format!("{}", value.unraw()).as_str() {
|
||||
"any" => Self::Any,
|
||||
"nil" => Self::Nil,
|
||||
"boolean" => Self::Boolean,
|
||||
"lightuserdata" => Self::Lightuserdata,
|
||||
"number" => Self::Number,
|
||||
"integer" => Self::Integer,
|
||||
"string" => Self::String,
|
||||
"table" => Self::Table,
|
||||
"function" => Self::Function,
|
||||
"userdata" => Self::Userdata,
|
||||
"thread" => Self::Thread,
|
||||
"cdata" => Self::Cdata,
|
||||
_ => syn_error!(value, "invalid lua type"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Type> for LuaType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Type) -> Result<Self> {
|
||||
match value {
|
||||
Type::Infer(_) => Ok(Self::Any),
|
||||
Type::Path(path) if path.qself.is_none() => path.path.require_ident()?.try_into(),
|
||||
_ => syn_error!(value, "invalid lua type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,13 +77,6 @@ fn local_fn() {
|
||||
}),
|
||||
r#"function(a,b)local function inner(c,d)end;return inner;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
fn check(self: string, arg: number) {}
|
||||
inner
|
||||
}),
|
||||
r#"function()local function check(self,arg)do if type(self)=="number"then self=tostring(self);else assert(type(self)=="string","string expected in \'self\', got "..type(self));end;end;do local __arg=arg;arg=tonumber(arg);assert(arg~=nil,"number expected in \'arg\', got "..type(__arg));end;end;return inner;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -208,37 +201,6 @@ fn loops() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_checks() {
|
||||
assert_eq!(luaify!(|s| {}), r#"function(s)end"#);
|
||||
assert_eq!(
|
||||
luaify!(|s: table| {}),
|
||||
r#"function(s)do assert(type(s)=="table","table expected in \'s\', got "..type(s));end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as string }),
|
||||
r#"function(s)do if type(s)=="number"then s=tostring(s);else assert(type(s)=="string","string expected in \'s\', got "..type(s));end;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as number }),
|
||||
r#"function(s)do local __s=s;s=tonumber(s);assert(s~=nil,"number expected in \'s\', got "..type(__s));end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as nil }),
|
||||
r#"function(s)do assert(s==nil,"nil expected in \'s\', got "..type(s));end;end"#
|
||||
);
|
||||
assert_eq!(luaify!(|s| { s as any }), r#"function(s)do end;end"#);
|
||||
|
||||
assert_eq!(
|
||||
luaify!(|s| {
|
||||
let (ok, res) = coroutine::r#yield(thread);
|
||||
ok as boolean;
|
||||
res as nil;
|
||||
}),
|
||||
r#"function(s)local ok,res=coroutine.yield(thread);do assert(type(ok)=="boolean","boolean expected in \'ok\', got "..type(ok));end;do assert(res==nil,"nil expected in \'res\', got "..type(res));end;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concat() {
|
||||
assert_eq!(luaify!(concat!(a)), r#"a"#);
|
||||
|
16
src/main.rs
16
src/main.rs
@ -137,12 +137,13 @@ impl Args {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), ExitCode> {
|
||||
fn main() -> ExitCode {
|
||||
panic::set_hook(Box::new(panic_cb));
|
||||
|
||||
let args = Args::parse();
|
||||
if args.version {
|
||||
return Ok(print_version());
|
||||
print_version();
|
||||
return ExitCode::Ok;
|
||||
}
|
||||
|
||||
init_logger(&args);
|
||||
@ -153,7 +154,10 @@ fn main() -> Result<(), ExitCode> {
|
||||
|
||||
tokio.block_on(async {
|
||||
lua.await;
|
||||
main.await.unwrap()
|
||||
match main.await {
|
||||
Ok(res) => res,
|
||||
Err(err) => panic::resume_unwind(err.into_panic()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -268,13 +272,13 @@ fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> Result<(), ExitCode> {
|
||||
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
||||
for ref path in args.path {
|
||||
let chunk = match std::fs::read(path) {
|
||||
Ok(chunk) => chunk,
|
||||
Err(err) => {
|
||||
eprintln!("{}", format_args!("{path}: {err}").red().bold());
|
||||
return Err(ExitCode::NoInput);
|
||||
return ExitCode::NoInput;
|
||||
}
|
||||
};
|
||||
|
||||
@ -285,5 +289,5 @@ async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> Result<(), Exi
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
ExitCode::Ok
|
||||
}
|
||||
|
@ -111,8 +111,8 @@ local function main(item)
|
||||
end
|
||||
|
||||
return main(create_group("", function()
|
||||
local function glob(path, pat)
|
||||
for entry in fs:glob_dir(path, pat) do
|
||||
local function include(path, pat)
|
||||
for entry in fs.glob_dir(path, pat) do
|
||||
local path = entry:path()
|
||||
local f, err = loadfile(path)
|
||||
if not f then error(err) end
|
||||
@ -120,6 +120,6 @@ return main(create_group("", function()
|
||||
end
|
||||
end
|
||||
|
||||
glob("tests", "**/*.lua")
|
||||
glob("crates", "*/tests/**/*.lua")
|
||||
include("tests", "**/*.lua")
|
||||
include("crates", "*/tests/**/*.lua")
|
||||
end))
|
@ -1,7 +1,7 @@
|
||||
use luajit::Chunk;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{
|
||||
fs,
|
||||
fs, panic,
|
||||
process::{self, ExitCode},
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ fn main() -> ExitCode {
|
||||
rt.unhandled_error(error_cb).build().unwrap()
|
||||
};
|
||||
|
||||
let path = "tests/test.lua";
|
||||
let path = "tests/main.lua";
|
||||
let main = lua.spawn(async move |s| {
|
||||
if let Err(ref err) = s.load(Chunk::new(fs::read(path).unwrap()).path(path)) {
|
||||
s.report_error(err);
|
||||
@ -30,7 +30,10 @@ fn main() -> ExitCode {
|
||||
|
||||
tokio.block_on(async move {
|
||||
lua.await;
|
||||
main.await.unwrap()
|
||||
match main.await {
|
||||
Ok(res) => res,
|
||||
Err(err) => panic::resume_unwind(err.into_panic()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user