├── .github ├── FUNDING.yml └── workflows │ ├── coverage.yml │ ├── main.yml │ └── typos.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── FAQ.md ├── LICENSE ├── README.md ├── benches ├── benchmark.rs └── serde.rs ├── codecov.yml ├── docs └── release_notes │ ├── v0.10.md │ └── v0.9.md ├── examples ├── async_http_client.rs ├── async_http_reqwest.rs ├── async_http_server.rs ├── async_tcp_server.rs ├── guided_tour.rs ├── module │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── repl.rs ├── serialize.rs └── userdata.rs ├── mlua-sys ├── Cargo.toml ├── README.md ├── build │ ├── find_normal.rs │ ├── find_vendored.rs │ ├── main.rs │ └── main_inner.rs └── src │ ├── lib.rs │ ├── lua51 │ ├── compat.rs │ ├── lauxlib.rs │ ├── lua.rs │ ├── lualib.rs │ └── mod.rs │ ├── lua52 │ ├── compat.rs │ ├── lauxlib.rs │ ├── lua.rs │ ├── lualib.rs │ └── mod.rs │ ├── lua53 │ ├── compat.rs │ ├── lauxlib.rs │ ├── lua.rs │ ├── lualib.rs │ └── mod.rs │ ├── lua54 │ ├── lauxlib.rs │ ├── lua.rs │ ├── lualib.rs │ └── mod.rs │ ├── luau │ ├── compat.rs │ ├── lauxlib.rs │ ├── lua.rs │ ├── luacode.rs │ ├── luacodegen.rs │ ├── lualib.rs │ ├── luarequire.rs │ └── mod.rs │ └── macros.rs ├── mlua_derive ├── Cargo.toml └── src │ ├── chunk.rs │ ├── from_lua.rs │ ├── lib.rs │ └── token.rs ├── rustfmt.toml ├── src ├── buffer.rs ├── chunk.rs ├── conversion.rs ├── error.rs ├── function.rs ├── hook.rs ├── lib.rs ├── luau │ ├── mod.rs │ └── require.rs ├── macros.rs ├── memory.rs ├── multi.rs ├── prelude.rs ├── scope.rs ├── serde │ ├── de.rs │ ├── mod.rs │ └── ser.rs ├── state.rs ├── state │ ├── extra.rs │ ├── raw.rs │ └── util.rs ├── stdlib.rs ├── string.rs ├── table.rs ├── thread.rs ├── traits.rs ├── types.rs ├── types │ ├── app_data.rs │ ├── registry_key.rs │ ├── sync.rs │ └── value_ref.rs ├── userdata.rs ├── userdata │ ├── cell.rs │ ├── lock.rs │ ├── object.rs │ ├── ref.rs │ ├── registry.rs │ └── util.rs ├── util │ ├── error.rs │ ├── mod.rs │ ├── short_names.rs │ ├── types.rs │ └── userdata.rs ├── value.rs └── vector.rs ├── tarpaulin.toml ├── tests ├── async.rs ├── buffer.rs ├── byte_string.rs ├── chunk.rs ├── compile.rs ├── compile │ ├── async_any_userdata_method.rs │ ├── async_any_userdata_method.stderr │ ├── async_nonstatic_userdata.rs │ ├── async_nonstatic_userdata.stderr │ ├── function_borrow.rs │ ├── function_borrow.stderr │ ├── lua_norefunwindsafe.rs │ ├── lua_norefunwindsafe.stderr │ ├── non_send.rs │ ├── non_send.stderr │ ├── ref_nounwindsafe.rs │ ├── ref_nounwindsafe.stderr │ ├── scope_callback_capture.rs │ ├── scope_callback_capture.stderr │ ├── scope_invariance.rs │ ├── scope_invariance.stderr │ ├── scope_mutable_aliasing.rs │ ├── scope_mutable_aliasing.stderr │ ├── scope_userdata_borrow.rs │ └── scope_userdata_borrow.stderr ├── conversion.rs ├── debug.rs ├── error.rs ├── function.rs ├── hooks.rs ├── luau.rs ├── luau │ ├── require.rs │ └── require │ │ ├── with_config │ │ ├── .luaurc │ │ └── src │ │ │ ├── .luaurc │ │ │ ├── alias_requirer.luau │ │ │ ├── dependency.luau │ │ │ ├── directory_alias_requirer.luau │ │ │ ├── other_dependency.luau │ │ │ ├── parent_alias_requirer.luau │ │ │ └── subdirectory │ │ │ └── subdirectory_dependency.luau │ │ └── without_config │ │ ├── ambiguous │ │ ├── directory │ │ │ ├── dependency.luau │ │ │ └── dependency │ │ │ │ └── init.luau │ │ └── file │ │ │ ├── dependency.lua │ │ │ └── dependency.luau │ │ ├── ambiguous_directory_requirer.luau │ │ ├── ambiguous_file_requirer.luau │ │ ├── dependency.luau │ │ ├── lua │ │ └── init.lua │ │ ├── lua_dependency.lua │ │ ├── luau │ │ └── init.luau │ │ ├── module.luau │ │ ├── nested │ │ ├── init.luau │ │ └── submodule.luau │ │ ├── nested_module_requirer.luau │ │ └── validate_cache.luau ├── memory.rs ├── module │ ├── Cargo.toml │ ├── build.rs │ ├── loader │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ └── tests │ │ │ └── load.rs │ └── src │ │ └── lib.rs ├── multi.rs ├── scope.rs ├── send.rs ├── serde.rs ├── string.rs ├── table.rs ├── tests.rs ├── thread.rs ├── types.rs ├── userdata.rs └── value.rs └── typos.toml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: khvzak 2 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | on: [push] 3 | 4 | jobs: 5 | test: 6 | name: coverage 7 | runs-on: ubuntu-latest 8 | container: 9 | image: xd009642/tarpaulin:develop-nightly 10 | options: --security-opt seccomp=unconfined 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@main 14 | 15 | - name: Generate coverage report 16 | run: | 17 | cargo +nightly tarpaulin --verbose --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/* 18 | 19 | - name: Upload report to codecov.io 20 | uses: codecov/codecov-action@v4 21 | with: 22 | token: ${{secrets.CODECOV_TOKEN}} 23 | fail_ci_if_error: false 24 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | name: Typos Check 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | run: 8 | name: Spell Check with Typos 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout Actions Repository 12 | uses: actions/checkout@v4 13 | 14 | - name: Check spelling 15 | uses: crate-ci/typos@master 16 | with: 17 | config: ./typos.toml 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | .vscode/ 6 | .DS_Store 7 | .stignore 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mlua" 3 | version = "0.11.0-beta.1" # remember to update mlua_derive 4 | authors = ["Aleksandr Orlenko ", "kyren "] 5 | rust-version = "1.79.0" 6 | edition = "2021" 7 | repository = "https://github.com/mlua-rs/mlua" 8 | documentation = "https://docs.rs/mlua" 9 | readme = "README.md" 10 | keywords = ["lua", "luajit", "luau", "async", "scripting"] 11 | categories = ["api-bindings", "asynchronous"] 12 | license = "MIT" 13 | description = """ 14 | High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau 15 | with async/await features and support of writing native Lua modules in Rust. 16 | """ 17 | 18 | [package.metadata.docs.rs] 19 | features = ["lua54", "vendored", "async", "send", "serialize", "macros"] 20 | rustdoc-args = ["--cfg", "docsrs"] 21 | 22 | [workspace] 23 | members = [ 24 | "mlua_derive", 25 | "mlua-sys", 26 | ] 27 | 28 | [features] 29 | lua54 = ["ffi/lua54"] 30 | lua53 = ["ffi/lua53"] 31 | lua52 = ["ffi/lua52"] 32 | lua51 = ["ffi/lua51"] 33 | luajit = ["ffi/luajit"] 34 | luajit52 = ["luajit", "ffi/luajit52"] 35 | luau = ["ffi/luau"] 36 | luau-jit = ["luau", "ffi/luau-codegen"] 37 | luau-vector4 = ["luau", "ffi/luau-vector4"] 38 | vendored = ["ffi/vendored"] 39 | module = ["mlua_derive", "ffi/module"] 40 | async = ["dep:futures-util"] 41 | send = ["error-send"] 42 | error-send = [] 43 | serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"] 44 | macros = ["mlua_derive/macros"] 45 | anyhow = ["dep:anyhow", "error-send"] 46 | userdata-wrappers = ["parking_lot/send_guard"] 47 | 48 | [dependencies] 49 | mlua_derive = { version = "=0.11.0-beta.1", optional = true, path = "mlua_derive" } 50 | bstr = { version = "1.0", features = ["std"], default-features = false } 51 | either = "1.0" 52 | num-traits = { version = "0.2.14" } 53 | rustc-hash = "2.0" 54 | futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] } 55 | serde = { version = "1.0", optional = true } 56 | erased-serde = { version = "0.4", optional = true } 57 | serde-value = { version = "0.7", optional = true } 58 | parking_lot = { version = "0.12", features = ["arc_lock"] } 59 | anyhow = { version = "1.0", optional = true } 60 | rustversion = "1.0" 61 | 62 | ffi = { package = "mlua-sys", version = "0.7.0", path = "mlua-sys" } 63 | 64 | [dev-dependencies] 65 | trybuild = "1.0" 66 | hyper = { version = "1.2", features = ["full"] } 67 | hyper-util = { version = "0.1.3", features = ["full"] } 68 | http-body-util = "0.1.1" 69 | reqwest = { version = "0.12", features = ["json"] } 70 | tokio = { version = "1.0", features = ["macros", "rt", "time"] } 71 | serde = { version = "1.0", features = ["derive"] } 72 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 73 | maplit = "1.0" 74 | tempfile = "3" 75 | static_assertions = "1.0" 76 | 77 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] 78 | criterion = { version = "0.5", features = ["async_tokio"] } 79 | rustyline = "15.0" 80 | tokio = { version = "1.0", features = ["full"] } 81 | 82 | [lints.rust] 83 | unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] } 84 | 85 | [[bench]] 86 | name = "benchmark" 87 | harness = false 88 | required-features = ["async"] 89 | 90 | [[bench]] 91 | name = "serde" 92 | harness = false 93 | required-features = ["serialize"] 94 | 95 | [[example]] 96 | name = "async_http_client" 97 | required-features = ["async", "macros"] 98 | 99 | [[example]] 100 | name = "async_http_reqwest" 101 | required-features = ["async", "serialize", "macros"] 102 | 103 | [[example]] 104 | name = "async_http_server" 105 | required-features = ["async", "macros", "send"] 106 | 107 | [[example]] 108 | name = "async_tcp_server" 109 | required-features = ["async", "macros", "send"] 110 | 111 | [[example]] 112 | name = "guided_tour" 113 | required-features = ["macros"] 114 | 115 | [[example]] 116 | name = "serialize" 117 | required-features = ["serialize"] 118 | 119 | [[example]] 120 | name = "userdata" 121 | required-features = ["macros"] 122 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # mlua FAQ 2 | 3 | This file is for general questions that don't fit into the README or crate docs. 4 | 5 | ## Loading a C module fails with error `undefined symbol: lua_xxx`. How to fix? 6 | 7 | Add the following rustflags to your [.cargo/config](http://doc.crates.io/config.html) in order to properly export Lua symbols: 8 | 9 | ```toml 10 | [target.x86_64-unknown-linux-gnu] 11 | rustflags = ["-C", "link-args=-rdynamic"] 12 | 13 | [target.x86_64-apple-darwin] 14 | rustflags = ["-C", "link-args=-rdynamic"] 15 | ``` 16 | 17 | ## I want to add support for a Lua VM fork to mlua. Do you accept pull requests? 18 | 19 | Adding new feature flag to support a Lua VM fork is a major step that requires huge effort to maintain it. 20 | Regular updates, testing, checking compatibility, etc. 21 | That's why I don't plan to support new Lua VM forks or other languages in mlua. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Both mlua and rlua are distributed under the MIT license, which is reproduced 2 | below: 3 | 4 | MIT License 5 | 6 | Copyright (c) 2019-2021 A. Orlenko 7 | Copyright (c) 2017 rlua 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /benches/serde.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; 4 | 5 | use mlua::prelude::*; 6 | 7 | fn collect_gc_twice(lua: &Lua) { 8 | lua.gc_collect().unwrap(); 9 | lua.gc_collect().unwrap(); 10 | } 11 | 12 | fn encode_json(c: &mut Criterion) { 13 | let lua = Lua::new(); 14 | 15 | let encode = lua 16 | .create_function(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap())) 17 | .unwrap(); 18 | let table = lua 19 | .load( 20 | r#"{ 21 | name = "Clark Kent", 22 | address = { 23 | city = "Smallville", 24 | state = "Kansas", 25 | country = "USA", 26 | }, 27 | age = 22, 28 | parents = {"Jonathan Kent", "Martha Kent"}, 29 | superman = true, 30 | interests = {"flying", "saving the world", "kryptonite"}, 31 | }"#, 32 | ) 33 | .eval::() 34 | .unwrap(); 35 | 36 | c.bench_function("serialize json", |b| { 37 | b.iter_batched( 38 | || collect_gc_twice(&lua), 39 | |_| { 40 | encode.call::(&table).unwrap(); 41 | }, 42 | BatchSize::SmallInput, 43 | ); 44 | }); 45 | } 46 | 47 | fn decode_json(c: &mut Criterion) { 48 | let lua = Lua::new(); 49 | 50 | let decode = lua 51 | .create_function(|lua, s: String| { 52 | lua.to_value(&serde_json::from_str::(&s).unwrap()) 53 | }) 54 | .unwrap(); 55 | let json = r#"{ 56 | "name": "Clark Kent", 57 | "address": { 58 | "city": "Smallville", 59 | "state": "Kansas", 60 | "country": "USA" 61 | }, 62 | "age": 22, 63 | "parents": ["Jonathan Kent", "Martha Kent"], 64 | "superman": true, 65 | "interests": ["flying", "saving the world", "kryptonite"] 66 | }"#; 67 | 68 | c.bench_function("deserialize json", |b| { 69 | b.iter_batched( 70 | || collect_gc_twice(&lua), 71 | |_| { 72 | decode.call::(json).unwrap(); 73 | }, 74 | BatchSize::SmallInput, 75 | ); 76 | }); 77 | } 78 | 79 | criterion_group! { 80 | name = benches; 81 | config = Criterion::default() 82 | .sample_size(500) 83 | .measurement_time(Duration::from_secs(10)) 84 | .noise_threshold(0.02); 85 | targets = 86 | encode_json, 87 | decode_json, 88 | } 89 | 90 | criterion_main!(benches); 91 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: 5 | only_pulls: true 6 | project: 7 | default: 8 | only_pulls: true 9 | -------------------------------------------------------------------------------- /examples/async_http_client.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use http_body_util::BodyExt as _; 4 | use hyper::body::Incoming; 5 | use hyper_util::client::legacy::Client as HyperClient; 6 | use hyper_util::rt::TokioExecutor; 7 | 8 | use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods}; 9 | 10 | struct BodyReader(Incoming); 11 | 12 | impl UserData for BodyReader { 13 | fn add_methods>(methods: &mut M) { 14 | // Every call returns a next chunk 15 | methods.add_async_method_mut("read", |lua, mut reader, ()| async move { 16 | if let Some(bytes) = reader.0.frame().await { 17 | if let Some(bytes) = bytes.into_lua_err()?.data_ref() { 18 | return Some(lua.create_string(&bytes)).transpose(); 19 | } 20 | } 21 | Ok(None) 22 | }); 23 | } 24 | } 25 | 26 | #[tokio::main(flavor = "current_thread")] 27 | async fn main() -> Result<()> { 28 | let lua = Lua::new(); 29 | 30 | let fetch_url = lua.create_async_function(|lua, uri: String| async move { 31 | let client = HyperClient::builder(TokioExecutor::new()).build_http::(); 32 | let uri = uri.parse().into_lua_err()?; 33 | let resp = client.get(uri).await.into_lua_err()?; 34 | 35 | let lua_resp = lua.create_table()?; 36 | lua_resp.set("status", resp.status().as_u16())?; 37 | 38 | let mut headers = HashMap::new(); 39 | for (key, value) in resp.headers() { 40 | headers 41 | .entry(key.as_str()) 42 | .or_insert(Vec::new()) 43 | .push(value.to_str().into_lua_err()?); 44 | } 45 | 46 | lua_resp.set("headers", headers)?; 47 | lua_resp.set("body", BodyReader(resp.into_body()))?; 48 | 49 | Ok(lua_resp) 50 | })?; 51 | 52 | let f = lua 53 | .load(chunk! { 54 | local res = $fetch_url(...) 55 | print("status: "..res.status) 56 | for key, vals in pairs(res.headers) do 57 | for _, val in ipairs(vals) do 58 | print(key..": "..val) 59 | end 60 | end 61 | repeat 62 | local chunk = res.body:read() 63 | if chunk then 64 | print(chunk) 65 | end 66 | until not chunk 67 | }) 68 | .into_function()?; 69 | 70 | f.call_async("http://httpbin.org/ip").await 71 | } 72 | -------------------------------------------------------------------------------- /examples/async_http_reqwest.rs: -------------------------------------------------------------------------------- 1 | use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value}; 2 | 3 | #[tokio::main(flavor = "current_thread")] 4 | async fn main() -> Result<()> { 5 | let lua = Lua::new(); 6 | 7 | let fetch_json = lua.create_async_function(|lua, uri: String| async move { 8 | let resp = reqwest::get(&uri) 9 | .await 10 | .and_then(|resp| resp.error_for_status()) 11 | .into_lua_err()?; 12 | let json = resp.json::().await.into_lua_err()?; 13 | lua.to_value(&json) 14 | })?; 15 | 16 | let dbg = lua.create_function(|_, value: Value| { 17 | println!("{value:#?}"); 18 | Ok(()) 19 | })?; 20 | 21 | let f = lua 22 | .load(chunk! { 23 | local res = $fetch_json(...) 24 | $dbg(res) 25 | }) 26 | .into_function()?; 27 | 28 | f.call_async("https://httpbin.org/anything?arg0=val0").await 29 | } 30 | -------------------------------------------------------------------------------- /examples/async_http_server.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::future::Future; 3 | use std::net::SocketAddr; 4 | use std::pin::Pin; 5 | 6 | use http_body_util::combinators::BoxBody; 7 | use http_body_util::{BodyExt as _, Empty, Full}; 8 | use hyper::body::{Bytes, Incoming}; 9 | use hyper::server::conn::http1; 10 | use hyper::{Request, Response}; 11 | use hyper_util::rt::TokioIo; 12 | use tokio::net::TcpListener; 13 | 14 | use mlua::{chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods}; 15 | 16 | /// Wrapper around incoming request that implements UserData 17 | struct LuaRequest(SocketAddr, Request); 18 | 19 | impl UserData for LuaRequest { 20 | fn add_methods>(methods: &mut M) { 21 | methods.add_method("remote_addr", |_, req, ()| Ok((req.0).to_string())); 22 | methods.add_method("method", |_, req, ()| Ok((req.1).method().to_string())); 23 | methods.add_method("path", |_, req, ()| Ok(req.1.uri().path().to_string())); 24 | } 25 | } 26 | 27 | /// Service that handles incoming requests 28 | #[derive(Clone)] 29 | pub struct Svc { 30 | handler: Function, 31 | peer_addr: SocketAddr, 32 | } 33 | 34 | impl Svc { 35 | pub fn new(handler: Function, peer_addr: SocketAddr) -> Self { 36 | Self { handler, peer_addr } 37 | } 38 | } 39 | 40 | impl hyper::service::Service> for Svc { 41 | type Response = Response>; 42 | type Error = LuaError; 43 | type Future = Pin> + Send>>; 44 | 45 | fn call(&self, req: Request) -> Self::Future { 46 | // If handler returns an error then generate 5xx response 47 | let handler = self.handler.clone(); 48 | let lua_req = LuaRequest(self.peer_addr, req); 49 | Box::pin(async move { 50 | match handler.call_async::(lua_req).await { 51 | Ok(lua_resp) => { 52 | let status = lua_resp.get::>("status")?.unwrap_or(200); 53 | let mut resp = Response::builder().status(status); 54 | 55 | // Set headers 56 | if let Some(headers) = lua_resp.get::>("headers")? { 57 | for pair in headers.pairs::() { 58 | let (h, v) = pair?; 59 | resp = resp.header(&h, &*v.as_bytes()); 60 | } 61 | } 62 | 63 | // Set body 64 | let body = lua_resp 65 | .get::>("body")? 66 | .map(|b| Full::new(Bytes::copy_from_slice(&b.as_bytes())).boxed()) 67 | .unwrap_or_else(|| Empty::::new().boxed()); 68 | 69 | Ok(resp.body(body).unwrap()) 70 | } 71 | Err(err) => { 72 | eprintln!("{}", err); 73 | Ok(Response::builder() 74 | .status(500) 75 | .body(Full::new(Bytes::from("Internal Server Error")).boxed()) 76 | .unwrap()) 77 | } 78 | } 79 | }) 80 | } 81 | } 82 | 83 | #[tokio::main(flavor = "current_thread")] 84 | async fn main() { 85 | let lua = Lua::new(); 86 | 87 | // Create Lua handler function 88 | let handler = lua 89 | .load(chunk! { 90 | function(req) 91 | return { 92 | status = 200, 93 | headers = { 94 | ["X-Req-Method"] = req:method(), 95 | ["X-Req-Path"] = req:path(), 96 | ["X-Remote-Addr"] = req:remote_addr(), 97 | }, 98 | body = "Hello from Lua!\n" 99 | } 100 | end 101 | }) 102 | .eval::() 103 | .expect("Failed to create Lua handler"); 104 | 105 | let listen_addr = "127.0.0.1:3000"; 106 | let listener = TcpListener::bind(listen_addr).await.unwrap(); 107 | println!("Listening on http://{listen_addr}"); 108 | 109 | loop { 110 | let (stream, peer_addr) = match listener.accept().await { 111 | Ok(x) => x, 112 | Err(err) => { 113 | eprintln!("Failed to accept connection: {err}"); 114 | continue; 115 | } 116 | }; 117 | 118 | let svc = Svc::new(handler.clone(), peer_addr); 119 | tokio::task::spawn(async move { 120 | if let Err(err) = http1::Builder::new() 121 | .serve_connection(TokioIo::new(stream), svc) 122 | .await 123 | { 124 | eprintln!("Error serving connection: {:?}", err); 125 | } 126 | }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/async_tcp_server.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::SocketAddr; 3 | 4 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 5 | use tokio::net::{TcpListener, TcpStream}; 6 | 7 | use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods}; 8 | 9 | struct LuaTcpStream(TcpStream); 10 | 11 | impl UserData for LuaTcpStream { 12 | fn add_methods>(methods: &mut M) { 13 | methods.add_method("peer_addr", |_, this, ()| Ok(this.0.peer_addr()?.to_string())); 14 | 15 | methods.add_async_method_mut("read", |lua, mut this, size| async move { 16 | let mut buf = vec![0; size]; 17 | let n = this.0.read(&mut buf).await?; 18 | buf.truncate(n); 19 | lua.create_string(&buf) 20 | }); 21 | 22 | methods.add_async_method_mut("write", |_, mut this, data: BString| async move { 23 | let n = this.0.write(&data).await?; 24 | Ok(n) 25 | }); 26 | 27 | methods.add_async_method_mut("close", |_, mut this, ()| async move { 28 | this.0.shutdown().await?; 29 | Ok(()) 30 | }); 31 | } 32 | } 33 | 34 | async fn run_server(handler: Function) -> io::Result<()> { 35 | let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); 36 | let listener = TcpListener::bind(addr).await.expect("cannot bind addr"); 37 | 38 | println!("Listening on {}", addr); 39 | 40 | loop { 41 | let (stream, _) = match listener.accept().await { 42 | Ok(res) => res, 43 | Err(err) if is_transient_error(&err) => continue, 44 | Err(err) => return Err(err), 45 | }; 46 | 47 | let handler = handler.clone(); 48 | tokio::task::spawn(async move { 49 | let stream = LuaTcpStream(stream); 50 | if let Err(err) = handler.call_async::<()>(stream).await { 51 | eprintln!("{}", err); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | #[tokio::main(flavor = "current_thread")] 58 | async fn main() { 59 | let lua = Lua::new(); 60 | 61 | // Create Lua handler function 62 | let handler = lua 63 | .load(chunk! { 64 | function(stream) 65 | local peer_addr = stream:peer_addr() 66 | print("connected from "..peer_addr) 67 | 68 | while true do 69 | local data = stream:read(100) 70 | data = data:match("^%s*(.-)%s*$") // trim 71 | print("["..peer_addr.."] "..data) 72 | if data == "bye" then 73 | stream:write("bye bye\n") 74 | stream:close() 75 | return 76 | end 77 | stream:write("echo: "..data.."\n") 78 | end 79 | end 80 | }) 81 | .eval::() 82 | .expect("cannot create Lua handler"); 83 | 84 | run_server(handler).await.expect("cannot run server") 85 | } 86 | 87 | fn is_transient_error(e: &io::Error) -> bool { 88 | e.kind() == io::ErrorKind::ConnectionRefused 89 | || e.kind() == io::ErrorKind::ConnectionAborted 90 | || e.kind() == io::ErrorKind::ConnectionReset 91 | } 92 | -------------------------------------------------------------------------------- /examples/module/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-apple-darwin] 2 | rustflags = [ 3 | "-C", "link-arg=-undefined", 4 | "-C", "link-arg=dynamic_lookup", 5 | ] 6 | 7 | [target.aarch64-apple-darwin] 8 | rustflags = [ 9 | "-C", "link-arg=-undefined", 10 | "-C", "link-arg=dynamic_lookup", 11 | ] 12 | -------------------------------------------------------------------------------- /examples/module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_module" 3 | version = "0.0.0" 4 | authors = ["Aleksandr Orlenko "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [workspace] 11 | 12 | [features] 13 | lua54 = ["mlua/lua54"] 14 | lua53 = ["mlua/lua53"] 15 | lua52 = ["mlua/lua52"] 16 | lua51 = ["mlua/lua51"] 17 | luajit = ["mlua/luajit"] 18 | 19 | [dependencies] 20 | mlua = { path = "../..", features = ["module"] } 21 | -------------------------------------------------------------------------------- /examples/module/src/lib.rs: -------------------------------------------------------------------------------- 1 | use mlua::prelude::*; 2 | 3 | fn sum(_: &Lua, (a, b): (i64, i64)) -> LuaResult { 4 | Ok(a + b) 5 | } 6 | 7 | fn used_memory(lua: &Lua, _: ()) -> LuaResult { 8 | Ok(lua.used_memory()) 9 | } 10 | 11 | #[mlua::lua_module] 12 | fn rust_module(lua: &Lua) -> LuaResult { 13 | let exports = lua.create_table()?; 14 | exports.set("sum", lua.create_function(sum)?)?; 15 | exports.set("used_memory", lua.create_function(used_memory)?)?; 16 | Ok(exports) 17 | } 18 | -------------------------------------------------------------------------------- /examples/repl.rs: -------------------------------------------------------------------------------- 1 | //! This example shows a simple read-evaluate-print-loop (REPL). 2 | 3 | use mlua::{Error, Lua, MultiValue}; 4 | use rustyline::DefaultEditor; 5 | 6 | fn main() { 7 | let lua = Lua::new(); 8 | let mut editor = DefaultEditor::new().expect("Failed to create editor"); 9 | 10 | loop { 11 | let mut prompt = "> "; 12 | let mut line = String::new(); 13 | 14 | loop { 15 | match editor.readline(prompt) { 16 | Ok(input) => line.push_str(&input), 17 | Err(_) => return, 18 | } 19 | 20 | match lua.load(&line).eval::() { 21 | Ok(values) => { 22 | editor.add_history_entry(line).unwrap(); 23 | println!( 24 | "{}", 25 | values 26 | .iter() 27 | .map(|value| format!("{:#?}", value)) 28 | .collect::>() 29 | .join("\t") 30 | ); 31 | break; 32 | } 33 | Err(Error::SyntaxError { 34 | incomplete_input: true, 35 | .. 36 | }) => { 37 | // continue reading input and append it to `line` 38 | line.push_str("\n"); // separate input lines 39 | prompt = ">> "; 40 | } 41 | Err(e) => { 42 | eprintln!("error: {}", e); 43 | break; 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/serialize.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Error, Lua, LuaSerdeExt, Result, UserData, Value}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Serialize, Deserialize)] 5 | enum Transmission { 6 | Manual, 7 | Automatic, 8 | } 9 | 10 | #[derive(Serialize, Deserialize)] 11 | struct Engine { 12 | v: u32, 13 | kw: u32, 14 | } 15 | 16 | #[derive(Serialize, Deserialize)] 17 | struct Car { 18 | active: bool, 19 | model: String, 20 | transmission: Transmission, 21 | engine: Engine, 22 | } 23 | 24 | impl UserData for Car {} 25 | 26 | fn main() -> Result<()> { 27 | let lua = Lua::new(); 28 | let globals = lua.globals(); 29 | 30 | // Create Car struct from a Lua table 31 | let car: Car = lua.from_value( 32 | lua.load( 33 | r#" 34 | {active = true, model = "Volkswagen Golf", transmission = "Automatic", engine = {v = 1499, kw = 90}} 35 | "#, 36 | ) 37 | .eval()?, 38 | )?; 39 | 40 | // Set it as (serializable) userdata 41 | globals.set("null", lua.null())?; 42 | globals.set("array_mt", lua.array_metatable())?; 43 | globals.set("car", lua.create_ser_userdata(car)?)?; 44 | 45 | // Create a Lua table with multiple data types 46 | let val: Value = lua 47 | .load(r#"{driver = "Boris", car = car, price = null, points = setmetatable({}, array_mt)}"#) 48 | .eval()?; 49 | 50 | // Serialize the table above to JSON 51 | let json_str = serde_json::to_string(&val).map_err(Error::external)?; 52 | println!("{}", json_str); 53 | 54 | // Create Lua Value from JSON (or any serializable type) 55 | let json = serde_json::json!({ 56 | "key": "value", 57 | "null": null, 58 | "array": [], 59 | }); 60 | globals.set("json_value", lua.to_value(&json)?)?; 61 | lua.load( 62 | r#" 63 | assert(json_value["key"] == "value") 64 | assert(json_value["null"] == null) 65 | assert(#(json_value["array"]) == 0) 66 | "#, 67 | ) 68 | .exec()?; 69 | 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /examples/userdata.rs: -------------------------------------------------------------------------------- 1 | use mlua::{chunk, Lua, MetaMethod, Result, UserData}; 2 | 3 | #[derive(Default)] 4 | struct Rectangle { 5 | length: u32, 6 | width: u32, 7 | } 8 | 9 | impl UserData for Rectangle { 10 | fn add_fields>(fields: &mut F) { 11 | fields.add_field_method_get("length", |_, this| Ok(this.length)); 12 | fields.add_field_method_set("length", |_, this, val| { 13 | this.length = val; 14 | Ok(()) 15 | }); 16 | fields.add_field_method_get("width", |_, this| Ok(this.width)); 17 | fields.add_field_method_set("width", |_, this, val| { 18 | this.width = val; 19 | Ok(()) 20 | }); 21 | } 22 | 23 | fn add_methods>(methods: &mut M) { 24 | methods.add_method("area", |_, this, ()| Ok(this.length * this.width)); 25 | methods.add_method("diagonal", |_, this, ()| { 26 | Ok((this.length.pow(2) as f64 + this.width.pow(2) as f64).sqrt()) 27 | }); 28 | 29 | // Constructor 30 | methods.add_meta_function(MetaMethod::Call, |_, ()| Ok(Rectangle::default())); 31 | } 32 | } 33 | 34 | fn main() -> Result<()> { 35 | let lua = Lua::new(); 36 | let rectangle = Rectangle::default(); 37 | lua.load(chunk! { 38 | local rect = $rectangle() 39 | rect.width = 10 40 | rect.length = 5 41 | assert(rect:area() == 50) 42 | assert(rect:diagonal() - 11.1803 < 0.0001) 43 | }) 44 | .exec() 45 | } 46 | -------------------------------------------------------------------------------- /mlua-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mlua-sys" 3 | version = "0.7.0" 4 | authors = ["Aleksandr Orlenko "] 5 | rust-version = "1.71" 6 | edition = "2021" 7 | repository = "https://github.com/mlua-rs/mlua" 8 | documentation = "https://docs.rs/mlua-sys" 9 | readme = "README.md" 10 | categories = ["external-ffi-bindings"] 11 | license = "MIT" 12 | links = "lua" 13 | build = "build/main.rs" 14 | description = """ 15 | Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau 16 | """ 17 | 18 | [package.metadata.docs.rs] 19 | features = ["lua54", "vendored"] 20 | rustdoc-args = ["--cfg", "docsrs"] 21 | 22 | [features] 23 | lua54 = [] 24 | lua53 = [] 25 | lua52 = [] 26 | lua51 = [] 27 | luajit = [] 28 | luajit52 = ["luajit"] 29 | luau = ["luau0-src"] 30 | luau-codegen = ["luau"] 31 | luau-vector4 = ["luau"] 32 | vendored = ["lua-src", "luajit-src"] 33 | module = [] 34 | 35 | [dependencies] 36 | 37 | [build-dependencies] 38 | cc = "1.0" 39 | cfg-if = "1.0" 40 | pkg-config = "0.3.17" 41 | lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true } 42 | luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } 43 | luau0-src = { version = "0.15.0", optional = true } 44 | 45 | [lints.rust] 46 | unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } 47 | -------------------------------------------------------------------------------- /mlua-sys/README.md: -------------------------------------------------------------------------------- 1 | # mlua-sys 2 | 3 | Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau]. 4 | 5 | Intended to be consumed by the [mlua] crate. 6 | 7 | [Luau]: https://github.com/luau-lang/luau 8 | [mlua]: https://crates.io/crates/mlua 9 | -------------------------------------------------------------------------------- /mlua-sys/build/find_normal.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::env; 4 | use std::ops::Bound; 5 | 6 | pub fn probe_lua() { 7 | let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); 8 | 9 | if target_arch == "wasm32" && cfg!(not(feature = "vendored")) { 10 | panic!("Please enable `vendored` feature to build for wasm32"); 11 | } 12 | 13 | let lib_dir = env::var("LUA_LIB").unwrap_or_default(); 14 | let lua_lib = env::var("LUA_LIB_NAME").unwrap_or_default(); 15 | 16 | println!("cargo:rerun-if-env-changed=LUA_LIB"); 17 | println!("cargo:rerun-if-env-changed=LUA_LIB_NAME"); 18 | println!("cargo:rerun-if-env-changed=LUA_LINK"); 19 | 20 | if !lua_lib.is_empty() { 21 | if !lib_dir.is_empty() { 22 | println!("cargo:rustc-link-search=native={lib_dir}"); 23 | } 24 | let mut link_lib = ""; 25 | if env::var("LUA_LINK").as_deref() == Ok("static") { 26 | link_lib = "static="; 27 | }; 28 | println!("cargo:rustc-link-lib={link_lib}{lua_lib}"); 29 | return; 30 | } 31 | 32 | // Find using `pkg-config` 33 | 34 | #[cfg(feature = "lua54")] 35 | let (incl_bound, excl_bound, alt_probe, ver) = 36 | ("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4"); 37 | #[cfg(feature = "lua53")] 38 | let (incl_bound, excl_bound, alt_probe, ver) = 39 | ("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3"); 40 | #[cfg(feature = "lua52")] 41 | let (incl_bound, excl_bound, alt_probe, ver) = 42 | ("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2"); 43 | #[cfg(feature = "lua51")] 44 | let (incl_bound, excl_bound, alt_probe, ver) = 45 | ("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1"); 46 | #[cfg(feature = "luajit")] 47 | let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT"); 48 | 49 | #[rustfmt::skip] 50 | let mut lua = pkg_config::Config::new() 51 | .range_version((Bound::Included(incl_bound), Bound::Excluded(excl_bound))) 52 | .cargo_metadata(true) 53 | .probe(if cfg!(feature = "luajit") { "luajit" } else { "lua" }); 54 | 55 | if lua.is_err() { 56 | for pkg in alt_probe { 57 | lua = pkg_config::Config::new() 58 | .cargo_metadata(true) 59 | .probe(pkg); 60 | 61 | if lua.is_ok() { 62 | break; 63 | } 64 | } 65 | } 66 | 67 | lua.unwrap_or_else(|err| panic!("cannot find Lua{ver} using `pkg-config`: {err}")); 68 | } 69 | -------------------------------------------------------------------------------- /mlua-sys/build/find_vendored.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | pub fn probe_lua() { 4 | #[cfg(feature = "lua54")] 5 | let artifacts = lua_src::Build::new().build(lua_src::Lua54); 6 | 7 | #[cfg(feature = "lua53")] 8 | let artifacts = lua_src::Build::new().build(lua_src::Lua53); 9 | 10 | #[cfg(feature = "lua52")] 11 | let artifacts = lua_src::Build::new().build(lua_src::Lua52); 12 | 13 | #[cfg(feature = "lua51")] 14 | let artifacts = lua_src::Build::new().build(lua_src::Lua51); 15 | 16 | #[cfg(feature = "luajit")] 17 | let artifacts = luajit_src::Build::new() 18 | .lua52compat(cfg!(feature = "luajit52")) 19 | .build(); 20 | 21 | #[cfg(feature = "luau")] 22 | let artifacts = luau0_src::Build::new() 23 | .enable_codegen(cfg!(feature = "luau-codegen")) 24 | .set_max_cstack_size(1000000) 25 | .set_vector_size(if cfg!(feature = "luau-vector4") { 4 } else { 3 }) 26 | .build(); 27 | 28 | artifacts.print_cargo_metadata(); 29 | } 30 | -------------------------------------------------------------------------------- /mlua-sys/build/main.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(all(feature = "lua54", not(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { 3 | include!("main_inner.rs"); 4 | } else if #[cfg(all(feature = "lua53", not(any(feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { 5 | include!("main_inner.rs"); 6 | } else if #[cfg(all(feature = "lua52", not(any(feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] { 7 | include!("main_inner.rs"); 8 | } else if #[cfg(all(feature = "lua51", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] { 9 | include!("main_inner.rs"); 10 | } else if #[cfg(all(feature = "luajit", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] { 11 | include!("main_inner.rs"); 12 | } else if #[cfg(all(feature = "luau", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] { 13 | include!("main_inner.rs"); 14 | } else { 15 | fn main() { 16 | compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mlua-sys/build/main_inner.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | cfg_if::cfg_if! { 4 | if #[cfg(any(feature = "luau", feature = "vendored"))] { 5 | #[path = "find_vendored.rs"] 6 | mod find; 7 | } else { 8 | #[path = "find_normal.rs"] 9 | mod find; 10 | } 11 | } 12 | 13 | fn main() { 14 | #[cfg(all(feature = "luau", feature = "module", windows))] 15 | compile_error!("Luau does not support `module` mode on Windows"); 16 | 17 | #[cfg(all(feature = "module", feature = "vendored"))] 18 | compile_error!("`vendored` and `module` features are mutually exclusive"); 19 | 20 | println!("cargo:rerun-if-changed=build"); 21 | 22 | let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); 23 | if target_os == "windows" && cfg!(feature = "module") { 24 | if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() { 25 | // Don't use raw-dylib linking 26 | find::probe_lua(); 27 | return; 28 | } 29 | 30 | println!("cargo:rustc-cfg=raw_dylib"); 31 | } 32 | 33 | #[cfg(not(feature = "module"))] 34 | find::probe_lua(); 35 | } 36 | -------------------------------------------------------------------------------- /mlua-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau. 2 | 3 | #![allow(non_camel_case_types, non_snake_case)] 4 | #![allow(clippy::missing_safety_doc)] 5 | #![allow(unsafe_op_in_unsafe_fn)] 6 | #![doc(test(attr(deny(warnings))))] 7 | #![cfg_attr(docsrs, feature(doc_cfg))] 8 | 9 | use std::os::raw::c_int; 10 | 11 | #[cfg(any(feature = "lua54", doc))] 12 | pub use lua54::*; 13 | 14 | #[cfg(any(feature = "lua53", doc))] 15 | pub use lua53::*; 16 | 17 | #[cfg(any(feature = "lua52", doc))] 18 | pub use lua52::*; 19 | 20 | #[cfg(any(feature = "lua51", feature = "luajit", doc))] 21 | pub use lua51::*; 22 | 23 | #[cfg(any(feature = "luau", doc))] 24 | pub use luau::*; 25 | 26 | #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] 27 | #[doc(hidden)] 28 | pub const LUA_MAX_UPVALUES: c_int = 255; 29 | 30 | #[cfg(any(feature = "lua51", feature = "luajit"))] 31 | #[doc(hidden)] 32 | pub const LUA_MAX_UPVALUES: c_int = 60; 33 | 34 | #[cfg(feature = "luau")] 35 | #[doc(hidden)] 36 | pub const LUA_MAX_UPVALUES: c_int = 200; 37 | 38 | // I believe `luaL_traceback` < 5.4 requires this much free stack to not error. 39 | // 5.4 uses `luaL_Buffer` 40 | #[doc(hidden)] 41 | pub const LUA_TRACEBACK_STACK: c_int = 11; 42 | 43 | // Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs 44 | // The minimum alignment guaranteed by the architecture. This value is used to 45 | // add fast paths for low alignment values. 46 | #[cfg(any( 47 | target_arch = "x86", 48 | target_arch = "arm", 49 | target_arch = "m68k", 50 | target_arch = "csky", 51 | target_arch = "mips", 52 | target_arch = "mips32r6", 53 | target_arch = "powerpc", 54 | target_arch = "powerpc64", 55 | target_arch = "sparc", 56 | target_arch = "wasm32", 57 | target_arch = "hexagon", 58 | all(target_arch = "riscv32", not(any(target_os = "espidf", target_os = "zkvm"))), 59 | all(target_arch = "xtensa", not(target_os = "espidf")), 60 | ))] 61 | #[doc(hidden)] 62 | pub const SYS_MIN_ALIGN: usize = 8; 63 | #[cfg(any( 64 | target_arch = "x86_64", 65 | target_arch = "aarch64", 66 | target_arch = "arm64ec", 67 | target_arch = "loongarch64", 68 | target_arch = "mips64", 69 | target_arch = "mips64r6", 70 | target_arch = "s390x", 71 | target_arch = "sparc64", 72 | target_arch = "riscv64", 73 | target_arch = "wasm64", 74 | ))] 75 | #[doc(hidden)] 76 | pub const SYS_MIN_ALIGN: usize = 16; 77 | // The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment. 78 | #[cfg(any( 79 | all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")), 80 | all(target_arch = "xtensa", target_os = "espidf"), 81 | ))] 82 | #[doc(hidden)] 83 | pub const SYS_MIN_ALIGN: usize = 4; 84 | 85 | #[macro_use] 86 | mod macros; 87 | 88 | #[cfg(any(feature = "lua54", doc))] 89 | #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] 90 | pub mod lua54; 91 | 92 | #[cfg(any(feature = "lua53", doc))] 93 | #[cfg_attr(docsrs, doc(cfg(feature = "lua53")))] 94 | pub mod lua53; 95 | 96 | #[cfg(any(feature = "lua52", doc))] 97 | #[cfg_attr(docsrs, doc(cfg(feature = "lua52")))] 98 | pub mod lua52; 99 | 100 | #[cfg(any(feature = "lua51", feature = "luajit", doc))] 101 | #[cfg_attr(docsrs, doc(cfg(any(feature = "lua51", feature = "luajit"))))] 102 | pub mod lua51; 103 | 104 | #[cfg(any(feature = "luau", doc))] 105 | #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] 106 | pub mod luau; 107 | -------------------------------------------------------------------------------- /mlua-sys/src/lua51/lauxlib.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `lauxlib.h`. 2 | 3 | use std::os::raw::{c_char, c_int, c_void}; 4 | use std::ptr; 5 | 6 | use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; 7 | 8 | // Extra error code for 'luaL_load' 9 | pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; 10 | 11 | // Key, in the registry, for table of loaded modules 12 | pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); 13 | 14 | #[repr(C)] 15 | pub struct luaL_Reg { 16 | pub name: *const c_char, 17 | pub func: lua_CFunction, 18 | } 19 | 20 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] 21 | unsafe extern "C-unwind" { 22 | pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg); 23 | #[link_name = "luaL_getmetafield"] 24 | pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; 25 | pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; 26 | pub fn luaL_typerror(L: *mut lua_State, narg: c_int, tname: *const c_char) -> c_int; 27 | pub fn luaL_argerror(L: *mut lua_State, narg: c_int, extramsg: *const c_char) -> c_int; 28 | pub fn luaL_checklstring(L: *mut lua_State, narg: c_int, l: *mut usize) -> *const c_char; 29 | pub fn luaL_optlstring( 30 | L: *mut lua_State, 31 | narg: c_int, 32 | def: *const c_char, 33 | l: *mut usize, 34 | ) -> *const c_char; 35 | pub fn luaL_checknumber(L: *mut lua_State, narg: c_int) -> lua_Number; 36 | pub fn luaL_optnumber(L: *mut lua_State, narg: c_int, def: lua_Number) -> lua_Number; 37 | pub fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer; 38 | pub fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer; 39 | #[link_name = "luaL_checkstack"] 40 | pub fn luaL_checkstack_(L: *mut lua_State, sz: c_int, msg: *const c_char); 41 | pub fn luaL_checktype(L: *mut lua_State, narg: c_int, t: c_int); 42 | pub fn luaL_checkany(L: *mut lua_State, narg: c_int); 43 | 44 | #[link_name = "luaL_newmetatable"] 45 | pub fn luaL_newmetatable_(L: *mut lua_State, tname: *const c_char) -> c_int; 46 | pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; 47 | 48 | pub fn luaL_where(L: *mut lua_State, lvl: c_int); 49 | pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int; 50 | 51 | pub fn luaL_checkoption( 52 | L: *mut lua_State, 53 | narg: c_int, 54 | def: *const c_char, 55 | lst: *const *const c_char, 56 | ) -> c_int; 57 | } 58 | 59 | // Pre-defined references 60 | pub const LUA_NOREF: c_int = -2; 61 | pub const LUA_REFNIL: c_int = -1; 62 | 63 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] 64 | unsafe extern "C-unwind" { 65 | pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; 66 | pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); 67 | 68 | pub fn luaL_loadfile(L: *mut lua_State, filename: *const c_char) -> c_int; 69 | pub fn luaL_loadbuffer(L: *mut lua_State, buff: *const c_char, sz: usize, name: *const c_char) -> c_int; 70 | pub fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int; 71 | 72 | pub fn luaL_newstate() -> *mut lua_State; 73 | 74 | pub fn luaL_gsub( 75 | L: *mut lua_State, 76 | s: *const c_char, 77 | p: *const c_char, 78 | r: *const c_char, 79 | ) -> *const c_char; 80 | 81 | pub fn luaL_findtable( 82 | L: *mut lua_State, 83 | idx: c_int, 84 | fname: *const c_char, 85 | szhint: c_int, 86 | ) -> *const c_char; 87 | } 88 | 89 | // 90 | // Some useful macros (implemented as Rust functions) 91 | // 92 | 93 | #[inline(always)] 94 | pub unsafe fn luaL_argcheck(L: *mut lua_State, cond: c_int, narg: c_int, extramsg: *const c_char) { 95 | if cond == 0 { 96 | luaL_argerror(L, narg, extramsg); 97 | } 98 | } 99 | 100 | #[inline(always)] 101 | pub unsafe fn luaL_checkstring(L: *mut lua_State, n: c_int) -> *const c_char { 102 | luaL_checklstring(L, n, ptr::null_mut()) 103 | } 104 | 105 | #[inline(always)] 106 | pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *const c_char { 107 | luaL_optlstring(L, n, d, ptr::null_mut()) 108 | } 109 | 110 | // Deprecated from 5.3: luaL_checkint, luaL_optint, luaL_checklong, luaL_optlong 111 | 112 | #[inline(always)] 113 | pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char { 114 | lua::lua_typename(L, lua::lua_type(L, i)) 115 | } 116 | 117 | pub unsafe fn luaL_dofile(L: *mut lua_State, filename: *const c_char) -> c_int { 118 | let status = luaL_loadfile(L, filename); 119 | if status == 0 { 120 | lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0) 121 | } else { 122 | status 123 | } 124 | } 125 | 126 | #[inline(always)] 127 | pub unsafe fn luaL_dostring(L: *mut lua_State, s: *const c_char) -> c_int { 128 | let status = luaL_loadstring(L, s); 129 | if status == 0 { 130 | lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0) 131 | } else { 132 | status 133 | } 134 | } 135 | 136 | #[inline(always)] 137 | pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { 138 | lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n); 139 | } 140 | 141 | // TODO: luaL_opt 142 | 143 | // 144 | // TODO: Generic Buffer Manipulation 145 | // 146 | -------------------------------------------------------------------------------- /mlua-sys/src/lua51/lualib.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `lualib.h`. 2 | 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | use super::lua::lua_State; 6 | 7 | pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); 8 | pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); 9 | pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); 10 | pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); 11 | pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); 12 | pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); 13 | pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); 14 | pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); 15 | 16 | #[cfg(feature = "luajit")] 17 | pub const LUA_BITLIBNAME: *const c_char = cstr!("bit"); 18 | #[cfg(feature = "luajit")] 19 | pub const LUA_JITLIBNAME: *const c_char = cstr!("jit"); 20 | #[cfg(feature = "luajit")] 21 | pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi"); 22 | 23 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))] 24 | unsafe extern "C-unwind" { 25 | pub fn luaopen_base(L: *mut lua_State) -> c_int; 26 | pub fn luaopen_table(L: *mut lua_State) -> c_int; 27 | pub fn luaopen_io(L: *mut lua_State) -> c_int; 28 | pub fn luaopen_os(L: *mut lua_State) -> c_int; 29 | pub fn luaopen_string(L: *mut lua_State) -> c_int; 30 | pub fn luaopen_math(L: *mut lua_State) -> c_int; 31 | pub fn luaopen_debug(L: *mut lua_State) -> c_int; 32 | pub fn luaopen_package(L: *mut lua_State) -> c_int; 33 | 34 | #[cfg(feature = "luajit")] 35 | pub fn luaopen_bit(L: *mut lua_State) -> c_int; 36 | #[cfg(feature = "luajit")] 37 | pub fn luaopen_jit(L: *mut lua_State) -> c_int; 38 | #[cfg(feature = "luajit")] 39 | pub fn luaopen_ffi(L: *mut lua_State) -> c_int; 40 | 41 | // open all builtin libraries 42 | pub fn luaL_openlibs(L: *mut lua_State); 43 | } 44 | -------------------------------------------------------------------------------- /mlua-sys/src/lua51/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low level bindings to Lua 5.1. 2 | 3 | pub use compat::*; 4 | pub use lauxlib::*; 5 | pub use lua::*; 6 | pub use lualib::*; 7 | 8 | pub mod compat; 9 | pub mod lauxlib; 10 | pub mod lua; 11 | pub mod lualib; 12 | -------------------------------------------------------------------------------- /mlua-sys/src/lua52/lualib.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `lualib.h`. 2 | 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | use super::lua::lua_State; 6 | 7 | pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); 8 | pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); 9 | pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); 10 | pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); 11 | pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); 12 | pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); 13 | pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); 14 | pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); 15 | pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); 16 | 17 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))] 18 | unsafe extern "C-unwind" { 19 | pub fn luaopen_base(L: *mut lua_State) -> c_int; 20 | pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; 21 | pub fn luaopen_table(L: *mut lua_State) -> c_int; 22 | pub fn luaopen_io(L: *mut lua_State) -> c_int; 23 | pub fn luaopen_os(L: *mut lua_State) -> c_int; 24 | pub fn luaopen_string(L: *mut lua_State) -> c_int; 25 | pub fn luaopen_bit32(L: *mut lua_State) -> c_int; 26 | pub fn luaopen_math(L: *mut lua_State) -> c_int; 27 | pub fn luaopen_debug(L: *mut lua_State) -> c_int; 28 | pub fn luaopen_package(L: *mut lua_State) -> c_int; 29 | 30 | // open all builtin libraries 31 | pub fn luaL_openlibs(L: *mut lua_State); 32 | } 33 | -------------------------------------------------------------------------------- /mlua-sys/src/lua52/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low level bindings to Lua 5.2. 2 | 3 | pub use compat::*; 4 | pub use lauxlib::*; 5 | pub use lua::*; 6 | pub use lualib::*; 7 | 8 | pub mod compat; 9 | pub mod lauxlib; 10 | pub mod lua; 11 | pub mod lualib; 12 | -------------------------------------------------------------------------------- /mlua-sys/src/lua53/compat.rs: -------------------------------------------------------------------------------- 1 | //! MLua compatibility layer for Lua 5.3 2 | 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | use super::lauxlib::*; 6 | use super::lua::*; 7 | 8 | #[inline(always)] 9 | pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int { 10 | let ret = lua_resume_(L, from, narg); 11 | if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) { 12 | *nres = lua_gettop(L); 13 | } 14 | ret 15 | } 16 | 17 | pub unsafe fn luaL_loadbufferenv( 18 | L: *mut lua_State, 19 | data: *const c_char, 20 | size: usize, 21 | name: *const c_char, 22 | mode: *const c_char, 23 | mut env: c_int, 24 | ) -> c_int { 25 | if env != 0 { 26 | env = lua_absindex(L, env); 27 | } 28 | let status = luaL_loadbufferx(L, data, size, name, mode); 29 | if status == LUA_OK && env != 0 { 30 | lua_pushvalue(L, env); 31 | lua_setupvalue(L, -2, 1); 32 | } 33 | status 34 | } 35 | -------------------------------------------------------------------------------- /mlua-sys/src/lua53/lauxlib.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `lauxlib.h`. 2 | 3 | use std::os::raw::{c_char, c_int, c_void}; 4 | use std::ptr; 5 | 6 | use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; 7 | 8 | // Extra error code for 'luaL_loadfilex' 9 | pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1; 10 | 11 | // Key, in the registry, for table of loaded modules 12 | pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED"); 13 | 14 | // Key, in the registry, for table of preloaded loaders 15 | pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD"); 16 | 17 | #[repr(C)] 18 | pub struct luaL_Reg { 19 | pub name: *const c_char, 20 | pub func: lua_CFunction, 21 | } 22 | 23 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] 24 | unsafe extern "C-unwind" { 25 | pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize); 26 | 27 | pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; 28 | pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; 29 | #[link_name = "luaL_tolstring"] 30 | pub fn luaL_tolstring_(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; 31 | pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; 32 | pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; 33 | pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) 34 | -> *const c_char; 35 | pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; 36 | pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; 37 | pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; 38 | pub fn luaL_optinteger(L: *mut lua_State, arg: c_int, def: lua_Integer) -> lua_Integer; 39 | 40 | pub fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char); 41 | pub fn luaL_checktype(L: *mut lua_State, arg: c_int, t: c_int); 42 | pub fn luaL_checkany(L: *mut lua_State, arg: c_int); 43 | 44 | pub fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int; 45 | pub fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char); 46 | pub fn luaL_testudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; 47 | pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void; 48 | 49 | pub fn luaL_where(L: *mut lua_State, lvl: c_int); 50 | pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int; 51 | 52 | pub fn luaL_checkoption( 53 | L: *mut lua_State, 54 | arg: c_int, 55 | def: *const c_char, 56 | lst: *const *const c_char, 57 | ) -> c_int; 58 | 59 | pub fn luaL_fileresult(L: *mut lua_State, stat: c_int, fname: *const c_char) -> c_int; 60 | pub fn luaL_execresult(L: *mut lua_State, stat: c_int) -> c_int; 61 | } 62 | 63 | // Pre-defined references 64 | pub const LUA_NOREF: c_int = -2; 65 | pub const LUA_REFNIL: c_int = -1; 66 | 67 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] 68 | unsafe extern "C-unwind" { 69 | pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int; 70 | pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int); 71 | 72 | pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int; 73 | } 74 | 75 | #[inline(always)] 76 | pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int { 77 | luaL_loadfilex(L, f, ptr::null()) 78 | } 79 | 80 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] 81 | unsafe extern "C-unwind" { 82 | pub fn luaL_loadbufferx( 83 | L: *mut lua_State, 84 | buff: *const c_char, 85 | sz: usize, 86 | name: *const c_char, 87 | mode: *const c_char, 88 | ) -> c_int; 89 | pub fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int; 90 | 91 | pub fn luaL_newstate() -> *mut lua_State; 92 | 93 | pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer; 94 | 95 | pub fn luaL_gsub( 96 | L: *mut lua_State, 97 | s: *const c_char, 98 | p: *const c_char, 99 | r: *const c_char, 100 | ) -> *const c_char; 101 | 102 | pub fn luaL_setfuncs(L: *mut lua_State, l: *const luaL_Reg, nup: c_int); 103 | 104 | pub fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int; 105 | 106 | pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int); 107 | 108 | pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int); 109 | } 110 | 111 | // 112 | // Some useful macros (implemented as Rust functions) 113 | // 114 | 115 | // TODO: luaL_newlibtable, luaL_newlib 116 | 117 | #[inline(always)] 118 | pub unsafe fn luaL_argcheck(L: *mut lua_State, cond: c_int, arg: c_int, extramsg: *const c_char) { 119 | if cond == 0 { 120 | luaL_argerror(L, arg, extramsg); 121 | } 122 | } 123 | 124 | #[inline(always)] 125 | pub unsafe fn luaL_checkstring(L: *mut lua_State, n: c_int) -> *const c_char { 126 | luaL_checklstring(L, n, ptr::null_mut()) 127 | } 128 | 129 | #[inline(always)] 130 | pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *const c_char { 131 | luaL_optlstring(L, n, d, ptr::null_mut()) 132 | } 133 | 134 | #[inline(always)] 135 | pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char { 136 | lua::lua_typename(L, lua::lua_type(L, i)) 137 | } 138 | 139 | #[inline(always)] 140 | pub unsafe fn luaL_dofile(L: *mut lua_State, filename: *const c_char) -> c_int { 141 | let status = luaL_loadfile(L, filename); 142 | if status == 0 { 143 | lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0) 144 | } else { 145 | status 146 | } 147 | } 148 | 149 | #[inline(always)] 150 | pub unsafe fn luaL_dostring(L: *mut lua_State, s: *const c_char) -> c_int { 151 | let status = luaL_loadstring(L, s); 152 | if status == 0 { 153 | lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0) 154 | } else { 155 | status 156 | } 157 | } 158 | 159 | #[inline(always)] 160 | pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) { 161 | lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n); 162 | } 163 | 164 | #[inline(always)] 165 | pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char { 166 | luaL_tolstring_(L, lua::lua_absindex(L, idx), len) 167 | } 168 | 169 | // luaL_opt would be implemented here but it is undocumented, so it's omitted 170 | 171 | #[inline(always)] 172 | pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { 173 | luaL_loadbufferx(L, s, sz, n, ptr::null()) 174 | } 175 | 176 | // 177 | // TODO: Generic Buffer Manipulation 178 | // 179 | -------------------------------------------------------------------------------- /mlua-sys/src/lua53/lualib.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `lualib.h`. 2 | 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | use super::lua::lua_State; 6 | 7 | pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); 8 | pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); 9 | pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); 10 | pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); 11 | pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); 12 | pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); 13 | pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); 14 | pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); 15 | pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); 16 | pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); 17 | 18 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))] 19 | unsafe extern "C-unwind" { 20 | pub fn luaopen_base(L: *mut lua_State) -> c_int; 21 | pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; 22 | pub fn luaopen_table(L: *mut lua_State) -> c_int; 23 | pub fn luaopen_io(L: *mut lua_State) -> c_int; 24 | pub fn luaopen_os(L: *mut lua_State) -> c_int; 25 | pub fn luaopen_string(L: *mut lua_State) -> c_int; 26 | pub fn luaopen_utf8(L: *mut lua_State) -> c_int; 27 | pub fn luaopen_bit32(L: *mut lua_State) -> c_int; 28 | pub fn luaopen_math(L: *mut lua_State) -> c_int; 29 | pub fn luaopen_debug(L: *mut lua_State) -> c_int; 30 | pub fn luaopen_package(L: *mut lua_State) -> c_int; 31 | 32 | // open all builtin libraries 33 | pub fn luaL_openlibs(L: *mut lua_State); 34 | } 35 | -------------------------------------------------------------------------------- /mlua-sys/src/lua53/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low level bindings to Lua 5.3. 2 | 3 | pub use compat::*; 4 | pub use lauxlib::*; 5 | pub use lua::*; 6 | pub use lualib::*; 7 | 8 | pub mod compat; 9 | pub mod lauxlib; 10 | pub mod lua; 11 | pub mod lualib; 12 | -------------------------------------------------------------------------------- /mlua-sys/src/lua54/lualib.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `lualib.h`. 2 | 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | use super::lua::lua_State; 6 | 7 | pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); 8 | pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); 9 | pub const LUA_IOLIBNAME: *const c_char = cstr!("io"); 10 | pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); 11 | pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); 12 | pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); 13 | pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); 14 | pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); 15 | pub const LUA_LOADLIBNAME: *const c_char = cstr!("package"); 16 | 17 | #[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))] 18 | unsafe extern "C-unwind" { 19 | pub fn luaopen_base(L: *mut lua_State) -> c_int; 20 | pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; 21 | pub fn luaopen_table(L: *mut lua_State) -> c_int; 22 | pub fn luaopen_io(L: *mut lua_State) -> c_int; 23 | pub fn luaopen_os(L: *mut lua_State) -> c_int; 24 | pub fn luaopen_string(L: *mut lua_State) -> c_int; 25 | pub fn luaopen_utf8(L: *mut lua_State) -> c_int; 26 | pub fn luaopen_math(L: *mut lua_State) -> c_int; 27 | pub fn luaopen_debug(L: *mut lua_State) -> c_int; 28 | pub fn luaopen_package(L: *mut lua_State) -> c_int; 29 | 30 | // open all builtin libraries 31 | pub fn luaL_openlibs(L: *mut lua_State); 32 | } 33 | -------------------------------------------------------------------------------- /mlua-sys/src/lua54/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low level bindings to Lua 5.4. 2 | 3 | pub use lauxlib::*; 4 | pub use lua::*; 5 | pub use lualib::*; 6 | 7 | pub mod lauxlib; 8 | pub mod lua; 9 | pub mod lualib; 10 | -------------------------------------------------------------------------------- /mlua-sys/src/luau/luacode.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `luacode.h`. 2 | 3 | use std::marker::{PhantomData, PhantomPinned}; 4 | use std::os::raw::{c_char, c_int, c_void}; 5 | use std::{ptr, slice}; 6 | 7 | #[repr(C)] 8 | #[non_exhaustive] 9 | pub struct lua_CompileOptions { 10 | pub optimizationLevel: c_int, 11 | pub debugLevel: c_int, 12 | pub typeInfoLevel: c_int, 13 | pub coverageLevel: c_int, 14 | pub vectorLib: *const c_char, 15 | pub vectorCtor: *const c_char, 16 | pub vectorType: *const c_char, 17 | pub mutableGlobals: *const *const c_char, 18 | pub userdataTypes: *const *const c_char, 19 | pub librariesWithKnownMembers: *const *const c_char, 20 | pub libraryMemberTypeCallback: Option, 21 | pub libraryMemberConstantCallback: Option, 22 | pub disabledBuiltins: *const *const c_char, 23 | } 24 | 25 | impl Default for lua_CompileOptions { 26 | fn default() -> Self { 27 | Self { 28 | optimizationLevel: 1, 29 | debugLevel: 1, 30 | typeInfoLevel: 0, 31 | coverageLevel: 0, 32 | vectorLib: ptr::null(), 33 | vectorCtor: ptr::null(), 34 | vectorType: ptr::null(), 35 | mutableGlobals: ptr::null(), 36 | userdataTypes: ptr::null(), 37 | librariesWithKnownMembers: ptr::null(), 38 | libraryMemberTypeCallback: None, 39 | libraryMemberConstantCallback: None, 40 | disabledBuiltins: ptr::null(), 41 | } 42 | } 43 | } 44 | 45 | #[repr(C)] 46 | pub struct lua_CompileConstant { 47 | _data: [u8; 0], 48 | _marker: PhantomData<(*mut u8, PhantomPinned)>, 49 | } 50 | 51 | /// Type table tags 52 | #[doc(hidden)] 53 | #[repr(i32)] 54 | #[non_exhaustive] 55 | pub enum luau_BytecodeType { 56 | Nil = 0, 57 | Boolean, 58 | Number, 59 | String, 60 | Table, 61 | Function, 62 | Thread, 63 | UserData, 64 | Vector, 65 | Buffer, 66 | 67 | Any = 15, 68 | } 69 | 70 | pub type lua_LibraryMemberTypeCallback = 71 | unsafe extern "C-unwind" fn(library: *const c_char, member: *const c_char) -> c_int; 72 | 73 | pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn( 74 | library: *const c_char, 75 | member: *const c_char, 76 | constant: *mut lua_CompileConstant, 77 | ); 78 | 79 | unsafe extern "C" { 80 | pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant); 81 | pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int); 82 | pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64); 83 | pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32); 84 | pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize); 85 | } 86 | 87 | unsafe extern "C-unwind" { 88 | #[link_name = "luau_compile"] 89 | pub fn luau_compile_( 90 | source: *const c_char, 91 | size: usize, 92 | options: *mut lua_CompileOptions, 93 | outsize: *mut usize, 94 | ) -> *mut c_char; 95 | } 96 | 97 | unsafe extern "C" { 98 | fn free(p: *mut c_void); 99 | } 100 | 101 | pub unsafe fn luau_compile(source: &[u8], mut options: lua_CompileOptions) -> Vec { 102 | let mut outsize = 0; 103 | let data_ptr = luau_compile_( 104 | source.as_ptr() as *const c_char, 105 | source.len(), 106 | &mut options, 107 | &mut outsize, 108 | ); 109 | assert!(!data_ptr.is_null(), "luau_compile failed"); 110 | let data = slice::from_raw_parts(data_ptr as *mut u8, outsize).to_vec(); 111 | free(data_ptr as *mut c_void); 112 | data 113 | } 114 | -------------------------------------------------------------------------------- /mlua-sys/src/luau/luacodegen.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `luacodegen.h`. 2 | 3 | use std::os::raw::c_int; 4 | 5 | use super::lua::lua_State; 6 | 7 | unsafe extern "C-unwind" { 8 | pub fn luau_codegen_supported() -> c_int; 9 | pub fn luau_codegen_create(state: *mut lua_State); 10 | pub fn luau_codegen_compile(state: *mut lua_State, idx: c_int); 11 | } 12 | -------------------------------------------------------------------------------- /mlua-sys/src/luau/lualib.rs: -------------------------------------------------------------------------------- 1 | //! Contains definitions from `lualib.h`. 2 | 3 | use std::os::raw::{c_char, c_int}; 4 | 5 | use super::lua::lua_State; 6 | 7 | pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine"); 8 | pub const LUA_TABLIBNAME: *const c_char = cstr!("table"); 9 | pub const LUA_OSLIBNAME: *const c_char = cstr!("os"); 10 | pub const LUA_STRLIBNAME: *const c_char = cstr!("string"); 11 | pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32"); 12 | pub const LUA_BUFFERLIBNAME: *const c_char = cstr!("buffer"); 13 | pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8"); 14 | pub const LUA_MATHLIBNAME: *const c_char = cstr!("math"); 15 | pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); 16 | pub const LUA_VECLIBNAME: *const c_char = cstr!("vector"); 17 | 18 | unsafe extern "C-unwind" { 19 | pub fn luaopen_base(L: *mut lua_State) -> c_int; 20 | pub fn luaopen_coroutine(L: *mut lua_State) -> c_int; 21 | pub fn luaopen_table(L: *mut lua_State) -> c_int; 22 | pub fn luaopen_os(L: *mut lua_State) -> c_int; 23 | pub fn luaopen_string(L: *mut lua_State) -> c_int; 24 | pub fn luaopen_bit32(L: *mut lua_State) -> c_int; 25 | pub fn luaopen_buffer(L: *mut lua_State) -> c_int; 26 | pub fn luaopen_utf8(L: *mut lua_State) -> c_int; 27 | pub fn luaopen_math(L: *mut lua_State) -> c_int; 28 | pub fn luaopen_debug(L: *mut lua_State) -> c_int; 29 | pub fn luaopen_vector(L: *mut lua_State) -> c_int; 30 | 31 | // open all builtin libraries 32 | pub fn luaL_openlibs(L: *mut lua_State); 33 | } 34 | -------------------------------------------------------------------------------- /mlua-sys/src/luau/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low level bindings to Luau. 2 | 3 | pub use compat::*; 4 | pub use lauxlib::*; 5 | pub use lua::*; 6 | pub use luacode::*; 7 | pub use luacodegen::*; 8 | pub use lualib::*; 9 | pub use luarequire::*; 10 | 11 | pub mod compat; 12 | pub mod lauxlib; 13 | pub mod lua; 14 | pub mod luacode; 15 | pub mod luacodegen; 16 | pub mod lualib; 17 | pub mod luarequire; 18 | -------------------------------------------------------------------------------- /mlua-sys/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_macros)] 2 | macro_rules! cstr { 3 | ($s:expr) => { 4 | concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] as *const ::std::os::raw::c_char 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /mlua_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mlua_derive" 3 | version = "0.11.0-beta.1" 4 | authors = ["Aleksandr Orlenko "] 5 | edition = "2021" 6 | description = "Procedural macros for the mlua crate." 7 | repository = "https://github.com/mlua-rs/mlua" 8 | keywords = ["lua", "mlua"] 9 | license = "MIT" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [features] 15 | macros = ["proc-macro-error2", "itertools", "regex", "once_cell"] 16 | 17 | [dependencies] 18 | quote = "1.0" 19 | proc-macro2 = { version = "1.0", features = ["span-locations"] } 20 | proc-macro-error2 = { version = "2.0.1", optional = true } 21 | syn = { version = "2.0", features = ["full"] } 22 | itertools = { version = "0.14", optional = true } 23 | regex = { version = "1.4", optional = true } 24 | once_cell = { version = "1.0", optional = true } 25 | -------------------------------------------------------------------------------- /mlua_derive/src/chunk.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{TokenStream, TokenTree}; 2 | 3 | use crate::token::{Pos, Token, Tokens}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub(crate) struct Capture { 7 | key: Token, 8 | rust: TokenTree, 9 | } 10 | 11 | impl Capture { 12 | fn new(key: Token, rust: TokenTree) -> Self { 13 | Self { key, rust } 14 | } 15 | 16 | /// Token string inside `chunk!` 17 | pub(crate) fn key(&self) -> &Token { 18 | &self.key 19 | } 20 | 21 | /// As rust variable, e.g. `x` 22 | pub(crate) fn as_rust(&self) -> &TokenTree { 23 | &self.rust 24 | } 25 | } 26 | 27 | #[derive(Debug)] 28 | pub(crate) struct Captures(Vec); 29 | 30 | impl Captures { 31 | pub(crate) fn new() -> Self { 32 | Self(Vec::new()) 33 | } 34 | 35 | pub(crate) fn add(&mut self, token: &Token) -> Capture { 36 | let tt = token.tree(); 37 | let key = token.clone(); 38 | 39 | match self.0.iter().find(|arg| arg.key() == &key) { 40 | Some(arg) => arg.clone(), 41 | None => { 42 | let arg = Capture::new(key, tt.clone()); 43 | self.0.push(arg.clone()); 44 | arg 45 | } 46 | } 47 | } 48 | 49 | pub(crate) fn captures(&self) -> &[Capture] { 50 | &self.0 51 | } 52 | } 53 | 54 | #[derive(Debug)] 55 | pub(crate) struct Chunk { 56 | source: String, 57 | caps: Captures, 58 | } 59 | 60 | impl Chunk { 61 | pub(crate) fn new(tokens: TokenStream) -> Self { 62 | let tokens = Tokens::retokenize(tokens); 63 | 64 | let mut source = String::new(); 65 | let mut caps = Captures::new(); 66 | 67 | let mut pos: Option = None; 68 | for t in tokens { 69 | if t.is_cap() { 70 | caps.add(&t); 71 | } 72 | 73 | let (line, col) = (t.start().line, t.start().column); 74 | let (prev_line, prev_col) = pos 75 | .take() 76 | .map(|lc| (lc.line, lc.column)) 77 | .unwrap_or_else(|| (line, col)); 78 | 79 | #[allow(clippy::comparison_chain)] 80 | if line > prev_line { 81 | source.push('\n'); 82 | } else if line == prev_line { 83 | for _ in 0..col.saturating_sub(prev_col) { 84 | source.push(' '); 85 | } 86 | } 87 | source.push_str(&t.to_string()); 88 | 89 | pos = Some(t.end()); 90 | } 91 | 92 | Self { 93 | source: source.trim_end().to_string(), 94 | caps, 95 | } 96 | } 97 | 98 | pub(crate) fn source(&self) -> &str { 99 | &self.source 100 | } 101 | 102 | pub(crate) fn captures(&self) -> &[Capture] { 103 | self.caps.captures() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /mlua_derive/src/from_lua.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, DeriveInput}; 4 | 5 | pub fn from_lua(input: TokenStream) -> TokenStream { 6 | let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput); 7 | 8 | let ident_str = ident.to_string(); 9 | let (impl_generics, ty_generics, _) = generics.split_for_impl(); 10 | let where_clause = match &generics.where_clause { 11 | Some(where_clause) => quote! { #where_clause, Self: 'static + Clone }, 12 | None => quote! { where Self: 'static + Clone }, 13 | }; 14 | 15 | quote! { 16 | impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause { 17 | #[inline] 18 | fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result { 19 | match value { 20 | ::mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), 21 | _ => Err(::mlua::Error::FromLuaConversionError { 22 | from: value.type_name(), 23 | to: #ident_str.to_string(), 24 | message: None, 25 | }), 26 | } 27 | } 28 | } 29 | } 30 | .into() 31 | } 32 | -------------------------------------------------------------------------------- /mlua_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Ident, Span}; 3 | use quote::quote; 4 | use syn::meta::ParseNestedMeta; 5 | use syn::{parse_macro_input, ItemFn, LitStr, Result}; 6 | 7 | #[cfg(feature = "macros")] 8 | use { 9 | crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2, 10 | proc_macro_error2::proc_macro_error, 11 | }; 12 | 13 | #[derive(Default)] 14 | struct ModuleAttributes { 15 | name: Option, 16 | skip_memory_check: bool, 17 | } 18 | 19 | impl ModuleAttributes { 20 | fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> { 21 | if meta.path.is_ident("name") { 22 | match meta.value() { 23 | Ok(value) => { 24 | self.name = Some(value.parse::()?.parse()?); 25 | } 26 | Err(_) => { 27 | return Err(meta.error("`name` attribute must have a value")); 28 | } 29 | } 30 | } else if meta.path.is_ident("skip_memory_check") { 31 | if meta.value().is_ok() { 32 | return Err(meta.error("`skip_memory_check` attribute have no values")); 33 | } 34 | self.skip_memory_check = true; 35 | } else { 36 | return Err(meta.error("unsupported module attribute")); 37 | } 38 | Ok(()) 39 | } 40 | } 41 | 42 | #[proc_macro_attribute] 43 | pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { 44 | let mut args = ModuleAttributes::default(); 45 | if !attr.is_empty() { 46 | let args_parser = syn::meta::parser(|meta| args.parse(meta)); 47 | parse_macro_input!(attr with args_parser); 48 | } 49 | 50 | let func = parse_macro_input!(item as ItemFn); 51 | let func_name = &func.sig.ident; 52 | let module_name = args.name.unwrap_or_else(|| func_name.clone()); 53 | let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site()); 54 | let skip_memory_check = if args.skip_memory_check { 55 | quote! { lua.skip_memory_check(true); } 56 | } else { 57 | quote! {} 58 | }; 59 | 60 | let wrapped = quote! { 61 | mlua::require_module_feature!(); 62 | 63 | #func 64 | 65 | #[no_mangle] 66 | unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int { 67 | mlua::Lua::entrypoint1(state, move |lua| { 68 | #skip_memory_check 69 | #func_name(lua) 70 | }) 71 | } 72 | }; 73 | 74 | wrapped.into() 75 | } 76 | 77 | #[cfg(feature = "macros")] 78 | fn to_ident(tt: &TokenTree) -> TokenStream2 { 79 | let s: TokenStream = tt.clone().into(); 80 | s.into() 81 | } 82 | 83 | #[cfg(feature = "macros")] 84 | #[proc_macro] 85 | #[proc_macro_error] 86 | pub fn chunk(input: TokenStream) -> TokenStream { 87 | let chunk = Chunk::new(input); 88 | 89 | let source = chunk.source(); 90 | 91 | let caps_len = chunk.captures().len(); 92 | let caps = chunk.captures().iter().map(|cap| { 93 | let cap_name = cap.as_rust().to_string(); 94 | let cap = to_ident(cap.as_rust()); 95 | quote! { env.raw_set(#cap_name, #cap)?; } 96 | }); 97 | 98 | let wrapped_code = quote! {{ 99 | use mlua::{AsChunk, ChunkMode, Lua, Result, Table}; 100 | use ::std::borrow::Cow; 101 | use ::std::cell::Cell; 102 | use ::std::io::Result as IoResult; 103 | 104 | struct InnerChunk Result
>(Cell>); 105 | 106 | impl AsChunk for InnerChunk 107 | where 108 | F: FnOnce(&Lua) -> Result
, 109 | { 110 | fn environment(&self, lua: &Lua) -> Result> { 111 | if #caps_len > 0 { 112 | if let Some(make_env) = self.0.take() { 113 | return make_env(lua).map(Some); 114 | } 115 | } 116 | Ok(None) 117 | } 118 | 119 | fn mode(&self) -> Option { 120 | Some(ChunkMode::Text) 121 | } 122 | 123 | fn source<'a>(&self) -> IoResult> { 124 | Ok(Cow::Borrowed((#source).as_bytes())) 125 | } 126 | } 127 | 128 | let make_env = move |lua: &Lua| -> Result
{ 129 | let globals = lua.globals(); 130 | let env = lua.create_table()?; 131 | let meta = lua.create_table()?; 132 | meta.raw_set("__index", globals.clone())?; 133 | meta.raw_set("__newindex", globals)?; 134 | 135 | // Add captured variables 136 | #(#caps)* 137 | 138 | env.set_metatable(Some(meta)); 139 | Ok(env) 140 | }; 141 | 142 | InnerChunk(Cell::new(Some(make_env))) 143 | }}; 144 | 145 | wrapped_code.into() 146 | } 147 | 148 | #[cfg(feature = "macros")] 149 | #[proc_macro_derive(FromLua)] 150 | pub fn from_lua(input: TokenStream) -> TokenStream { 151 | from_lua::from_lua(input) 152 | } 153 | 154 | #[cfg(feature = "macros")] 155 | mod chunk; 156 | #[cfg(feature = "macros")] 157 | mod from_lua; 158 | #[cfg(feature = "macros")] 159 | mod token; 160 | -------------------------------------------------------------------------------- /mlua_derive/src/token.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{Eq, PartialEq}; 2 | use std::fmt::{self, Display, Formatter}; 3 | use std::vec::IntoIter; 4 | 5 | use itertools::Itertools; 6 | use once_cell::sync::Lazy; 7 | use proc_macro::{Delimiter, Span, TokenStream, TokenTree}; 8 | use proc_macro2::Span as Span2; 9 | use regex::Regex; 10 | 11 | #[derive(Clone, Copy, Debug)] 12 | pub(crate) struct Pos { 13 | pub(crate) line: usize, 14 | pub(crate) column: usize, 15 | } 16 | 17 | impl Pos { 18 | fn new(line: usize, column: usize) -> Self { 19 | Self { line, column } 20 | } 21 | 22 | fn left(&self) -> Self { 23 | Self { 24 | line: self.line, 25 | column: self.column.saturating_sub(1), 26 | } 27 | } 28 | 29 | fn right(&self) -> Self { 30 | Self { 31 | line: self.line, 32 | column: self.column.saturating_add(1), 33 | } 34 | } 35 | } 36 | 37 | fn span_pos(span: &Span) -> (Pos, Pos) { 38 | let span2: Span2 = (*span).into(); 39 | let start = span2.start(); 40 | let end = span2.end(); 41 | 42 | // In stable, line/column information is not provided 43 | // and set to 0 (line is 1-indexed) 44 | if start.line == 0 || end.line == 0 { 45 | return fallback_span_pos(span); 46 | } 47 | 48 | (Pos::new(start.line, start.column), Pos::new(end.line, end.column)) 49 | } 50 | 51 | fn parse_pos(span: &Span) -> Option<(usize, usize)> { 52 | // Workaround to somehow retrieve location information in span in stable rust :( 53 | 54 | static RE: Lazy = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap()); 55 | 56 | match RE.captures(&format!("{span:?}")) { 57 | Some(caps) => match (caps.get(1), caps.get(2)) { 58 | (Some(start), Some(end)) => Some(( 59 | match start.as_str().parse() { 60 | Ok(v) => v, 61 | _ => return None, 62 | }, 63 | match end.as_str().parse() { 64 | Ok(v) => v, 65 | _ => return None, 66 | }, 67 | )), 68 | _ => None, 69 | }, 70 | None => None, 71 | } 72 | } 73 | 74 | fn fallback_span_pos(span: &Span) -> (Pos, Pos) { 75 | let (start, end) = match parse_pos(span) { 76 | Some(v) => v, 77 | None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"), 78 | }; 79 | (Pos::new(1, start), Pos::new(1, end)) 80 | } 81 | 82 | /// Attribute of token. 83 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 84 | enum TokenAttr { 85 | /// No attribute 86 | None, 87 | /// Starts with `$` 88 | Cap, 89 | } 90 | 91 | #[derive(Clone, Debug)] 92 | pub(crate) struct Token { 93 | source: String, 94 | tree: TokenTree, 95 | start: Pos, 96 | end: Pos, 97 | attr: TokenAttr, 98 | } 99 | 100 | impl PartialEq for Token { 101 | fn eq(&self, other: &Self) -> bool { 102 | self.source == other.source && self.attr == other.attr 103 | } 104 | } 105 | 106 | impl Eq for Token {} 107 | 108 | impl Token { 109 | fn new(tree: TokenTree) -> Self { 110 | let (start, end) = span_pos(&tree.span()); 111 | Self { 112 | source: tree.to_string(), 113 | start, 114 | end, 115 | tree, 116 | attr: TokenAttr::None, 117 | } 118 | } 119 | 120 | fn new_delim(source: String, tree: TokenTree, open: bool) -> Self { 121 | let (start, end) = span_pos(&tree.span()); 122 | let (start, end) = if open { 123 | (start, start.right()) 124 | } else { 125 | (end.left(), end) 126 | }; 127 | 128 | Self { 129 | source, 130 | tree, 131 | start, 132 | end, 133 | attr: TokenAttr::None, 134 | } 135 | } 136 | 137 | pub(crate) fn tree(&self) -> &TokenTree { 138 | &self.tree 139 | } 140 | 141 | pub(crate) fn is_cap(&self) -> bool { 142 | self.attr == TokenAttr::Cap 143 | } 144 | 145 | pub(crate) fn start(&self) -> Pos { 146 | self.start 147 | } 148 | 149 | pub(crate) fn end(&self) -> Pos { 150 | self.end 151 | } 152 | 153 | fn is(&self, s: &str) -> bool { 154 | self.source == s 155 | } 156 | 157 | fn attr(mut self, attr: TokenAttr) -> Self { 158 | self.attr = attr; 159 | self 160 | } 161 | } 162 | 163 | #[derive(Debug)] 164 | pub(crate) struct Tokens(pub(crate) Vec); 165 | 166 | impl Tokens { 167 | pub(crate) fn retokenize(tt: TokenStream) -> Tokens { 168 | Tokens( 169 | tt.into_iter() 170 | .flat_map(Tokens::from) 171 | .peekable() 172 | .batching(|iter| { 173 | // Find variable tokens 174 | let t = iter.next()?; 175 | if t.is("$") { 176 | // `$` + `ident` => `$ident` 177 | let t = iter.next().expect("$ must trail an identifier"); 178 | Some(t.attr(TokenAttr::Cap)) 179 | } else { 180 | Some(t) 181 | } 182 | }) 183 | .collect(), 184 | ) 185 | } 186 | } 187 | 188 | impl IntoIterator for Tokens { 189 | type Item = Token; 190 | type IntoIter = IntoIter; 191 | 192 | fn into_iter(self) -> Self::IntoIter { 193 | self.0.into_iter() 194 | } 195 | } 196 | 197 | impl From for Tokens { 198 | fn from(tt: TokenTree) -> Self { 199 | let tts = match tt.clone() { 200 | TokenTree::Group(g) => { 201 | let (b, e) = match g.delimiter() { 202 | Delimiter::Parenthesis => ("(", ")"), 203 | Delimiter::Brace => ("{", "}"), 204 | Delimiter::Bracket => ("[", "]"), 205 | Delimiter::None => ("", ""), 206 | }; 207 | let (b, e) = (b.into(), e.into()); 208 | 209 | vec![Token::new_delim(b, tt.clone(), true)] 210 | .into_iter() 211 | .chain(g.stream().into_iter().flat_map(Tokens::from)) 212 | .chain(vec![Token::new_delim(e, tt, false)]) 213 | .collect() 214 | } 215 | _ => vec![Token::new(tt)], 216 | }; 217 | Tokens(tts) 218 | } 219 | } 220 | 221 | impl Display for Token { 222 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 223 | write!(f, "{}", self.source) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Module" 2 | max_width = 110 3 | comment_width = 100 4 | wrap_comments = true 5 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serialize")] 2 | use serde::ser::{Serialize, Serializer}; 3 | 4 | use crate::types::ValueRef; 5 | 6 | /// A Luau buffer type. 7 | /// 8 | /// See the buffer [documentation] for more information. 9 | /// 10 | /// [documentation]: https://luau.org/library#buffer-library 11 | #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] 12 | #[derive(Clone, Debug, PartialEq)] 13 | pub struct Buffer(pub(crate) ValueRef); 14 | 15 | #[cfg_attr(not(feature = "luau"), allow(unused))] 16 | impl Buffer { 17 | /// Copies the buffer data into a new `Vec`. 18 | pub fn to_vec(&self) -> Vec { 19 | unsafe { self.as_slice().to_vec() } 20 | } 21 | 22 | /// Returns the length of the buffer. 23 | pub fn len(&self) -> usize { 24 | unsafe { self.as_slice().len() } 25 | } 26 | 27 | /// Returns `true` if the buffer is empty. 28 | #[doc(hidden)] 29 | pub fn is_empty(&self) -> bool { 30 | self.len() == 0 31 | } 32 | 33 | /// Reads given number of bytes from the buffer at the given offset. 34 | /// 35 | /// Offset is 0-based. 36 | #[track_caller] 37 | pub fn read_bytes(&self, offset: usize) -> [u8; N] { 38 | let data = unsafe { self.as_slice() }; 39 | let mut bytes = [0u8; N]; 40 | bytes.copy_from_slice(&data[offset..offset + N]); 41 | bytes 42 | } 43 | 44 | /// Writes given bytes to the buffer at the given offset. 45 | /// 46 | /// Offset is 0-based. 47 | #[track_caller] 48 | pub fn write_bytes(&self, offset: usize, bytes: &[u8]) { 49 | let data = unsafe { 50 | let (buf, size) = self.as_raw_parts(); 51 | std::slice::from_raw_parts_mut(buf, size) 52 | }; 53 | data[offset..offset + bytes.len()].copy_from_slice(bytes); 54 | } 55 | 56 | pub(crate) unsafe fn as_slice(&self) -> &[u8] { 57 | let (buf, size) = self.as_raw_parts(); 58 | std::slice::from_raw_parts(buf, size) 59 | } 60 | 61 | #[cfg(feature = "luau")] 62 | unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { 63 | let lua = self.0.lua.lock(); 64 | let mut size = 0usize; 65 | let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); 66 | mlua_assert!(!buf.is_null(), "invalid Luau buffer"); 67 | (buf as *mut u8, size) 68 | } 69 | 70 | #[cfg(not(feature = "luau"))] 71 | unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { 72 | unreachable!() 73 | } 74 | } 75 | 76 | #[cfg(feature = "serialize")] 77 | impl Serialize for Buffer { 78 | fn serialize(&self, serializer: S) -> std::result::Result { 79 | serializer.serialize_bytes(unsafe { self.as_slice() }) 80 | } 81 | } 82 | 83 | #[cfg(feature = "luau")] 84 | impl crate::types::LuaType for Buffer { 85 | const TYPE_ID: std::os::raw::c_int = ffi::LUA_TBUFFER; 86 | } 87 | -------------------------------------------------------------------------------- /src/luau/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::os::raw::c_int; 3 | use std::ptr; 4 | 5 | use crate::chunk::ChunkMode; 6 | use crate::error::Result; 7 | use crate::function::Function; 8 | use crate::state::{callback_error_ext, Lua}; 9 | use crate::traits::{FromLuaMulti, IntoLua}; 10 | 11 | pub use require::{NavigateError, Require}; 12 | 13 | // Since Luau has some missing standard functions, we re-implement them here 14 | 15 | impl Lua { 16 | /// Create a custom Luau `require` function using provided [`Require`] implementation to find 17 | /// and load modules. 18 | #[cfg(any(feature = "luau", doc))] 19 | #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] 20 | pub fn create_require_function(&self, require: R) -> Result { 21 | require::create_require_function(self, require) 22 | } 23 | 24 | pub(crate) unsafe fn configure_luau(&self) -> Result<()> { 25 | let globals = self.globals(); 26 | 27 | globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?; 28 | globals.raw_set("loadstring", self.create_c_function(lua_loadstring)?)?; 29 | 30 | // Set `_VERSION` global to include version number 31 | // The environment variable `LUAU_VERSION` set by the build script 32 | if let Some(version) = ffi::luau_version() { 33 | globals.raw_set("_VERSION", format!("Luau {version}"))?; 34 | } 35 | 36 | // Enable default `require` implementation 37 | let require = self.create_require_function(require::TextRequirer::new())?; 38 | self.globals().raw_set("require", require)?; 39 | 40 | Ok(()) 41 | } 42 | } 43 | 44 | unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int { 45 | let option = ffi::luaL_optstring(state, 1, cstr!("collect")); 46 | let option = CStr::from_ptr(option); 47 | let arg = ffi::luaL_optinteger(state, 2, 0); 48 | match option.to_str() { 49 | Ok("collect") => { 50 | ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0); 51 | 0 52 | } 53 | Ok("stop") => { 54 | ffi::lua_gc(state, ffi::LUA_GCSTOP, 0); 55 | 0 56 | } 57 | Ok("restart") => { 58 | ffi::lua_gc(state, ffi::LUA_GCRESTART, 0); 59 | 0 60 | } 61 | Ok("count") => { 62 | let kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0) as ffi::lua_Number; 63 | let kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0) as ffi::lua_Number; 64 | ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0); 65 | 1 66 | } 67 | Ok("step") => { 68 | let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _); 69 | ffi::lua_pushboolean(state, res); 70 | 1 71 | } 72 | Ok("isrunning") => { 73 | let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0); 74 | ffi::lua_pushboolean(state, res); 75 | 1 76 | } 77 | _ => ffi::luaL_error(state, cstr!("collectgarbage called with invalid option")), 78 | } 79 | } 80 | 81 | unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int { 82 | callback_error_ext(state, ptr::null_mut(), false, move |extra, nargs| { 83 | let rawlua = (*extra).raw_lua(); 84 | let (chunk, chunk_name) = 85 | <(String, Option)>::from_stack_args(nargs, 1, Some("loadstring"), rawlua)?; 86 | let chunk_name = chunk_name.as_deref().unwrap_or("=(loadstring)"); 87 | (rawlua.lua()) 88 | .load(chunk) 89 | .set_name(chunk_name) 90 | .set_mode(ChunkMode::Text) 91 | .into_function()? 92 | .push_into_stack(rawlua)?; 93 | Ok(1) 94 | }) 95 | } 96 | 97 | mod require; 98 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! bug_msg { 2 | ($arg:expr) => { 3 | concat!( 4 | "mlua internal error: ", 5 | $arg, 6 | " (this is a bug, please file an issue)" 7 | ) 8 | }; 9 | } 10 | 11 | macro_rules! cstr { 12 | ($s:expr) => { 13 | concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] as *const ::std::os::raw::c_char 14 | }; 15 | } 16 | 17 | macro_rules! mlua_panic { 18 | ($msg:expr) => { 19 | panic!(bug_msg!($msg)) 20 | }; 21 | 22 | ($msg:expr,) => { 23 | mlua_panic!($msg) 24 | }; 25 | 26 | ($msg:expr, $($arg:expr),+) => { 27 | panic!(bug_msg!($msg), $($arg),+) 28 | }; 29 | 30 | ($msg:expr, $($arg:expr),+,) => { 31 | mlua_panic!($msg, $($arg),+) 32 | }; 33 | } 34 | 35 | macro_rules! mlua_assert { 36 | ($cond:expr, $msg:expr) => { 37 | assert!($cond, bug_msg!($msg)); 38 | }; 39 | 40 | ($cond:expr, $msg:expr,) => { 41 | mlua_assert!($cond, $msg); 42 | }; 43 | 44 | ($cond:expr, $msg:expr, $($arg:expr),+) => { 45 | assert!($cond, bug_msg!($msg), $($arg),+); 46 | }; 47 | 48 | ($cond:expr, $msg:expr, $($arg:expr),+,) => { 49 | mlua_assert!($cond, $msg, $($arg),+); 50 | }; 51 | } 52 | 53 | macro_rules! mlua_debug_assert { 54 | ($cond:expr, $msg:expr) => { 55 | debug_assert!($cond, bug_msg!($msg)); 56 | }; 57 | 58 | ($cond:expr, $msg:expr,) => { 59 | mlua_debug_assert!($cond, $msg); 60 | }; 61 | 62 | ($cond:expr, $msg:expr, $($arg:expr),+) => { 63 | debug_assert!($cond, bug_msg!($msg), $($arg),+); 64 | }; 65 | 66 | ($cond:expr, $msg:expr, $($arg:expr),+,) => { 67 | mlua_debug_assert!($cond, $msg, $($arg),+); 68 | }; 69 | } 70 | 71 | macro_rules! mlua_expect { 72 | ($res:expr, $msg:expr) => { 73 | $res.expect(bug_msg!($msg)) 74 | }; 75 | 76 | ($res:expr, $msg:expr,) => { 77 | mlua_expect!($res, $msg) 78 | }; 79 | } 80 | 81 | #[cfg(feature = "module")] 82 | #[doc(hidden)] 83 | #[macro_export] 84 | macro_rules! require_module_feature { 85 | () => {}; 86 | } 87 | 88 | #[cfg(not(feature = "module"))] 89 | #[doc(hidden)] 90 | #[macro_export] 91 | macro_rules! require_module_feature { 92 | () => { 93 | compile_error!("Feature `module` must be enabled in the `mlua` crate"); 94 | }; 95 | } 96 | 97 | macro_rules! protect_lua { 98 | ($state:expr, $nargs:expr, $nresults:expr, $f:expr) => { 99 | crate::util::protect_lua_closure($state, $nargs, $nresults, $f) 100 | }; 101 | 102 | ($state:expr, $nargs:expr, $nresults:expr, fn($state_inner:ident) $code:expr) => {{ 103 | use ::std::os::raw::c_int; 104 | unsafe extern "C-unwind" fn do_call($state_inner: *mut ffi::lua_State) -> c_int { 105 | $code; 106 | let nresults = $nresults; 107 | if nresults == ::ffi::LUA_MULTRET { 108 | ffi::lua_gettop($state_inner) 109 | } else { 110 | nresults 111 | } 112 | } 113 | 114 | crate::util::protect_lua_call($state, $nargs, do_call) 115 | }}; 116 | } 117 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | use std::alloc::{self, Layout}; 2 | use std::os::raw::c_void; 3 | use std::ptr; 4 | 5 | pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator; 6 | 7 | #[repr(C)] 8 | #[derive(Default)] 9 | pub(crate) struct MemoryState { 10 | used_memory: isize, 11 | memory_limit: isize, 12 | // Can be set to temporary ignore the memory limit. 13 | // This is used when calling `lua_pushcfunction` for lua5.1/jit/luau. 14 | ignore_limit: bool, 15 | // Indicates that the memory limit was reached on the last allocation. 16 | #[cfg(feature = "luau")] 17 | limit_reached: bool, 18 | } 19 | 20 | impl MemoryState { 21 | #[cfg(feature = "luau")] 22 | #[inline] 23 | pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { 24 | let mut mem_state = ptr::null_mut(); 25 | ffi::lua_getallocf(state, &mut mem_state); 26 | mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata"); 27 | mem_state as *mut MemoryState 28 | } 29 | 30 | #[cfg(not(feature = "luau"))] 31 | #[rustversion::since(1.85)] 32 | #[inline] 33 | #[allow(clippy::incompatible_msrv)] 34 | pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { 35 | let mut mem_state = ptr::null_mut(); 36 | if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) { 37 | mem_state = ptr::null_mut(); 38 | } 39 | mem_state as *mut MemoryState 40 | } 41 | 42 | #[cfg(not(feature = "luau"))] 43 | #[rustversion::before(1.85)] 44 | #[inline] 45 | pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { 46 | let mut mem_state = ptr::null_mut(); 47 | if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR { 48 | mem_state = ptr::null_mut(); 49 | } 50 | mem_state as *mut MemoryState 51 | } 52 | 53 | #[inline] 54 | pub(crate) fn used_memory(&self) -> usize { 55 | self.used_memory as usize 56 | } 57 | 58 | #[inline] 59 | pub(crate) fn memory_limit(&self) -> usize { 60 | self.memory_limit as usize 61 | } 62 | 63 | #[inline] 64 | pub(crate) fn set_memory_limit(&mut self, limit: usize) -> usize { 65 | let prev_limit = self.memory_limit; 66 | self.memory_limit = limit as isize; 67 | prev_limit as usize 68 | } 69 | 70 | // This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit/luau 71 | // to bypass the memory limit (if set). 72 | #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] 73 | #[inline] 74 | pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) { 75 | let mem_state = Self::get(state); 76 | if !mem_state.is_null() { 77 | (*mem_state).ignore_limit = true; 78 | f(); 79 | (*mem_state).ignore_limit = false; 80 | } else { 81 | f(); 82 | } 83 | } 84 | 85 | // Does nothing apart from calling `f()`, we don't need to bypass any limits 86 | #[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))] 87 | #[inline] 88 | pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) { 89 | f(); 90 | } 91 | 92 | // Returns `true` if the memory limit was reached on the last memory operation 93 | #[cfg(feature = "luau")] 94 | #[inline] 95 | pub(crate) unsafe fn limit_reached(state: *mut ffi::lua_State) -> bool { 96 | (*Self::get(state)).limit_reached 97 | } 98 | } 99 | 100 | unsafe extern "C" fn allocator( 101 | extra: *mut c_void, 102 | ptr: *mut c_void, 103 | osize: usize, 104 | nsize: usize, 105 | ) -> *mut c_void { 106 | let mem_state = &mut *(extra as *mut MemoryState); 107 | #[cfg(feature = "luau")] 108 | { 109 | // Reset the flag 110 | mem_state.limit_reached = false; 111 | } 112 | 113 | if nsize == 0 { 114 | // Free memory 115 | if !ptr.is_null() { 116 | let layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN); 117 | alloc::dealloc(ptr as *mut u8, layout); 118 | mem_state.used_memory -= osize as isize; 119 | } 120 | return ptr::null_mut(); 121 | } 122 | 123 | // Do not allocate more than isize::MAX 124 | if nsize > isize::MAX as usize { 125 | return ptr::null_mut(); 126 | } 127 | 128 | // Are we fit to the memory limits? 129 | let mut mem_diff = nsize as isize; 130 | if !ptr.is_null() { 131 | mem_diff -= osize as isize; 132 | } 133 | let mem_limit = mem_state.memory_limit; 134 | let new_used_memory = mem_state.used_memory + mem_diff; 135 | if mem_limit > 0 && new_used_memory > mem_limit && !mem_state.ignore_limit { 136 | #[cfg(feature = "luau")] 137 | { 138 | mem_state.limit_reached = true; 139 | } 140 | return ptr::null_mut(); 141 | } 142 | mem_state.used_memory += mem_diff; 143 | 144 | if ptr.is_null() { 145 | // Allocate new memory 146 | let new_layout = match Layout::from_size_align(nsize, ffi::SYS_MIN_ALIGN) { 147 | Ok(layout) => layout, 148 | Err(_) => return ptr::null_mut(), 149 | }; 150 | let new_ptr = alloc::alloc(new_layout) as *mut c_void; 151 | if new_ptr.is_null() { 152 | alloc::handle_alloc_error(new_layout); 153 | } 154 | return new_ptr; 155 | } 156 | 157 | // Reallocate memory 158 | let old_layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN); 159 | let new_ptr = alloc::realloc(ptr as *mut u8, old_layout, nsize) as *mut c_void; 160 | if new_ptr.is_null() { 161 | alloc::handle_alloc_error(old_layout); 162 | } 163 | new_ptr 164 | } 165 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Re-exports most types with an extra `Lua*` prefix to prevent name clashes. 2 | 3 | #[doc(no_inline)] 4 | pub use crate::{ 5 | AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, 6 | Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, 7 | ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, 8 | Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, 9 | IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, 10 | MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, 11 | ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, 12 | String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, 13 | Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, 14 | UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, 15 | UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, 16 | UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, 17 | Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, 18 | }; 19 | 20 | #[cfg(not(feature = "luau"))] 21 | #[doc(no_inline)] 22 | pub use crate::HookTriggers as LuaHookTriggers; 23 | 24 | #[cfg(feature = "luau")] 25 | #[doc(no_inline)] 26 | pub use crate::{ 27 | CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, 28 | NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector, 29 | }; 30 | 31 | #[cfg(feature = "async")] 32 | #[doc(no_inline)] 33 | pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn}; 34 | 35 | #[cfg(feature = "serialize")] 36 | #[doc(no_inline)] 37 | pub use crate::{ 38 | DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions, 39 | }; 40 | -------------------------------------------------------------------------------- /src/stdlib.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; 2 | 3 | /// Flags describing the set of lua standard libraries to load. 4 | #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] 5 | pub struct StdLib(u32); 6 | 7 | impl StdLib { 8 | /// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library 9 | #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] 10 | #[cfg_attr( 11 | docsrs, 12 | doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) 13 | )] 14 | pub const COROUTINE: StdLib = StdLib(1); 15 | 16 | /// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library 17 | pub const TABLE: StdLib = StdLib(1 << 1); 18 | 19 | /// [`io`](https://www.lua.org/manual/5.4/manual.html#6.8) library 20 | #[cfg(not(feature = "luau"))] 21 | #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] 22 | pub const IO: StdLib = StdLib(1 << 2); 23 | 24 | /// [`os`](https://www.lua.org/manual/5.4/manual.html#6.9) library 25 | pub const OS: StdLib = StdLib(1 << 3); 26 | 27 | /// [`string`](https://www.lua.org/manual/5.4/manual.html#6.4) library 28 | pub const STRING: StdLib = StdLib(1 << 4); 29 | 30 | /// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library 31 | #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] 32 | #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))] 33 | pub const UTF8: StdLib = StdLib(1 << 5); 34 | 35 | /// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library 36 | #[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))] 37 | #[cfg_attr( 38 | docsrs, 39 | doc(cfg(any(feature = "lua52", feature = "luajit", feature = "luau"))) 40 | )] 41 | pub const BIT: StdLib = StdLib(1 << 6); 42 | 43 | /// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library 44 | pub const MATH: StdLib = StdLib(1 << 7); 45 | 46 | /// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library 47 | #[cfg(not(feature = "luau"))] 48 | #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] 49 | pub const PACKAGE: StdLib = StdLib(1 << 8); 50 | 51 | /// [`buffer`](https://luau.org/library#buffer-library) library 52 | #[cfg(any(feature = "luau", doc))] 53 | #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] 54 | pub const BUFFER: StdLib = StdLib(1 << 9); 55 | 56 | /// [`vector`](https://luau.org/library#vector-library) library 57 | #[cfg(any(feature = "luau", doc))] 58 | #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] 59 | pub const VECTOR: StdLib = StdLib(1 << 10); 60 | 61 | /// [`jit`](http://luajit.org/ext_jit.html) library 62 | #[cfg(any(feature = "luajit", doc))] 63 | #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] 64 | pub const JIT: StdLib = StdLib(1 << 11); 65 | 66 | /// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library 67 | #[cfg(any(feature = "luajit", doc))] 68 | #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] 69 | pub const FFI: StdLib = StdLib(1 << 30); 70 | 71 | /// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library 72 | pub const DEBUG: StdLib = StdLib(1 << 31); 73 | 74 | /// No libraries 75 | pub const NONE: StdLib = StdLib(0); 76 | /// (**unsafe**) All standard libraries 77 | pub const ALL: StdLib = StdLib(u32::MAX); 78 | /// The safe subset of the standard libraries 79 | #[cfg(not(feature = "luau"))] 80 | pub const ALL_SAFE: StdLib = StdLib((1 << 30) - 1); 81 | #[cfg(feature = "luau")] 82 | pub const ALL_SAFE: StdLib = StdLib(u32::MAX); 83 | 84 | pub fn contains(self, lib: Self) -> bool { 85 | (self & lib).0 != 0 86 | } 87 | } 88 | 89 | impl BitAnd for StdLib { 90 | type Output = Self; 91 | fn bitand(self, rhs: Self) -> Self::Output { 92 | StdLib(self.0 & rhs.0) 93 | } 94 | } 95 | 96 | impl BitAndAssign for StdLib { 97 | fn bitand_assign(&mut self, rhs: Self) { 98 | *self = StdLib(self.0 & rhs.0) 99 | } 100 | } 101 | 102 | impl BitOr for StdLib { 103 | type Output = Self; 104 | fn bitor(self, rhs: Self) -> Self::Output { 105 | StdLib(self.0 | rhs.0) 106 | } 107 | } 108 | 109 | impl BitOrAssign for StdLib { 110 | fn bitor_assign(&mut self, rhs: Self) { 111 | *self = StdLib(self.0 | rhs.0) 112 | } 113 | } 114 | 115 | impl BitXor for StdLib { 116 | type Output = Self; 117 | fn bitxor(self, rhs: Self) -> Self::Output { 118 | StdLib(self.0 ^ rhs.0) 119 | } 120 | } 121 | 122 | impl BitXorAssign for StdLib { 123 | fn bitxor_assign(&mut self, rhs: Self) { 124 | *self = StdLib(self.0 ^ rhs.0) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::os::raw::{c_int, c_void}; 3 | 4 | use crate::error::Result; 5 | #[cfg(not(feature = "luau"))] 6 | use crate::hook::{Debug, HookTriggers}; 7 | use crate::state::{ExtraData, Lua, RawLua}; 8 | 9 | // Re-export mutex wrappers 10 | pub(crate) use sync::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; 11 | 12 | #[cfg(all(feature = "async", feature = "send"))] 13 | pub(crate) type BoxFuture<'a, T> = futures_util::future::BoxFuture<'a, T>; 14 | 15 | #[cfg(all(feature = "async", not(feature = "send")))] 16 | pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>; 17 | 18 | pub use app_data::{AppData, AppDataRef, AppDataRefMut}; 19 | pub use either::Either; 20 | pub use registry_key::RegistryKey; 21 | pub(crate) use value_ref::ValueRef; 22 | 23 | /// Type of Lua integer numbers. 24 | pub type Integer = ffi::lua_Integer; 25 | /// Type of Lua floating point numbers. 26 | pub type Number = ffi::lua_Number; 27 | 28 | /// A "light" userdata value. Equivalent to an unmanaged raw pointer. 29 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 30 | pub struct LightUserData(pub *mut c_void); 31 | 32 | #[cfg(feature = "send")] 33 | unsafe impl Send for LightUserData {} 34 | #[cfg(feature = "send")] 35 | unsafe impl Sync for LightUserData {} 36 | 37 | #[cfg(feature = "send")] 38 | pub(crate) type Callback = Box Result + Send + 'static>; 39 | 40 | #[cfg(not(feature = "send"))] 41 | pub(crate) type Callback = Box Result + 'static>; 42 | 43 | pub(crate) type ScopedCallback<'s> = Box Result + 's>; 44 | 45 | pub(crate) struct Upvalue { 46 | pub(crate) data: T, 47 | pub(crate) extra: XRc>, 48 | } 49 | 50 | pub(crate) type CallbackUpvalue = Upvalue>; 51 | 52 | #[cfg(all(feature = "async", feature = "send"))] 53 | pub(crate) type AsyncCallback = 54 | Box Fn(&'a RawLua, c_int) -> BoxFuture<'a, Result> + Send + 'static>; 55 | 56 | #[cfg(all(feature = "async", not(feature = "send")))] 57 | pub(crate) type AsyncCallback = 58 | Box Fn(&'a RawLua, c_int) -> BoxFuture<'a, Result> + 'static>; 59 | 60 | #[cfg(feature = "async")] 61 | pub(crate) type AsyncCallbackUpvalue = Upvalue; 62 | 63 | #[cfg(feature = "async")] 64 | pub(crate) type AsyncPollUpvalue = Upvalue>>>; 65 | 66 | /// Type to set next Lua VM action after executing interrupt or hook function. 67 | pub enum VmState { 68 | Continue, 69 | /// Yield the current thread. 70 | /// 71 | /// Supported by Lua 5.3+ and Luau. 72 | Yield, 73 | } 74 | 75 | #[cfg(not(feature = "luau"))] 76 | pub(crate) enum HookKind { 77 | Global, 78 | Thread(HookTriggers, HookCallback), 79 | } 80 | 81 | #[cfg(all(feature = "send", not(feature = "luau")))] 82 | pub(crate) type HookCallback = XRc Result + Send>; 83 | 84 | #[cfg(all(not(feature = "send"), not(feature = "luau")))] 85 | pub(crate) type HookCallback = XRc Result>; 86 | 87 | #[cfg(all(feature = "send", feature = "luau"))] 88 | pub(crate) type InterruptCallback = XRc Result + Send>; 89 | 90 | #[cfg(all(not(feature = "send"), feature = "luau"))] 91 | pub(crate) type InterruptCallback = XRc Result>; 92 | 93 | #[cfg(all(feature = "send", feature = "luau"))] 94 | pub(crate) type ThreadCreationCallback = XRc Result<()> + Send>; 95 | 96 | #[cfg(all(not(feature = "send"), feature = "luau"))] 97 | pub(crate) type ThreadCreationCallback = XRc Result<()>>; 98 | 99 | #[cfg(all(feature = "send", feature = "luau"))] 100 | pub(crate) type ThreadCollectionCallback = XRc; 101 | 102 | #[cfg(all(not(feature = "send"), feature = "luau"))] 103 | pub(crate) type ThreadCollectionCallback = XRc; 104 | 105 | #[cfg(all(feature = "send", feature = "lua54"))] 106 | pub(crate) type WarnCallback = XRc Result<()> + Send>; 107 | 108 | #[cfg(all(not(feature = "send"), feature = "lua54"))] 109 | pub(crate) type WarnCallback = XRc Result<()>>; 110 | 111 | /// A trait that adds `Send` requirement if `send` feature is enabled. 112 | #[cfg(feature = "send")] 113 | pub trait MaybeSend: Send {} 114 | #[cfg(feature = "send")] 115 | impl MaybeSend for T {} 116 | 117 | #[cfg(not(feature = "send"))] 118 | pub trait MaybeSend {} 119 | #[cfg(not(feature = "send"))] 120 | impl MaybeSend for T {} 121 | 122 | pub(crate) struct DestructedUserdata; 123 | 124 | pub(crate) trait LuaType { 125 | const TYPE_ID: c_int; 126 | } 127 | 128 | impl LuaType for bool { 129 | const TYPE_ID: c_int = ffi::LUA_TBOOLEAN; 130 | } 131 | 132 | impl LuaType for Number { 133 | const TYPE_ID: c_int = ffi::LUA_TNUMBER; 134 | } 135 | 136 | impl LuaType for LightUserData { 137 | const TYPE_ID: c_int = ffi::LUA_TLIGHTUSERDATA; 138 | } 139 | 140 | mod app_data; 141 | mod registry_key; 142 | mod sync; 143 | mod value_ref; 144 | 145 | #[cfg(test)] 146 | mod assertions { 147 | use super::*; 148 | 149 | #[cfg(not(feature = "send"))] 150 | static_assertions::assert_not_impl_any!(ValueRef: Send); 151 | #[cfg(feature = "send")] 152 | static_assertions::assert_impl_all!(ValueRef: Send, Sync); 153 | } 154 | -------------------------------------------------------------------------------- /src/types/registry_key.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{Hash, Hasher}; 2 | use std::os::raw::c_int; 3 | use std::sync::Arc; 4 | use std::{fmt, mem, ptr}; 5 | 6 | use parking_lot::Mutex; 7 | 8 | /// An auto generated key into the Lua registry. 9 | /// 10 | /// This is a handle to a value stored inside the Lua registry. It is not automatically 11 | /// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`], 12 | /// and instances not manually removed can be garbage collected with 13 | /// [`Lua::expire_registry_values`]. 14 | /// 15 | /// Be warned, If you place this into Lua via a [`UserData`] type or a Rust callback, it is *easy* 16 | /// to accidentally cause reference cycles that the Lua garbage collector cannot resolve. Instead of 17 | /// placing a [`RegistryKey`] into a [`UserData`] type, consider to use 18 | /// [`AnyUserData::set_user_value`]. 19 | /// 20 | /// [`UserData`]: crate::UserData 21 | /// [`RegistryKey`]: crate::RegistryKey 22 | /// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value 23 | /// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values 24 | /// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value 25 | pub struct RegistryKey { 26 | pub(crate) registry_id: i32, 27 | pub(crate) unref_list: Arc>>>, 28 | } 29 | 30 | impl fmt::Debug for RegistryKey { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | write!(f, "RegistryKey({})", self.id()) 33 | } 34 | } 35 | 36 | impl Hash for RegistryKey { 37 | fn hash(&self, state: &mut H) { 38 | self.id().hash(state) 39 | } 40 | } 41 | 42 | impl PartialEq for RegistryKey { 43 | fn eq(&self, other: &RegistryKey) -> bool { 44 | self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list) 45 | } 46 | } 47 | 48 | impl Eq for RegistryKey {} 49 | 50 | impl Drop for RegistryKey { 51 | fn drop(&mut self) { 52 | let registry_id = self.id(); 53 | // We don't need to collect nil slot 54 | if registry_id > ffi::LUA_REFNIL { 55 | let mut unref_list = self.unref_list.lock(); 56 | if let Some(list) = unref_list.as_mut() { 57 | list.push(registry_id); 58 | } 59 | } 60 | } 61 | } 62 | 63 | impl RegistryKey { 64 | /// Creates a new instance of `RegistryKey` 65 | pub(crate) const fn new(id: c_int, unref_list: Arc>>>) -> Self { 66 | RegistryKey { 67 | registry_id: id, 68 | unref_list, 69 | } 70 | } 71 | 72 | /// Returns the underlying Lua reference of this `RegistryKey` 73 | #[inline(always)] 74 | pub fn id(&self) -> c_int { 75 | self.registry_id 76 | } 77 | 78 | /// Sets the unique Lua reference key of this `RegistryKey` 79 | #[inline(always)] 80 | pub(crate) fn set_id(&mut self, id: c_int) { 81 | self.registry_id = id; 82 | } 83 | 84 | /// Destroys the `RegistryKey` without adding to the unref list 85 | pub(crate) fn take(self) -> i32 { 86 | let registry_id = self.id(); 87 | unsafe { 88 | ptr::read(&self.unref_list); 89 | mem::forget(self); 90 | } 91 | registry_id 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod assertions { 97 | use super::*; 98 | 99 | static_assertions::assert_impl_all!(RegistryKey: Send, Sync); 100 | } 101 | -------------------------------------------------------------------------------- /src/types/sync.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "send")] 2 | mod inner { 3 | use parking_lot::{RawMutex, RawThreadId}; 4 | use std::sync::{Arc, Weak}; 5 | 6 | pub(crate) type XRc = Arc; 7 | pub(crate) type XWeak = Weak; 8 | 9 | pub(crate) type ReentrantMutex = parking_lot::ReentrantMutex; 10 | 11 | pub(crate) type ReentrantMutexGuard<'a, T> = parking_lot::ReentrantMutexGuard<'a, T>; 12 | 13 | pub(crate) type ArcReentrantMutexGuard = 14 | parking_lot::lock_api::ArcReentrantMutexGuard; 15 | } 16 | 17 | #[cfg(not(feature = "send"))] 18 | mod inner { 19 | use std::ops::Deref; 20 | use std::rc::{Rc, Weak}; 21 | 22 | pub(crate) type XRc = Rc; 23 | pub(crate) type XWeak = Weak; 24 | 25 | pub(crate) struct ReentrantMutex(T); 26 | 27 | impl ReentrantMutex { 28 | #[inline(always)] 29 | pub(crate) fn new(val: T) -> Self { 30 | ReentrantMutex(val) 31 | } 32 | 33 | #[inline(always)] 34 | pub(crate) fn lock(&self) -> ReentrantMutexGuard { 35 | ReentrantMutexGuard(&self.0) 36 | } 37 | 38 | #[inline(always)] 39 | pub(crate) fn lock_arc(self: &XRc) -> ArcReentrantMutexGuard { 40 | ArcReentrantMutexGuard(Rc::clone(self)) 41 | } 42 | 43 | #[inline(always)] 44 | pub(crate) fn into_lock_arc(self: XRc) -> ArcReentrantMutexGuard { 45 | ArcReentrantMutexGuard(self) 46 | } 47 | 48 | #[inline(always)] 49 | pub(crate) fn data_ptr(&self) -> *const T { 50 | &self.0 as *const _ 51 | } 52 | } 53 | 54 | pub(crate) struct ReentrantMutexGuard<'a, T>(&'a T); 55 | 56 | impl Deref for ReentrantMutexGuard<'_, T> { 57 | type Target = T; 58 | 59 | #[inline(always)] 60 | fn deref(&self) -> &Self::Target { 61 | self.0 62 | } 63 | } 64 | 65 | pub(crate) struct ArcReentrantMutexGuard(XRc>); 66 | 67 | impl Deref for ArcReentrantMutexGuard { 68 | type Target = T; 69 | 70 | #[inline(always)] 71 | fn deref(&self) -> &Self::Target { 72 | &self.0 .0 73 | } 74 | } 75 | } 76 | 77 | pub(crate) use inner::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak}; 78 | -------------------------------------------------------------------------------- /src/types/value_ref.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::os::raw::{c_int, c_void}; 3 | 4 | use crate::state::{RawLua, WeakLua}; 5 | 6 | /// A reference to a Lua (complex) value stored in the Lua auxiliary thread. 7 | pub struct ValueRef { 8 | pub(crate) lua: WeakLua, 9 | pub(crate) index: c_int, 10 | pub(crate) drop: bool, 11 | } 12 | 13 | impl ValueRef { 14 | #[inline] 15 | pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { 16 | ValueRef { 17 | lua: lua.weak().clone(), 18 | index, 19 | drop: true, 20 | } 21 | } 22 | 23 | #[inline] 24 | pub(crate) fn to_pointer(&self) -> *const c_void { 25 | let lua = self.lua.lock(); 26 | unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } 27 | } 28 | 29 | /// Returns a copy of the value, which is valid as long as the original value is held. 30 | #[inline] 31 | pub(crate) fn copy(&self) -> Self { 32 | ValueRef { 33 | lua: self.lua.clone(), 34 | index: self.index, 35 | drop: false, 36 | } 37 | } 38 | } 39 | 40 | impl fmt::Debug for ValueRef { 41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | write!(f, "Ref({:p})", self.to_pointer()) 43 | } 44 | } 45 | 46 | impl Clone for ValueRef { 47 | fn clone(&self) -> Self { 48 | unsafe { self.lua.lock().clone_ref(self) } 49 | } 50 | } 51 | 52 | impl Drop for ValueRef { 53 | fn drop(&mut self) { 54 | if self.drop { 55 | if let Some(lua) = self.lua.try_lock() { 56 | unsafe { lua.drop_ref(self) }; 57 | } 58 | } 59 | } 60 | } 61 | 62 | impl PartialEq for ValueRef { 63 | fn eq(&self, other: &Self) -> bool { 64 | assert!( 65 | self.lua == other.lua, 66 | "Lua instance passed Value created from a different main Lua state" 67 | ); 68 | let lua = self.lua.lock(); 69 | unsafe { ffi::lua_rawequal(lua.ref_thread(), self.index, other.index) == 1 } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/userdata/lock.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait UserDataLock { 2 | const INIT: Self; 3 | 4 | fn is_locked(&self) -> bool; 5 | fn try_lock_shared(&self) -> bool; 6 | fn try_lock_exclusive(&self) -> bool; 7 | 8 | unsafe fn unlock_shared(&self); 9 | unsafe fn unlock_exclusive(&self); 10 | 11 | fn try_lock_shared_guarded(&self) -> Result, ()> { 12 | if self.try_lock_shared() { 13 | Ok(LockGuard { 14 | lock: self, 15 | exclusive: false, 16 | }) 17 | } else { 18 | Err(()) 19 | } 20 | } 21 | 22 | fn try_lock_exclusive_guarded(&self) -> Result, ()> { 23 | if self.try_lock_exclusive() { 24 | Ok(LockGuard { 25 | lock: self, 26 | exclusive: true, 27 | }) 28 | } else { 29 | Err(()) 30 | } 31 | } 32 | } 33 | 34 | pub(crate) struct LockGuard<'a, L: UserDataLock + ?Sized> { 35 | lock: &'a L, 36 | exclusive: bool, 37 | } 38 | 39 | impl Drop for LockGuard<'_, L> { 40 | fn drop(&mut self) { 41 | unsafe { 42 | if self.exclusive { 43 | self.lock.unlock_exclusive(); 44 | } else { 45 | self.lock.unlock_shared(); 46 | } 47 | } 48 | } 49 | } 50 | 51 | pub(crate) use lock_impl::RawLock; 52 | 53 | #[cfg(not(feature = "send"))] 54 | #[cfg(not(tarpaulin_include))] 55 | mod lock_impl { 56 | use std::cell::Cell; 57 | 58 | // Positive values represent the number of read references. 59 | // Negative values represent the number of write references (only one allowed). 60 | pub(crate) type RawLock = Cell; 61 | 62 | const UNUSED: isize = 0; 63 | 64 | impl super::UserDataLock for RawLock { 65 | #[allow(clippy::declare_interior_mutable_const)] 66 | const INIT: Self = Cell::new(UNUSED); 67 | 68 | #[inline(always)] 69 | fn is_locked(&self) -> bool { 70 | self.get() != UNUSED 71 | } 72 | 73 | #[inline(always)] 74 | fn try_lock_shared(&self) -> bool { 75 | let flag = self.get().wrapping_add(1); 76 | if flag <= UNUSED { 77 | return false; 78 | } 79 | self.set(flag); 80 | true 81 | } 82 | 83 | #[inline(always)] 84 | fn try_lock_exclusive(&self) -> bool { 85 | let flag = self.get(); 86 | if flag != UNUSED { 87 | return false; 88 | } 89 | self.set(UNUSED - 1); 90 | true 91 | } 92 | 93 | #[inline(always)] 94 | unsafe fn unlock_shared(&self) { 95 | let flag = self.get(); 96 | debug_assert!(flag > UNUSED); 97 | self.set(flag - 1); 98 | } 99 | 100 | #[inline(always)] 101 | unsafe fn unlock_exclusive(&self) { 102 | let flag = self.get(); 103 | debug_assert!(flag < UNUSED); 104 | self.set(flag + 1); 105 | } 106 | } 107 | } 108 | 109 | #[cfg(feature = "send")] 110 | mod lock_impl { 111 | use parking_lot::lock_api::RawRwLock; 112 | 113 | pub(crate) type RawLock = parking_lot::RawRwLock; 114 | 115 | impl super::UserDataLock for RawLock { 116 | #[allow(clippy::declare_interior_mutable_const)] 117 | const INIT: Self = ::INIT; 118 | 119 | #[inline(always)] 120 | fn is_locked(&self) -> bool { 121 | RawRwLock::is_locked(self) 122 | } 123 | 124 | #[inline(always)] 125 | fn try_lock_shared(&self) -> bool { 126 | RawRwLock::try_lock_shared(self) 127 | } 128 | 129 | #[inline(always)] 130 | fn try_lock_exclusive(&self) -> bool { 131 | RawRwLock::try_lock_exclusive(self) 132 | } 133 | 134 | #[inline(always)] 135 | unsafe fn unlock_shared(&self) { 136 | RawRwLock::unlock_shared(self) 137 | } 138 | 139 | #[inline(always)] 140 | unsafe fn unlock_exclusive(&self) { 141 | RawRwLock::unlock_exclusive(self) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/userdata/object.rs: -------------------------------------------------------------------------------- 1 | use std::string::String as StdString; 2 | 3 | use crate::error::{Error, Result}; 4 | use crate::table::Table; 5 | use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; 6 | use crate::userdata::AnyUserData; 7 | use crate::value::Value; 8 | use crate::Function; 9 | 10 | #[cfg(feature = "async")] 11 | use futures_util::future::{self, Either, Future}; 12 | 13 | impl ObjectLike for AnyUserData { 14 | #[inline] 15 | fn get(&self, key: impl IntoLua) -> Result { 16 | // `lua_gettable` method used under the hood can work with any Lua value 17 | // that has `__index` metamethod 18 | Table(self.0.copy()).get_protected(key) 19 | } 20 | 21 | #[inline] 22 | fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { 23 | // `lua_settable` method used under the hood can work with any Lua value 24 | // that has `__newindex` metamethod 25 | Table(self.0.copy()).set_protected(key, value) 26 | } 27 | 28 | #[inline] 29 | fn call(&self, args: impl IntoLuaMulti) -> Result 30 | where 31 | R: FromLuaMulti, 32 | { 33 | Function(self.0.copy()).call(args) 34 | } 35 | 36 | #[cfg(feature = "async")] 37 | #[inline] 38 | fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> 39 | where 40 | R: FromLuaMulti, 41 | { 42 | Function(self.0.copy()).call_async(args) 43 | } 44 | 45 | #[inline] 46 | fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result 47 | where 48 | R: FromLuaMulti, 49 | { 50 | self.call_function(name, (self, args)) 51 | } 52 | 53 | #[cfg(feature = "async")] 54 | fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> 55 | where 56 | R: FromLuaMulti, 57 | { 58 | self.call_async_function(name, (self, args)) 59 | } 60 | 61 | fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result 62 | where 63 | R: FromLuaMulti, 64 | { 65 | match self.get(name)? { 66 | Value::Function(func) => func.call(args), 67 | val => { 68 | let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); 69 | Err(Error::RuntimeError(msg)) 70 | } 71 | } 72 | } 73 | 74 | #[cfg(feature = "async")] 75 | fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> 76 | where 77 | R: FromLuaMulti, 78 | { 79 | match self.get(name) { 80 | Ok(Value::Function(func)) => Either::Left(func.call_async(args)), 81 | Ok(val) => { 82 | let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); 83 | Either::Right(future::ready(Err(Error::RuntimeError(msg)))) 84 | } 85 | Err(err) => Either::Right(future::ready(Err(err))), 86 | } 87 | } 88 | 89 | #[inline] 90 | fn to_string(&self) -> Result { 91 | Value::UserData(AnyUserData(self.0.copy())).to_string() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/util/short_names.rs: -------------------------------------------------------------------------------- 1 | //! Mostly copied from [bevy_utils] 2 | //! 3 | //! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs 4 | 5 | use std::any::type_name; 6 | 7 | /// Returns a short version of a type name `T` without all module paths. 8 | /// 9 | /// The short name of a type is its full name as returned by 10 | /// [`std::any::type_name`], but with the prefix of all paths removed. For 11 | /// example, the short name of `alloc::vec::Vec>` 12 | /// would be `Vec>`. 13 | pub(crate) fn short_type_name() -> String { 14 | let full_name = type_name::(); 15 | 16 | // Generics result in nested paths within <..> blocks. 17 | // Consider "core::option::Option". 18 | // To tackle this, we parse the string from left to right, collapsing as we go. 19 | let mut index: usize = 0; 20 | let end_of_string = full_name.len(); 21 | let mut parsed_name = String::new(); 22 | 23 | while index < end_of_string { 24 | let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); 25 | 26 | // Collapse everything up to the next special character, 27 | // then skip over it 28 | if let Some(special_character_index) = 29 | rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c)) 30 | { 31 | let segment_to_collapse = rest_of_string.get(0..special_character_index).unwrap_or_default(); 32 | parsed_name += collapse_type_name(segment_to_collapse); 33 | // Insert the special character 34 | let special_character = &rest_of_string[special_character_index..=special_character_index]; 35 | parsed_name.push_str(special_character); 36 | 37 | match special_character { 38 | ">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => { 39 | parsed_name.push_str("::"); 40 | // Move the index past the "::" 41 | index += special_character_index + 3; 42 | } 43 | // Move the index just past the special character 44 | _ => index += special_character_index + 1, 45 | } 46 | } else { 47 | // If there are no special characters left, we're done! 48 | parsed_name += collapse_type_name(rest_of_string); 49 | index = end_of_string; 50 | } 51 | } 52 | parsed_name 53 | } 54 | 55 | #[inline(always)] 56 | fn collapse_type_name(string: &str) -> &str { 57 | string.rsplit("::").next().unwrap() 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::short_type_name; 63 | use std::collections::HashMap; 64 | 65 | #[test] 66 | fn tests() { 67 | assert_eq!(short_type_name::(), "String"); 68 | assert_eq!(short_type_name::>(), "Option"); 69 | assert_eq!(short_type_name::<(String, &str)>(), "(String, &str)"); 70 | assert_eq!(short_type_name::<[i32; 3]>(), "[i32; 3]"); 71 | assert_eq!( 72 | short_type_name::>>(), 73 | "HashMap>" 74 | ); 75 | assert_eq!(short_type_name:: i32>(), "dyn Fn(i32) -> i32"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/util/types.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::os::raw::c_void; 3 | 4 | use crate::types::{Callback, CallbackUpvalue}; 5 | 6 | #[cfg(feature = "async")] 7 | use crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}; 8 | 9 | pub(crate) trait TypeKey: Any { 10 | fn type_key() -> *const c_void; 11 | } 12 | 13 | impl TypeKey for String { 14 | #[inline(always)] 15 | fn type_key() -> *const c_void { 16 | static STRING_TYPE_KEY: u8 = 0; 17 | &STRING_TYPE_KEY as *const u8 as *const c_void 18 | } 19 | } 20 | 21 | impl TypeKey for Callback { 22 | #[inline(always)] 23 | fn type_key() -> *const c_void { 24 | static CALLBACK_TYPE_KEY: u8 = 0; 25 | &CALLBACK_TYPE_KEY as *const u8 as *const c_void 26 | } 27 | } 28 | 29 | impl TypeKey for CallbackUpvalue { 30 | #[inline(always)] 31 | fn type_key() -> *const c_void { 32 | static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; 33 | &CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void 34 | } 35 | } 36 | 37 | #[cfg(not(feature = "luau"))] 38 | impl TypeKey for crate::types::HookCallback { 39 | #[inline(always)] 40 | fn type_key() -> *const c_void { 41 | static HOOK_CALLBACK_TYPE_KEY: u8 = 0; 42 | &HOOK_CALLBACK_TYPE_KEY as *const u8 as *const c_void 43 | } 44 | } 45 | 46 | #[cfg(feature = "async")] 47 | impl TypeKey for AsyncCallback { 48 | #[inline(always)] 49 | fn type_key() -> *const c_void { 50 | static ASYNC_CALLBACK_TYPE_KEY: u8 = 0; 51 | &ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void 52 | } 53 | } 54 | 55 | #[cfg(feature = "async")] 56 | impl TypeKey for AsyncCallbackUpvalue { 57 | #[inline(always)] 58 | fn type_key() -> *const c_void { 59 | static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0; 60 | &ASYNC_CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void 61 | } 62 | } 63 | 64 | #[cfg(feature = "async")] 65 | impl TypeKey for AsyncPollUpvalue { 66 | #[inline(always)] 67 | fn type_key() -> *const c_void { 68 | static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0; 69 | &ASYNC_POLL_UPVALUE_TYPE_KEY as *const u8 as *const c_void 70 | } 71 | } 72 | 73 | #[cfg(feature = "async")] 74 | impl TypeKey for Option { 75 | #[inline(always)] 76 | fn type_key() -> *const c_void { 77 | static WAKER_TYPE_KEY: u8 = 0; 78 | &WAKER_TYPE_KEY as *const u8 as *const c_void 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/util/userdata.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::{c_int, c_void}; 2 | use std::{mem, ptr}; 3 | 4 | use crate::error::Result; 5 | use crate::userdata::collect_userdata; 6 | use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey}; 7 | 8 | // Pushes the userdata and attaches a metatable with __gc method. 9 | // Internally uses 3 stack spaces, does not call checkstack. 10 | pub(crate) unsafe fn push_internal_userdata( 11 | state: *mut ffi::lua_State, 12 | t: T, 13 | protect: bool, 14 | ) -> Result<*mut T> { 15 | #[cfg(not(feature = "luau"))] 16 | let ud_ptr = if protect { 17 | protect_lua!(state, 0, 1, move |state| { 18 | let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T; 19 | ptr::write(ud_ptr, t); 20 | ud_ptr 21 | })? 22 | } else { 23 | let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T; 24 | ptr::write(ud_ptr, t); 25 | ud_ptr 26 | }; 27 | 28 | #[cfg(feature = "luau")] 29 | let ud_ptr = if protect { 30 | protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::(state, t))? 31 | } else { 32 | ffi::lua_newuserdata_t::(state, t) 33 | }; 34 | 35 | get_internal_metatable::(state); 36 | ffi::lua_setmetatable(state, -2); 37 | Ok(ud_ptr) 38 | } 39 | 40 | #[track_caller] 41 | pub(crate) unsafe fn get_internal_metatable(state: *mut ffi::lua_State) { 42 | ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, T::type_key()); 43 | debug_assert!(ffi::lua_isnil(state, -1) == 0, "internal metatable not found"); 44 | } 45 | 46 | // Initialize the internal metatable for a type T (with __gc method). 47 | // Uses 6 stack spaces and calls checkstack. 48 | pub(crate) unsafe fn init_internal_metatable( 49 | state: *mut ffi::lua_State, 50 | customize_fn: Option, 51 | ) -> Result<()> { 52 | check_stack(state, 6)?; 53 | 54 | push_table(state, 0, 3, true)?; 55 | 56 | #[cfg(not(feature = "luau"))] 57 | { 58 | ffi::lua_pushcfunction(state, collect_userdata::); 59 | rawset_field(state, -2, "__gc")?; 60 | } 61 | 62 | ffi::lua_pushboolean(state, 0); 63 | rawset_field(state, -2, "__metatable")?; 64 | 65 | protect_lua!(state, 1, 0, |state| { 66 | if let Some(f) = customize_fn { 67 | f(state); 68 | } 69 | 70 | ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key()); 71 | })?; 72 | 73 | Ok(()) 74 | } 75 | 76 | // Uses 2 stack spaces, does not call checkstack 77 | pub(crate) unsafe fn get_internal_userdata( 78 | state: *mut ffi::lua_State, 79 | index: c_int, 80 | mut type_mt_ptr: *const c_void, 81 | ) -> *mut T { 82 | let ud = ffi::lua_touserdata(state, index) as *mut T; 83 | if ud.is_null() { 84 | return ptr::null_mut(); 85 | } 86 | let mt_ptr = get_metatable_ptr(state, index); 87 | if type_mt_ptr.is_null() { 88 | get_internal_metatable::(state); 89 | type_mt_ptr = ffi::lua_topointer(state, -1); 90 | ffi::lua_pop(state, 1); 91 | } 92 | if mt_ptr != type_mt_ptr { 93 | return ptr::null_mut(); 94 | } 95 | ud 96 | } 97 | 98 | // Internally uses 3 stack spaces, does not call checkstack. 99 | #[inline] 100 | #[cfg(not(feature = "luau"))] 101 | pub(crate) unsafe fn push_uninit_userdata(state: *mut ffi::lua_State, protect: bool) -> Result<*mut T> { 102 | if protect { 103 | protect_lua!(state, 0, 1, |state| { 104 | ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T 105 | }) 106 | } else { 107 | Ok(ffi::lua_newuserdata(state, const { mem::size_of::() }) as *mut T) 108 | } 109 | } 110 | 111 | // Internally uses 3 stack spaces, does not call checkstack. 112 | #[inline] 113 | pub(crate) unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<*mut T> { 114 | let size = const { mem::size_of::() }; 115 | 116 | #[cfg(not(feature = "luau"))] 117 | let ud_ptr = if protect { 118 | protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata(state, size))? 119 | } else { 120 | ffi::lua_newuserdata(state, size) 121 | } as *mut T; 122 | 123 | #[cfg(feature = "luau")] 124 | let ud_ptr = if protect { 125 | protect_lua!(state, 0, 1, |state| { 126 | ffi::lua_newuserdatadtor(state, size, collect_userdata::) 127 | })? 128 | } else { 129 | ffi::lua_newuserdatadtor(state, size, collect_userdata::) 130 | } as *mut T; 131 | 132 | ptr::write(ud_ptr, t); 133 | Ok(ud_ptr) 134 | } 135 | 136 | #[inline] 137 | #[track_caller] 138 | pub(crate) unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) -> *mut T { 139 | let ud = ffi::lua_touserdata(state, index) as *mut T; 140 | mlua_debug_assert!(!ud.is_null(), "userdata pointer is null"); 141 | ud 142 | } 143 | 144 | /// Unwraps `T` from the Lua userdata and invalidating it by setting the special "destructed" 145 | /// metatable. 146 | /// 147 | /// This method does not check that userdata is of type `T` and was not previously invalidated. 148 | /// 149 | /// Uses 1 extra stack space, does not call checkstack. 150 | pub(crate) unsafe fn take_userdata(state: *mut ffi::lua_State, idx: c_int) -> T { 151 | #[rustfmt::skip] 152 | let idx = if idx < 0 { ffi::lua_absindex(state, idx) } else { idx }; 153 | 154 | // Update the metatable of this userdata to a special one with no `__gc` method and with 155 | // metamethods that trigger an error on access. 156 | // We do this so that it will not be double dropped or used after being dropped. 157 | get_destructed_userdata_metatable(state); 158 | ffi::lua_setmetatable(state, idx); 159 | let ud = get_userdata::(state, idx); 160 | 161 | // Update userdata tag to disable destructor and mark as destructed 162 | #[cfg(feature = "luau")] 163 | ffi::lua_setuserdatatag(state, idx, 1); 164 | 165 | ptr::read(ud) 166 | } 167 | 168 | pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) { 169 | let key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void; 170 | ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key); 171 | } 172 | 173 | pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0; 174 | -------------------------------------------------------------------------------- /src/vector.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[cfg(feature = "serialize")] 4 | use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; 5 | 6 | /// A Luau vector type. 7 | /// 8 | /// By default vectors are 3-dimensional, but can be 4-dimensional 9 | /// if the `luau-vector4` feature is enabled. 10 | #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] 11 | #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] 12 | pub struct Vector(pub(crate) [f32; Self::SIZE]); 13 | 14 | impl fmt::Display for Vector { 15 | #[rustfmt::skip] 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | #[cfg(not(feature = "luau-vector4"))] 18 | return write!(f, "vector({}, {}, {})", self.x(), self.y(), self.z()); 19 | #[cfg(feature = "luau-vector4")] 20 | return write!(f, "vector({}, {}, {}, {})", self.x(), self.y(), self.z(), self.w()); 21 | } 22 | } 23 | 24 | #[cfg_attr(not(feature = "luau"), allow(unused))] 25 | impl Vector { 26 | pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 }; 27 | 28 | /// Creates a new vector. 29 | #[cfg(not(feature = "luau-vector4"))] 30 | pub const fn new(x: f32, y: f32, z: f32) -> Self { 31 | Self([x, y, z]) 32 | } 33 | 34 | /// Creates a new vector. 35 | #[cfg(feature = "luau-vector4")] 36 | pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { 37 | Self([x, y, z, w]) 38 | } 39 | 40 | /// Creates a new vector with all components set to `0.0`. 41 | #[doc(hidden)] 42 | pub const fn zero() -> Self { 43 | Self([0.0; Self::SIZE]) 44 | } 45 | 46 | /// Returns 1st component of the vector. 47 | pub const fn x(&self) -> f32 { 48 | self.0[0] 49 | } 50 | 51 | /// Returns 2nd component of the vector. 52 | pub const fn y(&self) -> f32 { 53 | self.0[1] 54 | } 55 | 56 | /// Returns 3rd component of the vector. 57 | pub const fn z(&self) -> f32 { 58 | self.0[2] 59 | } 60 | 61 | /// Returns 4th component of the vector. 62 | #[cfg(any(feature = "luau-vector4", doc))] 63 | #[cfg_attr(docsrs, doc(cfg(feature = "luau-vector4")))] 64 | pub const fn w(&self) -> f32 { 65 | self.0[3] 66 | } 67 | } 68 | 69 | #[cfg(feature = "serialize")] 70 | impl Serialize for Vector { 71 | fn serialize(&self, serializer: S) -> std::result::Result { 72 | let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; 73 | ts.serialize_field(&self.x())?; 74 | ts.serialize_field(&self.y())?; 75 | ts.serialize_field(&self.z())?; 76 | #[cfg(feature = "luau-vector4")] 77 | ts.serialize_field(&self.w())?; 78 | ts.end() 79 | } 80 | } 81 | 82 | impl PartialEq<[f32; Self::SIZE]> for Vector { 83 | #[inline] 84 | fn eq(&self, other: &[f32; Self::SIZE]) -> bool { 85 | self.0 == *other 86 | } 87 | } 88 | 89 | #[cfg(feature = "luau")] 90 | impl crate::types::LuaType for Vector { 91 | const TYPE_ID: std::os::raw::c_int = ffi::LUA_TVECTOR; 92 | } 93 | -------------------------------------------------------------------------------- /tarpaulin.toml: -------------------------------------------------------------------------------- 1 | [lua54] 2 | features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" 3 | 4 | [lua54_non_send] 5 | features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers" 6 | 7 | [lua54_with_memory_limit] 8 | features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" 9 | rustflags = "--cfg force_memory_limit" 10 | 11 | [lua51] 12 | features = "lua51,vendored,async,send,serialize,macros" 13 | 14 | [lua51_with_memory_limit] 15 | features = "lua51,vendored,async,send,serialize,macros" 16 | rustflags = "--cfg force_memory_limit" 17 | 18 | [luau] 19 | features = "luau,async,send,serialize,macros" 20 | 21 | [luau_with_memory_limit] 22 | features = "luau,async,send,serialize,macros" 23 | rustflags = "--cfg force_memory_limit" 24 | -------------------------------------------------------------------------------- /tests/buffer.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "luau")] 2 | 3 | use mlua::{Lua, Result, Value}; 4 | 5 | #[test] 6 | fn test_buffer() -> Result<()> { 7 | let lua = Lua::new(); 8 | 9 | let buf1 = lua 10 | .load( 11 | r#" 12 | local buf = buffer.fromstring("hello") 13 | assert(buffer.len(buf) == 5) 14 | return buf 15 | "#, 16 | ) 17 | .eval::()?; 18 | assert!(buf1.is_buffer()); 19 | assert_eq!(buf1.type_name(), "buffer"); 20 | 21 | let buf2 = lua.load("buffer.fromstring('hello')").eval::()?; 22 | assert_ne!(buf1, buf2); 23 | 24 | // Check that we can pass buffer type to Lua 25 | let buf1 = buf1.as_buffer().unwrap(); 26 | let func = lua.create_function(|_, buf: Value| return buf.to_string())?; 27 | assert!(func.call::(buf1)?.starts_with("buffer:")); 28 | 29 | // Check buffer methods 30 | assert_eq!(buf1.len(), 5); 31 | assert_eq!(buf1.to_vec(), b"hello"); 32 | assert_eq!(buf1.read_bytes::<3>(1), [b'e', b'l', b'l']); 33 | buf1.write_bytes(1, b"i"); 34 | assert_eq!(buf1.to_vec(), b"hillo"); 35 | 36 | let buf3 = lua.create_buffer(b"")?; 37 | assert!(buf3.is_empty()); 38 | assert!(!Value::Buffer(buf3).to_pointer().is_null()); 39 | 40 | Ok(()) 41 | } 42 | 43 | #[test] 44 | #[should_panic(expected = "range end index 14 out of range for slice of length 13")] 45 | fn test_buffer_out_of_bounds_read() { 46 | let lua = Lua::new(); 47 | let buf = lua.create_buffer(b"hello, world!").unwrap(); 48 | _ = buf.read_bytes::<1>(13); 49 | } 50 | 51 | #[test] 52 | #[should_panic(expected = "range end index 16 out of range for slice of length 13")] 53 | fn test_buffer_out_of_bounds_write() { 54 | let lua = Lua::new(); 55 | let buf = lua.create_buffer(b"hello, world!").unwrap(); 56 | buf.write_bytes(14, b"!!"); 57 | } 58 | -------------------------------------------------------------------------------- /tests/byte_string.rs: -------------------------------------------------------------------------------- 1 | use bstr::{BStr, BString}; 2 | use mlua::{Lua, Result}; 3 | 4 | #[test] 5 | fn test_byte_string_round_trip() -> Result<()> { 6 | let lua = Lua::new(); 7 | 8 | lua.load( 9 | r#" 10 | invalid_sequence_identifier = "\160\161" 11 | invalid_2_octet_sequence_2nd = "\195\040" 12 | invalid_3_octet_sequence_2nd = "\226\040\161" 13 | invalid_3_octet_sequence_3rd = "\226\130\040" 14 | invalid_4_octet_sequence_2nd = "\240\040\140\188" 15 | invalid_4_octet_sequence_3rd = "\240\144\040\188" 16 | invalid_4_octet_sequence_4th = "\240\040\140\040" 17 | 18 | an_actual_string = "Hello, world!" 19 | "#, 20 | ) 21 | .exec()?; 22 | 23 | let globals = lua.globals(); 24 | 25 | let isi = globals.get::("invalid_sequence_identifier")?; 26 | assert_eq!(isi, [0xa0, 0xa1].as_ref()); 27 | 28 | let i2os2 = globals.get::("invalid_2_octet_sequence_2nd")?; 29 | assert_eq!(i2os2, [0xc3, 0x28].as_ref()); 30 | 31 | let i3os2 = globals.get::("invalid_3_octet_sequence_2nd")?; 32 | assert_eq!(i3os2, [0xe2, 0x28, 0xa1].as_ref()); 33 | 34 | let i3os3 = globals.get::("invalid_3_octet_sequence_3rd")?; 35 | assert_eq!(i3os3, [0xe2, 0x82, 0x28].as_ref()); 36 | 37 | let i4os2 = globals.get::("invalid_4_octet_sequence_2nd")?; 38 | assert_eq!(i4os2, [0xf0, 0x28, 0x8c, 0xbc].as_ref()); 39 | 40 | let i4os3 = globals.get::("invalid_4_octet_sequence_3rd")?; 41 | assert_eq!(i4os3, [0xf0, 0x90, 0x28, 0xbc].as_ref()); 42 | 43 | let i4os4 = globals.get::("invalid_4_octet_sequence_4th")?; 44 | assert_eq!(i4os4, [0xf0, 0x28, 0x8c, 0x28].as_ref()); 45 | 46 | let aas = globals.get::("an_actual_string")?; 47 | assert_eq!(aas, b"Hello, world!".as_ref()); 48 | 49 | globals.set("bstr_invalid_sequence_identifier", isi.as_ref() as &BStr)?; 50 | globals.set("bstr_invalid_2_octet_sequence_2nd", i2os2.as_ref() as &BStr)?; 51 | globals.set("bstr_invalid_3_octet_sequence_2nd", i3os2.as_ref() as &BStr)?; 52 | globals.set("bstr_invalid_3_octet_sequence_3rd", i3os3.as_ref() as &BStr)?; 53 | globals.set("bstr_invalid_4_octet_sequence_2nd", i4os2.as_ref() as &BStr)?; 54 | globals.set("bstr_invalid_4_octet_sequence_3rd", i4os3.as_ref() as &BStr)?; 55 | globals.set("bstr_invalid_4_octet_sequence_4th", i4os4.as_ref() as &BStr)?; 56 | globals.set("bstr_an_actual_string", aas.as_ref() as &BStr)?; 57 | 58 | lua.load( 59 | r#" 60 | assert(bstr_invalid_sequence_identifier == invalid_sequence_identifier) 61 | assert(bstr_invalid_2_octet_sequence_2nd == invalid_2_octet_sequence_2nd) 62 | assert(bstr_invalid_3_octet_sequence_2nd == invalid_3_octet_sequence_2nd) 63 | assert(bstr_invalid_3_octet_sequence_3rd == invalid_3_octet_sequence_3rd) 64 | assert(bstr_invalid_4_octet_sequence_2nd == invalid_4_octet_sequence_2nd) 65 | assert(bstr_invalid_4_octet_sequence_3rd == invalid_4_octet_sequence_3rd) 66 | assert(bstr_invalid_4_octet_sequence_4th == invalid_4_octet_sequence_4th) 67 | assert(bstr_an_actual_string == an_actual_string) 68 | "#, 69 | ) 70 | .exec()?; 71 | 72 | globals.set("bstring_invalid_sequence_identifier", isi)?; 73 | globals.set("bstring_invalid_2_octet_sequence_2nd", i2os2)?; 74 | globals.set("bstring_invalid_3_octet_sequence_2nd", i3os2)?; 75 | globals.set("bstring_invalid_3_octet_sequence_3rd", i3os3)?; 76 | globals.set("bstring_invalid_4_octet_sequence_2nd", i4os2)?; 77 | globals.set("bstring_invalid_4_octet_sequence_3rd", i4os3)?; 78 | globals.set("bstring_invalid_4_octet_sequence_4th", i4os4)?; 79 | globals.set("bstring_an_actual_string", aas)?; 80 | 81 | lua.load( 82 | r#" 83 | assert(bstring_invalid_sequence_identifier == invalid_sequence_identifier) 84 | assert(bstring_invalid_2_octet_sequence_2nd == invalid_2_octet_sequence_2nd) 85 | assert(bstring_invalid_3_octet_sequence_2nd == invalid_3_octet_sequence_2nd) 86 | assert(bstring_invalid_3_octet_sequence_3rd == invalid_3_octet_sequence_3rd) 87 | assert(bstring_invalid_4_octet_sequence_2nd == invalid_4_octet_sequence_2nd) 88 | assert(bstring_invalid_4_octet_sequence_3rd == invalid_4_octet_sequence_3rd) 89 | assert(bstring_invalid_4_octet_sequence_4th == invalid_4_octet_sequence_4th) 90 | assert(bstring_an_actual_string == an_actual_string) 91 | "#, 92 | ) 93 | .exec()?; 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /tests/chunk.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io}; 2 | 3 | use mlua::{Chunk, ChunkMode, Lua, Result}; 4 | 5 | #[test] 6 | fn test_chunk_methods() -> Result<()> { 7 | let lua = Lua::new(); 8 | 9 | #[cfg(unix)] 10 | assert!(lua.load("return 123").name().starts_with("@tests/chunk.rs")); 11 | let chunk2 = lua.load("return 123").set_name("@new_name"); 12 | assert_eq!(chunk2.name(), "@new_name"); 13 | 14 | let env = lua.create_table_from([("a", 987)])?; 15 | let chunk3 = lua.load("return a").set_environment(env.clone()); 16 | assert_eq!(chunk3.environment().unwrap(), &env); 17 | assert_eq!(chunk3.mode(), ChunkMode::Text); 18 | assert_eq!(chunk3.call::(())?, 987); 19 | 20 | Ok(()) 21 | } 22 | 23 | #[test] 24 | fn test_chunk_path() -> Result<()> { 25 | let lua = Lua::new(); 26 | 27 | if cfg!(target_arch = "wasm32") { 28 | // TODO: figure out why emscripten fails on file operations 29 | // Also see https://github.com/rust-lang/rust/issues/119250 30 | return Ok(()); 31 | } 32 | 33 | let temp_dir = tempfile::tempdir().unwrap(); 34 | fs::write( 35 | temp_dir.path().join("module.lua"), 36 | r#" 37 | return 321 38 | "#, 39 | )?; 40 | let i: i32 = lua.load(temp_dir.path().join("module.lua")).eval()?; 41 | assert_eq!(i, 321); 42 | 43 | match lua.load(&*temp_dir.path().join("module2.lua")).exec() { 44 | Err(err) if err.downcast_ref::().unwrap().kind() == io::ErrorKind::NotFound => {} 45 | res => panic!("expected io::Error, got {:?}", res), 46 | }; 47 | 48 | // &Path 49 | assert_eq!( 50 | (lua.load(&*temp_dir.path().join("module.lua").as_path())).eval::()?, 51 | 321 52 | ); 53 | 54 | Ok(()) 55 | } 56 | 57 | #[test] 58 | fn test_chunk_impls() -> Result<()> { 59 | let lua = Lua::new(); 60 | 61 | // StdString 62 | assert_eq!(lua.load(String::from("1")).eval::()?, 1); 63 | assert_eq!(lua.load(&String::from("2")).eval::()?, 2); 64 | 65 | // &[u8] 66 | assert_eq!(lua.load(&b"3"[..]).eval::()?, 3); 67 | 68 | // Vec 69 | assert_eq!(lua.load(b"4".to_vec()).eval::()?, 4); 70 | assert_eq!(lua.load(&b"5".to_vec()).eval::()?, 5); 71 | 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | #[cfg(feature = "macros")] 77 | fn test_chunk_macro() -> Result<()> { 78 | let lua = Lua::new(); 79 | 80 | let name = "Rustacean"; 81 | let table = vec![1]; 82 | 83 | let data = lua.create_table()?; 84 | data.raw_set("num", 1)?; 85 | 86 | let ud = mlua::AnyUserData::wrap("hello"); 87 | let f = mlua::Function::wrap(|| Ok(())); 88 | 89 | lua.globals().set("g", 123)?; 90 | 91 | let string = String::new(); 92 | let str = string.as_str(); 93 | 94 | lua.load(mlua::chunk! { 95 | assert($name == "Rustacean") 96 | assert(type($table) == "table") 97 | assert($table[1] == 1) 98 | assert(type($data) == "table") 99 | assert($data.num == 1) 100 | assert(type($ud) == "userdata") 101 | assert(type($f) == "function") 102 | assert(type($str) == "string") 103 | assert($str == "") 104 | assert(g == 123) 105 | s = 321 106 | }) 107 | .exec()?; 108 | 109 | assert_eq!(lua.globals().get::("s")?, 321); 110 | 111 | Ok(()) 112 | } 113 | 114 | #[cfg(feature = "luau")] 115 | #[test] 116 | fn test_compiler() -> Result<()> { 117 | let compiler = mlua::Compiler::new() 118 | .set_optimization_level(2) 119 | .set_debug_level(2) 120 | .set_type_info_level(1) 121 | .set_coverage_level(2) 122 | .set_vector_lib("vector") 123 | .set_vector_ctor("new") 124 | .set_vector_type("vector") 125 | .set_mutable_globals(vec!["mutable_global"]) 126 | .set_userdata_types(vec!["MyUserdata"]) 127 | .set_disabled_builtins(vec!["tostring"]); 128 | 129 | assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok()); 130 | 131 | // Error 132 | match compiler.compile("%") { 133 | Err(mlua::Error::SyntaxError { ref message, .. }) => { 134 | assert!(message.contains("Expected identifier when parsing expression, got '%'"),); 135 | } 136 | res => panic!("expected result: {res:?}"), 137 | } 138 | 139 | Ok(()) 140 | } 141 | 142 | #[cfg(feature = "luau")] 143 | #[test] 144 | fn test_compiler_library_constants() { 145 | use mlua::{CompileConstant, Compiler, Vector}; 146 | 147 | let compiler = Compiler::new() 148 | .set_optimization_level(2) 149 | .set_library_constants(vec![ 150 | ("mylib", "const_bool", CompileConstant::Boolean(true)), 151 | ("mylib", "const_num", CompileConstant::Number(123.0)), 152 | ("mylib", "const_vec", CompileConstant::Vector(Vector::zero())), 153 | ("mylib", "const_str", "value1".into()), 154 | ]); 155 | 156 | let lua = Lua::new(); 157 | lua.set_compiler(compiler); 158 | let const_bool = lua.load("return mylib.const_bool").eval::().unwrap(); 159 | assert_eq!(const_bool, true); 160 | let const_num = lua.load("return mylib.const_num").eval::().unwrap(); 161 | assert_eq!(const_num, 123.0); 162 | let const_vec = lua.load("return mylib.const_vec").eval::().unwrap(); 163 | assert_eq!(const_vec, Vector::zero()); 164 | let const_str = lua.load("return mylib.const_str").eval::(); 165 | assert_eq!(const_str.unwrap(), "value1"); 166 | } 167 | 168 | #[test] 169 | fn test_chunk_wrap() -> Result<()> { 170 | let lua = Lua::new(); 171 | 172 | let f = Chunk::wrap("return 123"); 173 | lua.globals().set("f", f)?; 174 | lua.load("assert(f() == 123)").exec().unwrap(); 175 | 176 | lua.globals().set("f2", Chunk::wrap("c()"))?; 177 | assert!( 178 | (lua.load("f2()").exec().err().unwrap().to_string()).contains(file!()), 179 | "wrong chunk location" 180 | ); 181 | 182 | Ok(()) 183 | } 184 | -------------------------------------------------------------------------------- /tests/compile.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[ignore] 3 | fn test_compilation() { 4 | let t = trybuild::TestCases::new(); 5 | 6 | t.compile_fail("tests/compile/function_borrow.rs"); 7 | t.compile_fail("tests/compile/lua_norefunwindsafe.rs"); 8 | t.compile_fail("tests/compile/ref_nounwindsafe.rs"); 9 | t.compile_fail("tests/compile/scope_callback_capture.rs"); 10 | t.compile_fail("tests/compile/scope_invariance.rs"); 11 | t.compile_fail("tests/compile/scope_mutable_aliasing.rs"); 12 | t.compile_fail("tests/compile/scope_userdata_borrow.rs"); 13 | 14 | #[cfg(feature = "async")] 15 | { 16 | t.compile_fail("tests/compile/async_any_userdata_method.rs"); 17 | t.compile_fail("tests/compile/async_nonstatic_userdata.rs"); 18 | } 19 | 20 | #[cfg(feature = "send")] 21 | t.compile_fail("tests/compile/non_send.rs"); 22 | #[cfg(not(feature = "send"))] 23 | t.pass("tests/compile/non_send.rs"); 24 | } 25 | -------------------------------------------------------------------------------- /tests/compile/async_any_userdata_method.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Lua, UserDataMethods}; 2 | 3 | fn main() { 4 | let lua = Lua::new(); 5 | 6 | lua.register_userdata_type::(|reg| { 7 | let s = String::new(); 8 | let mut s = &s; 9 | reg.add_async_method("t", |_, this, ()| async { 10 | s = &*this; 11 | Ok(()) 12 | }); 13 | }) 14 | .unwrap(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile/async_any_userdata_method.stderr: -------------------------------------------------------------------------------- 1 | error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure 2 | --> tests/compile/async_any_userdata_method.rs:9:49 3 | | 4 | 9 | reg.add_async_method("t", |_, this, ()| async { 5 | | ^^^^^ cannot borrow as mutable 6 | 10 | s = &*this; 7 | | - mutable borrow occurs due to use of `s` in closure 8 | 9 | error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function 10 | --> tests/compile/async_any_userdata_method.rs:9:49 11 | | 12 | 9 | reg.add_async_method("t", |_, this, ()| async { 13 | | ^^^^^ may outlive borrowed value `this` 14 | 10 | s = &*this; 15 | | ---- `this` is borrowed here 16 | | 17 | note: async block is returned here 18 | --> tests/compile/async_any_userdata_method.rs:9:49 19 | | 20 | 9 | reg.add_async_method("t", |_, this, ()| async { 21 | | _________________________________________________^ 22 | 10 | | s = &*this; 23 | 11 | | Ok(()) 24 | 12 | | }); 25 | | |_________^ 26 | help: to force the async block to take ownership of `this` (and any other referenced variables), use the `move` keyword 27 | | 28 | 9 | reg.add_async_method("t", |_, this, ()| async move { 29 | | ++++ 30 | 31 | error: lifetime may not live long enough 32 | --> tests/compile/async_any_userdata_method.rs:9:49 33 | | 34 | 9 | reg.add_async_method("t", |_, this, ()| async { 35 | | ___________________________________-------------_^ 36 | | | | | 37 | | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:49: 9:54}` contains a lifetime `'2` 38 | | | lifetime `'1` represents this closure's body 39 | 10 | | s = &*this; 40 | 11 | | Ok(()) 41 | 12 | | }); 42 | | |_________^ returning this value requires that `'1` must outlive `'2` 43 | | 44 | = note: closure implements `Fn`, so references to captured variables can't escape the closure 45 | 46 | error[E0597]: `s` does not live long enough 47 | --> tests/compile/async_any_userdata_method.rs:8:21 48 | | 49 | 7 | let s = String::new(); 50 | | - binding `s` declared here 51 | 8 | let mut s = &s; 52 | | ^^ borrowed value does not live long enough 53 | 9 | / reg.add_async_method("t", |_, this, ()| async { 54 | 10 | | s = &*this; 55 | 11 | | Ok(()) 56 | 12 | | }); 57 | | |__________- argument requires that `s` is borrowed for `'static` 58 | 13 | }) 59 | | - `s` dropped here while still borrowed 60 | 61 | error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function 62 | --> tests/compile/async_any_userdata_method.rs:9:35 63 | | 64 | 9 | reg.add_async_method("t", |_, this, ()| async { 65 | | ^^^^^^^^^^^^^ may outlive borrowed value `s` 66 | 10 | s = &*this; 67 | | - `s` is borrowed here 68 | | 69 | note: function requires argument type to outlive `'static` 70 | --> tests/compile/async_any_userdata_method.rs:9:9 71 | | 72 | 9 | / reg.add_async_method("t", |_, this, ()| async { 73 | 10 | | s = &*this; 74 | 11 | | Ok(()) 75 | 12 | | }); 76 | | |__________^ 77 | help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword 78 | | 79 | 9 | reg.add_async_method("t", move |_, this, ()| async { 80 | | ++++ 81 | -------------------------------------------------------------------------------- /tests/compile/async_nonstatic_userdata.rs: -------------------------------------------------------------------------------- 1 | use mlua::{UserData, UserDataMethods}; 2 | 3 | fn main() { 4 | #[derive(Clone)] 5 | struct MyUserData<'a>(&'a i64); 6 | 7 | impl UserData for MyUserData<'_> { 8 | fn add_methods>(methods: &mut M) { 9 | methods.add_async_method("print", |_, data, ()| async move { 10 | println!("{}", data.0); 11 | Ok(()) 12 | }); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile/async_nonstatic_userdata.stderr: -------------------------------------------------------------------------------- 1 | error: lifetime may not live long enough 2 | --> tests/compile/async_nonstatic_userdata.rs:9:13 3 | | 4 | 7 | impl UserData for MyUserData<'_> { 5 | | -- lifetime `'1` appears in the `impl`'s self type 6 | 8 | fn add_methods>(methods: &mut M) { 7 | 9 | / methods.add_async_method("print", |_, data, ()| async move { 8 | 10 | | println!("{}", data.0); 9 | 11 | | Ok(()) 10 | 12 | | }); 11 | | |______________^ requires that `'1` must outlive `'static` 12 | -------------------------------------------------------------------------------- /tests/compile/function_borrow.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Lua, Result}; 2 | 3 | struct Test(i32); 4 | 5 | fn main() { 6 | let test = Test(0); 7 | 8 | let lua = Lua::new(); 9 | let _ = lua.create_function(|_, ()| -> Result { Ok(test.0) }); 10 | } 11 | -------------------------------------------------------------------------------- /tests/compile/function_borrow.stderr: -------------------------------------------------------------------------------- 1 | error[E0373]: closure may outlive the current function, but it borrows `test.0`, which is owned by the current function 2 | --> tests/compile/function_borrow.rs:9:33 3 | | 4 | 9 | let _ = lua.create_function(|_, ()| -> Result { Ok(test.0) }); 5 | | ^^^^^^^^^^^^^^^^^^^^^^ ------ `test.0` is borrowed here 6 | | | 7 | | may outlive borrowed value `test.0` 8 | | 9 | note: function requires argument type to outlive `'static` 10 | --> tests/compile/function_borrow.rs:9:13 11 | | 12 | 9 | let _ = lua.create_function(|_, ()| -> Result { Ok(test.0) }); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | help: to force the closure to take ownership of `test.0` (and any other referenced variables), use the `move` keyword 15 | | 16 | 9 | let _ = lua.create_function(move |_, ()| -> Result { Ok(test.0) }); 17 | | ++++ 18 | -------------------------------------------------------------------------------- /tests/compile/lua_norefunwindsafe.rs: -------------------------------------------------------------------------------- 1 | use std::panic::catch_unwind; 2 | 3 | use mlua::Lua; 4 | 5 | fn main() { 6 | let lua = Lua::new(); 7 | catch_unwind(|| lua.create_table().unwrap()); 8 | } 9 | -------------------------------------------------------------------------------- /tests/compile/lua_norefunwindsafe.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary 2 | --> tests/compile/lua_norefunwindsafe.rs:7:18 3 | | 4 | 7 | catch_unwind(|| lua.create_table().unwrap()); 5 | | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: within `mlua::types::sync::inner::ReentrantMutex`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>` 10 | note: required because it appears within the type `Cell<*mut lua_State>` 11 | --> $RUST/core/src/cell.rs 12 | | 13 | | pub struct Cell { 14 | | ^^^^ 15 | note: required because it appears within the type `mlua::state::raw::RawLua` 16 | --> src/state/raw.rs 17 | | 18 | | pub struct RawLua { 19 | | ^^^^^^ 20 | note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` 21 | --> src/types/sync.rs 22 | | 23 | | pub(crate) struct ReentrantMutex(T); 24 | | ^^^^^^^^^^^^^^ 25 | = note: required for `Rc>` to implement `RefUnwindSafe` 26 | note: required because it appears within the type `Lua` 27 | --> src/state.rs 28 | | 29 | | pub struct Lua { 30 | | ^^^ 31 | = note: required for `&Lua` to implement `UnwindSafe` 32 | note: required because it's used within this closure 33 | --> tests/compile/lua_norefunwindsafe.rs:7:18 34 | | 35 | 7 | catch_unwind(|| lua.create_table().unwrap()); 36 | | ^^ 37 | note: required by a bound in `std::panic::catch_unwind` 38 | --> $RUST/std/src/panic.rs 39 | | 40 | | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { 41 | | ^^^^^^^^^^ required by this bound in `catch_unwind` 42 | 43 | error[E0277]: the type `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary 44 | --> tests/compile/lua_norefunwindsafe.rs:7:18 45 | | 46 | 7 | catch_unwind(|| lua.create_table().unwrap()); 47 | | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary 48 | | | 49 | | required by a bound introduced by this call 50 | | 51 | = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell` 52 | = note: required for `Rc>` to implement `RefUnwindSafe` 53 | note: required because it appears within the type `mlua::state::raw::RawLua` 54 | --> src/state/raw.rs 55 | | 56 | | pub struct RawLua { 57 | | ^^^^^^ 58 | note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex` 59 | --> src/types/sync.rs 60 | | 61 | | pub(crate) struct ReentrantMutex(T); 62 | | ^^^^^^^^^^^^^^ 63 | = note: required for `Rc>` to implement `RefUnwindSafe` 64 | note: required because it appears within the type `Lua` 65 | --> src/state.rs 66 | | 67 | | pub struct Lua { 68 | | ^^^ 69 | = note: required for `&Lua` to implement `UnwindSafe` 70 | note: required because it's used within this closure 71 | --> tests/compile/lua_norefunwindsafe.rs:7:18 72 | | 73 | 7 | catch_unwind(|| lua.create_table().unwrap()); 74 | | ^^ 75 | note: required by a bound in `std::panic::catch_unwind` 76 | --> $RUST/std/src/panic.rs 77 | | 78 | | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { 79 | | ^^^^^^^^^^ required by this bound in `catch_unwind` 80 | -------------------------------------------------------------------------------- /tests/compile/non_send.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::rc::Rc; 3 | 4 | use mlua::{Lua, Result}; 5 | 6 | fn main() -> Result<()> { 7 | let lua = Lua::new(); 8 | 9 | let data = Rc::new(Cell::new(0)); 10 | 11 | lua.create_function(move |_, ()| Ok(data.get()))? 12 | .call::(())?; 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile/non_send.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: `Rc>` cannot be sent between threads safely 2 | --> tests/compile/non_send.rs:11:25 3 | | 4 | 11 | lua.create_function(move |_, ()| Ok(data.get()))? 5 | | --------------- ------------^^^^^^^^^^^^^^^ 6 | | | | 7 | | | `Rc>` cannot be sent between threads safely 8 | | | within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` 9 | | required by a bound introduced by this call 10 | | 11 | = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc>` 12 | note: required because it's used within this closure 13 | --> tests/compile/non_send.rs:11:25 14 | | 15 | 11 | lua.create_function(move |_, ()| Ok(data.get()))? 16 | | ^^^^^^^^^^^^ 17 | = note: required for `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` to implement `MaybeSend` 18 | note: required by a bound in `Lua::create_function` 19 | --> src/state.rs 20 | | 21 | | pub fn create_function(&self, func: F) -> Result 22 | | --------------- required by a bound in this associated function 23 | | where 24 | | F: Fn(&Lua, A) -> Result + MaybeSend + 'static, 25 | | ^^^^^^^^^ required by this bound in `Lua::create_function` 26 | -------------------------------------------------------------------------------- /tests/compile/ref_nounwindsafe.rs: -------------------------------------------------------------------------------- 1 | use std::panic::catch_unwind; 2 | 3 | use mlua::Lua; 4 | 5 | fn main() { 6 | let lua = Lua::new(); 7 | let table = lua.create_table().unwrap(); 8 | catch_unwind(move || table.set("a", "b").unwrap()); 9 | } 10 | -------------------------------------------------------------------------------- /tests/compile/scope_callback_capture.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Lua, Table}; 2 | 3 | fn main() { 4 | let lua = Lua::new(); 5 | lua.scope(|scope| { 6 | let mut inner: Option
= None; 7 | let f = scope.create_function_mut(|_, t: Table| { 8 | inner = Some(t); 9 | Ok(()) 10 | })?; 11 | f.call::<()>(lua.create_table()?)?; 12 | Ok(()) 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /tests/compile/scope_callback_capture.stderr: -------------------------------------------------------------------------------- 1 | error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function 2 | --> tests/compile/scope_callback_capture.rs:7:43 3 | | 4 | 5 | lua.scope(|scope| { 5 | | ----- has type `&'1 mlua::Scope<'1, '_>` 6 | 6 | let mut inner: Option
= None; 7 | 7 | let f = scope.create_function_mut(|_, t: Table| { 8 | | ^^^^^^^^^^^^^ may outlive borrowed value `inner` 9 | 8 | inner = Some(t); 10 | | ----- `inner` is borrowed here 11 | | 12 | note: function requires argument type to outlive `'1` 13 | --> tests/compile/scope_callback_capture.rs:7:17 14 | | 15 | 7 | let f = scope.create_function_mut(|_, t: Table| { 16 | | _________________^ 17 | 8 | | inner = Some(t); 18 | 9 | | Ok(()) 19 | 10 | | })?; 20 | | |__________^ 21 | help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword 22 | | 23 | 7 | let f = scope.create_function_mut(move |_, t: Table| { 24 | | ++++ 25 | -------------------------------------------------------------------------------- /tests/compile/scope_invariance.rs: -------------------------------------------------------------------------------- 1 | use mlua::Lua; 2 | 3 | struct Test { 4 | field: i32, 5 | } 6 | 7 | fn main() { 8 | let lua = Lua::new(); 9 | lua.scope(|scope| { 10 | let f = { 11 | let mut test = Test { field: 0 }; 12 | 13 | scope.create_function_mut(|_, ()| { 14 | test.field = 42; 15 | //~^ error: `test` does not live long enough 16 | Ok(()) 17 | })? 18 | }; 19 | 20 | f.call::<()>(()) 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /tests/compile/scope_invariance.stderr: -------------------------------------------------------------------------------- 1 | error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function 2 | --> tests/compile/scope_invariance.rs:13:39 3 | | 4 | 9 | lua.scope(|scope| { 5 | | ----- has type `&'1 mlua::Scope<'1, '_>` 6 | ... 7 | 13 | scope.create_function_mut(|_, ()| { 8 | | ^^^^^^^ may outlive borrowed value `test.field` 9 | 14 | test.field = 42; 10 | | ---------- `test.field` is borrowed here 11 | | 12 | note: function requires argument type to outlive `'1` 13 | --> tests/compile/scope_invariance.rs:13:13 14 | | 15 | 13 | / scope.create_function_mut(|_, ()| { 16 | 14 | | test.field = 42; 17 | 15 | | //~^ error: `test` does not live long enough 18 | 16 | | Ok(()) 19 | 17 | | })? 20 | | |______________^ 21 | help: to force the closure to take ownership of `test.field` (and any other referenced variables), use the `move` keyword 22 | | 23 | 13 | scope.create_function_mut(move |_, ()| { 24 | | ++++ 25 | -------------------------------------------------------------------------------- /tests/compile/scope_mutable_aliasing.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Lua, UserData}; 2 | 3 | fn main() { 4 | struct MyUserData<'a>(&'a mut i32); 5 | impl UserData for MyUserData<'_> {} 6 | 7 | let mut i = 1; 8 | 9 | let lua = Lua::new(); 10 | lua.scope(|scope| { 11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap(); 12 | let _b = scope.create_userdata(MyUserData(&mut i)).unwrap(); 13 | Ok(()) 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile/scope_mutable_aliasing.stderr: -------------------------------------------------------------------------------- 1 | error[E0499]: cannot borrow `i` as mutable more than once at a time 2 | --> tests/compile/scope_mutable_aliasing.rs:12:51 3 | | 4 | 10 | lua.scope(|scope| { 5 | | ----- has type `&mlua::Scope<'_, '1>` 6 | 11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap(); 7 | | ----------------------------------------- 8 | | | | 9 | | | first mutable borrow occurs here 10 | | argument requires that `i` is borrowed for `'1` 11 | 12 | let _b = scope.create_userdata(MyUserData(&mut i)).unwrap(); 12 | | ^^^^^^ second mutable borrow occurs here 13 | -------------------------------------------------------------------------------- /tests/compile/scope_userdata_borrow.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Lua, UserData}; 2 | 3 | fn main() { 4 | // Should not allow userdata borrow to outlive lifetime of AnyUserData handle 5 | struct MyUserData<'a>(&'a i32); 6 | impl UserData for MyUserData<'_> {} 7 | 8 | let igood = 1; 9 | 10 | let lua = Lua::new(); 11 | lua.scope(|scope| { 12 | let _ugood = scope.create_userdata(MyUserData(&igood)).unwrap(); 13 | let _ubad = { 14 | let ibad = 42; 15 | scope.create_userdata(MyUserData(&ibad)).unwrap(); 16 | }; 17 | Ok(()) 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /tests/compile/scope_userdata_borrow.stderr: -------------------------------------------------------------------------------- 1 | error[E0597]: `ibad` does not live long enough 2 | --> tests/compile/scope_userdata_borrow.rs:15:46 3 | | 4 | 11 | lua.scope(|scope| { 5 | | ----- has type `&mlua::Scope<'_, '1>` 6 | ... 7 | 14 | let ibad = 42; 8 | | ---- binding `ibad` declared here 9 | 15 | scope.create_userdata(MyUserData(&ibad)).unwrap(); 10 | | ---------------------------------^^^^^-- 11 | | | | 12 | | | borrowed value does not live long enough 13 | | argument requires that `ibad` is borrowed for `'1` 14 | 16 | }; 15 | | - `ibad` dropped here while still borrowed 16 | -------------------------------------------------------------------------------- /tests/debug.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Lua, Result}; 2 | 3 | #[test] 4 | fn test_debug_format() -> Result<()> { 5 | let lua = Lua::new(); 6 | 7 | // Globals 8 | let globals = lua.globals(); 9 | let dump = format!("{globals:#?}"); 10 | assert!(dump.starts_with("{\n _G = table:")); 11 | 12 | // TODO: Other cases 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /tests/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as _; 2 | use std::{fmt, io}; 3 | 4 | use mlua::{Error, ErrorContext, Lua, Result}; 5 | 6 | #[test] 7 | fn test_error_context() -> Result<()> { 8 | let lua = Lua::new(); 9 | 10 | let func = 11 | lua.create_function(|_, ()| Err::<(), _>(Error::runtime("runtime error")).context("some context"))?; 12 | lua.globals().set("func", func)?; 13 | 14 | let msg = lua 15 | .load("local _, err = pcall(func); return tostring(err)") 16 | .eval::()?; 17 | assert!(msg.contains("some context")); 18 | assert!(msg.contains("runtime error")); 19 | 20 | let func2 = lua.create_function(|lua, ()| { 21 | lua.globals() 22 | .get::("nonextant") 23 | .with_context(|_| "failed to find global") 24 | })?; 25 | lua.globals().set("func2", func2)?; 26 | 27 | let msg2 = lua 28 | .load("local _, err = pcall(func2); return tostring(err)") 29 | .eval::()?; 30 | assert!(msg2.contains("failed to find global")); 31 | assert!(msg2.contains("error converting Lua nil to String")); 32 | 33 | // Rewrite context message and test `downcast_ref` 34 | let func3 = lua.create_function(|_, ()| { 35 | Err::<(), _>(Error::external(io::Error::new(io::ErrorKind::Other, "other"))) 36 | .context("some context") 37 | .context("some new context") 38 | })?; 39 | let err = func3.call::<()>(()).unwrap_err(); 40 | let err = err.parent().unwrap(); 41 | assert!(!err.to_string().contains("some context")); 42 | assert!(err.to_string().contains("some new context")); 43 | assert!(err.downcast_ref::().is_some()); 44 | assert!(err.downcast_ref::().is_none()); 45 | 46 | Ok(()) 47 | } 48 | 49 | #[test] 50 | fn test_error_chain() -> Result<()> { 51 | let lua = Lua::new(); 52 | 53 | // Check that `Error::ExternalError` creates a chain with a single element 54 | let io_err = io::Error::new(io::ErrorKind::Other, "other"); 55 | assert_eq!(Error::external(io_err).chain().count(), 1); 56 | 57 | let func = lua.create_function(|_, ()| { 58 | let err = Error::external(io::Error::new(io::ErrorKind::Other, "other")).context("io error"); 59 | Err::<(), _>(err) 60 | })?; 61 | let err = func.call::<()>(()).unwrap_err(); 62 | assert_eq!(err.chain().count(), 3); 63 | for (i, err) in err.chain().enumerate() { 64 | match i { 65 | 0 => assert!(matches!(err.downcast_ref(), Some(Error::CallbackError { .. }))), 66 | 1 => assert!(matches!(err.downcast_ref(), Some(Error::WithContext { .. }))), 67 | 2 => assert!(matches!(err.downcast_ref(), Some(io::Error { .. }))), 68 | _ => unreachable!(), 69 | } 70 | } 71 | 72 | let err = err.parent().unwrap(); 73 | assert!(err.source().is_none()); // The source is included to the `Display` output 74 | assert!(err.to_string().contains("io error")); 75 | assert!(err.to_string().contains("other")); 76 | 77 | Ok(()) 78 | } 79 | 80 | #[cfg(feature = "anyhow")] 81 | #[test] 82 | fn test_error_anyhow() -> Result<()> { 83 | use mlua::IntoLua; 84 | 85 | let lua = Lua::new(); 86 | 87 | let err = anyhow::Error::msg("anyhow error"); 88 | let val = err.into_lua(&lua)?; 89 | assert!(val.is_error()); 90 | assert_eq!(val.as_error().unwrap().to_string(), "anyhow error"); 91 | 92 | // Try Error -> anyhow::Error -> Error roundtrip 93 | let err = Error::runtime("runtime error"); 94 | let err = anyhow::Error::new(err); 95 | let err = err.into_lua(&lua)?; 96 | assert!(err.is_error()); 97 | let err = err.as_error().unwrap(); 98 | assert!(matches!(err, Error::RuntimeError(msg) if msg == "runtime error")); 99 | 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /tests/luau/require.rs: -------------------------------------------------------------------------------- 1 | use mlua::{IntoLua, Lua, Result, Value}; 2 | 3 | fn run_require(lua: &Lua, path: impl IntoLua) -> Result { 4 | lua.load(r#"return require(...)"#).call(path) 5 | } 6 | 7 | #[track_caller] 8 | fn get_str(value: &Value, key: impl IntoLua) -> String { 9 | value.as_table().unwrap().get::(key).unwrap() 10 | } 11 | 12 | #[test] 13 | fn test_require_errors() { 14 | let lua = Lua::new(); 15 | 16 | // RequireAbsolutePath 17 | let res = run_require(&lua, "/an/absolute/path"); 18 | assert!(res.is_err()); 19 | assert!( 20 | (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @") 21 | ); 22 | 23 | // RequireUnprefixedPath 24 | let res = run_require(&lua, "an/unprefixed/path"); 25 | assert!(res.is_err()); 26 | assert!( 27 | (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @") 28 | ); 29 | 30 | // Pass non-string to require 31 | let res = run_require(&lua, true); 32 | assert!(res.is_err()); 33 | assert!((res.unwrap_err().to_string()) 34 | .contains("bad argument #1 to 'require' (string expected, got boolean)")); 35 | } 36 | 37 | #[test] 38 | fn test_require_without_config() { 39 | let lua = Lua::new(); 40 | 41 | // RequireSimpleRelativePath 42 | let res = run_require(&lua, "./require/without_config/dependency").unwrap(); 43 | assert_eq!("result from dependency", get_str(&res, 1)); 44 | 45 | // RequireRelativeToRequiringFile 46 | let res = run_require(&lua, "./require/without_config/module").unwrap(); 47 | assert_eq!("result from dependency", get_str(&res, 1)); 48 | assert_eq!("required into module", get_str(&res, 2)); 49 | 50 | // RequireLua 51 | let res = run_require(&lua, "./require/without_config/lua_dependency").unwrap(); 52 | assert_eq!("result from lua_dependency", get_str(&res, 1)); 53 | 54 | // RequireInitLuau 55 | let res = run_require(&lua, "./require/without_config/luau").unwrap(); 56 | assert_eq!("result from init.luau", get_str(&res, 1)); 57 | 58 | // RequireInitLua 59 | let res = run_require(&lua, "./require/without_config/lua").unwrap(); 60 | assert_eq!("result from init.lua", get_str(&res, 1)); 61 | 62 | // RequireSubmoduleUsingSelf 63 | let res = run_require(&lua, "./require/without_config/nested_module_requirer").unwrap(); 64 | assert_eq!("result from submodule", get_str(&res, 1)); 65 | 66 | // RequireWithFileAmbiguity 67 | let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer"); 68 | assert!(res.is_err()); 69 | assert!((res.unwrap_err().to_string()) 70 | .contains("could not resolve child component \"dependency\" (ambiguous)")); 71 | 72 | // RequireWithDirectoryAmbiguity 73 | let res = run_require(&lua, "./require/without_config/ambiguous_directory_requirer"); 74 | assert!(res.is_err()); 75 | assert!((res.unwrap_err().to_string()) 76 | .contains("could not resolve child component \"dependency\" (ambiguous)")); 77 | 78 | // CheckCachedResult 79 | let res = run_require(&lua, "./require/without_config/validate_cache").unwrap(); 80 | assert!(res.is_table()); 81 | } 82 | 83 | #[test] 84 | fn test_require_with_config() { 85 | let lua = Lua::new(); 86 | 87 | // RequirePathWithAlias 88 | let res = run_require(&lua, "./require/with_config/src/alias_requirer").unwrap(); 89 | assert_eq!("result from dependency", get_str(&res, 1)); 90 | 91 | // RequirePathWithParentAlias 92 | let res = run_require(&lua, "./require/with_config/src/parent_alias_requirer").unwrap(); 93 | assert_eq!("result from other_dependency", get_str(&res, 1)); 94 | 95 | // RequirePathWithAliasPointingToDirectory 96 | let res = run_require(&lua, "./require/with_config/src/directory_alias_requirer").unwrap(); 97 | assert_eq!("result from subdirectory_dependency", get_str(&res, 1)); 98 | 99 | // RequireAliasThatDoesNotExist 100 | let res = run_require(&lua, "@this.alias.does.not.exist"); 101 | assert!(res.is_err()); 102 | assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias")); 103 | 104 | // IllegalAlias 105 | let res = run_require(&lua, "@"); 106 | assert!(res.is_err()); 107 | assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); 108 | } 109 | 110 | #[cfg(feature = "async")] 111 | #[tokio::test] 112 | async fn test_async_require() -> Result<()> { 113 | let lua = Lua::new(); 114 | 115 | let temp_dir = tempfile::tempdir().unwrap(); 116 | let temp_path = temp_dir.path().join("async_chunk.luau"); 117 | std::fs::write( 118 | &temp_path, 119 | r#" 120 | sleep_ms(10) 121 | return "result_after_async_sleep" 122 | "#, 123 | ) 124 | .unwrap(); 125 | 126 | lua.globals().set( 127 | "sleep_ms", 128 | lua.create_async_function(|_, ms: u64| async move { 129 | tokio::time::sleep(std::time::Duration::from_millis(ms)).await; 130 | Ok(()) 131 | })?, 132 | )?; 133 | 134 | lua.load( 135 | r#" 136 | local result = require("./async_chunk") 137 | assert(result == "result_after_async_sleep") 138 | "#, 139 | ) 140 | .set_name(format!("@{}", temp_dir.path().join("require.rs").display())) 141 | .exec_async() 142 | .await 143 | } 144 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": { 3 | "dep": "./this_should_be_overwritten_by_child_luaurc", 4 | "otherdep": "./src/other_dependency" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/src/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": { 3 | "dep": "./dependency", 4 | "subdir": "./subdirectory" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/src/alias_requirer.luau: -------------------------------------------------------------------------------- 1 | return require("@dep") 2 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/src/dependency.luau: -------------------------------------------------------------------------------- 1 | return {"result from dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/src/directory_alias_requirer.luau: -------------------------------------------------------------------------------- 1 | return(require("@subdir/subdirectory_dependency")) 2 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/src/other_dependency.luau: -------------------------------------------------------------------------------- 1 | return {"result from other_dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/src/parent_alias_requirer.luau: -------------------------------------------------------------------------------- 1 | return require("@otherdep") 2 | -------------------------------------------------------------------------------- /tests/luau/require/with_config/src/subdirectory/subdirectory_dependency.luau: -------------------------------------------------------------------------------- 1 | return {"result from subdirectory_dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/ambiguous/directory/dependency.luau: -------------------------------------------------------------------------------- 1 | return {"result from dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/ambiguous/directory/dependency/init.luau: -------------------------------------------------------------------------------- 1 | return {"result from dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/ambiguous/file/dependency.lua: -------------------------------------------------------------------------------- 1 | return {"result from dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/ambiguous/file/dependency.luau: -------------------------------------------------------------------------------- 1 | return {"result from dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/ambiguous_directory_requirer.luau: -------------------------------------------------------------------------------- 1 | local result = require("./ambiguous/directory/dependency") 2 | result[#result+1] = "required into module" 3 | return result 4 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/ambiguous_file_requirer.luau: -------------------------------------------------------------------------------- 1 | local result = require("./ambiguous/file/dependency") 2 | result[#result+1] = "required into module" 3 | return result 4 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/dependency.luau: -------------------------------------------------------------------------------- 1 | return {"result from dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/lua/init.lua: -------------------------------------------------------------------------------- 1 | return {"result from init.lua"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/lua_dependency.lua: -------------------------------------------------------------------------------- 1 | return {"result from lua_dependency"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/luau/init.luau: -------------------------------------------------------------------------------- 1 | return {"result from init.luau"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/module.luau: -------------------------------------------------------------------------------- 1 | local result = require("./dependency") 2 | result[#result+1] = "required into module" 3 | return result 4 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/nested/init.luau: -------------------------------------------------------------------------------- 1 | local result = require("@self/submodule") 2 | return result 3 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/nested/submodule.luau: -------------------------------------------------------------------------------- 1 | return {"result from submodule"} 2 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/nested_module_requirer.luau: -------------------------------------------------------------------------------- 1 | local result = require("./nested") 2 | result[#result+1] = "required into module" 3 | return result 4 | -------------------------------------------------------------------------------- /tests/luau/require/without_config/validate_cache.luau: -------------------------------------------------------------------------------- 1 | local result1 = require("./dependency") 2 | local result2 = require("./dependency") 3 | assert(result1 == result2, "expect the same result when requiring the same module twice") 4 | return {} -------------------------------------------------------------------------------- /tests/memory.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use mlua::{Error, GCMode, Lua, Result, UserData}; 4 | 5 | #[test] 6 | fn test_memory_limit() -> Result<()> { 7 | let lua = Lua::new(); 8 | 9 | let initial_memory = lua.used_memory(); 10 | assert!( 11 | initial_memory > 0, 12 | "used_memory reporting is wrong, lua uses memory for stdlib" 13 | ); 14 | 15 | let f = lua 16 | .load("local t = {}; for i = 1,10000 do t[i] = i end") 17 | .into_function()?; 18 | f.call::<()>(()).expect("should trigger no memory limit"); 19 | 20 | if cfg!(feature = "luajit") && lua.set_memory_limit(0).is_err() { 21 | // seems this luajit version does not support memory limit 22 | return Ok(()); 23 | } 24 | 25 | lua.set_memory_limit(initial_memory + 10000)?; 26 | match f.call::<()>(()) { 27 | Err(Error::MemoryError(_)) => {} 28 | something_else => panic!("did not trigger memory error: {:?}", something_else), 29 | }; 30 | 31 | lua.set_memory_limit(0)?; 32 | f.call::<()>(()).expect("should trigger no memory limit"); 33 | 34 | // Test memory limit during chunk loading 35 | lua.set_memory_limit(1024)?; 36 | match lua 37 | .load("local t = {}; for i = 1,10000 do t[i] = i end") 38 | .into_function() 39 | { 40 | Err(Error::MemoryError(_)) => {} 41 | _ => panic!("did not trigger memory error"), 42 | }; 43 | 44 | Ok(()) 45 | } 46 | 47 | #[test] 48 | fn test_memory_limit_thread() -> Result<()> { 49 | let lua = Lua::new(); 50 | 51 | let f = lua 52 | .load("local t = {}; for i = 1,10000 do t[i] = i end") 53 | .into_function()?; 54 | 55 | if cfg!(feature = "luajit") && lua.set_memory_limit(0).is_err() { 56 | // seems this luajit version does not support memory limit 57 | return Ok(()); 58 | } 59 | 60 | let thread = lua.create_thread(f)?; 61 | lua.set_memory_limit(lua.used_memory() + 10000)?; 62 | match thread.resume::<()>(()) { 63 | Err(Error::MemoryError(_)) => {} 64 | something_else => panic!("did not trigger memory error: {:?}", something_else), 65 | }; 66 | 67 | Ok(()) 68 | } 69 | 70 | #[test] 71 | fn test_gc_control() -> Result<()> { 72 | let lua = Lua::new(); 73 | let globals = lua.globals(); 74 | 75 | #[cfg(feature = "lua54")] 76 | { 77 | assert_eq!(lua.gc_gen(0, 0), GCMode::Incremental); 78 | assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational); 79 | } 80 | 81 | #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] 82 | { 83 | assert!(lua.gc_is_running()); 84 | lua.gc_stop(); 85 | assert!(!lua.gc_is_running()); 86 | lua.gc_restart(); 87 | assert!(lua.gc_is_running()); 88 | } 89 | 90 | assert_eq!(lua.gc_inc(200, 100, 13), GCMode::Incremental); 91 | 92 | struct MyUserdata(#[allow(unused)] Arc<()>); 93 | impl UserData for MyUserdata {} 94 | 95 | let rc = Arc::new(()); 96 | globals.set("userdata", lua.create_userdata(MyUserdata(rc.clone()))?)?; 97 | globals.raw_remove("userdata")?; 98 | 99 | assert_eq!(Arc::strong_count(&rc), 2); 100 | lua.gc_collect()?; 101 | lua.gc_collect()?; 102 | assert_eq!(Arc::strong_count(&rc), 1); 103 | 104 | Ok(()) 105 | } 106 | 107 | #[cfg(any(feature = "lua53", feature = "lua52"))] 108 | #[test] 109 | fn test_gc_error() { 110 | use mlua::Error; 111 | 112 | let lua = Lua::new(); 113 | match lua 114 | .load( 115 | r#" 116 | val = nil 117 | table = {} 118 | setmetatable(table, { 119 | __gc = function() 120 | error("gcwascalled") 121 | end 122 | }) 123 | table = nil 124 | collectgarbage("collect") 125 | "#, 126 | ) 127 | .exec() 128 | { 129 | Err(Error::GarbageCollectorError(_)) => {} 130 | Err(e) => panic!("__gc error did not result in correct error, instead: {}", e), 131 | Ok(()) => panic!("__gc error did not result in error"), 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_module" 3 | version = "0.0.0" 4 | authors = ["Aleksandr Orlenko "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [workspace] 11 | members = [ 12 | "loader", 13 | ] 14 | 15 | [features] 16 | lua54 = ["mlua/lua54"] 17 | lua53 = ["mlua/lua53"] 18 | lua52 = ["mlua/lua52"] 19 | lua51 = ["mlua/lua51"] 20 | luajit = ["mlua/luajit"] 21 | 22 | [dependencies] 23 | mlua = { path = "../..", features = ["module"] } 24 | -------------------------------------------------------------------------------- /tests/module/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(target_os = "macos")] 3 | { 4 | println!("cargo:rustc-cdylib-link-arg=-undefined"); 5 | println!("cargo:rustc-cdylib-link-arg=dynamic_lookup"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/module/loader/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-apple-darwin] 2 | rustflags = ["-C", "link-args=-rdynamic"] 3 | 4 | [target.aarch64-apple-darwin] 5 | rustflags = ["-C", "link-args=-rdynamic"] 6 | 7 | [target.x86_64-unknown-linux-gnu] 8 | rustflags = ["-C", "link-args=-rdynamic"] 9 | -------------------------------------------------------------------------------- /tests/module/loader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "module_loader" 3 | version = "0.0.0" 4 | authors = ["Aleksandr Orlenko "] 5 | edition = "2021" 6 | 7 | [features] 8 | lua54 = ["mlua/lua54"] 9 | lua53 = ["mlua/lua53"] 10 | lua52 = ["mlua/lua52"] 11 | lua51 = ["mlua/lua51"] 12 | luajit = ["mlua/luajit"] 13 | vendored = ["mlua/vendored"] 14 | 15 | [dependencies] 16 | mlua = { path = "../../.." } 17 | -------------------------------------------------------------------------------- /tests/module/loader/tests/load.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | use mlua::{Lua, Result}; 5 | 6 | #[test] 7 | fn test_module_simple() -> Result<()> { 8 | let lua = make_lua()?; 9 | lua.load( 10 | r#" 11 | local mod = require("test_module") 12 | assert(mod.sum(2,2) == 4) 13 | "#, 14 | ) 15 | .exec() 16 | } 17 | 18 | #[test] 19 | fn test_module_multi() -> Result<()> { 20 | let lua = make_lua()?; 21 | lua.load( 22 | r#" 23 | local mod = require("test_module") 24 | local mod2 = require("test_module.second") 25 | assert(mod.check_userdata(mod2.userdata) == 123) 26 | "#, 27 | ) 28 | .exec() 29 | } 30 | 31 | #[test] 32 | fn test_module_error() -> Result<()> { 33 | let lua = make_lua()?; 34 | lua.load( 35 | r#" 36 | local ok, err = pcall(require, "test_module.error") 37 | assert(not ok) 38 | assert(string.find(tostring(err), "custom module error")) 39 | "#, 40 | ) 41 | .exec() 42 | } 43 | 44 | #[cfg(any( 45 | feature = "lua54", 46 | feature = "lua53", 47 | feature = "lua52", 48 | feature = "lua51" 49 | ))] 50 | #[test] 51 | fn test_module_from_thread() -> Result<()> { 52 | let lua = make_lua()?; 53 | lua.load( 54 | r#" 55 | local mod 56 | 57 | local co = coroutine.create(function(a, b) 58 | mod = require("test_module") 59 | assert(mod.sum(a, b) == a + b) 60 | end) 61 | 62 | local ok, err = coroutine.resume(co, 3, 5) 63 | assert(ok, err) 64 | collectgarbage() 65 | 66 | assert(mod.used_memory() > 0) 67 | "#, 68 | ) 69 | .exec() 70 | } 71 | 72 | #[cfg(any( 73 | feature = "lua54", 74 | feature = "lua53", 75 | feature = "lua52", 76 | feature = "lua51" 77 | ))] 78 | #[test] 79 | fn test_module_multi_from_thread() -> Result<()> { 80 | let lua = make_lua()?; 81 | lua.load( 82 | r#" 83 | local mod = require("test_module") 84 | local co = coroutine.create(function() 85 | local mod2 = require("test_module.second") 86 | assert(mod2.userdata ~= nil) 87 | end) 88 | local ok, err = coroutine.resume(co) 89 | assert(ok, err) 90 | "#, 91 | ) 92 | .exec() 93 | } 94 | 95 | #[test] 96 | fn test_module_new_vm() -> Result<()> { 97 | let lua = make_lua()?; 98 | lua.load( 99 | r#" 100 | local mod = require("test_module.new_vm") 101 | assert(mod.eval("return \"hello, world\"") == "hello, world") 102 | "#, 103 | ) 104 | .exec() 105 | } 106 | 107 | fn make_lua() -> Result { 108 | let (dylib_path, dylib_ext, separator); 109 | if cfg!(target_os = "macos") { 110 | dylib_path = env::var("DYLD_FALLBACK_LIBRARY_PATH").unwrap(); 111 | dylib_ext = "dylib"; 112 | separator = ":"; 113 | } else if cfg!(target_os = "linux") { 114 | dylib_path = env::var("LD_LIBRARY_PATH").unwrap(); 115 | dylib_ext = "so"; 116 | separator = ":"; 117 | } else if cfg!(target_os = "windows") { 118 | dylib_path = env::var("PATH").unwrap(); 119 | dylib_ext = "dll"; 120 | separator = ";"; 121 | } else { 122 | panic!("unknown target os"); 123 | }; 124 | 125 | let mut cpath = dylib_path 126 | .split(separator) 127 | .take(3) 128 | .map(|p| { 129 | let mut path = PathBuf::from(p); 130 | path.push(format!("lib?.{}", dylib_ext)); 131 | path.to_str().unwrap().to_owned() 132 | }) 133 | .collect::>() 134 | .join(";"); 135 | 136 | if cfg!(target_os = "windows") { 137 | cpath = cpath.replace("\\", "\\\\"); 138 | cpath = cpath.replace("lib?.", "?."); 139 | } 140 | 141 | let lua = unsafe { Lua::unsafe_new() }; // To be able to load C modules 142 | lua.load(&format!("package.cpath = \"{}\"", cpath)).exec()?; 143 | Ok(lua) 144 | } 145 | -------------------------------------------------------------------------------- /tests/module/src/lib.rs: -------------------------------------------------------------------------------- 1 | use mlua::prelude::*; 2 | 3 | fn sum(_: &Lua, (a, b): (i64, i64)) -> LuaResult { 4 | Ok(a + b) 5 | } 6 | 7 | fn used_memory(lua: &Lua, _: ()) -> LuaResult { 8 | Ok(lua.used_memory()) 9 | } 10 | 11 | fn check_userdata(_: &Lua, ud: LuaAnyUserData) -> LuaResult { 12 | Ok(ud.borrow::()?.0) 13 | } 14 | 15 | #[mlua::lua_module] 16 | fn test_module(lua: &Lua) -> LuaResult { 17 | let exports = lua.create_table()?; 18 | exports.set("sum", lua.create_function(sum)?)?; 19 | exports.set("used_memory", lua.create_function(used_memory)?)?; 20 | exports.set("check_userdata", lua.create_function(check_userdata)?)?; 21 | Ok(exports) 22 | } 23 | 24 | #[derive(Clone, Copy)] 25 | struct MyUserData(i32); 26 | 27 | impl LuaUserData for MyUserData {} 28 | 29 | #[mlua::lua_module(name = "test_module_second", skip_memory_check)] 30 | fn test_module2(lua: &Lua) -> LuaResult { 31 | let exports = lua.create_table()?; 32 | exports.set("userdata", MyUserData(123))?; 33 | Ok(exports) 34 | } 35 | 36 | #[mlua::lua_module] 37 | fn test_module_new_vm(lua: &Lua) -> LuaResult { 38 | let eval = lua.create_function(|_, prog: String| { 39 | let lua = Lua::new(); 40 | lua.load(prog).eval::>() 41 | })?; 42 | 43 | let exports = lua.create_table()?; 44 | exports.set("eval", eval)?; 45 | Ok(exports) 46 | } 47 | 48 | #[mlua::lua_module] 49 | fn test_module_error(_: &Lua) -> LuaResult { 50 | Err("custom module error".into_lua_err()) 51 | } 52 | -------------------------------------------------------------------------------- /tests/multi.rs: -------------------------------------------------------------------------------- 1 | use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic}; 2 | 3 | #[test] 4 | fn test_result_conversions() -> Result<()> { 5 | let lua = Lua::new(); 6 | let globals = lua.globals(); 7 | 8 | let ok = lua.create_function(|_, ()| Ok(Ok::<(), Error>(())))?; 9 | let err = lua.create_function(|_, ()| Ok(Err::<(), _>("failure1".into_lua_err())))?; 10 | let ok2 = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))?; 11 | let err2 = lua.create_function(|_, ()| Ok(Err::("failure2".into_lua_err())))?; 12 | 13 | globals.set("ok", ok)?; 14 | globals.set("ok2", ok2)?; 15 | globals.set("err", err)?; 16 | globals.set("err2", err2)?; 17 | 18 | lua.load( 19 | r#" 20 | local r, e = ok() 21 | assert(r == nil and e == nil) 22 | 23 | local r, e = err() 24 | assert(r == nil) 25 | assert(tostring(e):find("failure1") ~= nil) 26 | 27 | local r, e = ok2() 28 | assert(r == "!") 29 | assert(e == nil) 30 | 31 | local r, e = err2() 32 | assert(r == nil) 33 | assert(tostring(e):find("failure2") ~= nil) 34 | "#, 35 | ) 36 | .exec()?; 37 | 38 | // Try to convert Result into MultiValue 39 | let ok1 = Ok::<(), Error>(()); 40 | let multi_ok1 = ok1.into_lua_multi(&lua)?; 41 | assert_eq!(multi_ok1.len(), 0); 42 | let err1 = Err::<(), _>("failure1"); 43 | let multi_err1 = err1.into_lua_multi(&lua)?; 44 | assert_eq!(multi_err1.len(), 2); 45 | assert_eq!(multi_err1[0], Value::Nil); 46 | assert_eq!(multi_err1[1].as_str().unwrap(), "failure1"); 47 | 48 | let ok2 = Ok::<_, Error>("!"); 49 | let multi_ok2 = ok2.into_lua_multi(&lua)?; 50 | assert_eq!(multi_ok2.len(), 1); 51 | assert_eq!(multi_ok2[0].as_str().unwrap(), "!"); 52 | let err2 = Err::("failure2".into_lua_err()); 53 | let multi_err2 = err2.into_lua_multi(&lua)?; 54 | assert_eq!(multi_err2.len(), 2); 55 | assert_eq!(multi_err2[0], Value::Nil); 56 | assert!(matches!(multi_err2[1], Value::Error(_))); 57 | assert_eq!(multi_err2[1].to_string()?, "failure2"); 58 | 59 | Ok(()) 60 | } 61 | 62 | #[test] 63 | fn test_multivalue() { 64 | let mut multi = MultiValue::with_capacity(3); 65 | multi.push_back(Value::Integer(1)); 66 | multi.push_back(Value::Integer(2)); 67 | multi.push_front(Value::Integer(3)); 68 | assert_eq!(multi.iter().filter_map(|v| v.as_integer()).sum::(), 6); 69 | 70 | let vec = multi.into_vec(); 71 | assert_eq!(&vec, &[Value::Integer(3), Value::Integer(1), Value::Integer(2)]); 72 | let _multi2 = MultiValue::from_vec(vec); 73 | } 74 | 75 | #[test] 76 | fn test_variadic() { 77 | let mut var = Variadic::with_capacity(3); 78 | var.extend_from_slice(&[1, 2, 3]); 79 | assert_eq!(var.iter().sum::(), 6); 80 | 81 | let vec = Vec::::from(var); 82 | assert_eq!(&vec, &[1, 2, 3]); 83 | let var2 = Variadic::from(vec); 84 | assert_eq!(var2.as_slice(), &[1, 2, 3]); 85 | } 86 | -------------------------------------------------------------------------------- /tests/send.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "send")] 2 | 3 | use std::cell::UnsafeCell; 4 | use std::marker::PhantomData; 5 | use std::string::String as StdString; 6 | 7 | use mlua::{AnyUserData, Error, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef}; 8 | use static_assertions::{assert_impl_all, assert_not_impl_all}; 9 | 10 | #[test] 11 | fn test_userdata_multithread_access_send_only() -> Result<()> { 12 | let lua = Lua::new(); 13 | 14 | // This type is `Send` but not `Sync`. 15 | struct MyUserData(StdString, PhantomData>); 16 | assert_impl_all!(MyUserData: Send); 17 | assert_not_impl_all!(MyUserData: Sync); 18 | 19 | impl UserData for MyUserData { 20 | fn add_methods>(methods: &mut M) { 21 | methods.add_method("method", |lua, this, ()| { 22 | let ud = lua.globals().get::("ud")?; 23 | assert_eq!(ud.call_method::("method2", ())?, "method2"); 24 | Ok(this.0.clone()) 25 | }); 26 | 27 | methods.add_method("method2", |_, _, ()| Ok("method2")); 28 | } 29 | } 30 | 31 | lua.globals() 32 | .set("ud", MyUserData("hello".to_string(), PhantomData))?; 33 | 34 | // We acquired the exclusive reference. 35 | let ud = lua.globals().get::>("ud")?; 36 | 37 | std::thread::scope(|s| { 38 | s.spawn(|| { 39 | let res = lua.globals().get::>("ud"); 40 | assert!(matches!(res, Err(Error::UserDataBorrowError))); 41 | }); 42 | }); 43 | 44 | drop(ud); 45 | lua.load("ud:method()").exec().unwrap(); 46 | 47 | Ok(()) 48 | } 49 | 50 | #[test] 51 | fn test_userdata_multithread_access_sync() -> Result<()> { 52 | let lua = Lua::new(); 53 | 54 | // This type is `Send` and `Sync`. 55 | struct MyUserData(StdString); 56 | assert_impl_all!(MyUserData: Send, Sync); 57 | 58 | impl UserData for MyUserData { 59 | fn add_methods>(methods: &mut M) { 60 | methods.add_method("method", |lua, this, ()| { 61 | let ud = lua.globals().get::("ud")?; 62 | assert!(ud.call_method::<()>("method2", ()).is_ok()); 63 | Ok(this.0.clone()) 64 | }); 65 | 66 | methods.add_method("method2", |_, _, ()| Ok(())); 67 | } 68 | } 69 | 70 | lua.globals().set("ud", MyUserData("hello".to_string()))?; 71 | 72 | // We acquired the shared reference. 73 | let _ud = lua.globals().get::>("ud")?; 74 | 75 | std::thread::scope(|s| { 76 | s.spawn(|| { 77 | // Getting another shared reference for `Sync` type is allowed. 78 | let _ = lua.globals().get::>("ud").unwrap(); 79 | }); 80 | }); 81 | 82 | lua.load("ud:method()").exec().unwrap(); 83 | 84 | Ok(()) 85 | } 86 | -------------------------------------------------------------------------------- /tests/string.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashSet; 3 | 4 | use mlua::{Lua, Result, String}; 5 | 6 | #[test] 7 | fn test_string_compare() { 8 | fn with_str(s: &str, f: F) { 9 | f(Lua::new().create_string(s).unwrap()); 10 | } 11 | 12 | // Tests that all comparisons we want to have are usable 13 | with_str("teststring", |t| assert_eq!(t, "teststring")); // &str 14 | with_str("teststring", |t| assert_eq!(t, b"teststring")); // &[u8] 15 | with_str("teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec 16 | with_str("teststring", |t| assert_eq!(t, "teststring".to_string())); // String 17 | with_str("teststring", |t| assert_eq!(t, t)); // mlua::String 18 | with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed) 19 | with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned) 20 | 21 | // Test ordering 22 | with_str("a", |a| { 23 | assert!(!(a < a)); 24 | assert!(!(a > a)); 25 | }); 26 | with_str("a", |a| assert!(a < "b")); 27 | with_str("a", |a| assert!(a < b"b")); 28 | with_str("a", |a| with_str("b", |b| assert!(a < b))); 29 | } 30 | 31 | #[test] 32 | fn test_string_views() -> Result<()> { 33 | let lua = Lua::new(); 34 | 35 | lua.load( 36 | r#" 37 | ok = "null bytes are valid utf-8, wh\0 knew?" 38 | err = "but \255 isn't :(" 39 | empty = "" 40 | "#, 41 | ) 42 | .exec()?; 43 | 44 | let globals = lua.globals(); 45 | let ok: String = globals.get("ok")?; 46 | let err: String = globals.get("err")?; 47 | let empty: String = globals.get("empty")?; 48 | 49 | assert_eq!(ok.to_str()?, "null bytes are valid utf-8, wh\0 knew?"); 50 | assert_eq!(ok.to_string_lossy(), "null bytes are valid utf-8, wh\0 knew?"); 51 | assert_eq!(ok.as_bytes(), &b"null bytes are valid utf-8, wh\0 knew?"[..]); 52 | 53 | assert!(err.to_str().is_err()); 54 | assert_eq!(err.as_bytes(), &b"but \xff isn't :("[..]); 55 | 56 | assert_eq!(empty.to_str()?, ""); 57 | assert_eq!(empty.as_bytes_with_nul(), &[0]); 58 | assert_eq!(empty.as_bytes(), &[]); 59 | 60 | Ok(()) 61 | } 62 | 63 | #[test] 64 | fn test_string_from_bytes() -> Result<()> { 65 | let lua = Lua::new(); 66 | 67 | let rs = lua.create_string(&[0, 1, 2, 3, 0, 1, 2, 3])?; 68 | assert_eq!(rs.as_bytes(), &[0, 1, 2, 3, 0, 1, 2, 3]); 69 | 70 | Ok(()) 71 | } 72 | 73 | #[test] 74 | fn test_string_hash() -> Result<()> { 75 | let lua = Lua::new(); 76 | 77 | let set: HashSet = lua.load(r#"{"hello", "world", "abc", 321}"#).eval()?; 78 | assert_eq!(set.len(), 4); 79 | assert!(set.contains(&lua.create_string("hello")?)); 80 | assert!(set.contains(&lua.create_string("world")?)); 81 | assert!(set.contains(&lua.create_string("abc")?)); 82 | assert!(set.contains(&lua.create_string("321")?)); 83 | assert!(!set.contains(&lua.create_string("Hello")?)); 84 | 85 | Ok(()) 86 | } 87 | 88 | #[test] 89 | fn test_string_fmt_debug() -> Result<()> { 90 | let lua = Lua::new(); 91 | 92 | // Valid utf8 93 | let s = lua.create_string("hello")?; 94 | assert_eq!(format!("{s:?}"), r#""hello""#); 95 | assert_eq!(format!("{:?}", s.to_str()?), r#""hello""#); 96 | assert_eq!(format!("{:?}", s.as_bytes()), "[104, 101, 108, 108, 111]"); 97 | 98 | // Invalid utf8 99 | let s = lua.create_string(b"hello\0world\r\n\t\xf0\x90\x80")?; 100 | assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xf0\x90\x80""#); 101 | 102 | Ok(()) 103 | } 104 | 105 | #[test] 106 | fn test_string_pointer() -> Result<()> { 107 | let lua = Lua::new(); 108 | 109 | let str1 = lua.create_string("hello")?; 110 | let str2 = lua.create_string("hello")?; 111 | 112 | // Lua uses string interning, so these should be the same 113 | assert_eq!(str1.to_pointer(), str2.to_pointer()); 114 | 115 | Ok(()) 116 | } 117 | 118 | #[test] 119 | fn test_string_display() -> Result<()> { 120 | let lua = Lua::new(); 121 | 122 | let s = lua.create_string("hello")?; 123 | assert_eq!(format!("{}", s.display()), "hello"); 124 | 125 | // With invalid utf8 126 | let s = lua.create_string(b"hello\0world\xFF")?; 127 | assert_eq!(format!("{}", s.display()), "hello\0world�"); 128 | 129 | Ok(()) 130 | } 131 | 132 | #[test] 133 | fn test_string_wrap() -> Result<()> { 134 | let lua = Lua::new(); 135 | 136 | let s = String::wrap("hello, world"); 137 | lua.globals().set("s", s)?; 138 | assert_eq!(lua.globals().get::("s")?, "hello, world"); 139 | 140 | let s2 = String::wrap("hello, world (owned)".to_string()); 141 | lua.globals().set("s2", s2)?; 142 | assert_eq!(lua.globals().get::("s2")?, "hello, world (owned)"); 143 | 144 | Ok(()) 145 | } 146 | 147 | #[test] 148 | fn test_bytes_into_iter() -> Result<()> { 149 | let lua = Lua::new(); 150 | 151 | let s = lua.create_string("hello")?; 152 | let bytes = s.as_bytes(); 153 | 154 | for (i, &b) in bytes.into_iter().enumerate() { 155 | assert_eq!(b, s.as_bytes()[i]); 156 | } 157 | 158 | Ok(()) 159 | } 160 | -------------------------------------------------------------------------------- /tests/types.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_void; 2 | 3 | use mlua::{Function, LightUserData, Lua, Number, Result, String as LuaString, Thread}; 4 | 5 | #[test] 6 | fn test_lightuserdata() -> Result<()> { 7 | let lua = Lua::new(); 8 | 9 | let globals = lua.globals(); 10 | lua.load( 11 | r#" 12 | function id(a) 13 | return a 14 | end 15 | "#, 16 | ) 17 | .exec()?; 18 | 19 | let res = globals 20 | .get::("id")? 21 | .call::(LightUserData(42 as *mut c_void))?; 22 | 23 | assert_eq!(res, LightUserData(42 as *mut c_void)); 24 | 25 | Ok(()) 26 | } 27 | 28 | #[test] 29 | fn test_boolean_type_metatable() -> Result<()> { 30 | let lua = Lua::new(); 31 | 32 | let mt = lua.create_table()?; 33 | mt.set("__add", Function::wrap(|a, b| Ok(a || b)))?; 34 | lua.set_type_metatable::(Some(mt)); 35 | 36 | lua.load(r#"assert(true + true == true)"#).exec().unwrap(); 37 | lua.load(r#"assert(true + false == true)"#).exec().unwrap(); 38 | lua.load(r#"assert(false + true == true)"#).exec().unwrap(); 39 | lua.load(r#"assert(false + false == false)"#).exec().unwrap(); 40 | 41 | Ok(()) 42 | } 43 | 44 | #[test] 45 | fn test_lightuserdata_type_metatable() -> Result<()> { 46 | let lua = Lua::new(); 47 | 48 | let mt = lua.create_table()?; 49 | mt.set( 50 | "__add", 51 | Function::wrap(|a: LightUserData, b: LightUserData| { 52 | Ok(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void)) 53 | }), 54 | )?; 55 | lua.set_type_metatable::(Some(mt)); 56 | 57 | let res = lua 58 | .load( 59 | r#" 60 | local a, b = ... 61 | return a + b 62 | "#, 63 | ) 64 | .call::(( 65 | LightUserData(42 as *mut c_void), 66 | LightUserData(100 as *mut c_void), 67 | )) 68 | .unwrap(); 69 | assert_eq!(res, LightUserData(142 as *mut c_void)); 70 | 71 | Ok(()) 72 | } 73 | 74 | #[test] 75 | fn test_number_type_metatable() -> Result<()> { 76 | let lua = Lua::new(); 77 | 78 | let mt = lua.create_table()?; 79 | mt.set("__call", Function::wrap(|n1: f64, n2: f64| Ok(n1 * n2)))?; 80 | lua.set_type_metatable::(Some(mt)); 81 | lua.load(r#"assert((1.5)(3.0) == 4.5)"#).exec().unwrap(); 82 | lua.load(r#"assert((5)(5) == 25)"#).exec().unwrap(); 83 | 84 | Ok(()) 85 | } 86 | 87 | #[test] 88 | fn test_string_type_metatable() -> Result<()> { 89 | let lua = Lua::new(); 90 | 91 | let mt = lua.create_table()?; 92 | mt.set( 93 | "__add", 94 | Function::wrap(|a: String, b: String| Ok(format!("{a}{b}"))), 95 | )?; 96 | lua.set_type_metatable::(Some(mt)); 97 | 98 | lua.load(r#"assert(("foo" + "bar") == "foobar")"#).exec().unwrap(); 99 | 100 | Ok(()) 101 | } 102 | 103 | #[test] 104 | fn test_function_type_metatable() -> Result<()> { 105 | let lua = Lua::new(); 106 | 107 | let mt = lua.create_table()?; 108 | mt.set( 109 | "__index", 110 | Function::wrap(|_: Function, key: String| Ok(format!("function.{key}"))), 111 | )?; 112 | lua.set_type_metatable::(Some(mt)); 113 | 114 | lua.load(r#"assert((function() end).foo == "function.foo")"#) 115 | .exec() 116 | .unwrap(); 117 | 118 | Ok(()) 119 | } 120 | 121 | #[test] 122 | fn test_thread_type_metatable() -> Result<()> { 123 | let lua = Lua::new(); 124 | 125 | let mt = lua.create_table()?; 126 | mt.set( 127 | "__index", 128 | Function::wrap(|_: Thread, key: String| Ok(format!("thread.{key}"))), 129 | )?; 130 | lua.set_type_metatable::(Some(mt)); 131 | 132 | lua.load(r#"assert((coroutine.create(function() end)).foo == "thread.foo")"#) 133 | .exec() 134 | .unwrap(); 135 | 136 | Ok(()) 137 | } 138 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | extend-ignore-identifiers-re = ["catched", "2nd", "ser"] 3 | 4 | [default.extend-words] 5 | thr = "thr" 6 | aas = "aas" 7 | --------------------------------------------------------------------------------