Compare commits

...

13 Commits

Author SHA1 Message Date
Alex Orlenko 93d63cef35 v0.4.2 2020-08-17 12:17:08 +01:00
Alex Orlenko b743245aba Update CHANGELOG 2020-08-17 11:14:40 +01:00
Alex Orlenko a172c021c9 Update set_memory_limit doc 2020-07-30 12:16:25 +01:00
Alex Orlenko de2c5cd9a9 Fix compilation warnings on nightly rust 2020-07-28 21:10:55 +01:00
Alex Orlenko d201beadc9 Add ChunkMode enum to mark chunks as text or binary 2020-07-28 21:04:21 +01:00
Alex Orlenko dd58cdad52 Add Function::dump() to dump lua function to a binary chunk 2020-07-27 23:26:33 +01:00
Alex Orlenko 5c8a5e0a5a Merge pull request #9 from HybridEidolon/bytecode-chunks
Make Lua::load load binary chunks when unsafe
2020-07-27 14:19:33 +01:00
Alex Orlenko e07c53eafe Update compile tests (2) 2020-07-27 13:51:21 +01:00
Alex Orlenko ad619390e1 Run compile tests on macos (was ubuntu-18.04) 2020-07-27 11:34:48 +01:00
Alex Orlenko 350602ab6e Update lua-src dependency to 5.4.0 2020-07-27 10:52:28 +01:00
Alex Orlenko 4b1bc88273 Update compile tests 2020-07-27 10:49:01 +01:00
Eidolon 883bf082b9 Make Lua::load load binary chunks when unsafe 2020-07-27 03:37:38 -05:00
Alex Orlenko 4265250cfd 0.4.1 release
Fix docs.rs build features
Update Cargo.toml description
2020-06-08 14:08:07 +01:00
14 changed files with 283 additions and 137 deletions
+2 -2
View File
@@ -65,8 +65,8 @@ jobs:
cargo test --release --features "${{ matrix.lua }} vendored"
cargo test --release --features "${{ matrix.lua }} vendored async send"
shell: bash
- name: Run compile tests
if: ${{ matrix.os == 'ubuntu-18.04' && matrix.lua == 'lua53' }}
- name: Run compile tests (macos lua53)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua53' }}
run: |
cargo test --release --features "${{ matrix.lua }} vendored" -- --ignored
cargo test --release --features "${{ matrix.lua }} vendored async send" -- --ignored
+6
View File
@@ -1,3 +1,9 @@
## v0.4.2
- Added `Function::dump()` to dump lua function to a binary chunk
- Added `ChunkMode` enum to mark chunks as text or binary
- Updated `set_memory_limit` doc
## v0.4.0
- Lua 5.4 support with `MetaMethod::Close`.
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.4.0"
version = "0.4.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
@@ -12,7 +12,7 @@ license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.1/5.2/5.3/5.4 (including LuaJIT)
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT)
with async/await features and support of writing native lua modules in Rust.
"""
@@ -21,7 +21,7 @@ with async/await features and support of writing native lua modules in Rust.
maintenance = { status = "actively-developed" }
[package.metadata.docs.rs]
features = ["async"]
features = ["async", "send", "lua53"]
[workspace]
members = [
@@ -52,7 +52,7 @@ futures-util = { version = "0.3.5", optional = true }
[build-dependencies]
cc = { version = "1.0" }
pkg-config = { version = "0.3.17" }
lua-src = { version = "535.0.4", optional = true }
lua-src = { version = "540.0.0", optional = true }
luajit-src = { version = "210.1.0", optional = true }
[dev-dependencies]
+3
View File
@@ -38,7 +38,10 @@ pub use super::glue::LUA_REGISTRYINDEX;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use super::glue::{LUA_ENVIRONINDEX, LUA_GLOBALSINDEX};
#[cfg(not(feature = "luajit"))]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
#[cfg(feature = "luajit")]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLJ";
// option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
+2 -2
View File
@@ -211,8 +211,8 @@ pub use self::lua::{
LUA_HOOKCOUNT, LUA_HOOKLINE, LUA_HOOKRET, LUA_HOOKTAILCALL, LUA_MASKCALL, LUA_MASKCOUNT,
LUA_MASKLINE, LUA_MASKRET, LUA_MINSTACK, LUA_MULTRET, LUA_OK, LUA_OPADD, LUA_OPDIV, LUA_OPEQ,
LUA_OPLE, LUA_OPLT, LUA_OPMOD, LUA_OPMUL, LUA_OPPOW, LUA_OPSUB, LUA_OPUNM, LUA_REGISTRYINDEX,
LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE, LUA_TNUMBER, LUA_TSTRING,
LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
LUA_SIGNATURE, LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE,
LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
+38 -2
View File
@@ -1,5 +1,5 @@
use std::os::raw::c_int;
use std::ptr;
use std::os::raw::{c_int, c_void};
use std::{ptr, slice};
use crate::error::{Error, Result};
use crate::ffi;
@@ -205,6 +205,42 @@ impl<'lua> Function<'lua> {
Ok(Function(lua.pop_ref()))
}
}
/// Dumps the function as a binary chunk.
///
/// If `strip` is true, the binary representation may not include all debug information
/// about the function, to save space.
pub fn dump(&self, strip: bool) -> Result<Vec<u8>> {
unsafe extern "C" fn writer(
_state: *mut ffi::lua_State,
buf: *const c_void,
buf_len: usize,
data: *mut c_void,
) -> c_int {
let data = &mut *(data as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
0
}
let lua = self.0.lua;
let mut data: Vec<u8> = Vec::new();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
let strip = if strip { 1 } else { 0 };
ffi::lua_dump(
lua.state,
writer,
&mut data as *mut Vec<u8> as *mut c_void,
strip,
);
ffi::lua_pop(lua.state, 1);
}
Ok(data)
}
}
impl<'lua> PartialEq for Function<'lua> {
+1 -1
View File
@@ -81,7 +81,7 @@ pub use crate::ffi::lua_State;
pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers};
pub use crate::lua::{Chunk, GCMode, Lua};
pub use crate::lua::{Chunk, ChunkMode, GCMode, Lua};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
pub use crate::stdlib::StdLib;
+68 -30
View File
@@ -100,13 +100,15 @@ impl Drop for Lua {
fn drop(&mut self) {
unsafe {
if !self.ephemeral {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
mlua_debug_assert!(
ffi::lua_gettop(extra.ref_thread) == extra.ref_stack_max
&& extra.ref_stack_max as usize == extra.ref_free.len(),
"reference leak detected"
);
*mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned") = None;
let mut unref_list =
mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned");
*unref_list = None;
ffi::lua_close(self.main_state.expect("main_state is null"));
if !extra.mem_info.is_null() {
Box::from_raw(extra.mem_info);
@@ -490,7 +492,7 @@ impl Lua {
unsafe { (*extra.mem_info).used_memory as usize }
}
/// Sets a memory limit on this Lua state.
/// Sets a memory limit (in bytes) on this Lua state.
///
/// Once an allocation occurs that would pass this memory limit,
/// a `Error::MemoryError` is generated instead.
@@ -658,6 +660,9 @@ impl Lua {
/// similar on the returned builder. Code is not even parsed until one of these methods is
/// called.
///
/// If this `Lua` was created with `unsafe_new`, `load` will automatically detect and load
/// chunks of either text or binary type, as if passing `bt` mode to `luaL_loadbufferx`.
///
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
pub fn load<'lua, 'a, S>(&'lua self, source: &'a S) -> Chunk<'lua, 'a>
where
@@ -668,6 +673,7 @@ impl Lua {
source: source.as_ref(),
name: None,
env: None,
mode: None,
}
}
@@ -676,28 +682,35 @@ impl Lua {
source: &[u8],
name: Option<&CString>,
env: Option<Value<'lua>>,
mode: Option<ChunkMode>,
) -> Result<Function<'lua>> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
match if let Some(name) = name {
ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
name.as_ptr() as *const c_char,
cstr!("t"),
)
} else {
ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
ptr::null(),
cstr!("t"),
)
} {
let mode_str = match mode {
Some(ChunkMode::Binary) if self.safe => {
return Err(Error::SafetyError(
"binary chunks are disabled in safe mode".to_string(),
))
}
Some(ChunkMode::Binary) => cstr!("b"),
Some(ChunkMode::Text) => cstr!("t"),
None if source.starts_with(ffi::LUA_SIGNATURE) && self.safe => {
return Err(Error::SafetyError(
"binary chunks are disabled in safe mode".to_string(),
))
}
None => cstr!("bt"),
};
match ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null),
mode_str,
) {
ffi::LUA_OK => {
if let Some(env) = env {
self.push_value(env)?;
@@ -903,7 +916,7 @@ impl Lua {
/// ```
///
/// [`Thread`]: struct.Thread.html
/// [`ThreadStream`]: struct.ThreadStream.html
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
pub fn create_async_function<'lua, 'callback, A, R, F, FR>(
&'lua self,
@@ -1274,11 +1287,10 @@ impl Lua {
/// by `Lua::remove_registry_value`.
pub fn expire_registry_values(&self) {
unsafe {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let unref_list = mem::replace(
&mut *mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned"),
Some(Vec::new()),
);
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let mut unref_list =
mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned");
let unref_list = mem::replace(&mut *unref_list, Some(Vec::new()));
for id in mlua_expect!(unref_list, "unref list not set") {
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, id);
}
@@ -1802,6 +1814,14 @@ pub struct Chunk<'lua, 'a> {
source: &'a [u8],
name: Option<CString>,
env: Option<Value<'lua>>,
mode: Option<ChunkMode>,
}
/// Represents chunk mode (text or binary).
#[derive(Clone, Copy)]
pub enum ChunkMode {
Text,
Binary,
}
impl<'lua, 'a> Chunk<'lua, 'a> {
@@ -1833,6 +1853,17 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
Ok(self)
}
/// Sets whether the chunk is text or binary (autodetected by default).
///
/// Lua does not check the consistency of binary chunks, therefore this mode is allowed only
/// for instances created with [`Lua::unsafe_new`].
///
/// [`Lua::unsafe_new`]: struct.Lua.html#method.unsafe_new
pub fn set_mode(mut self, mode: ChunkMode) -> Chunk<'lua, 'a> {
self.mode = Some(mode);
self
}
/// Execute this chunk of code.
///
/// This is equivalent to calling the chunk function with no arguments and no return values.
@@ -1862,13 +1893,17 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal,
/// and this is equivalent to calling `exec`.
pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> {
// First, try interpreting the lua as an expression by adding
// Bytecode is always interpreted as a statement.
// For source code, first try interpreting the lua as an expression by adding
// "return", then as a statement. This is the same thing the
// actual lua repl does.
if let Ok(function) = self.lua.load_chunk(
if self.source.starts_with(ffi::LUA_SIGNATURE) {
self.call(())
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
self.mode,
) {
function.call(())
} else {
@@ -1889,10 +1924,13 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
'lua: 'fut,
R: FromLuaMulti<'lua> + 'fut,
{
if let Ok(function) = self.lua.load_chunk(
if self.source.starts_with(ffi::LUA_SIGNATURE) {
self.call_async(())
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
self.mode,
) {
function.call_async(())
} else {
@@ -1932,7 +1970,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// This simply compiles the chunk without actually executing it.
pub fn into_function(self) -> Result<Function<'lua>> {
self.lua
.load_chunk(self.source, self.name.as_ref(), self.env)
.load_chunk(self.source, self.name.as_ref(), self.env, self.mode)
}
fn expression_source(&self) -> Vec<u8> {
@@ -22,8 +22,8 @@ note: ...so that the types are compatible
13 | | Ok(())
14 | | });
| |_____________^
= note: expected `main::MyUserData<'_>`
found `main::MyUserData<'a>`
= note: expected `main::MyUserData<'_>`
found `main::MyUserData<'a>`
note: but, the lifetime must be valid for the lifetime `'lua` as defined on the method body at 10:24...
--> $DIR/async_nonstatic_userdata.rs:10:24
|
+22 -41
View File
@@ -1,45 +1,26 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_capture.rs:8:14
|
8 | .create_function_mut(move |_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
--> $DIR/scope_callback_capture.rs:5:15
|
5 | lua.scope(|scope| {
| _______________^
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(move |_, t: Table| {
... |
16 | | Ok(())
17 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
warning: unused variable: `old`
--> $DIR/scope_callback_capture.rs:9:29
|
9 | if let Some(old) = inner.take() {
| ^^^ help: if this is intentional, prefix it with an underscore: `_old`
|
= note: `#[warn(unused_variables)]` on by default
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_capture.rs:7:17
|
7 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 5:5...
--> $DIR/scope_callback_capture.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(move |_, t: Table| {
9 | | if let Some(old) = inner.take() {
10 | | // Access old callback `Lua`.
... |
16 | | Ok(())
17 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_capture.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(move |_, t: Table| {
... |
16 | | Ok(())
17 | | });
| |______^
13 | | Ok(())
14 | | })?;
| |______________^ `scope` escapes the closure body here
+37 -40
View File
@@ -1,45 +1,42 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_inner.rs:8:14
|
8 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
--> $DIR/scope_callback_inner.rs:5:15
|
5 | lua.scope(|scope| {
| _______________^
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_inner.rs:7:17
|
7 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 5:5...
--> $DIR/scope_callback_inner.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_inner.rs:5:5
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function
--> $DIR/scope_callback_inner.rs:8:34
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| ----- has type `&mlua::scope::Scope<'_, '2>`
...
8 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
9 | inner = Some(t);
| ----- `inner` is borrowed here
|
note: function requires argument type to outlive `'2`
--> $DIR/scope_callback_inner.rs:7:17
|
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |______^
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^
help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword
|
8 | .create_function_mut(move |_, t: Table| {
| ^^^^^^^^^^^^^^^^^^
+30 -11
View File
@@ -1,11 +1,30 @@
error: borrowed data cannot be stored outside of its closure
--> $DIR/scope_callback_outer.rs:7:17
|
5 | let mut outer: Option<Table> = None;
| --------- ...so that variable is valid at time of its declaration
6 | lua.scope(|scope| {
| ------- borrowed data cannot outlive this closure
7 | let f = scope
| ^^^^^ cannot be stored outside of its closure
8 | .create_function_mut(|_, t: Table| {
| ------------------- cannot infer an appropriate lifetime...
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_outer.rs:7:17
|
6 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
9 | | outer = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0597]: `outer` does not live long enough
--> $DIR/scope_callback_outer.rs:9:17
|
6 | lua.scope(|scope| {
| ------- value captured here
...
9 | outer = Some(t);
| ^^^^^ borrowed value does not live long enough
...
15 | }
| -
| |
| `outer` dropped here while still borrowed
| borrow might be used here, when `outer` is dropped and runs the destructor for type `std::option::Option<mlua::table::Table<'_>>`
+14
View File
@@ -87,3 +87,17 @@ fn test_rust_function() -> Result<()> {
Ok(())
}
#[test]
fn test_dump() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let concat_lua = lua
.load(r#"function(arg1, arg2) return arg1 .. arg2 end"#)
.eval::<Function>()?;
let concat = lua.load(&concat_lua.dump(false)?).into_function()?;
assert_eq!(concat.call::<_, String>(("foo", "bar"))?, "foobar");
Ok(())
}
+54 -2
View File
@@ -16,8 +16,8 @@ use std::sync::Arc;
use std::{error, f32, f64, fmt};
use mlua::{
Error, ExternalError, Function, Lua, Nil, Result, StdLib, String, Table, UserData, Value,
Variadic,
ChunkMode, Error, ExternalError, Function, Lua, Nil, Result, StdLib, String, Table, UserData,
Value, Variadic,
};
#[test]
@@ -56,6 +56,23 @@ fn test_safety() -> Result<()> {
Ok(_) => panic!("expected RuntimeError, got no error"),
}
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true)?;
match lua.load(&bytecode).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
Ok(())
}
@@ -127,6 +144,41 @@ fn test_eval() -> Result<()> {
Ok(())
}
#[test]
fn test_load_mode() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
assert_eq!(
lua.load("1 + 1").set_mode(ChunkMode::Text).eval::<i32>()?,
2
);
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a text chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true)?;
assert_eq!(lua.load(&bytecode).eval::<i32>()?, 2);
assert_eq!(
lua.load(&bytecode)
.set_mode(ChunkMode::Binary)
.eval::<i32>()?,
2
);
match lua.load(&bytecode).set_mode(ChunkMode::Text).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a binary chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
Ok(())
}
#[test]
fn test_lua_multi() -> Result<()> {
let lua = Lua::new();