This commit is contained in:
2025-06-22 15:11:37 +10:00
parent 5be3f2970c
commit 0667f79ff5
14 changed files with 508 additions and 319 deletions

View File

@@ -82,6 +82,9 @@ impl Error {
}
}
/// Lua result.
pub type Result<T> = ::std::result::Result<T, Error>;
/// Lua type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Type {
@@ -306,28 +309,37 @@ impl Default for DumpMode {
pub struct Chunk {
name: BString,
content: BString,
mode: LoadMode,
}
impl Chunk {
/// Creates a named [`Chunk`] with the given content.
pub fn named(name: impl Into<BString>, content: impl Into<BString>) -> Self {
Self {
name: name.into(),
content: content.into(),
}
}
/// Creates an unnamed [`Chunk`] with the given content.
pub fn unnamed(content: impl Into<BString>) -> Self {
/// Creates a new [`Chunk`] with the given content.
pub fn new(content: impl Into<BString>) -> Self {
Self {
name: "?".into(),
content: content.into(),
mode: LoadMode::AUTO,
}
}
/// Name of this chunk.
pub fn name(&self) -> &BStr {
self.name.as_ref()
/// Sets the name of this chunk as `name`.
pub fn name(&mut self, name: impl AsRef<[u8]>) -> &mut Self {
self.name = name.as_ref().into();
self
}
/// Sets the name of this chunk as the path `path`.
pub fn path(&mut self, path: impl AsRef<[u8]>) -> &mut Self {
let mut name = BString::from(b"@");
name.extend_from_slice(path.as_ref());
self.name = name;
self
}
/// Sets the mode flag for loading this chunk.
pub fn mode(&mut self, mode: LoadMode) -> &mut Self {
self.mode = mode;
self
}
}
@@ -345,13 +357,19 @@ impl DerefMut for Chunk {
}
}
impl<T: Into<BString>> From<T> for Chunk {
fn from(value: T) -> Self {
Self::new(value)
}
}
#[derive(Debug)]
struct GlobalState {
ptr: NonNull<lua_State>,
}
impl GlobalState {
pub fn new() -> Result<Self, Error> {
pub fn new() -> Result<Self> {
unsafe {
// SAFETY: lua_newstate may return a null pointer if allocation fails
let ptr = NonNull::new(lua_newstate(Some(Self::alloc_cb), ptr::null_mut()))
@@ -455,7 +473,7 @@ impl Drop for Ref {
/// A state instance can be manipulated using the [`Stack`] object that it mutably dereferences to.
#[derive(Debug)]
pub struct State {
thread_ref: Ref,
thread: Ref,
stack: Stack,
}
@@ -465,11 +483,11 @@ impl State {
/// All built-in libraries are opened by default.
///
/// This may return an error if allocation or library initialisation fails.
pub fn new() -> Result<Self, Error> {
pub fn new() -> Result<Self> {
let state = Rc::new(GlobalState::new()?);
let mut state = Self {
stack: unsafe { Stack::new_unchecked(state.as_ptr()) },
thread_ref: Ref {
thread: Ref {
state,
key: LUA_NOREF,
},
@@ -492,12 +510,25 @@ impl State {
Self {
// SAFETY: lua_newthread never returns null, but may panic on oom
stack: unsafe { Stack::new_unchecked(lua_newthread(self.as_ptr())) },
thread_ref: Ref {
state: Rc::clone(&self.thread_ref.state),
thread: Ref {
state: Rc::clone(&self.thread.state),
key: unsafe { luaL_ref(self.as_ptr(), LUA_REGISTRYINDEX) },
},
}
}
/// Creates a new [`Ref`] with the given key.
///
/// # Safety
///
/// The caller must ensure that the given ref key is unique and not already used by any other
/// instances of [`Ref`].
pub unsafe fn new_ref_unchecked(&self, key: c_int) -> Ref {
Ref {
state: Rc::clone(&self.thread.state),
key,
}
}
}
impl Deref for State {
@@ -616,6 +647,23 @@ impl Stack {
unsafe { lua_pop(self.as_ptr(), n) }
}
/// Pops the value at the top of the stack and inserts it at index `idx` by shifting up existing
/// values.
///
/// Index `idx` cannot be a pseudo-index.
///
/// Equivalent to [`lua_insert`].
///
/// # Panic
///
/// Panics if the stack is empty or the index `idx` is invalid.
pub fn pop_insert(&mut self, idx: c_int) {
assert!(self.size() >= 1, "cannot pop 1: {self:?}");
let idx = self.slot(idx).index();
assert!(idx > 0, "cannot insert into pseudo-index {idx}");
unsafe { lua_insert(self.as_ptr(), idx) }
}
/// Pops the value at the top of the stack and replaces the value at index `idx` with it.
///
/// If the index `idx` points to the top of the stack, this still pops the value and is
@@ -800,11 +848,11 @@ impl Stack {
/// Pushes the given chunk as a function at the top of the stack.
///
/// Equivalent to [`lua_loadx`].
pub fn load(&mut self, chunk: &Chunk, mode: LoadMode) -> Result<(), Error> {
pub fn load(&mut self, chunk: &Chunk) -> Result<()> {
type State<'s> = Option<&'s [u8]>;
let mut state: State = Some(chunk.content.as_ref());
let name = CString::new(chunk.name.to_vec()).map_err(Error::BadChunkName)?;
let mode = mode.to_mode_str();
let mode = chunk.mode.to_mode_str();
unsafe extern "C" fn reader_cb(
_L: *mut lua_State,
@@ -860,7 +908,7 @@ impl Stack {
/// # Panic
///
/// Panics if the value at index `idx` is not a function.
pub fn dump(&self, idx: c_int, mode: DumpMode) -> Result<BString, Error> {
pub fn dump(&self, idx: c_int, mode: DumpMode) -> Result<BString> {
let func = self.slot(idx);
assert!(
func.type_of() == Type::Function,
@@ -881,6 +929,24 @@ impl Stack {
}
}
/// Evaluates the given chunk on the stack synchronously with `narg` values at the top of the
/// stack as arguments.
///
/// Equivalent to calling [`load`](Self::load) on the chunk and then [`call`](Self::call) on the
/// loaded function.
///
/// # Panic
///
/// Panics if there are not enough values on the stack or thread status is invalid.
pub fn eval(&mut self, chunk: &Chunk, narg: c_int, nret: c_int) -> Result<c_int> {
assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET));
let base = self.size() - narg;
assert!(base >= 0, "expected {narg} values: {self:?}");
self.load(chunk)?;
self.pop_insert(base + 1);
self.call(narg, nret)
}
/// Calls a function on the stack synchronously with `narg` values at the top of the stack as
/// arguments.
///
@@ -888,8 +954,8 @@ impl Stack {
/// the index `top - narg` (i.e. the function is pushed first and then `narg` values as
/// arguments). All arguments and the function are popped from the stack and then any return
/// values are pushed. If `nret` is not [`LUA_MULTRET`], then the number of return values pushed
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of return values
/// pushed to the stack is returned.
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of returned
/// values pushed to the stack is returned.
///
/// The current thread status must not be suspended or dead.
///
@@ -899,7 +965,7 @@ impl Stack {
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
pub fn call(&mut self, narg: c_int, nret: c_int) -> Result<c_int, Error> {
pub fn call(&mut self, narg: c_int, nret: c_int) -> Result<c_int> {
assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET));
let top = self.size();
@@ -945,8 +1011,8 @@ impl Stack {
/// the index `top - narg` (i.e. the function is pushed first and then `narg` values as
/// arguments). All arguments and the function are popped from the stack and then any return
/// values are pushed. If `nret` is not [`LUA_MULTRET`], then the number of return values pushed
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of return values
/// pushed to the stack is returned.
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of returned
/// values pushed to the stack is returned.
///
/// If the thread yields a Rust [`Future`] value, then it will be polled to completion before
/// the thread is resumed with the output of the [`Future`] as the argument. If the thread
@@ -961,7 +1027,7 @@ impl Stack {
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
pub async fn call_async(&mut self, mut narg: c_int, nret: c_int) -> Result<c_int, Error> {
pub async fn call_async(&mut self, mut narg: c_int, nret: c_int) -> Result<c_int> {
assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET));
let top = self.size();
@@ -1015,7 +1081,7 @@ impl Stack {
/// pushed first and then `narg` values as arguments). If the current thread status is
/// suspended, then there must be `narg` values at the top of the stack. All arguments and the
/// function are popped from the stack and then any yielded values are pushed. Finally, the new
/// status of the thread is returned.
/// status of the thread indicating whether the thread had completed or suspended is returned.
///
/// The current thread status must not be dead.
///
@@ -1025,7 +1091,7 @@ impl Stack {
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
pub fn resume(&mut self, narg: c_int) -> Result<ResumeStatus, Error> {
pub fn resume(&mut self, narg: c_int) -> Result<ResumeStatus> {
assert!(0 <= narg);
let status = self.status();
let need = match status {
@@ -1237,7 +1303,7 @@ impl<'s> Slot<'s> {
}
/// Parses the value in this slot as a `T`.
pub fn parse<T: Parse<'s>>(&self) -> Result<T, Error> {
pub fn parse<T: Parse<'s>>(&self) -> Result<T> {
T::parse(self)
}
@@ -1264,6 +1330,9 @@ impl<'s> Slot<'s> {
/// Parses the value in this slot as a [`lua_Number`].
///
/// If the value is not a `number` or a `string` that can be parsed as a number, then this
/// returns [`None`].
///
/// Equivalent to [`lua_tonumberx`].
pub fn number(&self) -> Option<lua_Number> {
self.parse().ok()
@@ -1271,6 +1340,9 @@ impl<'s> Slot<'s> {
/// Parses the value in this slot as a [`lua_Integer`].
///
/// If the value is not a `number` or a `string` that can be parsed as an integer, then this
/// returns [`None`].
///
/// Equivalent to [`lua_tointegerx`].
pub fn integer(&self) -> Option<lua_Integer> {
self.parse().ok()
@@ -1278,6 +1350,11 @@ impl<'s> Slot<'s> {
/// Parses the value in this slot as a binary string.
///
/// If the value is a `number`, then it is converted in-place into a `string` representation of
/// the value first.
///
/// If the value is not a `string`, then this returns [`None`].
///
/// Equivalent to [`lua_tolstring`].
pub fn string(&self) -> Option<&'s BStr> {
self.parse().ok()
@@ -1285,12 +1362,19 @@ impl<'s> Slot<'s> {
/// Parses the value in this slot as a UTF-8 string.
///
/// If the value is a `number`, then it is converted in-place into a `string` representation
/// first.
///
/// Equivalent to [`lua_tolstring`].
pub fn string_utf8(&self) -> Option<&'s str> {
self.parse().ok()
}
/// Parses the value in this slot as a [`lua_CFunction`].
///
/// If the value is not a C function, then this returns [`None`].
///
/// Equivalent to [`lua_tocfunction`].
pub fn function_raw(&self) -> lua_CFunction {
unsafe { lua_tocfunction(self.stack.as_ptr(), self.idx) }
}
@@ -1299,6 +1383,8 @@ impl<'s> Slot<'s> {
///
/// If the value is a `cdata`, then the returned pointer is the address of the base of the cdata
/// payload. Otherwise this returns a null pointer.
///
/// Equivalent to [`lua_topointer`].
pub fn cdata<T>(&self) -> *const T {
(self.type_of() == Type::Cdata)
.then(|| self.pointer().cast())
@@ -1306,9 +1392,28 @@ impl<'s> Slot<'s> {
}
/// Parses the value in this slot as a generic pointer.
///
/// If the value is not a GC-managed object that can be represented by a pointer, then this
/// returns a null pointer.
///
/// Equivalent to [`lua_topointer`].
pub fn pointer(&self) -> *const c_void {
unsafe { lua_topointer(self.stack.as_ptr(), self.idx).cast() }
}
/// Returns the length of the value in this slot.
///
/// For strings, this is the byte-length of the contents of the string. For tables, this is the
/// length of the table defined by the Lua `#` operator. For userdata, this is the size of its
/// payload in bytes. For numbers, this is equivalent to converting the value in-place into a
/// `string` representation before calculating its length. Otherwise, this returns 0.
///
/// This function does not invoke the `__len` metamethod.
///
/// Equivalent to [`lua_objlen`].
pub fn length(&self) -> usize {
unsafe { lua_objlen(self.stack.as_ptr(), self.idx) }
}
}
/// Pushes a value onto a [`Stack`].
@@ -1509,11 +1614,11 @@ impl Push for CurrentThread {
/// [`Slot::parse`].
pub trait Parse<'s>: Sized {
/// Parses the value in the given slot.
fn parse(slot: &Slot<'s>) -> Result<Self, Error>;
fn parse(slot: &Slot<'s>) -> Result<Self>;
}
impl Parse<'_> for () {
fn parse(slot: &Slot) -> Result<Self, Error> {
fn parse(slot: &Slot) -> Result<Self> {
match slot.type_of() {
Type::Nil => Ok(()),
ty => Err(Error::InvalidType("nil", ty.name())),
@@ -1522,7 +1627,7 @@ impl Parse<'_> for () {
}
impl Parse<'_> for bool {
fn parse(slot: &Slot) -> Result<Self, Error> {
fn parse(slot: &Slot) -> Result<Self> {
Ok(unsafe { lua_toboolean(slot.stack.as_ptr(), slot.index()) != 0 })
}
}
@@ -1530,7 +1635,7 @@ impl Parse<'_> for bool {
macro_rules! impl_parse_ptr {
($type:ty) => {
impl<T> Parse<'_> for $type {
fn parse(slot: &Slot) -> Result<Self, Error> {
fn parse(slot: &Slot) -> Result<Self> {
let ptr = unsafe { lua_touserdata(slot.stack.as_ptr(), slot.idx) };
if !ptr.is_null() {
Ok(ptr as $type)
@@ -1548,7 +1653,7 @@ impl_parse_ptr!(*const T);
macro_rules! impl_parse_num {
($type:ty) => {
impl Parse<'_> for $type {
fn parse(slot: &Slot) -> Result<Self, Error> {
fn parse(slot: &Slot) -> Result<Self> {
let mut isnum = 0;
let n = unsafe { lua_tonumberx(slot.stack.as_ptr(), slot.idx, &raw mut isnum) };
if isnum != 0 {
@@ -1567,7 +1672,7 @@ impl_parse_num!(f64);
macro_rules! impl_parse_int {
($type:ty) => {
impl Parse<'_> for $type {
fn parse(slot: &Slot) -> Result<Self, Error> {
fn parse(slot: &Slot) -> Result<Self> {
let mut isnum = 0;
let n = unsafe { lua_tointegerx(slot.stack.as_ptr(), slot.idx, &raw mut isnum) };
if isnum != 0 {
@@ -1594,7 +1699,7 @@ impl_parse_int!(isize);
macro_rules! impl_parse_str {
($type:ty) => {
impl<'s> Parse<'s> for $type {
fn parse(slot: &Slot<'s>) -> Result<Self, Error> {
fn parse(slot: &Slot<'s>) -> Result<Self> {
let mut len = 0;
let ptr = unsafe { lua_tolstring(slot.stack.as_ptr(), slot.idx, &mut len) };
if !ptr.is_null() {
@@ -1610,7 +1715,7 @@ macro_rules! impl_parse_str {
macro_rules! impl_parse_str_utf8 {
($type:ty) => {
impl<'s> Parse<'s> for $type {
fn parse(slot: &Slot<'s>) -> Result<Self, Error> {
fn parse(slot: &Slot<'s>) -> Result<Self> {
Ok(std::str::from_utf8(Parse::parse(slot)?)?.into())
}
}