Compare commits
15 Commits
27c40c3244
...
8c406a46b3
Author | SHA1 | Date | |
---|---|---|---|
8c406a46b3 | |||
263ca1cf48 | |||
505364a661 | |||
d5e85f2c30 | |||
cea9bc0813 | |||
85ed0d8318 | |||
eaa40ff3bc | |||
124e9bedfe | |||
a2cbb24a75 | |||
36300b07d3 | |||
db9d611ff7 | |||
a90c36a260 | |||
c760d12c39 | |||
ef811ecfa9 | |||
d9ef6c7806 |
@ -11,7 +11,7 @@ repository.workspace = true
|
|||||||
runtime = ["tokio/rt"]
|
runtime = ["tokio/rt"]
|
||||||
task = ["tokio/rt", "tokio/time"]
|
task = ["tokio/rt", "tokio/time"]
|
||||||
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
||||||
net = ["tokio/net"]
|
net = ["tokio/net", "tokio/io-util"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
derive_more = { version = "2.0.1", features = ["full"] }
|
derive_more = { version = "2.0.1", features = ["full"] }
|
||||||
|
@ -12,9 +12,12 @@
|
|||||||
//! ## Exports
|
//! ## Exports
|
||||||
//!
|
//!
|
||||||
//! See [`lb_fslib`] for items exported by this library.
|
//! See [`lb_fslib`] for items exported by this library.
|
||||||
use derive_more::From;
|
|
||||||
use luaffi::{cdef, metatype};
|
use luaffi::{cdef, metatype};
|
||||||
use std::{path::PathBuf, time::SystemTime};
|
use std::{
|
||||||
|
cell::{BorrowError, BorrowMutError, RefCell},
|
||||||
|
path::PathBuf,
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Errors that can be thrown by this library.
|
/// Errors that can be thrown by this library.
|
||||||
@ -23,6 +26,12 @@ use thiserror::Error;
|
|||||||
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
|
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// Attempt to access an object while it is being modified.
|
||||||
|
#[error("cannot access object while it is being modified")]
|
||||||
|
Borrow(#[from] BorrowError),
|
||||||
|
/// Attempt to modify an object while it is in use
|
||||||
|
#[error("cannot modify object while it is in use")]
|
||||||
|
BorrowMut(#[from] BorrowMutError),
|
||||||
/// I/O error.
|
/// I/O error.
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
@ -54,38 +63,85 @@ impl lb_fslib {
|
|||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads the entire contents of a file.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file does not exist or cannot be read.
|
||||||
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
|
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
|
||||||
Ok(tokio::fs::read(path).await?)
|
Ok(tokio::fs::read(path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads the entire contents of a file synchronously.
|
||||||
|
///
|
||||||
|
/// This is a synchronous complement to [`read`](Self::read).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file does not exist or cannot be read.
|
||||||
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
|
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
|
||||||
Ok(std::fs::read(path)?)
|
Ok(std::fs::read(path)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes the given contents to a file, replacing its contents if it exists.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file cannot be written.
|
||||||
pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> {
|
pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> {
|
||||||
Ok(tokio::fs::write(path, contents).await?)
|
Ok(tokio::fs::write(path, contents).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes the given contents to a file synchronously, replacing its contents if it exists.
|
||||||
|
///
|
||||||
|
/// This is a synchronous complement to [`write`](Self::write).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file cannot be written.
|
||||||
pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> {
|
pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> {
|
||||||
Ok(std::fs::write(path, contents)?)
|
Ok(std::fs::write(path, contents)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads the entries in a directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory cannot be read.
|
||||||
pub async extern "Lua-C" fn read_dir(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())
|
Ok(lb_read_dir::new(tokio::fs::read_dir(path).await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads the entries in a directory synchronously.
|
||||||
|
///
|
||||||
|
/// This is a synchronous complement to [`read_dir`](Self::read_dir).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory cannot be read.
|
||||||
pub extern "Lua-C" fn read_dir_sync(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())
|
Ok(lb_read_dir_sync::new(std::fs::read_dir(path)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively walks a directory, yielding all entries within it.
|
||||||
pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir {
|
pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir {
|
||||||
walkdir::WalkDir::new(path).into_iter().into()
|
lb_walk_dir::new(walkdir::WalkDir::new(path).into_iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "Lua-C" fn glob(pattern: &str) -> Result<lb_glob_dir> {
|
/// Returns an iterator over all files matching a glob pattern in the current directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the pattern is invalid.
|
||||||
|
pub extern "Lua" fn glob(pattern: &str) -> Result<lb_glob_dir> {
|
||||||
Self::glob_dir(".", pattern)
|
Self::glob_dir(".", pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all files matching a glob pattern in the given directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the pattern is invalid.
|
||||||
pub extern "Lua-C" fn glob_dir(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 prefix = PathBuf::from(path);
|
||||||
let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter();
|
let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter();
|
||||||
@ -93,76 +149,128 @@ impl lb_fslib {
|
|||||||
.add(globset::Glob::new(pattern)?)
|
.add(globset::Glob::new(pattern)?)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
Ok(lb_glob_dir {
|
Ok(lb_glob_dir::new(iter, matcher, prefix))
|
||||||
iter,
|
|
||||||
matcher,
|
|
||||||
prefix,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new temporary directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the temporary directory cannot be created.
|
||||||
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
|
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
|
||||||
Ok(tempfile::tempdir()?.into())
|
Ok(lb_temp_dir::new(tempfile::tempdir()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new temporary directory inside the specified path.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the temporary directory cannot be created.
|
||||||
pub extern "Lua-C" fn temp_dir_in(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())
|
Ok(lb_temp_dir::new(tempfile::tempdir_in(path)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator over the entries in a directory.
|
/// Iterator over the entries in a directory.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_read_dir(#[opaque] tokio::fs::ReadDir);
|
pub struct lb_read_dir(#[opaque] RefCell<tokio::fs::ReadDir>);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_read_dir {
|
impl lb_read_dir {
|
||||||
|
fn new(iter: tokio::fs::ReadDir) -> Self {
|
||||||
|
Self(RefCell::new(iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry in the directory, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory cannot be read.
|
||||||
#[call]
|
#[call]
|
||||||
pub async extern "Lua-C" fn next(&mut self) -> Result<Option<lb_dir_entry>> {
|
pub async extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry>> {
|
||||||
Ok(self.0.next_entry().await?.map(Into::into))
|
Ok(self
|
||||||
|
.0
|
||||||
|
.try_borrow_mut()?
|
||||||
|
.next_entry()
|
||||||
|
.await?
|
||||||
|
.map(lb_dir_entry::new))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Synchronous version of [`lb_read_dir`].
|
/// Synchronous version of [`lb_read_dir`].
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_read_dir_sync(#[opaque] std::fs::ReadDir);
|
pub struct lb_read_dir_sync(#[opaque] RefCell<std::fs::ReadDir>);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_read_dir_sync {
|
impl lb_read_dir_sync {
|
||||||
|
fn new(iter: std::fs::ReadDir) -> Self {
|
||||||
|
Self(RefCell::new(iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry in the directory, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory cannot be read.
|
||||||
#[call]
|
#[call]
|
||||||
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_dir_entry_sync>> {
|
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry_sync>> {
|
||||||
Ok(self.0.next().transpose()?.map(Into::into))
|
Ok(self
|
||||||
|
.0
|
||||||
|
.try_borrow_mut()?
|
||||||
|
.next()
|
||||||
|
.transpose()?
|
||||||
|
.map(lb_dir_entry_sync::new))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Entry inside of a directory on the filesystem.
|
/// Entry inside of a directory on the filesystem.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_dir_entry(#[opaque] tokio::fs::DirEntry);
|
pub struct lb_dir_entry(#[opaque] tokio::fs::DirEntry);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_dir_entry {
|
impl lb_dir_entry {
|
||||||
|
fn new(entry: tokio::fs::DirEntry) -> Self {
|
||||||
|
Self(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
pub extern "Lua-C" fn path(&self) -> String {
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
self.0.path().to_string_lossy().into()
|
self.0.path().to_string_lossy().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the file name of this entry.
|
||||||
pub extern "Lua-C" fn name(&self) -> String {
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
self.0.file_name().to_string_lossy().into()
|
self.0.file_name().to_string_lossy().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file type cannot be determined.
|
||||||
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||||
Ok(self.0.file_type().await?.into())
|
Ok(lb_file_type::new(self.0.file_type().await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata for this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the metadata cannot be retrieved.
|
||||||
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
Ok(self.0.metadata().await?.into())
|
Ok(lb_file_meta::new(self.0.metadata().await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inode number for this entry.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub extern "Lua-C" fn ino(&self) -> u64 {
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
self.0.ino()
|
self.0.ino()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
#[tostring]
|
#[tostring]
|
||||||
pub extern "Lua" fn tostring(&self) -> String {
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
self.path()
|
self.path()
|
||||||
@ -170,34 +278,52 @@ impl lb_dir_entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Synchronous version of [`lb_dir_entry`].
|
/// Synchronous version of [`lb_dir_entry`].
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_dir_entry_sync(#[opaque] std::fs::DirEntry);
|
pub struct lb_dir_entry_sync(#[opaque] std::fs::DirEntry);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_dir_entry_sync {
|
impl lb_dir_entry_sync {
|
||||||
|
fn new(entry: std::fs::DirEntry) -> Self {
|
||||||
|
Self(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
pub extern "Lua-C" fn path(&self) -> String {
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
self.0.path().to_string_lossy().into()
|
self.0.path().to_string_lossy().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the file name of this entry.
|
||||||
pub extern "Lua-C" fn name(&self) -> String {
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
self.0.file_name().to_string_lossy().into()
|
self.0.file_name().to_string_lossy().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file type cannot be determined.
|
||||||
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||||
Ok(self.0.file_type()?.into())
|
Ok(lb_file_type::new(self.0.file_type()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata for this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the metadata cannot be retrieved.
|
||||||
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
Ok(self.0.metadata()?.into())
|
Ok(lb_file_meta::new(self.0.metadata()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inode number for this entry.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub extern "Lua-C" fn ino(&self) -> u64 {
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
use std::os::unix::fs::DirEntryExt;
|
use std::os::unix::fs::DirEntryExt;
|
||||||
self.0.ino()
|
self.0.ino()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
#[tostring]
|
#[tostring]
|
||||||
pub extern "Lua" fn tostring(&self) -> String {
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
self.path()
|
self.path()
|
||||||
@ -205,24 +331,33 @@ impl lb_dir_entry_sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Structure representing the type of a file with accessors for each file type.
|
/// Structure representing the type of a file with accessors for each file type.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_file_type(#[opaque] std::fs::FileType);
|
pub struct lb_file_type(#[opaque] std::fs::FileType);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_file_type {
|
impl lb_file_type {
|
||||||
|
fn new(ty: std::fs::FileType) -> Self {
|
||||||
|
Self(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file type is a directory.
|
||||||
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||||
self.0.is_dir()
|
self.0.is_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file type is a regular file.
|
||||||
pub extern "Lua-C" fn is_file(&self) -> bool {
|
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||||
self.0.is_file()
|
self.0.is_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file type is a symbolic link.
|
||||||
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
self.0.is_file()
|
self.0.is_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the string `"file"` if this is a regular file, `"dir"` if this is a directory,
|
||||||
|
/// `"symlink"` if this is a symbolic link, or `"other"` if it is some other type of file.
|
||||||
#[tostring]
|
#[tostring]
|
||||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||||
if self.0.is_file() {
|
if self.0.is_file() {
|
||||||
@ -239,36 +374,51 @@ impl lb_file_type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata information about a file.
|
/// Metadata information about a file.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_file_meta(#[opaque] std::fs::Metadata);
|
pub struct lb_file_meta(#[opaque] std::fs::Metadata);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_file_meta {
|
impl lb_file_meta {
|
||||||
|
fn new(meta: std::fs::Metadata) -> Self {
|
||||||
|
Self(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file is a directory.
|
||||||
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||||
self.0.is_dir()
|
self.0.is_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file is a regular file.
|
||||||
pub extern "Lua-C" fn is_file(&self) -> bool {
|
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||||
self.0.is_file()
|
self.0.is_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file is a symbolic link.
|
||||||
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
self.0.is_file()
|
self.0.is_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this file.
|
||||||
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||||
self.0.file_type().into()
|
lb_file_type::new(self.0.file_type())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the size of this file in bytes.
|
||||||
pub extern "Lua-C" fn size(&self) -> u64 {
|
pub extern "Lua-C" fn size(&self) -> u64 {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the permissions of this file.
|
||||||
pub extern "Lua-C" fn perms(&self) -> lb_file_perms {
|
pub extern "Lua-C" fn perms(&self) -> lb_file_perms {
|
||||||
self.0.permissions().into()
|
lb_file_perms::new(self.0.permissions())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the creation time of this file as seconds since the Unix epoch.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the creation time cannot be retrieved.
|
||||||
pub extern "Lua-C" fn created(&self) -> Result<f64> {
|
pub extern "Lua-C" fn created(&self) -> Result<f64> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
@ -278,6 +428,11 @@ impl lb_file_meta {
|
|||||||
.unwrap_or(0.))
|
.unwrap_or(0.))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the modification time of this file as seconds since the Unix epoch.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the modification time cannot be retrieved.
|
||||||
pub extern "Lua-C" fn modified(&self) -> Result<f64> {
|
pub extern "Lua-C" fn modified(&self) -> Result<f64> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
@ -287,6 +442,11 @@ impl lb_file_meta {
|
|||||||
.unwrap_or(0.))
|
.unwrap_or(0.))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the last access time of this file as seconds since the Unix epoch.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the access time cannot be retrieved.
|
||||||
pub extern "Lua-C" fn accessed(&self) -> Result<f64> {
|
pub extern "Lua-C" fn accessed(&self) -> Result<f64> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
@ -296,6 +456,7 @@ impl lb_file_meta {
|
|||||||
.unwrap_or(0.))
|
.unwrap_or(0.))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a string representation of this file's metadata.
|
||||||
#[tostring]
|
#[tostring]
|
||||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||||
let ty = self.0.file_type();
|
let ty = self.0.file_type();
|
||||||
@ -312,71 +473,107 @@ impl lb_file_meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of the various permissions on a file.
|
/// Representation of the various permissions on a file.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_file_perms(#[opaque] std::fs::Permissions);
|
pub struct lb_file_perms(#[opaque] RefCell<std::fs::Permissions>);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_file_perms {
|
impl lb_file_perms {
|
||||||
pub extern "Lua-C" fn readonly(&self) -> bool {
|
fn new(perms: std::fs::Permissions) -> Self {
|
||||||
self.0.readonly()
|
Self(RefCell::new(perms))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "Lua-C" fn set_readonly(&mut self, readonly: bool) {
|
/// Returns `true` if the readonly flag is set.
|
||||||
self.0.set_readonly(readonly);
|
pub extern "Lua-C" fn readonly(&self) -> bool {
|
||||||
|
self.0.borrow().readonly()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the readonly flag.
|
||||||
|
pub extern "Lua-C" fn set_readonly(&self, readonly: bool) {
|
||||||
|
self.0.borrow_mut().set_readonly(readonly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator for recursively descending into a directory.
|
/// Iterator for recursively descending into a directory.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_walk_dir(#[opaque] walkdir::IntoIter);
|
pub struct lb_walk_dir(#[opaque] RefCell<walkdir::IntoIter>);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_walk_dir {
|
impl lb_walk_dir {
|
||||||
|
fn new(iter: walkdir::IntoIter) -> Self {
|
||||||
|
Self(RefCell::new(iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry in the walk, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory cannot be read.
|
||||||
#[call]
|
#[call]
|
||||||
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_walk_dir_entry>> {
|
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
|
||||||
Ok(self.0.next().transpose()?.map(Into::into))
|
Ok(self
|
||||||
|
.0
|
||||||
|
.try_borrow_mut()?
|
||||||
|
.next()
|
||||||
|
.transpose()?
|
||||||
|
.map(lb_walk_dir_entry::new))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`].
|
/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`].
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_walk_dir_entry(#[opaque] walkdir::DirEntry);
|
pub struct lb_walk_dir_entry(#[opaque] walkdir::DirEntry);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_walk_dir_entry {
|
impl lb_walk_dir_entry {
|
||||||
|
fn new(entry: walkdir::DirEntry) -> Self {
|
||||||
|
Self(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
pub extern "Lua-C" fn path(&self) -> String {
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
self.0.path().to_string_lossy().into()
|
self.0.path().to_string_lossy().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the file name of this entry.
|
||||||
pub extern "Lua-C" fn name(&self) -> String {
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
self.0.file_name().to_string_lossy().into()
|
self.0.file_name().to_string_lossy().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this entry.
|
||||||
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||||
self.0.file_type().into()
|
lb_file_type::new(self.0.file_type())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata for this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the metadata cannot be retrieved.
|
||||||
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
Ok(self.0.metadata()?.into())
|
Ok(lb_file_meta::new(self.0.metadata()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this entry was created from a symbolic link.
|
||||||
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
self.0.path_is_symlink()
|
self.0.path_is_symlink()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the depth of this entry in the walk.
|
||||||
pub extern "Lua-C" fn depth(&self) -> u32 {
|
pub extern "Lua-C" fn depth(&self) -> u32 {
|
||||||
self.0.depth() as u32
|
self.0.depth() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inode number for this entry.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub extern "Lua-C" fn ino(&self) -> u64 {
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
use walkdir::DirEntryExt;
|
use walkdir::DirEntryExt;
|
||||||
self.0.ino()
|
self.0.ino()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
#[tostring]
|
#[tostring]
|
||||||
pub extern "Lua" fn tostring(&self) -> String {
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
self.path()
|
self.path()
|
||||||
@ -388,7 +585,7 @@ impl lb_walk_dir_entry {
|
|||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_glob_dir {
|
pub struct lb_glob_dir {
|
||||||
#[opaque]
|
#[opaque]
|
||||||
iter: walkdir::IntoIter,
|
iter: RefCell<walkdir::IntoIter>,
|
||||||
#[opaque]
|
#[opaque]
|
||||||
matcher: globset::GlobSet,
|
matcher: globset::GlobSet,
|
||||||
#[opaque]
|
#[opaque]
|
||||||
@ -397,13 +594,26 @@ pub struct lb_glob_dir {
|
|||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_glob_dir {
|
impl lb_glob_dir {
|
||||||
|
fn new(iter: walkdir::IntoIter, matcher: globset::GlobSet, prefix: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
iter: RefCell::new(iter),
|
||||||
|
matcher,
|
||||||
|
prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry matching the glob pattern, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory cannot be read.
|
||||||
#[call]
|
#[call]
|
||||||
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_walk_dir_entry>> {
|
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
|
||||||
while let Some(res) = self.iter.next() {
|
while let Some(res) = self.iter.try_borrow_mut()?.next() {
|
||||||
let entry = res?;
|
let entry = res?;
|
||||||
let path = entry.path().strip_prefix(&self.prefix).unwrap();
|
let path = entry.path().strip_prefix(&self.prefix).unwrap();
|
||||||
if self.matcher.is_match(path) {
|
if self.matcher.is_match(path) {
|
||||||
return Ok(Some(entry.into()));
|
return Ok(Some(lb_walk_dir_entry::new(entry)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,16 +622,22 @@ impl lb_glob_dir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Directory in the filesystem that is automatically deleted when it is garbage-collected.
|
/// Directory in the filesystem that is automatically deleted when it is garbage-collected.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_temp_dir(#[opaque] tempfile::TempDir);
|
pub struct lb_temp_dir(#[opaque] tempfile::TempDir);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_temp_dir {
|
impl lb_temp_dir {
|
||||||
|
fn new(dir: tempfile::TempDir) -> Self {
|
||||||
|
Self(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this temporary directory.
|
||||||
pub extern "Lua-C" fn path(&self) -> String {
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
self.0.path().to_string_lossy().into()
|
self.0.path().to_string_lossy().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this temporary directory.
|
||||||
#[tostring]
|
#[tostring]
|
||||||
pub extern "Lua" fn tostring(&self) -> String {
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
self.path()
|
self.path()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,8 +12,8 @@ use luaffi::{
|
|||||||
marker::{function, many},
|
marker::{function, many},
|
||||||
metatype,
|
metatype,
|
||||||
};
|
};
|
||||||
use luajit::{LUA_MULTRET, Type};
|
use luajit::LUA_MULTRET;
|
||||||
use std::{ffi::c_int, time::Duration};
|
use std::{cell::RefCell, ffi::c_int, time::Duration};
|
||||||
use tokio::{task::JoinHandle, time::sleep};
|
use tokio::{task::JoinHandle, time::sleep};
|
||||||
|
|
||||||
/// Items exported by the `lb:task` library.
|
/// Items exported by the `lb:task` library.
|
||||||
@ -36,43 +36,47 @@ impl lb_tasklib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async extern "Lua-C" fn sleep(ms: f64) {
|
pub async extern "Lua-C" fn sleep(ms: f64) {
|
||||||
sleep(Duration::from_secs_f64(ms / 1000.)).await;
|
sleep(Duration::from_secs_f64(ms.max(0.) / 1000.)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "Lua" fn spawn(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.
|
// 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
|
// this "state" table is used from rust-side to call the function with its args, and it's
|
||||||
// reused to store its return values that the task handle can return when awaited. the ref
|
// also reused to store its return values that the task handle can return when awaited. the
|
||||||
// is owned by the task handle and unref'ed when it's gc'ed.
|
// ref is owned by the task handle and unref'ed when it's gc'ed.
|
||||||
Self::__spawn(__ref(__tpack(f, variadic!())))
|
assert(
|
||||||
|
r#type(f) == "function",
|
||||||
|
concat!("function expected in argument 'f', got ", r#type(f)),
|
||||||
|
);
|
||||||
|
// we need two refs: one for the spawn call, and the other for the task handle. this is to
|
||||||
|
// ensure the task handle isn't gc'ed and the state table unref'ed before the spawn callback
|
||||||
|
// runs and puts the state table on the stack.
|
||||||
|
let state = __tpack(f, variadic!());
|
||||||
|
Self::__spawn(__ref(state), __ref(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua-C" fn __spawn(key: c_int) -> lb_task {
|
extern "Lua-C" fn __spawn(spawn_ref: c_int, handle_ref: c_int) -> lb_task {
|
||||||
let handle = spawn(async move |cx| {
|
let handle = spawn(async move |cx| {
|
||||||
// SAFETY: key is always unique, created by __ref above.
|
// SAFETY: handle_ref is always unique, created in Self::spawn above.
|
||||||
let arg = unsafe { cx.new_ref_unchecked(key) };
|
let state = unsafe { cx.new_ref_unchecked(spawn_ref) };
|
||||||
let mut s = cx.guard();
|
let mut s = cx.guard();
|
||||||
s.resize(0);
|
s.resize(0);
|
||||||
s.push(&arg);
|
s.push(state); // this drops the state table ref, but the table is still on the stack
|
||||||
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the table
|
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the state table
|
||||||
debug_assert!(s.slot(2).type_of() == Type::Function);
|
|
||||||
match s.call_async(narg, LUA_MULTRET).await {
|
match s.call_async(narg, LUA_MULTRET).await {
|
||||||
Ok(nret) => {
|
Ok(nret) => {
|
||||||
s.pack(1, nret); // pack the return values back into the table
|
s.pack(1, nret); // pack the return values back into the state table
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
drop(s);
|
drop(s);
|
||||||
cx.report_error(&err);
|
cx.report_error(&err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = arg.into_raw(); // the original ref is owned by the task handle and unref'ed there
|
|
||||||
});
|
});
|
||||||
|
|
||||||
lb_task {
|
// spawn_ref is owned by the task handle and unref'ed there when the handle gets gc'ed
|
||||||
handle: Some(handle),
|
lb_task::new(handle, handle_ref)
|
||||||
__ref: key,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,23 +84,30 @@ impl lb_tasklib {
|
|||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_task {
|
pub struct lb_task {
|
||||||
#[opaque]
|
#[opaque]
|
||||||
handle: Option<JoinHandle<()>>,
|
handle: RefCell<Option<JoinHandle<()>>>,
|
||||||
__ref: c_int,
|
__ref: c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_task {
|
impl lb_task {
|
||||||
|
fn new(handle: JoinHandle<()>, ref_key: c_int) -> Self {
|
||||||
|
lb_task {
|
||||||
|
handle: RefCell::new(Some(handle)),
|
||||||
|
__ref: ref_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async extern "Lua" fn r#await(&self) -> many {
|
pub async extern "Lua" fn r#await(&self) -> many {
|
||||||
self.__await();
|
self.__await();
|
||||||
let ret = __registry[self.__ref];
|
let ret = __registry[self.__ref];
|
||||||
__tunpack(ret, 1, ret.n)
|
__tunpack(ret, 1, ret.n)
|
||||||
}
|
}
|
||||||
|
|
||||||
async extern "Lua-C" fn __await(&mut self) {
|
async extern "Lua-C" fn __await(&self) {
|
||||||
if let Some(handle) = self.handle.take() {
|
if let Some(handle) = self.handle.borrow_mut().take() {
|
||||||
handle
|
handle
|
||||||
.await
|
.await // task handler should never panic
|
||||||
.unwrap_or_else(|err| panic!("task handler panicked: {err}"));
|
.unwrap_or_else(|err| std::panic::resume_unwind(err.into_panic()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,188 @@
|
|||||||
local ok, net = pcall(require, "lb:net")
|
local ok, net = pcall(require, "lb:net")
|
||||||
if not ok then return end
|
if not ok then return end
|
||||||
|
|
||||||
|
describe("ipaddr", function()
|
||||||
|
test("invalid ipaddr throws", function()
|
||||||
|
assert(not pcall(net.ipaddr, "invalid ip"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("comparison", function()
|
||||||
|
local a = net.ipaddr("10.0.0.1")
|
||||||
|
local b = net.ipaddr("10.0.0.1")
|
||||||
|
local c = net.ipaddr("10.0.0.2")
|
||||||
|
assert(a ~= nil and a ~= {} and a ~= "10.0.0.1" and a ~= 167772161)
|
||||||
|
assert(a == a and a == b and a ~= c and b ~= c and c == c and c ~= a)
|
||||||
|
assert(a <= b and a < c and a <= c and b < c and b <= c and a <= a and c <= c)
|
||||||
|
assert(not (a < b or a > b or a > c or b > c or a >= c or b >= c))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("tostring", function()
|
||||||
|
local ip = net.ipaddr("10.0.0.1")
|
||||||
|
assert(tostring(ip) == "10.0.0.1")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe("tcp", function()
|
describe("tcp", function()
|
||||||
describe("socket", function()
|
describe("socket", function()
|
||||||
test("bind", 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
|
-- binds to the correct port
|
||||||
assert(tostring(socket:local_addr():ip()) == "127.0.0.1")
|
assert(tostring(socket:local_addr():ip()) == "127.0.0.1")
|
||||||
assert(socket:local_addr():port() ~= 0)
|
assert(socket:local_addr():port() ~= 0)
|
||||||
|
|
||||||
-- should not be able to rebind socket
|
-- 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)
|
||||||
|
|
||||||
|
test("options", function()
|
||||||
|
local socket = net.tcp()
|
||||||
|
-- keepalive
|
||||||
|
socket:set_keepalive(true)
|
||||||
|
assert(socket:keepalive() == true)
|
||||||
|
socket:set_keepalive(false)
|
||||||
|
assert(socket:keepalive() == false)
|
||||||
|
-- reuseaddr
|
||||||
|
socket:set_reuseaddr(true)
|
||||||
|
assert(socket:reuseaddr() == true)
|
||||||
|
socket:set_reuseaddr(false)
|
||||||
|
assert(socket:reuseaddr() == false)
|
||||||
|
-- reuseport not always supported on all platforms
|
||||||
|
-- sendbuf
|
||||||
|
socket:set_sendbuf(4096)
|
||||||
|
assert(socket:sendbuf() >= 4096)
|
||||||
|
assert(not pcall(socket.set_sendbuf, socket, 0))
|
||||||
|
assert(not pcall(socket.set_sendbuf, socket, -1))
|
||||||
|
-- recvbuf
|
||||||
|
socket:set_recvbuf(4096)
|
||||||
|
assert(socket:recvbuf() >= 4096)
|
||||||
|
assert(not pcall(socket.set_recvbuf, socket, 0))
|
||||||
|
assert(not pcall(socket.set_recvbuf, socket, -1))
|
||||||
|
-- linger
|
||||||
|
socket:set_linger(0)
|
||||||
|
assert(socket:linger() == 0)
|
||||||
|
socket:set_linger(2)
|
||||||
|
assert(math.abs(socket:linger() - 2) < 0.1)
|
||||||
|
socket:set_linger(-1)
|
||||||
|
assert(socket:linger() == 0)
|
||||||
|
-- nodelay
|
||||||
|
socket:set_nodelay(true)
|
||||||
|
assert(socket:nodelay() == true)
|
||||||
|
socket:set_nodelay(false)
|
||||||
|
assert(socket:nodelay() == false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("can't use socket after conversion", function()
|
||||||
|
local socket = net.tcp()
|
||||||
|
socket:bind(net.socketaddr("127.0.0.1"))
|
||||||
|
socket:listen(10) -- convert to listener
|
||||||
|
assert(not pcall(socket.listen, socket, 10)) -- socket consumed
|
||||||
|
assert(not pcall(socket.local_addr, socket))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("stream", function()
|
||||||
|
test("no concurrent two reads/writes", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local reader = spawn(function()
|
||||||
|
assert(client:read(1) == nil) -- this should block first, then return nil from disconnection
|
||||||
|
end)
|
||||||
|
spawn(function()
|
||||||
|
assert(not pcall(client.read, client, 1)) -- this should fail, since the first task is still reading
|
||||||
|
end):await()
|
||||||
|
server:shutdown()
|
||||||
|
reader:await()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("allow concurrent read/write", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local reader = spawn(function()
|
||||||
|
assert(client:read(1) == nil) -- this should block first, then return nil from disconnection
|
||||||
|
end)
|
||||||
|
spawn(function()
|
||||||
|
client:write("hello") -- should be able to write while the first task is reading
|
||||||
|
end):await()
|
||||||
|
server:shutdown()
|
||||||
|
reader:await()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("stop reading from disconnected stream", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local reader = spawn(function()
|
||||||
|
while client:read(4) ~= nil do
|
||||||
|
end
|
||||||
|
assert(client:try_read(4) == nil)
|
||||||
|
assert(client:read_partial(4) == nil)
|
||||||
|
assert(client:read(4) == nil)
|
||||||
|
end)
|
||||||
|
for _ = 1, 10 do
|
||||||
|
assert(server:write("ping") == true)
|
||||||
|
end
|
||||||
|
sleep(100)
|
||||||
|
server:shutdown()
|
||||||
|
server = nil
|
||||||
|
collectgarbage()
|
||||||
|
reader:await()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("stop writing to disconnected stream", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local writer = spawn(function()
|
||||||
|
while client:write("pong") do
|
||||||
|
end
|
||||||
|
assert(client:try_write("pong") == nil)
|
||||||
|
assert(client:write_partial("pong") == nil)
|
||||||
|
assert(client:write("pong") == false)
|
||||||
|
end)
|
||||||
|
for _ = 1, 10 do
|
||||||
|
assert(server:read(4) == "pong")
|
||||||
|
end
|
||||||
|
sleep(100)
|
||||||
|
server:shutdown()
|
||||||
|
server = nil
|
||||||
|
collectgarbage()
|
||||||
|
writer:await()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("listener", function()
|
||||||
|
test("accept", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local addr = listener:local_addr()
|
||||||
|
local accepted = false
|
||||||
|
local client = net.tcp()
|
||||||
|
local accepted_stream
|
||||||
|
listener:on_accept(function(stream)
|
||||||
|
accepted = true
|
||||||
|
accepted_stream = stream
|
||||||
|
-- configure stream
|
||||||
|
stream:set_nodelay(true)
|
||||||
|
assert(stream:nodelay() == true)
|
||||||
|
end)
|
||||||
|
-- connect client
|
||||||
|
local client_stream = client:connect(addr)
|
||||||
|
local server_stream = listener()
|
||||||
|
assert(accepted)
|
||||||
|
assert(accepted_stream ~= nil)
|
||||||
|
-- check addresses
|
||||||
|
assert(server_stream:local_addr() ~= nil)
|
||||||
|
assert(server_stream:peer_addr() ~= nil)
|
||||||
|
assert(client_stream:local_addr() ~= nil)
|
||||||
|
assert(client_stream:peer_addr() ~= nil)
|
||||||
|
-- test data transfer
|
||||||
|
server_stream:write("hello")
|
||||||
|
local buf = client_stream:read(5)
|
||||||
|
assert(buf ~= nil and #buf == 5)
|
||||||
|
assert(buf == "hello")
|
||||||
|
-- shutdown
|
||||||
|
server_stream:shutdown()
|
||||||
|
client_stream:shutdown()
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
@ -36,6 +36,13 @@ describe("spawn", function()
|
|||||||
assert(res.n == 4 and res[1] == 1 and res[2] == 2 and res[3] == nil and res[4] == 3)
|
assert(res.n == 4 and res[1] == 1 and res[2] == 2 and res[3] == nil and res[4] == 3)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("handles invalid args", function()
|
||||||
|
assert(not pcall(spawn))
|
||||||
|
assert(not pcall(spawn, 123))
|
||||||
|
assert(not pcall(spawn, 1, 2, 3))
|
||||||
|
assert(not pcall(spawn, {}, 2, 3))
|
||||||
|
end)
|
||||||
|
|
||||||
test("callback args and results", function()
|
test("callback args and results", function()
|
||||||
local res = table.pack(spawn(function(...)
|
local res = table.pack(spawn(function(...)
|
||||||
assert(select("#", ...) == 5)
|
assert(select("#", ...) == 5)
|
||||||
@ -46,6 +53,25 @@ describe("spawn", function()
|
|||||||
assert(res.n == 3 and res[1] == 1 and res[2] == 3 and res[3] == nil)
|
assert(res.n == 3 and res[1] == 1 and res[2] == 3 and res[3] == nil)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("large number of args", function()
|
||||||
|
local args = {}
|
||||||
|
for i = 1, 1000 do
|
||||||
|
args[i] = i
|
||||||
|
end
|
||||||
|
local res = table.pack(spawn(function(...)
|
||||||
|
return ...
|
||||||
|
end, table.unpack(args)):await())
|
||||||
|
assert(res.n == 1000 and res[1] == 1 and res[1000] == 1000)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("callback closes over upvalues", function()
|
||||||
|
local x = 42
|
||||||
|
local function f()
|
||||||
|
return x
|
||||||
|
end
|
||||||
|
assert(spawn(f):await() == 42)
|
||||||
|
end)
|
||||||
|
|
||||||
test("order is consistent", function()
|
test("order is consistent", function()
|
||||||
-- all tasks spawned in one batch should be resumed in the spawn order
|
-- all tasks spawned in one batch should be resumed in the spawn order
|
||||||
local tasks, nums = {}, {}
|
local tasks, nums = {}, {}
|
||||||
@ -65,9 +91,31 @@ describe("spawn", function()
|
|||||||
assert(nums[i] == i)
|
assert(nums[i] == i)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("nested spawns", function()
|
||||||
|
local result = {}
|
||||||
|
local function inner()
|
||||||
|
table.insert(result, "inner")
|
||||||
|
return "done"
|
||||||
|
end
|
||||||
|
local function outer()
|
||||||
|
table.insert(result, "outer")
|
||||||
|
local v = spawn(inner):await()
|
||||||
|
table.insert(result, v)
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
local v = spawn(outer):await()
|
||||||
|
assert(v == "done")
|
||||||
|
assert(result[1] == "outer" and result[2] == "inner" and result[3] == "done")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("sleep", function()
|
describe("sleep", function()
|
||||||
|
test("invalid arg", function()
|
||||||
|
assert(not pcall(task.sleep, "invalid"))
|
||||||
|
task.sleep(-1) -- negative sleep should just become 0ms
|
||||||
|
end)
|
||||||
|
|
||||||
test("sleep is asynchronous", function()
|
test("sleep is asynchronous", function()
|
||||||
local value
|
local value
|
||||||
spawn(function()
|
spawn(function()
|
||||||
@ -77,6 +125,44 @@ describe("sleep", function()
|
|||||||
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")
|
assert(value == "value")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("sleep in nested spawns", function()
|
||||||
|
local value1, value2, value3 = nil, nil, nil
|
||||||
|
local results = {}
|
||||||
|
local function inner()
|
||||||
|
task.sleep(30)
|
||||||
|
value1 = "set by inner"
|
||||||
|
table.insert(results, "inner")
|
||||||
|
return value1
|
||||||
|
end
|
||||||
|
local function middle()
|
||||||
|
task.sleep(20)
|
||||||
|
value2 = "set by middle"
|
||||||
|
local v = spawn(inner):await()
|
||||||
|
table.insert(results, v)
|
||||||
|
table.insert(results, "middle")
|
||||||
|
return v, value2
|
||||||
|
end
|
||||||
|
local function outer()
|
||||||
|
task.sleep(10)
|
||||||
|
value3 = "set by outer"
|
||||||
|
local v1, v2 = spawn(middle):await()
|
||||||
|
table.insert(results, v1)
|
||||||
|
table.insert(results, v2)
|
||||||
|
table.insert(results, "outer")
|
||||||
|
return v1, v2, value3
|
||||||
|
end
|
||||||
|
assert(value1 == nil and value2 == nil and value3 == nil)
|
||||||
|
local r1, r2, r3 = spawn(outer):await()
|
||||||
|
assert(r1 == "set by inner" and r2 == "set by middle" and r3 == "set by outer")
|
||||||
|
assert(value1 == "set by inner" and value2 == "set by middle" and value3 == "set by outer")
|
||||||
|
assert(results[1] == "inner")
|
||||||
|
assert(results[2] == "set by inner")
|
||||||
|
assert(results[3] == "middle")
|
||||||
|
assert(results[4] == "set by inner")
|
||||||
|
assert(results[5] == "set by middle")
|
||||||
|
assert(results[6] == "outer")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("task", function()
|
describe("task", function()
|
||||||
|
@ -156,7 +156,12 @@ impl lua_pollable {
|
|||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
// TODO: signature check can currently read out-of-bounds if lua code for some reason yields
|
// TODO: signature check can currently read out-of-bounds if lua code for some reason yields
|
||||||
// a cdata of size less than 8 bytes that is not a lua_future. there is no easy way to fix
|
// a cdata of size less than 8 bytes that is not a lua_future. there is no easy way to fix
|
||||||
// afaik this because there is no way to find the size of a cdata payload using the C API.
|
// AFAIK this because there is no way to find the size of a cdata payload using the C API.
|
||||||
|
// unfortunately we have to trust that the user won't do that right now.
|
||||||
|
//
|
||||||
|
// the only "saving grace" is that the user should never be running untrusted code anyway,
|
||||||
|
// because this whole project is based on the ffi library which should never be exposed to
|
||||||
|
// untrusted code in the first place.
|
||||||
self.sig == SIGNATURE
|
self.sig == SIGNATURE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,24 +2,21 @@
|
|||||||
local LUA_REFNIL = -1 -- lib_aux.c
|
local LUA_REFNIL = -1 -- lib_aux.c
|
||||||
local FREELIST_REF = 0
|
local FREELIST_REF = 0
|
||||||
|
|
||||||
local function __ref(value, t)
|
local function __ref(value)
|
||||||
if value == nil then return LUA_REFNIL end
|
if rawequal(value, nil) then return LUA_REFNIL end
|
||||||
if t == nil then t = __registry end
|
local ref = __registry[FREELIST_REF]
|
||||||
local ref = t[FREELIST_REF]
|
|
||||||
if ref ~= nil and ref ~= 0 then
|
if ref ~= nil and ref ~= 0 then
|
||||||
t[FREELIST_REF] = t[ref]
|
__registry[FREELIST_REF] = __registry[ref]
|
||||||
else
|
else
|
||||||
ref = #t + 1
|
ref = rawlen(__registry) + 1
|
||||||
end
|
end
|
||||||
t[ref] = value
|
__registry[ref] = value
|
||||||
return ref
|
return ref
|
||||||
end
|
end
|
||||||
|
|
||||||
local function __unref(ref, t)
|
local function __unref(ref)
|
||||||
if ref < 0 then return nil end
|
if ref > 0 then
|
||||||
if t == nil then t = __registry end
|
__registry[ref] = __registry[FREELIST_REF]
|
||||||
local value = t[ref]
|
__registry[FREELIST_REF] = ref
|
||||||
t[ref] = t[FREELIST_REF]
|
end
|
||||||
t[FREELIST_REF] = ref
|
|
||||||
return value
|
|
||||||
end
|
end
|
||||||
|
@ -3,8 +3,8 @@ use crate::{
|
|||||||
string::{DROP_BUFFER_FN, IS_UTF8_FN, lua_buffer},
|
string::{DROP_BUFFER_FN, IS_UTF8_FN, lua_buffer},
|
||||||
};
|
};
|
||||||
pub use luaffi_impl::*;
|
pub use luaffi_impl::*;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
|
||||||
ffi::{c_double, c_float, c_void},
|
ffi::{c_double, c_float, c_void},
|
||||||
fmt::{self, Display, Formatter, Write},
|
fmt::{self, Display, Formatter, Write},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
@ -26,8 +26,8 @@ pub mod string;
|
|||||||
// `ffi.keep(obj)`.
|
// `ffi.keep(obj)`.
|
||||||
//
|
//
|
||||||
// https://github.com/LuaJIT/LuaJIT/issues/1167
|
// https://github.com/LuaJIT/LuaJIT/issues/1167
|
||||||
pub(crate) const KEEP_FN: &str = "luaffi_keep";
|
pub(crate) const KEEP_FN: &str = "__lf_keep";
|
||||||
#[unsafe(export_name = "luaffi_keep")]
|
#[unsafe(export_name = "__lf_keep")]
|
||||||
extern "C" fn __keep(_ptr: *const c_void) {}
|
extern "C" fn __keep(_ptr: *const c_void) {}
|
||||||
export![__keep];
|
export![__keep];
|
||||||
|
|
||||||
@ -128,8 +128,8 @@ fn cache_local(f: &mut Formatter, list: &[(&str, &str)]) -> fmt::Result {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
types: HashSet<String>,
|
types: FxHashSet<String>,
|
||||||
decls: HashSet<String>,
|
decls: FxHashSet<String>,
|
||||||
cdef: String,
|
cdef: String,
|
||||||
lua: String,
|
lua: String,
|
||||||
}
|
}
|
||||||
@ -458,6 +458,7 @@ pub struct MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
cargs: String, // C call arguments
|
cargs: String, // C call arguments
|
||||||
prelude: String, // lua function body prelude
|
prelude: String, // lua function body prelude
|
||||||
postlude: String, // lua function body postlude
|
postlude: String, // lua function body postlude
|
||||||
|
is_eqmm: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||||
@ -469,15 +470,16 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
cargs: String::new(),
|
cargs: String::new(),
|
||||||
prelude: String::new(),
|
prelude: String::new(),
|
||||||
postlude: String::new(),
|
postlude: String::new(),
|
||||||
|
is_eqmm: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
|
pub fn set_eqmm(&mut self, eq: bool) -> &mut Self {
|
||||||
assert!(
|
self.is_eqmm = eq; // are we building an __eq metamethod?
|
||||||
T::From::ty() != TypeType::Void,
|
self
|
||||||
"cannot declare void parameter"
|
}
|
||||||
);
|
|
||||||
|
|
||||||
|
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
|
||||||
let Self {
|
let Self {
|
||||||
metatype: MetatypeBuilder { reg, .. },
|
metatype: MetatypeBuilder { reg, .. },
|
||||||
lparams,
|
lparams,
|
||||||
@ -488,6 +490,14 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
T::From::ty() != TypeType::Void,
|
||||||
|
"cannot declare void parameter"
|
||||||
|
);
|
||||||
|
|
||||||
|
// should already be prevented by #[metatype] macro
|
||||||
|
debug_assert!(!name.to_string().starts_with("__"));
|
||||||
|
|
||||||
reg.include::<T::From>();
|
reg.include::<T::From>();
|
||||||
|
|
||||||
(!lparams.is_empty()).then(|| lparams.push_str(", "));
|
(!lparams.is_empty()).then(|| lparams.push_str(", "));
|
||||||
@ -507,6 +517,26 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn param_ctchecked<T: FromFfi, U: Type + Metatype<Target = U>>(
|
||||||
|
&mut self,
|
||||||
|
name: impl Display,
|
||||||
|
) -> &mut Self {
|
||||||
|
let ct = U::name();
|
||||||
|
if self.is_eqmm {
|
||||||
|
// special case for building __eq metamethods which are expected to return false if the
|
||||||
|
// arguments aren't of the expected type, because otherwise the `==` and `~=` operators
|
||||||
|
// could throw instead of returning false.
|
||||||
|
write!(
|
||||||
|
self.prelude,
|
||||||
|
r#"if not __istype(__ct.{ct}, {name}) then return false; end; "#
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
write!(self.prelude, r#"assert(__istype(__ct.{ct}, {name})); "#)
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
self.param::<T>(name)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn param_str(
|
pub fn param_str(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Display,
|
name: impl Display,
|
||||||
@ -517,7 +547,9 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
//
|
//
|
||||||
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
|
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
|
||||||
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
|
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
|
||||||
// temporary cdata to pass the string and its length in one argument.
|
// temporary cdata to pass the string and its length in one argument. this is used when the
|
||||||
|
// #[metatype] macro detects a string-like parameter, currently defined to be &[u8], &str,
|
||||||
|
// &BStr (from the bstr crate), or those types wrapped in Option<T>.
|
||||||
let Self {
|
let Self {
|
||||||
lparams,
|
lparams,
|
||||||
cparams,
|
cparams,
|
||||||
@ -526,6 +558,9 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
// should already be prevented by #[metatype] macro
|
||||||
|
debug_assert!(!name.to_string().starts_with("__"));
|
||||||
|
|
||||||
let param_ptr = <*const u8>::cdecl(&name);
|
let param_ptr = <*const u8>::cdecl(&name);
|
||||||
let param_len = usize::cdecl(format!("{name}_len"));
|
let param_len = usize::cdecl(format!("{name}_len"));
|
||||||
|
|
||||||
@ -536,7 +571,11 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
write!(lparams, "{name}").unwrap();
|
write!(lparams, "{name}").unwrap();
|
||||||
write!(cparams, "{param_ptr}, {param_len}").unwrap();
|
write!(cparams, "{param_ptr}, {param_len}").unwrap();
|
||||||
write!(cargs, "{name}, __{name}_len").unwrap();
|
write!(cargs, "{name}, __{name}_len").unwrap();
|
||||||
write!(prelude, "local __{name}_len = 0; if {name} ~= nil then ").unwrap();
|
write!(
|
||||||
|
prelude,
|
||||||
|
"local __{name}_len = 0; if not rawequal({name}, nil) then "
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
write!(prelude, r#"assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); "#).unwrap();
|
write!(prelude, r#"assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); "#).unwrap();
|
||||||
write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
|
write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
|
||||||
|
|
||||||
@ -577,22 +616,22 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
if T::Into::ty() == TypeType::Void {
|
if T::Into::ty() == TypeType::Void {
|
||||||
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
|
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
|
||||||
} else {
|
} else {
|
||||||
let check = T::postlude("__ret");
|
let check = T::postlude("ret");
|
||||||
write!(lua, "local __ret = __C.{func}({cargs}); ").unwrap();
|
write!(lua, "local ret = __C.{func}({cargs}); ").unwrap();
|
||||||
write!(lua, "{check}{postlude}return __ret; end").unwrap();
|
write!(lua, "{check}{postlude}return ret; end").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
|
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
|
||||||
}
|
}
|
||||||
FfiReturnConvention::ByOutParam => {
|
FfiReturnConvention::ByOutParam => {
|
||||||
let ct = T::Into::name();
|
let ct = T::Into::name();
|
||||||
let check = T::postlude("__out");
|
let check = T::postlude("__ret");
|
||||||
write!(lua, "local __out = __new(__ct.{ct}); __C.{func}(__out").unwrap();
|
write!(lua, "local __ret = __new(__ct.{ct}); __C.{func}(__ret").unwrap();
|
||||||
if !cargs.is_empty() {
|
if !cargs.is_empty() {
|
||||||
write!(lua, ", {cargs}").unwrap();
|
write!(lua, ", {cargs}").unwrap();
|
||||||
}
|
}
|
||||||
write!(lua, "); {check}{postlude}return __out; end").unwrap();
|
write!(lua, "); {check}{postlude}return __ret; end").unwrap();
|
||||||
write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("out")).unwrap();
|
write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("__out")).unwrap();
|
||||||
if !cparams.is_empty() {
|
if !cparams.is_empty() {
|
||||||
write!(cdef, ", {cparams}").unwrap();
|
write!(cdef, ", {cparams}").unwrap();
|
||||||
}
|
}
|
||||||
@ -770,8 +809,7 @@ macro_rules! impl_bigint_intoabi {
|
|||||||
fn postlude(ret: &str) -> impl Display {
|
fn postlude(ret: &str) -> impl Display {
|
||||||
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
|
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
|
||||||
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
|
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
|
||||||
// precision here and that 53 bits of precision for big integers are enough. (the
|
// precision here and that 53 bits of precision for big integers are enough.
|
||||||
// vain of Lua 5.3 integer subtype ;D )
|
|
||||||
display!("{ret} = tonumber({ret}); ")
|
display!("{ret} = tonumber({ret}); ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -827,22 +865,6 @@ impl_ptr!(*mut T, true);
|
|||||||
impl_ptr!(Option<&T>, false);
|
impl_ptr!(Option<&T>, false);
|
||||||
impl_ptr!(Option<&mut T>, true);
|
impl_ptr!(Option<&mut T>, true);
|
||||||
|
|
||||||
macro_rules! impl_ptr_annotation {
|
|
||||||
($ty:ty) => {
|
|
||||||
impl<T> Annotation for $ty
|
|
||||||
where
|
|
||||||
T: Annotation,
|
|
||||||
{
|
|
||||||
fn annotation() -> impl Display {
|
|
||||||
"lightuserdata"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_ptr_annotation!(*const T);
|
|
||||||
impl_ptr_annotation!(*mut T);
|
|
||||||
|
|
||||||
macro_rules! impl_ref_annotation {
|
macro_rules! impl_ref_annotation {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
impl<T> Annotation for $ty
|
impl<T> Annotation for $ty
|
||||||
@ -919,75 +941,43 @@ impl_ptr_intoabi!(Option<&'static T>);
|
|||||||
#[cfg(feature = "option_ref_abi")]
|
#[cfg(feature = "option_ref_abi")]
|
||||||
impl_ptr_intoabi!(Option<&'static mut T>);
|
impl_ptr_intoabi!(Option<&'static mut T>);
|
||||||
|
|
||||||
//
|
unsafe impl<'s, T> FromFfi for &'s T
|
||||||
// SAFETY: `FromFfi` for *mutable* references is safe because it is guaranteed that no two Rust code
|
where
|
||||||
// called via FFI can be running at the same time on the same OS thread (no Lua reentrancy).
|
T: Type,
|
||||||
//
|
{
|
||||||
// i.e. The call stack will always look something like this:
|
type From = Option<&'s T>;
|
||||||
//
|
|
||||||
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI): This is SAFE and the only use case we
|
|
||||||
// support. All references (mutable or not) to Rust user objects will be dropped before
|
|
||||||
// returning to Lua.
|
|
||||||
//
|
|
||||||
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI) -> Lua (via callback): This is UNSAFE
|
|
||||||
// because we cannot prevent the Lua callback from calling back into Rust code via FFI which
|
|
||||||
// could violate exclusive borrow semantics. This is prevented by not implementing `FromFfi` for
|
|
||||||
// function pointers (see below).
|
|
||||||
//
|
|
||||||
// The runtime does not keep any references to Rust user objects boxed in cdata (futures are the
|
|
||||||
// only exception; their ownership is transferred to the runtime via yield).
|
|
||||||
//
|
|
||||||
macro_rules! impl_ref_fromabi {
|
|
||||||
($ty:ty) => {
|
|
||||||
unsafe impl<'s, T> FromFfi for $ty
|
|
||||||
where
|
|
||||||
T: Type,
|
|
||||||
{
|
|
||||||
type From = Option<$ty>;
|
|
||||||
|
|
||||||
fn prelude(arg: &str) -> impl Display {
|
fn prelude(arg: &str) -> impl Display {
|
||||||
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
|
display!(r#"assert(not rawequal({arg}, nil), "argument '{arg}' cannot be nil"); "#)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert(from: Self::From) -> Self {
|
fn convert(from: Self::From) -> Self {
|
||||||
// SAFETY: we already checked that the reference is nonnull from the lua side
|
// SAFETY: we already checked that the reference is nonnull from the lua side
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
from.is_some(),
|
from.is_some(),
|
||||||
"<{}>::convert() called on a null reference when it was checked to be nonnull",
|
"<{}>::convert() called on a null reference when it was checked to be nonnull",
|
||||||
stringify!($ty),
|
stringify!($ty),
|
||||||
);
|
);
|
||||||
|
|
||||||
unsafe { from.unwrap_unchecked() }
|
unsafe { from.unwrap_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_ref_fromabi!(&'s T);
|
|
||||||
impl_ref_fromabi!(&'s mut T);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// SAFETY: `IntoFfi` only for 'static references because we cannot guarantee that the pointer will
|
// SAFETY: `IntoFfi` only for 'static references because we cannot guarantee that the pointer will
|
||||||
// not outlive the pointee otherwise.
|
// not outlive the pointee otherwise.
|
||||||
//
|
//
|
||||||
macro_rules! impl_ref_intoabi {
|
unsafe impl<T> IntoFfi for &'static T
|
||||||
($ty:ty) => {
|
where
|
||||||
unsafe impl<T> IntoFfi for $ty
|
T: Type,
|
||||||
where
|
{
|
||||||
T: Type,
|
type Into = Self;
|
||||||
{
|
|
||||||
type Into = Self;
|
|
||||||
|
|
||||||
fn convert(self) -> Self::Into {
|
fn convert(self) -> Self::Into {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_ref_intoabi!(&'static T);
|
|
||||||
impl_ref_intoabi!(&'static mut T);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// SAFETY: No `FromFfi` and `IntoFfi` for arrays because passing or returning them by value is not a
|
// SAFETY: No `FromFfi` and `IntoFfi` for arrays because passing or returning them by value is not a
|
||||||
// thing in C (they are just pointers).
|
// thing in C (they are just pointers).
|
||||||
@ -1062,13 +1052,6 @@ macro_rules! impl_externcfn {
|
|||||||
};
|
};
|
||||||
|
|
||||||
($ty:ident, fn($($arg:ident),*) -> $ret:ident) => {
|
($ty:ident, fn($($arg:ident),*) -> $ret:ident) => {
|
||||||
//
|
|
||||||
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
|
|
||||||
// in `&mut T`).
|
|
||||||
//
|
|
||||||
// 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,)* $ret> Type for $ty<($($arg,)*), $ret>
|
unsafe impl<$($arg,)* $ret> Type for $ty<($($arg,)*), $ret>
|
||||||
where
|
where
|
||||||
$($arg: Type,)*
|
$($arg: Type,)*
|
||||||
@ -1123,12 +1106,24 @@ 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);
|
||||||
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||||
|
|
||||||
|
impl<'s> Annotation for &'s [u8] {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'s> Annotation for &'s str {
|
impl<'s> Annotation for &'s str {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"string"
|
"string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Annotation for Vec<u8> {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Annotation for String {
|
impl Annotation for String {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"string"
|
"string"
|
||||||
|
@ -6,16 +6,16 @@ use bstr::{BStr, BString};
|
|||||||
use luaffi_impl::{cdef, metatype};
|
use luaffi_impl::{cdef, metatype};
|
||||||
use std::{fmt::Display, mem::ManuallyDrop, slice};
|
use std::{fmt::Display, mem::ManuallyDrop, slice};
|
||||||
|
|
||||||
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
|
pub(crate) const IS_UTF8_FN: &str = "__lf_is_utf8";
|
||||||
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
|
pub(crate) const DROP_BUFFER_FN: &str = "__lf_drop_buffer";
|
||||||
|
|
||||||
#[unsafe(export_name = "luaffi_is_utf8")]
|
#[unsafe(export_name = "__lf_is_utf8")]
|
||||||
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
|
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
|
||||||
debug_assert!(!ptr.is_null());
|
debug_assert!(!ptr.is_null());
|
||||||
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
|
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(export_name = "luaffi_drop_buffer")]
|
#[unsafe(export_name = "__lf_drop_buffer")]
|
||||||
unsafe extern "C" fn __drop_buffer(buf: *mut lua_buffer) {
|
unsafe extern "C" fn __drop_buffer(buf: *mut lua_buffer) {
|
||||||
debug_assert!(!buf.is_null());
|
debug_assert!(!buf.is_null());
|
||||||
debug_assert!(!unsafe { (*buf).__ptr.is_null() });
|
debug_assert!(!unsafe { (*buf).__ptr.is_null() });
|
||||||
|
@ -101,7 +101,7 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
|||||||
|
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let ty = &str.ident;
|
let ty = &str.ident;
|
||||||
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
let build = generate_cdef_build(&parse_cfields(&mut str.fields)?)?;
|
||||||
|
|
||||||
Ok(quote!(
|
Ok(quote!(
|
||||||
unsafe impl #ffi::Cdef for #ty {
|
unsafe impl #ffi::Cdef for #ty {
|
||||||
@ -123,7 +123,7 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
|||||||
.variants
|
.variants
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
let build = generate_cdef_build(&parse_cfields(&mut variant.fields)?)?;
|
||||||
Ok(quote!(b.inner_struct(|b| { #build })))
|
Ok(quote!(b.inner_struct(|b| { #build })))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
@ -148,7 +148,7 @@ struct CFieldAttrs {
|
|||||||
opaque: bool,
|
opaque: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
fn parse_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||||
match fields {
|
match fields {
|
||||||
Fields::Named(fields) => fields.named.iter_mut(),
|
Fields::Named(fields) => fields.named.iter_mut(),
|
||||||
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident,
|
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_resultlike, is_stringlike, is_unit,
|
||||||
syn_assert, syn_error, ty_name,
|
pat_ident, syn_assert, syn_error, ty_name,
|
||||||
};
|
};
|
||||||
use darling::FromMeta;
|
use darling::FromMeta;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
@ -78,7 +78,7 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// process extern "Lua-C" ffi functions
|
// process extern "Lua-C" ffi functions
|
||||||
for func in get_ffi_functions(imp)? {
|
for func in parse_ffi_functions(imp)? {
|
||||||
if let Some(mm) = func.attrs.metamethod {
|
if let Some(mm) = func.attrs.metamethod {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
mms.insert(mm),
|
mms.insert(mm),
|
||||||
@ -87,11 +87,11 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_ffi_function(&mut registry, &func)?;
|
generate_ffi_function(&mut registry, &func)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// process extern "Lua" lua functions
|
// process extern "Lua" lua functions
|
||||||
for func in get_lua_functions(imp)? {
|
for func in parse_lua_functions(imp)? {
|
||||||
if let Some(mm) = func.attrs.metamethod {
|
if let Some(mm) = func.attrs.metamethod {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
mms.insert(mm),
|
mms.insert(mm),
|
||||||
@ -100,10 +100,10 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Metamethod::Gc) = func.attrs.metamethod {
|
if func.attrs.metamethod == Some(Metamethod::Gc) {
|
||||||
lua_drop = Some(func);
|
lua_drop = Some(func);
|
||||||
} else {
|
} else {
|
||||||
add_lua_function(&mut registry, &func)?;
|
generate_lua_function(&mut registry, &func)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +155,25 @@ enum Metamethod {
|
|||||||
Ipairs,
|
Ipairs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Metamethod {
|
||||||
|
fn narg(&self) -> Option<usize> {
|
||||||
|
Some(match *self {
|
||||||
|
Self::Eq
|
||||||
|
| Self::Lt
|
||||||
|
| Self::Le
|
||||||
|
| Self::Concat
|
||||||
|
| Self::Add
|
||||||
|
| Self::Sub
|
||||||
|
| Self::Mul
|
||||||
|
| Self::Div
|
||||||
|
| Self::Mod
|
||||||
|
| Self::Pow => 2,
|
||||||
|
Self::Gc | Self::Len | Self::Unm | Self::ToString | Self::Pairs | Self::Ipairs => 1,
|
||||||
|
Self::Call | Self::New => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<&Ident> for Metamethod {
|
impl TryFrom<&Ident> for Metamethod {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@ -230,8 +249,8 @@ struct FfiFunctionAttrs {
|
|||||||
metamethod: Option<Metamethod>,
|
metamethod: Option<Metamethod>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
fn parse_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||||
let mut funcs = vec![];
|
let mut parsed = vec![];
|
||||||
|
|
||||||
for item in imp.items.iter_mut() {
|
for item in imp.items.iter_mut() {
|
||||||
if let ImplItem::Fn(func) = item
|
if let ImplItem::Fn(func) = item
|
||||||
@ -285,21 +304,30 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let attrs = parse_ffi_function_attrs(&mut func.attrs)?;
|
let attrs = parse_ffi_function_attrs(&mut func.attrs)?;
|
||||||
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
|
||||||
func.sig.asyncness.is_some().then(|| document_async(func));
|
|
||||||
document_ffi_function(func);
|
|
||||||
|
|
||||||
funcs.push(FfiFunction {
|
if let Some(mm) = attrs.metamethod
|
||||||
|
&& let Some(narg) = mm.narg()
|
||||||
|
{
|
||||||
|
syn_assert!(
|
||||||
|
params.len() == narg,
|
||||||
|
func.sig.inputs,
|
||||||
|
"expected {narg} parameters for `{mm}` metamethod"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push(FfiFunction {
|
||||||
name: func.sig.ident.clone(),
|
name: func.sig.ident.clone(),
|
||||||
params,
|
params,
|
||||||
ret,
|
ret,
|
||||||
attrs,
|
attrs,
|
||||||
is_async: func.sig.asyncness.is_some(),
|
is_async: func.sig.asyncness.is_some(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document_ffi_function(func, parsed.last().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(funcs)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAttrs> {
|
fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAttrs> {
|
||||||
@ -307,14 +335,10 @@ fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAtt
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while let Some(attr) = attrs.get(i) {
|
while let Some(attr) = attrs.get(i) {
|
||||||
if let Some(name) = attr.path().get_ident()
|
if let Some(name) = attr.path().get_ident()
|
||||||
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
&& let Ok(mm) = Metamethod::try_from(&name.unraw())
|
||||||
{
|
{
|
||||||
match method {
|
syn_assert!(mm != Metamethod::Gc, attr, "implement `Drop` instead");
|
||||||
Metamethod::Gc => syn_error!(attr, "implement `Drop` instead"),
|
parsed.metamethod = Some(mm);
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.metamethod = Some(method);
|
|
||||||
attrs.remove(i);
|
attrs.remove(i);
|
||||||
} else {
|
} else {
|
||||||
i += 1;
|
i += 1;
|
||||||
@ -373,7 +397,7 @@ fn get_ffi_ret_type(ty: &Type) -> FfiReturnType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
fn generate_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let ty = ®istry.ty;
|
let ty = ®istry.ty;
|
||||||
let func_name = &func.name;
|
let func_name = &func.name;
|
||||||
@ -392,12 +416,22 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
let mut asserts = vec![]; // compile-time asserts
|
let mut asserts = vec![]; // compile-time asserts
|
||||||
let mut build = vec![]; // ffi builder body
|
let mut build = vec![]; // ffi builder body
|
||||||
|
|
||||||
// for __new metamethods, ignore the first argument (ctype of self, for which there is no
|
match func.attrs.metamethod {
|
||||||
// equivalent in C)
|
Some(Metamethod::New) => {
|
||||||
if func.attrs.metamethod == Some(Metamethod::New) {
|
// for __new metamethods, ignore the first argument (ctype of self, for which there is
|
||||||
build.push(quote!(
|
// no equivalent in C)
|
||||||
b.param_ignored();
|
build.push(quote!(
|
||||||
));
|
b.param_ignored();
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some(Metamethod::Eq) => {
|
||||||
|
// for __eq metamethods, set special eq mode to return false on argument type mismatch
|
||||||
|
// instead of throwing
|
||||||
|
build.push(quote!(
|
||||||
|
b.set_eqmm(true);
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, (name, func_param)) in func_params.iter().enumerate() {
|
for (i, (name, func_param)) in func_params.iter().enumerate() {
|
||||||
@ -413,9 +447,18 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
func_args.push(quote_spanned!(span =>
|
func_args.push(quote_spanned!(span =>
|
||||||
<#func_param as #ffi::FromFfi>::convert(#shim_param)
|
<#func_param as #ffi::FromFfi>::convert(#shim_param)
|
||||||
));
|
));
|
||||||
build.push(quote_spanned!(span =>
|
build.push(if func.attrs.metamethod == Some(Metamethod::Eq) {
|
||||||
b.param::<#func_param>(#name);
|
quote_spanned!(span =>
|
||||||
));
|
// preliminary `return false` for type mismatch in __eq mode. we just check
|
||||||
|
// against the ctype of Self because that's what __eq should be comparing
|
||||||
|
// anyway.
|
||||||
|
b.param_ctchecked::<#func_param, Self>(#name);
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
quote_spanned!(span =>
|
||||||
|
b.param::<#func_param>(#name);
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
ty @ (FfiParameterType::StringLike(str) | FfiParameterType::OptionStringLike(str)) => {
|
ty @ (FfiParameterType::StringLike(str) | FfiParameterType::OptionStringLike(str)) => {
|
||||||
let shim_param_len = format_ident!("arg{i}_len");
|
let shim_param_len = format_ident!("arg{i}_len");
|
||||||
@ -497,19 +540,21 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
build.push({
|
build.push(if func.is_async {
|
||||||
|
// we can't name the future from an async function, so instead we provide a closure with a
|
||||||
|
// "dummy call" that never gets called so that the builder can infer the return type from
|
||||||
|
// the closure
|
||||||
let infer_args = iter::repeat_n(quote!(::std::unreachable!()), func_params.len());
|
let infer_args = iter::repeat_n(quote!(::std::unreachable!()), func_params.len());
|
||||||
let infer = if func.is_async {
|
quote!(b.call_inferred(#c_name, || {
|
||||||
quote!(|| #ffi::future::lua_future::new(Self::#func_name(#(#infer_args),*)))
|
#ffi::future::lua_future::new(Self::#func_name(#(#infer_args),*))
|
||||||
} else {
|
});)
|
||||||
quote!(|| Self::#func_name(#(#infer_args),*))
|
} else {
|
||||||
};
|
quote!(b.call::<#func_ret>(#c_name);)
|
||||||
quote!(b.call_inferred(#c_name, #infer);)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.build.push(quote!(#(::std::assert!(#asserts);)*));
|
registry.build.push(quote!(#(::std::assert!(#asserts);)*));
|
||||||
registry.build.push(match func.attrs.metamethod {
|
registry.build.push(match func.attrs.metamethod {
|
||||||
Some(ref mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
Some(mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
||||||
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -547,8 +592,8 @@ struct LuaFunctionAttrs {
|
|||||||
metamethod: Option<Metamethod>,
|
metamethod: Option<Metamethod>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
fn parse_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||||
let mut funcs = vec![];
|
let mut parsed = vec![];
|
||||||
|
|
||||||
for item in imp.items.iter_mut() {
|
for item in imp.items.iter_mut() {
|
||||||
if let ImplItem::Fn(func) = item
|
if let ImplItem::Fn(func) = item
|
||||||
@ -579,26 +624,53 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(ref variadic) = func.sig.variadic {
|
if let Some(ref variadic) = func.sig.variadic {
|
||||||
params.push(parse_quote_spanned!(variadic.span() => variadic!())); // luaify builtin macro
|
// need to transform variadic to the `varidic!()` luaify builtin macro because we
|
||||||
|
// luaify a closure below, and closures don't have variadics.
|
||||||
|
params.push(parse_quote_spanned!(variadic.span() => variadic!()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let attrs = parse_lua_function_attrs(&mut func.attrs)?;
|
let attrs = parse_lua_function_attrs(&mut func.attrs)?;
|
||||||
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
|
||||||
func.sig.asyncness.is_some().then(|| document_async(func));
|
|
||||||
document_lua_function(func);
|
|
||||||
|
|
||||||
funcs.push(LuaFunction {
|
if let Some(mm) = attrs.metamethod
|
||||||
|
&& let Some(narg) = mm.narg()
|
||||||
|
{
|
||||||
|
syn_assert!(
|
||||||
|
params.len() == narg,
|
||||||
|
func.sig.inputs,
|
||||||
|
"expected {narg} parameters for `{mm}` metamethod"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push(LuaFunction {
|
||||||
name: func.sig.ident.clone(),
|
name: func.sig.ident.clone(),
|
||||||
params,
|
params,
|
||||||
body: func.block.clone(),
|
body: func.block.clone(),
|
||||||
attrs,
|
attrs,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document_lua_function(func, parsed.last().unwrap());
|
||||||
stub_lua_function(func)?;
|
stub_lua_function(func)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(funcs)
|
Ok(parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
||||||
|
let mut parsed = LuaFunctionAttrs::default();
|
||||||
|
let mut i = 0;
|
||||||
|
while let Some(attr) = attrs.get(i) {
|
||||||
|
if let Some(name) = attr.path().get_ident()
|
||||||
|
&& let Ok(mm) = Metamethod::try_from(&name.unraw())
|
||||||
|
{
|
||||||
|
parsed.metamethod = Some(mm);
|
||||||
|
attrs.remove(i);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
||||||
@ -649,24 +721,7 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
fn generate_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
|
||||||
let mut parsed = LuaFunctionAttrs::default();
|
|
||||||
let mut i = 0;
|
|
||||||
while let Some(attr) = attrs.get(i) {
|
|
||||||
if let Some(name) = attr.path().get_ident()
|
|
||||||
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
|
||||||
{
|
|
||||||
parsed.metamethod = Some(method);
|
|
||||||
attrs.remove(i);
|
|
||||||
} else {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
|
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let luaify = quote!(#ffi::__internal::luaify!);
|
let luaify = quote!(#ffi::__internal::luaify!);
|
||||||
let func_name = &func.name;
|
let func_name = &func.name;
|
||||||
@ -675,7 +730,7 @@ fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
|
|||||||
let name = func_name.unraw().to_string();
|
let name = func_name.unraw().to_string();
|
||||||
|
|
||||||
registry.build.push(match func.attrs.metamethod {
|
registry.build.push(match func.attrs.metamethod {
|
||||||
Some(ref mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
Some(mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
||||||
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -701,16 +756,10 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
|||||||
let luaify = quote!(#ffi::__internal::luaify!);
|
let luaify = quote!(#ffi::__internal::luaify!);
|
||||||
let ty = ®istry.ty;
|
let ty = ®istry.ty;
|
||||||
let shim_name = format_ident!("__ffi_drop");
|
let shim_name = format_ident!("__ffi_drop");
|
||||||
let c_name = format_ident!("{}_drop", ty.unraw());
|
let c_name = format_ident!("__{}_drop", ty.unraw());
|
||||||
let c_name_str = c_name.to_string();
|
let c_name_str = c_name.to_string();
|
||||||
|
|
||||||
if let Some(lua) = lua {
|
if let Some(lua) = lua {
|
||||||
syn_assert!(
|
|
||||||
lua.params.len() == 1,
|
|
||||||
lua.name,
|
|
||||||
"finaliser must take exactly one parameter"
|
|
||||||
);
|
|
||||||
|
|
||||||
let param = pat_ident(&lua.params[0])?;
|
let param = pat_ident(&lua.params[0])?;
|
||||||
let body = &lua.body;
|
let body = &lua.body;
|
||||||
|
|
||||||
@ -750,75 +799,176 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_ffi_function(func: &mut ImplItemFn) {
|
// the transparency makes it work in dark mode too
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
// const FFI_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
|
||||||
r#"<span
|
// const LUA_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
|
||||||
class="stab"
|
const FALLIBLE_COLOR: &str = "rgba(255, 168, 168, 0.3)"; // red
|
||||||
title="This function is implemented in Rust and called via FFI."
|
const ASYNC_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
|
||||||
style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
const METAMETHOD_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
|
||||||
>FFI</span>"#
|
|
||||||
]));
|
struct StabConfig {
|
||||||
|
text: &'static str,
|
||||||
|
desc: &'static str,
|
||||||
|
color: &'static str,
|
||||||
|
strong: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_lua_function(func: &mut ImplItemFn) {
|
fn generate_stab(
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
StabConfig {
|
||||||
r#"<span
|
text,
|
||||||
class="stab"
|
desc,
|
||||||
title="This function is implemented in Lua."
|
color,
|
||||||
style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
strong,
|
||||||
>Lua</span>"#
|
}: StabConfig,
|
||||||
]));
|
) -> Attribute {
|
||||||
|
let attrs = vec![
|
||||||
|
("class", "stab".into()),
|
||||||
|
("title", desc.into()),
|
||||||
|
("aria-label", desc.into()),
|
||||||
|
("style", {
|
||||||
|
let mut style = vec![
|
||||||
|
("float", "right"),
|
||||||
|
("margin", "1px"),
|
||||||
|
("margin-left", "4px"),
|
||||||
|
("padding-left", "5px"),
|
||||||
|
("padding-right", "5px"),
|
||||||
|
("background", color),
|
||||||
|
];
|
||||||
|
|
||||||
|
if strong {
|
||||||
|
style.push(("font-weight", "600"));
|
||||||
|
}
|
||||||
|
|
||||||
|
style
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| format!("{k}: {v}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("; ")
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
let attrs = attrs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| format!(r#"{k}="{v}""#))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
let tag = format!(r#"<span {attrs}>{text}</span>"#);
|
||||||
|
parse_quote!(#[doc = #tag])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_async(func: &mut ImplItemFn) {
|
fn document_ffi_function(item: &mut ImplItemFn, func: &FfiFunction) {
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
let mut attrs = vec![];
|
||||||
r#"<span
|
|
||||||
class="stab"
|
// attrs.push(generate_stab(StabConfig {
|
||||||
title="This function is asynchronous and will yield the calling thread."
|
// text: "FFI",
|
||||||
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
// desc: "This function is implemented in Rust and called via FFI.",
|
||||||
>Async</span>"#
|
// color: FFI_COLOR,
|
||||||
]));
|
// strong: true,
|
||||||
|
// }));
|
||||||
|
|
||||||
|
generate_stab_fallible(item).map(|stab| attrs.push(stab));
|
||||||
|
generate_stab_async(item).map(|stab| attrs.push(stab));
|
||||||
|
generate_stab_metamethod(func.attrs.metamethod).map(|(stab, help)| {
|
||||||
|
attrs.push(stab);
|
||||||
|
item.attrs.push(help);
|
||||||
|
});
|
||||||
|
|
||||||
|
item.attrs.splice(0..0, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
|
fn document_lua_function(item: &mut ImplItemFn, func: &LuaFunction) {
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
let mut attrs = vec![];
|
||||||
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 {
|
// attrs.push(generate_stab(StabConfig {
|
||||||
Metamethod::Eq => "This function is a metamethod which is called by the `==` operator.",
|
// text: "Lua",
|
||||||
Metamethod::Len => "This function is a metamethod which is called by the `#` operator.",
|
// desc: "This function is implemented in Lua.",
|
||||||
Metamethod::Lt => "This function is a metamethod which is called by the `<` operator.",
|
// color: LUA_COLOR,
|
||||||
Metamethod::Le => "This function is a metamethod which is called by the `<=` operator.",
|
// strong: true,
|
||||||
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 = ""]));
|
generate_stab_fallible(item).map(|stab| attrs.push(stab));
|
||||||
func.attrs.push(parse_quote!(#[doc = "# Metamethod"]));
|
generate_stab_async(item).map(|stab| attrs.push(stab));
|
||||||
func.attrs.push(parse_quote!(#[doc = ""]));
|
generate_stab_metamethod(func.attrs.metamethod).map(|(stab, help)| {
|
||||||
func.attrs.push(parse_quote!(#[doc = #doc]));
|
attrs.push(stab);
|
||||||
|
item.attrs.push(help);
|
||||||
|
});
|
||||||
|
|
||||||
|
item.attrs.splice(0..0, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_stab_async(item: &ImplItemFn) -> Option<Attribute> {
|
||||||
|
item.sig.asyncness.as_ref().map(|_| {
|
||||||
|
generate_stab(StabConfig {
|
||||||
|
text: "Async",
|
||||||
|
desc: "This function is asynchronous and will yield the calling thread.",
|
||||||
|
color: ASYNC_COLOR,
|
||||||
|
strong: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_stab_fallible(item: &ImplItemFn) -> Option<Attribute> {
|
||||||
|
match item.sig.output {
|
||||||
|
ReturnType::Default => None,
|
||||||
|
ReturnType::Type(_, ref ty) => is_resultlike(ty).map(|_| {
|
||||||
|
generate_stab(StabConfig {
|
||||||
|
text: "Fallible",
|
||||||
|
desc: "This function is fallible and may throw an error on failure.",
|
||||||
|
color: FALLIBLE_COLOR,
|
||||||
|
strong: false,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_stab_metamethod(mm: Option<Metamethod>) -> Option<(Attribute, Attribute)> {
|
||||||
|
mm.map(|mm| {
|
||||||
|
let stab = generate_stab(StabConfig {
|
||||||
|
text: "Metamethod",
|
||||||
|
desc: "This function is a metamethod.",
|
||||||
|
color: METAMETHOD_COLOR,
|
||||||
|
strong: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let help = match mm {
|
||||||
|
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.",
|
||||||
|
};
|
||||||
|
|
||||||
|
let help = format!("\n# Metamethod\n\n{help}");
|
||||||
|
(stab, parse_quote!(#[doc = #help]))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -163,3 +163,19 @@ pub fn is_optionlike(ty: &Type) -> Option<&Type> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_resultlike(ty: &Type) -> Option<&Type> {
|
||||||
|
if let Type::Path(path) = ty
|
||||||
|
&& path.path.leading_colon.is_none()
|
||||||
|
&& path.path.segments.len() == 1
|
||||||
|
&& let Some(segment) = path.path.segments.get(0)
|
||||||
|
&& segment.ident == "Result"
|
||||||
|
&& let PathArguments::AngleBracketed(ref angle) = segment.arguments
|
||||||
|
&& angle.args.len() <= 2 // allow Result<T, E> or Result<T>
|
||||||
|
&& let Some(GenericArgument::Type(ty)) = angle.args.get(0)
|
||||||
|
{
|
||||||
|
Some(ty)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ local icons = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local function color(name, s)
|
local function color(name, s)
|
||||||
return colors[name] .. s .. colors.reset
|
return ("%s %s %s"):format(colors[name], s, colors.reset)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_test(name, f, group)
|
local function create_test(name, f, group)
|
||||||
@ -54,7 +54,7 @@ local function name_test(test)
|
|||||||
local name = test.name
|
local name = test.name
|
||||||
local group = test.group
|
local group = test.group
|
||||||
while group ~= nil do
|
while group ~= nil do
|
||||||
if group.name ~= "" then name = string.format("%s %s %s", group.name, icons.chevron, name) end
|
if group.name ~= "" then name = ("%s %s %s"):format(group.name, icons.chevron, name) end
|
||||||
group = group.parent
|
group = group.parent
|
||||||
end
|
end
|
||||||
return name
|
return name
|
||||||
@ -68,11 +68,12 @@ local function run_test(test)
|
|||||||
local ok, res = xpcall(test.f, trace, test)
|
local ok, res = xpcall(test.f, trace, test)
|
||||||
if ok then
|
if ok then
|
||||||
test.state = "pass"
|
test.state = "pass"
|
||||||
print("", string.format("%s %s", color("pass", "PASS"), name_test(test)))
|
print("", ("%s %s"):format(color("pass", "PASS"), name_test(test)))
|
||||||
else
|
else
|
||||||
test.state = "fail"
|
test.state = "fail"
|
||||||
print("", string.format("%s %s\n\n%s\n", color("fail", "FAIL"), name_test(test), res))
|
print("", ("%s %s\n\n%s\n"):format(color("fail", "FAIL"), name_test(test), res))
|
||||||
end
|
end
|
||||||
|
collectgarbage() -- gc after each test to test destructors
|
||||||
return test
|
return test
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -86,6 +87,23 @@ local function start(cx, item)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function check_unrefs()
|
||||||
|
-- ensure all refs were properly unref'ed
|
||||||
|
local registry = debug.getregistry()
|
||||||
|
local count = #registry
|
||||||
|
local ref = 0 -- FREELIST_REF
|
||||||
|
while type(registry[ref]) == "number" do
|
||||||
|
local next = registry[ref]
|
||||||
|
registry[ref], ref = nil, next
|
||||||
|
end
|
||||||
|
for i = 1, count do
|
||||||
|
local value = registry[i]
|
||||||
|
if type(value) ~= "thread" then -- ignore threads pinned by the runtime
|
||||||
|
assert(rawequal(registry[i], nil), ("ref %d not unref'ed: %s"):format(i, registry[i]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function main(item)
|
local function main(item)
|
||||||
local cx = { tasks = {} }
|
local cx = { tasks = {} }
|
||||||
local pass, fail = 0, 0
|
local pass, fail = 0, 0
|
||||||
@ -97,17 +115,23 @@ local function main(item)
|
|||||||
fail = fail + 1
|
fail = fail + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local code = 1
|
||||||
if fail == 0 then
|
if fail == 0 then
|
||||||
print("", color("pass", string.format("%s %d tests passed", icons.check, pass)))
|
print("", color("pass", ("%s %d tests passed"):format(icons.check, pass)))
|
||||||
return 0
|
code = 0
|
||||||
|
else
|
||||||
|
print(
|
||||||
|
"",
|
||||||
|
("%s, %s"):format(
|
||||||
|
color("pass", ("%s %d tests passed"):format(icons.check, pass)),
|
||||||
|
color("fail", ("%s %d tests failed"):format(icons.cross, fail))
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
print(
|
cx = nil
|
||||||
"",
|
collectgarbage()
|
||||||
color("pass", string.format("%s %d tests passed", icons.check, pass))
|
check_unrefs()
|
||||||
.. ", "
|
return code -- report error to cargo
|
||||||
.. color("fail", string.format("%s %d tests failed", icons.cross, fail))
|
|
||||||
)
|
|
||||||
return 1 -- report error to cargo
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return main(create_group("", function()
|
return main(create_group("", function()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user