Compare commits

...

89 Commits

Author SHA1 Message Date
Alex Orlenko e7fa8d75bb v0.11.6 2026-01-27 16:06:24 +00:00
Alex Orlenko c10718ed2f Update CHANGELOG 2026-01-27 16:04:33 +00:00
Alex Orlenko ec2ce3620f Some final Lua 5.5 updates 2026-01-27 16:00:49 +00:00
Alex Orlenko 71757003c7 mlua-sys: v0.10.0 2026-01-27 15:56:53 +00:00
Alex Orlenko 6bb7f09927 Don't use luaL_typename to get a static type name in Luau.
In Luau this function returns heap-allocated string rather than static string,
so accessing this value when Lua state is destroyed is UB.
Fixes #674
2026-01-27 14:10:43 +00:00
Alex Orlenko e67ae7f0de Make RawLua::{push,push_value,pop_value} public 2026-01-27 12:17:58 +00:00
Alex Orlenko 8c1535c27b cargo fmt 2026-01-27 12:14:50 +00:00
WASDetchan fd245daa6f Expose RawLua via Lua::exec_raw_lua (#670) 2026-01-23 16:19:10 +00:00
Alex Orlenko 7fb7e8685f Update spelling check 2026-01-23 15:37:18 +00:00
Alex Orlenko 93617eef4e Update trybuild (compile) messages 2026-01-23 15:16:35 +00:00
Alex Orlenko 171cdf1758 Some fixes and more tests for Function::info 2026-01-23 12:44:22 +00:00
psentee 3f8f016daa Add num_params, is_vararg, nups to FunctionInfo (#665) 2026-01-23 11:59:59 +00:00
Alex Orlenko 86d0c9bddb More multi borrow "send" test fixes due to regression in Rust 1.93+ 2026-01-22 23:08:17 +00:00
Alex Orlenko 86d63ef27b Fix missing BUFSIZ for wasm32 in libc 2026-01-22 15:46:31 +00:00
Alex Orlenko e33bcf7938 Add Lua 5.5 external string null byte test 2026-01-22 15:32:14 +00:00
Alex Orlenko c80a97b526 Ignore userdata_multithread_access_sync test 2026-01-22 15:25:18 +00:00
Alex Orlenko 3c40cfe199 mlua-sys: Use 2024 edition 2026-01-22 14:09:14 +00:00
Alex Orlenko e9efb73125 Update Luau FFI bindings (added some missing functions) 2026-01-22 14:05:57 +00:00
Alex Orlenko 386c6d8ed8 Update Lua 5.5 FFI bindings (add buffer manipulation, etc) 2026-01-22 12:54:55 +00:00
Alex Orlenko 8f086bf837 Update Lua 5.4 FFI bindings (add buffer manipulation, etc) 2026-01-22 12:50:56 +00:00
Alex Orlenko ad167612dc Update Lua 5.3 FFI bindings (add buffer manipulation, etc) 2026-01-20 15:36:36 +00:00
Alex Orlenko 3366f47d40 Minor Lua 5.2 fixes 2026-01-20 15:28:33 +00:00
Alex Orlenko 0b5ef91f44 Update Lua 5.2 FFI bindings (add buffer manipulation, etc) 2026-01-20 14:29:35 +00:00
Alex Orlenko a162b0ceca Update Lua 5.1 FFI bindings (add buffer manipulation, etc) 2026-01-20 14:29:15 +00:00
Alex Orlenko 8e6d652a21 Bump luau-src to 0.18.0 (Luau 0.705) 2026-01-20 13:05:00 +00:00
Alex Orlenko 1be9e6ce2d Add Lua 5.5 external strings support 2026-01-18 00:03:43 +00:00
Alex Orlenko b1f99aa852 Update tests 2026-01-17 13:26:13 +00:00
Alex Orlenko 4c5465229e Update CI 2026-01-14 21:44:25 +00:00
Alex Orlenko 9b24bb2319 Add Lua 5.5 to CI 2026-01-14 16:01:04 +00:00
Alex Orlenko e1701b6b56 cargo fmt 2026-01-14 15:57:52 +00:00
Alex Orlenko 77d7d5d6bd Add initial Lua 5.5 support 2026-01-13 23:43:31 +00:00
psentee ee9232eda1 AnyUserData::is_proxy (#666) 2026-01-13 15:08:38 +00:00
Alex Orlenko da526595bb mlua-sys: Add Lua 5.5 support 2026-01-13 00:05:01 +00:00
Alex Orlenko 39a7d3b862 Update SYS_MIN_ALIGN 2025-12-31 19:26:38 +02:00
Alex Orlenko 0245d4ce6b v0.11.5 2025-11-22 16:13:08 +00:00
Alex Orlenko a7f105c698 Update Lua::set_memory_category doc 2025-11-22 14:27:02 +00:00
Alex Orlenko d2a8670bef (CI) Update wasi/wasmtime 2025-11-22 13:58:15 +00:00
Alex Orlenko e9de70a030 (CI) Move from x86_64-apple-darwin to aarch64-apple-darwin 2025-11-22 13:57:04 +00:00
Alex Orlenko aee647c6c0 Update CHANGELOG 2025-11-22 13:55:07 +00:00
Alex Orlenko 2e4184e7e4 Update spelling 2025-11-22 13:49:23 +00:00
Alex Orlenko 12b24b6c5b mlua-sys: v0.9.0 2025-11-22 13:47:17 +00:00
Alex Orlenko 1b500b7d47 Remove generic from internal definition of RawLua::create_string 2025-11-22 13:36:50 +00:00
Alex Orlenko ce4fc80e18 Bump luau-src to 0.17.0 (Luau 0.701) 2025-11-22 12:58:59 +00:00
Alex Orlenko 121971f54e Add Lua::set_memory_category and Lua::heap_dump functions to profile Luau memory usage.
This functionality uses Luau private api to dump heap mempory in JSON format for inspection.
The new type `HeapDump` represents memory snapshot with some basic API to calculate stats.
2025-11-22 00:01:34 +00:00
Alex Orlenko 6835537e3b Switch to released verson of luau0-src 2025-11-19 11:25:59 +00:00
Alex Orlenko a2728928cf Temporary disable some send tests on nightly
Aparently there is a regression in the compiler and sync detection does not work correctly
2025-11-18 00:51:24 +00:00
Alex Orlenko 676f3a6983 Fix tests 2025-11-17 13:44:29 +00:00
Alex Orlenko 0beaac228c Update Luau require tests 2025-11-16 23:51:51 +00:00
Alex Orlenko 9a7f75ad6b Update require implementation to satisfy Luau 0.700 2025-11-16 23:13:11 +00:00
Alex Orlenko cd56f92a7f Update Luau definitions in mlua-sys to 0.700 2025-11-16 23:12:06 +00:00
Alex Orlenko 1bd1359f43 Exclude the first arg when checking for yield_with call.
This is part of the previous commit
2025-11-06 23:28:37 +00:00
Alex Orlenko feec72bcbd Reduce number of allocations when calling async function
Instead of creating a uniq poller with upvalue on each async call, return future directly
and pass it to the poller
This also gives about 3-5% perf improvements
2025-11-06 23:09:26 +00:00
Alex Orlenko 0611906c6a Add Lua::type_metatable helper to get metatable of a primitive type.
The accompany function `Lua::set_type_metatable` already exists.
2025-11-05 22:07:14 +00:00
Alex Orlenko 72ac247dca Fix MaybeSend doc 2025-11-04 23:20:53 +00:00
Alex Orlenko f2fd010c5f Add missing lua_remove when discovering function name 2025-10-28 16:04:24 +00:00
Alex Orlenko 0619f264de Add Lua::traceback function to generate stack traces at different levels
This is similar to `debug.traceback`, through does not require debug module.
Close #652
2025-10-28 14:49:22 +00:00
Alex Orlenko ddd44bdd36 Add LUA_LOADED_TABLE constant (Luau) 2025-10-27 20:57:56 +00:00
Alex Orlenko 1152519074 Add add_method_once and add_async_method_once UserData methods (experimental).
They will allow implementing userdata methods that can be called only once, destructing
userdata instance during the call.
2025-10-26 20:14:46 +00:00
Alex Orlenko 3a2fd1ec59 Make AnyUserData::type_name public 2025-10-17 20:00:53 +01:00
Alex Orlenko a4c8b20697 impl IntoLuaMulti for &MultiValue 2025-10-13 12:36:42 +01:00
Alex Crichton 6e353d6c9f Build/test wasm32-wasip2 in CI (#649)
This is a follow-up from mlua-rs/lua-src-rs#13 which verifies/tests that
mlua/lua all work when compiled for a WASI target. While this doesn't
have formal documentation yet it also codifies in CI configuration how
to build for WASI and get tests passing (notably C compiler
configuration and some misc Rust flags).

This moves some `dev-dependencies` that don't compile for
`wasm32-wasip2` to a different section of the manifest. This
additionally annotates panicking tests with `#[cfg(not(panic =
"abort"))]` to skip those tests on WASI.

This does not test either the `send` or `async` feature at this time.
Testing `send` requires threads which WASI does not yet support, and
testing `async` requires more support in Tokio which is not currently
there yet.
2025-10-04 09:52:23 +01:00
Alex Orlenko 247208edb1 v0.11.4 2025-09-28 23:46:55 +01:00
Alex Orlenko e08768cc5e Derive Default for Value (clippy) 2025-09-28 23:42:12 +01:00
Alex Orlenko 5b38af9746 AsyncCallFuture is Unpin 2025-09-19 10:00:28 +01:00
Alex Orlenko 54907f80c5 Add SerializableValue to lib and prelude exports 2025-09-12 12:40:43 +01:00
Alex Orlenko ae512f2b49 Remove const from SerializableValue (it's not really useful) 2025-09-12 12:40:00 +01:00
Alex Orlenko 53c159b6cb Unhide Value::to_serializable 2025-09-12 11:49:43 +01:00
Alex Orlenko 2beca6ebe1 Add test for Table::for_each_value 2025-09-12 11:49:37 +01:00
Alex Orlenko 09da7a41e5 Add new serde option "detect_mixed_tables"
This option would allow detecting mixed tables (with array-like and map-like entries or several borders)
to encoding them chosing the best method (as a map or as a table).
2025-09-12 11:11:18 +01:00
Alex Orlenko bad20374ad Simplify Table::clear method
There is no need to traverse array part, lua_next will cover everything
2025-09-08 23:37:28 +01:00
Alex Orlenko 40b507c3ec Add ObjectLike::get_path helper 2025-09-04 19:12:44 +01:00
Andrew Dunbar 537cc995f6 Copyedit English in README.md (#639) 2025-09-04 14:59:24 +01:00
Alex Orlenko 5d27cb91b2 Add optional __namecall optimization for Luau
Add `UserDataRegistry::enable_namecall()` hint to set `__namecall` metamethod to enable Luau-specific method resolution optimization.
2025-09-02 00:53:12 +01:00
Alex Orlenko c70a636ca9 Remove newlines from yield_with examples 2025-08-30 12:51:53 +01:00
Alex Orlenko 13ff0ca798 v0.11.3 2025-08-29 23:11:21 +01:00
Alex Orlenko 44f49e35d6 Update CHANGELOG 2025-08-29 00:18:06 +01:00
Alex Orlenko e1ee4058a6 Add new benchmark to measure complex userdata method calls 2025-08-28 23:56:03 +01:00
Alex Orlenko f06d0020ea Add test to emulate method through field 2025-08-28 23:50:18 +01:00
Alex Orlenko d399559d30 Add Lua::yield_with to allow yielding Rust async functions and exchange values between Lua coroutine and Rust.
This functionality is similar to `coroutine.yield` and `coroutine.resume` without C restrictions.
2025-08-28 18:41:24 +01:00
Alex Orlenko 30735d5ff1 Fix thread recovery when pushing a bad arg
We should not erase thread stack if a bad argument is pushed before resuming the thread.
2025-08-25 23:07:37 +01:00
Alex Orlenko 75c23e5853 Add lua_cpcall to Luau ffi (0.688+) 2025-08-25 12:54:17 +01:00
Alex Orlenko 347856b806 Do not try to yield at non-yielable points in Luau interrupt
In particular we cannot yeild across metamethod/C-call boundaries.
This behaviour matches with Lua 5.3+ yielding from hooks only at safe points.
Closes #632
2025-08-25 12:19:50 +01:00
Alex Orlenko 774a63bece Add Buffer::cursor() method
This can be useful for providing access to buffers through core IO traits.
2025-08-24 11:29:01 +01:00
Alex Orlenko c481c87eac Add Lua::create_buffer_with_capacity method
This allow creating a preallocated buffer with specified size initialized to zero.
2025-08-23 22:38:55 +01:00
Alex Orlenko 85b280a9d6 Update nightly Rust error message matching 2025-08-23 09:40:13 +01:00
Alex Orlenko db7b782d3c Remove lifetimes from short type names 2025-08-23 09:13:31 +01:00
Alex Orlenko 5f38445558 Fix warnings 2025-08-20 16:25:06 +01:00
Alex Orlenko df0a44d405 Make Lua reference values cheap to clone
Instead of locking the VM and making a copy on auxiliary thread, track number of references using Rust ref counter.
This should also help reducing number of used references (they are limited to to 1M usually) on auxiliary thread.
2025-08-20 12:05:37 +01:00
Alex Orlenko f0806a6d62 Lower fastpath table creation limit to 1 << 26
When Lua is configured without memory restrictions, we use fastpath for table creation (unprotected mode).
In generally it's safe as long as we `abort()` on allocation failure.
However some Lua versions have additional restrictions on table size that we need to adhere in mlua too.
Probably Luau has the lowest limits.
Fixes #627
2025-08-13 22:49:40 +01:00
116 changed files with 5090 additions and 1086 deletions
+50 -31
View File
@@ -9,12 +9,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
@@ -31,35 +31,19 @@ jobs:
cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-latest' }}
if: ${{ matrix.os == 'ubuntu-latest' && matrix.lua != 'lua55' }}
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends liblua5.4-dev liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
cargo build --features "${{ matrix.lua }}"
build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin
runs-on: macos-latest
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: aarch64-apple-darwin
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
lua: [lua55, lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -81,7 +65,7 @@ jobs:
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51]
lua: [lua55, lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -105,12 +89,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
@@ -126,8 +110,8 @@ jobs:
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
- name: Run compile tests (macos lua55)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua55' }}
run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serde,macros" --tests -- --ignored
@@ -141,7 +125,7 @@ jobs:
matrix:
os: [ubuntu-latest]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
@@ -168,7 +152,7 @@ jobs:
matrix:
os: [ubuntu-latest]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
@@ -194,12 +178,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit]
lua: [lua55, lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
target: aarch64-apple-darwin
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -240,7 +224,7 @@ jobs:
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luau]
lua: [lua55, lua54, lua53, lua52, lua51, luau]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -256,6 +240,41 @@ jobs:
cargo test --tests --features "${{ matrix.lua }},vendored"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
test_wasm32_wasip2:
name: Test on wasm32-wasip2
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
lua: [lua55, lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly-2025-10-02
target: wasm32-wasip2
- name: Install wasi-sdk/Wasmtime
working-directory: ${{ runner.tool_cache }}
run: |
wasi_sdk=29
wasmtime=v40.0.1
curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$wasi_sdk/wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz
tar xf wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz
WASI_SDK_PATH=`pwd`/wasi-sdk-$wasi_sdk.0-x86_64-linux
echo "WASI_SDK_PATH=$WASI_SDK_PATH" >> $GITHUB_ENV
echo "CC_wasm32_wasip2=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV
echo "CARGO_TARGET_WASM32_WASIP2_LINKER=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV
echo "CARGO_TARGET_WASM32_WASIP2_RUSTFLAGS=-Clink-arg=-Wl,--export=cabi_realloc" >> $GITHUB_ENV
curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/$wasmtime/wasmtime-$wasmtime-x86_64-linux.tar.xz
tar xf wasmtime-$wasmtime-x86_64-linux.tar.xz
echo "CARGO_TARGET_WASM32_WASIP2_RUNNER=`pwd`/wasmtime-$wasmtime-x86_64-linux/wasmtime -W exceptions" >> $GITHUB_ENV
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored"
cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored,serde,macros,anyhow,userdata-wrappers"
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
@@ -271,7 +290,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
+10 -5
View File
@@ -1,17 +1,22 @@
name: Typos Check
name: Spelling Check
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
env:
CLICOLOR: 1
jobs:
run:
spelling:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
uses: actions/checkout@main
- name: Check spelling
uses: crate-ci/typos@master
uses: crate-ci/typos@v1.42.1
with:
config: ./typos.toml
+33
View File
@@ -1,3 +1,36 @@
## v0.11.6 (Jan 27, 2026)
- Added Lua 5.5 support (`lua55` feature flag)
- Luau updated to 0.705+
- Added `AnyUserData::is_proxy` method to check if userdata is a proxy
- Added `num_params`, `num_upvalues`, `is_vararg` to `FunctionInfo`
## v0.11.5 (Nov 22, 2025)
- Luau updated to 0.701
- Added `Lua::set_memory_category` and `Lua::heap_dump` functions to profile (Luau) memory
- Added `Lua::type_metatable` helper to get metatable of a primitive type
- Added `Lua::traceback` function to generate stack traces at different levels
- Added `add_method_once` /`add_async_method_once` UserData methods (experimental)
- Make `AnyUserData::type_name` public
- impl `IntoLuaMulti` for `&MultiValue`
- Bugfixes and async perf improvements
## v0.11.4 (Sep 29, 2025)
- Make `Value::to_serializable` public
- Add new serde option `detect_mixed_tables` (to encode mixed array+map tables)
- Add `ObjectLike::get_path` helper (for tables and userdata)
## v0.11.3 (Aug 30, 2025)
- Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua
- Do not try to yield at non-yielable points in Luau interrupt (#632)
- Add `Buffer::cursor` method (Luau)
- Add `Lua::create_buffer_with_capacity` method (Luau)
- Make Lua reference values cheap to clone (only increments ref count)
- Fix panic on large (>67M entries) table creation
## v0.11.2 (Aug 10, 2025)
- Faster stack push for `Variadic<T>`
+13 -11
View File
@@ -1,8 +1,8 @@
[package]
name = "mlua"
version = "0.11.2" # remember to update mlua_derive
version = "0.11.6" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
rust-version = "1.85.0"
edition = "2021"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua"
@@ -11,12 +11,12 @@ keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
description = """
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
High level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
with async/await features and support of writing native Lua modules in Rust.
"""
[package.metadata.docs.rs]
features = ["lua54", "vendored", "async", "send", "serde", "macros"]
features = ["lua55", "vendored", "async", "send", "serde", "macros"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
@@ -26,6 +26,7 @@ members = [
]
[features]
lua55 = ["ffi/lua55"]
lua54 = ["ffi/lua54"]
lua53 = ["ffi/lua53"]
lua52 = ["ffi/lua52"]
@@ -61,23 +62,24 @@ serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
rustversion = "1.0"
libc = "0.2"
ffi = { package = "mlua-sys", version = "0.8.3", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.10.0", path = "mlua-sys" }
[dev-dependencies]
trybuild = "1.0"
hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1.0", features = ["macros", "rt", "time"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
maplit = "1.0"
tempfile = "3"
static_assertions = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
tempfile = "3"
criterion = { version = "0.7", features = ["async_tokio"] }
rustyline = "17.0"
tokio = { version = "1.0", features = ["full"] }
+7 -5
View File
@@ -17,14 +17,14 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal to provide a
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
Started as an `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode.
Started as an `rlua` fork, `mlua` supports Lua 5.5, 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode.
`mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms and cross-compilation to `aarch64` (other targets are also supported).
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions
[Luau]: https://luau.org
@@ -33,9 +33,10 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a
### Feature flags
`mlua` uses feature flags to reduce the amount of dependencies and compiled code, and allow to choose only required set of features.
`mlua` uses feature flags to reduce the number of dependencies and compiled code, and allow choosing only the required set of features.
Below is a list of the available feature flags. By default `mlua` does not enable any features.
* `lua55`: enable Lua [5.5] support
* `lua54`: enable Lua [5.4] support
* `lua53`: enable Lua [5.3] support
* `lua52`: enable Lua [5.2] support
@@ -55,6 +56,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `anyhow`: enable `anyhow::Error` conversion into Lua
* `userdata-wrappers`: opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
[5.5]: https://www.lua.org/manual/5.5/manual.html
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
[5.2]: https://www.lua.org/manual/5.2/manual.html
@@ -270,7 +272,7 @@ remain usable after a user generated panic, and such panics should not break int
leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be
using panics for general error handling.
Below is a list of `mlua` behaviors that should be considered a bug.
Below is a list of `mlua` behaviors that should be considered bugs.
If you encounter them, a bug report would be very welcome:
+ If you can cause UB with `mlua` without typing the word "unsafe", this is a bug.
+54
View File
@@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) {
});
}
fn table_ref_clone(c: &mut Criterion) {
let lua = Lua::new();
let t = lua.create_table().unwrap();
c.bench_function("table [ref clone]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let _t2 = t.clone();
},
BatchSize::SmallInput,
);
});
}
fn function_create(c: &mut Criterion) {
let lua = Lua::new();
@@ -350,6 +366,42 @@ fn userdata_call_method(c: &mut Criterion) {
});
}
// A userdata method call that goes through an implicit `__index` function
fn userdata_call_method_complex(c: &mut Criterion) {
struct UserData(u64);
impl LuaUserData for UserData {
fn register(registry: &mut LuaUserDataRegistry<Self>) {
registry.add_field_method_get("val", |_, this| Ok(this.0));
registry.add_method_mut("inc_by", |_, this, by: u64| {
this.0 += by;
Ok(this.0)
});
#[cfg(feature = "luau")]
registry.enable_namecall();
}
}
let lua = Lua::new();
let ud = lua.create_userdata(UserData(0)).unwrap();
let inc_by = lua
.load("function(ud, s) return ud:inc_by(s) end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("userdata [call method complex]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
},
|_| {
inc_by.call::<()>((&ud, 1)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn userdata_async_call_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
@@ -399,6 +451,7 @@ criterion_group! {
table_traversal_pairs,
table_traversal_for_each,
table_traversal_sequence,
table_ref_clone,
function_create,
function_call_sum,
@@ -413,6 +466,7 @@ criterion_group! {
userdata_create,
userdata_call_index,
userdata_call_method,
userdata_call_method_complex,
userdata_async_call_method,
}
+1 -1
View File
@@ -336,7 +336,7 @@ In previous mlua versions, building a Lua module for Windows requires having Lua
In contrast, on Linux and macOS, modules can be built without any external dependencies using the `-undefined=dynamic_lookup` linker flag.
With Rust 1.71+ it's now possible to lift this restriction for Windows as well. You can build modules normally and they will be linked with
`lua54.dll`/`lua53.dll`/`lua52.dll`/`lua51.dll` depending on the enabled Lua version.
`lua5x.dll` depending on the enabled Lua version.
You still need to have the dll although, linked to application where the module will be loaded.
+1
View File
@@ -10,6 +10,7 @@ crate-type = ["cdylib"]
[workspace]
[features]
lua55 = ["mlua/lua55"]
lua54 = ["mlua/lua54"]
lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
+9 -7
View File
@@ -1,9 +1,9 @@
[package]
name = "mlua-sys"
version = "0.8.3"
version = "0.10.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
rust-version = "1.85"
edition = "2024"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua-sys"
readme = "README.md"
@@ -12,14 +12,15 @@ license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
"""
[package.metadata.docs.rs]
features = ["lua54", "vendored"]
features = ["lua55", "vendored"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
lua55 = []
lua54 = []
lua53 = []
lua52 = []
@@ -34,14 +35,15 @@ external = []
module = []
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true }
lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
luau0-src = { version = "0.15.4", optional = true }
luau0-src = { version = "0.18.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2 -1
View File
@@ -1,8 +1,9 @@
# mlua-sys
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau].
Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including [LuaJIT]) and [Luau].
Intended to be consumed by the [mlua] crate.
[LuaJIT]: https://github.com/LuaJIT/LuaJIT
[Luau]: https://github.com/luau-lang/luau
[mlua]: https://crates.io/crates/mlua
+7 -11
View File
@@ -31,18 +31,16 @@ pub fn probe_lua() {
// Find using `pkg-config`
#[cfg(feature = "lua55")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.5", "5.6", ["lua5.5", "lua-5.5", "lua55"], "5.5");
#[cfg(feature = "lua54")]
let (incl_bound, excl_bound, alt_probe, ver) =
("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4");
let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4");
#[cfg(feature = "lua53")]
let (incl_bound, excl_bound, alt_probe, ver) =
("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3");
let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3");
#[cfg(feature = "lua52")]
let (incl_bound, excl_bound, alt_probe, ver) =
("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2");
let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2");
#[cfg(feature = "lua51")]
let (incl_bound, excl_bound, alt_probe, ver) =
("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1");
let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1");
#[cfg(feature = "luajit")]
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT");
@@ -54,9 +52,7 @@ pub fn probe_lua() {
if lua.is_err() {
for pkg in alt_probe {
lua = pkg_config::Config::new()
.cargo_metadata(true)
.probe(pkg);
lua = pkg_config::Config::new().cargo_metadata(true).probe(pkg);
if lua.is_ok() {
break;
+3
View File
@@ -1,6 +1,9 @@
#![allow(dead_code)]
pub fn probe_lua() {
#[cfg(feature = "lua55")]
let artifacts = lua_src::Build::new().build(lua_src::Lua55);
#[cfg(feature = "lua54")]
let artifacts = lua_src::Build::new().build(lua_src::Lua54);
+9 -7
View File
@@ -1,19 +1,21 @@
cfg_if::cfg_if! {
if #[cfg(all(feature = "lua54", not(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
if #[cfg(all(feature = "lua55", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua53", not(any(feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
} else if #[cfg(all(feature = "lua54", not(any(feature = "lua55", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua52", not(any(feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] {
} else if #[cfg(all(feature = "lua53", not(any(feature = "lua55", feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua51", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] {
} else if #[cfg(all(feature = "lua52", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luajit", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] {
} else if #[cfg(all(feature = "lua51", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luau", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] {
} else if #[cfg(all(feature = "luajit", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luau", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] {
include!("main_inner.rs");
} else {
fn main() {
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau");
compile_error!("You can enable only one of the features: lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau");
}
}
}
+31 -22
View File
@@ -1,4 +1,4 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
//! Low level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
#![allow(non_camel_case_types, non_snake_case)]
#![allow(clippy::missing_safety_doc)]
@@ -8,6 +8,9 @@
use std::os::raw::c_int;
#[cfg(any(feature = "lua55", doc))]
pub use lua55::*;
#[cfg(any(feature = "lua54", doc))]
pub use lua54::*;
@@ -23,7 +26,7 @@ pub use lua51::*;
#[cfg(any(feature = "luau", doc))]
pub use luau::*;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 255;
@@ -40,14 +43,22 @@ pub const LUA_MAX_UPVALUES: c_int = 200;
#[doc(hidden)]
pub const LUA_TRACEBACK_STACK: c_int = 11;
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs
// The minimum alignment guaranteed by the architecture. This value is used to
// add fast paths for low alignment values.
#[cfg(any(
// The minimum alignment guaranteed by the architecture.
// Copied from https://github.com/rust-lang/rust/blob/main/library/std/src/sys/alloc/mod.rs
#[doc(hidden)]
#[rustfmt::skip]
pub const SYS_MIN_ALIGN: usize = if cfg!(any(
all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")),
all(target_arch = "xtensa", target_os = "espidf"),
)) {
// The allocator on the esp-idf and zkvm platforms guarantees 4 byte alignment.
4
} else if cfg!(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "m68k",
target_arch = "csky",
target_arch = "loongarch32",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "powerpc",
@@ -55,12 +66,11 @@ pub const LUA_TRACEBACK_STACK: c_int = 11;
target_arch = "sparc",
target_arch = "wasm32",
target_arch = "hexagon",
all(target_arch = "riscv32", not(any(target_os = "espidf", target_os = "zkvm"))),
all(target_arch = "xtensa", not(target_os = "espidf")),
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(any(
target_arch = "riscv32",
target_arch = "xtensa",
)) {
8
} else if cfg!(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm64ec",
@@ -71,20 +81,19 @@ pub const SYS_MIN_ALIGN: usize = 8;
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 16;
// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment.
#[cfg(any(
all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")),
all(target_arch = "xtensa", target_os = "espidf"),
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 4;
)) {
16
} else {
panic!("no value for SYS_MIN_ALIGN")
};
#[macro_use]
mod macros;
#[cfg(any(feature = "lua55", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub mod lua55;
#[cfg(any(feature = "lua54", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub mod lua54;
+51 -48
View File
@@ -2,6 +2,7 @@
//!
//! Based on github.com/keplerproject/lua-compat-5.3
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_void};
use std::{mem, ptr};
@@ -20,8 +21,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
}
}
const COMPAT53_LEVELS1: c_int = 12; // size of the first part of the stack
const COMPAT53_LEVELS2: c_int = 10; // size of the second part of the stack
const COMPAT53_LEVELS1: c_int = 10; // size of the first part of the stack
const COMPAT53_LEVELS2: c_int = 11; // size of the second part of the stack
unsafe fn compat53_countlevels(L: *mut lua_State) -> c_int {
let mut ar: lua_Debug = mem::zeroed();
@@ -54,11 +55,7 @@ unsafe fn compat53_checkmode(
while *st != 0 && *st != c {
st = st.offset(1);
}
if *st == c {
st
} else {
ptr::null()
}
if *st == c { st } else { ptr::null() }
}
if !mode.is_null() && strchr(mode, *modename).is_null() {
@@ -88,11 +85,10 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
lua_pop(L, 1); // remove value (but keep name)
return 1;
} else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, c".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
// stack: lib_name, lib_table, field_name (top)
lua_pushliteral(L, c"."); // place '.' between the two names
lua_replace(L, -3); // (in the slot occupied by table)
lua_concat(L, 3); // lib_name.field_name
return 1;
}
}
@@ -101,13 +97,20 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
0 // not found
}
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, ar: *mut lua_Debug) -> c_int {
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, L1: *mut lua_State, ar: *mut lua_Debug) -> c_int {
let top = lua_gettop(L);
lua_getinfo(L, cstr!("f"), ar); // push function
lua_getinfo(L1, cstr!("f"), ar); // push function
lua_xmove(L1, L, 1); // and move onto L
lua_pushvalue(L, LUA_GLOBALSINDEX);
luaL_checkstack(L, 6, cstr!("not enough stack")); // slots for 'findfield'
if compat53_findfield(L, top + 1, 2) != 0 {
let name = lua_tostring(L, -1);
if CStr::from_ptr(name).to_bytes().starts_with(b"_G.") {
lua_pushstring(L, name.add(3)); // push name without prefix
lua_remove(L, -2); // remove original name
}
lua_copy(L, -1, top + 1); // move name to proper place
lua_pop(L, 2); // remove pushed values
lua_settop(L, top + 1); // remove pushed values
1
} else {
lua_settop(L, top); // remove function and global table
@@ -115,27 +118,23 @@ unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, ar: *mut lua_Debug) ->
}
}
unsafe fn compat53_pushfuncname(L: *mut lua_State, ar: *mut lua_Debug) {
if *(*ar).namewhat != b'\0' as c_char {
// is there a name?
lua_pushfstring(L, cstr!("function '%s'"), (*ar).name);
unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, ar: *mut lua_Debug) {
// try first a global name
if compat53_pushglobalfuncname(L, L1, ar) != 0 {
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else if *(*ar).namewhat != b'\0' as c_char {
// use name from code
lua_pushfstring(L, cstr!("%s '%s'"), (*ar).namewhat, (*ar).name);
} else if *(*ar).what == b'm' as c_char {
// main?
lua_pushliteral(L, c"main chunk");
} else if *(*ar).what == b'C' as c_char {
if compat53_pushglobalfuncname(L, ar) != 0 {
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else {
lua_pushliteral(L, c"?");
}
} else if *(*ar).what != b'C' as c_char {
// for Lua functions, use <file:line>
let short_src = (*ar).short_src.as_ptr();
lua_pushfstring(L, cstr!("function <%s:%d>"), short_src, (*ar).linedefined);
} else {
lua_pushfstring(
L,
cstr!("function <%s:%d>"),
(*ar).short_src.as_ptr(),
(*ar).linedefined,
);
lua_pushliteral(L, c"?");
}
}
@@ -459,32 +458,36 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
let mut ar: lua_Debug = mem::zeroed();
let top = lua_gettop(L);
let numlevels = compat53_countlevels(L1);
let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 {
COMPAT53_LEVELS1
} else {
0
};
#[rustfmt::skip]
let mut limit = if numlevels - level > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { COMPAT53_LEVELS1 } else { -1 };
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
}
lua_pushliteral(L, c"stack traceback:");
while lua_getstack(L1, level, &mut ar) != 0 {
level += 1;
if level == mark {
if limit == 0 {
// too many levels?
lua_pushliteral(L, c"\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
let n = numlevels - level - COMPAT53_LEVELS2;
// add warning about skip ("n + 1" because we skip current level too)
lua_pushfstring(L, cstr!("\n\t...\t(skipping %d levels)"), n + 1); // add warning about skip
level += n; // and skip to last levels
} else {
lua_getinfo(L1, cstr!("Slnt"), &mut ar);
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src.as_ptr());
if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
lua_getinfo(L1, cstr!("Sln"), &mut ar);
if *ar.what != b't' as c_char {
if ar.currentline <= 0 {
lua_pushfstring(L, cstr!("\n\t%s: in "), ar.short_src.as_ptr());
} else {
lua_pushfstring(L, cstr!("\n\t%s:%d: in "), ar.short_src.as_ptr(), ar.currentline);
}
compat53_pushfuncname(L, L1, &mut ar);
lua_concat(L, lua_gettop(L) - top);
} else {
lua_pushstring(L, cstr!("\n\t(...tail calls...)"));
}
lua_pushliteral(L, c" in ");
compat53_pushfuncname(L, &mut ar);
lua_concat(L, lua_gettop(L) - top);
}
level += 1;
limit -= 1;
}
lua_concat(L, lua_gettop(L) - top);
}
+56 -4
View File
@@ -107,8 +107,6 @@ pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *
luaL_optlstring(L, n, d, ptr::null_mut())
}
// Deprecated from 5.3: luaL_checkint, luaL_optint, luaL_checklong, luaL_optlong
#[inline(always)]
pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char {
lua::lua_typename(L, lua::lua_type(L, i))
@@ -138,8 +136,62 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n);
}
// TODO: luaL_opt
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
//
// TODO: Generic Buffer Manipulation
// Generic Buffer Manipulation
//
#[cfg(target_arch = "wasm32")]
const BUFSIZ: usize = 1024; // WASI libc's BUFSIZ is 1024
#[cfg(not(target_arch = "wasm32"))]
const BUFSIZ: usize = libc::BUFSIZ as usize;
// The buffer size used by the lauxlib buffer system.
// The "16384" workaround is taken from the LuaJIT source code.
pub const LUAL_BUFFERSIZE: usize = if BUFSIZ > 16384 { 8192 } else { BUFSIZ };
#[repr(C)]
pub struct luaL_Buffer {
pub p: *mut c_char, // current position in buffer
pub lvl: c_int, // number of strings in the stack
pub L: *mut lua_State,
pub buffer: [c_char; LUAL_BUFFERSIZE],
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
let buffer_end = (*B).buffer.as_mut_ptr().add(LUAL_BUFFERSIZE);
if (*B).p >= buffer_end {
luaL_prepbuffer(B);
}
*(*B).p = c;
(*B).p = (*B).p.add(1);
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).p = (*B).p.add(n);
}
+4 -1
View File
@@ -270,7 +270,10 @@ pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
lua_pushcclosure(L, f, 0)
}
// TODO: lua_strlen
#[inline(always)]
pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize {
lua_objlen(L, i)
}
#[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
+2 -2
View File
@@ -125,7 +125,7 @@ pub unsafe fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int {
#[inline(always)]
pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int {
let n = n.try_into().expect("cannot convert index to lua_Integer");
let n = n.try_into().expect("cannot convert index to c_int");
lua_rawgeti_(L, idx, n);
lua_type(L, -1)
}
@@ -153,7 +153,7 @@ pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
#[inline(always)]
pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
let n = n.try_into().expect("cannot convert index from lua_Integer");
let n = n.try_into().expect("cannot convert index to c_int");
lua_rawseti_(L, idx, n)
}
+67 -4
View File
@@ -32,7 +32,7 @@ unsafe extern "C-unwind" {
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -166,13 +166,76 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n);
}
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
}
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
//
// TODO: Generic Buffer Manipulation
// Generic Buffer Manipulation
//
#[cfg(target_arch = "wasm32")]
const BUFSIZ: usize = 1024; // WASI libc's BUFSIZ is 1024
#[cfg(not(target_arch = "wasm32"))]
const BUFSIZ: usize = libc::BUFSIZ as usize;
// The buffer size used by the lauxlib buffer system.
// The "16384" workaround is taken from the LuaJIT source code.
pub const LUAL_BUFFERSIZE: usize = if BUFSIZ > 16384 { 8192 } else { BUFSIZ };
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub initb: [c_char; LUAL_BUFFERSIZE], // initial buffer space
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
+64 -5
View File
@@ -1,7 +1,7 @@
//! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::{mem, ptr};
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
@@ -31,7 +31,7 @@ unsafe extern "C-unwind" {
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -166,13 +166,72 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
luaL_tolstring_(L, lua::lua_absindex(L, idx), len)
}
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
}
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
//
// TODO: Generic Buffer Manipulation
// Generic Buffer Manipulation
//
// The buffer size used by the lauxlib buffer system.
// In Lua 5.3: LUAL_BUFFERSIZE = (int)(0x80 * sizeof(void*) * sizeof(lua_Integer))
#[rustfmt::skip]
pub const LUAL_BUFFERSIZE: usize = 0x80 * mem::size_of::<*const ()>() * mem::size_of::<lua_Integer>();
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub initb: [c_char; LUAL_BUFFERSIZE], // initial buffer space
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
+95 -7
View File
@@ -1,7 +1,7 @@
//! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::os::raw::{c_char, c_double, c_int, c_long, c_void};
use std::{mem, ptr};
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
@@ -30,7 +30,7 @@ unsafe extern "C-unwind" {
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -91,7 +91,7 @@ unsafe extern "C-unwind" {
pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer;
// TODO: luaL_addgsub
pub fn luaL_addgsub(B: *mut luaL_Buffer, s: *const c_char, p: *const c_char, r: *const c_char);
pub fn luaL_gsub(
L: *mut lua_State,
@@ -162,8 +162,6 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n);
}
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
@@ -188,6 +186,96 @@ pub unsafe fn luaL_loadbufferenv(
status
}
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
//
// TODO: Generic Buffer Manipulation
// Generic Buffer Manipulation
//
// The buffer size used by the lauxlib buffer system.
// LUAL_BUFFERSIZE = (int)(16 * sizeof(void*) * sizeof(lua_Number))
#[rustfmt::skip]
pub const LUAL_BUFFERSIZE: usize = 16 * mem::size_of::<*const ()>() * mem::size_of::<lua_Number>();
// Union used for the initial buffer with maximum alignment.
// This ensures proper alignment for the buffer data.
#[repr(C)]
pub union luaL_BufferInit {
// Alignment matches LUAI_MAXALIGN
pub _align_n: lua_Number,
pub _align_u: c_double,
pub _align_s: *mut c_void,
pub _align_i: lua_Integer,
pub _align_l: c_long,
// Initial buffer space
pub b: [c_char; LUAL_BUFFERSIZE],
}
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub init: luaL_BufferInit, // initial buffer (union with alignment)
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
#[inline(always)]
pub unsafe fn luaL_buffsub(B: *mut luaL_Buffer, n: usize) {
(*B).n -= n;
}
#[inline(always)]
pub unsafe fn luaL_bufflen(B: *mut luaL_Buffer) -> usize {
(*B).n
}
#[inline(always)]
pub unsafe fn luaL_buffaddr(B: *mut luaL_Buffer) -> *mut c_char {
(*B).b
}
+299
View File
@@ -0,0 +1,299 @@
//! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_double, c_int, c_long, c_uint, c_void};
use std::{mem, ptr};
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
// Extra error code for 'luaL_loadfilex'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
pub func: lua_CFunction,
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
pub fn luaL_optinteger(L: *mut lua_State, arg: c_int, def: lua_Integer) -> lua_Integer;
pub fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char);
pub fn luaL_checktype(L: *mut lua_State, arg: c_int, t: c_int);
pub fn luaL_checkany(L: *mut lua_State, arg: c_int);
pub fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int;
pub fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char);
pub fn luaL_testudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
pub fn luaL_checkoption(
L: *mut lua_State,
arg: c_int,
def: *const c_char,
lst: *const *const c_char,
) -> c_int;
pub fn luaL_fileresult(L: *mut lua_State, stat: c_int, fname: *const c_char) -> c_int;
pub fn luaL_execresult(L: *mut lua_State, stat: c_int) -> c_int;
pub fn luaL_alloc(L: *mut lua_State, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
}
// Pre-defined references
pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int;
}
#[inline(always)]
pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
luaL_loadfilex(L, f, ptr::null())
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
sz: usize,
name: *const c_char,
mode: *const c_char,
) -> c_int;
pub fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int;
pub fn luaL_newstate() -> *mut lua_State;
#[link_name = "luaL_makeseed"]
pub fn luaL_makeseed_(L: *mut lua_State) -> c_uint;
pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer;
pub fn luaL_addgsub(B: *mut luaL_Buffer, s: *const c_char, p: *const c_char, r: *const c_char);
pub fn luaL_gsub(
L: *mut lua_State,
s: *const c_char,
p: *const c_char,
r: *const c_char,
) -> *const c_char;
pub fn luaL_setfuncs(L: *mut lua_State, l: *const luaL_Reg, nup: c_int);
pub fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int;
pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int);
}
//
// Some useful macros (implemented as Rust functions)
//
// TODO: luaL_newlibtable, luaL_newlib
#[inline(always)]
pub unsafe fn luaL_argcheck(L: *mut lua_State, cond: c_int, arg: c_int, extramsg: *const c_char) {
if cond == 0 {
luaL_argerror(L, arg, extramsg);
}
}
#[inline(always)]
pub unsafe fn luaL_checkstring(L: *mut lua_State, n: c_int) -> *const c_char {
luaL_checklstring(L, n, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *const c_char {
luaL_optlstring(L, n, d, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char {
lua::lua_typename(L, lua::lua_type(L, i))
}
#[inline(always)]
pub unsafe fn luaL_dofile(L: *mut lua_State, filename: *const c_char) -> c_int {
let status = luaL_loadfile(L, filename);
if status == 0 {
lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0)
} else {
status
}
}
#[inline(always)]
pub unsafe fn luaL_dostring(L: *mut lua_State, s: *const c_char) -> c_int {
let status = luaL_loadstring(L, s);
if status == 0 {
lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0)
} else {
status
}
}
#[inline(always)]
pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n);
}
#[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
}
pub unsafe fn luaL_loadbufferenv(
L: *mut lua_State,
data: *const c_char,
size: usize,
name: *const c_char,
mode: *const c_char,
mut env: c_int,
) -> c_int {
if env != 0 {
env = lua::lua_absindex(L, env);
}
let status = luaL_loadbufferx(L, data, size, name, mode);
if status == lua::LUA_OK && env != 0 {
lua::lua_pushvalue(L, env);
lua::lua_setupvalue(L, -2, 1);
}
status
}
pub unsafe fn luaL_makeseed(L: *mut lua_State) -> c_uint {
#[cfg(macos)]
return libc::arc4random();
#[cfg(linux)]
{
let mut seed = 0u32;
let buf = &mut seed as *mut _ as *mut c_void;
if libc::getrandom(buf, 4, libc::GRND_NONBLOCK) == 4 {
return seed;
}
}
luaL_makeseed_(L)
}
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
//
// Generic Buffer Manipulation
//
// The buffer size used by the lauxlib buffer system.
// LUAL_BUFFERSIZE = (int)(16 * sizeof(void*) * sizeof(lua_Number))
#[rustfmt::skip]
pub const LUAL_BUFFERSIZE: usize = 16 * mem::size_of::<*const ()>() * mem::size_of::<lua_Number>();
// Union used for the initial buffer with maximum alignment.
// This ensures proper alignment for the buffer data.
#[repr(C)]
pub union luaL_BufferInit {
// Alignment matches LUAI_MAXALIGN
pub _align_n: lua_Number,
pub _align_u: c_double,
pub _align_s: *mut c_void,
pub _align_i: lua_Integer,
pub _align_l: c_long,
// Initial buffer space
pub b: [c_char; LUAL_BUFFERSIZE],
}
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub init: luaL_BufferInit, // initial buffer (union with alignment)
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
#[inline(always)]
pub unsafe fn luaL_buffsub(B: *mut luaL_Buffer, n: usize) {
(*B).n -= n;
}
#[inline(always)]
pub unsafe fn luaL_bufflen(B: *mut luaL_Buffer) -> usize {
(*B).n
}
#[inline(always)]
pub unsafe fn luaL_buffaddr(B: *mut luaL_Buffer) -> *mut c_char {
(*B).b
}
+578
View File
@@ -0,0 +1,578 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_uint, c_void};
use std::{mem, ptr};
// Mark for precompiled code (`<esc>Lua`)
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
// Option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
// Size of the Lua stack
#[doc(hidden)]
pub const LUAI_MAXSTACK: c_int = c_int::MAX;
// Size of a raw memory area associated with a Lua state with very fast access.
pub const LUA_EXTRASPACE: usize = mem::size_of::<*const ()>();
//
// Pseudo-indices
//
pub const LUA_REGISTRYINDEX: c_int = -(c_int::MAX / 2 + 1000);
pub const fn lua_upvalueindex(i: c_int) -> c_int {
LUA_REGISTRYINDEX - i
}
//
// Thread status
//
pub const LUA_OK: c_int = 0;
pub const LUA_YIELD: c_int = 1;
pub const LUA_ERRRUN: c_int = 2;
pub const LUA_ERRSYNTAX: c_int = 3;
pub const LUA_ERRMEM: c_int = 4;
pub const LUA_ERRERR: c_int = 5;
/// A raw Lua state associated with a thread.
#[repr(C)]
pub struct lua_State {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
//
// Basic types
//
pub const LUA_TNONE: c_int = -1;
pub const LUA_TNIL: c_int = 0;
pub const LUA_TBOOLEAN: c_int = 1;
pub const LUA_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3;
pub const LUA_TSTRING: c_int = 4;
pub const LUA_TTABLE: c_int = 5;
pub const LUA_TFUNCTION: c_int = 6;
pub const LUA_TUSERDATA: c_int = 7;
pub const LUA_TTHREAD: c_int = 8;
pub const LUA_NUMTYPES: c_int = 9;
/// Minimum Lua stack available to a C function
pub const LUA_MINSTACK: c_int = 20;
// Predefined values in the registry
// index 1 is reserved for the reference mechanism
pub const LUA_RIDX_GLOBALS: lua_Integer = 2;
pub const LUA_RIDX_MAINTHREAD: lua_Integer = 3;
pub const LUA_RIDX_LAST: lua_Integer = 3;
/// A Lua number, usually equivalent to `f64`
pub type lua_Number = c_double;
/// A Lua integer, usually equivalent to `i64`
pub type lua_Integer = i64;
/// A Lua unsigned integer, usually equivalent to `u64`
pub type lua_Unsigned = u64;
/// Type for continuation-function contexts
pub type lua_KContext = isize;
/// Type for native C functions that can be passed to Lua
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
/// Type for continuation functions
pub type lua_KFunction =
unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int;
// Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader =
unsafe extern "C-unwind" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
#[rustfmt::skip]
pub type lua_Writer =
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Type for warning functions
pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// State manipulation
//
pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void, seed: c_uint) -> *mut lua_State;
pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
pub fn lua_closethread(L: *mut lua_State, from: *mut lua_State) -> c_int;
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
pub fn lua_version(L: *mut lua_State) -> lua_Number;
//
// Basic stack manipulation
//
pub fn lua_absindex(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_gettop(L: *mut lua_State) -> c_int;
pub fn lua_settop(L: *mut lua_State, idx: c_int);
pub fn lua_pushvalue(L: *mut lua_State, idx: c_int);
pub fn lua_rotate(L: *mut lua_State, idx: c_int, n: c_int);
pub fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int);
pub fn lua_checkstack(L: *mut lua_State, sz: c_int) -> c_int;
pub fn lua_xmove(from: *mut lua_State, to: *mut lua_State, n: c_int);
//
// Access functions (stack -> C)
//
pub fn lua_isnumber(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isstring(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_iscfunction(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isuserdata(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_type(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_typename(L: *mut lua_State, tp: c_int) -> *const c_char;
pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number;
pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
#[link_name = "lua_rawlen"]
fn lua_rawlen_(L: *mut lua_State, idx: c_int) -> lua_Unsigned;
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>;
pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State;
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
// lua_rawlen's return type changed from size_t to lua_Unsigned int in Lua 5.4.
// This adapts the crate API to the new Lua ABI.
#[inline(always)]
pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize {
lua_rawlen_(L, idx) as usize
}
//
// Comparison and arithmetic functions
//
pub const LUA_OPADD: c_int = 0;
pub const LUA_OPSUB: c_int = 1;
pub const LUA_OPMUL: c_int = 2;
pub const LUA_OPMOD: c_int = 3;
pub const LUA_OPPOW: c_int = 4;
pub const LUA_OPDIV: c_int = 5;
pub const LUA_OPIDIV: c_int = 6;
pub const LUA_OPBAND: c_int = 7;
pub const LUA_OPBOR: c_int = 8;
pub const LUA_OPBXOR: c_int = 9;
pub const LUA_OPSHL: c_int = 10;
pub const LUA_OPSHR: c_int = 11;
pub const LUA_OPUNM: c_int = 12;
pub const LUA_OPBNOT: c_int = 13;
pub const LUA_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
pub fn lua_pushnil(L: *mut lua_State);
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer);
pub fn lua_pushlstring(L: *mut lua_State, s: *const c_char, len: usize) -> *const c_char;
pub fn lua_pushexternalstring(
L: *mut lua_State,
s: *const c_char,
len: usize,
falloc: Option<lua_Alloc>,
ud: *mut c_void,
) -> *const c_char;
pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char;
// lua_pushvfstring
pub fn lua_pushfstring(L: *mut lua_State, fmt: *const c_char, ...) -> *const c_char;
pub fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, n: c_int);
pub fn lua_pushboolean(L: *mut lua_State, b: c_int);
pub fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void);
pub fn lua_pushthread(L: *mut lua_State) -> c_int;
//
// Get functions (Lua -> stack)
//
pub fn lua_getglobal(L: *mut lua_State, name: *const c_char) -> c_int;
pub fn lua_gettable(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_getfield(L: *mut lua_State, idx: c_int, k: *const c_char) -> c_int;
pub fn lua_geti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int;
pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int;
pub fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int;
pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int);
pub fn lua_newuserdatauv(L: *mut lua_State, sz: usize, nuvalue: c_int) -> *mut c_void;
pub fn lua_getmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
pub fn lua_getiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
//
// Set functions (stack -> Lua)
//
pub fn lua_setglobal(L: *mut lua_State, name: *const c_char);
pub fn lua_settable(L: *mut lua_State, idx: c_int);
pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char);
pub fn lua_seti(L: *mut lua_State, idx: c_int, n: lua_Integer);
pub fn lua_rawset(L: *mut lua_State, idx: c_int);
pub fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer);
pub fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void);
pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
pub fn lua_setiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
//
// 'load' and 'call' functions (load and run Lua code)
//
pub fn lua_callk(
L: *mut lua_State,
nargs: c_int,
nresults: c_int,
ctx: lua_KContext,
k: Option<lua_KFunction>,
);
pub fn lua_pcallk(
L: *mut lua_State,
nargs: c_int,
nresults: c_int,
errfunc: c_int,
ctx: lua_KContext,
k: Option<lua_KFunction>,
) -> c_int;
pub fn lua_load(
L: *mut lua_State,
reader: lua_Reader,
data: *mut c_void,
chunkname: *const c_char,
mode: *const c_char,
) -> c_int;
pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, strip: c_int) -> c_int;
}
#[inline(always)]
pub unsafe fn lua_call(L: *mut lua_State, n: c_int, r: c_int) {
lua_callk(L, n, r, 0, None)
}
#[inline(always)]
pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_int {
lua_pcallk(L, n, r, f, 0, None)
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// Coroutine functions
//
pub fn lua_yieldk(
L: *mut lua_State,
nresults: c_int,
ctx: lua_KContext,
k: Option<lua_KFunction>,
) -> c_int;
pub fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int;
pub fn lua_status(L: *mut lua_State) -> c_int;
pub fn lua_isyieldable(L: *mut lua_State) -> c_int;
}
#[inline(always)]
pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
lua_yieldk(L, n, 0, None)
}
//
// Warning-related functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn lua_setwarnf(L: *mut lua_State, f: Option<lua_WarnFunction>, ud: *mut c_void);
pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
}
//
// Garbage-collection options
//
pub const LUA_GCSTOP: c_int = 0;
pub const LUA_GCRESTART: c_int = 1;
pub const LUA_GCCOLLECT: c_int = 2;
pub const LUA_GCCOUNT: c_int = 3;
pub const LUA_GCCOUNTB: c_int = 4;
pub const LUA_GCSTEP: c_int = 5;
pub const LUA_GCISRUNNING: c_int = 6;
pub const LUA_GCGEN: c_int = 7;
pub const LUA_GCINC: c_int = 8;
pub const LUA_GCPARAM: c_int = 9;
// Parameters for GC generational mode
pub const LUA_GCPMINORMUL: c_int = 0; // control minor collections
pub const LUA_GCPMAJORMINOR: c_int = 1; // control shift major->minor
pub const LUA_GCPMINORMAJOR: c_int = 2; // control shift minor->major
// Parameters for GC incremental mode
pub const LUA_GCPPAUSE: c_int = 3; // size of pause between successive GCs
pub const LUA_GCPSTEPMUL: c_int = 4; // GC "speed"
pub const LUA_GCPSTEPSIZE: c_int = 5; // GC granularity
pub const LUA_GCPNUM: c_int = 6; // number of parameters
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
#[link_name = "lua_error"]
fn lua_error_(L: *mut lua_State) -> c_int;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: c_int);
pub fn lua_numbertocstring(L: *mut lua_State, idx: c_int, buff: *mut c_char) -> c_uint;
pub fn lua_stringtonumber(L: *mut lua_State, s: *const c_char) -> usize;
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
pub fn lua_toclose(L: *mut lua_State, idx: c_int);
pub fn lua_closeslot(L: *mut lua_State, idx: c_int);
}
// lua_error does not return but is declared to return int, and Rust translates
// ! to void which can cause link-time errors if the platform linker is aware
// of return types and requires they match (for example: wasm does this).
#[inline(always)]
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
lua_error_(L);
unreachable!();
}
//
// Some useful macros (implemented as Rust functions)
//
#[inline(always)]
pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void {
(L as *mut c_char).sub(LUA_EXTRASPACE) as *mut c_void
}
#[inline(always)]
pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number {
lua_tonumberx(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_pop(L: *mut lua_State, n: c_int) {
lua_settop(L, -n - 1)
}
#[inline(always)]
pub unsafe fn lua_newtable(L: *mut lua_State) {
lua_createtable(L, 0, 0)
}
#[inline(always)]
pub unsafe fn lua_register(L: *mut lua_State, n: *const c_char, f: lua_CFunction) {
lua_pushcfunction(L, f);
lua_setglobal(L, n)
}
#[inline(always)]
pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
lua_pushcclosure(L, f, 0)
}
#[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TFUNCTION) as c_int
}
#[inline(always)]
pub unsafe fn lua_istable(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TTABLE) as c_int
}
#[inline(always)]
pub unsafe fn lua_islightuserdata(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TLIGHTUSERDATA) as c_int
}
#[inline(always)]
pub unsafe fn lua_isnil(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TNIL) as c_int
}
#[inline(always)]
pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TBOOLEAN) as c_int
}
#[inline(always)]
pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TTHREAD) as c_int
}
#[inline(always)]
pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TNONE) as c_int
}
#[inline(always)]
pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) <= 0) as c_int
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int {
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
}
#[inline(always)]
pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void {
if lua_islightuserdata(L, idx) != 0 {
return lua_touserdata(L, idx);
}
ptr::null_mut()
}
#[inline(always)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_insert(L: *mut lua_State, idx: c_int) {
lua_rotate(L, idx, 1)
}
#[inline(always)]
pub unsafe fn lua_remove(L: *mut lua_State, idx: c_int) {
lua_rotate(L, idx, -1);
lua_pop(L, 1)
}
#[inline(always)]
pub unsafe fn lua_replace(L: *mut lua_State, idx: c_int) {
lua_copy(L, -1, idx);
lua_pop(L, 1)
}
#[inline(always)]
pub unsafe fn lua_xpush(from: *mut lua_State, to: *mut lua_State, idx: c_int) {
lua_pushvalue(from, idx);
lua_xmove(from, to, 1);
}
#[inline(always)]
pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
lua_newuserdatauv(L, sz, 1)
}
#[inline(always)]
pub unsafe fn lua_getuservalue(L: *mut lua_State, idx: c_int) -> c_int {
lua_getiuservalue(L, idx, 1)
}
#[inline(always)]
pub unsafe fn lua_setuservalue(L: *mut lua_State, idx: c_int) -> c_int {
lua_setiuservalue(L, idx, 1)
}
//
// Debug API
//
// Maximum size for the description of the source of a function in debug information.
const LUA_IDSIZE: usize = 60;
// Event codes
pub const LUA_HOOKCALL: c_int = 0;
pub const LUA_HOOKRET: c_int = 1;
pub const LUA_HOOKLINE: c_int = 2;
pub const LUA_HOOKCOUNT: c_int = 3;
pub const LUA_HOOKTAILCALL: c_int = 4;
// Event masks
pub const LUA_MASKCALL: c_int = 1 << (LUA_HOOKCALL as usize);
pub const LUA_MASKRET: c_int = 1 << (LUA_HOOKRET as usize);
pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events.
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
pub fn lua_setlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
pub fn lua_getupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void;
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int);
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
}
#[repr(C)]
pub struct lua_Debug {
pub event: c_int,
pub name: *const c_char, // (n)
pub namewhat: *const c_char, // (n) 'global', 'local', 'field', 'method'
pub what: *const c_char, // (S) 'Lua', 'C', 'main', 'tail'
pub source: *const c_char, // (S)
pub srclen: usize, // (S)
pub currentline: c_int, // (l)
pub linedefined: c_int, // (S)
pub lastlinedefined: c_int, // (S)
pub nups: c_uchar, // (u) number of upvalues
pub nparams: c_uchar, // (u) number of parameters
pub isvararg: c_char, // (u)
pub extraargs: c_uchar, // (t) number of extra arguments
pub istailcall: c_char, // (t)
pub ftransfer: c_int, // (r) index of first value transferred
pub ntransfer: c_int, // (r) number of transferred values
pub short_src: [c_char; LUA_IDSIZE], // (S)
// lua.h mentions this is for private use
i_ci: *mut c_void,
}
+55
View File
@@ -0,0 +1,55 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_GLIBK: c_int = 1;
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
pub const LUA_LOADLIBK: c_int = LUA_GLIBK << 1;
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_COLIBK: c_int = LUA_GLIBK << 2;
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_DBLIBK: c_int = LUA_GLIBK << 3;
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_IOLIBK: c_int = LUA_GLIBK << 4;
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_MATHLIBK: c_int = LUA_GLIBK << 5;
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_OSLIBK: c_int = LUA_GLIBK << 6;
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_STRLIBK: c_int = LUA_GLIBK << 7;
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_TABLIBK: c_int = LUA_GLIBK << 8;
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_UTF8LIBK: c_int = LUA_GLIBK << 9;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_package(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_io(L: *mut lua_State) -> c_int;
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_os(L: *mut lua_State) -> c_int;
pub fn luaopen_string(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openselectedlibs(L: *mut lua_State, load: c_int, preload: c_int);
}
pub unsafe fn luaL_openlibs(L: *mut lua_State) {
luaL_openselectedlibs(L, !0, 0);
}
+9
View File
@@ -0,0 +1,9 @@
//! Low level bindings to Lua 5.5.
pub use lauxlib::*;
pub use lua::*;
pub use lualib::*;
pub mod lauxlib;
pub mod lua;
pub mod lualib;
+54 -41
View File
@@ -23,8 +23,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
}
}
const COMPAT53_LEVELS1: c_int = 12; // size of the first part of the stack
const COMPAT53_LEVELS2: c_int = 10; // size of the second part of the stack
const COMPAT53_LEVELS1: c_int = 10; // size of the first part of the stack
const COMPAT53_LEVELS2: c_int = 11; // size of the second part of the stack
unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> c_int {
if level == 0 || lua_istable(L, -1) == 0 {
@@ -41,11 +41,10 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
lua_pop(L, 1); // remove value (but keep name)
return 1;
} else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, c".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
// stack: lib_name, lib_table, field_name (top)
lua_pushliteral(L, c"."); // place '.' between the two names
lua_replace(L, -3); // (in the slot occupied by table)
lua_concat(L, 3); // lib_name.field_name
return 1;
}
}
@@ -54,14 +53,25 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
0 // not found
}
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int {
unsafe fn compat53_pushglobalfuncname(
L: *mut lua_State,
L1: *mut lua_State,
level: c_int,
ar: *mut lua_Debug,
) -> c_int {
let top = lua_gettop(L);
// push function
lua_getinfo(L, level, cstr!("f"), ar);
lua_getinfo(L1, level, cstr!("f"), ar); // push function
lua_xmove(L1, L, 1); // and move onto L
lua_pushvalue(L, LUA_GLOBALSINDEX);
luaL_checkstack(L, 6, cstr!("not enough stack")); // slots for 'findfield'
if compat53_findfield(L, top + 1, 2) != 0 {
let name = lua_tostring(L, -1);
if CStr::from_ptr(name).to_bytes().starts_with(b"_G.") {
lua_pushstring(L, name.add(3)); // push name without prefix
lua_remove(L, -2); // remove original name
}
lua_copy(L, -1, top + 1); // move name to proper place
lua_pop(L, 2); // remove pushed values
lua_settop(L, top + 1); // remove pushed values
1
} else {
lua_settop(L, top); // remove function and global table
@@ -69,13 +79,16 @@ unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut
}
}
unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) {
unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, level: c_int, ar: *mut lua_Debug) {
if !(*ar).name.is_null() {
// is there a name?
lua_pushfstring(L, cstr!("function '%s'"), (*ar).name);
} else if compat53_pushglobalfuncname(L, level, ar) != 0 {
} else if compat53_pushglobalfuncname(L, L1, level, ar) != 0 {
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else if *(*ar).what != b'C' as c_char {
// for Lua functions, use <file:line>
lua_pushfstring(L, cstr!("function <%s:%d>"), (*ar).short_src, (*ar).linedefined);
} else {
lua_pushliteral(L, c"?");
}
@@ -190,9 +203,7 @@ pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_in
#[inline(always)]
pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int {
let abs_i = lua_absindex(L, idx);
lua_pushlightuserdata(L, p as *mut c_void);
lua_rawget(L, abs_i)
lua_rawgetptagged(L, idx, p, 0)
}
#[inline(always)]
@@ -226,11 +237,7 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
#[inline(always)]
pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
let abs_i = lua_absindex(L, idx);
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_pushlightuserdata(L, p as *mut c_void);
lua_insert(L, -2);
lua_rawset(L, abs_i);
lua_rawsetptagged(L, idx, p, 0)
}
#[inline(always)]
@@ -452,36 +459,42 @@ pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) {
let mut ar: lua_Debug = mem::zeroed();
let top = lua_gettop(L);
let numlevels = lua_stackdepth(L);
let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 {
COMPAT53_LEVELS1
} else {
0
};
#[rustfmt::skip]
let mut limit = if numlevels - level > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { COMPAT53_LEVELS1 } else { -1 };
let mut buf: luaL_Strbuf = mem::zeroed();
luaL_buffinit(L, &mut buf);
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
luaL_addstring(&mut buf, msg);
luaL_addstring(&mut buf, cstr!("\n"));
}
lua_pushliteral(L, c"stack traceback:");
while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 {
if level + 1 == mark {
luaL_addstring(&mut buf, cstr!("stack traceback:"));
while lua_getinfo(L1, level, cstr!("sln"), &mut ar) != 0 {
if limit == 0 {
// too many levels?
lua_pushliteral(L, c"\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
let n = numlevels - level - COMPAT53_LEVELS2;
// add warning about skip ("n + 1" because we skip current level too)
lua_pushfstring(L, cstr!("\n\t...\t(skipping %d levels)"), n + 1);
luaL_addvalue(&mut buf);
level += n; // and skip to last levels
} else {
lua_getinfo(L1, level, cstr!("sln"), &mut ar);
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src);
luaL_addstring(&mut buf, cstr!("\n\t"));
luaL_addstring(&mut buf, ar.short_src);
luaL_addstring(&mut buf, cstr!(":"));
if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
luaL_addunsigned(&mut buf, ar.currentline as _);
luaL_addstring(&mut buf, cstr!(":"));
}
lua_pushliteral(L, c" in ");
compat53_pushfuncname(L, level, &mut ar);
lua_concat(L, lua_gettop(L) - top);
luaL_addstring(&mut buf, cstr!(" in "));
compat53_pushfuncname(L, L1, level, &mut ar);
luaL_addvalue(&mut buf);
}
level += 1;
limit -= 1;
}
lua_concat(L, lua_gettop(L) - top);
luaL_pushresult(&mut buf);
}
pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
@@ -544,7 +557,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
+35 -2
View File
@@ -3,7 +3,10 @@
use std::os::raw::{c_char, c_float, c_int, c_void};
use std::ptr;
use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX};
use super::lua::{self, LUA_REGISTRYINDEX, lua_CFunction, lua_Number, lua_State, lua_Unsigned};
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
#[repr(C)]
pub struct luaL_Reg {
@@ -82,6 +85,9 @@ unsafe extern "C-unwind" {
pub fn luaL_callyieldable(L: *mut lua_State, nargs: c_int, nresults: c_int) -> c_int;
#[link_name = "luaL_traceback"]
pub fn luaL_traceback_(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
// sandbox libraries and globals
#[link_name = "luaL_sandbox"]
pub fn luaL_sandbox_(L: *mut lua_State);
@@ -116,7 +122,19 @@ pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *
luaL_optlstring(L, n, d, ptr::null_mut())
}
// TODO: luaL_opt
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
#[inline(always)]
pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) -> c_int {
@@ -209,3 +227,18 @@ pub unsafe fn luaL_addstring(B: *mut luaL_Strbuf, s: *const c_char) {
}
luaL_addlstring(B, s, len);
}
pub unsafe fn luaL_addunsigned(B: *mut luaL_Strbuf, mut n: lua_Unsigned) {
let mut buf: [c_char; 32] = [0; 32];
let mut i = 32;
loop {
i -= 1;
let digit = (n % 10) as u8;
buf[i] = (b'0' + digit) as c_char;
n /= 10;
if n == 0 {
break;
}
}
luaL_addlstring(B, buf.as_ptr().add(i), 32 - i);
}
+39 -3
View File
@@ -37,6 +37,16 @@ pub const LUA_ERRRUN: c_int = 2;
pub const LUA_ERRSYNTAX: c_int = 3;
pub const LUA_ERRMEM: c_int = 4;
pub const LUA_ERRERR: c_int = 5;
pub const LUA_BREAK: c_int = 6; // yielded for a debug breakpoint
//
// Coroutine status
//
pub const LUA_CORUN: c_int = 0; // running
pub const LUA_COSUS: c_int = 1; // suspended
pub const LUA_CONOR: c_int = 2; // 'normal' (it resumed another coroutine)
pub const LUA_COFIN: c_int = 3; // finished
pub const LUA_COERR: c_int = 4; // finished with error
/// A raw Lua state associated with a thread.
#[repr(C)]
@@ -145,8 +155,15 @@ unsafe extern "C-unwind" {
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char;
pub fn lua_tolstringatom(
L: *mut lua_State,
idx: c_int,
len: *mut usize,
atom: *mut c_int,
) -> *const c_char;
pub fn lua_namecallatom(L: *mut lua_State, atom: *mut c_int) -> *const c_char;
pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize;
#[link_name = "lua_objlen"]
pub fn lua_objlen_(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>;
pub fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
pub fn lua_tolightuserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void;
@@ -203,6 +220,7 @@ unsafe extern "C-unwind" {
pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int;
#[link_name = "lua_rawgeti"]
pub fn lua_rawgeti_(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
pub fn lua_rawgetptagged(L: *mut lua_State, idx: c_int, p: *const c_void, tag: c_int) -> c_int;
pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int);
pub fn lua_setreadonly(L: *mut lua_State, idx: c_int, enabled: c_int);
@@ -217,9 +235,11 @@ unsafe extern "C-unwind" {
//
pub fn lua_settable(L: *mut lua_State, idx: c_int);
pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char);
pub fn lua_rawsetfield(L: *mut lua_State, idx: c_int, k: *const c_char);
pub fn lua_rawset(L: *mut lua_State, idx: c_int);
#[link_name = "lua_rawseti"]
pub fn lua_rawseti_(L: *mut lua_State, idx: c_int, n: c_int);
pub fn lua_rawsetptagged(L: *mut lua_State, idx: c_int, p: *const c_void, tag: c_int);
pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
pub fn lua_setfenv(L: *mut lua_State, idx: c_int) -> c_int;
@@ -235,6 +255,7 @@ unsafe extern "C-unwind" {
) -> c_int;
pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int);
pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int;
pub fn lua_cpcall(L: *mut lua_State, f: lua_CFunction, ud: *mut c_void) -> c_int;
//
// Coroutine functions
@@ -248,6 +269,12 @@ unsafe extern "C-unwind" {
pub fn lua_isyieldable(L: *mut lua_State) -> c_int;
pub fn lua_getthreaddata(L: *mut lua_State) -> *mut c_void;
pub fn lua_setthreaddata(L: *mut lua_State, data: *mut c_void);
pub fn lua_costatus(L: *mut lua_State, co: *mut lua_State) -> c_int;
}
#[inline(always)]
pub unsafe fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize {
lua_objlen_(L, idx) as usize
}
//
@@ -284,7 +311,7 @@ unsafe extern "C-unwind" {
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
// TODO: lua_encodepointer
pub fn lua_encodepointer(L: *mut lua_State, p: usize) -> usize;
pub fn lua_clock() -> c_double;
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>);
@@ -295,6 +322,7 @@ unsafe extern "C-unwind" {
pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char;
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int);
pub fn lua_cleartable(L: *mut lua_State, idx: c_int);
pub fn lua_clonetable(L: *mut lua_State, idx: c_int);
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
}
@@ -354,7 +382,10 @@ pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State, data: T) -> *mut T {
ud_ptr
}
// TODO: lua_strlen
#[inline(always)]
pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize {
lua_objlen(L, i)
}
#[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
@@ -544,4 +575,9 @@ unsafe extern "C" {
unsafe extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void;
pub fn lua_gcdump(
L: *mut lua_State,
file: *mut c_void,
category_name: Option<unsafe extern "C" fn(L: *mut lua_State, memcat: u8) -> *const c_char>,
);
}
+49 -7
View File
@@ -23,6 +23,16 @@ pub enum luarequire_WriteResult {
Failure,
}
/// Represents whether a configuration file is present, and if so, its syntax.
#[repr(C)]
pub enum luarequire_ConfigStatus {
Absent,
// Signals the presence of multiple configuration files
Ambiguous,
PresentJson,
PresentLuau,
}
#[repr(C)]
pub struct luarequire_Configuration {
// Returns whether requires are permitted from the given chunkname.
@@ -48,6 +58,29 @@ pub struct luarequire_Configuration {
path: *const c_char,
) -> luarequire_NavigateResult,
// Provides an initial alias override opportunity prior to searching for configuration files.
// If NAVIGATE_SUCCESS is returned, the internal state must be updated to point at the
// aliased location.
// Can be left undefined.
pub to_alias_override: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> luarequire_NavigateResult,
>,
// Provides a final override opportunity if an alias cannot be found in configuration files. If
// NAVIGATE_SUCCESS is returned, this must update the internal state to point at the aliased module.
// Can be left undefined.
pub to_alias_fallback: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> luarequire_NavigateResult,
>,
// Navigates through the context by making mutations to the internal state.
pub to_parent:
unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult,
@@ -90,13 +123,14 @@ pub struct luarequire_Configuration {
size_out: *mut usize,
) -> luarequire_WriteResult,
// Returns whether a configuration file is present in the current context.
// If not, require-by-string will call to_parent until either a configuration file is present or
// Returns whether a configuration file is present in the current context, and if so, its syntax.
// If not present, require-by-string will call to_parent until either a configuration file is present or
// NAVIGATE_FAILURE is returned (at root).
pub is_config_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
pub get_config_status:
unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_ConfigStatus,
// Parses the configuration file in the current context for the given alias and returns its
// value or WRITE_FAILURE if not found. This function is only called if is_config_present
// value or WRITE_FAILURE if not found. This function is only called if get_config_status
// returns true. If this function pointer is set, get_config must not be set. Opting in to this
// function pointer disables parsing configuration files internally and can be used for finer
// control over the configuration file parsing process.
@@ -111,9 +145,10 @@ pub struct luarequire_Configuration {
) -> luarequire_WriteResult,
>,
// Provides the contents of the configuration file in the current context. This function is only called
// if is_config_present returns true. If this function pointer is set, get_alias must not be set. Opting
// in to this function pointer enables parsing configuration files internally.
// Provides the contents of the configuration file in the current context.
// This function is only called if get_config_status does not return CONFIG_ABSENT. If this function
// pointer is set, get_alias must not be set. Opting in to this function pointer enables parsing
// configuration files internally.
pub get_config: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
@@ -124,6 +159,13 @@ pub struct luarequire_Configuration {
) -> luarequire_WriteResult,
>,
// Returns the maximum number of milliseconds to allow for executing a given Luau-syntax configuration
// file. This function is only called if get_config_status returns CONFIG_PRESENT_LUAU and can be left
// undefined if support for Luau-syntax configuration files is not needed. A default value of 2000ms is
// used. Negative values are treated as infinite.
pub get_luau_config_timeout:
Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> c_int>,
// Executes the module and places the result on the stack. Returns the number of results placed on the
// stack.
// Returning -1 directs the requiring thread to yield. In this case, this thread should be resumed with
+79 -4
View File
@@ -1,3 +1,5 @@
use std::io;
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
@@ -50,13 +52,18 @@ impl Buffer {
#[track_caller]
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
let lua = self.0.lua.lock();
let data = unsafe {
let (buf, size) = self.as_raw_parts(&lua);
std::slice::from_raw_parts_mut(buf, size)
};
let data = self.as_slice_mut(&lua);
data[offset..offset + bytes.len()].copy_from_slice(bytes);
}
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
/// buffer.
///
/// Buffer operations are infallible, none of the read/write functions will return a Err.
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
BufferCursor(self, 0)
}
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
unsafe {
let (buf, size) = self.as_raw_parts(lua);
@@ -64,6 +71,14 @@ impl Buffer {
}
}
#[allow(clippy::mut_from_ref)]
fn as_slice_mut(&self, lua: &RawLua) -> &mut [u8] {
unsafe {
let (buf, size) = self.as_raw_parts(lua);
std::slice::from_raw_parts_mut(buf, size)
}
}
#[cfg(feature = "luau")]
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
let mut size = 0usize;
@@ -78,6 +93,66 @@ impl Buffer {
}
}
struct BufferCursor(Buffer, usize);
impl io::Read for BufferCursor {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let lua = self.0 .0.lua.lock();
let data = self.0.as_slice(&lua);
if self.1 == data.len() {
return Ok(0);
}
let len = buf.len().min(data.len() - self.1);
buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
self.1 += len;
Ok(len)
}
}
impl io::Write for BufferCursor {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let lua = self.0 .0.lua.lock();
let data = self.0.as_slice_mut(&lua);
if self.1 == data.len() {
return Ok(0);
}
let len = buf.len().min(data.len() - self.1);
data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
self.1 += len;
Ok(len)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Seek for BufferCursor {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let lua = self.0 .0.lua.lock();
let data = self.0.as_slice(&lua);
let new_offset = match pos {
io::SeekFrom::Start(offset) => offset as i64,
io::SeekFrom::End(offset) => data.len() as i64 + offset,
io::SeekFrom::Current(offset) => self.1 as i64 + offset,
};
if new_offset < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to a negative position",
));
}
if new_offset as usize > data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to a position beyond the end of the buffer",
));
}
self.1 = new_offset as usize;
Ok(self.1 as u64)
}
}
#[cfg(feature = "serde")]
impl Serialize for Buffer {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
+22 -1
View File
@@ -500,11 +500,21 @@ impl FromLua for crate::Buffer {
impl IntoLua for StdString {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
#[cfg(feature = "lua55")]
if true {
return Ok(Value::String(lua.create_external_string(self)?));
}
Ok(Value::String(lua.create_string(self)?))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
#[cfg(feature = "lua55")]
if lua.unlikely_memory_error() {
return crate::util::push_external_string(lua.state(), self.into(), false);
}
push_bytes_into_stack(self, lua)
}
}
@@ -591,6 +601,11 @@ impl FromLua for Box<str> {
impl IntoLua for CString {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
#[cfg(feature = "lua55")]
if true {
return Ok(Value::String(lua.create_external_string(self)?));
}
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
}
@@ -635,6 +650,11 @@ impl IntoLua for Cow<'_, CStr> {
impl IntoLua for BString {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
#[cfg(feature = "lua55")]
if true {
return Ok(Value::String(lua.create_external_string(self)?));
}
Ok(Value::String(lua.create_string(self)?))
}
}
@@ -1191,7 +1211,8 @@ impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
Err(_) => match R::from_stack(idx, lua).map(Either::Right) {
Ok(r) => Ok(r),
Err(_) => {
let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx));
let value_type_name =
CStr::from_ptr(ffi::lua_typename(lua.state(), ffi::lua_type(lua.state(), idx)));
Err(Error::FromLuaConversionError {
from: value_type_name.to_str().unwrap(),
to: Self::type_name(),
+32 -8
View File
@@ -159,10 +159,10 @@ impl<'a> Debug<'a> {
/// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
/// false otherwise.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52")))
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52")))
)]
pub fn is_tail_call(&self) -> bool {
unsafe {
@@ -191,9 +191,9 @@ impl<'a> Debug<'a> {
#[cfg(not(feature = "luau"))]
let stack = DebugStack {
num_ups: (*self.ar).nups as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar).nparams as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar).isvararg != 0,
};
#[cfg(feature = "luau")]
@@ -248,17 +248,41 @@ pub struct DebugStack {
/// Number of upvalues.
pub num_ups: u8,
/// Number of parameters.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
)))
)]
pub num_params: u8,
/// Whether the function is a vararg function.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
)))
)]
pub is_vararg: bool,
}
+41 -17
View File
@@ -18,7 +18,7 @@ use {
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
std::future::{self, Future},
std::pin::Pin,
std::pin::{pin, Pin},
std::task::{Context, Poll},
};
@@ -32,6 +32,7 @@ pub struct Function(pub(crate) ValueRef);
///
/// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct FunctionInfo {
/// A (reasonable) name of the function (`None` if the name cannot be found).
pub name: Option<String>,
@@ -50,6 +51,16 @@ pub struct FunctionInfo {
pub line_defined: Option<usize>,
/// The line number where the definition of the function ends (not set by Luau).
pub last_line_defined: Option<usize>,
/// The number of upvalues of the function.
pub num_upvalues: u8,
/// The number of parameters of the function (always 0 for C).
#[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
pub num_params: u8,
/// Whether the function is a variadic function (always true for C).
#[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
pub is_vararg: bool,
}
/// Luau function coverage snapshot.
@@ -276,7 +287,7 @@ impl Function {
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_getfenv(state, -1);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 {
// Traverse upvalues until we find the _ENV one
match ffi::lua_getupvalue(state, -1, i) {
@@ -316,7 +327,7 @@ impl Function {
lua.push_ref(&env.0);
ffi::lua_setfenv(state, -2);
}
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 {
match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => return Ok(false),
@@ -343,7 +354,8 @@ impl Function {
/// Returns information about the function.
///
/// Corresponds to the `>Sn` what mask for [`lua_getinfo`] when applied to the function.
/// Corresponds to the `>Snu` (`>Sn` for Luau) what mask for
/// [`lua_getinfo`] when applied to the function.
///
/// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo
pub fn info(&self) -> FunctionInfo {
@@ -355,11 +367,16 @@ impl Function {
let mut ar: ffi::lua_Debug = mem::zeroed();
lua.push_ref(&self.0);
#[cfg(not(feature = "luau"))]
let res = ffi::lua_getinfo(state, cstr!(">Sn"), &mut ar);
let res = ffi::lua_getinfo(state, cstr!(">Snu"), &mut ar);
#[cfg(not(feature = "luau"))]
mlua_assert!(res != 0, "lua_getinfo failed with `>Snu`");
#[cfg(feature = "luau")]
let res = ffi::lua_getinfo(state, -1, cstr!("sn"), &mut ar);
mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`");
let res = ffi::lua_getinfo(state, -1, cstr!("snau"), &mut ar);
#[cfg(feature = "luau")]
mlua_assert!(res != 0, "lua_getinfo failed with `snau`");
FunctionInfo {
name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()),
@@ -381,6 +398,14 @@ impl Function {
last_line_defined: linenumber_to_usize(ar.lastlinedefined),
#[cfg(feature = "luau")]
last_line_defined: None,
#[cfg(not(feature = "luau"))]
num_upvalues: ar.nups as _,
#[cfg(feature = "luau")]
num_upvalues: ar.nupvals,
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
num_params: ar.nparams,
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
is_vararg: ar.isvararg != 0,
}
}
}
@@ -400,11 +425,14 @@ impl Function {
_state: *mut ffi::lua_State,
buf: *const c_void,
buf_len: usize,
data: *mut c_void,
data_ptr: *mut c_void,
) -> c_int {
let data = &mut *(data as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
// If `data` is null, then it's a signal that write is finished.
if !data_ptr.is_null() && buf_len > 0 {
let data = &mut *(data_ptr as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
}
0
}
@@ -669,13 +697,9 @@ impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: We're not moving any pinned data
let this = unsafe { self.get_unchecked_mut() };
let this = self.get_mut();
match &mut this.0 {
Ok(thread) => {
let pinned_thread = unsafe { Pin::new_unchecked(thread) };
pinned_thread.poll(cx)
}
Ok(thread) => pin!(thread).poll(cx),
Err(err) => Poll::Ready(Err(err.clone())),
}
}
+5 -2
View File
@@ -132,7 +132,7 @@ pub use crate::{
buffer::Buffer,
chunk::{CompileConstant, Compiler},
function::CoverageInfo,
luau::{NavigateError, Require, TextRequirer},
luau::{HeapDump, NavigateError, Require, TextRequirer},
vector::Vector,
};
@@ -142,7 +142,10 @@ pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
#[cfg(feature = "serde")]
#[doc(inline)]
pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt};
pub use crate::{
serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt},
value::SerializableValue,
};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
+178
View File
@@ -0,0 +1,178 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::mem;
use std::os::raw::c_char;
use crate::state::ExtraData;
use super::json::{self, Json};
/// Represents a heap dump of a Luau memory state.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub struct HeapDump {
data: Json<'static>, // refers to the contents of `buf`
buf: Box<str>,
}
impl HeapDump {
/// Dumps the current Lua heap state.
pub(crate) unsafe fn new(state: *mut ffi::lua_State) -> Option<Self> {
unsafe extern "C" fn category_name(state: *mut ffi::lua_State, cat: u8) -> *const c_char {
(&*ExtraData::get(state))
.mem_categories
.get(cat as usize)
.map(|s| s.as_ptr())
.unwrap_or(cstr!("unknown"))
}
let mut buf = Vec::new();
unsafe {
let file = libc::tmpfile();
if file.is_null() {
return None;
}
ffi::lua_gcdump(state, file as *mut _, Some(category_name));
libc::fseek(file, 0, libc::SEEK_END);
let len = libc::ftell(file) as usize;
libc::rewind(file);
if len > 0 {
buf.reserve(len);
libc::fread(buf.as_mut_ptr() as *mut _, 1, len, file);
buf.set_len(len);
}
libc::fclose(file);
}
let buf = String::from_utf8(buf).ok()?.into_boxed_str();
let data = json::parse(unsafe { mem::transmute::<&str, &'static str>(&buf) }).ok()?;
Some(HeapDump { data, buf })
}
/// Returns the raw JSON representation of the heap dump.
///
/// The JSON structure is an internal detail and may change in future versions.
#[doc(hidden)]
pub fn to_json(&self) -> &str {
&self.buf
}
/// Returns the total size of the Lua heap in bytes.
pub fn size(&self) -> u64 {
self.data["stats"]["size"].as_u64().unwrap_or_default()
}
/// Returns a mapping from object type to (count, total size in bytes).
///
/// If `category` is provided, only objects in that category are considered.
pub fn size_by_type<'a>(&'a self, category: Option<&str>) -> HashMap<&'a str, (usize, u64)> {
self.size_by_type_inner(category).unwrap_or_default()
}
fn size_by_type_inner<'a>(&'a self, category: Option<&str>) -> Option<HashMap<&'a str, (usize, u64)>> {
let category_id = match category {
// If we cannot find the category, return empty result
Some(cat) => Some(self.find_category_id(cat)?),
None => None,
};
let mut size_by_type = HashMap::new();
let objects = self.data["objects"].as_object()?;
for obj in objects.values() {
if let Some(cat_id) = category_id {
if obj["cat"].as_i64()? != cat_id {
continue;
}
}
update_size(&mut size_by_type, obj["type"].as_str()?, obj["size"].as_u64()?);
}
Some(size_by_type)
}
/// Returns a mapping from category name to total size in bytes.
pub fn size_by_category(&self) -> HashMap<&str, u64> {
let mut size_by_category = HashMap::new();
if let Some(categories) = self.data["stats"]["categories"].as_object() {
for cat in categories.values() {
if let Some(cat_name) = cat["name"].as_str() {
size_by_category.insert(cat_name, cat["size"].as_u64().unwrap_or_default());
}
}
}
size_by_category
}
/// Returns a mapping from userdata type to (count, total size in bytes).
pub fn size_by_userdata<'a>(&'a self, category: Option<&str>) -> HashMap<&'a str, (usize, u64)> {
self.size_by_userdata_inner(category).unwrap_or_default()
}
fn size_by_userdata_inner<'a>(
&'a self,
category: Option<&str>,
) -> Option<HashMap<&'a str, (usize, u64)>> {
let category_id = match category {
// If we cannot find the category, return empty result
Some(cat) => Some(self.find_category_id(cat)?),
None => None,
};
let mut size_by_userdata = HashMap::new();
let objects = self.data["objects"].as_object()?;
for obj in objects.values() {
if obj["type"] != "userdata" {
continue;
}
if let Some(cat_id) = category_id {
if obj["cat"].as_i64()? != cat_id {
continue;
}
}
// Determine userdata type from metatable
let mut ud_type = "unknown";
if let Some(metatable_addr) = obj["metatable"].as_str() {
if let Some(t) = get_key(objects, &objects[metatable_addr], "__type") {
ud_type = t;
}
}
update_size(&mut size_by_userdata, ud_type, obj["size"].as_u64()?);
}
Some(size_by_userdata)
}
/// Finds the category ID for a given category name.
fn find_category_id(&self, category: &str) -> Option<i64> {
let categories = self.data["stats"]["categories"].as_object()?;
for (cat_id, cat) in categories {
if cat["name"].as_str() == Some(category) {
return cat_id.parse().ok();
}
}
None
}
}
/// Updates the size mapping for a given key.
fn update_size<K: Eq + Hash>(size_type: &mut HashMap<K, (usize, u64)>, key: K, size: u64) {
let (ref mut count, ref mut total_size) = size_type.entry(key).or_insert((0, 0));
*count += 1;
*total_size += size;
}
/// Retrieves the value associated with a given `key` from a Lua table `tbl`.
fn get_key<'a>(objects: &'a HashMap<&'a str, Json>, tbl: &Json, key: &str) -> Option<&'a str> {
let pairs = tbl["pairs"].as_array()?;
for kv in pairs.chunks_exact(2) {
#[rustfmt::skip]
let (Some(key_addr), Some(val_addr)) = (kv[0].as_str(), kv[1].as_str()) else { continue; };
if objects[key_addr]["type"] == "string" && objects[key_addr]["data"].as_str() == Some(key) {
if objects[val_addr]["type"] == "string" {
return objects[val_addr]["data"].as_str();
} else {
break;
}
}
}
None
}
+327
View File
@@ -0,0 +1,327 @@
use std::array;
use std::collections::HashMap;
use std::iter::Peekable;
use std::ops::Index;
use std::str::CharIndices;
// A simple JSON parser and representation.
// This parser supports only a subset of JSON specification and is intended for Luau's use cases.
#[derive(Debug, PartialEq)]
pub(crate) enum Json<'a> {
Null,
Bool(bool),
Integer(i64),
Number(f64),
String(&'a str),
Array(Vec<Json<'a>>),
Object(HashMap<&'a str, Json<'a>>),
}
impl<'a> Index<&str> for Json<'a> {
type Output = Json<'a>;
fn index(&self, key: &str) -> &Self::Output {
match self {
Json::Object(map) => map.get(key).unwrap_or(&Json::Null),
_ => &Json::Null,
}
}
}
impl PartialEq<&str> for Json<'_> {
fn eq(&self, other: &&str) -> bool {
matches!(self, Json::String(s) if s == other)
}
}
impl<'a> Json<'a> {
pub(crate) fn as_str(&self) -> Option<&'a str> {
match self {
Json::String(s) => Some(s),
_ => None,
}
}
pub(crate) fn as_i64(&self) -> Option<i64> {
match self {
Json::Integer(i) => Some(*i),
Json::Number(n) if n.fract() == 0.0 => Some(*n as i64),
_ => None,
}
}
pub(crate) fn as_u64(&self) -> Option<u64> {
self.as_i64()
.and_then(|i| if i >= 0 { Some(i as u64) } else { None })
}
pub(crate) fn as_array(&self) -> Option<&[Json<'a>]> {
match self {
Json::Array(arr) => Some(arr),
_ => None,
}
}
pub(crate) fn as_object(&self) -> Option<&HashMap<&'a str, Json<'a>>> {
match self {
Json::Object(map) => Some(map),
_ => None,
}
}
}
pub(crate) fn parse<'a>(s: &'a str) -> Result<Json<'a>, &'static str> {
let s = s.trim_ascii();
let mut chars = s.char_indices().peekable();
let value = parse_value(s, &mut chars)?;
Ok(value)
}
fn parse_value<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
skip_whitespace(chars);
match chars.peek() {
Some((_, '{')) => parse_object(s, chars),
Some((_, '[')) => parse_array(s, chars),
Some((_, '"')) => parse_string(s, chars).map(Json::String),
Some((_, 't' | 'f')) => parse_bool(chars),
Some((_, 'n')) => parse_null(chars),
Some((_, '-' | '0'..='9')) => parse_number(chars),
Some(_) => Err("unexpected character"),
None => Err("unexpected end of input"),
}
}
fn parse_object<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
chars.next(); // consume '{'
let mut map = HashMap::new();
skip_whitespace(chars);
if matches!(chars.peek(), Some((_, '}'))) {
chars.next();
return Ok(Json::Object(map));
}
loop {
skip_whitespace(chars);
let key = parse_string(s, chars)?;
skip_whitespace(chars);
if !matches!(chars.next(), Some((_, ':'))) {
return Err("expected ':'");
}
let value = parse_value(s, chars)?;
map.insert(key, value);
skip_whitespace(chars);
match chars.next() {
Some((_, ',')) => continue,
Some((_, '}')) => break,
_ => return Err("expected ',' or '}'"),
}
}
Ok(Json::Object(map))
}
fn parse_array<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
chars.next(); // consume '['
let mut arr = Vec::new();
skip_whitespace(chars);
if matches!(chars.peek(), Some((_, ']'))) {
chars.next();
return Ok(Json::Array(arr));
}
loop {
skip_whitespace(chars);
arr.push(parse_value(s, chars)?);
skip_whitespace(chars);
match chars.next() {
Some((_, ',')) => continue,
Some((_, ']')) => return Ok(Json::Array(arr)),
_ => return Err("expected ',' or ']'"),
}
}
}
fn parse_string<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<&'a str, &'static str> {
if !matches!(chars.next(), Some((_, '"'))) {
return Err("expected string starting with '\"'");
}
let start = chars.peek().map(|(i, _)| *i).unwrap_or(0);
for (i, c) in chars {
if c == '"' {
return Ok(&s[start..i]);
}
}
Err("unterminated string")
}
fn parse_number(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
let mut is_float = false;
let mut num = String::new();
while let Some((_, c @ ('0'..='9' | '-' | '.' | 'e' | 'E' | '+'))) = chars.peek() {
num.push(*c);
is_float = is_float || matches!(c, '.' | 'e' | 'E');
chars.next();
}
if !is_float {
let i = num.parse::<i64>().map_err(|_| "invalid integer")?;
return Ok(Json::Integer(i));
}
let n = num.parse::<f64>().map_err(|_| "invalid number")?;
Ok(Json::Number(n))
}
fn parse_bool(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
let bool = next_chars(chars);
if bool == [Some('t'), Some('r'), Some('u'), Some('e')] {
return Ok(Json::Bool(true));
}
if bool == [Some('f'), Some('a'), Some('l'), Some('s')] && matches!(chars.next(), Some((_, 'e'))) {
return Ok(Json::Bool(false));
}
Err("invalid boolean literal")
}
fn parse_null(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
if next_chars(chars) == [Some('n'), Some('u'), Some('l'), Some('l')] {
return Ok(Json::Null);
}
Err("invalid \"null\" literal")
}
fn skip_whitespace(chars: &mut Peekable<CharIndices>) {
while let Some((_, ' ' | '\n' | '\r' | '\t')) = chars.peek() {
chars.next();
}
}
fn next_chars<const N: usize>(chars: &mut Peekable<CharIndices>) -> [Option<char>; N] {
array::from_fn(|_| chars.next().map(|(_, c)| c))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse() {
assert_eq!(parse("null").unwrap(), Json::Null);
assert_eq!(parse("true").unwrap(), Json::Bool(true));
assert_eq!(parse("false").unwrap(), Json::Bool(false));
assert_eq!(parse("42").unwrap(), Json::Integer(42));
assert_eq!(parse("42.0").unwrap(), Json::Number(42.0));
assert_eq!(parse(r#""hello""#).unwrap(), Json::String("hello"));
assert_eq!(
parse("[1,2.0,3]").unwrap(),
Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)])
);
let mut obj = HashMap::new();
obj.insert("key", Json::String("value"));
assert_eq!(parse(r#"{"key":"value"}"#).unwrap(), Json::Object(obj));
}
#[test]
fn test_whitespace_handling() {
assert_eq!(parse(" null ").unwrap(), Json::Null);
assert_eq!(parse(" true ").unwrap(), Json::Bool(true));
assert_eq!(
parse(" [ 1 , 2.0 , 3 ] ").unwrap(),
Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)])
);
let mut obj = HashMap::new();
obj.insert("key", Json::String("value"));
assert_eq!(parse(r#" { "key" : "value" } "#).unwrap(), Json::Object(obj));
}
#[test]
fn test_empty_collections() {
assert_eq!(parse("[]").unwrap(), Json::Array(vec![]));
assert_eq!(parse("{}").unwrap(), Json::Object(HashMap::new()));
assert_eq!(parse("[ ]").unwrap(), Json::Array(vec![]));
assert_eq!(parse("{ }").unwrap(), Json::Object(HashMap::new()));
}
#[test]
fn test_nested_structures() {
assert_eq!(
parse(r#"{"nested":{"inner":"value"}}"#).unwrap(),
Json::Object({
let mut outer = HashMap::new();
let mut inner = HashMap::new();
inner.insert("inner", Json::String("value"));
outer.insert("nested", Json::Object(inner));
outer
})
);
assert_eq!(
parse("[[1,2],[3,4]]").unwrap(),
Json::Array(vec![
Json::Array(vec![Json::Integer(1), Json::Integer(2)]),
Json::Array(vec![Json::Integer(3), Json::Integer(4)])
])
);
}
#[test]
fn test_numbers() {
assert_eq!(parse("0").unwrap(), Json::Integer(0));
assert_eq!(parse("-42").unwrap(), Json::Integer(-42));
assert_eq!(parse("3.14").unwrap(), Json::Number(3.14));
assert_eq!(parse("-3.14").unwrap(), Json::Number(-3.14));
assert_eq!(parse("1e10").unwrap(), Json::Number(1e10));
assert_eq!(parse("1E10").unwrap(), Json::Number(1E10));
assert_eq!(parse("1e-10").unwrap(), Json::Number(1e-10));
assert_eq!(parse("1.5e+10").unwrap(), Json::Number(1.5e+10));
}
#[test]
fn test_strings() {
assert_eq!(parse(r#""""#).unwrap(), Json::String(""));
assert_eq!(parse(r#""hello world""#).unwrap(), Json::String("hello world"));
assert_eq!(
parse(r#""with spaces and 123""#).unwrap(),
Json::String("with spaces and 123")
);
}
#[test]
fn test_mixed_array() {
assert_eq!(
parse(r#"[null, true, false, 35.1, 42, "text", [], {}]"#).unwrap(),
Json::Array(vec![
Json::Null,
Json::Bool(true),
Json::Bool(false),
Json::Number(35.1),
Json::Integer(42),
Json::String("text"),
Json::Array(vec![]),
Json::Object(HashMap::new())
])
);
}
#[test]
fn test_object_multiple_keys() {
let mut obj = HashMap::new();
obj.insert("a", Json::Integer(1));
obj.insert("b", Json::Bool(true));
obj.insert("c", Json::Null);
assert_eq!(parse(r#"{"a":1,"b":true,"c":null}"#).unwrap(), Json::Object(obj));
}
#[test]
fn test_error_cases() {
assert!(parse("").is_err());
assert!(parse("nul").is_err());
assert!(parse("tru").is_err()); // typos:ignore
assert!(parse("fals").is_err()); // typos:ignore
assert!(parse(r#""unterminated"#).is_err());
assert!(parse("[1,2,]").is_err());
assert!(parse(r#"{"key""#).is_err());
assert!(parse(r#"{"key":"value""#).is_err());
assert!(parse(r#"{"key":"value",}"#).is_err());
assert!(parse("invalid").is_err());
assert!(parse("[1 2]").is_err());
assert!(parse(r#"{"key":"value" "key2":"value2"}"#).is_err());
}
}
+55 -2
View File
@@ -1,14 +1,15 @@
use std::ffi::CStr;
use std::ffi::{CStr, CString};
use std::os::raw::c_int;
use std::ptr;
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{callback_error_ext, ExtraData, Lua};
use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use heap_dump::HeapDump;
pub use require::{NavigateError, Require, TextRequirer};
// Since Luau has some missing standard functions, we re-implement them here
@@ -22,6 +23,56 @@ impl Lua {
require::create_require_function(self, require)
}
/// Set the memory category for subsequent allocations from this Lua state.
///
/// The category "main" is reserved for the default memory category.
/// Maximum of 255 categories can be registered.
/// The category is set per Lua thread (state) and affects all allocations made from that
/// thread.
///
/// Return error if too many categories are registered or if the category name is invalid.
///
/// See [`Lua::heap_dump`] for tracking memory usage by category.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_memory_category(&self, category: &str) -> Result<()> {
let lua = self.lock();
if category.contains(|c| !matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_')) {
return Err(Error::runtime("invalid memory category name"));
}
let cat_id = unsafe {
let extra = ExtraData::get(lua.state());
match ((*extra).mem_categories.iter().enumerate())
.find(|&(_, name)| name.as_bytes() == category.as_bytes())
{
Some((id, _)) => id as u8,
None => {
let new_id = (*extra).mem_categories.len() as u8;
if new_id == 255 {
return Err(Error::runtime("too many memory categories registered"));
}
(*extra).mem_categories.push(CString::new(category).unwrap());
new_id
}
}
};
unsafe { ffi::lua_setmemcat(lua.state(), cat_id as i32) };
Ok(())
}
/// Dumps the current Lua VM heap state.
///
/// The returned `HeapDump` can be used to analyze memory usage.
/// It's recommended to call [`Lua::gc_collect`] before dumping the heap.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn heap_dump(&self) -> Result<HeapDump> {
let lua = self.lock();
unsafe { heap_dump::HeapDump::new(lua.state()).ok_or_else(|| Error::runtime("unable to dump heap")) }
}
pub(crate) unsafe fn configure_luau(&self) -> Result<()> {
let globals = self.globals();
@@ -96,4 +147,6 @@ unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int
})
}
mod heap_dump;
mod json;
mod require;
+56 -273
View File
@@ -1,12 +1,10 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::CStr;
use std::io::Result as IoResult;
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int, c_void};
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult;
use std::{env, fmt, fs, mem, ptr};
use std::{fmt, mem, ptr};
use crate::error::{Error, Result};
use crate::function::Function;
@@ -14,9 +12,10 @@ use crate::state::{callback_error_ext, Lua};
use crate::table::Table;
use crate::types::MaybeSend;
// TODO: Rename to FsRequirer
pub use fs::TextRequirer;
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Debug, Clone)]
pub enum NavigateError {
Ambiguous,
@@ -50,9 +49,10 @@ impl From<Error> for NavigateError {
#[cfg(feature = "luau")]
type WriteResult = ffi::luarequire_WriteResult;
#[cfg(feature = "luau")]
type ConfigStatus = ffi::luarequire_ConfigStatus;
/// A trait for handling modules loading and navigation in the Luau `require-by-string` system.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub trait Require {
/// Returns `true` if "require" is permitted for the given chunk name.
fn is_require_allowed(&self, chunk_name: &str) -> bool;
@@ -73,7 +73,7 @@ pub trait Require {
/// Navigate to the given child directory.
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>;
/// Returns whether the context is currently pointing at a module
/// Returns whether the context is currently pointing at a module.
fn has_module(&self) -> bool;
/// Provides a cache key representing the current module.
@@ -103,226 +103,31 @@ impl fmt::Debug for dyn Require {
}
}
/// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)]
pub struct TextRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
rel_path: PathBuf,
/// A physical path to the current Luau module, which is a file or a directory with an
/// `init.lua(u)` file
resolved_path: Option<PathBuf>,
struct Context {
require: Box<dyn Require>,
config_cache: Option<IoResult<Vec<u8>>>,
}
impl TextRequirer {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
/// The file extensions that are considered valid for Luau modules.
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
/// Creates a new `TextRequirer` instance.
pub fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.rsplit_once(':') {
if line.parse::<u32>().is_ok() {
return path;
}
}
chunk_name
}
// Normalizes the path by removing unnecessary components
fn normalize_path(path: &Path) -> PathBuf {
let mut components = VecDeque::new();
for comp in path.components() {
match comp {
Component::Prefix(..) | Component::RootDir => {
components.push_back(comp);
}
Component::CurDir => {}
Component::ParentDir => {
if matches!(components.back(), None | Some(Component::ParentDir)) {
components.push_back(Component::ParentDir);
} else if matches!(components.back(), Some(Component::Normal(..))) {
components.pop_back();
}
}
Component::Normal(..) => components.push_back(comp),
}
}
if matches!(components.front(), None | Some(Component::Normal(..))) {
components.push_front(Component::CurDir);
}
// Join the components back together
components.into_iter().collect()
}
/// Resolve a Luau module path to a physical file or directory.
///
/// Empty directories without init files are considered valid as "intermediate" directories.
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
let mut found_path = None;
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
let current_ext = (path.extension().and_then(|s| s.to_str()))
.map(|s| format!("{s}."))
.unwrap_or_default();
for ext in Self::FILE_EXTENSIONS {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
}
if path.is_dir() {
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
let candidate = path.join(component);
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
if found_path.is_none() {
// Directories without init files are considered valid "intermediate" path
return Ok(None);
}
}
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
}
}
impl Require for TextRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
return Err(NavigateError::NotFound);
}
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
let chunk_path = Self::normalize_path(chunk_name.as_ref());
if chunk_path.extension() == Some("rs".as_ref()) {
// Special case for Rust source files, reset to the current directory
let chunk_filename = chunk_path.file_name().unwrap();
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
self.resolved_path = None;
return Ok(());
}
if chunk_path.is_absolute() {
let resolved_path = Self::resolve_module(&chunk_path)?;
self.abs_path = chunk_path.clone();
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
} else {
// Relative path
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
}
Ok(())
}
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
let path = Self::normalize_path(path.as_ref());
let resolved_path = Self::resolve_module(&path)?;
self.abs_path = path.clone();
self.rel_path = path;
self.resolved_path = resolved_path;
Ok(())
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.clone();
if !abs_path.pop() {
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
// cannot go beyond the root directory.
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
return Err(NavigateError::NotFound);
}
let mut rel_parent = self.rel_path.clone();
rel_parent.pop();
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = Self::normalize_path(&rel_parent);
self.resolved_path = resolved_path;
Ok(())
}
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
let abs_path = self.abs_path.join(name);
let rel_path = self.rel_path.join(name);
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = rel_path;
self.resolved_path = resolved_path;
Ok(())
}
fn has_module(&self) -> bool {
(self.resolved_path.as_deref())
.map(Path::is_file)
.unwrap_or(false)
}
fn cache_key(&self) -> String {
self.resolved_path.as_deref().unwrap().display().to_string()
}
fn has_config(&self) -> bool {
self.abs_path.is_dir() && self.abs_path.join(".luaurc").is_file()
}
fn config(&self) -> IoResult<Vec<u8>> {
fs::read(self.abs_path.join(".luaurc"))
}
fn loader(&self, lua: &Lua) -> Result<Function> {
let name = format!("@{}", self.rel_path.display());
lua.load(self.resolved_path.as_deref().unwrap())
.set_name(name)
.into_function()
}
}
struct Context(Box<dyn Require>);
impl Deref for Context {
type Target = dyn Require;
fn deref(&self) -> &Self::Target {
&*self.0
&*self.require
}
}
impl DerefMut for Context {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.0
&mut *self.require
}
}
impl Context {
fn new(require: impl Require + MaybeSend + 'static) -> Self {
Context {
require: Box::new(require),
config_cache: None,
}
}
}
@@ -447,9 +252,18 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_
write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes())
}
unsafe extern "C-unwind" fn is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = try_borrow!(state, ctx);
this.has_config()
unsafe extern "C-unwind" fn get_config_status(
state: *mut ffi::lua_State,
ctx: *mut c_void,
) -> ConfigStatus {
let mut this = try_borrow_mut!(state, ctx);
if this.has_config() {
this.config_cache = Some(this.config());
if let Some(Ok(data)) = &this.config_cache {
return detect_config_format(data);
}
}
ConfigStatus::Absent
}
unsafe extern "C-unwind" fn get_config(
@@ -459,8 +273,10 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = try_borrow!(state, ctx);
let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| Ok(this.config()?));
let mut this = try_borrow_mut!(state, ctx);
let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
Ok(this.config_cache.take().unwrap_or_else(|| this.config())?)
});
write_to_buffer(buffer, buffer_size, size_out, &config)
}
@@ -483,18 +299,33 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_
(*config).is_require_allowed = is_require_allowed;
(*config).reset = reset;
(*config).jump_to_alias = jump_to_alias;
(*config).to_alias_override = None;
(*config).to_alias_fallback = None;
(*config).to_parent = to_parent;
(*config).to_child = to_child;
(*config).is_module_present = is_module_present;
(*config).get_chunkname = get_chunkname;
(*config).get_loadname = get_loadname;
(*config).get_cache_key = get_cache_key;
(*config).is_config_present = is_config_present;
(*config).get_config_status = get_config_status;
(*config).get_alias = None;
(*config).get_config = Some(get_config);
(*config).load = load;
}
/// Detect configuration file format (JSON or Luau)
#[cfg(feature = "luau")]
fn detect_config_format(data: &[u8]) -> ConfigStatus {
let data = data.trim_ascii();
if data.starts_with(b"{") {
let data = &data[1..].trim_ascii_start();
if data.starts_with(b"\"") || data == b"}" {
return ConfigStatus::PresentJson;
}
}
ConfigStatus::PresentLuau
}
/// Helper function to write data to a buffer
#[cfg(feature = "luau")]
unsafe fn write_to_buffer(
@@ -545,7 +376,7 @@ pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe {
lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| {
let context = Context(Box::new(require));
let context = Context::new(require);
let context_ptr = ffi::lua_newuserdata_t(state, RefCell::new(context));
ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1);
ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file"));
@@ -637,52 +468,4 @@ pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
.into_function()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::TextRequirer;
#[test]
fn test_path_normalize() {
for (input, expected) in [
// Basic formatting checks
("", "./"),
(".", "./"),
("a/relative/path", "./a/relative/path"),
// Paths containing extraneous '.' and '/' symbols
("./remove/extraneous/symbols/", "./remove/extraneous/symbols"),
("./remove/extraneous//symbols", "./remove/extraneous/symbols"),
("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"),
("./remove/extraneous/./symbols", "./remove/extraneous/symbols"),
("../remove/extraneous/symbols/", "../remove/extraneous/symbols"),
("../remove/extraneous//symbols", "../remove/extraneous/symbols"),
("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"),
("../remove/extraneous/./symbols", "../remove/extraneous/symbols"),
("/remove/extraneous/symbols/", "/remove/extraneous/symbols"),
("/remove/extraneous//symbols", "/remove/extraneous/symbols"),
("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"),
("/remove/extraneous/./symbols", "/remove/extraneous/symbols"),
// Paths containing '..'
("./remove/me/..", "./remove"),
("./remove/me/../", "./remove"),
("../remove/me/..", "../remove"),
("../remove/me/../", "../remove"),
("/remove/me/..", "/remove"),
("/remove/me/../", "/remove"),
("./..", "../"),
("./../", "../"),
("../..", "../../"),
("../../", "../../"),
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
"wrong normalization for {input}"
);
}
}
}
mod fs;
+278
View File
@@ -0,0 +1,278 @@
use std::collections::VecDeque;
use std::io::Result as IoResult;
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult;
use std::{env, fs};
use crate::error::Result;
use crate::function::Function;
use crate::state::Lua;
use super::{NavigateError, Require};
/// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)]
pub struct TextRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
rel_path: PathBuf,
/// A physical path to the current Luau module, which is a file or a directory with an
/// `init.lua(u)` file
resolved_path: Option<PathBuf>,
}
impl TextRequirer {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
/// The file extensions that are considered valid for Luau modules.
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
/// The filename for the JSON configuration file.
const LUAURC_CONFIG_FILENAME: &str = ".luaurc";
/// The filename for the Luau configuration file.
const LUAU_CONFIG_FILENAME: &str = ".config.luau";
/// Creates a new `TextRequirer` instance.
pub fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.rsplit_once(':') {
if line.parse::<u32>().is_ok() {
return path;
}
}
chunk_name
}
// Normalizes the path by removing unnecessary components
fn normalize_path(path: &Path) -> PathBuf {
let mut components = VecDeque::new();
for comp in path.components() {
match comp {
Component::Prefix(..) | Component::RootDir => {
components.push_back(comp);
}
Component::CurDir => {}
Component::ParentDir => {
if matches!(components.back(), None | Some(Component::ParentDir)) {
components.push_back(Component::ParentDir);
} else if matches!(components.back(), Some(Component::Normal(..))) {
components.pop_back();
}
}
Component::Normal(..) => components.push_back(comp),
}
}
if matches!(components.front(), None | Some(Component::Normal(..))) {
components.push_front(Component::CurDir);
}
// Join the components back together
components.into_iter().collect()
}
/// Resolve a Luau module path to a physical file or directory.
///
/// Empty directories without init files are considered valid as "intermediate" directories.
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
let mut found_path = None;
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
let current_ext = (path.extension().and_then(|s| s.to_str()))
.map(|s| format!("{s}."))
.unwrap_or_default();
for ext in Self::FILE_EXTENSIONS {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
}
if path.is_dir() {
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
let candidate = path.join(component);
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
if found_path.is_none() {
// Directories without init files are considered valid "intermediate" path
return Ok(None);
}
}
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
}
}
impl Require for TextRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
return Err(NavigateError::NotFound);
}
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
let chunk_path = Self::normalize_path(chunk_name.as_ref());
if chunk_path.extension() == Some("rs".as_ref()) {
// Special case for Rust source files, reset to the current directory
let chunk_filename = chunk_path.file_name().unwrap();
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
self.resolved_path = None;
return Ok(());
}
if chunk_path.is_absolute() {
let resolved_path = Self::resolve_module(&chunk_path)?;
self.abs_path = chunk_path.clone();
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
} else {
// Relative path
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
}
Ok(())
}
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
let path = Self::normalize_path(path.as_ref());
let resolved_path = Self::resolve_module(&path)?;
self.abs_path = path.clone();
self.rel_path = path;
self.resolved_path = resolved_path;
Ok(())
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.clone();
if !abs_path.pop() {
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
// cannot go beyond the root directory.
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
return Err(NavigateError::NotFound);
}
let mut rel_parent = self.rel_path.clone();
rel_parent.pop();
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = Self::normalize_path(&rel_parent);
self.resolved_path = resolved_path;
Ok(())
}
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
let abs_path = self.abs_path.join(name);
let rel_path = self.rel_path.join(name);
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = rel_path;
self.resolved_path = resolved_path;
Ok(())
}
fn has_module(&self) -> bool {
(self.resolved_path.as_deref())
.map(Path::is_file)
.unwrap_or(false)
}
fn cache_key(&self) -> String {
self.resolved_path.as_deref().unwrap().display().to_string()
}
fn has_config(&self) -> bool {
self.abs_path.is_dir() && self.abs_path.join(Self::LUAURC_CONFIG_FILENAME).is_file()
|| self.abs_path.is_dir() && self.abs_path.join(Self::LUAU_CONFIG_FILENAME).is_file()
}
fn config(&self) -> IoResult<Vec<u8>> {
if self.abs_path.join(Self::LUAURC_CONFIG_FILENAME).is_file() {
return fs::read(self.abs_path.join(Self::LUAURC_CONFIG_FILENAME));
}
fs::read(self.abs_path.join(Self::LUAU_CONFIG_FILENAME))
}
fn loader(&self, lua: &Lua) -> Result<Function> {
let name = format!("@{}", self.rel_path.display());
lua.load(self.resolved_path.as_deref().unwrap())
.set_name(name)
.into_function()
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::TextRequirer;
#[test]
fn test_path_normalize() {
for (input, expected) in [
// Basic formatting checks
("", "./"),
(".", "./"),
("a/relative/path", "./a/relative/path"),
// Paths containing extraneous '.' and '/' symbols
("./remove/extraneous/symbols/", "./remove/extraneous/symbols"),
("./remove/extraneous//symbols", "./remove/extraneous/symbols"),
("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"),
("./remove/extraneous/./symbols", "./remove/extraneous/symbols"),
("../remove/extraneous/symbols/", "../remove/extraneous/symbols"),
("../remove/extraneous//symbols", "../remove/extraneous/symbols"),
("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"),
("../remove/extraneous/./symbols", "../remove/extraneous/symbols"),
("/remove/extraneous/symbols/", "/remove/extraneous/symbols"),
("/remove/extraneous//symbols", "/remove/extraneous/symbols"),
("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"),
("/remove/extraneous/./symbols", "/remove/extraneous/symbols"),
// Paths containing '..'
("./remove/me/..", "./remove"),
("./remove/me/../", "./remove"),
("../remove/me/..", "../remove"),
("../remove/me/../", "../remove"),
("/remove/me/..", "/remove"),
("/remove/me/../", "/remove"),
("./..", "../"),
("./../", "../"),
("../..", "../../"),
("../../", "../../"),
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
"wrong normalization for {input}"
);
}
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ impl MemoryState {
}
// Does nothing apart from calling `f()`, we don't need to bypass any limits
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline]
pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) {
f();
+17
View File
@@ -204,6 +204,23 @@ impl IntoLuaMulti for MultiValue {
}
}
impl IntoLuaMulti for &MultiValue {
#[inline]
fn into_lua_multi(self, _: &Lua) -> Result<MultiValue> {
Ok(self.clone())
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
let nresults = self.len() as i32;
check_stack(lua.state(), nresults + 1)?;
for value in &self.0 {
lua.push_value(value)?;
}
Ok(nresults)
}
}
impl FromLuaMulti for MultiValue {
#[inline]
fn from_lua_multi(values: MultiValue, _: &Lua) -> Result<Self> {
+2 -1
View File
@@ -36,5 +36,6 @@ pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
#[cfg(feature = "serde")]
#[doc(no_inline)]
pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions,
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializableValue as LuaSerializableValue,
SerializeOptions as LuaSerializeOptions,
};
+41 -9
View File
@@ -15,11 +15,12 @@ use crate::userdata::AnyUserData;
use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct Deserializer {
value: Value,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
len: Option<usize>, // A length hint for sequences
}
/// A struct with options to change default deserializer behavior.
@@ -54,6 +55,19 @@ pub struct Options {
///
/// Default: **false**
pub encode_empty_tables_as_array: bool,
/// If true, enable detection of mixed tables.
///
/// A mixed table is a table that has both array-like and map-like entries or several borders.
/// See [`The Length Operator`] documentation for details about borders.
///
/// When this option is disabled, a table with a non-zero length (with one or more borders) will
/// be always encoded as an array.
///
/// Default: **false**
///
/// [`The Length Operator`]: https://www.lua.org/manual/5.4/manual.html#3.4.7
pub detect_mixed_tables: bool,
}
impl Default for Options {
@@ -70,6 +84,7 @@ impl Options {
deny_recursive_tables: true,
sort_keys: false,
encode_empty_tables_as_array: false,
detect_mixed_tables: false,
}
}
@@ -108,6 +123,15 @@ impl Options {
self.encode_empty_tables_as_array = enabled;
self
}
/// Sets [`detect_mixed_tables`] option.
///
/// [`detect_mixed_tables`]: #structfield.detect_mixed_tables
#[must_use]
pub const fn detect_mixed_tables(mut self, enable: bool) -> Self {
self.detect_mixed_tables = enable;
self
}
}
impl Deserializer {
@@ -121,7 +145,7 @@ impl Deserializer {
Deserializer {
value,
options,
visited: Rc::new(RefCell::new(FxHashSet::default())),
..Default::default()
}
}
@@ -130,8 +154,14 @@ impl Deserializer {
value,
options,
visited,
..Default::default()
}
}
fn with_len(mut self, len: usize) -> Self {
self.len = Some(len);
self
}
}
impl<'de> serde::Deserializer<'de> for Deserializer {
@@ -155,11 +185,13 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Ok(s) => visitor.visit_str(&s),
Err(_) => visitor.visit_bytes(&s.as_bytes()),
},
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => {
self.deserialize_seq(visitor)
Value::Table(ref t) => {
if let Some(len) = t.encode_as_array(self.options) {
self.with_len(len).deserialize_seq(visitor)
} else {
self.deserialize_map(visitor)
}
}
Value::Table(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_any(visitor))
@@ -270,14 +302,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Value::Table(t) => {
let _guard = RecursionGuard::new(&t, &self.visited);
let len = t.raw_len();
let len = self.len.unwrap_or_else(|| t.raw_len());
let mut deserializer = SeqDeserializer {
seq: t.sequence_values(),
seq: t.sequence_values().with_len(len),
options: self.options,
visited: self.visited,
};
let seq = visitor.visit_seq(&mut deserializer)?;
if deserializer.seq.count() == 0 {
if deserializer.seq.next().is_none() {
Ok(seq)
} else {
Err(de::Error::invalid_length(len, &"fewer elements in the table"))
+279 -77
View File
@@ -37,6 +37,7 @@ use crate::{buffer::Buffer, chunk::Compiler};
use {
crate::types::LightUserData,
std::future::{self, Future},
std::task::Poll,
};
#[cfg(feature = "serde")]
@@ -72,8 +73,8 @@ pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode {
Incremental,
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
Generational,
}
@@ -248,7 +249,7 @@ impl Lua {
ffi::luaL_loadstring as _,
ffi::luaL_openlibs as _,
]);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
{
_symbols.push(ffi::lua_getglobal as _);
_symbols.push(ffi::lua_setglobal as _);
@@ -336,6 +337,39 @@ impl Lua {
R::from_stack_multi(nresults, &lua)
}
/// Runs callback with the inner RawLua value. It can be used to manually push and get values on
/// the stack.
///
/// This function is safe because all unsafe actions with RawLua can only be done with unsafe
///
/// # Example
/// ```
/// # use mlua::{Lua, Result, FromLua, IntoLua};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let n: i32 = {
/// let num = 11i32;
/// lua.exec_raw_lua(|lua| {
/// unsafe {
/// <i32 as IntoLua>::push_into_stack(num, lua)?;
/// }
///
/// let n = unsafe {
/// <i32 as FromLua>::from_stack(-1, lua)?
/// };
/// Result::Ok(n)
/// })
/// }?;
/// assert_eq!(n, 11);
/// # Ok(())
/// # }
/// ```
#[doc(hidden)]
pub fn exec_raw_lua<R>(&self, f: impl FnOnce(&RawLua) -> R) -> R {
let lua = self.lock();
f(&lua)
}
/// Loads the specified subset of the standard libraries into an existing Lua state.
///
/// Use the [`StdLib`] flags to specify the libraries you want to load.
@@ -381,7 +415,7 @@ impl Lua {
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
let preload = unsafe {
self.exec_raw::<Option<Table>>((), |state| {
ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE);
@@ -631,13 +665,13 @@ impl Lua {
///
/// Any Luau code is guaranteed to call this handler "eventually"
/// (in practice this can happen at any function call or at any loop iteration).
/// This is similar to `Lua::set_hook` but in more simplified form.
///
/// The provided interrupt function can error, and this error will be propagated through
/// the Luau code that was executing at the time the interrupt was triggered.
/// Also this can be used to implement continuous execution limits by instructing Luau VM to
/// yield by returning [`VmState::Yield`].
///
/// This is similar to `Lua::set_hook` but in more simplified form.
/// yield by returning [`VmState::Yield`]. The yield will happen only at yieldable points
/// of execution (not across metamethod/C-call boundaries).
///
/// # Example
///
@@ -695,7 +729,10 @@ impl Lua {
match result {
VmState::Continue => {}
VmState::Yield => {
ffi::lua_yield(state, 0);
// We can yield only at yieldable points, otherwise ignore and continue
if ffi::lua_isyieldable(state) != 0 {
ffi::lua_yield(state, 0);
}
}
}
}
@@ -810,8 +847,8 @@ impl Lua {
}
/// Sets the warning function to be used by Lua to emit warnings.
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn set_warning_function<F>(&self, callback: F)
where
F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static,
@@ -843,8 +880,8 @@ impl Lua {
/// Removes warning function previously set by `set_warning_function`.
///
/// This function has no effect if a warning function was not previously set.
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn remove_warning_function(&self) {
let lua = self.lock();
unsafe {
@@ -857,8 +894,8 @@ impl Lua {
///
/// A message in a call with `incomplete` set to `true` should be continued in
/// another call to this function.
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn warning(&self, msg: impl AsRef<str>, incomplete: bool) {
let msg = msg.as_ref();
let mut bytes = vec![0; msg.len() + 1];
@@ -871,7 +908,7 @@ impl Lua {
}
}
/// Gets information about the interpreter runtime stack at a given level.
/// Gets information about the interpreter runtime stack at the given level.
///
/// This function calls callback `f`, passing the [`Debug`] structure that can be used to get
/// information about the function executing at a given level.
@@ -895,6 +932,26 @@ impl Lua {
}
}
/// Creates a traceback of the call stack at the given level.
///
/// The `msg` parameter, if provided, is added at the beginning of the traceback.
/// The `level` parameter works the same way as in [`Lua::inspect_stack`].
pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result<String> {
let lua = self.lock();
unsafe {
check_stack(lua.state(), 3)?;
protect_lua!(lua.state(), 0, 1, |state| {
let msg = match msg {
Some(s) => ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()),
None => ptr::null(),
};
// `protect_lua` adds it's own call frame, so we need to increase level by 1
ffi::luaL_traceback(state, state, msg, (level + 1) as c_int);
})?;
Ok(String(lua.pop_ref()))
}
}
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
pub fn used_memory(&self) -> usize {
let lua = self.lock();
@@ -930,7 +987,13 @@ impl Lua {
}
/// Returns `true` if the garbage collector is currently running automatically.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
pub fn gc_is_running(&self) -> bool {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
@@ -995,8 +1058,12 @@ impl Lua {
let lua = self.lock();
let state = lua.main_state();
unsafe {
#[cfg(not(feature = "luau"))]
#[cfg(feature = "lua55")]
return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
#[cfg(not(any(feature = "lua55", feature = "luau")))]
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")]
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
@@ -1010,7 +1077,18 @@ impl Lua {
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier) }
unsafe {
#[cfg(feature = "lua55")]
return ffi::lua_gc(
lua.main_state(),
ffi::LUA_GCPARAM,
ffi::LUA_GCPSTEPMUL,
step_multiplier,
);
#[cfg(not(feature = "lua55"))]
return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
}
/// Changes the collector to incremental mode with the given parameters.
@@ -1049,12 +1127,19 @@ impl Lua {
#[cfg(not(feature = "luau"))]
let _ = step_size; // Ignored
GCMode::Incremental
return GCMode::Incremental;
}
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, step_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, step_size);
ffi::lua_gc(state, ffi::LUA_GCINC)
};
#[cfg(feature = "lua54")]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) };
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
match prev_mode {
ffi::LUA_GCINC => GCMode::Incremental,
ffi::LUA_GCGEN => GCMode::Generational,
@@ -1068,11 +1153,19 @@ impl Lua {
/// can be found in the Lua 5.4 [documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state();
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, minor_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, major_multiplier);
// TODO: LUA_GCPMAJORMINOR
ffi::lua_gc(state, ffi::LUA_GCGEN)
};
#[cfg(not(feature = "lua55"))]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) };
match prev_mode {
ffi::LUA_GCGEN => GCMode::Generational,
@@ -1150,36 +1243,52 @@ impl Lua {
}
}
/// Create and return an interned Lua string.
/// Creates and returns an interned Lua string.
///
/// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str`
/// and `&String`, you can also pass plain `&[u8]` here.
#[inline]
pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result<String> {
unsafe { self.lock().create_string(s) }
unsafe { self.lock().create_string(s.as_ref()) }
}
/// Create and return a Luau [buffer] object from a byte slice of data.
/// Creates and returns an external Lua string.
///
/// External string is a string where the memory is managed by Rust code, and Lua only holds a
/// reference to it. This can be used to avoid copying large strings into Lua memory.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
#[inline]
pub fn create_external_string(&self, s: impl Into<Vec<u8>>) -> Result<String> {
unsafe { self.lock().create_external_string(s.into()) }
}
/// Creates and returns a Luau [buffer] object from a byte slice of data.
///
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<Buffer> {
pub fn create_buffer(&self, data: impl AsRef<[u8]>) -> Result<Buffer> {
let lua = self.lock();
let state = lua.state();
let data = data.as_ref();
unsafe {
if lua.unlikely_memory_error() {
crate::util::push_buffer(state, buf.as_ref(), false)?;
return Ok(Buffer(lua.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
crate::util::push_buffer(state, buf.as_ref(), true)?;
Ok(Buffer(lua.pop_ref()))
let (ptr, buffer) = lua.create_buffer_with_capacity(data.len())?;
ptr.copy_from_nonoverlapping(data.as_ptr(), data.len());
Ok(buffer)
}
}
/// Creates and returns a Luau [buffer] object with the specified size.
///
/// Size limit is 1GB. All bytes will be initialized to zero.
///
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_buffer_with_capacity(&self, size: usize) -> Result<Buffer> {
unsafe { Ok(self.lock().create_buffer_with_capacity(size)?.1) }
}
/// Creates and returns a new empty table.
#[inline]
pub fn create_table(&self) -> Result<Table> {
@@ -1288,7 +1397,12 @@ impl Lua {
/// This function is unsafe because provides a way to execute unsafe C function.
pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result<Function> {
let lua = self.lock();
if cfg!(any(feature = "lua54", feature = "lua53", feature = "lua52")) {
if cfg!(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52"
)) {
ffi::lua_pushcfunction(lua.ref_thread(), func);
return Ok(Function(lua.pop_ref_thread()));
}
@@ -1485,7 +1599,27 @@ impl Lua {
unsafe { self.lock().make_userdata(UserDataStorage::new(ud)) }
}
/// Sets the metatable for a Lua builtin type.
/// Gets the metatable of a Lua built-in (primitive) type.
///
/// The metatable is shared by all values of the given type.
///
/// See [`Lua::set_type_metatable`] for examples.
#[allow(private_bounds)]
pub fn type_metatable<T: LuaType>(&self) -> Option<Table> {
let lua = self.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
if lua.push_primitive_type::<T>() && ffi::lua_getmetatable(state, -1) != 0 {
return Some(Table(lua.pop_ref()));
}
}
None
}
/// Sets the metatable for a Lua built-in (primitive) type.
///
/// The metatable will be shared by all values of the given type.
///
@@ -1512,44 +1646,13 @@ impl Lua {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
match T::TYPE_ID {
ffi::LUA_TBOOLEAN => {
ffi::lua_pushboolean(state, 0);
if lua.push_primitive_type::<T>() {
match metatable {
Some(metatable) => lua.push_ref(&metatable.0),
None => ffi::lua_pushnil(state),
}
ffi::LUA_TLIGHTUSERDATA => {
ffi::lua_pushlightuserdata(state, ptr::null_mut());
}
ffi::LUA_TNUMBER => {
ffi::lua_pushnumber(state, 0.);
}
#[cfg(feature = "luau")]
ffi::LUA_TVECTOR => {
#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, 0., 0., 0.);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, 0., 0., 0., 0.);
}
ffi::LUA_TSTRING => {
ffi::lua_pushstring(state, b"\0" as *const u8 as *const _);
}
ffi::LUA_TFUNCTION => match self.load("function() end").eval::<Function>() {
Ok(func) => lua.push_ref(&func.0),
Err(_) => return,
},
ffi::LUA_TTHREAD => {
ffi::lua_pushthread(state);
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
ffi::lua_newbuffer(state, 0);
}
_ => return,
ffi::lua_setmetatable(state, -2);
}
match metatable {
Some(metatable) => lua.push_ref(&metatable.0),
None => ffi::lua_pushnil(state),
}
ffi::lua_setmetatable(state, -2);
}
}
@@ -1560,7 +1663,7 @@ impl Lua {
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 1);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX);
@@ -1592,7 +1695,7 @@ impl Lua {
lua.push_ref(&globals.0);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
@@ -2071,6 +2174,105 @@ impl Lua {
LightUserData(&ASYNC_POLL_TERMINATE as *const u8 as *mut std::os::raw::c_void)
}
#[cfg(feature = "async")]
#[inline(always)]
pub(crate) fn poll_yield() -> LightUserData {
static ASYNC_POLL_YIELD: u8 = 0;
LightUserData(&ASYNC_POLL_YIELD as *const u8 as *mut std::os::raw::c_void)
}
/// Suspends the current async function, returning the provided arguments to caller.
///
/// This function is similar to [`coroutine.yield`] but allow yielding Rust functions
/// and passing values to the caller.
/// Please note that you cannot cross [`Thread`] boundaries (e.g. calling `yield_with` on one
/// thread and resuming on another).
///
/// # Examples
///
/// Async iterator:
///
/// ```
/// # use mlua::{Lua, Result};
/// #
/// async fn generator(lua: Lua, _: ()) -> Result<()> {
/// for i in 0..10 {
/// lua.yield_with::<()>(i).await?;
/// }
/// Ok(())
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("generator", lua.create_async_function(generator)?)?;
///
/// lua.load(r#"
/// local n = 0
/// for i in coroutine.wrap(generator) do
/// n = n + i
/// end
/// assert(n == 45)
/// "#)
/// .exec()
/// }
/// ```
///
/// Exchange values on yield:
///
/// ```
/// # use mlua::{Lua, Result, Value};
/// #
/// async fn pingpong(lua: Lua, mut val: i32) -> Result<()> {
/// loop {
/// val = lua.yield_with::<i32>(val).await? + 1;
/// }
/// Ok(())
/// }
///
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
///
/// let co = lua.create_thread(lua.create_async_function(pingpong)?)?;
/// assert_eq!(co.resume::<i32>(1)?, 1);
/// assert_eq!(co.resume::<i32>(2)?, 3);
/// assert_eq!(co.resume::<i32>(3)?, 4);
///
/// # Ok(())
/// # }
/// ```
///
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn yield_with<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
let mut args = Some(args.into_lua_multi(self)?);
future::poll_fn(move |_cx| match args.take() {
Some(args) => unsafe {
let lua = self.lock();
lua.push(Self::poll_yield())?; // yield marker
if args.len() <= 1 {
lua.push(args.front())?;
} else {
lua.push(lua.create_sequence_from(&args)?)?;
}
lua.push(args.len())?;
Poll::Pending
},
None => unsafe {
let lua = self.lock();
let state = lua.state();
let top = ffi::lua_gettop(state);
if top == 0 || ffi::lua_type(state, 1) != ffi::LUA_TUSERDATA {
// This must be impossible scenario if used correctly
return Poll::Ready(R::from_stack_multi(0, &lua));
}
let _sg = StackGuard::with_top(state, 1);
Poll::Ready(R::from_stack_multi(top - 1, &lua))
},
})
.await
}
/// Returns a weak reference to the Lua instance.
///
/// This is useful for creating a reference to the Lua instance that does not prevent it from
@@ -2093,7 +2295,7 @@ impl Lua {
})?,
)?;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
let searchers: Table = package.get("searchers")?;
#[cfg(any(feature = "lua51", feature = "luajit"))]
let searchers: Table = package.get("loaders")?;
+7 -3
View File
@@ -64,7 +64,7 @@ pub(crate) struct ExtraData {
pub(super) wrapped_failure_top: usize,
// Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")]
pub(super) thread_pool: Vec<c_int>,
pub(super) thread_pool: Vec<crate::types::ValueRefIndex>,
// Address of `WrappedFailure` metatable
pub(super) wrapped_failure_mt_ptr: *const c_void,
@@ -77,7 +77,7 @@ pub(crate) struct ExtraData {
pub(super) hook_callback: Option<crate::types::HookCallback>,
#[cfg(not(feature = "luau"))]
pub(super) hook_triggers: crate::debug::HookTriggers,
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
@@ -94,6 +94,8 @@ pub(crate) struct ExtraData {
pub(super) compiler: Option<Compiler>,
#[cfg(feature = "luau-jit")]
pub(super) enable_jit: bool,
#[cfg(feature = "luau")]
pub(crate) mem_categories: Vec<std::ffi::CString>,
}
impl Drop for ExtraData {
@@ -180,7 +182,7 @@ impl ExtraData {
hook_callback: None,
#[cfg(not(feature = "luau"))]
hook_triggers: Default::default(),
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
warn_callback: None,
#[cfg(feature = "luau")]
interrupt_callback: None,
@@ -196,6 +198,8 @@ impl ExtraData {
enable_jit: true,
#[cfg(feature = "luau")]
running_gc: false,
#[cfg(feature = "luau")]
mem_categories: vec![std::ffi::CString::new("main").unwrap()],
}));
// Store it in the registry
+170 -56
View File
@@ -19,7 +19,7 @@ use crate::thread::Thread;
use crate::traits::IntoLua;
use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
};
use crate::userdata::{
init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry,
@@ -28,8 +28,8 @@ use crate::userdata::{
use crate::util::{
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error,
push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name,
StackGuard, WrappedFailure,
push_internal_userdata, push_string, push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall,
short_type_name, StackGuard, WrappedFailure,
};
use crate::value::{Nil, Value};
@@ -122,6 +122,12 @@ impl RawLua {
pub(super) unsafe fn new(libs: StdLib, options: &LuaOptions) -> XRc<ReentrantMutex<Self>> {
let mem_state: *mut MemoryState = Box::into_raw(Box::default());
#[cfg(feature = "lua55")]
let mut state = {
let seed = ffi::luaL_makeseed(ptr::null_mut());
ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void, seed)
};
#[cfg(not(feature = "lua55"))]
let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void);
// If state is null then switch to Lua internal allocator
if state.is_null() {
@@ -153,7 +159,7 @@ impl RawLua {
(|| -> Result<()> {
let _sg = StackGuard::new(state);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX);
@@ -416,7 +422,7 @@ impl RawLua {
VmState::Yield => {
// Only count and line events can yield
if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE {
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
if ffi::lua_isyieldable(state) != 0 {
ffi::lua_yield(state, 0);
}
@@ -510,19 +516,50 @@ impl RawLua {
}
/// See [`Lua::create_string`]
pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result<String> {
pub(crate) unsafe fn create_string(&self, s: &[u8]) -> Result<String> {
let state = self.state();
if self.unlikely_memory_error() {
push_string(state, s.as_ref(), false)?;
push_string(state, s, false)?;
return Ok(String(self.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
push_string(state, s.as_ref(), true)?;
push_string(state, s, true)?;
Ok(String(self.pop_ref()))
}
/// Creates an external string, that is, a string that uses memory not managed by Lua.
///
/// Modifies the input data to add `\0` terminator.
#[cfg(feature = "lua55")]
pub(crate) unsafe fn create_external_string(&self, bytes: Vec<u8>) -> Result<String> {
let state = self.state();
if self.unlikely_memory_error() {
crate::util::push_external_string(state, bytes, false)?;
return Ok(String(self.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
crate::util::push_external_string(state, bytes, true)?;
Ok(String(self.pop_ref()))
}
#[cfg(feature = "luau")]
pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> {
let state = self.state();
if self.unlikely_memory_error() {
let ptr = crate::util::push_buffer(state, size, false)?;
return Ok((ptr, crate::Buffer(self.pop_ref())));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
let ptr = crate::util::push_buffer(state, size, true)?;
Ok((ptr, crate::Buffer(self.pop_ref())))
}
/// See [`Lua::create_table_with_capacity`]
pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result<Table> {
let state = self.state();
@@ -624,7 +661,7 @@ impl RawLua {
#[cfg(feature = "async")]
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
#[cfg(feature = "luau")]
@@ -645,23 +682,64 @@ impl RawLua {
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
let extra = &mut *self.extra.get();
if extra.thread_pool.len() < extra.thread_pool.capacity() {
extra.thread_pool.push(thread.0.index);
thread.0.drop = false; // Prevent thread from being garbage collected
if let Some(index) = thread.0.index_count.take() {
extra.thread_pool.push(index);
}
}
}
/// Pushes a primitive type value onto the Lua stack.
pub(crate) unsafe fn push_primitive_type<T: LuaType>(&self) -> bool {
match T::TYPE_ID {
ffi::LUA_TBOOLEAN => {
ffi::lua_pushboolean(self.state(), 0);
}
ffi::LUA_TLIGHTUSERDATA => {
ffi::lua_pushlightuserdata(self.state(), ptr::null_mut());
}
ffi::LUA_TNUMBER => {
ffi::lua_pushnumber(self.state(), 0.);
}
#[cfg(feature = "luau")]
ffi::LUA_TVECTOR => {
#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(self.state(), 0., 0., 0.);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(self.state(), 0., 0., 0., 0.);
}
ffi::LUA_TSTRING => {
ffi::lua_pushstring(self.state(), b"\0" as *const u8 as *const _);
}
ffi::LUA_TFUNCTION => {
unsafe extern "C-unwind" fn func(_state: *mut ffi::lua_State) -> c_int {
0
}
ffi::lua_pushcfunction(self.state(), func);
}
ffi::LUA_TTHREAD => {
ffi::lua_pushthread(self.state());
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
ffi::lua_newbuffer(self.state(), 0);
}
_ => return false,
}
true
}
/// Pushes a value that implements `IntoLua` onto the Lua stack.
///
/// Uses up to 2 stack spaces to push a single value, does not call `checkstack`.
#[inline(always)]
pub(crate) unsafe fn push(&self, value: impl IntoLua) -> Result<()> {
pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> {
value.push_into_stack(self)
}
/// Pushes a `Value` (by reference) onto the Lua stack.
///
/// Uses 2 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> {
pub unsafe fn push_value(&self, value: &Value) -> Result<()> {
let state = self.state();
match value {
Value::Nil => ffi::lua_pushnil(state),
@@ -695,7 +773,8 @@ impl RawLua {
/// Pops a value from the Lua stack.
///
/// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn pop_value(&self) -> Value {
#[inline]
pub unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None);
ffi::lua_pop(self.state(), 1);
value
@@ -713,7 +792,7 @@ impl RawLua {
ffi::LUA_TLIGHTUSERDATA => Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))),
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
ffi::LUA_TNUMBER => {
if ffi::lua_isinteger(state, idx) != 0 {
Value::Integer(ffi::lua_tointeger(state, idx))
@@ -827,13 +906,6 @@ impl RawLua {
ValueRef::new(self, index)
}
#[inline]
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
ffi::lua_pushvalue(self.ref_thread(), vref.index);
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) {
let ref_thread = self.ref_thread();
mlua_debug_assert!(
@@ -851,7 +923,7 @@ impl RawLua {
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX);
// Lua 5.2+ support light C functions that does not require extra allocations
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushcfunction(state, crate::util::error_traceback);
}
@@ -920,7 +992,7 @@ impl RawLua {
// We generate metatable first to make sure it *always* available when userdata pushed
let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error();
crate::util::push_userdata(state, data, protect)?;
push_userdata(state, data, protect)?;
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
ffi::lua_setmetatable(state, -2);
@@ -1048,6 +1120,18 @@ impl RawLua {
field_setters_index = Some(ffi::lua_absindex(state, -1));
}
// Create methods namecall table
#[cfg_attr(not(feature = "luau"), allow(unused_mut))]
let mut methods_map = None;
#[cfg(feature = "luau")]
if registry.enable_namecall {
let map: &mut rustc_hash::FxHashMap<_, crate::types::CallbackPtr> =
methods_map.get_or_insert_with(Default::default);
for (k, m) in &registry.methods {
map.insert(k.as_bytes().to_vec(), &**m);
}
}
let mut methods_index = None;
let methods_nrec = registry.methods.len();
#[cfg(feature = "async")]
@@ -1095,6 +1179,7 @@ impl RawLua {
field_getters_index,
field_setters_index,
methods_index,
methods_map,
)?;
// Update stack guard to keep metatable after return
@@ -1134,7 +1219,10 @@ impl RawLua {
Ok(type_id) => Ok(type_id),
Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => {
// Report `FromLuaConversionError` instead
let idx_type_name = CStr::from_ptr(ffi::luaL_typename(state, idx));
// In Luau `luaL_typename` return heap-allocated string that is valid only for
// the `state` lifetime.
// `lua_typename` is used instead to get a truly static string.
let idx_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx)));
let idx_type_name = idx_type_name.to_str().unwrap();
let message = format!("expected userdata of type '{}'", short_type_name::<T>());
Err(Error::from_lua_conversion(idx_type_name, "userdata", message))
@@ -1218,7 +1306,13 @@ impl RawLua {
#[cfg(feature = "async")]
pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result<Function> {
// Ensure that the coroutine library is loaded
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
unsafe {
if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) {
load_std_libs(self.main_state(), StdLib::COROUTINE)?;
@@ -1226,7 +1320,7 @@ impl RawLua {
}
}
unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn get_future_callback(state: *mut ffi::lua_State) -> c_int {
// Async functions cannot be scoped and therefore destroyed,
// so the first upvalue is always valid
let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1));
@@ -1240,36 +1334,37 @@ impl RawLua {
let extra = XRc::clone(&(*upvalue).extra);
let protect = !rawlua.unlikely_memory_error();
push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?;
if protect {
protect_lua!(state, 1, 1, fn(state) {
ffi::lua_pushcclosure(state, poll_future, 1);
})?;
} else {
ffi::lua_pushcclosure(state, poll_future, 1);
}
Ok(1)
})
}
unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int {
let upvalue = get_userdata::<AsyncPollUpvalue>(state, ffi::lua_upvalueindex(1));
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
// Future is always passed in the first argument
let future = get_userdata::<AsyncPollUpvalue>(state, 1);
callback_error_ext(state, (*future).extra.get(), true, |extra, nargs| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the future is polled
let rawlua = (*extra).raw_lua();
if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
if nargs == 2 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
// Destroy the future and terminate the Lua thread
(*upvalue).data.take();
(*future).data.take();
ffi::lua_pushinteger(state, -1);
return Ok(1);
}
let fut = &mut (*upvalue).data;
let fut = &mut (*future).data;
let mut ctx = Context::from_waker(rawlua.waker());
match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) {
Some(Poll::Pending) => {
let fut_nvals = ffi::lua_gettop(state) - 1; // Exclude the future itself
if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 {
// We have some values to yield
ffi::lua_pushnil(state);
ffi::lua_replace(state, -4);
return Ok(3);
}
ffi::lua_pushnil(state);
ffi::lua_pushlightuserdata(state, Lua::poll_pending().0);
Ok(2)
@@ -1298,7 +1393,7 @@ impl RawLua {
}
let state = self.state();
let get_poll = unsafe {
let get_future = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
@@ -1308,10 +1403,10 @@ impl RawLua {
push_internal_userdata(state, upvalue, protect)?;
if protect {
protect_lua!(state, 1, 1, fn(state) {
ffi::lua_pushcclosure(state, call_callback, 1);
ffi::lua_pushcclosure(state, get_future_callback, 1);
})?;
} else {
ffi::lua_pushcclosure(state, call_callback, 1);
ffi::lua_pushcclosure(state, get_future_callback, 1);
}
Function(self.pop_ref())
@@ -1330,16 +1425,19 @@ impl RawLua {
let coroutine = lua.globals().get::<Table>("coroutine")?;
// Prepare environment for the async poller
let env = lua.create_table_with_capacity(0, 3)?;
env.set("get_poll", get_poll)?;
let env = lua.create_table_with_capacity(0, 4)?;
env.set("get_future", get_future)?;
env.set("poll", unsafe { lua.create_c_function(poll_future)? })?;
env.set("yield", coroutine.get::<Function>("yield")?)?;
env.set("unpack", unsafe { lua.create_c_function(unpack)? })?;
lua.load(
r#"
local poll = get_poll(...)
local nres, res, res2 = poll()
local poll, yield = poll, yield
local future = get_future(...)
local nres, res, res2 = poll(future)
while true do
-- Poll::Ready branch, `nres` is the number of results
if nres ~= nil then
if nres == 0 then
return
@@ -1355,10 +1453,20 @@ impl RawLua {
return unpack(res, nres)
end
end
-- `res` is a "pending" value
-- `yield` can return a signal to drop the future that we should propagate
-- to the poller
nres, res, res2 = poll(yield(res))
-- Poll::Pending branch
if res2 == nil then
-- `res` is a "pending" value
-- `yield` can return a signal to drop the future that we should propagate
-- to the poller
nres, res, res2 = poll(future, yield(res))
elseif res2 == 0 then
nres, res, res2 = poll(future, yield())
elseif res2 == 1 then
nres, res, res2 = poll(future, yield(res))
else
nres, res, res2 = poll(future, yield(unpack(res, res2)))
end
end
"#,
)
@@ -1370,14 +1478,14 @@ impl RawLua {
#[cfg(feature = "async")]
#[inline]
pub(crate) unsafe fn waker(&self) -> &Waker {
(*self.extra.get()).waker.as_ref()
pub(crate) fn waker(&self) -> &Waker {
unsafe { (*self.extra.get()).waker.as_ref() }
}
#[cfg(feature = "async")]
#[inline]
pub(crate) unsafe fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
mem::replace(&mut (*self.extra.get()).waker, waker)
pub(crate) fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
unsafe { mem::replace(&mut (*self.extra.get()).waker, waker) }
}
}
@@ -1417,7 +1525,13 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
#[cfg(feature = "luajit")]
let _gc_guard = GcGuard::new(state);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
{
if libs.contains(StdLib::COROUTINE) {
requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?;
@@ -1441,7 +1555,7 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?;
}
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
{
if libs.contains(StdLib::UTF8) {
requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?;
+19 -4
View File
@@ -6,10 +6,22 @@ pub struct StdLib(u32);
impl StdLib {
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
)))
)]
pub const COROUTINE: StdLib = StdLib(1);
@@ -28,8 +40,11 @@ impl StdLib {
pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau")))
)]
pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
+118 -37
View File
@@ -1,14 +1,14 @@
use std::collections::HashSet;
use std::fmt;
use std::marker::PhantomData;
use std::os::raw::{c_int, c_void};
use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{LuaGuard, RawLua};
use crate::state::{LuaGuard, RawLua, WeakLua};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::types::{Integer, LuaType, ValueRef};
use crate::types::{Integer, ValueRef};
use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
use crate::value::{Nil, Value};
@@ -416,14 +416,7 @@ impl Table {
lua.push_ref(&self.0);
// Clear array part
for i in 1..=ffi::lua_rawlen(state, -1) {
ffi::lua_pushnil(state);
ffi::lua_rawseti(state, -2, i as Integer);
}
// Clear hash part
// It must be safe as long as we don't use invalid keys
// This is safe as long as we don't assign new keys
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
ffi::lua_pop(state, 1); // pop value
@@ -675,16 +668,25 @@ impl Table {
guard: self.0.lua.lock(),
table: self,
index: 1,
len: None,
_phantom: PhantomData,
}
}
/// Iterates over the sequence part of the table, invoking the given closure on each value.
///
/// This methods is similar to [`Table::sequence_values`], but optimized for performance.
#[doc(hidden)]
pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
where
V: FromLua,
{
pub fn for_each_value<V: FromLua>(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> {
self.for_each_value_by_len(None, f)
}
fn for_each_value_by_len<V: FromLua>(
&self,
len: impl Into<Option<usize>>,
mut f: impl FnMut(V) -> Result<()>,
) -> Result<()> {
let len = len.into();
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -692,9 +694,14 @@ impl Table {
check_stack(state, 4)?;
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(state, -1);
for i in 1..=len {
ffi::lua_rawgeti(state, -1, i as _);
for i in 1.. {
if len.map(|len| i > len).unwrap_or(false) {
break;
}
let t = ffi::lua_rawgeti(state, -1, i as _);
if len.is_none() && t == ffi::LUA_TNIL {
break;
}
f(V::from_stack(-1, &lua)?)?;
ffi::lua_pop(state, 1);
}
@@ -727,8 +734,9 @@ impl Table {
Ok(())
}
/// Checks if the table has the array metatable attached.
#[cfg(feature = "serde")]
pub(crate) fn is_array(&self) -> bool {
fn has_array_metatable(&self) -> bool {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -744,6 +752,70 @@ impl Table {
}
}
/// If the table is an array, returns the number of non-nil elements and max index.
///
/// Returns `None` if the table is not an array.
///
/// This operation has O(n) complexity.
#[cfg(feature = "serde")]
fn find_array_len(&self) -> Option<(usize, usize)> {
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
unsafe {
let _sg = StackGuard::new(ref_thread);
let (mut count, mut max_index) = (0, 0);
ffi::lua_pushnil(ref_thread);
while ffi::lua_next(ref_thread, self.0.index) != 0 {
if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER {
return None;
}
let k = ffi::lua_tonumber(ref_thread, -2);
if k.trunc() != k || k < 1.0 {
return None;
}
max_index = std::cmp::max(max_index, k as usize);
count += 1;
ffi::lua_pop(ref_thread, 1);
}
Some((count, max_index))
}
}
/// Determines if the table should be encoded as an array or a map.
///
/// The algorithm is the following:
/// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they
/// all are positive integers. If non-array key is found, return `None` (encode as map).
/// Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps.
///
/// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the
/// array metatable. If so, encode as array. If the table is empty and
/// `encode_empty_tables_as_array` is enabled, encode as array.
///
/// Returns the length of the array if it should be encoded as an array.
#[cfg(feature = "serde")]
pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option<usize> {
if options.detect_mixed_tables {
if let Some((len, max_idx)) = self.find_array_len() {
// If the array is too sparse, serialize it as a map instead
if len < 10 || len * 2 >= max_idx {
return Some(max_idx);
}
}
} else {
let len = self.raw_len();
if len > 0 || self.has_array_metatable() {
return Some(len);
}
if options.encode_empty_tables_as_array && self.is_empty() {
return Some(0);
}
}
None
}
#[cfg(feature = "luau")]
#[inline(always)]
fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
@@ -863,10 +935,6 @@ where
}
}
impl LuaType for Table {
const TYPE_ID: c_int = ffi::LUA_TTABLE;
}
impl ObjectLike for Table {
#[inline]
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
@@ -884,7 +952,7 @@ impl ObjectLike for Table {
R: FromLuaMulti,
{
// Convert table to a function and call via pcall that respects the `__call` metamethod.
Function(self.0.copy()).call(args)
Function(self.0.clone()).call(args)
}
#[cfg(feature = "async")]
@@ -893,7 +961,7 @@ impl ObjectLike for Table {
where
R: FromLuaMulti,
{
Function(self.0.copy()).call_async(args)
Function(self.0.clone()).call_async(args)
}
#[inline]
@@ -941,7 +1009,17 @@ impl ObjectLike for Table {
#[inline]
fn to_string(&self) -> Result<StdString> {
Value::Table(Table(self.0.copy())).to_string()
Value::Table(Table(self.0.clone())).to_string()
}
#[inline]
fn to_value(&self) -> Value {
Value::Table(self.clone())
}
#[inline]
fn weak_lua(&self) -> &WeakLua {
&self.0.lua
}
}
@@ -977,6 +1055,15 @@ impl<'a> SerializableTable<'a> {
}
}
impl<V> TableSequence<'_, V> {
/// Sets the length (hint) of the sequence.
#[cfg(feature = "serde")]
pub(crate) fn with_len(mut self, len: usize) -> Self {
self.len = Some(len);
self
}
}
#[cfg(feature = "serde")]
impl Serialize for SerializableTable<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
@@ -998,14 +1085,10 @@ impl Serialize for SerializableTable<'_> {
let _guard = RecursionGuard::new(self.table, visited);
// Array
let len = self.table.raw_len();
if len > 0
|| self.table.is_array()
|| (self.options.encode_empty_tables_as_array && self.table.is_empty())
{
if let Some(len) = self.table.encode_as_array(self.options) {
let mut seq = serializer.serialize_seq(Some(len))?;
let mut serialize_err = None;
let res = self.table.for_each_value::<Value>(|value| {
let res = self.table.for_each_value_by_len::<Value>(len, |value| {
let skip = check_value_for_skip(&value, self.options, visited)
.map_err(|err| Error::SerializeError(err.to_string()))?;
if skip {
@@ -1129,13 +1212,11 @@ pub struct TableSequence<'a, V> {
guard: LuaGuard,
table: &'a Table,
index: Integer,
len: Option<usize>,
_phantom: PhantomData<V>,
}
impl<V> Iterator for TableSequence<'_, V>
where
V: FromLua,
{
impl<V: FromLua> Iterator for TableSequence<'_, V> {
type Item = Result<V>;
fn next(&mut self) -> Option<Self::Item> {
@@ -1149,7 +1230,7 @@ where
lua.push_ref(&self.table.0);
match ffi::lua_rawgeti(state, -1, self.index) {
ffi::LUA_TNIL => None,
ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None,
_ => {
self.index += 1;
Some(V::from_stack(-1, lua))
+9 -9
View File
@@ -156,7 +156,6 @@ impl Thread {
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
@@ -165,6 +164,7 @@ impl Thread {
pushed_nargs += nargs;
}
let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -192,12 +192,12 @@ impl Thread {
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
check_stack(state, 1)?;
error.push_into_stack(&lua)?;
ffi::lua_xmove(state, thread_state, 1);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -336,22 +336,22 @@ impl Thread {
}
ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")),
ThreadStatusInner::Finished => Ok(()),
#[cfg(not(any(feature = "lua54", feature = "luau")))]
#[cfg(not(any(feature = "lua55", feature = "lua54", feature = "luau")))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
Err(Error::runtime("cannot reset non-finished thread"))
}
#[cfg(any(feature = "lua54", feature = "luau"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
let thread_state = self.state();
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
#[cfg(any(feature = "lua55", all(feature = "lua54", feature = "vendored")))]
let status = {
let lua = self.0.lua.lock();
ffi::lua_closethread(thread_state, lua.state())
};
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
@@ -604,7 +604,7 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
if status.is_yielded() {
if !(nresults == 1 && is_poll_pending(thread_state)) {
// Ignore value returned via yield()
// Ignore values returned via yield()
cx.waker().wake_by_ref();
}
return Poll::Pending;
@@ -635,7 +635,7 @@ struct WakerGuard<'lua, 'a> {
impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[inline]
pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
let prev = lua.set_waker(NonNull::from(waker));
Ok(WakerGuard {
lua,
prev,
@@ -647,7 +647,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[cfg(feature = "async")]
impl Drop for WakerGuard<'_, '_> {
fn drop(&mut self) {
unsafe { self.lua.set_waker(self.prev) };
self.lua.set_waker(self.prev);
}
}
+42 -2
View File
@@ -5,9 +5,9 @@ use std::sync::Arc;
use crate::error::{Error, Result};
use crate::multi::MultiValue;
use crate::private::Sealed;
use crate::state::{Lua, RawLua};
use crate::state::{Lua, RawLua, WeakLua};
use crate::types::MaybeSend;
use crate::util::{check_stack, short_type_name};
use crate::util::{check_stack, parse_lookup_path, short_type_name};
use crate::value::Value;
#[cfg(feature = "async")]
@@ -200,10 +200,50 @@ pub trait ObjectLike: Sealed {
where
R: FromLuaMulti;
/// Look up a value by a path of keys.
///
/// The syntax is similar to accessing nested tables in Lua, with additional support for
/// `?` operator to perform safe navigation.
///
/// For example, the path `a[1].c` is equivalent to `table.a[1].c` in Lua.
/// With `?` operator, `a[1]?.c` is equivalent to `table.a[1] and table.a[1].c or nil` in Lua.
///
/// Bracket notation rules:
/// - `[123]` - integer keys
/// - `["string key"]` or `['string key']` - string keys (must be quoted)
/// - String keys support escape sequences: `\"`, `\'`, `\\`
fn get_path<V: FromLua>(&self, path: &str) -> Result<V> {
let mut current = self.to_value();
for (key, safe_nil) in parse_lookup_path(path)? {
current = match current {
Value::Table(table) => table.get::<Value>(key),
Value::UserData(ud) => ud.get::<Value>(key),
_ => {
let type_name = current.type_name();
let err = format!("attempt to index a {type_name} value with key '{key}'");
Err(Error::runtime(err))
}
}?;
if safe_nil && (current == Value::Nil || current == Value::NULL) {
break;
}
}
let lua = self.weak_lua().lock();
V::from_lua(current, lua.lua())
}
/// Converts the object to a string in a human-readable format.
///
/// This might invoke the `__tostring` metamethod.
fn to_string(&self) -> Result<StdString>;
/// Converts the object to a Lua value.
fn to_value(&self) -> Value;
/// Gets a reference to the associated Lua state.
#[doc(hidden)]
fn weak_lua(&self) -> &WeakLua;
}
/// A trait for types that can be used as Lua functions.
+13 -4
View File
@@ -20,6 +20,9 @@ pub use either::Either;
pub use registry_key::RegistryKey;
pub(crate) use value_ref::ValueRef;
#[cfg(feature = "async")]
pub(crate) use value_ref::ValueRefIndex;
/// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers.
@@ -35,10 +38,13 @@ unsafe impl Send for LightUserData {}
unsafe impl Sync for LightUserData {}
#[cfg(feature = "send")]
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'static>;
type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'a;
#[cfg(not(feature = "send"))]
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 'static>;
type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + 'a;
pub(crate) type Callback = Box<CallbackFn<'static>>;
pub(crate) type CallbackPtr = *const CallbackFn<'static>;
pub(crate) type ScopedCallback<'s> = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 's>;
@@ -102,10 +108,12 @@ pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Se
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
#[cfg(all(feature = "send", feature = "lua54"))]
#[cfg(feature = "send")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "lua54"))]
#[cfg(not(feature = "send"))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()>>;
/// A trait that adds `Send` requirement if `send` feature is enabled.
@@ -114,6 +122,7 @@ pub trait MaybeSend: Send {}
#[cfg(feature = "send")]
impl<T: Send> MaybeSend for T {}
/// A trait that adds `Send` requirement if `send` feature is enabled.
#[cfg(not(feature = "send"))]
pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
+28 -23
View File
@@ -1,22 +1,39 @@
use std::fmt;
use std::os::raw::{c_int, c_void};
use super::XRc;
use crate::state::{RawLua, WeakLua};
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
#[derive(Clone)]
pub struct ValueRef {
pub(crate) lua: WeakLua,
// Keep index separate to avoid additional indirection when accessing it.
pub(crate) index: c_int,
pub(crate) drop: bool,
// If `index_count` is `None`, the value does not need to be destroyed.
pub(crate) index_count: Option<ValueRefIndex>,
}
/// A reference to a Lua value index in the auxiliary thread.
/// It's cheap to clone and can be used to track the number of references to a value.
#[derive(Clone)]
pub(crate) struct ValueRefIndex(pub(crate) XRc<c_int>);
impl From<c_int> for ValueRefIndex {
#[inline]
fn from(index: c_int) -> Self {
ValueRefIndex(XRc::new(index))
}
}
impl ValueRef {
#[inline]
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
let index = index.into();
ValueRef {
lua: lua.weak().clone(),
index,
drop: true,
index: *index.0,
index_count: Some(index),
}
}
@@ -25,16 +42,6 @@ impl ValueRef {
let lua = self.lua.lock();
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
}
/// Returns a copy of the value, which is valid as long as the original value is held.
#[inline]
pub(crate) fn copy(&self) -> Self {
ValueRef {
lua: self.lua.clone(),
index: self.index,
drop: false,
}
}
}
impl fmt::Debug for ValueRef {
@@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef {
}
}
impl Clone for ValueRef {
fn clone(&self) -> Self {
unsafe { self.lua.lock().clone_ref(self) }
}
}
impl Drop for ValueRef {
fn drop(&mut self) {
if self.drop {
if let Some(lua) = self.lua.try_lock() {
unsafe { lua.drop_ref(self) };
if let Some(ValueRefIndex(index)) = self.index_count.take() {
// It's guaranteed that the inner value returns exactly once.
// This means in particular that the value is not dropped.
if XRc::into_inner(index).is_some() {
if let Some(lua) = self.lua.try_lock() {
unsafe { lua.drop_ref(self) };
}
}
}
}
+130 -30
View File
@@ -12,7 +12,7 @@ use crate::string::String;
use crate::table::{Table, TablePairs};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{MaybeSend, ValueRef};
use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard};
use crate::util::{check_stack, get_userdata, push_string, short_type_name, take_userdata, StackGuard};
use crate::value::Value;
#[cfg(feature = "async")]
@@ -56,32 +56,53 @@ pub enum MetaMethod {
/// The unary minus (`-`) operator.
Unm,
/// The floor division (//) operator.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau")))
)]
IDiv,
/// The bitwise AND (&) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BAnd,
/// The bitwise OR (|) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BOr,
/// The bitwise XOR (binary ~) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BXor,
/// The bitwise NOT (unary ~) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BNot,
/// The bitwise left shift (<<) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
Shl,
/// The bitwise right shift (>>) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
Shr,
/// The string concatenation operator `..`.
Concat,
@@ -106,10 +127,22 @@ pub enum MetaMethod {
/// The `__pairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52")))
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
)))
)]
Pairs,
/// The `__ipairs` metamethod.
@@ -135,8 +168,8 @@ pub enum MetaMethod {
/// [documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
Close,
/// The `__name`/`__type` metafield.
///
@@ -176,19 +209,19 @@ impl MetaMethod {
MetaMethod::Pow => "__pow",
MetaMethod::Unm => "__unm",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
MetaMethod::IDiv => "__idiv",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BAnd => "__band",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BOr => "__bor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BXor => "__bxor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BNot => "__bnot",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::Shl => "__shl",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::Shr => "__shr",
MetaMethod::Concat => "__concat",
@@ -201,14 +234,20 @@ impl MetaMethod {
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
MetaMethod::Pairs => "__pairs",
#[cfg(any(feature = "lua52", feature = "luajit52"))]
MetaMethod::IPairs => "__ipairs",
#[cfg(feature = "luau")]
MetaMethod::Iter => "__iter",
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
MetaMethod::Close => "__close",
#[rustfmt::skip]
@@ -273,6 +312,29 @@ pub trait UserDataMethods<T> {
A: FromLuaMulti,
R: IntoLuaMulti;
/// Add a method which accepts `T` as the first parameter.
///
/// The userdata `T` will be moved out of the userdata container. This is useful for
/// methods that need to consume the userdata.
///
/// The method can be called only once per userdata instance, subsequent calls will result in a
/// [`Error::UserDataDestructed`] error.
#[doc(hidden)]
fn add_method_once<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(&Lua, T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let method_name = format!("{}.{name}", short_type_name::<T>());
self.add_function(name, move |lua, (ud, args): (AnyUserData, A)| {
let this = (ud.take()).map_err(|err| Error::bad_self_argument(&method_name, err))?;
method(lua, this, args)
});
}
/// Add an async method which accepts a `&T` as the first parameter and returns [`Future`].
///
/// Refer to [`add_method`] for more information about the implementation.
@@ -303,6 +365,34 @@ pub trait UserDataMethods<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti;
/// Add an async method which accepts a `T` as the first parameter and returns [`Future`].
///
/// The userdata `T` will be moved out of the userdata container. This is useful for
/// methods that need to consume the userdata.
///
/// The method can be called only once per userdata instance, subsequent calls will result in a
/// [`Error::UserDataDestructed`] error.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[doc(hidden)]
fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let method_name = format!("{}.{name}", short_type_name::<T>());
self.add_async_function(name, move |lua, (ud, args): (AnyUserData, A)| {
match (ud.take()).map_err(|err| Error::bad_self_argument(&method_name, err)) {
Ok(this) => either::Either::Left(method(lua, this, args)),
Err(err) => either::Either::Right(async move { Err(err) }),
}
});
}
/// Add a regular method as a function which accepts generic arguments.
///
/// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua
@@ -628,6 +718,14 @@ impl AnyUserData {
matches!(type_id, Some(type_id) if type_id == TypeId::of::<T>())
}
/// Checks whether the type of this userdata is a [proxy object] for `T`.
///
/// [proxy object]: crate::Lua::create_proxy
#[inline]
pub fn is_proxy<T: 'static>(&self) -> bool {
self.is::<UserDataProxy<T>>()
}
/// Borrow this userdata immutably if it is of type `T`.
///
/// # Errors
@@ -921,8 +1019,10 @@ impl AnyUserData {
lua.get_userdata_ref_type_id(&self.0).ok().flatten()
}
/// Returns a type name of this `UserData` (from a metatable field).
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
/// Returns a type name of this userdata (from a metatable field).
///
/// If no type name is set, returns `None`.
pub fn type_name(&self) -> Result<Option<StdString>> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
+16 -5
View File
@@ -1,6 +1,7 @@
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::state::WeakLua;
use crate::table::Table;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::userdata::AnyUserData;
@@ -15,14 +16,14 @@ impl ObjectLike for AnyUserData {
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
// `lua_gettable` method used under the hood can work with any Lua value
// that has `__index` metamethod
Table(self.0.copy()).get_protected(key)
Table(self.0.clone()).get_protected(key)
}
#[inline]
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
// `lua_settable` method used under the hood can work with any Lua value
// that has `__newindex` metamethod
Table(self.0.copy()).set_protected(key, value)
Table(self.0.clone()).set_protected(key, value)
}
#[inline]
@@ -30,7 +31,7 @@ impl ObjectLike for AnyUserData {
where
R: FromLuaMulti,
{
Function(self.0.copy()).call(args)
Function(self.0.clone()).call(args)
}
#[cfg(feature = "async")]
@@ -39,7 +40,7 @@ impl ObjectLike for AnyUserData {
where
R: FromLuaMulti,
{
Function(self.0.copy()).call_async(args)
Function(self.0.clone()).call_async(args)
}
#[inline]
@@ -88,6 +89,16 @@ impl ObjectLike for AnyUserData {
#[inline]
fn to_string(&self) -> Result<StdString> {
Value::UserData(AnyUserData(self.0.copy())).to_string()
Value::UserData(self.clone()).to_string()
}
#[inline]
fn to_value(&self) -> Value {
Value::UserData(self.clone())
}
#[inline]
fn weak_lua(&self) -> &WeakLua {
&self.0.lua
}
}
+22
View File
@@ -56,6 +56,9 @@ pub(crate) struct RawUserDataRegistry {
pub(crate) destructor: ffi::lua_CFunction,
pub(crate) type_id: Option<TypeId>,
pub(crate) type_name: StdString,
#[cfg(feature = "luau")]
pub(crate) enable_namecall: bool,
}
impl UserDataType {
@@ -100,6 +103,8 @@ impl<T> UserDataRegistry<T> {
destructor: super::util::destroy_userdata_storage::<T>,
type_id: r#type.type_id(),
type_name: short_type_name::<T>(),
#[cfg(feature = "luau")]
enable_namecall: false,
};
UserDataRegistry {
@@ -110,6 +115,23 @@ impl<T> UserDataRegistry<T> {
}
}
/// Enables support for the namecall optimization in Luau.
///
/// This enables methods resolution optimization in Luau for complex userdata types with methods
/// and field getters. When enabled, Luau will use a faster lookup path for method calls when a
/// specific syntax is used (e.g. `obj:method()`.
///
/// This optimization does not play well with async methods, custom `__index` metamethod and
/// field getters as functions. So, it is disabled by default.
///
/// Use with caution.
#[doc(hidden)]
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn enable_namecall(&mut self) {
self.raw.enable_namecall = true;
}
fn box_method<M, A, R>(&self, name: &str, method: M) -> Callback
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
+41
View File
@@ -4,8 +4,11 @@ use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr;
use rustc_hash::FxHashMap;
use super::UserDataStorage;
use crate::error::{Error, Result};
use crate::types::CallbackPtr;
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
// This is a trick to check if a type is `Sync` or not.
@@ -244,6 +247,7 @@ pub(crate) unsafe fn init_userdata_metatable(
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
_methods_map: Option<FxHashMap<Vec<u8>, CallbackPtr>>, // Used only in Luau for `__namecall`
) -> Result<()> {
if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function
@@ -267,6 +271,13 @@ pub(crate) unsafe fn init_userdata_metatable(
}
rawset_field(state, metatable, "__index")?;
#[cfg(feature = "luau")]
if let Some(methods_map) = _methods_map {
// In Luau we can speedup method calls by providing a dedicated `__namecall` metamethod
push_userdata_metatable_namecall(state, methods_map)?;
rawset_field(state, metatable, "__namecall")?;
}
}
if let Some(field_setters) = field_setters {
@@ -425,6 +436,36 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
})
}
#[cfg(feature = "luau")]
unsafe fn push_userdata_metatable_namecall(
state: *mut ffi::lua_State,
methods_map: FxHashMap<Vec<u8>, CallbackPtr>,
) -> Result<()> {
unsafe extern "C-unwind" fn namecall(state: *mut ffi::lua_State) -> c_int {
let name = ffi::lua_namecallatom(state, ptr::null_mut());
if name.is_null() {
ffi::luaL_error(state, cstr!("attempt to call an unknown method"));
}
let name_cs = std::ffi::CStr::from_ptr(name);
let methods_map = get_userdata::<FxHashMap<Vec<u8>, CallbackPtr>>(state, ffi::lua_upvalueindex(1));
let callback_ptr = match (*methods_map).get(name_cs.to_bytes()) {
Some(ptr) => *ptr,
#[rustfmt::skip]
None => ffi::luaL_error(state, cstr!("attempt to call an unknown method '%s'"), name),
};
crate::state::callback_error_ext(state, ptr::null_mut(), true, |extra, nargs| {
let rawlua = (*extra).raw_lua();
(*callback_ptr)(rawlua, nargs)
})
}
// Automatic destructor is provided for any Luau userdata
crate::util::push_userdata(state, methods_map, true)?;
protect_lua!(state, 1, 1, |state| {
ffi::lua_pushcclosured(state, namecall, cstr!("__namecall"), 1);
})
}
// This method is called by Lua GC when it's time to collect the userdata.
//
// This method is usually used to collect internal userdata.
+19 -11
View File
@@ -208,7 +208,7 @@ where
F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy,
{
let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>;
let params = ffi::lua_tolightuserdata(state, -1) as *mut Params<F, R>;
ffi::lua_pop(state, 1);
let f = (*params).function.take().unwrap();
@@ -239,7 +239,7 @@ where
ffi::lua_pushlightuserdata(state, &mut params as *mut Params<F, R> as *mut c_void);
let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1);
ffi::lua_remove(state, stack_start + 1);
ffi::lua_remove(state, stack_start + 1); // remove error handler
if ret == ffi::LUA_OK {
// `LUA_OK` is only returned when the `do_call` function has completed successfully, so
@@ -373,19 +373,19 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
"__mod",
"__pow",
"__unm",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
"__idiv",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__band",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__bor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__bxor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__bnot",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__shl",
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__shr",
"__concat",
"__len",
@@ -396,13 +396,21 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
"__newindex",
"__call",
"__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
"__pairs",
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))]
"__ipairs",
#[cfg(feature = "luau")]
"__iter",
#[cfg(feature = "lua54")]
#[cfg(feature = "luau")]
"__namecall",
#[cfg(any(feature = "lua55", feature = "lua54"))]
"__close",
] {
ffi::lua_pushvalue(state, -1);
+41 -9
View File
@@ -9,6 +9,7 @@ pub(crate) use error::{
error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call,
protect_lua_closure, WrappedFailure,
};
pub(crate) use path::parse_path as parse_lookup_path;
pub(crate) use short_names::short_type_name;
pub(crate) use types::TypeKey;
pub(crate) use userdata::{
@@ -98,18 +99,48 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect:
}
}
// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack.
#[cfg(feature = "lua55")]
pub(crate) unsafe fn push_external_string(
state: *mut ffi::lua_State,
mut bytes: Vec<u8>,
protect: bool,
) -> Result<()> {
bytes.push(0);
let s_len = bytes.len() - 1; // exclude null terminator
let s_ptr = bytes.as_ptr() as *const c_char;
let bytes_ud = Box::into_raw(Box::new(bytes));
unsafe extern "C" fn dealloc(ud: *mut c_void, _: *mut c_void, _: usize, _: usize) -> *mut c_void {
drop(Box::from_raw(ud as *mut Vec<u8>));
ptr::null_mut()
}
if protect {
let res = protect_lua!(state, 0, 1, move |state| {
ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _);
});
if res.is_err() {
// Deallocate on error
drop(Box::from_raw(bytes_ud));
return res;
}
} else {
ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _);
}
Ok(())
}
// Uses 3 stack spaces (when protect), does not call checkstack.
#[cfg(feature = "luau")]
#[inline(always)]
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> {
let data = if protect {
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))?
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, size: usize, protect: bool) -> Result<*mut u8> {
let data = if protect || size > const { 1024 * 1024 * 1024 } {
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, size))?
} else {
ffi::lua_newbuffer(state, b.len())
ffi::lua_newbuffer(state, size)
};
let buf = slice::from_raw_parts_mut(data as *mut u8, b.len());
buf.copy_from_slice(b);
Ok(())
Ok(data as *mut u8)
}
// Uses 3 stack spaces, does not call checkstack.
@@ -122,7 +153,7 @@ pub(crate) unsafe fn push_table(
) -> Result<()> {
let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
if protect || narr >= const { 1 << 30 } || nrec >= const { 1 << 27 } {
if protect || narr >= const { 1 << 26 } || nrec >= const { 1 << 26 } {
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
} else {
ffi::lua_createtable(state, narr, nrec);
@@ -221,7 +252,7 @@ pub(crate) unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -
// Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1.
// Does not call lua_checkstack, uses 1 stack space.
pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
{
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
let main_state = ffi::lua_tothread(state, -1);
@@ -329,6 +360,7 @@ pub(crate) fn linenumber_to_usize(n: c_int) -> Option<usize> {
}
mod error;
mod path;
mod short_names;
mod types;
mod userdata;
+255
View File
@@ -0,0 +1,255 @@
use std::borrow::Cow;
use std::fmt;
use std::iter::Peekable;
use std::str::CharIndices;
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::traits::IntoLua;
use crate::types::Integer;
use crate::value::Value;
#[derive(Debug)]
pub(crate) enum PathKey<'a> {
Str(Cow<'a, str>),
Int(Integer),
}
impl fmt::Display for PathKey<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PathKey::Str(s) => write!(f, "{}", s),
PathKey::Int(i) => write!(f, "{}", i),
}
}
}
impl IntoLua for PathKey<'_> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
match self {
PathKey::Str(s) => Ok(Value::String(lua.create_string(s.as_ref())?)),
PathKey::Int(i) => Ok(Value::Integer(i)),
}
}
}
// Parses a path like `a.b[3]?.c["d"]` into segments of `(key, safe_nil)`.
pub(crate) fn parse_path<'a>(path: &'a str) -> Result<Vec<(PathKey<'a>, bool)>> {
fn read_ident<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> (Cow<'a, str>, bool) {
let mut safe_nil = false;
let start = chars.peek().map(|&(i, _)| i).unwrap_or(path.len());
let mut end = start;
while let Some(&(pos, c)) = chars.peek() {
if c == '.' || c == '?' || c.is_ascii_whitespace() || c == '[' {
if c == '?' {
safe_nil = true;
chars.next(); // consume '?'
}
break;
}
end = pos + c.len_utf8();
chars.next();
}
(Cow::Borrowed(&path[start..end]), safe_nil)
}
let mut segments = Vec::new();
let mut chars = path.char_indices().peekable();
while let Some(&(pos, next)) = chars.peek() {
match next {
'.' => {
// Dot notation: identifier
chars.next();
let (key, safe_nil) = read_ident(path, &mut chars);
if key.is_empty() {
return Err(Error::runtime(format!("empty key in path at position {pos}")));
}
segments.push((PathKey::Str(key), safe_nil));
}
'[' => {
// Bracket notation: either integer or quoted string
chars.next();
let key = match chars.peek() {
Some(&(pos, c @ '0'..='9' | c @ '-')) => {
// Integer key
let negative = c == '-';
if negative {
chars.next(); // consume '-'
}
let mut num: Option<Integer> = None;
while let Some(&(_, c @ '0'..='9')) = chars.peek() {
let new_num = num
.unwrap_or(0)
.checked_mul(10)
.and_then(|n| n.checked_add((c as u8 - b'0') as Integer))
.ok_or_else(|| {
Error::runtime(format!("integer overflow in path at position {pos}"))
})?;
num = Some(new_num);
chars.next(); // consume digit
}
match num {
Some(n) if negative => PathKey::Int(-n),
Some(n) => PathKey::Int(n),
None => {
let err = format!("invalid integer in path at position {pos}");
return Err(Error::runtime(err));
}
}
}
Some((_, '\'' | '"')) => {
// Quoted string
PathKey::Str(unquote_string(path, &mut chars)?)
}
Some((_, ']')) => {
return Err(Error::runtime(format!("empty key in path at position {pos}")));
}
Some((pos, c)) => {
let err = format!("unexpected character '{c}' in path at position {pos}");
return Err(Error::runtime(err));
}
None => {
return Err(Error::runtime("unexpected end of path"));
}
};
// Expect closing bracket
let mut safe_nil = false;
match chars.next() {
Some((_, ']')) => {
// Check for optional safe-nil operator
if let Some(&(_, '?')) = chars.peek() {
safe_nil = true;
chars.next(); // consume '?'
}
}
Some((pos, c)) => {
let err = format!("expected ']' in path at position {pos}, found '{c}'");
return Err(Error::runtime(err));
}
None => {
return Err(Error::runtime("unexpected end of path"));
}
}
segments.push((key, safe_nil));
}
c if c.is_ascii_whitespace() => {
chars.next(); // Skip whitespace
}
_ if segments.is_empty() => {
// First segment without dot/bracket notation
let (key_cow, safe_nil) = read_ident(path, &mut chars);
if key_cow.is_empty() {
return Err(Error::runtime(format!("empty key in path at position {pos}")));
}
segments.push((PathKey::Str(key_cow), safe_nil));
}
c => {
let err = format!("unexpected character '{c}' in path at position {pos}");
return Err(Error::runtime(err));
}
}
}
Ok(segments)
}
fn unquote_string<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> Result<Cow<'a, str>> {
let (start_pos, first_quote) = chars.next().unwrap();
let mut result = String::new();
loop {
match chars.next() {
Some((pos, '\\')) => {
if result.is_empty() {
// First escape found, copy everything up to this point
result.push_str(&path[start_pos + 1..pos]);
}
match chars.next() {
Some((_, '\\')) => result.push('\\'),
Some((_, '"')) => result.push('"'),
Some((_, '\'')) => result.push('\''),
Some((_, other)) => {
result.push('\\');
result.push(other);
}
None => continue, // will be handled by outer loop
}
}
Some((pos, c)) if c == first_quote => {
if !result.is_empty() {
return Ok(Cow::Owned(result));
}
// No escapes, return borrowed slice
return Ok(Cow::Borrowed(&path[start_pos + 1..pos]));
}
Some((_, c)) => {
if !result.is_empty() {
result.push(c);
}
// If no escapes yet, continue tracking for potential borrowed slice
}
None => {
let err = format!("unexpected end of string at position {start_pos}");
return Err(Error::runtime(err));
}
}
}
}
#[cfg(test)]
mod tests {
use super::{parse_path, PathKey};
#[test]
fn test_parse_path() {
// Test valid paths
let path = parse_path("a.b[3]?.c['d']").unwrap();
assert_eq!(path.len(), 5);
assert!(matches!(path[0], (PathKey::Str(ref s), false) if s == "a"));
assert!(matches!(path[1], (PathKey::Str(ref s), false) if s == "b"));
assert!(matches!(path[2], (PathKey::Int(3), true)));
assert!(matches!(path[3], (PathKey::Str(ref s), false) if s == "c"));
assert!(matches!(path[4], (PathKey::Str(ref s), false) if s == "d"));
// Test empty path
let path = parse_path("").unwrap();
assert_eq!(path.len(), 0);
let path = parse_path(" ").unwrap();
assert_eq!(path.len(), 0);
// Test invalid dot syntax
let err = parse_path("a..b").unwrap_err().to_string();
assert_eq!(err, "runtime error: empty key in path at position 1");
let err = parse_path("a.b.").unwrap_err().to_string();
assert_eq!(err, "runtime error: empty key in path at position 3");
// Test invalid bracket syntax
let err = parse_path("a[unclosed").unwrap_err().to_string();
assert_eq!(
err,
"runtime error: unexpected character 'u' in path at position 2"
);
let err = parse_path("a[]").unwrap_err().to_string();
assert_eq!(err, "runtime error: empty key in path at position 1");
let err = parse_path(r#"a["unclosed"#).unwrap_err().to_string();
assert_eq!(err, "runtime error: unexpected end of string at position 2");
let err = parse_path(r#"a["#).unwrap_err().to_string();
assert_eq!(err, "runtime error: unexpected end of path");
let err = parse_path(r#"a[123"#).unwrap_err().to_string();
assert_eq!(err, "runtime error: unexpected end of path");
let err = parse_path(r#"a['bla'123"#).unwrap_err().to_string();
assert_eq!(
err,
"runtime error: expected ']' in path at position 7, found '1'"
);
let err = parse_path(r#"a["bla"]x"#).unwrap_err().to_string();
assert_eq!(
err,
"runtime error: unexpected character 'x' in path at position 8"
);
// Test bad integers
let err = parse_path("a[99999999999999999999]").unwrap_err().to_string();
assert_eq!(err, "runtime error: integer overflow in path at position 2");
let err = parse_path("a[-]").unwrap_err().to_string();
assert_eq!(err, "runtime error: invalid integer in path at position 2");
}
}
+18 -8
View File
@@ -1,6 +1,6 @@
//! Mostly copied from [bevy_utils]
//! Inspired by bevy's [disqualified]
//!
//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
//! [disqualified]: https://github.com/bevyengine/disqualified/blob/main/src/short_name.rs
use std::any::type_name;
@@ -23,8 +23,7 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
while index < end_of_string {
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
// Collapse everything up to the next special character,
// then skip over it
// Collapse everything up to the next special character, then skip over it
if let Some(special_character_index) =
rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
{
@@ -32,11 +31,16 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
parsed_name += collapse_type_name(segment_to_collapse);
// Insert the special character
let special_character = &rest_of_string[special_character_index..=special_character_index];
parsed_name.push_str(special_character);
parsed_name += special_character;
// Remove lifetimes like <'_> or <'_, '_, ...>
if parsed_name.ends_with("<'_>") || parsed_name.ends_with("<'_, ") {
_ = parsed_name.split_off(parsed_name.len() - 4);
}
match special_character {
">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => {
parsed_name.push_str("::");
parsed_name += "::";
// Move the index past the "::"
index += special_character_index + 3;
}
@@ -53,14 +57,18 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
}
#[inline(always)]
fn collapse_type_name(string: &str) -> &str {
string.rsplit("::").next().unwrap()
fn collapse_type_name(segment: &str) -> &str {
segment.rsplit("::").next().unwrap()
}
#[cfg(test)]
mod tests {
use super::short_type_name;
use std::collections::HashMap;
use std::marker::PhantomData;
struct MyData<'a, 'b>(PhantomData<&'a &'b ()>);
struct MyDataT<'a, T>(PhantomData<&'a T>);
#[test]
fn tests() {
@@ -73,5 +81,7 @@ mod tests {
"HashMap<String, Option<[i32; 3]>>"
);
assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32");
assert_eq!(short_type_name::<MyDataT<&str>>(), "MyDataT<&str>");
assert_eq!(short_type_name::<(&MyData, [MyData])>(), "(MyData, [MyData])");
}
}
+17 -12
View File
@@ -28,9 +28,10 @@ use {
/// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types
/// into the internal Lua state. It is a logic error to mix handle types between separate
/// `Lua` instances, and doing so will result in a panic.
#[derive(Clone)]
#[derive(Clone, Default)]
pub enum Value {
/// The Lua value `nil`.
#[default]
Nil,
/// The Lua value `true` or `false`.
Boolean(bool),
@@ -491,7 +492,6 @@ impl Value {
/// This allows customizing serialization behavior using serde.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[doc(hidden)]
pub fn to_serializable(&self) -> SerializableValue<'_> {
SerializableValue::new(self, Default::default(), None)
}
@@ -580,12 +580,6 @@ impl Value {
}
}
impl Default for Value {
fn default() -> Self {
Self::Nil
}
}
impl fmt::Debug for Value {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
@@ -684,7 +678,7 @@ impl<'a> SerializableValue<'a> {
///
/// Default: **true**
#[must_use]
pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self {
pub fn deny_unsupported_types(mut self, enabled: bool) -> Self {
self.options.deny_unsupported_types = enabled;
self
}
@@ -695,7 +689,7 @@ impl<'a> SerializableValue<'a> {
///
/// Default: **true**
#[must_use]
pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self {
pub fn deny_recursive_tables(mut self, enabled: bool) -> Self {
self.options.deny_recursive_tables = enabled;
self
}
@@ -704,7 +698,7 @@ impl<'a> SerializableValue<'a> {
///
/// Default: **false**
#[must_use]
pub const fn sort_keys(mut self, enabled: bool) -> Self {
pub fn sort_keys(mut self, enabled: bool) -> Self {
self.options.sort_keys = enabled;
self
}
@@ -713,10 +707,21 @@ impl<'a> SerializableValue<'a> {
///
/// Default: **false**
#[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
pub fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.options.encode_empty_tables_as_array = enabled;
self
}
/// If true, enable detection of mixed tables.
///
/// A mixed table is a table that has both array-like and map-like entries or several borders.
///
/// Default: **false**
#[must_use]
pub fn detect_mixed_tables(mut self, enabled: bool) -> Self {
self.options.detect_mixed_tables = enabled;
self
}
}
#[cfg(feature = "serde")]
+6 -6
View File
@@ -1,11 +1,11 @@
[lua54]
features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
[lua55]
features = "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
[lua54_non_send]
features = "lua54,vendored,async,serde,macros,anyhow,userdata-wrappers"
[lua55_non_send]
features = "lua55,vendored,async,serde,macros,anyhow,userdata-wrappers"
[lua54_with_memory_limit]
features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
[lua55_with_memory_limit]
features = "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
rustflags = "--cfg force_memory_limit"
[lua51]
+58 -6
View File
@@ -8,7 +8,7 @@ use futures_util::stream::TryStreamExt;
use tokio::sync::Mutex;
use mlua::{
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
UserDataMethods, UserDataRef, Value,
};
@@ -249,7 +249,7 @@ async fn test_async_return_async_closure() -> Result<()> {
Ok(())
}
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[tokio::test]
async fn test_async_lua54_to_be_closed() -> Result<()> {
let lua = Lua::new();
@@ -423,9 +423,9 @@ async fn test_async_thread_pool() -> Result<()> {
#[tokio::test]
async fn test_async_userdata() -> Result<()> {
struct MyUserData(u64);
struct MyUserdata(u64);
impl UserData for MyUserData {
impl UserData for MyUserdata {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move {
sleep_ms(10).await;
@@ -438,6 +438,11 @@ async fn test_async_userdata() -> Result<()> {
Ok(())
});
methods.add_async_method_once("take_value", |_, data, ()| async move {
sleep_ms(10).await;
Ok(data.0)
});
methods.add_async_function("sleep", |_, n| async move {
sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n))
@@ -479,7 +484,7 @@ async fn test_async_userdata() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(11))?;
let userdata = lua.create_userdata(MyUserdata(11))?;
globals.set("userdata", &userdata)?;
lua.load(
@@ -518,6 +523,21 @@ async fn test_async_userdata() -> Result<()> {
#[cfg(not(any(feature = "lua51", feature = "luau")))]
assert_eq!(userdata.call_async::<String>(()).await?, "elapsed:24ms");
// Take value
let userdata2 = lua.create_userdata(MyUserdata(0))?;
globals.set("userdata2", userdata2)?;
lua.load("assert(userdata:take_value() == 24)")
.exec_async()
.await?;
match lua.load("userdata2.take_value(userdata)").exec_async().await {
Err(Error::CallbackError { cause, .. }) => {
let err = cause.to_string();
assert!(err.contains("bad argument `self` to `MyUserdata.take_value`"));
assert!(err.contains("userdata has been destructed"));
}
r => panic!("expected Err(CallbackError), got {r:?}"),
}
Ok(())
}
@@ -650,7 +670,7 @@ async fn test_async_hook() -> Result<()> {
static HOOK_CALLED: AtomicBool = AtomicBool::new(false);
lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| {
if !HOOK_CALLED.swap(true, Ordering::Relaxed) {
#[cfg(any(feature = "lu53", feature = "lua54"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
return Ok(mlua::VmState::Yield);
}
Ok(mlua::VmState::Continue)
@@ -667,3 +687,35 @@ async fn test_async_hook() -> Result<()> {
Ok(())
}
#[test]
fn test_async_yield_with() -> Result<()> {
let lua = Lua::new();
let func = lua.create_async_function(|lua, (mut a, mut b): (i32, i32)| async move {
let zero = lua.yield_with::<MultiValue>(()).await?;
assert!(zero.is_empty());
let one = lua.yield_with::<MultiValue>(a + b).await?;
assert_eq!(one.len(), 1);
for _ in 0..3 {
(a, b) = lua.yield_with((a + b, a * b)).await?;
}
Ok((0, 0))
})?;
let thread = lua.create_thread(func)?;
let zero = thread.resume::<MultiValue>((2, 3))?; // function arguments
assert!(zero.is_empty());
let one = thread.resume::<i32>(())?; // value of "zero" is passed here
assert_eq!(one, 5);
assert_eq!(thread.resume::<(i32, i32)>(1)?, (5, 6)); // value of "one" is passed here
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
assert_eq!(thread.status(), ThreadStatus::Finished);
Ok(())
}
+68 -2
View File
@@ -1,5 +1,7 @@
#![cfg(feature = "luau")]
use std::io::{Read, Seek, SeekFrom, Write};
use mlua::{Lua, Result, Value};
#[test]
@@ -41,7 +43,7 @@ fn test_buffer() -> Result<()> {
}
#[test]
#[should_panic(expected = "range end index 14 out of range for slice of length 13")]
#[should_panic(expected = "out of range for slice of length 13")]
fn test_buffer_out_of_bounds_read() {
let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap();
@@ -49,9 +51,73 @@ fn test_buffer_out_of_bounds_read() {
}
#[test]
#[should_panic(expected = "range end index 16 out of range for slice of length 13")]
#[should_panic(expected = "out of range for slice of length 13")]
fn test_buffer_out_of_bounds_write() {
let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap();
buf.write_bytes(14, b"!!");
}
#[test]
fn create_large_buffer() {
let lua = Lua::new();
let err = lua.create_buffer_with_capacity(1_073_741_824 + 1).unwrap_err(); // 1GB
assert!(err.to_string().contains("memory allocation error"));
// Normal buffer is okay
let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap();
assert_eq!(buf.len(), 1024 * 1024);
}
#[test]
fn test_buffer_cursor() -> Result<()> {
let lua = Lua::new();
let mut cursor = lua.create_buffer(b"hello, world")?.cursor();
let mut data = Vec::new();
cursor.read_to_end(&mut data)?;
assert_eq!(data, b"hello, world");
// No more data to read
let mut one = [0u8; 1];
assert_eq!(cursor.read(&mut one)?, 0);
// Seek to start
cursor.seek(SeekFrom::Start(0))?;
cursor.read_exact(&mut one)?;
assert_eq!(one, [b'h']);
// Seek to end -5
cursor.seek(SeekFrom::End(-5))?;
let mut five = [0u8; 5];
cursor.read_exact(&mut five)?;
assert_eq!(&five, b"world");
// Seek to current -1
cursor.seek(SeekFrom::Current(-1))?;
cursor.read_exact(&mut one)?;
assert_eq!(one, [b'd']);
// Invalid seek
assert!(cursor.seek(SeekFrom::Current(-100)).is_err());
assert!(cursor.seek(SeekFrom::End(1)).is_err());
// Write data
let buf = lua.create_buffer_with_capacity(100)?;
cursor = buf.clone().cursor();
cursor.write_all(b"hello, ...")?;
cursor.seek(SeekFrom::Current(-3))?;
cursor.write_all(b"Rust!")?;
assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!");
// Writing beyond the end of the buffer does nothing
cursor.seek(SeekFrom::End(0))?;
assert_eq!(cursor.write(b".")?, 0);
// Flush is no-op
cursor.flush()?;
Ok(())
}
+1
View File
@@ -21,6 +21,7 @@ fn test_chunk_methods() -> Result<()> {
}
#[test]
#[cfg(not(target_os = "wasi"))]
fn test_chunk_path() -> Result<()> {
let lua = Lua::new();
+22 -12
View File
@@ -1,15 +1,19 @@
error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure
--> tests/compile/async_any_userdata_method.rs:9:49
|
9 | reg.add_async_method("t", |_, this, ()| async {
| ^^^^^ cannot borrow as mutable
8 | let mut s = &s;
| ----- `s` declared here, outside the closure
9 | reg.add_async_method("t", |_, this, ()| async {
| ------------- ^^^^^ cannot borrow as mutable
| |
| in this closure
10 | s = &*this;
| - mutable borrow occurs due to use of `s` in closure
error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function
--> tests/compile/async_any_userdata_method.rs:9:49
|
9 | reg.add_async_method("t", |_, this, ()| async {
9 | reg.add_async_method("t", |_, this, ()| async {
| ^^^^^ may outlive borrowed value `this`
10 | s = &*this;
| ---- `this` is borrowed here
@@ -17,7 +21,7 @@ error[E0373]: async block may outlive the current function, but it borrows `this
note: async block is returned here
--> tests/compile/async_any_userdata_method.rs:9:49
|
9 | reg.add_async_method("t", |_, this, ()| async {
9 | reg.add_async_method("t", |_, this, ()| async {
| _________________________________________________^
10 | | s = &*this;
11 | | Ok(())
@@ -25,13 +29,13 @@ note: async block is returned here
| |_________^
help: to force the async block to take ownership of `this` (and any other referenced variables), use the `move` keyword
|
9 | reg.add_async_method("t", |_, this, ()| async move {
9 | reg.add_async_method("t", |_, this, ()| async move {
| ++++
error: lifetime may not live long enough
--> tests/compile/async_any_userdata_method.rs:9:49
|
9 | reg.add_async_method("t", |_, this, ()| async {
9 | reg.add_async_method("t", |_, this, ()| async {
| ___________________________________-------------_^
| | | |
| | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:49: 9:54}` contains a lifetime `'2`
@@ -46,22 +50,28 @@ error: lifetime may not live long enough
error[E0597]: `s` does not live long enough
--> tests/compile/async_any_userdata_method.rs:8:21
|
7 | let s = String::new();
7 | let s = String::new();
| - binding `s` declared here
8 | let mut s = &s;
8 | let mut s = &s;
| ^^ borrowed value does not live long enough
9 | / reg.add_async_method("t", |_, this, ()| async {
9 | / reg.add_async_method("t", |_, this, ()| async {
10 | | s = &*this;
11 | | Ok(())
12 | | });
| |__________- argument requires that `s` is borrowed for `'static`
13 | })
| - `s` dropped here while still borrowed
|
note: requirement that the value outlives `'static` introduced here
--> src/userdata.rs
|
| M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
| ^^^^^^^
error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function
--> tests/compile/async_any_userdata_method.rs:9:35
|
9 | reg.add_async_method("t", |_, this, ()| async {
9 | reg.add_async_method("t", |_, this, ()| async {
| ^^^^^^^^^^^^^ may outlive borrowed value `s`
10 | s = &*this;
| - `s` is borrowed here
@@ -69,12 +79,12 @@ error[E0373]: closure may outlive the current function, but it borrows `s`, whic
note: function requires argument type to outlive `'static`
--> tests/compile/async_any_userdata_method.rs:9:9
|
9 | / reg.add_async_method("t", |_, this, ()| async {
9 | / reg.add_async_method("t", |_, this, ()| async {
10 | | s = &*this;
11 | | Ok(())
12 | | });
| |__________^
help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword
|
9 | reg.add_async_method("t", move |_, this, ()| async {
9 | reg.add_async_method("t", move |_, this, ()| async {
| ++++
@@ -1,10 +1,10 @@
error: lifetime may not live long enough
--> tests/compile/async_nonstatic_userdata.rs:9:13
|
7 | impl UserData for MyUserData<'_> {
7 | impl UserData for MyUserData<'_> {
| -- lifetime `'1` appears in the `impl`'s self type
8 | fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
9 | / methods.add_async_method("print", |_, data, ()| async move {
8 | fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
9 | / methods.add_async_method("print", |_, data, ()| async move {
10 | | println!("{}", data.0);
11 | | Ok(())
12 | | });
+51 -29
View File
@@ -1,28 +1,32 @@
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
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
note: required because it appears within the type `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>` to implement `RefUnwindSafe`
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
note: required because it appears within the type `Lua`
--> src/state.rs
|
@@ -40,27 +44,45 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::extra::ExtraData>`
= note: required for `Rc<UnsafeCell<mlua::state::extra::ExtraData>>` to implement `RefUnwindSafe`
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
|
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>` to implement `RefUnwindSafe`
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
note: required because it appears within the type `Lua`
--> src/state.rs
|
+71 -128
View File
@@ -1,143 +1,86 @@
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
| pub struct Table(pub(crate) ValueRef);
| ^^^^^
note: required because it's used within this closure
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^
note: required by a bound in `std::panic::catch_unwind`
--> $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
| pub struct Table(pub(crate) ValueRef);
| ^^^^^
note: required because it's used within this closure
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^
note: required by a bound in `std::panic::catch_unwind`
--> $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
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
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
note: required because it appears within the type `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
| pub struct Table(pub(crate) ValueRef);
| ^^^^^
note: required because it's used within this closure
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^
note: required by a bound in `std::panic::catch_unwind`
--> $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::extra::ExtraData>`
= note: required for `Rc<UnsafeCell<mlua::state::extra::ExtraData>>` to implement `RefUnwindSafe`
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Weak<
| ^^^^
+8 -8
View File
@@ -1,24 +1,24 @@
error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function
--> tests/compile/scope_callback_capture.rs:7:43
|
5 | lua.scope(|scope| {
5 | lua.scope(|scope| {
| ----- has type `&'1 mlua::Scope<'1, '_>`
6 | let mut inner: Option<Table> = None;
7 | let f = scope.create_function_mut(|_, t: Table| {
6 | let mut inner: Option<Table> = None;
7 | let f = scope.create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
8 | inner = Some(t);
8 | inner = Some(t);
| ----- `inner` is borrowed here
|
note: function requires argument type to outlive `'1`
--> tests/compile/scope_callback_capture.rs:7:17
|
7 | let f = scope.create_function_mut(|_, t: Table| {
7 | let f = scope.create_function_mut(|_, t: Table| {
| _________________^
8 | | inner = Some(t);
9 | | Ok(())
8 | | inner = Some(t);
9 | | Ok(())
10 | | })?;
| |__________^
help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword
|
7 | let f = scope.create_function_mut(move |_, t: Table| {
7 | let f = scope.create_function_mut(move |_, t: Table| {
| ++++
+1 -1
View File
@@ -1,7 +1,7 @@
error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function
--> tests/compile/scope_invariance.rs:13:39
|
9 | lua.scope(|scope| {
9 | lua.scope(|scope| {
| ----- has type `&'1 mlua::Scope<'1, '_>`
...
13 | scope.create_function_mut(|_, ()| {
@@ -10,3 +10,9 @@ error[E0499]: cannot borrow `i` as mutable more than once at a time
| argument requires that `i` is borrowed for `'1`
12 | let _b = scope.create_userdata(MyUserData(&mut i)).unwrap();
| ^^^^^^ second mutable borrow occurs here
|
note: requirement that the value outlives `'1` introduced here
--> src/scope.rs
|
| T: UserData + 'env,
| ^^^^
@@ -13,3 +13,9 @@ error[E0597]: `ibad` does not live long enough
| argument requires that `ibad` is borrowed for `'1`
16 | };
| - `ibad` dropped here while still borrowed
|
note: requirement that the value outlives `'1` introduced here
--> src/scope.rs
|
| T: UserData + 'env,
| ^^^^
+19
View File
@@ -194,6 +194,25 @@ fn test_function_info() -> Result<()> {
assert_eq!(print_info.what, "C");
assert_eq!(print_info.line_defined, None);
// Function with upvalues and params
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
{
let func_with_upvalues = lua
.load(
r#"
local x, y = ...
return function(a, ...)
return a*x + y
end
"#,
)
.call::<Function>((10, 20))?;
let func_with_upvalues_info = func_with_upvalues.info();
assert_eq!(func_with_upvalues_info.num_upvalues, 2);
assert_eq!(func_with_upvalues_info.num_params, 1);
assert_eq!(func_with_upvalues_info.is_vararg, true);
}
Ok(())
}
+1 -1
View File
@@ -274,7 +274,7 @@ fn test_hook_yield() -> Result<()> {
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield))?;
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
{
assert!(co.resume::<()>(()).is_ok());
assert!(co.resume::<()>(()).is_ok());
+75 -1
View File
@@ -330,6 +330,15 @@ fn test_interrupts() -> Result<()> {
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
assert_eq!(co.status(), ThreadStatus::Finished);
// Test no yielding at non-yieldable points
yield_count.store(0, Ordering::Relaxed);
let co = lua.create_thread(lua.create_function(|lua, arg: Value| {
(lua.load("return (function(x) return x end)(...)")).call::<Value>(arg)
})?)?;
let res = co.resume::<String>("abc")?;
assert_eq!(res, "abc".to_string());
assert_eq!(yield_count.load(Ordering::Relaxed), 3);
//
// Test errors in interrupts
//
@@ -439,7 +448,7 @@ fn test_loadstring() -> Result<()> {
assert_eq!(f.call::<i32>(())?, 123);
let err = lua
.load(r#"loadstring("retur 123", "chunk")"#)
.load(r#"loadstring("retur 123", "chunk")"#) // typos:ignore
.exec()
.err()
.unwrap();
@@ -461,5 +470,70 @@ fn test_typeof_error() -> Result<()> {
Ok(())
}
#[test]
fn test_memory_category() -> Result<()> {
let lua = Lua::new();
lua.set_memory_category("main").unwrap();
// Invalid category names should be rejected
let err = lua.set_memory_category("invalid$");
assert!(err.is_err());
for i in 0..254 {
let name = format!("category_{}", i);
lua.set_memory_category(&name).unwrap();
}
// 255th category should fail
let err = lua.set_memory_category("category_254");
assert!(err.is_err());
Ok(())
}
#[test]
fn test_heap_dump() -> Result<()> {
let lua = Lua::new();
// Assign a new memory category and create few objects
lua.set_memory_category("test_category")?;
let _t = lua.create_table()?;
let _ud = lua.create_any_userdata("hello, world")?;
let dump = lua.heap_dump()?;
assert!(dump.size() > 0);
let size_by_category = dump.size_by_category();
assert_eq!(size_by_category.len(), 2);
assert!(size_by_category.contains_key("test_category"));
assert!(size_by_category["main"] < dump.size());
// Check size by type within the category
let size_by_type = dump.size_by_type(Some("test_category"));
assert!(!size_by_type.is_empty());
assert!(size_by_type.contains_key("table"));
assert!(size_by_type.contains_key("userdata"));
// Try non-existent category
let size_by_type2 = dump.size_by_type(Some("non_existent_category"));
assert!(size_by_type2.is_empty());
// Remove category filter
let size_by_type_all = dump.size_by_type(None);
assert!(size_by_type.len() < size_by_type_all.len());
// Check size by userdata type within the category
let size_by_udtype = dump.size_by_userdata(Some("test_category"));
assert_eq!(size_by_udtype.len(), 1);
assert!(size_by_udtype.contains_key("&str"));
assert_eq!(size_by_udtype["&str"].0, 1);
// Try non-existent category
let size_by_udtype2 = dump.size_by_userdata(Some("non_existent_category"));
assert!(size_by_udtype2.is_empty());
// Remove category filter
let size_by_udtype_all = dump.size_by_userdata(None);
assert!(size_by_udtype.len() < size_by_udtype_all.len());
Ok(())
}
#[path = "luau/require.rs"]
mod require;
+58 -19
View File
@@ -1,7 +1,7 @@
use std::io::Result as IoResult;
use std::result::Result as StdResult;
use mlua::{Error, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};
use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};
fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
lua.load(r#"return require(...)"#).call(path)
@@ -11,9 +11,14 @@ fn run_require_pcall(lua: &Lua, path: impl IntoLua) -> Result<MultiValue> {
lua.load(r#"return pcall(require, ...)"#).call(path)
}
#[track_caller]
fn get_value<V: FromLua>(value: &Value, key: impl IntoLua) -> V {
value.as_table().unwrap().get(key).unwrap()
}
#[track_caller]
fn get_str(value: &Value, key: impl IntoLua) -> String {
value.as_table().unwrap().get::<String>(key).unwrap()
get_value(value, key)
}
#[test]
@@ -47,6 +52,16 @@ fn test_require_errors() {
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("require is not supported in this context"));
// RequireAliasThatDoesNotExist
let res = run_require(&lua, "@this.alias.does.not.exist");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias"));
// IllegalAlias
let res = run_require(&lua, "@");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
// Test throwing mlua::Error
struct MyRequire(TextRequirer);
@@ -171,40 +186,64 @@ fn test_require_without_config() {
assert!(res.is_table());
}
#[test]
fn test_require_with_config() {
fn test_require_with_config_inner(r#type: &str) {
let lua = Lua::new();
let base_path = format!("./tests/luau/require/{type}");
// RequirePathWithAlias
let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap();
let res = run_require(&lua, format!("{base_path}/src/alias_requirer")).unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequirePathWithAlias (case-insensitive)
let res2 = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer_uc").unwrap();
let res2 = run_require(&lua, format!("{base_path}/src/alias_requirer_uc")).unwrap();
assert_eq!("result from dependency", get_str(&res2, 1));
assert_eq!(res.to_pointer(), res2.to_pointer());
// RequirePathWithParentAlias
let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap();
let res = run_require(&lua, format!("{base_path}/src/parent_alias_requirer")).unwrap();
assert_eq!("result from other_dependency", get_str(&res, 1));
// RequirePathWithAliasPointingToDirectory
let res = run_require(
&lua,
"./tests/luau/require/with_config/src/directory_alias_requirer",
)
.unwrap();
let res = run_require(&lua, format!("{base_path}/src/directory_alias_requirer")).unwrap();
assert_eq!("result from subdirectory_dependency", get_str(&res, 1));
// RequireAliasThatDoesNotExist
let res = run_require(&lua, "@this.alias.does.not.exist");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias"));
// RequireChainedAliasesSuccess
let res = run_require(
&lua,
format!("{base_path}/chained_aliases/subdirectory/successful_requirer"),
)
.unwrap();
assert_eq!("result from inner_dependency", get_str(&get_value(&res, 1), 1));
assert_eq!("result from outer_dependency", get_str(&get_value(&res, 2), 1));
// IllegalAlias
let res = run_require(&lua, "@");
// RequireChainedAliasesFailureCyclic
let res = run_require(
&lua,
format!("{base_path}/chained_aliases/subdirectory/failing_requirer_cyclic"),
);
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
let err_msg = "error requiring module \"@cyclicentry\": detected alias cycle (@cyclic1 -> @cyclic2 -> @cyclic3 -> @cyclic1)";
assert!(res.unwrap_err().to_string().contains(err_msg));
// RequireChainedAliasesFailureMissing
let res = run_require(
&lua,
format!("{base_path}/chained_aliases/subdirectory/failing_requirer_missing"),
);
assert!(res.is_err());
let err_msg = "error requiring module \"@brokenchain\": @missing is not a valid alias";
assert!(res.unwrap_err().to_string().contains(err_msg));
}
#[test]
fn test_require_with_config() {
test_require_with_config_inner("with_config");
}
#[test]
fn test_require_with_config_luau() {
test_require_with_config_inner("with_config_luau");
}
#[cfg(all(feature = "async", not(windows)))]
@@ -0,0 +1,9 @@
{
"aliases":{
"outer": "./",
"cyclicentry": "@cyclic1",
"cyclic1": "@cyclic2",
"cyclic2": "@cyclic3",
"cyclic3": "@cyclic1"
}
}
@@ -0,0 +1 @@
return {"result from outer_dependency"}
@@ -0,0 +1,10 @@
{
"aliases":{
"passthroughinner": "./inner_dependency",
"passthroughouter": "@outer",
"dep": "@passthroughinner",
"outerdep": "@outer/outer_dependency",
"outerdir": "@passthroughouter",
"brokenchain": "@missing"
}
}
@@ -0,0 +1 @@
return require("@cyclicentry")
@@ -0,0 +1 @@
return require("@brokenchain")
@@ -0,0 +1 @@
return {"result from inner_dependency"}
@@ -0,0 +1,7 @@
local result = {}
table.insert(result, require("@dep"))
table.insert(result, require("@outerdep"))
table.insert(result, require("@outerdir/outer_dependency"))
return result
@@ -0,0 +1,8 @@
return {
luau = {
aliases = {
dep = "./this_should_be_overwritten_by_child_luaurc",
otherdep = "./src/other_dependency"
}
}
}
@@ -0,0 +1,11 @@
return {
luau = {
aliases = {
outer = "./",
cyclicentry = "@cyclic1",
cyclic1 = "@cyclic2",
cyclic2 = "@cyclic3",
cyclic3 = "@cyclic1"
}
}
}
@@ -0,0 +1 @@
return {"result from outer_dependency"}
@@ -0,0 +1,12 @@
return {
luau = {
aliases = {
passthroughinner = "./inner_dependency",
passthroughouter = "@outer",
dep = "@passthroughinner",
outerdep = "@outer/outer_dependency",
outerdir = "@passthroughouter",
brokenchain = "@missing"
}
}
}
@@ -0,0 +1 @@
return require("@cyclicentry")
@@ -0,0 +1 @@
return require("@brokenchain")
@@ -0,0 +1 @@
return {"result from inner_dependency"}
@@ -0,0 +1,7 @@
local result = {}
table.insert(result, require("@dep"))
table.insert(result, require("@outerdep"))
table.insert(result, require("@outerdir/outer_dependency"))
return result
@@ -0,0 +1,8 @@
return {
luau = {
aliases = {
dep = "./dependency",
subdir = "./subdirectory"
}
}
}
@@ -0,0 +1 @@
return require("@dep")
@@ -0,0 +1 @@
return require("@DeP")
@@ -0,0 +1 @@
return {"result from dependency"}
@@ -0,0 +1 @@
return(require("@subdir/subdirectory_dependency"))
@@ -0,0 +1 @@
return {"result from other_dependency"}
@@ -0,0 +1 @@
return require("@otherdep")
@@ -0,0 +1 @@
return {"result from subdirectory_dependency"}

Some files were not shown because too many files have changed in this diff Show More