From 823dd39e5deaf8bfe1a58d991b5c6109b390d428 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Fri, 15 Sep 2017 22:03:14 +0200 Subject: [PATCH 1/3] Move string and table wrappers into own files --- src/conversion.rs | 2 + src/lib.rs | 8 +- src/lua.rs | 518 +--------------------------------------------- src/string.rs | 81 ++++++++ src/table.rs | 429 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 529 insertions(+), 509 deletions(-) create mode 100644 src/string.rs create mode 100644 src/table.rs diff --git a/src/conversion.rs b/src/conversion.rs index 5a1a1bf..3569529 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -4,6 +4,8 @@ use std::string::String as StdString; use error::*; use lua::*; +use string::String; +use table::Table; impl<'lua> ToLua<'lua> for Value<'lua> { fn to_lua(self, _: &'lua Lua) -> Result> { diff --git a/src/lib.rs b/src/lib.rs index 1c4097e..a112cc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,14 +49,18 @@ mod error; mod lua; mod conversion; mod multi; +mod string; +mod table; #[cfg(test)] mod tests; pub use error::{Error, Result, ExternalError, ExternalResult}; pub use lua::{Value, Nil, ToLua, FromLua, MultiValue, ToLuaMulti, FromLuaMulti, Integer, Number, - LightUserData, String, Table, TablePairs, TableSequence, Function, ThreadStatus, - Thread, MetaMethod, UserDataMethods, UserData, AnyUserData, Lua}; + LightUserData, Function, ThreadStatus, Thread, + MetaMethod, UserDataMethods, UserData, AnyUserData, Lua}; pub use multi::Variadic; +pub use string::String; +pub use table::{Table, TablePairs, TableSequence}; pub mod prelude; diff --git a/src/lua.rs b/src/lua.rs index 9008b97..e1a790d 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,4 +1,4 @@ -use std::{fmt, ptr, slice, str}; +use std::{fmt, ptr, str}; use std::ops::{Deref, DerefMut}; use std::iter::FromIterator; use std::cell::{RefCell, Ref, RefMut}; @@ -16,6 +16,8 @@ use libc; use ffi; use error::*; use util::*; +use string::String; +use table::Table; /// A dynamically typed Lua value. #[derive(Debug, Clone)] @@ -145,9 +147,9 @@ pub trait FromLuaMulti<'lua>: Sized { type Callback<'lua> = Box) -> Result> + 'lua>; -struct LuaRef<'lua> { - lua: &'lua Lua, - registry_id: c_int, +pub(crate) struct LuaRef<'lua> { + pub lua: &'lua Lua, + pub registry_id: c_int, } impl<'lua> fmt::Debug for LuaRef<'lua> { @@ -182,504 +184,6 @@ pub type Number = ffi::lua_Number; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LightUserData(pub *mut c_void); -/// Handle to an internal Lua string. -/// -/// Unlike Rust strings, Lua strings may not be valid UTF-8. -#[derive(Clone, Debug)] -pub struct String<'lua>(LuaRef<'lua>); - -impl<'lua> String<'lua> { - /// Get a `&str` slice if the Lua string is valid UTF-8. - /// - /// # Examples - /// - /// ``` - /// # extern crate rlua; - /// # use rlua::{Lua, String, Result}; - /// # fn try_main() -> Result<()> { - /// let lua = Lua::new(); - /// let globals = lua.globals(); - /// - /// let version: String = globals.get("_VERSION")?; - /// assert!(version.to_str().unwrap().contains("Lua")); - /// - /// let non_utf8: String = lua.eval(r#" "test\xff" "#, None)?; - /// assert!(non_utf8.to_str().is_err()); - /// # Ok(()) - /// # } - /// # fn main() { - /// # try_main().unwrap(); - /// # } - /// ``` - pub fn to_str(&self) -> Result<&str> { - str::from_utf8(self.as_bytes()).map_err(|e| { - Error::FromLuaConversionError { - from: "string", - to: "&str", - message: Some(e.to_string()), - } - }) - } - - /// Get the bytes that make up this string. - /// - /// The returned slice will not contain the terminating null byte, but will contain any null - /// bytes embedded into the Lua string. - /// - /// # Examples - /// - /// ``` - /// # extern crate rlua; - /// # use rlua::{Lua, String}; - /// # fn main() { - /// let lua = Lua::new(); - /// - /// let non_utf8: String = lua.eval(r#" "test\xff" "#, None).unwrap(); - /// assert!(non_utf8.to_str().is_err()); // oh no :( - /// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]); - /// # } - /// ``` - pub fn as_bytes(&self) -> &[u8] { - let lua = self.0.lua; - unsafe { - stack_guard(lua.state, 0, || { - check_stack(lua.state, 1); - lua.push_ref(lua.state, &self.0); - assert_eq!(ffi::lua_type(lua.state, -1), ffi::LUA_TSTRING); - - let mut size = 0; - let data = ffi::lua_tolstring(lua.state, -1, &mut size); - - ffi::lua_pop(lua.state, 1); - slice::from_raw_parts(data as *const u8, size) - }) - } - } -} - -/// Handle to an internal Lua table. -#[derive(Clone, Debug)] -pub struct Table<'lua>(LuaRef<'lua>); - -impl<'lua> Table<'lua> { - /// Sets a key-value pair in the table. - /// - /// If the value is `nil`, this will effectively remove the pair. - /// - /// This might invoke the `__newindex` metamethod. Use the [`raw_set`] method if that is not - /// desired. - /// - /// # Examples - /// - /// Export a value as a global to make it usable from Lua: - /// - /// ``` - /// # extern crate rlua; - /// # use rlua::{Lua, Result}; - /// # fn try_main() -> Result<()> { - /// let lua = Lua::new(); - /// let globals = lua.globals(); - /// - /// globals.set("assertions", cfg!(debug_assertions))?; - /// - /// lua.exec::<()>(r#" - /// if assertions == true then - /// -- ... - /// elseif assertions == false then - /// -- ... - /// else - /// error("assertions neither on nor off?") - /// end - /// "#, None)?; - /// # Ok(()) - /// # } - /// # fn main() { - /// # try_main().unwrap(); - /// # } - /// ``` - /// - /// [`raw_set`]: #method.raw_set - pub fn set, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { - let lua = self.0.lua; - unsafe { - stack_err_guard(lua.state, 0, || { - check_stack(lua.state, 7); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - lua.push_value(lua.state, value.to_lua(lua)?); - psettable(lua.state, -3)?; - ffi::lua_pop(lua.state, 1); - Ok(()) - }) - } - } - - /// Gets the value associated to `key` from the table. - /// - /// If no value is associated to `key`, returns the `nil` value. - /// - /// This might invoke the `__index` metamethod. Use the [`raw_get`] method if that is not - /// desired. - /// - /// # Examples - /// - /// Query the version of the Lua interpreter: - /// - /// ``` - /// # extern crate rlua; - /// # use rlua::{Lua, Result}; - /// # fn try_main() -> Result<()> { - /// let lua = Lua::new(); - /// let globals = lua.globals(); - /// - /// let version: String = globals.get("_VERSION")?; - /// println!("Lua version: {}", version); - /// # Ok(()) - /// # } - /// # fn main() { - /// # try_main().unwrap(); - /// # } - /// ``` - /// - /// [`raw_get`]: #method.raw_get - pub fn get, V: FromLua<'lua>>(&self, key: K) -> Result { - let lua = self.0.lua; - unsafe { - stack_err_guard(lua.state, 0, || { - check_stack(lua.state, 5); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - pgettable(lua.state, -2)?; - let res = lua.pop_value(lua.state); - ffi::lua_pop(lua.state, 1); - V::from_lua(res, lua) - }) - } - } - - /// Checks whether the table contains a non-nil value for `key`. - pub fn contains_key>(&self, key: K) -> Result { - let lua = self.0.lua; - unsafe { - stack_err_guard(lua.state, 0, || { - check_stack(lua.state, 5); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - pgettable(lua.state, -2)?; - let has = ffi::lua_isnil(lua.state, -1) == 0; - ffi::lua_pop(lua.state, 2); - Ok(has) - }) - } - } - - /// Sets a key-value pair without invoking metamethods. - pub fn raw_set, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { - let lua = self.0.lua; - unsafe { - stack_err_guard(lua.state, 0, || { - check_stack(lua.state, 3); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - lua.push_value(lua.state, value.to_lua(lua)?); - ffi::lua_rawset(lua.state, -3); - ffi::lua_pop(lua.state, 1); - Ok(()) - }) - } - } - - /// Gets the value associated to `key` without invoking metamethods. - pub fn raw_get, V: FromLua<'lua>>(&self, key: K) -> Result { - let lua = self.0.lua; - unsafe { - stack_err_guard(lua.state, 0, || { - check_stack(lua.state, 2); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - ffi::lua_rawget(lua.state, -2); - let res = V::from_lua(lua.pop_value(lua.state), lua)?; - ffi::lua_pop(lua.state, 1); - Ok(res) - }) - } - } - - /// Returns the result of the Lua `#` operator. - /// - /// This might invoke the `__len` metamethod. Use the [`raw_len`] method if that is not desired. - /// - /// [`raw_len`]: #method.raw_len - pub fn len(&self) -> Result { - let lua = self.0.lua; - unsafe { - stack_err_guard(lua.state, 0, || { - check_stack(lua.state, 3); - lua.push_ref(lua.state, &self.0); - let len = plen(lua.state, -1)?; - ffi::lua_pop(lua.state, 1); - Ok(len) - }) - } - } - - /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod. - pub fn raw_len(&self) -> Integer { - let lua = self.0.lua; - unsafe { - stack_guard(lua.state, 0, || { - check_stack(lua.state, 1); - lua.push_ref(lua.state, &self.0); - let len = ffi::lua_rawlen(lua.state, -1); - ffi::lua_pop(lua.state, 1); - len as Integer - }) - } - } - - /// Returns a reference to the metatable of this table, or `None` if no metatable is set. - /// - /// Unlike the `getmetatable` Lua function, this method ignores the `__metatable` field. - pub fn get_metatable(&self) -> Option> { - let lua = self.0.lua; - unsafe { - stack_guard(lua.state, 0, || { - check_stack(lua.state, 1); - lua.push_ref(lua.state, &self.0); - if ffi::lua_getmetatable(lua.state, -1) == 0 { - ffi::lua_pop(lua.state, 1); - None - } else { - let table = Table(lua.pop_ref(lua.state)); - ffi::lua_pop(lua.state, 1); - Some(table) - } - }) - } - } - - /// Sets or removes the metatable of this table. - /// - /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does - /// nothing). - pub fn set_metatable(&self, metatable: Option>) { - let lua = self.0.lua; - unsafe { - stack_guard(lua.state, 0, move || { - check_stack(lua.state, 1); - lua.push_ref(lua.state, &self.0); - if let Some(metatable) = metatable { - lua.push_ref(lua.state, &metatable.0); - } else { - ffi::lua_pushnil(lua.state); - } - ffi::lua_setmetatable(lua.state, -2); - ffi::lua_pop(lua.state, 1); - }) - } - } - - /// Consume this table and return an iterator over the pairs of the table. - /// - /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod. - /// - /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types. - /// - /// # Note - /// - /// While this method consumes the `Table` object, it can not prevent code from mutating the - /// table while the iteration is in progress. Refer to the [Lua manual] for information about - /// the consequences of such mutation. - /// - /// # Examples - /// - /// Iterate over all globals: - /// - /// ``` - /// # extern crate rlua; - /// # use rlua::{Lua, Result, Value}; - /// # fn try_main() -> Result<()> { - /// let lua = Lua::new(); - /// let globals = lua.globals(); - /// - /// for pair in globals.pairs::() { - /// let (key, value) = pair?; - /// # let _ = (key, value); // used - /// // ... - /// } - /// # Ok(()) - /// # } - /// # fn main() { - /// # try_main().unwrap(); - /// # } - /// ``` - /// - /// [`Result`]: type.Result.html - /// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next - pub fn pairs, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> { - let next_key = Some(LuaRef { - lua: self.0.lua, - registry_id: ffi::LUA_REFNIL, - }); - - TablePairs { - table: self.0, - next_key, - _phantom: PhantomData, - } - } - - /// Consume this table and return an iterator over all values in the sequence part of the table. - /// - /// The iterator will yield all values `t[1]`, `t[2]`, and so on, until a `nil` value is - /// encountered. This mirrors the behaviour of Lua's `ipairs` function and will invoke the - /// `__index` metamethod according to the usual rules. However, the deprecated `__ipairs` - /// metatable will not be called. - /// - /// Just like [`pairs`], the values are wrapped in a [`Result`]. - /// - /// # Note - /// - /// While this method consumes the `Table` object, it can not prevent code from mutating the - /// table while the iteration is in progress. Refer to the [Lua manual] for information about - /// the consequences of such mutation. - /// - /// # Examples - /// - /// ``` - /// # extern crate rlua; - /// # use rlua::{Lua, Result, Table}; - /// # fn try_main() -> Result<()> { - /// let lua = Lua::new(); - /// let my_table: Table = lua.eval("{ [1] = 4, [2] = 5, [4] = 7, key = 2 }", None)?; - /// - /// let expected = [4, 5]; - /// for (&expected, got) in expected.iter().zip(my_table.sequence_values::()) { - /// assert_eq!(expected, got?); - /// } - /// # Ok(()) - /// # } - /// # fn main() { - /// # try_main().unwrap(); - /// # } - /// ``` - /// - /// [`pairs`]: #method.pairs - /// [`Result`]: type.Result.html - /// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next - pub fn sequence_values>(self) -> TableSequence<'lua, V> { - TableSequence { - table: self.0, - index: Some(1), - _phantom: PhantomData, - } - } -} - -/// An iterator over the pairs of a Lua table. -/// -/// This struct is created by the [`Table::pairs`] method. -/// -/// [`Table::pairs`]: struct.Table.html#method.pairs -pub struct TablePairs<'lua, K, V> { - table: LuaRef<'lua>, - next_key: Option>, - _phantom: PhantomData<(K, V)>, -} - -impl<'lua, K, V> Iterator for TablePairs<'lua, K, V> -where - K: FromLua<'lua>, - V: FromLua<'lua>, -{ - type Item = Result<(K, V)>; - - fn next(&mut self) -> Option { - if let Some(next_key) = self.next_key.take() { - let lua = self.table.lua; - - unsafe { - stack_guard(lua.state, 0, || { - check_stack(lua.state, 6); - - lua.push_ref(lua.state, &self.table); - lua.push_ref(lua.state, &next_key); - - match pnext(lua.state, -2) { - Ok(0) => { - ffi::lua_pop(lua.state, 1); - None - } - Ok(_) => { - ffi::lua_pushvalue(lua.state, -2); - let key = lua.pop_value(lua.state); - let value = lua.pop_value(lua.state); - self.next_key = Some(lua.pop_ref(lua.state)); - ffi::lua_pop(lua.state, 1); - - Some((|| { - let key = K::from_lua(key, lua)?; - let value = V::from_lua(value, lua)?; - Ok((key, value)) - })()) - } - Err(e) => Some(Err(e)), - } - }) - } - } else { - None - } - } -} - -/// An iterator over the sequence part of a Lua table. -/// -/// This struct is created by the [`Table::sequence_values`] method. -/// -/// [`Table::sequence_values`]: struct.Table.html#method.sequence_values -pub struct TableSequence<'lua, V> { - table: LuaRef<'lua>, - index: Option, - _phantom: PhantomData, -} - -impl<'lua, V> Iterator for TableSequence<'lua, V> -where - V: FromLua<'lua>, -{ - type Item = Result; - - fn next(&mut self) -> Option { - if let Some(index) = self.index.take() { - let lua = self.table.lua; - - unsafe { - stack_guard(lua.state, 0, || { - check_stack(lua.state, 4); - - lua.push_ref(lua.state, &self.table); - match pgeti(lua.state, -1, index) { - Ok(ffi::LUA_TNIL) => { - ffi::lua_pop(lua.state, 2); - None - } - Ok(_) => { - let value = lua.pop_value(lua.state); - ffi::lua_pop(lua.state, 1); - self.index = Some(index + 1); - Some(V::from_lua(value, lua)) - } - Err(err) => Some(Err(err)), - } - }) - } - } else { - None - } - } -} - /// Handle to an internal Lua function. #[derive(Clone, Debug)] pub struct Function<'lua>(LuaRef<'lua>); @@ -1351,7 +855,7 @@ impl<'lua> AnyUserData<'lua> { /// Top level Lua struct which holds the Lua state itself. pub struct Lua { - state: *mut ffi::lua_State, + pub(crate) state: *mut ffi::lua_State, main_state: *mut ffi::lua_State, ephemeral: bool, } @@ -1866,7 +1370,7 @@ impl Lua { } } - unsafe fn push_value(&self, state: *mut ffi::lua_State, value: Value) { + pub(crate) unsafe fn push_value(&self, state: *mut ffi::lua_State, value: Value) { match value { Value::Nil => { ffi::lua_pushnil(state); @@ -1914,7 +1418,7 @@ impl Lua { } } - unsafe fn pop_value(&self, state: *mut ffi::lua_State) -> Value { + pub(crate) unsafe fn pop_value(&self, state: *mut ffi::lua_State) -> Value { match ffi::lua_type(state, -1) { ffi::LUA_TNIL => { ffi::lua_pop(state, 1); @@ -1969,7 +1473,7 @@ impl Lua { } } - unsafe fn push_ref(&self, state: *mut ffi::lua_State, lref: &LuaRef) { + pub(crate) unsafe fn push_ref(&self, state: *mut ffi::lua_State, lref: &LuaRef) { assert_eq!( lref.lua.main_state, self.main_state, @@ -1988,7 +1492,7 @@ impl Lua { // // This pins the object, preventing garbage collection until the returned // `LuaRef` is dropped. - unsafe fn pop_ref(&self, state: *mut ffi::lua_State) -> LuaRef { + pub(crate) unsafe fn pop_ref(&self, state: *mut ffi::lua_State) -> LuaRef { let registry_id = ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); LuaRef { lua: self, diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 0000000..7d57027 --- /dev/null +++ b/src/string.rs @@ -0,0 +1,81 @@ +use std::{slice, str}; + +use ffi; +use lua::LuaRef; +use error::{Error, Result}; +use util::{check_stack, stack_guard}; + +/// Handle to an internal Lua string. +/// +/// Unlike Rust strings, Lua strings may not be valid UTF-8. +#[derive(Clone, Debug)] +pub struct String<'lua>(pub(crate) LuaRef<'lua>); + +impl<'lua> String<'lua> { + /// Get a `&str` slice if the Lua string is valid UTF-8. + /// + /// # Examples + /// + /// ``` + /// # extern crate rlua; + /// # use rlua::{Lua, String, Result}; + /// # fn try_main() -> Result<()> { + /// let lua = Lua::new(); + /// let globals = lua.globals(); + /// + /// let version: String = globals.get("_VERSION")?; + /// assert!(version.to_str().unwrap().contains("Lua")); + /// + /// let non_utf8: String = lua.eval(r#" "test\xff" "#, None)?; + /// assert!(non_utf8.to_str().is_err()); + /// # Ok(()) + /// # } + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + pub fn to_str(&self) -> Result<&str> { + str::from_utf8(self.as_bytes()).map_err(|e| { + Error::FromLuaConversionError { + from: "string", + to: "&str", + message: Some(e.to_string()), + } + }) + } + + /// Get the bytes that make up this string. + /// + /// The returned slice will not contain the terminating null byte, but will contain any null + /// bytes embedded into the Lua string. + /// + /// # Examples + /// + /// ``` + /// # extern crate rlua; + /// # use rlua::{Lua, String}; + /// # fn main() { + /// let lua = Lua::new(); + /// + /// let non_utf8: String = lua.eval(r#" "test\xff" "#, None).unwrap(); + /// assert!(non_utf8.to_str().is_err()); // oh no :( + /// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]); + /// # } + /// ``` + pub fn as_bytes(&self) -> &[u8] { + let lua = self.0.lua; + unsafe { + stack_guard(lua.state, 0, || { + check_stack(lua.state, 1); + lua.push_ref(lua.state, &self.0); + assert_eq!(ffi::lua_type(lua.state, -1), ffi::LUA_TSTRING); + + let mut size = 0; + let data = ffi::lua_tolstring(lua.state, -1, &mut size); + + ffi::lua_pop(lua.state, 1); + slice::from_raw_parts(data as *const u8, size) + }) + } + } +} diff --git a/src/table.rs b/src/table.rs new file mode 100644 index 0000000..22a50eb --- /dev/null +++ b/src/table.rs @@ -0,0 +1,429 @@ +use std::marker::PhantomData; + +use ffi; +use lua::{LuaRef, ToLua, FromLua, Integer}; +use util::*; +use error::Result; + +/// Handle to an internal Lua table. +#[derive(Clone, Debug)] +pub struct Table<'lua>(pub(crate) LuaRef<'lua>); + +impl<'lua> Table<'lua> { + /// Sets a key-value pair in the table. + /// + /// If the value is `nil`, this will effectively remove the pair. + /// + /// This might invoke the `__newindex` metamethod. Use the [`raw_set`] method if that is not + /// desired. + /// + /// # Examples + /// + /// Export a value as a global to make it usable from Lua: + /// + /// ``` + /// # extern crate rlua; + /// # use rlua::{Lua, Result}; + /// # fn try_main() -> Result<()> { + /// let lua = Lua::new(); + /// let globals = lua.globals(); + /// + /// globals.set("assertions", cfg!(debug_assertions))?; + /// + /// lua.exec::<()>(r#" + /// if assertions == true then + /// -- ... + /// elseif assertions == false then + /// -- ... + /// else + /// error("assertions neither on nor off?") + /// end + /// "#, None)?; + /// # Ok(()) + /// # } + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// + /// [`raw_set`]: #method.raw_set + pub fn set, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { + let lua = self.0.lua; + unsafe { + stack_err_guard(lua.state, 0, || { + check_stack(lua.state, 7); + lua.push_ref(lua.state, &self.0); + lua.push_value(lua.state, key.to_lua(lua)?); + lua.push_value(lua.state, value.to_lua(lua)?); + psettable(lua.state, -3)?; + ffi::lua_pop(lua.state, 1); + Ok(()) + }) + } + } + + /// Gets the value associated to `key` from the table. + /// + /// If no value is associated to `key`, returns the `nil` value. + /// + /// This might invoke the `__index` metamethod. Use the [`raw_get`] method if that is not + /// desired. + /// + /// # Examples + /// + /// Query the version of the Lua interpreter: + /// + /// ``` + /// # extern crate rlua; + /// # use rlua::{Lua, Result}; + /// # fn try_main() -> Result<()> { + /// let lua = Lua::new(); + /// let globals = lua.globals(); + /// + /// let version: String = globals.get("_VERSION")?; + /// println!("Lua version: {}", version); + /// # Ok(()) + /// # } + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// + /// [`raw_get`]: #method.raw_get + pub fn get, V: FromLua<'lua>>(&self, key: K) -> Result { + let lua = self.0.lua; + unsafe { + stack_err_guard(lua.state, 0, || { + check_stack(lua.state, 5); + lua.push_ref(lua.state, &self.0); + lua.push_value(lua.state, key.to_lua(lua)?); + pgettable(lua.state, -2)?; + let res = lua.pop_value(lua.state); + ffi::lua_pop(lua.state, 1); + V::from_lua(res, lua) + }) + } + } + + /// Checks whether the table contains a non-nil value for `key`. + pub fn contains_key>(&self, key: K) -> Result { + let lua = self.0.lua; + unsafe { + stack_err_guard(lua.state, 0, || { + check_stack(lua.state, 5); + lua.push_ref(lua.state, &self.0); + lua.push_value(lua.state, key.to_lua(lua)?); + pgettable(lua.state, -2)?; + let has = ffi::lua_isnil(lua.state, -1) == 0; + ffi::lua_pop(lua.state, 2); + Ok(has) + }) + } + } + + /// Sets a key-value pair without invoking metamethods. + pub fn raw_set, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { + let lua = self.0.lua; + unsafe { + stack_err_guard(lua.state, 0, || { + check_stack(lua.state, 3); + lua.push_ref(lua.state, &self.0); + lua.push_value(lua.state, key.to_lua(lua)?); + lua.push_value(lua.state, value.to_lua(lua)?); + ffi::lua_rawset(lua.state, -3); + ffi::lua_pop(lua.state, 1); + Ok(()) + }) + } + } + + /// Gets the value associated to `key` without invoking metamethods. + pub fn raw_get, V: FromLua<'lua>>(&self, key: K) -> Result { + let lua = self.0.lua; + unsafe { + stack_err_guard(lua.state, 0, || { + check_stack(lua.state, 2); + lua.push_ref(lua.state, &self.0); + lua.push_value(lua.state, key.to_lua(lua)?); + ffi::lua_rawget(lua.state, -2); + let res = V::from_lua(lua.pop_value(lua.state), lua)?; + ffi::lua_pop(lua.state, 1); + Ok(res) + }) + } + } + + /// Returns the result of the Lua `#` operator. + /// + /// This might invoke the `__len` metamethod. Use the [`raw_len`] method if that is not desired. + /// + /// [`raw_len`]: #method.raw_len + pub fn len(&self) -> Result { + let lua = self.0.lua; + unsafe { + stack_err_guard(lua.state, 0, || { + check_stack(lua.state, 3); + lua.push_ref(lua.state, &self.0); + let len = plen(lua.state, -1)?; + ffi::lua_pop(lua.state, 1); + Ok(len) + }) + } + } + + /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod. + pub fn raw_len(&self) -> Integer { + let lua = self.0.lua; + unsafe { + stack_guard(lua.state, 0, || { + check_stack(lua.state, 1); + lua.push_ref(lua.state, &self.0); + let len = ffi::lua_rawlen(lua.state, -1); + ffi::lua_pop(lua.state, 1); + len as Integer + }) + } + } + + /// Returns a reference to the metatable of this table, or `None` if no metatable is set. + /// + /// Unlike the `getmetatable` Lua function, this method ignores the `__metatable` field. + pub fn get_metatable(&self) -> Option> { + let lua = self.0.lua; + unsafe { + stack_guard(lua.state, 0, || { + check_stack(lua.state, 1); + lua.push_ref(lua.state, &self.0); + if ffi::lua_getmetatable(lua.state, -1) == 0 { + ffi::lua_pop(lua.state, 1); + None + } else { + let table = Table(lua.pop_ref(lua.state)); + ffi::lua_pop(lua.state, 1); + Some(table) + } + }) + } + } + + /// Sets or removes the metatable of this table. + /// + /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does + /// nothing). + pub fn set_metatable(&self, metatable: Option>) { + let lua = self.0.lua; + unsafe { + stack_guard(lua.state, 0, move || { + check_stack(lua.state, 1); + lua.push_ref(lua.state, &self.0); + if let Some(metatable) = metatable { + lua.push_ref(lua.state, &metatable.0); + } else { + ffi::lua_pushnil(lua.state); + } + ffi::lua_setmetatable(lua.state, -2); + ffi::lua_pop(lua.state, 1); + }) + } + } + + /// Consume this table and return an iterator over the pairs of the table. + /// + /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod. + /// + /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types. + /// + /// # Note + /// + /// While this method consumes the `Table` object, it can not prevent code from mutating the + /// table while the iteration is in progress. Refer to the [Lua manual] for information about + /// the consequences of such mutation. + /// + /// # Examples + /// + /// Iterate over all globals: + /// + /// ``` + /// # extern crate rlua; + /// # use rlua::{Lua, Result, Value}; + /// # fn try_main() -> Result<()> { + /// let lua = Lua::new(); + /// let globals = lua.globals(); + /// + /// for pair in globals.pairs::() { + /// let (key, value) = pair?; + /// # let _ = (key, value); // used + /// // ... + /// } + /// # Ok(()) + /// # } + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// + /// [`Result`]: type.Result.html + /// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next + pub fn pairs, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> { + let next_key = Some(LuaRef { + lua: self.0.lua, + registry_id: ffi::LUA_REFNIL, + }); + + TablePairs { + table: self.0, + next_key, + _phantom: PhantomData, + } + } + + /// Consume this table and return an iterator over all values in the sequence part of the table. + /// + /// The iterator will yield all values `t[1]`, `t[2]`, and so on, until a `nil` value is + /// encountered. This mirrors the behaviour of Lua's `ipairs` function and will invoke the + /// `__index` metamethod according to the usual rules. However, the deprecated `__ipairs` + /// metatable will not be called. + /// + /// Just like [`pairs`], the values are wrapped in a [`Result`]. + /// + /// # Note + /// + /// While this method consumes the `Table` object, it can not prevent code from mutating the + /// table while the iteration is in progress. Refer to the [Lua manual] for information about + /// the consequences of such mutation. + /// + /// # Examples + /// + /// ``` + /// # extern crate rlua; + /// # use rlua::{Lua, Result, Table}; + /// # fn try_main() -> Result<()> { + /// let lua = Lua::new(); + /// let my_table: Table = lua.eval("{ [1] = 4, [2] = 5, [4] = 7, key = 2 }", None)?; + /// + /// let expected = [4, 5]; + /// for (&expected, got) in expected.iter().zip(my_table.sequence_values::()) { + /// assert_eq!(expected, got?); + /// } + /// # Ok(()) + /// # } + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + /// + /// [`pairs`]: #method.pairs + /// [`Result`]: type.Result.html + /// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next + pub fn sequence_values>(self) -> TableSequence<'lua, V> { + TableSequence { + table: self.0, + index: Some(1), + _phantom: PhantomData, + } + } +} + +/// An iterator over the pairs of a Lua table. +/// +/// This struct is created by the [`Table::pairs`] method. +/// +/// [`Table::pairs`]: struct.Table.html#method.pairs +pub struct TablePairs<'lua, K, V> { + table: LuaRef<'lua>, + next_key: Option>, + _phantom: PhantomData<(K, V)>, +} + +impl<'lua, K, V> Iterator for TablePairs<'lua, K, V> + where + K: FromLua<'lua>, + V: FromLua<'lua>, +{ + type Item = Result<(K, V)>; + + fn next(&mut self) -> Option { + if let Some(next_key) = self.next_key.take() { + let lua = self.table.lua; + + unsafe { + stack_guard(lua.state, 0, || { + check_stack(lua.state, 6); + + lua.push_ref(lua.state, &self.table); + lua.push_ref(lua.state, &next_key); + + match pnext(lua.state, -2) { + Ok(0) => { + ffi::lua_pop(lua.state, 1); + None + } + Ok(_) => { + ffi::lua_pushvalue(lua.state, -2); + let key = lua.pop_value(lua.state); + let value = lua.pop_value(lua.state); + self.next_key = Some(lua.pop_ref(lua.state)); + ffi::lua_pop(lua.state, 1); + + Some((|| { + let key = K::from_lua(key, lua)?; + let value = V::from_lua(value, lua)?; + Ok((key, value)) + })()) + } + Err(e) => Some(Err(e)), + } + }) + } + } else { + None + } + } +} + +/// An iterator over the sequence part of a Lua table. +/// +/// This struct is created by the [`Table::sequence_values`] method. +/// +/// [`Table::sequence_values`]: struct.Table.html#method.sequence_values +pub struct TableSequence<'lua, V> { + table: LuaRef<'lua>, + index: Option, + _phantom: PhantomData, +} + +impl<'lua, V> Iterator for TableSequence<'lua, V> + where + V: FromLua<'lua>, +{ + type Item = Result; + + fn next(&mut self) -> Option { + if let Some(index) = self.index.take() { + let lua = self.table.lua; + + unsafe { + stack_guard(lua.state, 0, || { + check_stack(lua.state, 4); + + lua.push_ref(lua.state, &self.table); + match pgeti(lua.state, -1, index) { + Ok(ffi::LUA_TNIL) => { + ffi::lua_pop(lua.state, 2); + None + } + Ok(_) => { + let value = lua.pop_value(lua.state); + ffi::lua_pop(lua.state, 1); + self.index = Some(index + 1); + Some(V::from_lua(value, lua)) + } + Err(err) => Some(Err(err)), + } + }) + } + } else { + None + } + } +} From 0e4414fc0b48414ae6b688c4c4b5dbe5098ad17e Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Fri, 15 Sep 2017 23:10:53 +0200 Subject: [PATCH 2/3] Impl `AsRef<[u8]>` and generic `PartialEq` for `String` Tests are also moved to the new string.rs file to ensure related functionality is in one place. --- src/string.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/tests.rs | 28 ------------------- 2 files changed, 74 insertions(+), 28 deletions(-) diff --git a/src/string.rs b/src/string.rs index 7d57027..b6c48ee 100644 --- a/src/string.rs +++ b/src/string.rs @@ -79,3 +79,77 @@ impl<'lua> String<'lua> { } } } + +impl<'lua> AsRef<[u8]> for String<'lua> { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +// Lua strings are basically &[u8] slices, so implement PartialEq for anything resembling that. +// +// This makes our `String` comparable with `Vec`, `[u8]`, `&str`, `String` and `rlua::String` +// itself. +// +// The only downside is that this disallows a comparison with `Cow`, as that only implements +// `AsRef`, which collides with this impl. Requiring `AsRef` would fix that, but limit us +// in other ways. +impl<'lua, T> PartialEq for String<'lua> where T: AsRef<[u8]> { + fn eq(&self, other: &T) -> bool { + self.as_bytes() == other.as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use Lua; + + use std::borrow::Cow; + + fn with_str(s: &str, f: F) where F: FnOnce(String) { + let lua = Lua::new(); + let string = lua.create_string(s); + f(string); + } + + #[test] + fn compare() { + // Tests that all comparisons we want to have are usable + with_str("teststring", |t| assert_eq!(t, "teststring")); // &str + with_str("teststring", |t| assert_eq!(t, b"teststring")); // &[u8] + with_str("teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec + with_str("teststring", |t| assert_eq!(t, "teststring".to_string())); // String + with_str("teststring", |t| assert_eq!(t, t)); // rlua::String + with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed) + with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned) + } + + #[test] + fn string_views() { + let lua = Lua::new(); + lua.eval::<()>( + r#" + ok = "null bytes are valid utf-8, wh\0 knew?" + err = "but \xff isn't :(" + "#, + None, + ).unwrap(); + + let globals = lua.globals(); + let ok: String = globals.get("ok").unwrap(); + let err: String = globals.get("err").unwrap(); + + assert_eq!( + ok.to_str().unwrap(), + "null bytes are valid utf-8, wh\0 knew?" + ); + assert_eq!( + ok.as_bytes(), + &b"null bytes are valid utf-8, wh\0 knew?"[..] + ); + + assert!(err.to_str().is_err()); + assert_eq!(err.as_bytes(), &b"but \xff isn't :("[..]); + } +} diff --git a/src/tests.rs b/src/tests.rs index c3d1321..f9d29ac 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -843,34 +843,6 @@ fn detroys_userdata() { assert_eq!(DROPPED.load(Ordering::SeqCst), true); } -#[test] -fn string_views() { - let lua = Lua::new(); - lua.eval::<()>( - r#" - ok = "null bytes are valid utf-8, wh\0 knew?" - err = "but \xff isn't :(" - "#, - None, - ).unwrap(); - - let globals = lua.globals(); - let ok: LuaString = globals.get("ok").unwrap(); - let err: LuaString = globals.get("err").unwrap(); - - assert_eq!( - ok.to_str().unwrap(), - "null bytes are valid utf-8, wh\0 knew?" - ); - assert_eq!( - ok.as_bytes(), - &b"null bytes are valid utf-8, wh\0 knew?"[..] - ); - - assert!(err.to_str().is_err()); - assert_eq!(err.as_bytes(), &b"but \xff isn't :("[..]); -} - #[test] fn coroutine_from_closure() { let lua = Lua::new(); From a5b6d8fc85f224b1d15417fca6a27b94945e9cd7 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Fri, 15 Sep 2017 23:33:28 +0200 Subject: [PATCH 3/3] Add `String::as_bytes_with_nul` This cannot be accomplished without using unsafe code, which justifies this addition in my opinion. Also changes "null" to "nul" to be in sync with `std::ffi` docs. Naming is derived from `CStr::to_bytes_with_nul`, using `as_*` instead of `to_*` since this isn't doing any computation. --- src/string.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/string.rs b/src/string.rs index b6c48ee..f7c7ec4 100644 --- a/src/string.rs +++ b/src/string.rs @@ -46,7 +46,7 @@ impl<'lua> String<'lua> { /// Get the bytes that make up this string. /// - /// The returned slice will not contain the terminating null byte, but will contain any null + /// The returned slice will not contain the terminating nul byte, but will contain any nul /// bytes embedded into the Lua string. /// /// # Examples @@ -63,6 +63,12 @@ impl<'lua> String<'lua> { /// # } /// ``` pub fn as_bytes(&self) -> &[u8] { + let nulled = self.as_bytes_with_nul(); + &nulled[..nulled.len()-1] + } + + /// Get the bytes that make up this string, including the trailing nul byte. + pub fn as_bytes_with_nul(&self) -> &[u8] { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { @@ -74,7 +80,7 @@ impl<'lua> String<'lua> { let data = ffi::lua_tolstring(lua.state, -1, &mut size); ffi::lua_pop(lua.state, 1); - slice::from_raw_parts(data as *const u8, size) + slice::from_raw_parts(data as *const u8, size + 1) }) } } @@ -132,6 +138,7 @@ mod tests { r#" ok = "null bytes are valid utf-8, wh\0 knew?" err = "but \xff isn't :(" + empty = "" "#, None, ).unwrap(); @@ -139,6 +146,7 @@ mod tests { let globals = lua.globals(); let ok: String = globals.get("ok").unwrap(); let err: String = globals.get("err").unwrap(); + let empty: String = globals.get("empty").unwrap(); assert_eq!( ok.to_str().unwrap(), @@ -151,5 +159,9 @@ mod tests { assert!(err.to_str().is_err()); assert_eq!(err.as_bytes(), &b"but \xff isn't :("[..]); + + assert_eq!(empty.to_str().unwrap(), ""); + assert_eq!(empty.as_bytes_with_nul(), &[0]); + assert_eq!(empty.as_bytes(), &[]); } }