diff --git a/hlua/ffi/src/lib.rs b/hlua/ffi/src/lib.rs index ef949a3f..00b1e927 100644 --- a/hlua/ffi/src/lib.rs +++ b/hlua/ffi/src/lib.rs @@ -1,13 +1,24 @@ +//! Module provides FFI bindings for the following constants, +//! types and functions, realted to Lua C API: +//! 1. Plain lua C API +//! 2. lauxlib +//! 3. Lua utitlites, implemented in Tarantool + #![allow(non_camel_case_types)] use std::os::raw::{c_double, c_int, c_schar}; use std::ptr::null_mut; -/// Module provides FFI bindings for the following constants, -/// types and functions, realted to Lua C API: -/// 1. Plain lua C API -/// 2. lauxlib -/// 3. Lua utitlites, implemented in Tarantool - +/// Lua provides a registry, a pre-defined table that can be used by any C code +/// to store whatever Lua value it needs to store. This table is always located +/// at pseudo-index `LUA_REGISTRYINDEX`. Any C library can store data into this +/// table, but it should take care to choose keys different from those used by +/// other libraries, to avoid collisions. Typically, you should use as key a +/// string containing your library name or a light userdata with the address of +/// a C object in your code. +/// +/// The integer keys in the registry are used by the reference mechanism, +/// implemented by the auxiliary library, and therefore should not be used for +/// other purposes. pub const LUA_REGISTRYINDEX: c_int = -10000; pub const LUA_ENVIRONINDEX: c_int = -10001; pub const LUA_GLOBALSINDEX: c_int = -10002; @@ -33,6 +44,9 @@ pub const LUA_TTHREAD: c_int = 8; pub const LUA_MINSTACK: c_int = 20; +pub const LUA_NOREF: c_int = -2; +pub const LUA_REFNIL: c_int = -1; + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct lua_State { @@ -48,6 +62,40 @@ pub struct luaL_Reg { pub type lua_Number = libc::c_double; pub type lua_Integer = libc::ptrdiff_t; +/// Type for C functions. +/// +/// In order to communicate properly with Lua, a C function must use the +/// following protocol, which defines the way parameters and results are passed: +/// a C function receives its arguments from Lua in its stack in direct order +/// (the first argument is pushed first). So, when the function starts, +/// [`lua_gettop`]`(L)` returns the number of arguments received by the function. +/// The first argument (if any) is at index 1 and its last argument is at index +/// [`lua_gettop`]`(L)`. To return values to Lua, a C function just pushes them +/// onto the stack, in direct order (the first result is pushed first), and +/// returns the number of results. Any other value in the stack below the +/// results will be properly discarded by Lua. Like a Lua function, a C function +/// called by Lua can also return many results. +/// +/// As an example, the following function receives a variable number of +/// numerical arguments and returns their average and sum: +/// +/// ``` +/// unsafe extern "C" fn foo(l: *mut lua_State) { +/// let n = lua_gettop(l); /* number of arguments */ +/// let mut sum: lua_Number = 0; +/// let i: i32; +/// for i in 1..=n { +/// if !lua_isnumber(l, i) { +/// lua_pushstring(l, CString::new("incorrect argument").into_raw()); +/// lua_error(l); +/// } +/// sum += lua_tonumber(l, i); +/// } +/// lua_pushnumber(l, sum / n); /* first result */ +/// lua_pushnumber(l, sum); /* second result */ +/// return 2; /* number of results */ +/// } +/// ``` pub type lua_CFunction = unsafe extern "C" fn(l: *mut lua_State) -> c_int; pub type lua_Alloc = extern "C" fn( @@ -80,56 +128,212 @@ extern "C" { pub fn lua_version(L: *mut lua_State) -> *const lua_Number; + /// Returns the index of the top element in the stack. Because indices start + /// at 1, this result is equal to the number of elements in the stack (and + /// so 0 means an empty stack). + /// *[-0, +0, -]* pub fn lua_gettop(l: *mut lua_State) -> c_int; - pub fn lua_settop(l: *mut lua_State, idx: c_int); + pub fn lua_settop(l: *mut lua_State, index: c_int); pub fn lua_pushboolean(l: *mut lua_State, n: c_int); pub fn lua_pushlstring(l: *mut lua_State, s: *const libc::c_char, l: libc::size_t); + + /// Pushes the zero-terminated string pointed to by `s` onto the stack. Lua + /// makes (or reuses) an internal copy of the given string, so the memory at + /// s can be freed or reused immediately after the function returns. The + /// string cannot contain embedded zeros; it is assumed to end at the first + /// zero. + /// *[-0, +1, m]* pub fn lua_pushstring(l: *mut lua_State, s: *const c_schar) -> *const c_schar; pub fn lua_pushinteger(l: *mut lua_State, n: isize); pub fn lua_pushnumber(l: *mut lua_State, n: c_double); + + /// Pushes a new C closure onto the stack. + /// *[-n, +1, m]* + /// + /// When a C function is created, it is possible to associate some values + /// with it, thus creating a C closure; these values are then accessible to + /// the function whenever it is called. To associate values with a C + /// function, first these values should be pushed onto the stack (when there + /// are multiple values, the first value is pushed first). Then + /// lua_pushcclosure is called to create and push the C function onto the + /// stack, with the argument `n` telling how many values should be + /// associated with the function. lua_pushcclosure also pops these values + /// from the stack. + /// + /// The maximum value for `n` is 255. pub fn lua_pushcclosure(l: *mut lua_State, fun: lua_CFunction, n: c_int); pub fn lua_pushnil(l: *mut lua_State); - /// [-0, +1, -] - /// + /// Pushes a copy of the element at the given valid `index` onto the stack. + /// *[-0, +1, -]* pub fn lua_pushvalue(l: *mut lua_State, index: c_int); - pub fn lua_tointeger(l: *mut lua_State, idx: c_int) -> isize; - pub fn lua_toboolean(l: *mut lua_State, idx: c_int) -> c_int; - pub fn lua_tolstring(l: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_schar; - pub fn lua_touserdata(l: *mut lua_State, idx: c_int) -> *mut libc::c_void; - pub fn lua_setfield(l: *mut lua_State, idx: c_int, s: *const c_schar); - pub fn lua_getfield(l: *mut lua_State, idx: c_int, s: *const c_schar); + pub fn lua_tointeger(l: *mut lua_State, index: c_int) -> isize; + pub fn lua_toboolean(l: *mut lua_State, index: c_int) -> c_int; + + /// Converts the Lua value at the given acceptable `index` to a C string. If + /// `len` is not NULL, it also sets `*len` with the string length. The Lua + /// value must be a string or a number; otherwise, the function returns + /// NULL. If the value is a number, then `lua_tolstring` also changes the + /// actual value in the stack to a string. (This change confuses + /// [`lua_next`] when `lua_tolstring` is applied to keys during a table + /// traversal.) + /// *[-0, +0, m]* + /// + /// `lua_tolstring` returns a fully aligned pointer to a string inside the + /// Lua state. This string always has a zero ('\0') after its last character + /// (as in C), but can contain other zeros in its body. Because Lua has + /// garbage collection, there is no guarantee that the pointer returned by + /// `lua_tolstring` will be valid after the corresponding value is removed + /// from the stack. + pub fn lua_tolstring(l: *mut lua_State, index: c_int, len: *mut usize) -> *const c_schar; + + /// If the value at the given acceptable `index` is a full userdata, returns + /// its block address. If the value is a light userdata, returns its + /// pointer. Otherwise, returns `NULL`. + /// *[-0, +0, -]* + pub fn lua_touserdata(l: *mut lua_State, index: c_int) -> *mut libc::c_void; + + /// Does the equivalent to `t[k] = v`, where `t` is the value at the given + /// valid index and `v` is the value at the top of the stack. + /// *[-1, +0, e]* + /// + /// This function pops the value from the stack. As in Lua, this function + /// may trigger a metamethod for the "newindex" event + pub fn lua_setfield(l: *mut lua_State, index: c_int, k: *const c_schar); + + /// Pushes onto the stack the value `t[k]`, where `t` is the value at the + /// given valid `index`. As in Lua, this function may trigger a metamethod + /// for the "index" event + /// *[-0, +1, e]* + pub fn lua_getfield(l: *mut lua_State, index: c_int, k: *const c_schar); + pub fn lua_createtable(l: *mut lua_State, narr: c_int, nrec: c_int); - pub fn lua_newuserdata(l: *mut lua_State, sz: libc::size_t) -> *mut libc::c_void; - /// [-1, +1, e] + + /// This function allocates a new block of memory with the given size, + /// pushes onto the stack a new full userdata with the block address, and + /// returns this address. + /// *[-0, +1, m]* /// + /// Userdata represent C values in Lua. A full userdata represents a block + /// of memory. It is an object (like a table): you must create it, it can + /// have its own metatable, and you can detect when it is being collected. A + /// full userdata is only equal to itself (under raw equality). + /// + /// When Lua collects a full userdata with a gc metamethod, Lua calls the + /// metamethod and marks the userdata as finalized. When this userdata is + /// collected again then Lua frees its corresponding memory. + pub fn lua_newuserdata(l: *mut lua_State, sz: libc::size_t) -> *mut libc::c_void; + /// Pushes onto the stack the value `t[k]`, where `t` is the value at the /// given valid `index` and `k` is the value at the top of the stack. + /// *[-1, +1, e]* /// /// This function pops the key from the stack (putting the resulting value /// in its place). As in Lua, this function may trigger a metamethod for the /// "index" event pub fn lua_gettable(l: *mut lua_State, index: c_int); - pub fn lua_settable(l: *mut lua_State, idx: c_int); + + /// Similar to [`lua_gettable`], but does a raw access (i.e., without + /// metamethods). + /// *[-1, +1, -]* + pub fn lua_rawget(l: *mut lua_State, index: c_int); + + /// Pushes onto the stack the value `t[n]`, where `t` is the value at the + /// given valid `index`. The access is *raw*; that is, it does not invoke + /// metamethods. + /// *[-0, +1, -]* + pub fn lua_rawgeti(l: *mut lua_State, index: c_int, n: c_int); + + /// Does the equivalent to `t[k] = v`, where `t` is the value at the given + /// valid `index`, `v` is the value at the top of the stack, and `k` is the + /// value just below the top. + /// *[-2, +0, e]* + /// + /// This function pops both the key and the value from the stack. As in Lua, + /// this function may trigger a metamethod for the "newindex" event. + pub fn lua_settable(l: *mut lua_State, index: c_int); + + /// Similar to [`lua_settable`], but does a raw assignment (i.e., without + /// metamethods). + /// *[-2, +0, m]* + pub fn lua_rawset(l: *mut lua_State, index: c_int); + + /// Does the equivalent of `t[n] = v`, where `t` is the value at the given + /// valid `index` and `v` is the value at the top of the stack. + /// *[-1, +0, m]* + /// + /// This function pops the value from the stack. The assignment is raw; that + /// is, it does not invoke metamethods. + pub fn lua_rawseti(l: *mut lua_State, index: c_int, n: c_int); + + /// Returns the type of the value in the given acceptable `index`, or + /// [`LUA_TNONE`] for a non-valid index (that is, an index to an "empty" + /// stack position). The types returned by lua_type are coded by the + /// following constants: [`LUA_TNIL`], [`LUA_TNUMBER`], [`LUA_TBOOLEAN`], + /// [`LUA_TSTRING`], [`LUA_TTABLE`], [`LUA_TFUNCTION`], [`LUA_TUSERDATA`], + /// [`LUA_TTHREAD`], and [`LUA_TLIGHTUSERDATA`]. + /// *[-0, +0, -]* pub fn lua_type(state: *mut lua_State, index: c_int) -> c_int; + + /// Returns the name of the type encoded by the value `tp`, which must be + /// one the values returned by [`lua_type`]. + /// *[-0, +0, -]* pub fn lua_typename(state: *mut lua_State, tp: c_int) -> *mut c_schar; - pub fn lua_setmetatable(l: *mut lua_State, objindex: c_int) -> c_int; - pub fn lua_getmetatable(l: *mut lua_State, objindex: c_int) -> c_int; - pub fn lua_tonumberx(l: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number; - pub fn lua_tointegerx(l: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer; + /// Pops a table from the stack and sets it as the new metatable for the + /// value at the given acceptable `index`. + /// *[-1, +0, -]* + pub fn lua_setmetatable(l: *mut lua_State, index: c_int) -> c_int; + pub fn lua_getmetatable(l: *mut lua_State, index: c_int) -> c_int; - pub fn lua_pcall(l: *mut lua_State, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int; + pub fn lua_tonumberx(l: *mut lua_State, index: c_int, isnum: *mut c_int) -> lua_Number; + pub fn lua_tointegerx(l: *mut lua_State, index: c_int, isnum: *mut c_int) -> lua_Integer; + + /// Calls a function in protected mode. + /// *[-(nargs + 1), +(nresults|1), -]* + /// + /// Both `nargs` and `nresults` have the same meaning as in `lua_call`. If + /// there are no errors during the call, `lua_pcall` behaves exactly like + /// `lua_call`. However, if there is any error, `lua_pcall` catches it, + /// pushes a single value on the stack (the error message), and returns an + /// error code. Like lua_call, `lua_pcall` always removes the function and + /// its arguments from the stack. + /// + /// If `errfunc` is 0, then the error message returned on the stack is + /// exactly the original error message. Otherwise, `errfunc` is the stack + /// index of an error handler function. (In the current implementation, this + /// index cannot be a pseudo-index.) In case of runtime errors, this + /// function will be called with the error message and its return value will + /// be the message returned on the stack by `lua_pcall`. + /// + /// Typically, the error handler function is used to add more debug + /// information to the error message, such as a stack traceback. Such + /// information cannot be gathered after the return of `lua_pcall`, since by + /// then the stack has unwound. + /// + /// The `lua_pcall` function returns 0 in case of success or one of the + /// following error codes: + /// - [`LUA_ERRRUN`]: a runtime error. + /// + /// - [`LUA_ERRMEM`]: memory allocation error. For such errors, Lua does not + /// call the error handler function. + /// + /// - [`LUA_ERRERR`]: error while running the error handler function. + pub fn lua_pcall(l: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int; pub fn lua_load(l: *mut lua_State, reader: lua_Reader, dt: *mut libc::c_void, chunkname: *const libc::c_char, mode: *const libc::c_char) -> c_int; pub fn lua_dump(l: *mut lua_State, writer: lua_Writer, data: *mut libc::c_void) -> c_int; + /// Generates a Lua error. The error message (which can actually be a Lua + /// value of any type) must be on the stack top. This function does a long + /// jump, and therefore never returns. (see [`luaL_error`]). + /// *[-1, +0, v]* pub fn lua_error(l: *mut lua_State) -> c_int; - pub fn lua_next(l: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_next(l: *mut lua_State, index: c_int) -> c_int; pub fn lua_concat(l: *mut lua_State, n: c_int); - pub fn lua_len(l: *mut lua_State, idx: c_int); + pub fn lua_len(l: *mut lua_State, index: c_int); - pub fn lua_insert(l: *mut lua_State, idx: c_int); - pub fn lua_remove(l: *mut lua_State, idx: c_int); + pub fn lua_insert(l: *mut lua_State, index: c_int); + pub fn lua_remove(l: *mut lua_State, index: c_int); pub fn luaopen_base(l: *mut lua_State); pub fn luaopen_bit(l: *mut lua_State); @@ -144,18 +348,54 @@ extern "C" { // lauxlib functions. pub fn luaL_newstate() -> *mut lua_State; pub fn luaL_register(l: *mut lua_State, libname: *const c_schar, lr: *const luaL_Reg); + + /// Raises an error. The error message format is given by `fmt` plus any + /// extra arguments, following the same rules of `lua_pushfstring`. It also + /// adds at the beginning of the message the file name and the line number + /// where the error occurred, if this information is available. + /// *[-0, +0, v]* + /// + /// This function never returns, but it is an idiom to use it in C functions + /// as return `luaL_error(args)`. pub fn luaL_error(l: *mut lua_State, fmt: *const c_schar, ...) -> c_int; pub fn luaL_openlibs(L: *mut lua_State); + + /// Creates and returns a reference, in the table at index `t`, for the + /// object at the top of the stack (and pops the object). + /// *[-1, +0, m]* + /// + /// A reference is a unique integer key. As long as you do not manually add + /// integer keys into table t, `luaL_ref` ensures the uniqueness of the key + /// it returns. You can retrieve an object referred by reference r by + /// calling [`lua_rawgeti`]`(L, t, r)`. Function [`luaL_unref`] frees a + /// reference and its associated object. + /// + /// If the object at the top of the stack is nil, `luaL_ref` returns the + /// constant [`LUA_REFNIL`]. The constant [`LUA_NOREF`] is guaranteed to be + /// different from any reference returned by `luaL_ref`. + pub fn luaL_ref(l: *mut lua_State, t: c_int) -> c_int; + + /// Releases reference `r` from the table at index `t` (see [`luaL_ref`]). + /// The entry is removed from the table, so that the referred object can be + /// collected. The reference `r` is also freed to be used again. + /// *[-0, +0, -]* + /// + /// If ref is [`LUA_NOREF`] or [`LUA_REFNIL`], `luaL_unref` does nothing. + pub fn luaL_unref(l: *mut lua_State, t: c_int, r: c_int); } #[inline(always)] -pub unsafe fn lua_getglobal(state: *mut lua_State, s: *const c_schar) { - lua_getfield(state, LUA_GLOBALSINDEX, s); +/// Pushes onto the stack the value of the global `name`. +/// *[-0, +1, e]* +pub unsafe fn lua_getglobal(state: *mut lua_State, name: *const c_schar) { + lua_getfield(state, LUA_GLOBALSINDEX, name); } #[inline(always)] -pub unsafe fn lua_setglobal(state: *mut lua_State, s: *const c_schar) { - lua_setfield(state, LUA_GLOBALSINDEX, s); +/// Pops a value from the stack and sets it as the new value of global `name`. +/// *[-1, +0, e]* +pub unsafe fn lua_setglobal(state: *mut lua_State, name: *const c_schar) { + lua_setfield(state, LUA_GLOBALSINDEX, name); } #[inline(always)] @@ -164,6 +404,13 @@ pub unsafe fn lua_pop(state: *mut lua_State, n: c_int) { } #[inline(always)] +/// Pushes a C function onto the stack. This function receives a pointer to a C +/// function and pushes onto the stack a Lua value of type function that, when +/// called, invokes the corresponding C function. +/// `[-0, +1, m]` +/// +/// Any function to be registered in Lua must follow the correct protocol to +/// receive its parameters and return its results (see [`lua_CFunction`]). pub unsafe fn lua_pushcfunction(state: *mut lua_State, f: lua_CFunction) { lua_pushcclosure(state, f, 0); } @@ -179,23 +426,34 @@ pub unsafe fn lua_newtable(state: *mut lua_State) { } #[inline(always)] +/// When a C function is created, it is possible to associate some values with +/// it, thus creating a C closure; these values are called upvalues and are +/// accessible to the function whenever it is called (see [`lua_pushcclosure`]). +/// +/// Whenever a C function is called, its **upvalues** are located at specific +/// pseudo-indices. These pseudo-indices are produced by the function +/// `lua_upvalueindex`. The first value associated with a function is at +/// position `lua_upvalueindex(1)`, and so on. Any access to +/// `lua_upvalueindex(n)`, where n is greater than the number of upvalues of the +/// current function (but not greater than 256), produces an acceptable (but +/// invalid) index. pub fn lua_upvalueindex(i: c_int) -> c_int { LUA_GLOBALSINDEX - i } #[inline(always)] -pub unsafe fn lua_isfunction(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TFUNCTION +pub unsafe fn lua_isfunction(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TFUNCTION } #[inline(always)] -pub unsafe fn lua_istable(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TTABLE +pub unsafe fn lua_istable(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TTABLE } #[inline(always)] -pub unsafe fn lua_islightuserdata(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TLIGHTUSERDATA +pub unsafe fn lua_islightuserdata(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TLIGHTUSERDATA } #[inline(always)] @@ -204,23 +462,23 @@ pub unsafe fn lua_isnil(state: *mut lua_State, index: c_int) -> bool { } #[inline(always)] -pub unsafe fn lua_isboolean(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TBOOLEAN +pub unsafe fn lua_isboolean(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TBOOLEAN } #[inline(always)] -pub unsafe fn lua_isthread(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TTHREAD +pub unsafe fn lua_isthread(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TTHREAD } #[inline(always)] -pub unsafe fn lua_isnone(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TNONE +pub unsafe fn lua_isnone(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TNONE } #[inline(always)] -pub unsafe fn lua_isnoneornil(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) <= 0 +pub unsafe fn lua_isnoneornil(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) <= 0 } #[inline(always)] diff --git a/hlua/hlua/src/lib.rs b/hlua/hlua/src/lib.rs index d56f6ff9..7fe2a2cf 100644 --- a/hlua/hlua/src/lib.rs +++ b/hlua/hlua/src/lib.rs @@ -125,9 +125,12 @@ pub use lua_tables::LuaTable; pub use lua_tables::LuaTableIterator; pub use tuples::TuplePushError; pub use userdata::UserdataOnStack; -pub use userdata::{push_userdata, read_userdata}; +pub use userdata::{push_userdata, read_userdata, push_some_userdata}; pub use values::StringInLua; +// Needed for `lua_error` macro +pub use ffi::luaL_error; + mod any; mod functions_write; mod lua_functions; @@ -369,7 +372,7 @@ pub trait PushOne: Push {} /// Type that cannot be instantiated. /// -/// Will be replaced with `!` eventually (https://github.com/rust-lang/rust/issues/35121). +/// Will be replaced with `!` eventually (). #[derive(Debug, Copy, Clone)] pub enum Void {} @@ -520,7 +523,7 @@ impl<'lua> Lua<'lua> { /// Opens all standard Lua libraries. /// /// See the reference for the standard library here: - /// https://www.lua.org/manual/5.2/manual.html#6 + /// /// /// This is done by calling `luaL_openlibs`. /// @@ -538,7 +541,7 @@ impl<'lua> Lua<'lua> { /// Opens base library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_base + /// #[inline] pub fn open_base(&mut self) { unsafe { ffi::luaopen_base(self.lua.0) } @@ -546,7 +549,7 @@ impl<'lua> Lua<'lua> { /// Opens bit32 library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_bit32 + /// #[inline] pub fn open_bit(&mut self) { unsafe { ffi::luaopen_bit(self.lua.0) } @@ -554,7 +557,7 @@ impl<'lua> Lua<'lua> { /// Opens debug library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_debug + /// #[inline] pub fn open_debug(&mut self) { unsafe { ffi::luaopen_debug(self.lua.0) } @@ -562,7 +565,7 @@ impl<'lua> Lua<'lua> { /// Opens io library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_io + /// #[inline] pub fn open_io(&mut self) { unsafe { ffi::luaopen_io(self.lua.0) } @@ -570,7 +573,7 @@ impl<'lua> Lua<'lua> { /// Opens math library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_math + /// #[inline] pub fn open_math(&mut self) { unsafe { ffi::luaopen_math(self.lua.0) } @@ -578,7 +581,7 @@ impl<'lua> Lua<'lua> { /// Opens os library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_os + /// #[inline] pub fn open_os(&mut self) { unsafe { ffi::luaopen_os(self.lua.0) } @@ -586,7 +589,7 @@ impl<'lua> Lua<'lua> { /// Opens package library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_package + /// #[inline] pub fn open_package(&mut self) { unsafe { ffi::luaopen_package(self.lua.0) } @@ -594,7 +597,7 @@ impl<'lua> Lua<'lua> { /// Opens string library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_string + /// #[inline] pub fn open_string(&mut self) { unsafe { ffi::luaopen_string(self.lua.0) } @@ -602,7 +605,7 @@ impl<'lua> Lua<'lua> { /// Opens table library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_table + /// #[inline] pub fn open_table(&mut self) { unsafe { ffi::luaopen_table(self.lua.0) } diff --git a/hlua/hlua/src/macros.rs b/hlua/hlua/src/macros.rs index 4ca88f12..02f4cc12 100644 --- a/hlua/hlua/src/macros.rs +++ b/hlua/hlua/src/macros.rs @@ -60,3 +60,21 @@ macro_rules! implement_lua_read { } }; } + +#[macro_export] +macro_rules! c_ptr { + ($s:literal) => { + ::std::concat!($s, "\0").as_bytes().as_ptr() as *mut i8 + }; +} + +#[macro_export] +macro_rules! lua_error { + ($l:expr, $msg:literal) => { + { + $crate::luaL_error($l, c_ptr!($msg)); + unreachable!() + } + } +} + diff --git a/hlua/hlua/src/userdata.rs b/hlua/hlua/src/userdata.rs index dfc1e34d..11978bc5 100644 --- a/hlua/hlua/src/userdata.rs +++ b/hlua/hlua/src/userdata.rs @@ -13,8 +13,45 @@ use crate::{ LuaRead, InsideCallback, LuaTable, + c_ptr, }; +/// Pushes `value` of type `T` onto the stack as a userdata. The value is +/// put inside a `Option` so that it can be safely moved out of there. Useful +/// for example when passing `FnOnce` as a c closure, because it must be dropped +/// after the call. +/// *[0, +1, -]* +pub unsafe fn push_some_userdata(lua: *mut ffi::lua_State, value: T) { + type UDBox = Option; + let ud_ptr = ffi::lua_newuserdata(lua, std::mem::size_of::>()); + std::ptr::write(ud_ptr as *mut UDBox, Some(value)); + + if std::mem::needs_drop::() { + // Creating a metatable. + ffi::lua_newtable(lua); + + // Index "__gc" in the metatable calls the object's destructor. + ffi::lua_pushstring(lua, c_ptr!("__gc")); + ffi::lua_pushcfunction(lua, wrap_gc::); + ffi::lua_settable(lua, -3); + + ffi::lua_setmetatable(lua, -2); + } + + /// A callback for the "__gc" event. It checks if the value was moved out + /// and if not it drops the value. + unsafe extern "C" fn wrap_gc(lua: *mut ffi::lua_State) -> i32 { + let ud_ptr = ffi::lua_touserdata(lua, 1); + let ud = (ud_ptr as *mut UDBox) + .as_mut() + .expect("__gc called with userdata pointing to NULL"); + drop(ud.take()); + + 0 + } +} + + // Called when an object inside Lua is being dropped. #[inline] extern "C" fn destructor_wrapper(lua: *mut ffi::lua_State) -> libc::c_int { diff --git a/tarantool/src/error.rs b/tarantool/src/error.rs index 7151d216..c043ea4e 100644 --- a/tarantool/src/error.rs +++ b/tarantool/src/error.rs @@ -26,6 +26,7 @@ use rmp::decode::{MarkerReadError, NumValueReadError, ValueReadError}; use rmp::encode::ValueWriteError; use crate::ffi::tarantool as ffi; +use crate::hlua::LuaError; /// A specialized [`Result`] type for the crate pub type Result = std::result::Result; @@ -71,6 +72,9 @@ pub enum Error { #[cfg(feature = "net_box")] #[fail(display = "Sever respond with error: {}", _0)] Remote(crate::net_box::ResponseError), + + #[fail(display = "Lua error: {}", _0)] + LuaError(LuaError), } impl From for Error { @@ -142,6 +146,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: LuaError) -> Self { + Error::LuaError(error) + } +} + /// Transaction-related error cases #[derive(Debug, Fail)] pub enum TransactionError { diff --git a/tarantool/src/fiber.rs b/tarantool/src/fiber.rs index b28aec17..1b094381 100644 --- a/tarantool/src/fiber.rs +++ b/tarantool/src/fiber.rs @@ -15,10 +15,11 @@ use std::marker::PhantomData; use std::os::raw::c_void; use std::time::Duration; +use crate::hlua::{AsLua, c_ptr, lua_error}; use va_list::VaList; use crate::error::TarantoolError; -use crate::ffi::tarantool as ffi; +use crate::ffi::{tarantool as ffi, lua}; use crate::Result; /// *OBSOLETE*: This struct is being deprecated in favour of [`Immediate`], @@ -409,6 +410,255 @@ where } } +//////////////////////////////////////////////////////////////////////////////// +/// LuaFiber +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaFiber { + callee: C, +} + +/// Deferred non-yielding fiber implemented using **lua** api. This (hopefully) +/// temporary implementation is a workaround. Tarantool C API lacks the method +/// for passing the necessary information into the underlying `struct fiber` +/// reliably. In this case we need to be able to set the `void *f_arg` field to +/// be able to implement correct deferred fibers which don't yield. +impl LuaFiber +where + C: LuaCallee, + +{ + pub fn new(callee: C) -> Self { + Self { callee } + } + + pub fn spawn(self) -> Result { + let Self { callee } = self; + let fiber_ref = unsafe { + let l = ffi::luaT_state(); + lua::lua_getglobal(l, c_ptr!("require")); + lua::lua_pushstring(l, c_ptr!("fiber")); + if lua::lua_pcall(l, 1, 1, 0) == lua::LUA_ERRRUN { + let ret = Err(impl_details::lua_error_from_top(l).into()); + lua::lua_pop(l, 1); + return ret + }; + lua::lua_getfield(l, -1, c_ptr!("new")); + hlua::push_some_userdata(l, callee.into_inner()); + lua::lua_pushcclosure(l, Self::trampoline, 1); + if lua::lua_pcall(l, 1, 1, 0) == lua::LUA_ERRRUN { + let ret = Err(impl_details::lua_error_from_top(l).into()); + lua::lua_pop(l, 2); + return ret + }; + lua::lua_getfield(l, -1, c_ptr!("set_joinable")); + lua::lua_pushvalue(l, -2); + lua::lua_pushboolean(l, true as i32); + if lua::lua_pcall(l, 2, 0, 0) == lua::LUA_ERRRUN { + panic!("{}", impl_details::lua_error_from_top(l)) + }; + let fiber_ref = lua::luaL_ref(l, lua::LUA_REGISTRYINDEX); + // pop the fiber module from the stack + lua::lua_pop(l, 1); + + fiber_ref + }; + + Ok(C::join_handle(fiber_ref)) + } + + unsafe extern "C" fn trampoline(l: *mut lua::lua_State) -> i32 { + let ud_ptr = lua::lua_touserdata(l, lua::lua_upvalueindex(1)); + + let f = (ud_ptr as *mut Option).as_mut() + .unwrap_or_else(|| + // lua_touserdata returned NULL + lua_error!(l, "failed to extract upvalue") + ) + // put None back into userdata + .take() + .unwrap_or_else(|| + // userdata originally contained None + lua_error!(l, "rust FnOnce callback was called more than once") + ); + + // call f and drop it afterwards + let res = f(); + + // return results to lua + C::save_result(l, res) + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaJoinHandle +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaJoinHandle { + fiber_ref: i32, + marker: PhantomData, +} + +impl LuaJoinHandle { + pub fn join(self) -> Result { + let Self { fiber_ref, .. } = self; + unsafe { + let guard = impl_details::lua_fiber_join(fiber_ref)?; + let l = guard.as_lua().state_ptr(); + let ud_ptr = lua::lua_touserdata(l, -1); + let res = (ud_ptr as *mut Option).as_mut() + .expect("fiber:join must return correct userdata") + .take() + .expect("data can only be taken once from the UDBox"); + Ok(res) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaUnitJoinHandle +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaUnitJoinHandle { + fiber_ref: i32, +} + +impl LuaUnitJoinHandle { + pub fn join(self) -> Result<()> { + let Self { fiber_ref, .. } = self; + unsafe { impl_details::lua_fiber_join(fiber_ref)? }; + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// impl_details +//////////////////////////////////////////////////////////////////////////////// + +mod impl_details { + use super::*; + use hlua::{AsMutLua, Lua, LuaError, PushGuard}; + + pub unsafe fn lua_error_from_top(l: *mut lua::lua_State) -> LuaError { + let mut len = std::mem::MaybeUninit::uninit(); + let data = lua::lua_tolstring(l, -1, len.as_mut_ptr()); + assert!(!data.is_null()); + let msg_bytes = std::slice::from_raw_parts( + data as *mut u8, len.assume_init() + ); + let msg = String::from_utf8_lossy(msg_bytes); + hlua::LuaError::ExecutionError(msg.into()).into() + } + + pub unsafe fn lua_fiber_join(f_ref: i32) -> Result>> { + let mut l = Lua::from_existing_state(ffi::luaT_state(), false); + let lptr = l.as_mut_lua().state_ptr(); + lua::lua_rawgeti(lptr, lua::LUA_REGISTRYINDEX, f_ref); + lua::lua_getfield(lptr, -1, c_ptr!("join")); + lua::lua_pushvalue(lptr, -2); + + if lua::lua_pcall(lptr, 1, 2, 0) == lua::LUA_ERRRUN { + let err = lua_error_from_top(lptr).into(); + // 2 values on the stack: + // 1) fiber; 2) error + let _guard = PushGuard::new(l, 2); + return Err(err) + }; + // 3 values on the stack that need to be dropped: + // 1) fiber; 2) join function; 3) fiber + let guard = PushGuard::new(l, 3); + + // check fiber return code + assert_ne!(lua::lua_toboolean(lptr, -2), 0); + + // fiber object and the 2 return values will need to be poped + Ok(guard) + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaCallee +//////////////////////////////////////////////////////////////////////////////// + +pub trait LuaCallee { + /// Type of the callee + type Function: FnOnce() -> Self::Output; + + /// Return type of the callee + type Output; + + /// Type of the join handle returned by [`LuaFiber::spawn`] method + type JoinHandle; + + /// Extract the inner function + fn into_inner(self) -> Self::Function; + + /// Construct a `Self::JoinHandle` from a fiber reference + fn join_handle(fiber_ref: i32) -> Self::JoinHandle; + + /// This function is called within `LuaFiber::trampoline` to save the + /// return value after the callee's invocation + /// + /// This function is unsafe, because it is very easy to mess things up + /// when preparing arugments. + unsafe fn save_result(l: *mut lua::lua_State, res: Self::Output) -> i32; +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaFiberFunc +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaFiberFunc(pub F); + +impl LuaCallee for LuaFiberFunc +where + F: FnOnce() -> T, +{ + type Function = F; + type Output = T; + type JoinHandle = LuaJoinHandle; + + fn into_inner(self) -> F { + self.0 + } + + fn join_handle(fiber_ref: i32) -> Self::JoinHandle { + LuaJoinHandle { fiber_ref, marker: PhantomData } + } + + unsafe fn save_result(l: *mut lua::lua_State, res: T) -> i32 { + hlua::push_some_userdata(l, res); + 1 + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaFiberProc +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaFiberProc(pub F); + +impl LuaCallee for LuaFiberProc +where + F: FnOnce(), +{ + type Function = F; + type Output = (); + type JoinHandle = LuaUnitJoinHandle; + + fn join_handle(fiber_ref: i32) -> Self::JoinHandle { + LuaUnitJoinHandle { fiber_ref } + } + + fn into_inner(self) -> F { + self.0 + } + + unsafe fn save_result(_: *mut lua::lua_State, _: ()) -> i32 { + 0 + } +} + //////////////////////////////////////////////////////////////////////////////// /// Callee //////////////////////////////////////////////////////////////////////////////// @@ -681,47 +931,39 @@ where Builder::new().proc(f).start().unwrap() } -#[cfg(any(feature = "defer", doc))] -/// Creates a new fiber and schedules it for exeution, returning a -/// [`JoinHandle`] for it. +/// Creates a new fiber and schedules it for execution, returning a +/// [`LuaJoinHandle`] for it. /// -/// **NOTE:** In the current implementation the current fiber performs a -/// **yield** to start the newly created fiber and then the new fiber -/// performs another **yield**. This means that the deferred fiber is **not -/// applicable for transactions** (which do not allow any context switches). -/// In the future we are planning to add a correct implementation. +/// **NOTE:** In the current implementation the fiber is constructed using the +/// lua api, so it's efficiency is far from perfect. /// /// **NOTE**: The argument `f` is a function that returns `T`. In case when `T = /// ()` (no return value) one should instead use [`defer_proc`]. /// -/// The new fiber can be joined by calling [`JoinHandle::join`] method on it's -/// join handle. -pub fn defer(f: F) -> JoinHandle +/// The new fiber can be joined by calling [`LuaJoinHandle::join`] method on +/// it's join handle. +pub fn defer(f: F) -> LuaJoinHandle where F: FnOnce() -> T, { - Builder::new().func(f).defer().unwrap() + LuaFiber::new(LuaFiberFunc(f)).spawn().unwrap() } -#[cfg(any(feature = "defer", doc))] -/// Creates a new proc fiber and schedules it for exeution, returning a -/// [`UnitJoinHandle`] for it. +/// Creates a new proc fiber and schedules it for execution, returning a +/// [`LuaUnitJoinHandle`] for it. /// -/// **NOTE:** In the current implementation the current fiber performs a -/// **yield** to start the newly created fiber and then the new fiber performs -/// another **yield**. This means that the deferred fiber is **not applicable -/// for transactions** (which do not allow any context switches). In the future -/// we are planning to add a correct implementation. +/// **NOTE:** In the current implementation the fiber is constructed using the +/// lua api, so it's efficiency is far from perfect. /// -/// The new fiber can be joined by calling [`UnitJoinHandle::join`] method on +/// The new fiber can be joined by calling [`LuaUnitJoinHandle::join`] method on /// it's join handle. /// /// This is an optimized version [`defer`]``. -pub fn defer_proc(f: F) -> UnitJoinHandle +pub fn defer_proc(f: F) -> LuaUnitJoinHandle where F: FnOnce(), { - Builder::new().proc(f).defer().unwrap() + LuaFiber::new(LuaFiberProc(f)).spawn().unwrap() } /// Make it possible or not possible to wakeup the current diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 20952bab..cc9e408b 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -284,6 +284,11 @@ fn run_tests(cfg: TestConfig) -> Result { test_fiber::test_unit_deferred, test_fiber::test_unit_deferred_with_attrs, test_fiber::test_multiple_unit_deferred, + test_fiber::deferred_doesnt_yield, + test_fiber::immediate_yields, + test_fiber::start_error, + test_fiber::require_error, + test_box::test_space_get_by_name, test_box::test_space_get_system, test_box::test_index_get_by_name, diff --git a/tests/src/test_fiber.rs b/tests/src/test_fiber.rs index e573c5ed..d695da4b 100644 --- a/tests/src/test_fiber.rs +++ b/tests/src/test_fiber.rs @@ -7,6 +7,13 @@ use std::{ use tarantool::fiber::{ self, fiber_yield, is_cancelled, sleep, Cond, Fiber, FiberAttr }; +use tarantool::hlua::{ + AsMutLua, + Lua, + LuaFunction +}; + +use tarantool::ffi::lua; pub fn test_fiber_new() { let mut fiber = Fiber::new("test_fiber", &mut |_| 0); @@ -246,7 +253,7 @@ pub fn test_deferred() { assert_eq!(jh.join(), 13); let jh = fiber::defer(|| 42); - assert_eq!(jh.join(), 42); + assert_eq!(jh.join().unwrap(), 42); } pub fn test_deferred_with_attrs() { @@ -273,7 +280,8 @@ pub fn test_multiple_deferred() { res.push(1); res.extend( fibers.into_iter() - .map(fiber::JoinHandle::join) + .map(fiber::LuaJoinHandle::join) + .map(Result::unwrap) .flatten() ); res.push(8); @@ -290,7 +298,7 @@ pub fn test_unit_deferred() { let res = std::cell::Cell::new(0); let jh = fiber::defer_proc(|| res.set(42)); assert_eq!(res.get(), 0); - jh.join(); + jh.join().unwrap(); assert_eq!(res.get(), 42); } @@ -320,8 +328,161 @@ pub fn test_multiple_unit_deferred() { res.borrow_mut().push(1); for f in fibers { f.join() + .unwrap() } res.borrow_mut().push(8); let res = res.borrow().iter().copied().collect::>(); assert_eq!(res, vec![1, 2, 3, 4, 5, 6, 7, 8]); } + +fn fiber_csw() -> i32 { + static mut FUNCTION_DEFINED: bool = false; + let mut lua: Lua = crate::hlua::global(); + + if unsafe { !FUNCTION_DEFINED } { + lua.execute::<()>(r#" + function fiber_csw() + local fiber = require('fiber') + return fiber.info()[fiber.id()].csw + end + "#).unwrap(); + unsafe { FUNCTION_DEFINED = true; } + } + + return lua.get::, _>("fiber_csw").unwrap().call().unwrap(); +} + +struct LuaStackIntegrityGuard { + name: &'static str, +} + +impl LuaStackIntegrityGuard { + fn new(name: &'static str) -> Self { + let mut lua: Lua = crate::hlua::global(); + let l = lua.as_mut_lua().state_ptr(); + unsafe { lua::lua_pushlstring(l, name.as_bytes().as_ptr() as *mut i8, name.len()) }; + Self{name} + } +} + +impl Drop for LuaStackIntegrityGuard { + fn drop(&mut self) { + let mut lua: Lua = crate::hlua::global(); + let l = lua.as_mut_lua().state_ptr(); + + let msg = unsafe { + let cstr = lua::lua_tostring(l, -1); + if cstr.is_null() { + panic!("Lua stack integrity violation"); + } + let msg = std::ffi::CStr::from_ptr(cstr).to_str().unwrap(); + lua::lua_pop(l, 1); + msg + }; + + assert_eq!(msg, self.name); + } +} + +pub fn immediate_yields() { + let _guard = LuaStackIntegrityGuard::new("immediate_fiber_guard"); + + let mut upvalue = 0; + let csw1 = fiber_csw(); + fiber::start(|| upvalue = 69); + let csw2 = fiber_csw(); + + assert_eq!(upvalue, 69); + assert_eq!(csw2, csw1+1); +} + +pub fn deferred_doesnt_yield() { + let _guard = LuaStackIntegrityGuard::new("deferred_fiber_guard"); + + let mut upvalue = 0; + let csw1 = fiber_csw(); + fiber::defer(|| upvalue = 96); + let csw2 = fiber_csw(); + + assert_eq!(upvalue, 0); + assert_eq!(csw2, csw1); + + fiber::sleep(0.); + assert_eq!(upvalue, 96); +} + +pub fn start_error() { + let _guard = LuaStackIntegrityGuard::new("fiber_error_guard"); + + let _spoiler = LuaContextSpoiler::new(); + + match fiber::LuaFiber::new(fiber::LuaFiberFunc(|| ())).spawn() { + Err(e) => assert_eq!( + format!("{}", e), + "Lua error: Execution error: Artificial error" + ), + _ => panic!(), + } + + struct LuaContextSpoiler; + + impl LuaContextSpoiler { + fn new() -> Self { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + _fiber_new_backup = package.loaded.fiber.new + package.loaded.fiber.new = function() error("Artificial error", 0) end + "#).unwrap(); + Self + } + } + + impl Drop for LuaContextSpoiler { + fn drop(&mut self) { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + package.loaded.fiber.new = _fiber_new_backup + _fiber_new_backup = nil + "#).unwrap(); + } + } +} + +pub fn require_error() { + let _guard = LuaStackIntegrityGuard::new("fiber_error_guard"); + + let _spoiler = LuaContextSpoiler::new(); + + match fiber::LuaFiber::new(fiber::LuaFiberFunc(|| ())).spawn() { + Err(e) => assert_eq!( + format!("{}", e), + "Lua error: Execution error: Artificial require error" + ), + _ => panic!(), + } + + struct LuaContextSpoiler; + + impl LuaContextSpoiler { + fn new() -> Self { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + _fiber_backup = package.loaded.fiber + package.loaded.fiber = nil + package.preload.fiber = function() error("Artificial require error", 0) end + "#).unwrap(); + Self + } + } + + impl Drop for LuaContextSpoiler { + fn drop(&mut self) { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + package.preload.fiber = nil + package.loaded.fiber = _fiber_backup + _fiber_backup = nil + "#).unwrap(); + } + } +}