diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 3dd3bab..afa8b54 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -62,6 +62,8 @@ pub fn chunk(input: TokenStream) -> TokenStream { let wrapped_code = quote! {{ use ::mlua::{AsChunk, ChunkMode, Lua, Result, Value}; + use ::std::borrow::Cow; + use ::std::io::Result as IoResult; use ::std::marker::PhantomData; use ::std::sync::Mutex; @@ -73,8 +75,8 @@ pub fn chunk(input: TokenStream) -> TokenStream { where F: FnOnce(&'lua Lua) -> Result>, { - fn source(&self) -> &[u8] { - (#source).as_bytes() + fn source(&self) -> IoResult> { + Ok(Cow::Borrowed((#source).as_bytes())) } fn env(&self, lua: &'lua Lua) -> Result>> { diff --git a/src/chunk.rs b/src/chunk.rs index 44076e1..050475d 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,5 +1,8 @@ use std::borrow::Cow; use std::ffi::CString; +use std::io::Result as IoResult; +use std::path::{Path, PathBuf}; +use std::string::String as StdString; use crate::error::{Error, Result}; use crate::ffi; @@ -16,10 +19,10 @@ use {futures_core::future::LocalBoxFuture, futures_util::future}; /// [`Chunk`]: crate::Chunk pub trait AsChunk<'lua> { /// Returns chunk data (can be text or binary) - fn source(&self) -> &[u8]; + fn source(&self) -> IoResult>; /// Returns optional chunk name - fn name(&self) -> Option { + fn name(&self) -> Option { None } @@ -36,9 +39,47 @@ pub trait AsChunk<'lua> { } } -impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T { - fn source(&self) -> &[u8] { - self.as_ref() +impl<'lua> AsChunk<'lua> for str { + fn source(&self) -> IoResult> { + Ok(Cow::Borrowed(self.as_ref())) + } +} + +impl<'lua> AsChunk<'lua> for StdString { + fn source(&self) -> IoResult> { + Ok(Cow::Borrowed(self.as_ref())) + } +} + +impl<'lua> AsChunk<'lua> for [u8] { + fn source(&self) -> IoResult> { + Ok(Cow::Borrowed(self)) + } +} + +impl<'lua> AsChunk<'lua> for Vec { + fn source(&self) -> IoResult> { + Ok(Cow::Borrowed(self)) + } +} + +impl<'lua> AsChunk<'lua> for Path { + fn source(&self) -> IoResult> { + std::fs::read(self).map(Cow::Owned) + } + + fn name(&self) -> Option { + Some(format!("@{}", self.display())) + } +} + +impl<'lua> AsChunk<'lua> for PathBuf { + fn source(&self) -> IoResult> { + std::fs::read(self).map(Cow::Owned) + } + + fn name(&self) -> Option { + Some(format!("@{}", self.display())) } } @@ -48,8 +89,8 @@ impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T { #[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] pub struct Chunk<'lua, 'a> { pub(crate) lua: &'lua Lua, - pub(crate) source: Cow<'a, [u8]>, - pub(crate) name: Option, + pub(crate) source: IoResult>, + pub(crate) name: Option, pub(crate) env: Result>>, pub(crate) mode: Option, #[cfg(feature = "luau")] @@ -174,7 +215,7 @@ impl Compiler { .map(|s| s.as_ptr()) .collect::>(); let mut mutable_globals_ptr = ptr::null_mut(); - if mutable_globals.len() > 0 { + if !mutable_globals.is_empty() { mutable_globals.push(ptr::null()); mutable_globals_ptr = mutable_globals.as_mut_ptr(); } @@ -195,14 +236,10 @@ impl Compiler { impl<'lua, 'a> Chunk<'lua, 'a> { /// Sets the name of this chunk, which results in more informative error traces. - pub fn set_name + ?Sized>(mut self, name: &S) -> Result { - let name = - CString::new(name.as_ref().to_vec()).map_err(|e| Error::ToLuaConversionError { - from: "&str", - to: "string", - message: Some(e.to_string()), - })?; - self.name = Some(name); + pub fn set_name(mut self, name: impl AsRef) -> Result { + self.name = Some(name.as_ref().to_string()); + // Do extra validation + let _ = self.convert_name()?; Ok(self) } @@ -244,23 +281,6 @@ impl<'lua, 'a> Chunk<'lua, 'a> { self } - /// Compiles the chunk and changes mode to binary. - /// - /// It does nothing if the chunk is already binary. - #[cfg(feature = "luau")] - #[doc(hidden)] - pub fn compile(mut self) -> Self { - if self.detect_mode() == ChunkMode::Text { - let data = self - .compiler - .get_or_insert_with(Default::default) - .compile(self.source.as_ref()); - self.mode = Some(ChunkMode::Binary); - self.source = Cow::Owned(data); - } - self - } - /// Execute this chunk of code. /// /// This is equivalent to calling the chunk function with no arguments and no return values. @@ -358,38 +378,41 @@ impl<'lua, 'a> Chunk<'lua, 'a> { /// Load this chunk into a regular `Function`. /// /// This simply compiles the chunk without actually executing it. - pub fn into_function(self) -> Result> { - #[cfg(not(feature = "luau"))] - let self_ = self; + #[cfg_attr(not(feature = "luau"), allow(unused_mut))] + pub fn into_function(mut self) -> Result> { #[cfg(feature = "luau")] - let self_ = match self.compiler { - // We don't need to compile source if no compiler options set - Some(_) => self.compile(), - _ => self, - }; + if self.compiler.is_some() { + // We don't need to compile source if no compiler set + self.compile(); + } - self_.lua.load_chunk( - self_.source.as_ref(), - self_.name.as_ref(), - self_.env()?, - self_.mode, - ) + let name = self.convert_name()?; + self.lua + .load_chunk(self.source?.as_ref(), name.as_deref(), self.env?, self.mode) } - fn env(&self) -> Result>> { - self.env.clone() - } - - fn expression_source(&self) -> Vec { - let mut buf = Vec::with_capacity(b"return ".len() + self.source.len()); - buf.extend(b"return "); - buf.extend(self.source.as_ref()); - buf + /// Compiles the chunk and changes mode to binary. + /// + /// It does nothing if the chunk is already binary. + #[cfg(feature = "luau")] + fn compile(&mut self) { + if let Ok(ref source) = self.source { + if self.detect_mode() == ChunkMode::Text { + let data = self + .compiler + .get_or_insert_with(Default::default) + .compile(source); + self.mode = Some(ChunkMode::Binary); + self.source = Ok(Cow::Owned(data)); + } + } } fn to_expression(&self) -> Result> { // We assume that mode is Text - let source = self.expression_source(); + let source = self.source.as_ref(); + let source = source.map_err(|err| Error::RuntimeError(err.to_string()))?; + let source = Self::expression_source(source); // We don't need to compile source if no compiler options set #[cfg(feature = "luau")] let source = self @@ -398,24 +421,42 @@ impl<'lua, 'a> Chunk<'lua, 'a> { .map(|c| c.compile(&source)) .unwrap_or(source); + let name = self.convert_name()?; self.lua - .load_chunk(&source, self.name.as_ref(), self.env()?, None) + .load_chunk(&source, name.as_deref(), self.env.clone()?, None) } fn detect_mode(&self) -> ChunkMode { - match self.mode { - Some(mode) => mode, - None => { + match (self.mode, &self.source) { + (Some(mode), _) => mode, + (None, Ok(source)) if source.len() == 0 => ChunkMode::Text, + (None, Ok(source)) => { #[cfg(not(feature = "luau"))] - if self.source.starts_with(ffi::LUA_SIGNATURE) { + if source.starts_with(ffi::LUA_SIGNATURE) { return ChunkMode::Binary; } #[cfg(feature = "luau")] - if self.source[0] < b'\n' { + if source[0] < b'\n' { return ChunkMode::Binary; } ChunkMode::Text } + (None, Err(_)) => ChunkMode::Text, // any value is fine } } + + fn convert_name(&self) -> Result> { + self.name + .clone() + .map(CString::new) + .transpose() + .map_err(|err| Error::RuntimeError(format!("invalid name: {err}"))) + } + + fn expression_source(source: &[u8]) -> Vec { + let mut buf = Vec::with_capacity(b"return ".len() + source.len()); + buf.extend(b"return "); + buf.extend(source); + buf + } } diff --git a/src/lua.rs b/src/lua.rs index be7b907..773281a 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,8 +1,7 @@ use std::any::{Any, TypeId}; -use std::borrow::Cow; use std::cell::{Ref, RefCell, RefMut, UnsafeCell}; use std::collections::HashMap; -use std::ffi::CString; +use std::ffi::{CStr, CString}; use std::fmt; use std::marker::PhantomData; use std::mem::ManuallyDrop; @@ -41,10 +40,7 @@ use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Va #[cfg(not(feature = "lua54"))] use crate::util::push_userdata; #[cfg(feature = "lua54")] -use { - crate::{types::WarnCallback, userdata::USER_VALUE_MAXSLOT, util::push_userdata_uv}, - std::ffi::CStr, -}; +use crate::{types::WarnCallback, userdata::USER_VALUE_MAXSLOT, util::push_userdata_uv}; #[cfg(not(feature = "luau"))] use crate::{hook::HookTriggers, types::HookCallback}; @@ -1359,19 +1355,20 @@ impl Lua { /// /// [`Chunk::exec`]: crate::Chunk::exec #[track_caller] - pub fn load<'lua, 'a, S>(&'lua self, source: &'a S) -> Chunk<'lua, 'a> + pub fn load<'lua, 'a, S>(&'lua self, chunk: &'a S) -> Chunk<'lua, 'a> where S: AsChunk<'lua> + ?Sized, { + let name = chunk + .name() + .unwrap_or_else(|| Location::caller().to_string()); + Chunk { lua: self, - source: Cow::Borrowed(source.source()), - name: match source.name() { - Some(name) => Some(name), - None => CString::new(Location::caller().to_string()).ok(), - }, - env: source.env(self), - mode: source.mode(), + source: chunk.source(), + name: Some(name), + env: chunk.env(self), + mode: chunk.mode(), #[cfg(feature = "luau")] compiler: self.compiler.clone(), } @@ -1380,7 +1377,7 @@ impl Lua { pub(crate) fn load_chunk<'lua>( &'lua self, source: &[u8], - name: Option<&CString>, + name: Option<&CStr>, env: Option>, mode: Option, ) -> Result> { diff --git a/tests/chunk.rs b/tests/chunk.rs new file mode 100644 index 0000000..4f87d60 --- /dev/null +++ b/tests/chunk.rs @@ -0,0 +1,54 @@ +use std::fs; +use std::io; + +use mlua::{Error, Lua, Result}; + +#[test] +fn test_chunk_path() -> Result<()> { + let lua = Lua::new(); + + let temp_dir = tempfile::tempdir().unwrap(); + fs::write( + temp_dir.path().join("module.lua"), + r#" + return 321 + "#, + )?; + let i: i32 = lua.load(&temp_dir.path().join("module.lua")).eval()?; + assert_eq!(i, 321); + + match lua.load(&temp_dir.path().join("module2.lua")).exec() { + Err(Error::ExternalError(err)) + if err.downcast_ref::().unwrap().kind() == io::ErrorKind::NotFound => {} + res => panic!("expected io::Error, got {:?}", res), + }; + + Ok(()) +} + +#[test] +#[cfg(feature = "macros")] +fn test_chunk_macro() -> Result<()> { + let lua = Lua::new(); + + let name = "Rustacean"; + let table = vec![1]; + + let data = lua.create_table()?; + data.raw_set("num", 1)?; + + lua.globals().set("g", 123)?; + + lua.load(mlua::chunk! { + assert($name == "Rustacean") + assert($table[1] == 1) + assert($data.num == 1) + assert(g == 123) + s = 321 + }) + .exec()?; + + assert_eq!(lua.globals().get::<_, i32>("s")?, 321); + + Ok(()) +} diff --git a/tests/macros.rs b/tests/macros.rs deleted file mode 100644 index fe16cc3..0000000 --- a/tests/macros.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![cfg(feature = "macros")] - -use mlua::{chunk, Lua, Result}; - -#[test] -fn test_chunk_macro() -> Result<()> { - let lua = Lua::new(); - - let name = "Rustacean"; - let table = vec![1]; - - let data = lua.create_table()?; - data.raw_set("num", 1)?; - - lua.globals().set("g", 123)?; - - lua.load(chunk! { - assert($name == "Rustacean") - assert($table[1] == 1) - assert($data.num == 1) - assert(g == 123) - s = 321 - }) - .exec()?; - - assert_eq!(lua.globals().get::<_, i32>("s")?, 321); - - Ok(()) -}