use std::ffi::CString;
use std::io::Cursor;
use std::io::Error as IoError;
use std::io::Read;
use std::num::NonZeroI32;
use std::panic::Location;
use crate::{
ffi, impl_object, nzi32,
object::{Call, CallError, FromObject, Object},
AsLua, LuaError, LuaRead, LuaState, Push, PushGuard, PushInto, PushOne, PushOneInto,
};
#[derive(Debug)]
pub struct LuaCode<'a>(pub &'a str);
impl<'c, L> Push<L> for LuaCode<'c>
where
L: AsLua,
{
type Err = LuaError;
#[track_caller]
#[inline]
fn push_to_lua(&self, lua: L) -> Result<PushGuard<L>, (LuaError, L)> {
let reader = Cursor::new(self.0.as_bytes());
LuaCodeFromReader::new(reader).push_into_lua(lua)
}
}
impl<'c, L> PushOne<L> for LuaCode<'c> where L: AsLua {}
#[derive(Debug)]
pub struct LuaCodeFromReader<R> {
reader: R,
location: &'static Location<'static>,
}
impl<R> LuaCodeFromReader<R> {
#[track_caller]
pub fn new(reader: R) -> Self {
Self {
reader,
location: Location::caller(),
}
}
}
impl<L, R> PushInto<L> for LuaCodeFromReader<R>
where
L: AsLua,
R: Read,
{
type Err = LuaError;
#[inline]
fn push_into_lua(self, lua: L) -> Result<PushGuard<L>, (LuaError, L)> {
unsafe {
struct ReadData<R> {
reader: R,
buffer: [u8; 128],
triggered_error: Option<IoError>,
}
let mut read_data = ReadData {
reader: self.reader,
buffer: [0; 128],
triggered_error: None,
};
extern "C" fn reader<R>(
_: LuaState,
data: *mut libc::c_void,
size: *mut libc::size_t,
) -> *const libc::c_char
where
R: Read,
{
unsafe {
let data: *mut ReadData<R> = data as *mut _;
let data: &mut ReadData<R> = &mut *data;
if data.triggered_error.is_some() {
(*size) = 0;
return data.buffer.as_ptr() as *const libc::c_char;
}
match data.reader.read(&mut data.buffer) {
Ok(len) => (*size) = len as libc::size_t,
Err(e) => {
(*size) = 0;
data.triggered_error = Some(e);
}
};
data.buffer.as_ptr() as *const libc::c_char
}
}
let (load_return_value, pushed_value) = {
let location = format!("=[{}:{}]\0", self.location.file(), self.location.line());
let location = CString::from_vec_with_nul_unchecked(location.into());
let code = ffi::lua_load(
lua.as_lua(),
reader::<R>,
&mut read_data as *mut ReadData<_> as *mut _,
location.as_ptr(),
);
(code, PushGuard::new(lua, 1))
};
if read_data.triggered_error.is_some() {
let error = read_data.triggered_error.unwrap();
return Err((LuaError::ReadError(error), pushed_value.into_inner()));
}
if load_return_value == 0 {
return Ok(pushed_value);
}
let error_msg: String = LuaRead::lua_read(pushed_value.as_lua())
.expect("can't find error message at the top of the Lua stack");
if load_return_value == ffi::LUA_ERRMEM {
panic!("LUA_ERRMEM");
}
if load_return_value == ffi::LUA_ERRSYNTAX {
return Err((LuaError::SyntaxError(error_msg), pushed_value.into_inner()));
}
panic!("Unknown error while calling lua_load");
}
}
}
impl<L, R> PushOneInto<L> for LuaCodeFromReader<R>
where
L: AsLua,
R: Read,
{
}
#[derive(Debug)]
pub struct LuaFunction<L> {
inner: Object<L>,
}
impl<L> LuaFunction<L>
where
L: AsLua,
{
unsafe fn new(lua: L, index: NonZeroI32) -> Self {
Self::from_obj(Object::new(lua, index))
}
pub fn into_inner(self) -> L {
self.inner.into_guard()
}
}
impl_object! { LuaFunction,
check(lua, index) {
ffi::lua_isfunction(lua.as_lua(), index.into())
}
impl Call,
}
impl<'lua, L> LuaFunction<L>
where
L: 'lua,
L: AsLua,
{
#[track_caller]
#[inline]
pub fn call<V>(&'lua self) -> Result<V, LuaError>
where
V: LuaRead<PushGuard<&'lua L>>,
{
Call::call(self)
}
#[track_caller]
#[inline]
pub fn into_call<V>(self) -> Result<V, LuaError>
where
V: LuaRead<PushGuard<Self>>,
{
Call::into_call(self)
}
#[track_caller]
#[inline]
pub fn call_with_args<V, A>(&'lua self, args: A) -> Result<V, CallError<A::Err>>
where
A: PushInto<LuaState>,
V: LuaRead<PushGuard<&'lua L>>,
{
Call::call_with(self, args)
}
#[track_caller]
#[inline]
pub fn into_call_with_args<V, A>(self, args: A) -> Result<V, CallError<A::Err>>
where
A: PushInto<LuaState>,
V: LuaRead<PushGuard<Self>>,
{
Call::into_call_with(self, args)
}
}
impl<L> LuaFunction<PushGuard<L>>
where
L: AsLua,
{
#[track_caller]
#[inline]
pub fn load_from_reader(lua: L, code: impl Read) -> Result<Self, LuaError> {
match LuaCodeFromReader::new(code).push_into_lua(lua) {
Ok(pushed) => unsafe { Ok(Self::new(pushed, nzi32!(-1))) },
Err((err, _)) => Err(err),
}
}
#[track_caller]
#[inline(always)]
pub fn load(lua: L, code: &str) -> Result<Self, LuaError> {
let reader = Cursor::new(code.as_bytes());
Self::load_from_reader(lua, reader)
}
}