pub use self::arg::*;
use crate::collections::{BTreeMap, CollectionValue};
use crate::lapi::{lua_checkstack, lua_typename};
use crate::ldo::luaD_call;
use crate::lobject::luaO_arith;
use crate::value::UnsafeValue;
use crate::vm::{F2Ieq, luaV_equalobj, luaV_lessthan, luaV_objlen, luaV_tointeger};
use crate::{
CallError, Inputs, LuaFn, NON_YIELDABLE_WAKER, Ops, Outputs, ParseError, Ref, RegKey, RegValue,
StackOverflow, Str, Table, Thread, Type, UserData, YIELDABLE_WAKER, luaH_get, luaH_getshortstr,
};
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::any::Any;
use core::cell::Cell;
use core::convert::identity;
use core::num::NonZero;
use core::pin::{Pin, pin};
use core::ptr::{addr_eq, null};
use core::task::{Poll, Waker};
mod arg;
pub struct Context<'a, A, T> {
th: &'a Thread<A>,
ret: Cell<usize>,
payload: T,
yieldable: bool,
}
impl<'a, A, T> Context<'a, A, T> {
#[inline(always)]
pub(crate) fn new(td: &'a Thread<A>, payload: T) -> impl Future<Output = Self>
where
T: Unpin,
{
New {
td,
payload: Some(payload),
}
}
#[inline(always)]
pub fn is_yieldable(&self) -> bool {
self.yieldable
}
#[inline(always)]
pub fn thread(&self) -> &'a Thread<A> {
self.th
}
#[inline(always)]
pub fn associated_data(&self) -> &A {
&self.th.hdr.global().associated_data
}
pub fn set_registry<'b, K>(&self, v: <K::Value<'b> as RegValue<A>>::In<'b>)
where
K: RegKey<A>,
K::Value<'b>: RegValue<A>,
{
self.th.hdr.global().set_registry::<K>(v);
}
pub fn registry<K>(&self) -> Option<<K::Value<'a> as RegValue<A>>::Out<'a>>
where
K: RegKey<A>,
K::Value<'a>: RegValue<A>,
{
self.th.hdr.global().registry::<K>()
}
#[inline(always)]
pub fn create_str<V>(&self, v: V) -> Ref<'a, Str<A>>
where
V: AsRef<str> + AsRef<[u8]> + Into<Vec<u8>>,
{
let g = self.th.hdr.global();
let s = unsafe { Str::from_str(g, v) };
let v = unsafe { Ref::new(s.unwrap_or_else(identity)) };
if s.is_ok() {
g.gc.step();
}
v
}
#[inline(always)]
pub fn create_bytes<V>(&self, v: V) -> Ref<'a, Str<A>>
where
V: AsRef<[u8]> + Into<Vec<u8>>,
{
let g = self.th.hdr.global();
let s = unsafe { Str::from_bytes(g, v) };
let v = unsafe { Ref::new(s.unwrap_or_else(identity)) };
if s.is_ok() {
g.gc.step();
}
v
}
#[inline(always)]
pub fn create_table(&self) -> Ref<'a, Table<A>> {
let v = unsafe { Ref::new(Table::new(self.th.hdr.global)) };
self.th.hdr.global().gc.step();
v
}
#[inline(always)]
pub fn create_ud<V: Any>(&self, v: V) -> Ref<'a, UserData<A, V>> {
let v = unsafe { Ref::new(UserData::new(self.th.hdr.global, v).cast()) };
self.th.hdr.global().gc.step();
v
}
pub fn create_thread(&self) -> Ref<'a, Thread<A>> {
let v = unsafe { Ref::new(Thread::new(self.th.hdr.global())) };
self.th.hdr.global().gc.step();
v
}
pub fn create_btree_map<K, V>(&self) -> Ref<'a, BTreeMap<A, K, V>>
where
K: Ord + 'static,
V: CollectionValue<A> + 'static,
{
let v = unsafe { Ref::new(BTreeMap::new(self.th.hdr.global())) };
self.th.hdr.global().gc.step();
v
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn deserialize_value<'de, D: serde::Deserializer<'de>>(
&self,
deserializer: D,
) -> Result<crate::Value<'a, A>, D::Error> {
deserializer.deserialize_any(crate::value::serde::ValueVisitor::new(self.th.hdr.global()))
}
#[inline(always)]
pub fn load(
&self,
name: impl Into<String>,
chunk: impl AsRef<[u8]>,
) -> Result<Ref<'a, LuaFn<A>>, ParseError> {
self.th.hdr.global().load(name, chunk)
}
pub fn get_value_len(
&self,
v: impl Into<UnsafeValue<A>>,
) -> Result<i64, Box<dyn core::error::Error>> {
let v = v.into();
if unsafe { (v.tt_ & 1 << 6 != 0) && (*v.value_.gc).global != self.th.hdr.global } {
panic!("attempt to get a length of the value created from a different Lua");
}
let l = unsafe { luaV_objlen(self.th, &v)? };
if l.tt_ == 3 | 0 << 4 {
return Ok(unsafe { l.value_.i });
}
match unsafe { luaV_tointeger(&l, F2Ieq) } {
Some(v) => Ok(v),
None => Err("object length is not an integer".into()),
}
}
#[inline(never)]
pub fn is_value_eq(
&self,
lhs: impl Into<UnsafeValue<A>>,
rhs: impl Into<UnsafeValue<A>>,
mt: bool,
) -> Result<bool, Box<dyn core::error::Error>> {
let lhs = lhs.into();
if unsafe { (lhs.tt_ & 1 << 6 != 0) && (*lhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to compare a value created from a different Lua");
}
let rhs = rhs.into();
if unsafe { (rhs.tt_ & 1 << 6 != 0) && (*rhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to compare a value created from a different Lua");
}
let th = match mt {
true => Some(self.th),
false => None,
};
unsafe { luaV_equalobj(th, &lhs, &rhs) }
}
pub fn is_value_lt(
&self,
lhs: impl Into<UnsafeValue<A>>,
rhs: impl Into<UnsafeValue<A>>,
) -> Result<bool, Box<dyn core::error::Error>> {
let lhs = lhs.into();
if unsafe { (lhs.tt_ & 1 << 6 != 0) && (*lhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to compare a value created from a different Lua");
}
let rhs = rhs.into();
if unsafe { (rhs.tt_ & 1 << 6 != 0) && (*rhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to compare a value created from a different Lua");
}
Ok(unsafe { luaV_lessthan(self.th, &lhs, &rhs)? != 0 })
}
pub fn type_name(&self, v: impl Into<UnsafeValue<A>>) -> Cow<'static, str> {
let v = v.into();
if unsafe { (v.tt_ & 1 << 6 != 0) && (*v.value_.gc).global != self.th.hdr.global } {
panic!("attempt to resolve type name for a value created from a different Lua");
}
let g = self.th.hdr.global();
let mt = unsafe { g.metatable(&v) };
(!mt.is_null())
.then(move || unsafe {
luaH_getshortstr(mt, Str::from_str(g, "__name").unwrap_or_else(identity))
})
.and_then(|v| match unsafe { (*v).tt_ & 0xf } {
4 => Some(unsafe { (*v).value_.gc.cast::<Str<A>>() }),
_ => None,
})
.and_then(|v| unsafe { (*v).as_utf8() })
.map(|v| Cow::Owned(v.into()))
.unwrap_or_else(move || Cow::Borrowed(lua_typename((v.tt_ & 0xf).into())))
}
#[inline(never)]
pub fn push(&self, v: impl Into<UnsafeValue<A>>) -> Result<(), StackOverflow> {
let v = v.into();
if unsafe { (v.tt_ & 1 << 6 != 0) && (*v.value_.gc).global != self.th.hdr.global } {
panic!("attempt to push a value created from a different Lua");
}
unsafe { lua_checkstack(self.th, 1, 5)? };
unsafe { self.th.top.write(v) };
unsafe { self.th.top.add(1) };
self.ret.set(self.ret.get() + 1);
Ok(())
}
pub unsafe fn push_unchecked(&self, v: impl Into<UnsafeValue<A>>) -> Result<(), StackOverflow> {
let v = v.into();
unsafe { lua_checkstack(self.th, 1, 5)? };
unsafe { self.th.top.write(v) };
unsafe { self.th.top.add(1) };
self.ret.set(self.ret.get() + 1);
Ok(())
}
#[inline(never)]
pub fn push_str<V>(&self, v: V) -> Result<(), StackOverflow>
where
V: AsRef<str> + AsRef<[u8]> + Into<Vec<u8>>,
{
unsafe { lua_checkstack(self.th, 1, 5)? };
let g = self.th.hdr.global();
let s = unsafe { Str::from_str(g, v) };
let v = unsafe { UnsafeValue::from_obj(s.unwrap_or_else(identity).cast()) };
unsafe { self.th.top.write(v) };
unsafe { self.th.top.add(1) };
self.ret.update(|v| v + 1);
if s.is_ok() {
g.gc.step();
}
Ok(())
}
#[inline(never)]
pub fn push_bytes<V>(&self, v: V) -> Result<(), StackOverflow>
where
V: AsRef<[u8]> + Into<Vec<u8>>,
{
unsafe { lua_checkstack(self.th, 1, 5)? };
let g = self.th.hdr.global();
let s = unsafe { Str::from_bytes(g, v) };
let v = unsafe { UnsafeValue::from_obj(s.unwrap_or_else(identity).cast()) };
unsafe { self.th.top.write(v) };
unsafe { self.th.top.add(1) };
self.ret.update(|v| v + 1);
if s.is_ok() {
g.gc.step();
}
Ok(())
}
pub fn push_next(
&self,
t: &Table<A>,
k: impl Into<UnsafeValue<A>>,
) -> Result<bool, Box<dyn core::error::Error>> {
unsafe { lua_checkstack(self.th, 2, 5)? };
if t.hdr.global != self.th.hdr.global {
panic!("attempt to push a value from a table created from different Lua");
}
let k = k.into();
if unsafe { (k.tt_ & 1 << 6 != 0) && (*k.value_.gc).global != self.th.hdr.global } {
panic!("attempt to push a value created from different Lua");
}
let [k, v] = match unsafe { t.next_raw(&k)? } {
Some(v) => v,
None => return Ok(false),
};
unsafe { self.th.top.write(k) };
unsafe { self.th.top.add(1) };
unsafe { self.th.top.write(v) };
unsafe { self.th.top.add(1) };
self.ret.set(self.ret.get() + 2);
Ok(true)
}
pub fn push_from_table(
&self,
t: &Table<A>,
k: impl Into<UnsafeValue<A>>,
) -> Result<Type, StackOverflow> {
unsafe { lua_checkstack(self.th, 1, 5)? };
if t.hdr.global != self.th.hdr.global {
panic!("attempt to push a value from a table created from different Lua");
}
let v = t.get_raw(k);
unsafe { self.th.top.write(*v) };
unsafe { self.th.top.add(1) };
self.ret.set(self.ret.get() + 1);
Ok(unsafe { Type::from_tt((*v).tt_) })
}
pub fn push_from_str_key<K>(&self, t: &Table<A>, k: K) -> Result<Type, StackOverflow>
where
K: AsRef<[u8]> + Into<Vec<u8>>,
{
unsafe { lua_checkstack(self.th, 1, 5)? };
if t.hdr.global != self.th.hdr.global {
panic!("attempt to push a value from a table created from different Lua");
}
let g = self.th.hdr.global();
let s = unsafe { Str::from_bytes(g, k) };
let k = unsafe { UnsafeValue::from_obj(s.unwrap_or_else(identity).cast()) };
let v = unsafe { luaH_get(t, &k) };
unsafe { self.th.top.write(v.read()) };
unsafe { self.th.top.add(1) };
self.ret.set(self.ret.get() + 1);
if s.is_ok() {
g.gc.step();
}
Ok(unsafe { Type::from_tt((*v).tt_) })
}
pub fn push_add(
&self,
lhs: impl Into<UnsafeValue<A>>,
rhs: impl Into<UnsafeValue<A>>,
) -> Result<Type, Box<dyn core::error::Error>> {
let lhs = lhs.into();
let rhs = rhs.into();
if unsafe { (lhs.tt_ & 1 << 6 != 0) && (*lhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform addition on a value created from different Lua");
}
if unsafe { (rhs.tt_ & 1 << 6 != 0) && (*rhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform addition on a value created from different Lua");
}
let r = unsafe { luaO_arith(self.th, Ops::Add, &lhs, &rhs)? };
unsafe { lua_checkstack(self.th, 1, 5)? };
unsafe { self.th.top.write(r) };
unsafe { self.th.top.add(1) };
self.ret.update(|v| v + 1);
Ok(Type::from_tt(r.tt_))
}
pub fn push_sub(
&self,
lhs: impl Into<UnsafeValue<A>>,
rhs: impl Into<UnsafeValue<A>>,
) -> Result<Type, Box<dyn core::error::Error>> {
let lhs = lhs.into();
let rhs = rhs.into();
if unsafe { (lhs.tt_ & 1 << 6 != 0) && (*lhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform subtraction on a value created from different Lua");
}
if unsafe { (rhs.tt_ & 1 << 6 != 0) && (*rhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform subtraction on a value created from different Lua");
}
let r = unsafe { luaO_arith(self.th, Ops::Sub, &lhs, &rhs)? };
unsafe { lua_checkstack(self.th, 1, 5)? };
unsafe { self.th.top.write(r) };
unsafe { self.th.top.add(1) };
self.ret.update(|v| v + 1);
Ok(Type::from_tt(r.tt_))
}
pub fn push_rem(
&self,
lhs: impl Into<UnsafeValue<A>>,
rhs: impl Into<UnsafeValue<A>>,
) -> Result<Type, Box<dyn core::error::Error>> {
let lhs = lhs.into();
let rhs = rhs.into();
if unsafe { (lhs.tt_ & 1 << 6 != 0) && (*lhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform modulo on a value created from different Lua");
}
if unsafe { (rhs.tt_ & 1 << 6 != 0) && (*rhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform modulo on a value created from different Lua");
}
let r = unsafe { luaO_arith(self.th, Ops::Mod, &lhs, &rhs)? };
unsafe { lua_checkstack(self.th, 1, 5)? };
unsafe { self.th.top.write(r) };
unsafe { self.th.top.add(1) };
self.ret.update(|v| v + 1);
Ok(Type::from_tt(r.tt_))
}
pub fn push_pow(
&self,
lhs: impl Into<UnsafeValue<A>>,
rhs: impl Into<UnsafeValue<A>>,
) -> Result<Type, Box<dyn core::error::Error>> {
let lhs = lhs.into();
let rhs = rhs.into();
if unsafe { (lhs.tt_ & 1 << 6 != 0) && (*lhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform exponentiation on a value created from different Lua");
}
if unsafe { (rhs.tt_ & 1 << 6 != 0) && (*rhs.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform exponentiation on a value created from different Lua");
}
let r = unsafe { luaO_arith(self.th, Ops::Pow, &lhs, &rhs)? };
unsafe { lua_checkstack(self.th, 1, 5)? };
unsafe { self.th.top.write(r) };
unsafe { self.th.top.add(1) };
self.ret.update(|v| v + 1);
Ok(Type::from_tt(r.tt_))
}
pub fn push_neg(
&self,
v: impl Into<UnsafeValue<A>>,
) -> Result<Type, Box<dyn core::error::Error>> {
let v = v.into();
if unsafe { (v.tt_ & 1 << 6 != 0) && (*v.value_.gc).global != self.th.hdr.global } {
panic!("attempt to perform negation on a value created from different Lua");
}
let r = unsafe { luaO_arith(self.th, Ops::Neg, &v, &v)? };
unsafe { lua_checkstack(self.th, 1, 5)? };
unsafe { self.th.top.write(r) };
unsafe { self.th.top.add(1) };
self.ret.update(|v| v + 1);
Ok(Type::from_tt(r.tt_))
}
pub fn reserve(&self, additional: usize) -> Result<(), StackOverflow> {
unsafe { lua_checkstack(self.th, additional, 0) }
}
pub fn call<R: Outputs<'a, A>>(
&self,
f: impl Into<UnsafeValue<A>>,
args: impl Inputs<A>,
) -> Result<R, Box<dyn core::error::Error>> {
self.th.call(f, args)
}
pub fn forward(
self,
f: impl TryInto<NonZero<isize>>,
) -> (Context<'a, A, Ret>, Option<Box<CallError>>) {
let f = match f.try_into() {
Ok(v) => v,
Err(_) => panic!("zero is not a valid stack index"),
};
let th = self.th;
let stack = th.stack.get();
let ci = th.ci.get();
let top = unsafe {
self.th
.top
.get()
.offset_from_unsigned(stack.add((*ci).func))
};
let f = match usize::try_from(f.get()) {
Ok(v) => {
if v >= top {
panic!("{f} is not a valid stack index");
}
v
}
Err(_) => match top.saturating_sub(f.get().unsigned_abs()) {
0 => panic!("{f} is not a valid stack index"),
v => v,
},
};
let rem = f - 1;
let f = unsafe { stack.add((*ci).func + f) };
let cx = Context {
th: self.th,
ret: Cell::new(0),
payload: Ret(rem),
yieldable: self.yieldable,
};
{
let f = unsafe { pin!(luaD_call(self.th, f, -1)) };
let w = unsafe { Waker::new(null(), &NON_YIELDABLE_WAKER) };
match f.poll(&mut core::task::Context::from_waker(&w)) {
Poll::Ready(Ok(_)) => (),
Poll::Ready(Err(e)) => return (cx, Some(e)),
Poll::Pending => unreachable!(),
}
}
let stack = th.stack.get();
let ret = unsafe { stack.add((*ci).func + rem + 1) };
let ret = unsafe { cx.th.top.get().offset_from_unsigned(ret) };
cx.ret.set(ret);
(cx, None)
}
pub fn into_results(self, i: impl TryInto<NonZero<isize>>) -> Context<'a, A, Ret> {
let i = match i.try_into() {
Ok(v) => v,
Err(_) => panic!("zero is not a valid stack index"),
};
let th = self.th;
let stack = th.stack.get();
let ci = th.ci.get();
let top = unsafe {
self.th
.top
.get()
.offset_from_unsigned(stack.add((*ci).func))
};
let off = match usize::try_from(i.get()) {
Ok(v) => v,
Err(_) => match top.saturating_sub(i.get().unsigned_abs()) {
0 => panic!("{i} is not a valid stack index"),
v => v,
},
};
let ret = match top.checked_sub(off) {
Some(v) => v,
None => panic!("{i} is not a valid stack index"),
};
Context {
th: self.th,
ret: Cell::new(ret),
payload: Ret(off - 1),
yieldable: self.yieldable,
}
}
}
impl<'a, A> Context<'a, A, Args> {
#[inline(always)]
pub fn args(&self) -> usize {
self.payload.0
}
#[inline(always)]
pub fn arg(&self, n: impl TryInto<NonZero<usize>>) -> Arg<'_, 'a, A> {
let n = match n.try_into() {
Ok(v) => v,
Err(_) => panic!("zero is not a valid argument index"),
};
Arg::new(self, n)
}
}
impl<'a, A> Context<'a, A, Ret> {
pub fn insert(
&self,
i: impl TryInto<NonZero<usize>>,
v: impl Into<UnsafeValue<A>>,
) -> Result<(), StackOverflow> {
let i = match i.try_into() {
Ok(v) => v,
Err(_) => panic!("zero is not a valid stack index"),
};
if i.get() <= self.payload.0 {
panic!("{i} is lower than the first result");
}
unsafe { lua_checkstack(self.th, 1, 5)? };
let th = self.th;
let stack = th.stack.get();
let ci = th.ci.get();
let top = unsafe {
self.th
.top
.get()
.offset_from_unsigned(stack.add((*ci).func))
};
if i.get() > top {
panic!("{i} is not a valid stack index");
}
let v = v.into();
if unsafe { (v.tt_ & 1 << 6 != 0) && (*v.value_.gc).global != self.th.hdr.global } {
panic!("attempt to push a value created from a different Lua");
}
let src = unsafe { stack.add((*ci).func + i.get()) };
let dst = unsafe { stack.add((*ci).func + i.get() + 1) };
for i in (0..(top - i.get())).rev() {
let src = unsafe { src.add(i) };
let dst = unsafe { dst.add(i) };
unsafe { (*dst).tt_ = (*src).tt_ };
unsafe { (*dst).value_ = (*src).value_ };
}
unsafe { (*src).tt_ = v.tt_ };
unsafe { (*src).value_ = v.value_ };
unsafe { self.th.top.add(1) };
self.ret.set(self.ret.get() + 1);
Ok(())
}
pub fn pop(&mut self) {
let ret = self.ret.get().checked_sub(1).unwrap();
unsafe { self.th.top.sub(1) };
self.ret.set(ret);
}
pub fn truncate(&mut self, len: usize) {
let remove = match self.ret.get().checked_sub(len) {
Some(v) => v,
None => return,
};
unsafe { self.th.top.sub(remove) };
self.ret.set(len);
}
#[inline]
pub(crate) fn results(self) -> usize {
self.ret.get()
}
}
impl<'a, A> From<Context<'a, A, Args>> for Context<'a, A, Ret> {
#[inline(always)]
fn from(value: Context<'a, A, Args>) -> Self {
Self {
th: value.th,
ret: value.ret,
payload: Ret(value.payload.0),
yieldable: value.yieldable,
}
}
}
pub struct Args(usize);
impl Args {
#[inline(always)]
pub(crate) fn new(v: usize) -> Self {
Self(v)
}
}
pub struct Ret(usize);
struct New<'a, A, T> {
td: &'a Thread<A>,
payload: Option<T>,
}
impl<'a, A, T> Future for New<'a, A, T>
where
T: Unpin,
{
type Output = Context<'a, A, T>;
#[inline(always)]
fn poll(mut self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Context {
th: self.td,
ret: Cell::new(0),
payload: self.payload.take().unwrap(),
yieldable: addr_eq(cx.waker().vtable(), &YIELDABLE_WAKER),
})
}
}