Compare commits

..

62 Commits

Author SHA1 Message Date
Alex Orlenko 31efd0cf72 v0.10.5 2025-05-24 12:41:05 +01:00
Alex Orlenko 6b79e4cd76 Update CHANGELOG 2025-05-24 12:41:04 +01:00
Alex Orlenko ee2da72685 Revert default chunk name change 2025-05-24 12:40:58 +01:00
Alex Orlenko 6180a528a7 Revert = prefix from __mlua_async_poll 2025-05-24 12:30:32 +01:00
Alex Orlenko 69ef08d4d2 Switch back to Luau userdata destructors defined in mlua-sys v0.6 2025-05-24 11:35:25 +01:00
Alex Orlenko f7f04d5180 Revert mlua-sys to v0.6.8 2025-05-24 10:43:28 +01:00
Alex Orlenko e10623d658 Update __mlua_index/__mlua_newindex chunk names 2025-05-24 10:22:33 +01:00
Alex Orlenko c3ab89ba66 Fix tests 2025-05-24 10:22:26 +01:00
Alex Orlenko fa500639f0 Generate doc for lua_module macro using doc cfg instead of docsrs 2025-05-24 10:22:20 +01:00
Alex Orlenko 3d2574a855 Revert "Use c string literal where appropriate"
This reverts commit 46e949c184.
2025-05-24 10:19:19 +01:00
Alex Orlenko 003935db47 Revert "Change Lua library name constants from &str to *const c_char."
This reverts commit 272cfdb89b.
2025-05-24 10:18:55 +01:00
Alex Orlenko fbed171f90 Revert "Support 52-bit integers for Luau"
This reverts commit ef8b8e11ec.
2025-05-24 10:18:16 +01:00
Alex Orlenko 298d48f708 Update error message for UserDataRef 2025-05-06 10:24:53 +01:00
Alex Orlenko 4ff677f5fc v0.10.4 2025-05-05 15:43:00 +01:00
Alex Orlenko 5b920321dd mlua-sys: v0.7.0 2025-05-05 13:25:55 +01:00
Alex Orlenko 6bb99783e9 Update CHANGELOG 2025-05-05 13:16:26 +01:00
Alex Orlenko 547f82acc1 Bump TARGET_MLUA_LUAU_ABI_VERSION 2025-05-05 12:13:50 +01:00
Alex Orlenko 9ea98e3f1e Sync mlua-sys with the main branch 2025-05-05 12:02:13 +01:00
Alex Orlenko 0ad03bee98 Update README 2025-05-05 11:59:42 +01:00
Alex Orlenko ba7c331984 Update __mlua_async_poll chunk name 2025-05-05 01:02:15 +01:00
Alex Orlenko 53dea3166a Update Lua* dependencies 2025-05-05 01:01:57 +01:00
Alex Orlenko 129dec43ed Silence clippy false positives 2025-05-05 00:59:06 +01:00
Alex Orlenko a7f58e57ec Update scoped userdata mismatch tests 2025-05-05 00:59:00 +01:00
Alex Orlenko e6871525c4 Check that type passed to scoped userdata self argument is userdata.
If passed type is non-userdata we try to get a pointer (which will be null) that triggers an assertion.
Having a check also allow us to generate right error message.
Fixes #569
2025-05-05 00:58:46 +01:00
Alex Orlenko c4956dbf49 Make StateGuard automatically enabled inside callback_error_ext.
Remove manual usage of `StateGuard` in other places.
Closes #567
2025-05-05 00:57:23 +01:00
Alex Orlenko d0ea428e23 Add encode_empty_tables_as_array serialize option.
This will change the behaviour of encoding empty Lua tables into array instead of map.
2025-05-05 00:52:22 +01:00
Alex Orlenko 3e87d3ea9b Optimize Table::is_empty 2025-05-05 00:52:14 +01:00
krakow10 0f8bde4b4e Fix Reversed Comments & Typo (#560)
* Fix reversed comments

* Fix typos
2025-05-05 00:52:07 +01:00
Alex Orlenko b9c9dfbd03 mlua-sys: Update Luau to 0.671 2025-05-05 00:51:38 +01:00
Alex Orlenko 71824297f6 mlua-sys: Add definitions for Luau require library (since 0.669) 2025-05-05 00:50:39 +01:00
Alex Orlenko c0a8064228 Update references to mlua repo 2025-05-05 00:50:31 +01:00
Alex Orlenko 84efb8bf95 Fix warnings when testing documentation 2025-05-05 00:50:19 +01:00
Alex Orlenko cf687a6ac6 Make Lua::weak() method and WeakLua struct public.
This can be useful to prevent circular dependencies between Rust and Lua
or check that Lua instance is still alive.
2025-05-05 00:50:12 +01:00
Alex Orlenko ea85edb242 Update dependencies 2025-05-05 00:49:41 +01:00
Alex Orlenko 788175e0d6 Restrict access to Luau VM from UserData destructors.
It's unsafe to make almost any Lua calls when userdata destructor is running.
This can cause recursive GC run and crash.
See https://github.com/luau-lang/luau/pull/510 for some details.
2025-05-05 00:49:26 +01:00
Alex Orlenko 90ef25a6ee Prepare for custom (Luau) userdata destructors.
We need to add logic later to prevent calling any Lua functions when UserData destructor is running.
2025-05-05 00:49:17 +01:00
Alex Orlenko ac72f6acae Do not propagate collect_garbage flag when clonning Lua 2025-05-05 00:47:39 +01:00
Alex Orlenko 4444ac4ea3 Add AnyUserData::type_id method 2025-05-05 00:47:09 +01:00
Alex Orlenko 5f950e30ec Update Lua::poll_pending doc (still hidden) 2025-05-05 00:47:02 +01:00
Alex Orlenko 311aa5f6e1 Bump Luau to 0.667 2025-05-05 00:46:45 +01:00
Alex Orlenko 739d5f5262 Reduce stack operations when creating userdata 2025-05-05 00:46:28 +01:00
Alex Orlenko 375028e13f Add Chunk::name(), Chunk::environment() and Chunk::mode() functions.
They can be used to retrieve existing chunk params.
2025-05-05 00:46:20 +01:00
Alex Orlenko 46e949c184 Use c string literal where appropriate 2025-05-05 00:44:05 +01:00
Alex Orlenko 62b53e218c Move some userdata helpers from crate::util to crate::userdata::util 2025-05-05 00:43:12 +01:00
Alex Orlenko 0393406b9f Refactor userdata-wrappers feature.
Support borrowing underlying data in `UserDataRef` and `UserDataRefMut`.
2025-05-04 18:45:42 +01:00
Alex Orlenko 272cfdb89b Change Lua library name constants from &str to *const c_char.
It makes easier to pass them to Lua API functions.
2025-05-04 12:49:08 +01:00
Alex Orlenko 8e244a25ea Update test case 2025-05-04 12:48:47 +01:00
Alex Orlenko ef8b8e11ec Support 52-bit integers for Luau
Simply to float conversion (it actually never fails or goes out of range)
2025-05-04 12:48:34 +01:00
Alex Orlenko 2543414726 Fix clippy warnings 2025-05-04 12:47:30 +01:00
Alex Orlenko ee112568d6 Remove Roblox from references to Luau
Closes #537
2025-05-04 12:46:47 +01:00
Alex Orlenko 69d3ddec29 Remove (internal) borrow counter and use instead "locked" flag and strong reference counter 2025-05-04 12:46:35 +01:00
Alex Orlenko 5b0d811c5a Add Variadic to prelude 2025-05-04 12:31:13 +01:00
Alex Orlenko 19536db976 Add bstr/serde dependency if serialize feature flag is enabled 2025-05-04 12:30:52 +01:00
Alex Orlenko fc69551f87 Fix tests 2025-05-04 12:29:57 +01:00
Alex Orlenko a68b0b6905 Do not allow recursive warnings (Lua 5.4) 2025-05-04 12:29:51 +01:00
Alex Orlenko 1ec1cc9922 Fix tests 2025-05-04 12:28:40 +01:00
Joel Natividad fd68b033f0 Fix typos (#522)
* fix various typos in the codebase

* fix typos in CHANGELOG.md
2025-05-04 12:28:24 +01:00
Andrew Farkas c3817409a8 Fix version number in changelog (#521) 2025-05-04 12:28:04 +01:00
Alex Orlenko 863d8092d6 Imporove BorrowedStr/BorrowedBytes ergonomic.
Implement `FromLua` and `IntoLua` for these types to allow working with them directly.
2025-05-04 12:27:24 +01:00
Alex Orlenko 24b6ff3c20 Improve From/Into Lua char conversion 2025-05-04 12:18:28 +01:00
tk bc36261f5c impl FromLua/IntoLua for char (#516) 2025-05-04 12:18:22 +01:00
Alex Orlenko bcb9a4d220 Enable Thread::reset() for all Lua versions 2025-05-04 12:07:55 +01:00
229 changed files with 4035 additions and 13216 deletions
-68
View File
@@ -1,68 +0,0 @@
name: Documentation (main)
on:
push:
branches: [main]
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
name: Build Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- name: Build documentation
env:
RUSTDOCFLAGS: "--cfg docsrs"
run: |
cargo +nightly doc --no-deps \
--features "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
- name: Create index redirect
run: |
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redirecting to mlua documentation</title>
<meta http-equiv="refresh" content="0; URL=mlua/index.html">
<link rel="canonical" href="mlua/index.html">
</head>
<body>
<p>Redirecting to <a href="mlua/index.html">mlua documentation</a>...</p>
</body>
</html>' > target/doc/index.html
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: target/doc
deploy:
name: Deploy to GitHub Pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+43 -62
View File
@@ -9,12 +9,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
@@ -27,23 +27,39 @@ jobs:
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-latest' && matrix.lua != 'lua55' }}
if: ${{ matrix.os == 'ubuntu-latest' }}
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,serialize,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: [lua55, lua54, lua53, lua52, lua51, luajit]
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -56,7 +72,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
build_armv7_cross_ubuntu:
@@ -65,7 +81,7 @@ jobs:
needs: build
strategy:
matrix:
lua: [lua55, lua54, lua53, lua52, lua51]
lua: [lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -78,7 +94,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
test:
@@ -89,12 +105,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, nightly]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4]
lua: [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: aarch64-apple-darwin
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
@@ -107,14 +123,14 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
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"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Run compile tests (macos lua55)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua55' }}
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
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
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" --tests -- --ignored
shell: bash
test_with_sanitizer:
@@ -125,7 +141,7 @@ jobs:
matrix:
os: [ubuntu-latest]
rust: [nightly]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
@@ -138,8 +154,8 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with address sanitizer
run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash
env:
RUSTFLAGS: -Z sanitizer=address
@@ -152,7 +168,7 @@ jobs:
matrix:
os: [ubuntu-latest]
rust: [nightly]
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
@@ -165,7 +181,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with forced memory limit
run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
env:
RUSTFLAGS: --cfg=force_memory_limit
@@ -178,12 +194,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
rust: [stable]
lua: [lua55, lua54, lua53, lua52, lua51, luajit]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
target: x86_64-apple-darwin
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -224,7 +240,7 @@ jobs:
needs: build
strategy:
matrix:
lua: [lua55, lua54, lua53, lua52, lua51, luau]
lua: [lua54, lua53, lua52, lua51, luau]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -238,42 +254,7 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
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"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
rustfmt:
name: Rustfmt
@@ -290,7 +271,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
@@ -300,4 +281,4 @@ jobs:
- uses: giraffate/clippy-action@v1
with:
reporter: 'github-pr-review'
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
-22
View File
@@ -1,22 +0,0 @@
name: Spelling Check
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
env:
CLICOLOR: 1
jobs:
spelling:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@main
- name: Check spelling
uses: crate-ci/typos@v1.42.1
with:
config: ./typos.toml
+6 -119
View File
@@ -1,126 +1,13 @@
## v0.12.0-rc.2 (Jun 06, 2026)
## v0.10.5 (May 24th, 2025)
- Add `#[derive(UserData)]` and `#[mlua::userdata_impl]` macros
- Support thread create/resume/yield callbacks for all Lua versions (including Luau)
- Support `to_alias_override`/`to_alias_fallback` in `Require` trait (Luau)
- Prevent `XRc` overflow when dropping `RawLua` with foreign Lua state
- implement `Not` for `StdLib` (#699)
- Fix `String::to_pointer` return NULL in Lua <5.4
## v0.12.0-rc.1 (Apr 21, 2026)
- Rust 2024 edition
- Removed `Error::ToLuaConversionError` variant as it was unused (and not practically useful)
- New modules to group data types: `chunk`, `debug`, `error`, `function`, `table`, `string`, `state`, `thread`, `userdata`, `luau`
- Support `__todebugstring` metamethod for pretty formatting userdata value (for debugging)
- New `MaybeSync` trait that is required for userdata types
- Removed lifetime from `BorrowedStr` and `BorrowedBytes`
- New `Thread` methods: `is_resumable`, `is_running`, `is_finished`, `is_error`
- Added `Thread::state` to get raw Lua state pointer
- Luau `TextRequirer` is renamed to `FsRequirer`
- GC interface refactor: `Lua::gc_inc/Lua::gc_gen` is replaced with `gc_set_mode`
- Added `GcIncParams` and `GcGenParams` for GC tuning
- New `UserDataMethods::add_method_once` and `UserDataMethods::add_async_method_once`
- Initial Luau integer64 type support
- Changed interface of `Function::wrap/wrap_mut/wrap_async` to support any Error type
- Changed `AnyUserData::type_name` to return `LuaString` instead
- Added `UserDataOwned<T>` wrapper to take ownership of userdata `T` and implements `FromLua`
## 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>`
- Fix handling Windows paths with drive letter in Luau require (#623)
- Make Luau registered aliases ascii case-insensitive (#620)
- Fix deserializing negative zeros `-0.0` (#618)
## v0.11.1 (Jul 15, 2025)
- Fixed bug exhausting Lua auxiliary stack and leaving it without reserve (#615)
- `Lua::push_c_function` now correctly handles OOM for Lua 5.1 and Luau
## v0.11.0 (Jul 14, 2025)
Changes since v0.11.0-beta.3
- Allow linking external Lua libraries in a build script (e.g. pluto) using `external` mlua-sys feature flag
- `Lua::inspect_stack` takes a callback with `&Debug` argument, instead of returning `Debug` directly
- Added `Debug::function` method to get function running at a given level
- `Debug::curr_line` is deprecated in favour of `Debug::current_line` that returns `Option<usize>`
- Added `Lua::set_globals` method to replace global environment
- `Table::set_metatable` now returns `Result<()>` (this operation can fail in sandboxed Luau mode)
- `impl ToString` replaced with `Into<StdString>` in `UserData` registration
- `Value::as_str` and `Value::as_string_lossy` methods are deprecated (as they are non-idiomatic)
- Bugfixes and improvements
## v0.11.0-beta.3 (Jun 23, 2025)
- Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc)
- `Function::deep_clone` now returns `Result<Function>` as this operation can trigger memory errors
- Luau "Require" resolves included Lua files relative to the current directory (#605)
- Fixed bug when finalizing `AsyncThread` on drop (`call_async` methods family)
## v0.11.0-beta.2 (Jun 12, 2025)
- Lua 5.4 updated to 5.4.8
- Terminate Rust `Future` when `AsyncThread` is dropped (without relying on Lua GC)
- Added `loadstring` function to Luau
- Make `AsChunk` trait dyn-friendly
- Luau `Require` trait synced with Luau 0.674
- Luau `Require` trait methods now can return `Error` variant (in `NavigateError` enum)
- Added `__type` to `Error`'s userdata metatable (for `typeof` function)
- `parking_log/send_guard` is moved to `userdata-wrappers` feature flag
- New `serde` feature flag to replace `serialize` (the old one is still available)
## v0.11.0-beta.1 (May 7th, 2025)
- New "require-by-string" for Luau (with `Require` trait and async support)
- Added `Thread::resume_error` support for Luau
- 52 bit integers support for Luau (this is a breaking change)
- New features for Luau compiler (constants, disabled builtins, known members)
- `AsyncThread<A, R>` changed to `AsyncThread<R>` (`A` pushed to stack immediately)
- Lifetime `'a` moved from `AsChunk<'a>` to `AsChunk::source where Self: 'a`
- `Lua::scope` pass `&Scope` instead of `&mut Scope` to closure
- Added global hooks support (Lua 5.1+)
- Added per-thread hooks support (Lua 5.1+)
- `Lua::init_from_ptr` renamed to `Lua::get_or_init_from_ptr` and returns `&Lua`
- `Lua:load_from_function` is deprecated (this is `register_module` now)
- Added `Lua::register_module` and `Lua::preload_module`
- mlua-sys is back to 0.6.x (Luau 0.663)
- Reverted: Trigger abort when Luau userdata destructors are panic (requires new mlua-sys)
- Reverted: Added large (52bit) integers support for Luau (breaking change)
## v0.10.4 (May 5th, 2025)
_yanked_ because of semver-breaking changes
- Luau updated to 0.672
- New serde option `encode_empty_tables_as_array` to serialize empty tables as arrays
- Added `WeakLua` and `Lua::weak()` to create weak references to Lua state
+28 -30
View File
@@ -1,9 +1,9 @@
[package]
name = "mlua"
version = "0.12.0-rc.2" # remember to update mlua_derive
version = "0.10.5" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.88"
edition = "2024"
rust-version = "1.79.0"
edition = "2021"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
@@ -11,12 +11,12 @@ keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
description = """
High level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
High level bindings to Lua 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 = ["lua55", "vendored", "async", "send", "serde", "macros"]
features = ["lua54", "vendored", "async", "send", "serialize", "macros"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
@@ -26,31 +26,27 @@ members = [
]
[features]
lua55 = ["ffi/lua55"]
lua54 = ["ffi/lua54"]
lua53 = ["ffi/lua53"]
lua52 = ["ffi/lua52"]
lua51 = ["ffi/lua51"]
luajit = ["ffi/luajit"]
luajit52 = ["luajit", "ffi/luajit52"]
luau = ["ffi/luau"]
luau = ["ffi/luau", "dep:libloading"]
luau-jit = ["luau", "ffi/luau-codegen"]
luau-vector4 = ["luau", "ffi/luau-vector4"]
vendored = ["ffi/vendored"]
module = ["mlua_derive", "ffi/module"]
async = ["dep:futures-util"]
send = ["error-send"]
send = ["parking_lot/send_guard", "error-send"]
error-send = []
serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
macros = ["mlua_derive/macros", "dep:inventory"]
serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
macros = ["mlua_derive/macros"]
anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = ["parking_lot/send_guard"]
# deprecated features
serialize = ["serde"]
userdata-wrappers = []
[dependencies]
mlua_derive = { version = "=0.12.0-rc.1", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0"
num-traits = { version = "0.2.14" }
@@ -61,27 +57,29 @@ erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
inventory = { version = "0.3", optional = true }
libc = "0.2"
rustversion = "1.0"
ffi = { package = "mlua-sys", version = "0.11.0-rc.1", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.6.8", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
[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_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.13", features = ["json"] }
tempfile = "3"
criterion = { version = "0.8", features = ["async_tokio"] }
rustyline = "18.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
rustyline = "15.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
@@ -95,7 +93,7 @@ required-features = ["async"]
[[bench]]
name = "serde"
harness = false
required-features = ["serde"]
required-features = ["serialize"]
[[example]]
name = "async_http_client"
@@ -103,7 +101,7 @@ required-features = ["async", "macros"]
[[example]]
name = "async_http_reqwest"
required-features = ["async", "serde", "macros"]
required-features = ["async", "serialize", "macros"]
[[example]]
name = "async_http_server"
@@ -118,8 +116,8 @@ name = "guided_tour"
required-features = ["macros"]
[[example]]
name = "serde"
required-features = ["serde"]
name = "serialize"
required-features = ["serialize"]
[[example]]
name = "userdata"
+45 -45
View File
@@ -17,16 +17,18 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
## The main branch is the development version of `mlua`. Please see the [v0.11](https://github.com/mlua-rs/mlua/tree/v0.11) branch for the stable versions of `mlua`.
> **Note**
>
> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md).
`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.
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
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.
Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows to write native Lua modules in Rust as well as use 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).
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions
[Luau]: https://luau.org
@@ -35,10 +37,9 @@ WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target f
### Feature flags
`mlua` uses feature flags to reduce the number of dependencies and compiled code, and allow choosing only the required set of features.
`mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only 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
@@ -48,17 +49,16 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `luau`: enable [Luau] support (auto vendored mode)
* `luau-jit`: enable [Luau] support with JIT backend.
* `luau-vector4`: enable [Luau] support with 4-dimensional vector.
* `vendored`: build static Lua(JIT) libraries from sources during `mlua` compilation using [lua-src] or [luajit-src]
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
* `module`: enable module mode (building loadable `cdylib` library for Lua)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
* `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `error-send`: make `mlua:Error: Send + Sync`
* `serde`: add serialization and deserialization support to `mlua` types using [serde]
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `macros`: enable procedural macros (such as `chunk!`)
* `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
@@ -76,7 +76,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
`mlua` supports async/await for all Lua versions including Luau.
This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and requires running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
@@ -91,18 +91,18 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6
cargo run --example async_http_client --features=lua54,async,macros
# async http client (reqwest)
cargo run --example async_http_reqwest --features=lua54,async,macros,serde
cargo run --example async_http_reqwest --features=lua54,async,macros,serialize
# async http server
cargo run --example async_http_server --features=lua54,async,macros,send
curl -v http://localhost:3000
```
### Serde support
### Serialization (serde) support
With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for `mlua::Value` (including `UserData` support).
With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support).
[Example](examples/serde.rs)
[Example](examples/serialize.rs)
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
@@ -112,28 +112,28 @@ With the `serde` feature flag enabled, `mlua` allows you to serialize/deserializ
You have to enable one of the features: `lua54`, `lua53`, `lua52`, `lua51`, `luajit(52)` or `luau`, according to the chosen Lua version.
By default `mlua` uses `pkg-config` to find Lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes it may be preferable to use a custom Lua library.
To achieve this, mlua supports the `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
By default `mlua` uses `pkg-config` tool to find lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes could be more preferable to use a custom lua library.
To achieve this, mlua supports `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
`LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
An example of how to use them:
An example how to use them:
``` sh
my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build
```
`mlua` also supports vendored Lua/LuaJIT using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
`mlua` also supports vendored lua/luajit using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
[luajit-src](https://crates.io/crates/luajit-src).
Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with `mlua`.
### Standalone mode
In standalone mode, `mlua` allows adding scripting support to your application with a properly configured Lua runtime to ensure safety and soundness.
In a standalone mode `mlua` allows to add to your application scripting support with a gently configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml`:
Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.11", features = ["lua54", "vendored"] }
mlua = { version = "0.10", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -157,21 +157,21 @@ fn main() -> LuaResult<()> {
```
### Module mode
In module mode, `mlua` allows creating a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to the unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
[Example](examples/module)
Add to `Cargo.toml`:
Add to `Cargo.toml` :
``` toml
[lib]
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.11", features = ["lua54", "module"] }
mlua = { version = "0.10", features = ["lua54", "module"] }
```
`lib.rs`:
`lib.rs` :
``` rust
use mlua::prelude::*;
@@ -214,14 +214,14 @@ rustflags = [
```
On Linux you can build modules normally with `cargo build --release`.
On Windows the target module will be linked with the `lua5x.dll` library (depending on your feature flags).
On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags).
Your main application should provide this library.
Module builds don't require Lua binaries or headers to be installed on the system.
Module builds don't require Lua lib or headers to be installed on the system.
### Publishing to luarocks.org
There is a LuaRocks build backend for mlua modules: [`luarocks-build-rust-mlua`].
There is a LuaRocks build backend for mlua modules [`luarocks-build-rust-mlua`].
Modules written in Rust and published to luarocks:
- [`decasify`](https://github.com/alerque/decasify)
@@ -234,10 +234,10 @@ Modules written in Rust and published to luarocks:
## Safety
One of `mlua`'s goals is to provide a *safe* API between Rust and Lua.
Every place where the Lua C API may trigger an error longjmp is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack.
There is overhead associated with this safety.
One of the `mlua` goals is to provide *safe* API between Rust and Lua.
Every place where the Lua C API may trigger an error longjmp in any way is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack,
and there is overhead associated with this safety.
Unfortunately, `mlua` does not provide absolute safety even without using `unsafe` .
This library contains a huge amount of unsafe code. There are almost certainly bugs still lurking in this library!
@@ -245,8 +245,8 @@ It is surprisingly, fiendishly difficult to use the Lua C API without the potent
## Panic handling
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics can then be
resumed by returning or propagating the Lua error to Rust code.
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics could be
resumed then by returning or propagating the Lua error to Rust code.
For example:
``` rust
@@ -265,16 +265,16 @@ let _ = lua.load(r#"
unreachable!()
```
Optionally, `mlua` can disable Rust panic catching in Lua via `pcall`/`xpcall` and automatically resume
Optionally `mlua` can disable Rust panics catching in Lua via `pcall`/`xpcall` and automatically resume
them across the Lua API boundary. This is controlled via `LuaOptions` and done by wrapping the Lua `pcall`/`xpcall`
functions to prevent catching errors that are wrapped Rust panics.
functions on a way to prevent catching errors that are wrapped Rust panics.
`mlua` should also be panic safe in another way as well, which is that any `Lua` instances or handles
remain usable after a user generated panic, and such panics should not break internal invariants or
remains usable after a user generated panic, and such panics should not break internal invariants or
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 bugs.
Below is a list of `mlua` behaviors that should be considered a bug.
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.
@@ -287,12 +287,12 @@ If you encounter them, a bug report would be very welcome:
## Sandboxing
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in a controlled environment.
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in controlled environment.
`mlua` provides the `Lua::sandbox` method for enabling sandbox mode (Luau only).
`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only).
[Luau Sandboxing]: https://luau.org/sandbox
## License
This project is licensed under the [MIT license](LICENSE).
This project is licensed under the [MIT license](LICENSE)
+1 -55
View File
@@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use tokio::runtime::Runtime;
use tokio::task;
@@ -128,22 +128,6 @@ 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();
@@ -366,42 +350,6 @@ 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 {
@@ -451,7 +399,6 @@ criterion_group! {
table_traversal_pairs,
table_traversal_for_each,
table_traversal_sequence,
table_ref_clone,
function_create,
function_call_sum,
@@ -466,7 +413,6 @@ criterion_group! {
userdata_create,
userdata_call_index,
userdata_call_method,
userdata_call_method_complex,
userdata_async_call_method,
}
+1 -1
View File
@@ -1,6 +1,6 @@
use std::time::Duration;
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use mlua::prelude::*;
-149
View File
@@ -1,149 +0,0 @@
Implements the [`UserData`] trait for a Rust type.
This derive macro generates an implementation of [`UserData`] that exposes
struct fields to Lua and integrates with `#[mlua::userdata_impl]` for
registering methods.
Named fields are exposed as readable and writable fields in Lua by default.
Use `#[lua(...)]` on individual fields or methods to control how they are
registered.
```rust,ignore
use mlua::{Lua, Result, UserData};
#[derive(UserData)]
struct Rectangle {
length: u32,
width: u32,
}
#[mlua::userdata_impl]
impl Rectangle {
#[lua(infallible)]
fn new(length: u32, width: u32) -> Self {
Self { length, width }
}
#[lua(getter, name = "area", infallible)]
fn calculate_area(&self) -> u32 {
self.length * self.width
}
fn diagonal(&self) -> Result<f64> {
Ok(((self.length.pow(2) + self.width.pow(2)) as f64).sqrt())
}
}
```
# Struct field attributes
Each named field can be annotated with `#[lua(...)]`:
| Attribute | Description |
| -------------- | ----------------------------------------------------- |
| `get` | Expose a getter. The field becomes readable from Lua. |
| `set` | Expose a setter. The field becomes writable from Lua. |
| `skip` | Do not expose this field. |
| `name = "..."` | Override the Lua-facing name for the field. |
If neither `get` nor `set` is specified, both are enabled.
Fields exposed as readable (via `get` or by default) must implement `Clone`.
The generated getter clones the field value when accessed from Lua.
# Methods registration
Use `#[mlua::userdata_impl]` on an `impl` block to register methods,
metamethods, and constants. All public items in the block are registered
automatically.
## Method detection
The receiver type determines how a method is registered:
| Receiver | Registration |
| ----------- | ----------------- |
| `&self` | `add_method` |
| `&mut self` | `add_method_mut` |
| `self` | `add_method_once` |
| None | `add_function` |
A first parameter of type `&Lua` (or `&mlua::Lua`) is treated as the
Lua state reference and passed automatically.
## Method and constant attributes
Each item in the impl block can be annotated with `#[lua(...)]`:
| Attribute | Applies to | Description |
| -------------- | ------------------ | -------------------------------------------------------------------------------------- |
| `skip` | Methods, constants | Exclude this item from registration. |
| `name = "..."` | Methods, constants | Override the Lua-facing name. |
| `infallible` | Methods | Wrap the return value in `Ok(...)`. |
| `getter` | Methods | Register as a field getter. Must take `&self` and no Lua-facing arguments. |
| `setter` | Methods | Register as a field setter. Must take `&[mut] self` and one value argument. |
| `field` | Methods, constants | Register as a static field. Methods must take no receiver and no Lua-facing arguments. |
| `meta` | Methods, constants | Register as a metamethod. May be combined with `field` for meta static fields. |
At most one of `getter`, `setter`, `field` may be specified on a method.
## Constants
Constants in an `#[mlua::userdata_impl]` block are registered as static
fields:
```rust,ignore
#[mlua::userdata_impl]
impl MyType {
const VERSION: &str = "1.0";
const COUNT: u32 = 42;
}
```
Use `#[lua(meta)]` on a constant to register it as a meta static field.
## Metamethods
Annotate a method with `#[lua(meta)]` to register it as a Lua metamethod.
The metamethod name is inferred from the function name when it starts with
`__`. Use `name = "..."` to specify the name explicitly.
```rust,ignore
#[mlua::userdata_impl]
impl MyType {
#[lua(meta, infallible)]
fn __add(&self, other: &Self) -> Self { ... }
#[lua(meta, name = "__call", infallible)]
fn construct(lua: &Lua, value: u32) -> Self { ... }
}
```
## Reference parameters
Reference parameters in method signatures are automatically mapped to
the appropriate callback wrapper types:
| Parameter type | Callback type |
| -------------- | ------------------- |
| `&str` | `BorrowedStr` |
| `&[u8]` | `BorrowedBytes` |
| `&T` | `UserDataRef<T>` |
| `&mut T` | `UserDataRefMut<T>` |
## Async methods
Async methods are supported and registered via the corresponding async
variants (`add_async_method`, `add_async_method_mut`, etc.).
# Limitations
Generics are not supported. Wrap a generic type in a concrete newtype
instead.
Union types cannot derive `UserData`.
Enum types are accepted but generate no field registrations. All method
registration must be done via `#[mlua::userdata_impl]`.
[`UserData`]: crate::UserData
-52
View File
@@ -1,52 +0,0 @@
Create a type that implements [`AsChunk`] and can capture Rust variables.
This macro allows to write Lua code directly in Rust code.
Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
User's Rust types needs to implement [`UserData`] or [`IntoLua`] traits.
Captured variables are **moved** into the chunk.
```rust
use mlua::{Lua, Result, chunk};
fn main() -> Result<()> {
let lua = Lua::new();
let name = "Rustacean";
lua.load(chunk! {
print("hello, " .. $name)
}).exec()
}
```
## Syntax issues
Since the Rust tokenizer will tokenize Lua code, this imposes some restrictions.
The main thing to remember is:
- Use double quoted strings (`""`) instead of single quoted strings (`''`).
(Single quoted strings only work if they contain a single character, since in Rust,
`'a'` is a character literal).
- Using Lua comments `--` is not desirable in **stable** Rust and can have bad side effects.
This is because procedural macros have Line/Column information available only in
**nightly** Rust. Instead, Lua chunks represented as a big single line of code in stable Rust.
As workaround, Rust comments `//` can be used.
Other minor limitations:
- Certain escape codes in string literals don't work. (Specifically: `\a`, `\b`, `\f`, `\v`,
`\123` (octal escape codes), `\u`, and `\U`).
These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`.
- The `//` (floor division) operator is unusable, as its start a comment.
Everything else should work.
[`AsChunk`]: crate::chunk::AsChunk
[`UserData`]: crate::UserData
[`IntoLua`]: crate::IntoLua
-41
View File
@@ -1,41 +0,0 @@
Registers Lua module entrypoint.
You can register multiple entrypoints as required.
```rust,ignore
use mlua::{Lua, Result, Table};
#[mlua::lua_module]
fn my_module(lua: &Lua) -> Result<Table> {
let exports = lua.create_table()?;
exports.set("hello", "world")?;
Ok(exports)
}
```
Internally in the code above the compiler defines C function `luaopen_my_module`.
You can also pass options to the attribute:
* name - name of the module, defaults to the name of the function
```rust,ignore
#[mlua::lua_module(name = "alt_module")]
fn my_module(lua: &Lua) -> Result<Table> {
...
}
```
* skip_memory_check - skip memory allocation checks for some operations.
In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory
limits or not. As a result, some operations that require memory allocation run in protected
mode. Setting this attribute will improve performance of such operations with risk of having
uncaught exceptions and memory leaks.
```rust,ignore
#[mlua::lua_module(skip_memory_check)]
fn my_module(lua: &Lua) -> Result<Table> {
...
}
```
+3 -3
View File
@@ -1,6 +1,6 @@
## mlua v0.10 release notes
The v0.10 version of mlua has a goal to improve the user experience while keeping the same performance and safety guarantees.
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
This document highlights the most notable features. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
@@ -40,7 +40,7 @@ assert_eq!(lua.globals().get::<i32>("i")?, 20);
Under the hood, to synchronize access to the Lua state, mlua uses [`ReentrantMutex`] which can be recursively locked by a single thread. Only one thread can execute Lua code at a time, but it's possible to share Lua values between threads.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and is not supported in module mode.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and does not supported in module mode.
[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html
@@ -144,7 +144,7 @@ The following `Scope` methods were changed:
Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`.
`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data.
In mlua v0.9 this could cause a read-after-free bug in some edge cases.
In mlua v0.9 this can cause read-after-free bug in some edge cases.
To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced:
+4 -4
View File
@@ -152,9 +152,9 @@ It will automatically trigger JIT compilation for new Lua chunks. To disable it,
#### 1. Better error reporting
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported an error message without any context or reference to the particular argument.
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported a error message without any context or reference to the particular argument.
In v0.9 it reports an error message with the argument index and expected type:
In v0.9 it reports a error message with the argument index and expected type:
```rust
let func = lua.create_function(|_, _a: i32| Ok(()))?;
@@ -327,7 +327,7 @@ Under the hood a new function `luaopen_alt_module` will be created for the Lua m
- `skip_memory_check` - skip memory allocation checks for some operations.
In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory limits or not. As a result, some operations that require memory allocation run in
In module mode, mlua runs in unknown environment and cannot say are there any memory limits or not. As result, some operations that require memory allocation runs in
protected mode. Setting this attribute will improve performance of such operations with risk of having uncaught exceptions and memory leaks.
#### Improved Windows target
@@ -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
`lua5x.dll` depending on the enabled Lua version.
`lua54.dll`/`lua53.dll`/`lua52.dll`/`lua51.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 -1
View File
@@ -5,7 +5,7 @@ use hyper::body::Incoming;
use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor;
use mlua::{ExternalResult, Lua, Result, UserData, UserDataMethods, chunk};
use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods};
struct BodyReader(Incoming);
+1 -1
View File
@@ -1,4 +1,4 @@
use mlua::{ExternalResult, Lua, LuaSerdeExt, Result, Value, chunk};
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
+1 -1
View File
@@ -11,7 +11,7 @@ use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use mlua::{Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods, chunk};
use mlua::{chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods};
/// Wrapper around incoming request that implements UserData
struct LuaRequest(SocketAddr, Request<Incoming>);
+1 -1
View File
@@ -4,7 +4,7 @@ use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use mlua::{BString, Function, Lua, UserData, UserDataMethods, chunk};
use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods};
struct LuaTcpStream(TcpStream);
+1 -1
View File
@@ -1,7 +1,7 @@
use std::f32;
use std::iter::FromIterator;
use mlua::{FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic, chunk};
use mlua::{chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic};
fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
-1
View File
@@ -10,7 +10,6 @@ crate-type = ["cdylib"]
[workspace]
[features]
lua55 = ["mlua/lua55"]
lua54 = ["mlua/lua54"]
lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
+8 -10
View File
@@ -20,16 +20,14 @@ fn main() {
match lua.load(&line).eval::<MultiValue>() {
Ok(values) => {
editor.add_history_entry(line).unwrap();
if values.len() > 0 {
println!(
"{}",
values
.iter()
.map(|value| format!("{:#?}", value))
.collect::<Vec<_>>()
.join("\t")
);
}
println!(
"{}",
values
.iter()
.map(|value| format!("{:#?}", value))
.collect::<Vec<_>>()
.join("\t")
);
break;
}
Err(Error::SyntaxError {
+27 -28
View File
@@ -1,46 +1,45 @@
use mlua::{Lua, Result, UserData, chunk};
use mlua::{chunk, Lua, MetaMethod, Result, UserData};
#[derive(Default, UserData)]
#[derive(Default)]
struct Rectangle {
length: u32,
width: u32,
}
#[mlua::userdata_impl]
impl Rectangle {
const NAME: &str = "Rectangle";
#[lua(infallible)]
fn new(length: u32, width: u32) -> Self {
Self { length, width }
impl UserData for Rectangle {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("length", |_, this| Ok(this.length));
fields.add_field_method_set("length", |_, this, val| {
this.length = val;
Ok(())
});
fields.add_field_method_get("width", |_, this| Ok(this.width));
fields.add_field_method_set("width", |_, this, val| {
this.width = val;
Ok(())
});
}
#[lua(getter, name = "area", infallible)]
fn calculate_area(&self) -> u32 {
self.length * self.width
}
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("area", |_, this, ()| Ok(this.length * this.width));
methods.add_method("diagonal", |_, this, ()| {
Ok((this.length.pow(2) as f64 + this.width.pow(2) as f64).sqrt())
});
fn diagonal(&self) -> Result<f64> {
Ok((self.length.pow(2) as f64 + self.width.pow(2) as f64).sqrt())
}
// Constructor via `__call` metamethod
#[lua(meta, infallible)]
fn __call(length: u32, width: u32) -> Self {
Rectangle::new(length, width)
// Constructor
methods.add_meta_function(MetaMethod::Call, |_, ()| Ok(Rectangle::default()));
}
}
fn main() -> Result<()> {
let lua = Lua::new();
lua.globals().set("Rectangle", lua.create_proxy::<Rectangle>()?)?;
let rectangle = Rectangle::default();
lua.load(chunk! {
local rect = Rectangle(10, 5)
rect.width = rect.width + 5
rect.length = rect.length + 5
assert(rect.NAME == "Rectangle")
assert(rect.area == 150)
assert(math.floor(rect:diagonal()) == 18)
local rect = $rectangle()
rect.width = 10
rect.length = 5
assert(rect:area() == 50)
assert(rect:diagonal() - 11.1803 < 0.0001)
})
.exec()
}
+8 -11
View File
@@ -1,9 +1,9 @@
[package]
name = "mlua-sys"
version = "0.11.0-rc.1"
version = "0.6.8"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.88"
edition = "2024"
rust-version = "1.71"
edition = "2021"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua-sys"
readme = "README.md"
@@ -12,15 +12,14 @@ license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
"""
[package.metadata.docs.rs]
features = ["lua55", "vendored"]
features = ["lua54", "vendored"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
lua55 = []
lua54 = []
lua53 = []
lua52 = []
@@ -31,19 +30,17 @@ luau = ["luau0-src"]
luau-codegen = ["luau"]
luau-vector4 = ["luau"]
vendored = ["lua-src", "luajit-src"]
external = []
module = []
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 550.1.0, < 550.2.0", optional = true }
luajit-src = { version = ">= 210.7.0, < 210.8.0", optional = true }
luau0-src = { version = "0.20.0", optional = true }
lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true }
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
luau0-src = { version = "0.12.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+1 -2
View File
@@ -1,9 +1,8 @@
# mlua-sys
Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including [LuaJIT]) and [Luau].
Low level (FFI) bindings to Lua 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
+11 -7
View File
@@ -31,16 +31,18 @@ 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");
@@ -52,7 +54,9 @@ 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,9 +1,6 @@
#![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);
+7 -9
View File
@@ -1,21 +1,19 @@
cfg_if::cfg_if! {
if #[cfg(all(feature = "lua55", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
if #[cfg(all(feature = "lua54", not(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua54", not(any(feature = "lua55", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
} else if #[cfg(all(feature = "lua53", not(any(feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua53", not(any(feature = "lua55", feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
} else if #[cfg(all(feature = "lua52", not(any(feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua52", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] {
} else if #[cfg(all(feature = "lua51", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua51", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] {
} else if #[cfg(all(feature = "luajit", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] {
include!("main_inner.rs");
} 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"))))] {
} else if #[cfg(all(feature = "luau", not(any(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: lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau");
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau");
}
}
}
+12 -19
View File
@@ -14,29 +14,22 @@ fn main() {
#[cfg(all(feature = "luau", feature = "module", windows))]
compile_error!("Luau does not support `module` mode on Windows");
#[cfg(any(
all(feature = "vendored", any(feature = "external", feature = "module")),
all(feature = "external", any(feature = "vendored", feature = "module")),
all(feature = "module", any(feature = "vendored", feature = "external"))
))]
compile_error!("`vendored`, `external` and `module` features are mutually exclusive");
#[cfg(all(feature = "module", feature = "vendored"))]
compile_error!("`vendored` and `module` features are mutually exclusive");
println!("cargo:rerun-if-changed=build");
// Check if compilation and linking is handled by external crate
if cfg!(not(feature = "external")) {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "windows" && cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
return;
}
println!("cargo:rustc-cfg=raw_dylib");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "windows" && cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
return;
}
#[cfg(not(feature = "module"))]
find::probe_lua();
println!("cargo:rustc-cfg=raw_dylib");
}
#[cfg(not(feature = "module"))]
find::probe_lua();
}
+23 -33
View File
@@ -1,16 +1,12 @@
//! Low level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
#![allow(non_camel_case_types, non_snake_case)]
#![allow(non_camel_case_types, non_snake_case, dead_code)]
#![allow(clippy::missing_safety_doc)]
#![allow(unsafe_op_in_unsafe_fn)]
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::os::raw::c_int;
#[cfg(any(feature = "lua55", doc))]
pub use lua55::*;
#[cfg(any(feature = "lua54", doc))]
pub use lua54::*;
@@ -26,7 +22,7 @@ pub use lua51::*;
#[cfg(any(feature = "luau", doc))]
pub use luau::*;
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 255;
@@ -43,22 +39,14 @@ pub const LUA_MAX_UPVALUES: c_int = 200;
#[doc(hidden)]
pub const LUA_TRACEBACK_STACK: c_int = 11;
// 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(
// 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(
target_arch = "x86",
target_arch = "arm",
target_arch = "m68k",
target_arch = "csky",
target_arch = "loongarch32",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "powerpc",
@@ -66,11 +54,12 @@ pub const SYS_MIN_ALIGN: usize = if cfg!(any(
target_arch = "sparc",
target_arch = "wasm32",
target_arch = "hexagon",
target_arch = "riscv32",
target_arch = "xtensa",
)) {
8
} else if cfg!(any(
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 = "x86_64",
target_arch = "aarch64",
target_arch = "arm64ec",
@@ -81,19 +70,20 @@ pub const SYS_MIN_ALIGN: usize = if cfg!(any(
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
)) {
16
} else {
panic!("no value for SYS_MIN_ALIGN")
};
))]
#[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;
#[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;
+58 -62
View File
@@ -2,7 +2,6 @@
//!
//! 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};
@@ -21,8 +20,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
}
}
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
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
unsafe fn compat53_countlevels(L: *mut lua_State) -> c_int {
let mut ar: lua_Debug = mem::zeroed();
@@ -55,7 +54,11 @@ 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() {
@@ -85,10 +88,11 @@ 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 {
// 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
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, ".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
return 1;
}
}
@@ -97,20 +101,13 @@ 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, L1: *mut lua_State, ar: *mut lua_Debug) -> c_int {
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, ar: *mut lua_Debug) -> c_int {
let top = lua_gettop(L);
lua_getinfo(L1, cstr!("f"), ar); // push function
lua_xmove(L1, L, 1); // and move onto L
lua_getinfo(L, cstr!("f"), ar); // push function
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_settop(L, top + 1); // remove pushed values
lua_pop(L, 2); // remove pushed values
1
} else {
lua_settop(L, top); // remove function and global table
@@ -118,23 +115,27 @@ unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, L1: *mut lua_State, ar:
}
}
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);
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);
} 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 {
// 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);
lua_pushliteral(L, "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, "?");
}
} else {
lua_pushliteral(L, c"?");
lua_pushfstring(
L,
cstr!("function <%s:%d>"),
(*ar).short_src.as_ptr(),
(*ar).linedefined,
);
}
}
@@ -185,8 +186,7 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx);
// Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
return 1;
}
}
@@ -377,7 +377,7 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char)
if !msg.is_null() {
luaL_error(L, cstr!("stack overflow (%s)"), msg);
} else {
lua_pushliteral(L, c"stack overflow");
lua_pushliteral(L, "stack overflow");
lua_error(L);
}
}
@@ -458,36 +458,32 @@ 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);
#[rustfmt::skip]
let mut limit = if numlevels - level > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { COMPAT53_LEVELS1 } else { -1 };
let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 {
COMPAT53_LEVELS1
} else {
0
};
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
}
lua_pushliteral(L, c"stack traceback:");
lua_pushliteral(L, "stack traceback:");
while lua_getstack(L1, level, &mut ar) != 0 {
if limit == 0 {
// too many levels?
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!("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...)"));
}
}
level += 1;
limit -= 1;
if level == mark {
// too many levels?
lua_pushliteral(L, "\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} 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_pushliteral(L, " in ");
compat53_pushfuncname(L, &mut ar);
lua_concat(L, lua_gettop(L) - top);
}
}
lua_concat(L, lua_gettop(L) - top);
}
@@ -497,16 +493,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, c"nil");
lua_pushliteral(L, "nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, c"false");
lua_pushliteral(L, "false");
} else {
lua_pushliteral(L, c"true");
lua_pushliteral(L, "true");
}
}
t => {
+6 -58
View File
@@ -18,7 +18,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -61,7 +61,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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);
@@ -107,6 +107,8 @@ 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))
@@ -136,62 +138,8 @@ 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_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: luaL_opt
//
// Generic Buffer Manipulation
// TODO: 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);
}
+11 -13
View File
@@ -1,6 +1,5 @@
//! 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_void};
use std::ptr;
@@ -84,13 +83,13 @@ pub type lua_Reader =
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)
/// Type for memory-allocation functions
#[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;
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -220,7 +219,7 @@ pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
@@ -228,7 +227,7 @@ unsafe extern "C-unwind" {
// Miscellaneous functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
#[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;
@@ -270,10 +269,7 @@ pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
lua_pushcclosure(L, f, 0)
}
#[inline(always)]
pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize {
lua_objlen(L, i)
}
// TODO: lua_strlen
#[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
@@ -316,8 +312,10 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring_(L, s.as_ptr());
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
}
#[inline(always)]
@@ -373,7 +371,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
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 = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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;
+13 -13
View File
@@ -1,27 +1,27 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use super::lua::lua_State;
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
#[cfg(feature = "luajit")]
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit");
pub const LUA_BITLIBNAME: &str = "bit";
#[cfg(feature = "luajit")]
pub const LUA_JITLIBNAME: *const c_char = cstr!("jit");
pub const LUA_JITLIBNAME: &str = "jit";
#[cfg(feature = "luajit")]
pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi");
pub const LUA_FFILIBNAME: &str = "ffi";
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
pub fn luaopen_io(L: *mut lua_State) -> c_int;
+6 -7
View File
@@ -51,8 +51,7 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx);
// Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
return 1;
}
}
@@ -125,7 +124,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 c_int");
let n = n.try_into().expect("cannot convert index to lua_Integer");
lua_rawgeti_(L, idx, n);
lua_type(L, -1)
}
@@ -153,7 +152,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 to c_int");
let n = n.try_into().expect("cannot convert index from lua_Integer");
lua_rawseti_(L, idx, n)
}
@@ -200,16 +199,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, c"nil");
lua_pushliteral(L, "nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, c"false");
lua_pushliteral(L, "false");
} else {
lua_pushliteral(L, c"true");
lua_pushliteral(L, "true");
}
}
t => {
+7 -70
View File
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
#[link_name = "luaL_getmetafield"]
@@ -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;
@@ -69,7 +69,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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);
@@ -82,7 +82,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -166,76 +166,13 @@ 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)
}
}
//
// Generic Buffer Manipulation
// TODO: 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;
}
+13 -12
View File
@@ -1,6 +1,5 @@
//! 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::ptr;
@@ -89,13 +88,13 @@ pub type lua_Reader =
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)
/// Type for memory-allocation functions
#[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;
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -161,14 +160,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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 = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -257,7 +256,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Coroutine functions
//
@@ -289,12 +288,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -396,8 +395,10 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
}
#[inline(always)]
@@ -448,7 +449,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
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 = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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;
+11 -11
View File
@@ -1,21 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use super::lua::lua_State;
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+10 -69
View File
@@ -1,7 +1,7 @@
//! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_int, c_void};
use std::{mem, ptr};
use std::ptr;
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
@@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
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");
pub const LUA_LOADED_TABLE: &str = "_LOADED";
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
#[repr(C)]
pub struct luaL_Reg {
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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;
@@ -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;
@@ -65,7 +65,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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);
@@ -78,7 +78,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -166,72 +166,13 @@ 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)
}
}
//
// Generic Buffer Manipulation
// TODO: 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;
}
+13 -12
View File
@@ -1,6 +1,5 @@
//! 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_void};
use std::{mem, ptr};
@@ -96,13 +95,13 @@ pub type lua_Reader =
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)
/// Type for memory-allocation functions
#[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;
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -172,14 +171,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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 = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -265,7 +264,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Coroutine functions
//
@@ -300,12 +299,12 @@ pub const LUA_GCSETSTEPMUL: c_int = 7;
pub const LUA_GCISRUNNING: c_int = 9;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -408,8 +407,10 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len())
}
#[inline(always)]
@@ -477,7 +478,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
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 = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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;
+12 -12
View File
@@ -1,22 +1,22 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use super::lua::lua_State;
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+12 -100
View File
@@ -1,7 +1,7 @@
//! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_double, c_int, c_long, c_void};
use std::{mem, ptr};
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
@@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
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");
pub const LUA_LOADED_TABLE: &str = "_LOADED";
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
#[repr(C)]
pub struct luaL_Reg {
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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;
@@ -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;
@@ -64,7 +64,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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);
@@ -77,7 +77,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -91,7 +91,7 @@ unsafe extern "C-unwind" {
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);
// TODO: luaL_addgsub
pub fn luaL_gsub(
L: *mut lua_State,
@@ -162,6 +162,8 @@ 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())
@@ -186,96 +188,6 @@ 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)
}
}
//
// Generic Buffer Manipulation
// TODO: 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
}
+14 -13
View File
@@ -1,6 +1,5 @@
//! 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_ushort, c_void};
use std::{mem, ptr};
@@ -95,16 +94,16 @@ pub type lua_Reader =
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)
/// Type for memory-allocation functions
#[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;
unsafe extern "C-unwind" 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 = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -186,14 +185,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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 = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -279,7 +278,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Coroutine functions
//
@@ -303,7 +302,7 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
// Warning-related functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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);
}
@@ -324,12 +323,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -435,8 +434,10 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len())
}
#[inline(always)]
@@ -519,7 +520,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
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 = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
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;
+11 -11
View File
@@ -1,21 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use super::lua::lua_State;
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
-299
View File
@@ -1,299 +0,0 @@
//! 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
@@ -1,578 +0,0 @@
//! 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
@@ -1,55 +0,0 @@
//! 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
@@ -1,9 +0,0 @@
//! 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;
+57 -118
View File
@@ -10,8 +10,6 @@ use super::lauxlib::*;
use super::lua::*;
use super::luacode::*;
pub const LUA_RESUMEERROR: c_int = -1;
unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
while a < b {
lua_pushvalue(L, a);
@@ -23,8 +21,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
}
}
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
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
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,10 +39,11 @@ 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 {
// 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
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, ".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
return 1;
}
}
@@ -53,25 +52,14 @@ 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,
L1: *mut lua_State,
level: c_int,
ar: *mut lua_Debug,
) -> c_int {
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int {
let top = lua_gettop(L);
lua_getinfo(L1, level, cstr!("f"), ar); // push function
lua_xmove(L1, L, 1); // and move onto L
// push function
lua_getinfo(L, level, cstr!("f"), ar);
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_settop(L, top + 1); // remove pushed values
lua_pop(L, 2); // remove pushed values
1
} else {
lua_settop(L, top); // remove function and global table
@@ -79,18 +67,15 @@ unsafe fn compat53_pushglobalfuncname(
}
}
unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, level: c_int, ar: *mut lua_Debug) {
unsafe fn compat53_pushfuncname(L: *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, L1, level, ar) != 0 {
} else if compat53_pushglobalfuncname(L, 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"?");
lua_pushliteral(L, "?");
}
}
@@ -133,19 +118,13 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx);
// Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
return 1;
}
}
0
}
#[inline(always)]
pub unsafe fn lua_pushinteger(L: *mut lua_State, i: lua_Integer) {
lua_pushnumber(L, i as lua_Number);
}
#[inline(always)]
pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx(L, i, ptr::null_mut())
@@ -197,20 +176,21 @@ pub unsafe fn lua_geti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) -> c_i
#[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 from lua_Integer");
lua_rawgeti_(L, idx, n)
}
#[inline(always)]
pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int {
lua_rawgetptagged(L, idx, p, 0)
let abs_i = lua_absindex(L, idx);
lua_pushlightuserdata(L, p as *mut c_void);
lua_rawget(L, abs_i)
}
#[inline(always)]
pub unsafe fn lua_getuservalue(L: *mut lua_State, mut idx: c_int) -> c_int {
luaL_checkstack(L, 2, cstr!("not enough stack slots available"));
idx = lua_absindex(L, idx);
lua_pushliteral(L, c"__mlua_uservalues");
lua_pushliteral(L, "__mlua_uservalues");
if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
return LUA_TNIL;
}
@@ -231,26 +211,29 @@ 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");
lua_rawseti_(L, idx, n)
}
#[inline(always)]
pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
lua_rawsetptagged(L, idx, p, 0)
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);
}
#[inline(always)]
pub unsafe fn lua_setuservalue(L: *mut lua_State, mut idx: c_int) {
luaL_checkstack(L, 4, cstr!("not enough stack slots available"));
idx = lua_absindex(L, idx);
lua_pushliteral(L, c"__mlua_uservalues");
lua_pushliteral(L, "__mlua_uservalues");
lua_pushvalue(L, -1);
if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
lua_pop(L, 1);
lua_createtable(L, 0, 2); // main table
lua_createtable(L, 0, 1); // metatable
lua_pushliteral(L, c"k");
lua_pushliteral(L, "k");
lua_setfield(L, -2, cstr!("__mode"));
lua_setmetatable(L, -2);
lua_pushvalue(L, -2);
@@ -301,19 +284,6 @@ pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, n
ret
}
#[inline(always)]
pub unsafe fn lua_resumex(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = if narg == LUA_RESUMEERROR {
lua_resumeerror(L, from)
} else {
lua_resume_(L, from, narg)
};
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
}
ret
}
//
// lauxlib ported functions
//
@@ -324,30 +294,12 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char)
if !msg.is_null() {
luaL_error(L, cstr!("stack overflow (%s)"), msg);
} else {
lua_pushliteral(L, c"stack overflow");
lua_pushliteral(L, "stack overflow");
lua_error(L);
}
}
}
#[inline(always)]
pub unsafe fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer {
let mut isnum = 0;
let int = lua_tointegerx(L, narg, &mut isnum);
if isnum == 0 {
luaL_typeerror(L, narg, lua_typename(L, LUA_TNUMBER));
}
int
}
pub unsafe fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer {
if lua_isnoneornil(L, narg) != 0 {
def
} else {
luaL_checkinteger(L, narg)
}
}
#[inline(always)]
pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int {
if luaL_getmetafield_(L, obj, e) != 0 {
@@ -376,11 +328,11 @@ pub unsafe fn luaL_loadbufferenv(
mode: *const c_char,
mut env: c_int,
) -> c_int {
unsafe extern "C" {
extern "C" {
fn free(p: *mut c_void);
}
unsafe extern "C" fn data_dtor(_: *mut lua_State, data: *mut c_void) {
unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) {
free(*(data as *mut *mut c_char) as *mut c_void);
}
@@ -396,7 +348,7 @@ pub unsafe fn luaL_loadbufferenv(
}
}
let status = if chunk_is_text {
if chunk_is_text {
if env < 0 {
env -= 1;
}
@@ -405,21 +357,14 @@ pub unsafe fn luaL_loadbufferenv(
ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released.
let status = luau_load(L, name, data, size, env);
let ok = luau_load(L, name, data, size, env) == 0;
lua_replace(L, -2); // replace data with the result
status
} else {
luau_load(L, name, data, size, env)
};
if status != 0 {
if lua_isstring(L, -1) != 0 && CStr::from_ptr(lua_tostring(L, -1)) == c"not enough memory" {
// A case for Luau >= 0.679
return LUA_ERRMEM;
if !ok {
return LUA_ERRSYNTAX;
}
} else if luau_load(L, name, data, size, env) != 0 {
return LUA_ERRSYNTAX;
}
LUA_OK
}
@@ -459,42 +404,36 @@ 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);
#[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);
let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 {
COMPAT53_LEVELS1
} else {
0
};
if !msg.is_null() {
luaL_addstring(&mut buf, msg);
luaL_addstring(&mut buf, cstr!("\n"));
lua_pushfstring(L, cstr!("%s\n"), msg);
}
luaL_addstring(&mut buf, cstr!("stack traceback:"));
while lua_getinfo(L1, level, cstr!("sln"), &mut ar) != 0 {
if limit == 0 {
lua_pushliteral(L, "stack traceback:");
while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 {
if level + 1 == mark {
// too many levels?
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
lua_pushliteral(L, "\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else {
luaL_addstring(&mut buf, cstr!("\n\t"));
luaL_addstring(&mut buf, ar.short_src);
luaL_addstring(&mut buf, cstr!(":"));
lua_getinfo(L1, level, cstr!("sln"), &mut ar);
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src);
if ar.currentline > 0 {
luaL_addunsigned(&mut buf, ar.currentline as _);
luaL_addstring(&mut buf, cstr!(":"));
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
}
luaL_addstring(&mut buf, cstr!(" in "));
compat53_pushfuncname(L, L1, level, &mut ar);
luaL_addvalue(&mut buf);
lua_pushliteral(L, " in ");
compat53_pushfuncname(L, level, &mut ar);
lua_concat(L, lua_gettop(L) - top);
}
level += 1;
limit -= 1;
}
luaL_pushresult(&mut buf);
lua_concat(L, lua_gettop(L) - top);
}
pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
@@ -502,16 +441,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, c"nil");
lua_pushliteral(L, "nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, c"false");
lua_pushliteral(L, "false");
} else {
lua_pushliteral(L, c"true");
lua_pushliteral(L, "true");
}
}
t => {
@@ -557,7 +496,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, LUA_LOADED_TABLE);
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
+8 -52
View File
@@ -3,10 +3,7 @@
use std::os::raw::{c_char, c_float, c_int, c_void};
use std::ptr;
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");
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX};
#[repr(C)]
pub struct luaL_Reg {
@@ -14,7 +11,7 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -36,12 +33,8 @@ unsafe extern "C-unwind" {
pub fn luaL_checkboolean(L: *mut lua_State, narg: c_int) -> c_int;
pub fn luaL_optboolean(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
#[link_name = "luaL_checkinteger"]
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int;
pub fn luaL_checkinteger64(L: *mut lua_State, narg: c_int) -> i64;
#[link_name = "luaL_optinteger"]
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_optinteger64(L: *mut lua_State, narg: c_int, def: i64) -> i64;
pub fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer;
pub fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer;
pub fn luaL_checkunsigned(L: *mut lua_State, narg: c_int) -> lua_Unsigned;
pub fn luaL_optunsigned(L: *mut lua_State, narg: c_int, def: lua_Unsigned) -> lua_Unsigned;
@@ -76,20 +69,10 @@ unsafe extern "C-unwind" {
pub fn luaL_newstate() -> *mut lua_State;
pub fn luaL_findtable(
L: *mut lua_State,
idx: c_int,
fname: *const c_char,
szhint: c_int,
) -> *const c_char;
// TODO: luaL_findtable
pub fn luaL_typename(L: *mut lua_State, idx: c_int) -> *const c_char;
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);
@@ -124,19 +107,7 @@ pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *
luaL_optlstring(L, n, d, ptr::null_mut())
}
#[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: luaL_opt
#[inline(always)]
pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) -> c_int {
@@ -170,7 +141,7 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
}
// set all builtin metatables to read-only
lua_pushliteral(L, c"");
lua_pushliteral(L, "");
if lua_getmetatable(L, -1) != 0 {
lua_setreadonly(L, -1, enabled);
lua_pop(L, 2);
@@ -202,7 +173,7 @@ pub struct luaL_Strbuf {
// For compatibility
pub type luaL_Buffer = luaL_Strbuf;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Strbuf);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
pub fn luaL_prepbuffsize(B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
@@ -229,18 +200,3 @@ 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);
}
+44 -106
View File
@@ -1,6 +1,5 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void};
use std::{mem, ptr};
@@ -12,10 +11,10 @@ pub const LUA_MULTRET: c_int = -1;
const LUAI_MAXCSTACK: c_int = 1000000;
// Number of valid Lua userdata tags
pub const LUA_UTAG_LIMIT: c_int = 128;
const LUA_UTAG_LIMIT: c_int = 128;
// Number of valid Lua lightuserdata tags
pub const LUA_LUTAG_LIMIT: c_int = 128;
const LUA_LUTAG_LIMIT: c_int = 128;
//
// Pseudo-indices
@@ -37,16 +36,6 @@ 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)]
@@ -65,15 +54,14 @@ pub const LUA_TBOOLEAN: c_int = 1;
pub const LUA_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3;
pub const LUA_TINTEGER: c_int = 4;
pub const LUA_TVECTOR: c_int = 5;
pub const LUA_TVECTOR: c_int = 4;
pub const LUA_TSTRING: c_int = 6;
pub const LUA_TTABLE: c_int = 7;
pub const LUA_TFUNCTION: c_int = 8;
pub const LUA_TUSERDATA: c_int = 9;
pub const LUA_TTHREAD: c_int = 10;
pub const LUA_TBUFFER: c_int = 11;
pub const LUA_TSTRING: c_int = 5;
pub const LUA_TTABLE: c_int = 6;
pub const LUA_TFUNCTION: c_int = 7;
pub const LUA_TUSERDATA: c_int = 8;
pub const LUA_TTHREAD: c_int = 9;
pub const LUA_TBUFFER: c_int = 10;
/// Guaranteed number of Lua stack slots available to a C function.
pub const LUA_MINSTACK: c_int = 20;
@@ -81,11 +69,8 @@ pub const LUA_MINSTACK: c_int = 20;
/// A Lua number, usually equivalent to `f64`.
pub type lua_Number = c_double;
/// A Lua integer, usually equivalent to `i64`
#[cfg(target_pointer_width = "32")]
pub type lua_Integer = i32;
#[cfg(target_pointer_width = "64")]
pub type lua_Integer = i64;
/// A Lua integer, equivalent to `i32`.
pub type lua_Integer = c_int;
/// A Lua unsigned integer, equivalent to `u32`.
pub type lua_Unsigned = c_uint;
@@ -94,19 +79,20 @@ pub type lua_Unsigned = c_uint;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
pub type lua_Continuation = unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int) -> c_int;
/// Type for userdata destructor functions (no unwinding).
pub type lua_Destructor = unsafe extern "C" fn(L: *mut lua_State, *mut c_void);
/// Type for userdata destructor functions.
pub type lua_Udestructor = unsafe extern "C-unwind" fn(*mut c_void);
pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void);
/// Type for memory-allocation functions (no unwinding).
/// Type for memory-allocation functions.
pub type lua_Alloc =
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Returns Luau release version (eg. `0.xxx`).
pub const fn luau_version() -> Option<&'static str> {
option_env!("LUAU_VERSION")
}
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -150,22 +136,14 @@ unsafe extern "C-unwind" {
pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number;
#[link_name = "lua_tointegerx"]
pub fn lua_tointegerx_(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> c_int;
pub fn lua_tointegerx_(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer;
pub fn lua_tounsignedx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Unsigned;
pub fn lua_tovector(L: *mut lua_State, idx: c_int) -> *const c_float;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tointeger64(L: *mut lua_State, idx: c_int, isinteger: *mut c_int) -> i64;
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;
#[link_name = "lua_objlen"]
pub fn lua_objlen_(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize;
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;
@@ -182,9 +160,7 @@ unsafe extern "C-unwind" {
//
pub fn lua_pushnil(L: *mut lua_State);
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
#[link_name = "lua_pushinteger"]
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int);
pub fn lua_pushinteger64(L: *mut lua_State, n: i64);
pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer);
pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned);
#[cfg(not(feature = "luau-vector4"))]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float);
@@ -210,7 +186,7 @@ unsafe extern "C-unwind" {
pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int);
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatataggedwithmetatable(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Destructor) -> *mut c_void;
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void;
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
@@ -223,7 +199,6 @@ 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);
@@ -238,11 +213,9 @@ 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;
@@ -258,7 +231,6 @@ 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
@@ -272,12 +244,6 @@ 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
}
//
@@ -294,14 +260,14 @@ pub const LUA_GCSETGOAL: c_int = 7;
pub const LUA_GCSETSTEPMUL: c_int = 8;
pub const LUA_GCSETSTEPSIZE: c_int = 9;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
//
// Memory statistics
//
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_setmemcat(L: *mut lua_State, category: c_int);
pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize;
}
@@ -309,23 +275,22 @@ unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_error(L: *mut lua_State) -> !;
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);
pub fn lua_encodepointer(L: *mut lua_State, p: usize) -> usize;
// TODO: lua_encodepointer
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>);
pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int);
pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int, idx: c_int);
pub fn lua_getuserdatametatable(L: *mut lua_State, tag: c_int);
pub fn lua_setlightuserdataname(L: *mut lua_State, tag: c_int, name: *const c_char);
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;
}
@@ -335,7 +300,7 @@ unsafe extern "C-unwind" {
pub const LUA_NOREF: c_int = -1;
pub const LUA_REFNIL: c_int = 0;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_ref(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_unref(L: *mut lua_State, r#ref: c_int);
}
@@ -345,13 +310,13 @@ unsafe extern "C-unwind" {
//
#[inline(always)]
pub unsafe fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number {
lua_tonumberx(L, idx, ptr::null_mut())
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, idx: c_int) -> c_int {
lua_tointegerx_(L, idx, ptr::null_mut())
pub unsafe fn lua_tointeger_(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx_(L, i, ptr::null_mut())
}
#[inline(always)]
@@ -375,20 +340,15 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
}
#[inline(always)]
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State, data: T) -> *mut T {
unsafe extern "C" fn destructor<T>(_: *mut lua_State, ud: *mut c_void) {
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State) -> *mut T {
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
ptr::drop_in_place(ud as *mut T);
}
let ud_ptr = lua_newuserdatadtor(L, const { mem::size_of::<T>() }, destructor::<T>) as *mut T;
ptr::write(ud_ptr, data);
ud_ptr
lua_newuserdatadtor(L, mem::size_of::<T>(), destructor::<T>) as *mut T
}
#[inline(always)]
pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize {
lua_objlen(L, i)
}
// TODO: lua_strlen
#[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
@@ -415,11 +375,6 @@ 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_isinteger64(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TINTEGER) as c_int
}
#[inline(always)]
pub unsafe fn lua_isvector(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TVECTOR) as c_int
@@ -446,8 +401,10 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring_(L, s.as_ptr());
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
}
#[inline(always)]
@@ -509,13 +466,7 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
size: usize,
);
pub type lua_CounterFunction =
unsafe extern "C-unwind" fn(context: *mut c_void, function: *const c_char, linedefined: c_int);
pub type lua_CounterValue =
unsafe extern "C-unwind" fn(context: *mut c_void, kind: c_int, line: c_int, hits: u64);
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getargument(L: *mut lua_State, level: c_int, n: c_int) -> c_int;
@@ -529,14 +480,6 @@ unsafe extern "C-unwind" {
pub fn lua_getcoverage(L: *mut lua_State, funcindex: c_int, context: *mut c_void, callback: lua_Coverage);
pub fn lua_getcounters(
L: *mut lua_State,
funcindex: c_int,
context: *mut c_void,
functionvisit: lua_CounterFunction,
countervisit: lua_CounterValue,
);
pub fn lua_debugtrace(L: *mut lua_State) -> *const c_char;
}
@@ -573,8 +516,8 @@ pub struct lua_Callbacks {
/// gets called when L is created (LP == parent) or destroyed (LP == NULL)
pub userthread: Option<unsafe extern "C-unwind" fn(LP: *mut lua_State, L: *mut lua_State)>,
/// gets called when a string is created to assign an atom id
pub useratom: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, s: *const c_char, l: usize) -> i16>,
/// gets called when a string is created; returned atom can be retrieved via tostringatom
pub useratom: Option<unsafe extern "C-unwind" fn(s: *const c_char, l: usize) -> i16>,
/// gets called when BREAK instruction is encountered
pub debugbreak: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
@@ -589,17 +532,12 @@ pub struct lua_Callbacks {
pub onallocate: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, osize: usize, nsize: usize)>,
}
unsafe extern "C" {
extern "C" {
pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks;
}
// Functions from customization lib
unsafe extern "C" {
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>,
);
}
+3 -4
View File
@@ -76,16 +76,15 @@ pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn(
constant: *mut lua_CompileConstant,
);
unsafe extern "C" {
extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
pub fn luau_set_compile_constant_integer64(cons: *mut lua_CompileConstant, l: i64);
pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32);
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
unsafe extern "C-unwind" {
extern "C-unwind" {
#[link_name = "luau_compile"]
pub fn luau_compile_(
source: *const c_char,
@@ -95,7 +94,7 @@ unsafe extern "C-unwind" {
) -> *mut c_char;
}
unsafe extern "C" {
extern "C" {
fn free(p: *mut c_void);
}
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use super::lua::lua_State;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luau_codegen_supported() -> c_int;
pub fn luau_codegen_create(state: *mut lua_State);
pub fn luau_codegen_compile(state: *mut lua_State, idx: c_int);
+12 -14
View File
@@ -1,22 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use super::lua::lua_State;
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_BUFFERLIBNAME: *const c_char = cstr!("buffer");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
pub const LUA_INTLIBNAME: *const c_char = cstr!("integer");
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_BUFFERLIBNAME: &str = "buffer";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_VECLIBNAME: &str = "vector";
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
@@ -28,7 +27,6 @@ unsafe extern "C-unwind" {
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_vector(L: *mut lua_State) -> c_int;
pub fn luaopen_integer(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State);
-219
View File
@@ -1,219 +0,0 @@
//! Contains definitions from `Require.h`.
use std::os::raw::{c_char, c_int, c_void};
use super::lua::lua_State;
pub const LUA_REGISTERED_MODULES_TABLE: *const c_char = cstr!("_REGISTEREDMODULES");
#[repr(C)]
pub enum luarequire_NavigateResult {
Success,
Ambiguous,
NotFound,
}
// Functions returning WriteSuccess are expected to set their size_out argument
// to the number of bytes written to the buffer. If WriteBufferTooSmall is
// returned, size_out should be set to the required buffer size.
#[repr(C)]
pub enum luarequire_WriteResult {
Success,
BufferTooSmall,
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.
pub is_require_allowed: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool,
// Resets the internal state to point at the requirer module.
pub reset: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> luarequire_NavigateResult,
// Resets the internal state to point at an aliased module, given its exact path from a configuration
// file. This function is only called when an alias's path cannot be resolved relative to its
// configuration file.
pub jump_to_alias: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
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,
pub to_child: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> luarequire_NavigateResult,
// Returns whether the context is currently pointing at a module.
pub is_module_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// Provides a chunkname for the current module. This will be accessible through the debug library. This
// function is only called if is_module_present returns true.
pub get_chunkname: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// Provides a loadname that identifies the current module and is passed to load. This function
// is only called if is_module_present returns true.
pub get_loadname: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// Provides a cache key representing the current module. This function is only called if
// is_module_present returns true.
pub get_cache_key: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// 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 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 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.
pub get_alias: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias: *const c_char,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
>,
// 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,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> 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
// the module result pushed onto its stack.
pub load: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
path: *const c_char,
chunkname: *const c_char,
loadname: *const c_char,
) -> c_int,
}
// Populates function pointers in the given luarequire_Configuration.
pub type luarequire_Configuration_init = unsafe extern "C-unwind" fn(config: *mut luarequire_Configuration);
unsafe extern "C-unwind" {
// Initializes and pushes the require closure onto the stack without registration.
pub fn luarequire_pushrequire(
L: *mut lua_State,
config_init: luarequire_Configuration_init,
ctx: *mut c_void,
) -> c_int;
// Initializes the require library and registers it globally.
pub fn luaopen_require(L: *mut lua_State, config_init: luarequire_Configuration_init, ctx: *mut c_void);
// Initializes and pushes a "proxyrequire" closure onto the stack.
//
// The closure takes two parameters: the string path to resolve and the chunkname of an existing
// module.
pub fn luarequire_pushproxyrequire(
L: *mut lua_State,
config_init: luarequire_Configuration_init,
ctx: *mut c_void,
) -> c_int;
// Registers an aliased require path to a result.
//
// After registration, the given result will always be immediately returned when the given path is
// required.
// Expects the path and table to be passed as arguments on the stack.
pub fn luarequire_registermodule(L: *mut lua_State) -> c_int;
// Clears the entry associated with the given cache key from the require cache.
// Expects the cache key to be passed as an argument on the stack.
pub fn luarequire_clearcacheentry(L: *mut lua_State) -> c_int;
// Clears all entries from the require cache.
pub fn luarequire_clearcache(L: *mut lua_State) -> c_int;
}
-2
View File
@@ -6,7 +6,6 @@ pub use lua::*;
pub use luacode::*;
pub use luacodegen::*;
pub use lualib::*;
pub use luarequire::*;
pub mod compat;
pub mod lauxlib;
@@ -14,4 +13,3 @@ pub mod lua;
pub mod luacode;
pub mod luacodegen;
pub mod lualib;
pub mod luarequire;
+5 -4
View File
@@ -1,9 +1,8 @@
[package]
name = "mlua_derive"
version = "0.12.0-rc.1"
version = "0.10.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.88"
edition = "2024"
edition = "2021"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/mlua-rs/mlua"
keywords = ["lua", "mlua"]
@@ -13,7 +12,7 @@ license = "MIT"
proc-macro = true
[features]
macros = ["proc-macro-error2", "itertools"]
macros = ["proc-macro-error2", "itertools", "regex", "once_cell"]
[dependencies]
quote = "1.0"
@@ -21,3 +20,5 @@ proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.14", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
+105
View File
@@ -0,0 +1,105 @@
use proc_macro::{TokenStream, TokenTree};
use crate::token::{Pos, Token, Tokens};
#[derive(Debug, Clone)]
pub(crate) struct Capture {
key: Token,
rust: TokenTree,
}
impl Capture {
fn new(key: Token, rust: TokenTree) -> Self {
Self { key, rust }
}
/// Token string inside `chunk!`
pub(crate) fn key(&self) -> &Token {
&self.key
}
/// As rust variable, e.g. `x`
pub(crate) fn as_rust(&self) -> &TokenTree {
&self.rust
}
}
#[derive(Debug)]
pub(crate) struct Captures(Vec<Capture>);
impl Captures {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn add(&mut self, token: &Token) -> Capture {
let tt = token.tree();
let key = token.clone();
match self.0.iter().find(|arg| arg.key() == &key) {
Some(arg) => arg.clone(),
None => {
let arg = Capture::new(key, tt.clone());
self.0.push(arg.clone());
arg
}
}
}
pub(crate) fn captures(&self) -> &[Capture] {
&self.0
}
}
#[derive(Debug)]
pub(crate) struct Chunk {
source: String,
caps: Captures,
}
impl Chunk {
pub(crate) fn new(tokens: TokenStream) -> Self {
let tokens = Tokens::retokenize(tokens);
let mut source = String::new();
let mut caps = Captures::new();
let mut pos: Option<Pos> = None;
for t in tokens {
if t.is_cap() {
caps.add(&t);
}
let (line, col) = (t.start().line, t.start().column);
let (prev_line, prev_col) = pos
.take()
.map(|lc| (lc.line, lc.column))
.unwrap_or_else(|| (line, col));
#[allow(clippy::comparison_chain)]
if line > prev_line {
source.push('\n');
} else if line == prev_line {
for _ in 0..col.saturating_sub(prev_col) {
source.push(' ');
}
}
source.push_str(&t.to_string());
pos = Some(t.end());
}
Self {
source: source.trim_end().to_string(),
caps,
}
}
pub(crate) fn source(&self) -> &str {
&self.source
}
pub(crate) fn captures(&self) -> &[Capture] {
self.caps.captures()
}
}
-160
View File
@@ -1,160 +0,0 @@
use std::ops::Deref;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use self::token::{Pos, Token, Tokens};
mod token;
#[derive(Debug, Clone)]
pub(crate) struct Capture(Token);
impl Deref for Capture {
type Target = Token;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Capture {
fn new(token: &Token) -> Self {
Self(token.clone())
}
pub(crate) fn name(&self) -> String {
self.0.to_string()
}
}
impl ToTokens for Capture {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ts: TokenStream = self.0.tree().clone().into();
tokens.extend(TokenStream2::from(ts));
}
}
#[derive(Debug)]
pub(crate) struct Captures(Vec<Capture>);
impl Captures {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn add(&mut self, token: &Token) {
if self.0.iter().any(|arg| &**arg == token) {
return;
}
self.0.push(Capture::new(token));
}
pub(crate) fn captures(&self) -> &[Capture] {
&self.0
}
}
#[derive(Debug)]
pub(crate) struct Chunk {
source: String,
caps: Captures,
}
impl Chunk {
pub(crate) fn new(tokens: TokenStream) -> Self {
let tokens = Tokens::retokenize(tokens);
let mut source = String::new();
let mut caps = Captures::new();
let mut prev_end: Option<Pos> = None;
for t in tokens {
if t.is_cap() {
caps.add(&t);
}
let (line, col) = (t.start().line, t.start().column);
if let Some(prev) = prev_end {
if line > prev.line {
source.push('\n');
source.push_str(&" ".repeat(col.saturating_sub(1)));
} else if line == prev.line {
source.push_str(&" ".repeat(col.saturating_sub(prev.column)));
}
} else {
source.push_str(&" ".repeat(col.saturating_sub(1)));
}
source.push_str(&t.to_string());
prev_end = Some(t.end());
}
Self {
source: source.trim_end().to_string(),
caps,
}
}
pub(crate) fn captures(&self) -> &[Capture] {
self.caps.captures()
}
pub(crate) fn expand(&self) -> TokenStream2 {
let source = &self.source;
let caps_len = self.captures().len();
let caps = self.captures().iter().map(|cap| {
let cap_name = cap.name();
quote! { env.raw_set(#cap_name, #cap)?; }
});
quote! {{
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
if #caps_len > 0 {
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
}
}
Ok(None)
}
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
let make_env = move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", &globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta))?;
Ok(env)
};
InnerChunk(Cell::new(Some(make_env)))
}}
}
}
+13 -13
View File
@@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, parse_macro_input};
use syn::{parse_macro_input, DeriveInput};
pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);
@@ -13,19 +13,19 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
};
quote! {
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str.to_string(),
message: None,
}),
}
}
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str.to_string(),
message: None,
}),
}
}
}
}
.into()
}
+131 -27
View File
@@ -1,30 +1,148 @@
use proc_macro::TokenStream;
mod module;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::meta::ParseNestedMeta;
use syn::{parse_macro_input, ItemFn, LitStr, Result};
#[cfg(feature = "macros")]
use {crate::chunk::Chunk, proc_macro_error2::proc_macro_error};
use {
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error2::proc_macro_error,
};
#[cfg(feature = "macros")]
macro_rules! try_compile {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => return err.to_compile_error().into(),
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
skip_memory_check: bool,
}
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else if meta.path.is_ident("skip_memory_check") {
if meta.value().is_ok() {
return Err(meta.error("`skip_memory_check` attribute have no values"));
}
self.skip_memory_check = true;
} else {
return Err(meta.error("unsupported module attribute"));
}
};
Ok(())
}
}
#[proc_macro_attribute]
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
module::lua_module(attr, item)
let mut args = ModuleAttributes::default();
if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
parse_macro_input!(attr with args_parser);
}
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! {
mlua::require_module_feature!();
#func
#[no_mangle]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
mlua::Lua::entrypoint1(state, move |lua| {
#skip_memory_check
#func_name(lua)
})
}
};
wrapped.into()
}
#[cfg(feature = "macros")]
fn to_ident(tt: &TokenTree) -> TokenStream2 {
let s: TokenStream = tt.clone().into();
s.into()
}
#[cfg(feature = "macros")]
#[proc_macro]
#[proc_macro_error]
pub fn chunk(input: TokenStream) -> TokenStream {
Chunk::new(input).expand().into()
let chunk = Chunk::new(input);
let source = chunk.source();
let caps_len = chunk.captures().len();
let caps = chunk.captures().iter().map(|cap| {
let cap_name = cap.as_rust().to_string();
let cap = to_ident(cap.as_rust());
quote! { env.raw_set(#cap_name, #cap)?; }
});
let wrapped_code = quote! {{
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk<'static> for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
if #caps_len > 0 {
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
}
}
Ok(None)
}
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
let make_env = move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", globals.clone())?;
meta.raw_set("__newindex", globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta));
Ok(env)
};
InnerChunk(Cell::new(Some(make_env)))
}};
wrapped_code.into()
}
#[cfg(feature = "macros")]
@@ -33,23 +151,9 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
from_lua::from_lua(input)
}
/// Derive macro for implementing `UserData` for a Rust type.
#[cfg(feature = "macros")]
#[proc_macro_derive(UserData, attributes(lua))]
pub fn userdata(item: TokenStream) -> TokenStream {
userdata::userdata_type(item)
}
/// Attribute macro for exposing impl block methods to Lua userdata.
#[cfg(feature = "macros")]
#[proc_macro_attribute]
pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
userdata::userdata_impl::userdata_impl(attr, item)
}
#[cfg(feature = "macros")]
mod chunk;
#[cfg(feature = "macros")]
mod from_lua;
#[cfg(feature = "macros")]
mod userdata;
mod token;
-68
View File
@@ -1,68 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::meta::ParseNestedMeta;
use syn::{ItemFn, LitStr, Result, parse_macro_input};
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
skip_memory_check: bool,
}
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else if meta.path.is_ident("skip_memory_check") {
if meta.value().is_ok() {
return Err(meta.error("`skip_memory_check` attribute have no values"));
}
self.skip_memory_check = true;
} else {
return Err(meta.error("unsupported module attribute"));
}
Ok(())
}
}
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut args = ModuleAttributes::default();
if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
parse_macro_input!(attr with args_parser);
}
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! {
mlua::require_module_feature!();
#func
#[unsafe(no_mangle)]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
mlua::Lua::entrypoint1(state, move |lua| {
#skip_memory_check
#func_name(lua)
})
}
};
wrapped.into()
}
@@ -3,8 +3,10 @@ use std::fmt::{self, Display, Formatter};
use std::vec::IntoIter;
use itertools::Itertools;
use once_cell::sync::Lazy;
use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
use proc_macro2::Span as Span2;
use regex::Regex;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Pos {
@@ -37,16 +39,46 @@ fn span_pos(span: &Span) -> (Pos, Pos) {
let start = span2.start();
let end = span2.end();
// Rust 1.88 stabilized Span APIs, so this branch must be unreachable
// In stable, line/column information is not provided
// and set to 0 (line is 1-indexed)
if start.line == 0 || end.line == 0 {
proc_macro_error2::abort_call_site!(
"cannot retrieve span location information; mlua requires nightly Rust or stable >= 1.88"
);
return fallback_span_pos(span);
}
(Pos::new(start.line, start.column), Pos::new(end.line, end.column))
}
fn parse_pos(span: &Span) -> Option<(usize, usize)> {
// Workaround to somehow retrieve location information in span in stable rust :(
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap());
match RE.captures(&format!("{span:?}")) {
Some(caps) => match (caps.get(1), caps.get(2)) {
(Some(start), Some(end)) => Some((
match start.as_str().parse() {
Ok(v) => v,
_ => return None,
},
match end.as_str().parse() {
Ok(v) => v,
_ => return None,
},
)),
_ => None,
},
None => None,
}
}
fn fallback_span_pos(span: &Span) -> (Pos, Pos) {
let (start, end) = match parse_pos(span) {
Some(v) => v,
None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"),
};
(Pos::new(1, start), Pos::new(1, end))
}
/// Attribute of token.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TokenAttr {
@@ -76,9 +108,8 @@ impl Eq for Token {}
impl Token {
fn new(tree: TokenTree) -> Self {
let (start, end) = span_pos(&tree.span());
let source = tree.span().source_text().unwrap_or_else(|| tree.to_string());
Self {
source,
source: tree.to_string(),
start,
end,
tree,
@@ -137,17 +168,14 @@ impl Tokens {
Tokens(
tt.into_iter()
.flat_map(Tokens::from)
.peekable()
.batching(|iter| {
// Find variable tokens: `$` + `ident` => `$ident`
// Find variable tokens
let t = iter.next()?;
if t.is("$") {
if let Some(next) = iter.next()
&& matches!(next.tree, TokenTree::Ident(_))
{
Some(next.attr(TokenAttr::Cap))
} else {
proc_macro_error2::abort!(t.tree.span(), "`$` must be followed by an identifier");
}
// `$` + `ident` => `$ident`
let t = iter.next().expect("$ must trail an identifier");
Some(t.attr(TokenAttr::Cap))
} else {
Some(t)
}
-93
View File
@@ -1,93 +0,0 @@
use proc_macro2::Span;
use syn::meta::ParseNestedMeta;
use syn::{Ident, LitStr, Result};
/// Parsed `#[lua(...)]` attribute.
///
/// Some flags are context-dependent:
/// - Struct fields: `get`, `set`, `name`, `skip`
/// - Impl methods: `getter`, `setter`, `field`, `meta`, `infallible`, `name`, `skip`
#[derive(Default)]
pub(crate) struct LuaAttr {
pub(crate) span: Option<Span>,
pub(crate) name: Option<String>,
pub(crate) infallible: bool,
pub(crate) skip: bool,
// Struct field context flags
pub(crate) get: bool,
pub(crate) set: bool,
// Impl method context flags
pub(crate) getter: bool,
pub(crate) setter: bool,
pub(crate) field: bool,
pub(crate) meta: bool,
}
impl LuaAttr {
pub(crate) fn parse_inner(&mut self, meta: ParseNestedMeta) -> Result<()> {
match &meta.path {
path if path.is_ident("skip") => {
if meta.value().is_ok() {
return Err(meta.error("`skip` does not take a value"));
}
self.skip = true;
}
path if path.is_ident("infallible") => {
if meta.value().is_ok() {
return Err(meta.error("`infallible` does not take a value"));
}
self.infallible = true;
}
path if path.is_ident("get") => self.get = true,
path if path.is_ident("set") => self.set = true,
path if path.is_ident("getter") => self.getter = true,
path if path.is_ident("setter") => self.setter = true,
path if path.is_ident("field") => self.field = true,
path if path.is_ident("meta") => self.meta = true,
path if path.is_ident("name") => {
let value = meta.value()?;
let lit: LitStr = value.parse()?;
self.name = Some(lit.value());
}
_ => {
return Err(meta.error(
"unsupported lua attribute, expected: ".to_string()
+ "`skip`, `infallible`, `get`, `set`, `getter`, `setter`, `field`, `meta`, `name`",
));
}
}
Ok(())
}
/// Returns the effective Lua name.
pub(crate) fn name(&self, ident: &Ident) -> String {
self.name.clone().unwrap_or_else(|| ident.to_string())
}
/// Returns the span to use for error reporting.
pub(crate) fn span(&self) -> Span {
self.span.unwrap_or_else(Span::call_site)
}
/// Returns the effective Lua metamethod name.
///
/// If `name` is set via attribute, use it. Otherwise, if the function name
/// starts with `__`, use that. Returns an error if neither is available.
pub(crate) fn effective_meta_name(&self, fn_ident: &Ident) -> Result<String> {
if let Some(ref name) = self.name {
return Ok(name.clone());
}
let fn_name = fn_ident.to_string();
if fn_name.starts_with("__") {
return Ok(fn_name);
}
Err(syn::Error::new(
fn_ident.span(),
format!(
"could not infer metamethod name from `{fn_name}`, either add `name = \"...\"` to `#[lua(meta, ...)]` or prefix the function with `__`"
),
))
}
}
-165
View File
@@ -1,165 +0,0 @@
mod attr;
pub(crate) mod userdata_impl;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{Attribute, Data, DeriveInput, Error, Fields, FieldsNamed, Meta, parse_macro_input};
use self::attr::LuaAttr;
/// Wrap registration tokens with any `#[cfg]`/`#[cfg_attr]` attributes from the original item.
pub(crate) fn with_cfg(tokens: proc_macro2::TokenStream, attrs: &[Attribute]) -> proc_macro2::TokenStream {
let cfgs: Vec<_> = (attrs.iter())
.filter(|attr| attr.path().is_ident("cfg") || attr.path().is_ident("cfg_attr"))
.collect();
if cfgs.is_empty() {
return tokens;
}
quote! {
#(#cfgs)*
#tokens
}
}
/// Parse all `#[lua(...)]` attributes on a field, merging them into one `LuaAttr`.
fn parse_field_lua_attr(attrs: &[Attribute]) -> syn::Result<LuaAttr> {
let mut lua_attr = LuaAttr::default();
for attr in attrs {
if !attr.path().is_ident("lua") {
continue;
}
match &attr.meta {
Meta::List(_) => {
lua_attr.span = Some(attr.span());
attr.parse_nested_meta(|meta| lua_attr.parse_inner(meta))?;
validate_field_lua_attr(&lua_attr)?;
}
Meta::Path(_) => {}
Meta::NameValue(_) => {
return Err(syn::Error::new_spanned(
attr,
"`#[lua = \"...\"]` is not supported: use `#[lua(attr = \"...\")]`",
));
}
}
}
Ok(lua_attr)
}
fn validate_field_lua_attr(attr: &LuaAttr) -> syn::Result<()> {
for (set, name) in [
(attr.getter, "getter"),
(attr.setter, "setter"),
(attr.field, "field"),
(attr.meta, "meta"),
(attr.infallible, "infallible"),
] {
if set {
return Err(syn::Error::new(
attr.span(),
format!("`{name}` is not valid for struct fields"),
));
}
}
Ok(())
}
pub fn userdata_type(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let type_name = &input.ident;
let named_fields: Option<&FieldsNamed> = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => Some(fields),
Fields::Unnamed(_) | Fields::Unit => None,
},
Data::Enum(_) => None,
Data::Union(_) => {
return Error::new_spanned(&input, "`#[derive(UserData)]` cannot be applied to unions")
.to_compile_error()
.into();
}
};
// Check for generic parameters (not supported)
let has_generics = !input.generics.params.is_empty();
if has_generics {
return Error::new_spanned(
&input.generics,
"`#[derive(UserData)]` does not support generic type parameters. Wrap the generic type in a concrete newtype instead."
)
.to_compile_error()
.into();
}
let mut field_registrations = Vec::new();
if let Some(fields) = &named_fields {
for field in &fields.named {
let field_name = field.ident.as_ref().unwrap();
let lua_attr = try_compile!(parse_field_lua_attr(&field.attrs));
if lua_attr.skip {
continue;
}
let lua_name = lua_attr.name.unwrap_or_else(|| field_name.to_string());
// Assume get/set by default (unless explicitly specified)
let (has_get, has_set) = if lua_attr.get || lua_attr.set {
(lua_attr.get, lua_attr.set)
} else {
(true, true)
};
if has_get {
let tokens = quote! {
registry.add_field_method_get(#lua_name, |_lua, this| Ok(this.#field_name.clone()));
};
field_registrations.push(with_cfg(tokens, &field.attrs));
}
if has_set {
let tokens = quote! {
registry.add_field_method_set(#lua_name, |_lua, this, val| {
this.#field_name = val;
Ok(())
});
};
field_registrations.push(with_cfg(tokens, &field.attrs));
}
}
}
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
let register_fields_fn_name = format_ident!("__mlua_register_{type_name}_fields");
let output = quote! {
#[doc(hidden)]
#[allow(non_camel_case_types)]
struct #registration_type_name {
register: fn(&mut ::mlua::userdata::UserDataRegistry<#type_name>),
}
::mlua::__inventory::collect!(#registration_type_name);
#[allow(non_snake_case)]
fn #register_fields_fn_name(registry: &mut ::mlua::userdata::UserDataRegistry<#type_name>) {
use ::mlua::userdata::UserDataFields as _;
#(#field_registrations)*
}
::mlua::__inventory::submit! {
#registration_type_name { register: #register_fields_fn_name }
}
impl ::mlua::userdata::UserData for #type_name {
fn register(registry: &mut ::mlua::userdata::UserDataRegistry<Self>) {
for item in ::mlua::__inventory::iter::<#registration_type_name> {
(item.register)(registry);
}
}
}
};
output.into()
}
-730
View File
@@ -1,730 +0,0 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{
Attribute, FnArg, Ident, ImplItem, ItemImpl, Meta, Signature, Type, parse_macro_input, parse_quote,
};
use super::attr::LuaAttr;
use super::with_cfg;
/// `&T` reference types that mlua provides as wrapper types via `FromLua`.
static BORROW_WRAPPERS: &[(&str, &str)] = &[
("str", "::mlua::string::BorrowedStr"),
("[u8]", "::mlua::string::BorrowedBytes"),
];
enum SelfKind {
Ref(RefKind),
Owned,
None,
}
enum RefKind {
Ref,
Mut,
}
struct ArgInfo {
ident: Ident,
userdata_ref: Option<RefKind>,
callback_type: Type,
}
struct MethodInfo {
self_kind: SelfKind,
has_lua: bool,
args: Vec<ArgInfo>,
}
/// Extract the inner type from a reference type.
fn ref_inner_type(ty: &Type) -> Type {
match ty {
Type::Reference(ref_ty) => (*ref_ty.elem).clone(),
_ => ty.clone(),
}
}
/// Check if the type is `&Lua` or `&mlua::Lua`.
fn is_lua_ref(ty: &Type) -> bool {
let Type::Reference(ref_ty) = ty else { return false };
match &*ref_ty.elem {
Type::Path(p) if p.path.segments.len() == 1 => p.path.segments[0].ident == "Lua",
Type::Path(p) if p.path.segments.len() == 2 => {
p.path.segments[0].ident == "mlua" && p.path.segments[1].ident == "Lua"
}
_ => false,
}
}
/// Classify a `&[mut] T` parameter, returning the callback wrapper type.
///
/// Known borrow types come from the mapping table `BORROW_WRAPPERS`.
/// Everything else gets `UserDataRef[Mut]<T>`.
fn classify_ref_type(ty: &Type) -> Option<Type> {
let Type::Reference(ref_ty) = ty else { return None };
// Check known borrow wrappers:
// - For `&T` check the path name
// - For `&[T]` unpack the slice and format the element as `[T]` for lookup
if ref_ty.mutability.is_none() {
let lookup_name: Option<String> = match &*ref_ty.elem {
Type::Path(path) => path.path.segments.last().map(|seg| seg.ident.to_string()),
Type::Slice(slice) => {
if let Type::Path(path) = &*slice.elem {
path.path.segments.last().map(|seg| format!("[{}]", seg.ident))
} else {
None
}
}
_ => None,
};
if let Some(ref name) = lookup_name {
for &(inner, wrapper) in BORROW_WRAPPERS {
if name == inner {
let wrapper = syn::parse_str(wrapper).expect("invalid wrapper type");
return Some(wrapper);
}
}
}
}
// Mutable references to slices are not supported.
if matches!(&*ref_ty.elem, Type::Slice(_)) && ref_ty.mutability.is_some() {
return None;
}
let inner = ref_inner_type(ty);
if ref_ty.mutability.is_none() {
Some(parse_quote! { ::mlua::userdata::UserDataRef<#inner> })
} else {
Some(parse_quote! { ::mlua::userdata::UserDataRefMut<#inner> })
}
}
/// Analyze method signature.
///
/// Determine `self` kind and collect the callback arguments.
/// Auto-detects `&Lua` as the first non-self parameter.
fn analyze_self_and_args(sig: &Signature) -> syn::Result<MethodInfo> {
let mut self_kind = SelfKind::None;
let mut has_lua = false;
let mut args = Vec::new();
let mut check_first_typed = true;
for param in &sig.inputs {
match param {
FnArg::Receiver(recv) if recv.reference.is_some() && recv.mutability.is_some() => {
self_kind = SelfKind::Ref(RefKind::Mut);
}
FnArg::Receiver(recv) if recv.reference.is_some() => {
self_kind = SelfKind::Ref(RefKind::Ref);
}
FnArg::Receiver(_) => {
self_kind = SelfKind::Owned;
}
FnArg::Typed(typed) => {
if check_first_typed && is_lua_ref(&typed.ty) {
has_lua = true;
check_first_typed = false;
continue;
}
check_first_typed = false;
if let syn::Pat::Ident(pat_ident) = &*typed.pat {
let arg_type = &*typed.ty;
let ref_kind = match arg_type {
Type::Reference(r) if r.mutability.is_some() => Some(RefKind::Mut),
Type::Reference(_) => Some(RefKind::Ref),
_ => None,
};
let callback_type = match &ref_kind {
Some(_) => match classify_ref_type(arg_type) {
Some(ty) => ty,
None => {
return Err(syn::Error::new_spanned(
arg_type,
"this reference type is not supported as a callback parameter",
));
}
},
None => arg_type.clone(),
};
args.push(ArgInfo {
ident: pat_ident.ident.clone(),
userdata_ref: ref_kind,
callback_type,
});
}
}
}
}
Ok(MethodInfo {
self_kind,
has_lua,
args,
})
}
fn strip_item_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
(attrs.iter())
.filter(|attr| !attr.path().is_ident("lua"))
.cloned()
.collect()
}
fn parse_lua_attr(attrs: &[Attribute]) -> syn::Result<LuaAttr> {
let mut lua_attr = LuaAttr::default();
for attr in attrs {
if !attr.path().is_ident("lua") {
continue;
}
match &attr.meta {
Meta::List(_) => {
lua_attr.span = Some(attr.span());
attr.parse_nested_meta(|meta| lua_attr.parse_inner(meta))?;
validate_lua_attr(&lua_attr)?;
}
Meta::Path(_) => {}
Meta::NameValue(_) => {
return Err(syn::Error::new_spanned(
attr,
"`#[lua = \"...\"]` is not supported: use `#[lua(attr = \"...\")]`",
));
}
}
}
Ok(lua_attr)
}
fn validate_lua_attr(attr: &LuaAttr) -> syn::Result<()> {
for (set, name) in [(attr.get, "get"), (attr.set, "set")] {
if set {
return Err(syn::Error::new(
attr.span(),
format!("`{name}` is not valid for methods"),
));
}
}
Ok(())
}
pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return syn::Error::new_spanned(
proc_macro2::TokenStream::from(attr),
"`#[userdata_impl]` does not accept arguments",
)
.to_compile_error()
.into();
}
let mut input = parse_macro_input!(item as ItemImpl);
let type_path = match &*input.self_ty {
Type::Path(type_path) => &type_path.path,
_ => {
return syn::Error::new_spanned(&input.self_ty, "`#[userdata_impl]` requires a simple path type")
.to_compile_error()
.into();
}
};
let type_name = (type_path.segments)
.last()
.map(|seg| seg.ident.clone())
.ok_or_else(|| syn::Error::new_spanned(&input.self_ty, "cannot determine type name"));
let type_name = try_compile!(type_name);
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let unique_suffix = COUNTER.fetch_add(1, Ordering::Relaxed);
let register_fn_name = format_ident!("__mlua_register_{type_name}_{unique_suffix}");
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
let mut registration_calls = Vec::new();
for item in &input.items {
match item {
ImplItem::Const(const_item) => {
let lua_attr = try_compile!(parse_lua_attr(&const_item.attrs));
if lua_attr.skip {
continue;
}
if lua_attr.getter || lua_attr.setter {
return syn::Error::new(
lua_attr.span(),
"const items do not support `getter` or `setter`",
)
.to_compile_error()
.into();
}
let const_name = &const_item.ident;
let lua_name = lua_attr.name(const_name);
if lua_attr.meta {
let tokens = quote! {
registry.add_meta_field(#lua_name, #type_path::#const_name);
};
registration_calls.push(with_cfg(tokens, &const_item.attrs));
} else {
let tokens = quote! {
registry.add_field(#lua_name, #type_path::#const_name);
};
registration_calls.push(with_cfg(tokens, &const_item.attrs));
}
}
ImplItem::Fn(method) => {
let lua_attr = try_compile!(parse_lua_attr(&method.attrs));
if lua_attr.skip {
continue;
}
// Validate mutually exclusive role flags.
// `getter`, `setter`, `field` are exclusive.
// `meta` on its own means a metamethod.
// `meta` combined with `field` means a meta static field.
// `meta` with `getter` or `setter` is invalid.
let primary = [lua_attr.getter, lua_attr.setter, lua_attr.field];
let primary_count = primary.iter().filter(|&&x| x).count();
if primary_count > 1 {
return syn::Error::new(
lua_attr.span(),
"at most one of `getter`, `setter`, `field` can be specified",
)
.to_compile_error()
.into();
}
if lua_attr.meta && primary_count == 1 && !lua_attr.field {
return syn::Error::new(lua_attr.span(), "`meta` can only be combined with `field`")
.to_compile_error()
.into();
}
let fn_name = &method.sig.ident;
let info = try_compile!(analyze_self_and_args(&method.sig));
let is_async = method.sig.asyncness.is_some();
if lua_attr.getter {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field getter is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::Ref(RefKind::Ref)) {
return syn::Error::new_spanned(&method.sig, "field getter must take `&self`")
.to_compile_error()
.into();
}
if !info.args.is_empty() {
return syn::Error::new_spanned(
&method.sig,
"field getter must not take additional arguments",
)
.to_compile_error()
.into();
}
let tokens = gen_field_getter(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
continue;
}
if lua_attr.setter {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field setter is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::Ref(_)) {
return syn::Error::new_spanned(&method.sig, "field setter must take `&[mut] self`")
.to_compile_error()
.into();
}
if info.args.len() != 1 {
return syn::Error::new_spanned(
&method.sig,
"field setter must take exactly one value argument",
)
.to_compile_error()
.into();
}
let tokens = gen_field_setter(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
continue;
}
if lua_attr.field {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field function is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::None) {
return syn::Error::new_spanned(&method.sig, "field function must not take `self`")
.to_compile_error()
.into();
}
if !info.args.is_empty() {
return syn::Error::new_spanned(
&method.sig,
"field function must not take arguments",
)
.to_compile_error()
.into();
}
let lua_name = lua_attr.name(fn_name);
if lua_attr.meta {
let tokens = quote! {
registry.add_meta_field(#lua_name, #type_path::#fn_name());
};
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = quote! {
registry.add_field(#lua_name, #type_path::#fn_name());
};
registration_calls.push(with_cfg(tokens, &method.attrs));
}
continue;
}
if lua_attr.meta {
if matches!(info.self_kind, SelfKind::Owned) {
return syn::Error::new_spanned(
&method.sig,
"meta methods cannot take `self`, use `&[mut] self` instead",
)
.to_compile_error()
.into();
}
if is_async {
let tokens = gen_async_meta(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = gen_meta(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
}
continue;
}
if is_async {
let tokens = gen_async_regular_method(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = gen_regular_method(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
}
}
_ => {}
}
}
for item in &mut input.items {
match item {
ImplItem::Const(c) => c.attrs = strip_item_attrs(&c.attrs),
ImplItem::Fn(m) => m.attrs = strip_item_attrs(&m.attrs),
_ => {}
}
}
input.attrs = strip_item_attrs(&input.attrs);
let output = quote! {
#[allow(non_snake_case)]
fn #register_fn_name(registry: &mut ::mlua::userdata::UserDataRegistry<#type_path>) {
use ::mlua::userdata::{UserDataFields as _, UserDataMethods as _};
#(#registration_calls)*
}
::mlua::__inventory::submit! {
#registration_type_name { register: #register_fn_name }
}
#input
};
output.into()
}
/// Generate the closure argument destructuring pattern.
fn gen_closure_destructure(info: &MethodInfo) -> TokenStream2 {
if info.args.is_empty() {
return quote! { () };
}
let idents: Vec<_> = (info.args)
.iter()
.map(|a| {
let ident = &a.ident;
if matches!(a.userdata_ref, Some(RefKind::Mut)) {
quote! { mut #ident }
} else {
quote! { #ident }
}
})
.collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { (#(#idents),*): (#(#types),*) }
}
/// Generate call arguments for invoking the original method.
fn gen_call_args(info: &MethodInfo) -> TokenStream2 {
let mut call_args: Vec<TokenStream2> = Vec::new();
match info.self_kind {
SelfKind::None => {}
_ => call_args.push(quote! { this }),
}
if info.has_lua {
call_args.push(quote! { lua });
}
for arg in &info.args {
let ident = &arg.ident;
match arg.userdata_ref {
Some(RefKind::Ref) => call_args.push(quote! { &*#ident }),
Some(RefKind::Mut) => call_args.push(quote! { &mut *#ident }),
None => call_args.push(quote! { #ident }),
}
}
quote! { #(#call_args),* }
}
/// Generate call arguments for invoking the original async method.
fn gen_async_call_args(info: &MethodInfo) -> TokenStream2 {
let mut call_args: Vec<TokenStream2> = Vec::new();
match info.self_kind {
SelfKind::None => {}
SelfKind::Ref(RefKind::Ref) => call_args.push(quote! { &this }),
SelfKind::Ref(RefKind::Mut) => call_args.push(quote! { &mut this }),
SelfKind::Owned => call_args.push(quote! { this }),
}
if info.has_lua {
call_args.push(quote! { lua });
}
for arg in &info.args {
let ident = &arg.ident;
match arg.userdata_ref {
Some(RefKind::Ref) => call_args.push(quote! { &*#ident }),
Some(RefKind::Mut) => call_args.push(quote! { &mut *#ident }),
None => call_args.push(quote! { #ident }),
}
}
quote! { #(#call_args),* }
}
/// Generate the closure params for the registration callback.
fn gen_closure_params(info: &MethodInfo) -> TokenStream2 {
let destructure = gen_closure_destructure(info);
match info.self_kind {
SelfKind::None => quote! { |lua, #destructure| },
_ => quote! { |lua, this, #destructure| },
}
}
/// Generate the closure params for an async registration callback.
fn gen_async_closure_params(info: &MethodInfo) -> TokenStream2 {
let destructure = gen_closure_destructure(info);
match info.self_kind {
SelfKind::None => quote! { |lua, #destructure| },
SelfKind::Ref(RefKind::Mut) => quote! { |lua, mut this, #destructure| },
_ => quote! { |lua, this, #destructure| },
}
}
fn gen_field_getter(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let lua_name = lua_attr.name(fn_name);
let call_args = gen_call_args(info);
if lua_attr.infallible {
return quote! {
registry.add_field_method_get(#lua_name, |lua, this| {
let _ = lua; // silence unused variable warning
Ok(#type_path::#fn_name(#call_args))
});
};
}
quote! {
registry.add_field_method_get(#lua_name, |lua, this| {
let _ = lua; // silence unused variable warning
#type_path::#fn_name(#call_args)
});
}
}
fn gen_field_setter(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let lua_name = lua_attr.name(fn_name);
let call_args = gen_call_args(info);
if lua_attr.infallible {
let val_ident = info.args.first().map(|a| &a.ident);
return quote! {
registry.add_field_method_set(#lua_name, |lua, this, #val_ident| {
let _ = lua; // silence unused variable warning
Ok(#type_path::#fn_name(#call_args))
});
};
}
let val_ident = info.args.first().map(|a| &a.ident);
quote! {
registry.add_field_method_set(#lua_name, |lua, this, #val_ident| {
let _ = lua; // silence unused variable warning
#type_path::#fn_name(#call_args)
});
}
}
fn gen_meta(type_path: &syn::Path, fn_name: &Ident, lua_attr: &LuaAttr, info: &MethodInfo) -> TokenStream2 {
let meta_name = match lua_attr.effective_meta_name(fn_name) {
Ok(name) => name,
Err(err) => return err.to_compile_error(),
};
let closure_params = if matches!(info.self_kind, SelfKind::None) {
// Lua always passes `self` to the stack arg, just ignore it.
if info.args.is_empty() {
quote! { |lua, _this: ::mlua::AnyUserData| }
} else {
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
}
} else {
gen_closure_params(info)
};
let call_args = gen_call_args(info);
let fn_path = quote! { #type_path::#fn_name };
let body = if lua_attr.infallible {
quote! { Ok(#fn_path(#call_args)) }
} else {
quote! { #fn_path(#call_args) }
};
match info.self_kind {
SelfKind::None => quote! {
registry.add_meta_function(#meta_name, #closure_params { #body });
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_meta_method_mut(#meta_name, #closure_params { #body });
},
_ => quote! {
registry.add_meta_method(#meta_name, #closure_params { #body });
},
}
}
fn gen_regular_method(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let fn_path = quote! { #type_path::#fn_name };
let closure_params = gen_closure_params(info);
let call_args = gen_call_args(info);
let lua_name = lua_attr.name(fn_name);
let body = if lua_attr.infallible {
quote! { Ok(#fn_path(#call_args)) }
} else {
quote! { #fn_path(#call_args) }
};
match info.self_kind {
SelfKind::Ref(RefKind::Ref) => quote! {
registry.add_method(#lua_name, #closure_params { #body });
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_method_mut(#lua_name, #closure_params { #body });
},
SelfKind::Owned => quote! {
registry.add_method_once(#lua_name, #closure_params { #body });
},
SelfKind::None => quote! {
registry.add_function(#lua_name, #closure_params { #body });
},
}
}
fn gen_async_regular_method(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let fn_path = quote! { #type_path::#fn_name };
let closure_params = gen_async_closure_params(info);
let call_args = gen_async_call_args(info);
let lua_name = lua_attr.name(fn_name);
let body = if lua_attr.infallible {
quote! { async move { Ok(#fn_path(#call_args).await) } }
} else {
quote! { async move { #fn_path(#call_args).await } }
};
match info.self_kind {
SelfKind::Ref(RefKind::Ref) => quote! {
registry.add_async_method(#lua_name, #closure_params #body);
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_async_method_mut(#lua_name, #closure_params #body);
},
SelfKind::Owned => quote! {
registry.add_async_method_once(#lua_name, #closure_params #body);
},
SelfKind::None => quote! {
registry.add_async_function(#lua_name, #closure_params #body);
},
}
}
fn gen_async_meta(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let meta_name = match lua_attr.effective_meta_name(fn_name) {
Ok(name) => name,
Err(err) => return err.to_compile_error(),
};
let closure_params = if matches!(info.self_kind, SelfKind::None) {
if info.args.is_empty() {
quote! { |lua, _this: ::mlua::AnyUserData| }
} else {
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
}
} else {
gen_async_closure_params(info)
};
let call_args = gen_async_call_args(info);
let fn_path = quote! { #type_path::#fn_name };
let body = if lua_attr.infallible {
quote! { async move { Ok(#fn_path(#call_args).await) } }
} else {
quote! { async move { #fn_path(#call_args).await } }
};
match info.self_kind {
SelfKind::None => quote! {
registry.add_async_meta_function(#meta_name, #closure_params #body);
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_async_meta_method_mut(#meta_name, #closure_params #body);
},
_ => quote! {
registry.add_async_meta_method(#meta_name, #closure_params #body);
},
}
}
+17 -98
View File
@@ -1,9 +1,6 @@
use std::io;
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
use serde::ser::{Serialize, Serializer};
use crate::state::RawLua;
use crate::types::ValueRef;
/// A Luau buffer type.
@@ -19,17 +16,16 @@ pub struct Buffer(pub(crate) ValueRef);
impl Buffer {
/// Copies the buffer data into a new `Vec<u8>`.
pub fn to_vec(&self) -> Vec<u8> {
let lua = self.0.lua.lock();
self.as_slice(&lua).to_vec()
unsafe { self.as_slice().to_vec() }
}
/// Returns the length of the buffer.
pub fn len(&self) -> usize {
let lua = self.0.lua.lock();
self.as_slice(&lua).len()
unsafe { self.as_slice().len() }
}
/// Returns `true` if the buffer is empty.
#[doc(hidden)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
@@ -39,8 +35,7 @@ impl Buffer {
/// Offset is 0-based.
#[track_caller]
pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
let lua = self.0.lua.lock();
let data = self.as_slice(&lua);
let data = unsafe { self.as_slice() };
let mut bytes = [0u8; N];
bytes.copy_from_slice(&data[offset..offset + N]);
bytes
@@ -51,36 +46,21 @@ impl Buffer {
/// Offset is 0-based.
#[track_caller]
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
let lua = self.0.lua.lock();
let data = self.as_slice_mut(&lua);
let data = unsafe {
let (buf, size) = self.as_raw_parts();
std::slice::from_raw_parts_mut(buf, size)
};
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 an 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);
std::slice::from_raw_parts(buf, size)
}
}
#[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)
}
pub(crate) unsafe fn as_slice(&self) -> &[u8] {
let (buf, size) = self.as_raw_parts();
std::slice::from_raw_parts(buf, size)
}
#[cfg(feature = "luau")]
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
let lua = self.0.lua.lock();
let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
@@ -88,76 +68,15 @@ impl Buffer {
}
#[cfg(not(feature = "luau"))]
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
unreachable!()
}
}
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")]
#[cfg(feature = "serialize")]
impl Serialize for Buffer {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let lua = self.0.lua.lock();
serializer.serialize_bytes(self.as_slice(&lua))
serializer.serialize_bytes(unsafe { self.as_slice() })
}
}
+111 -298
View File
@@ -1,16 +1,11 @@
//! Lua chunk loading and execution.
//!
//! This module provides types for loading Lua source code or bytecode into a [`Chunk`],
//! configuring how it is compiled and executed, and converting it into a callable [`Function`].
//!
//! Chunks can be loaded from strings, byte slices, or files via the [`AsChunk`] trait.
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Result as IoResult;
use std::marker::PhantomData;
use std::panic::Location;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::function::Function;
@@ -22,11 +17,11 @@ use crate::value::Value;
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
pub trait AsChunk {
pub trait AsChunk<'a> {
/// Returns optional chunk name
///
/// See [`Chunk::set_name`] for possible name prefixes.
fn name(&self) -> Option<String> {
fn name(&self) -> Option<StdString> {
None
}
@@ -44,105 +39,70 @@ pub trait AsChunk {
}
/// Returns chunk data (can be text or binary)
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a;
fn source(self) -> IoResult<Cow<'a, [u8]>>;
}
impl AsChunk for &str {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
impl<'a> AsChunk<'a> for &'a str {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk<'static> for StdString {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self.into_bytes()))
}
}
impl<'a> AsChunk<'a> for &'a StdString {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl AsChunk for String {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone().into_bytes()))
}
}
impl AsChunk for &String {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl AsChunk for &[u8] {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
impl<'a> AsChunk<'a> for &'a [u8] {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl AsChunk for Vec<u8> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone()))
impl AsChunk<'static> for Vec<u8> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self))
}
}
impl AsChunk for &Vec<u8> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self))
impl<'a> AsChunk<'a> for &'a Vec<u8> {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk for &Path {
fn name(&self) -> Option<String> {
impl AsChunk<'static> for &Path {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl AsChunk for PathBuf {
fn name(&self) -> Option<String> {
impl AsChunk<'static> for PathBuf {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
fn name(&self) -> Option<String> {
(**self).name()
}
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
(**self).environment(lua)
}
fn mode(&self) -> Option<ChunkMode> {
(**self).mode()
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
(**self).source()
}
}
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
pub struct Chunk<'a> {
pub(crate) lua: WeakLua,
pub(crate) name: String,
pub(crate) name: StdString,
pub(crate) env: Result<Option<Table>>,
pub(crate) mode: Option<ChunkMode>,
pub(crate) source: IoResult<Cow<'a, [u8]>>,
@@ -157,50 +117,6 @@ pub enum ChunkMode {
Binary,
}
/// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum CompileConstant {
Nil,
Boolean(bool),
Number(crate::Number),
Vector(crate::Vector),
String(String),
}
#[cfg(any(feature = "luau", doc))]
impl From<bool> for CompileConstant {
fn from(b: bool) -> Self {
CompileConstant::Boolean(b)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<crate::Number> for CompileConstant {
fn from(n: crate::Number) -> Self {
CompileConstant::Number(n)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<crate::Vector> for CompileConstant {
fn from(v: crate::Vector) -> Self {
CompileConstant::Vector(v)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<&str> for CompileConstant {
fn from(s: &str) -> Self {
CompileConstant::String(s.to_owned())
}
}
#[cfg(any(feature = "luau", doc))]
type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>;
/// Luau compiler
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -215,9 +131,6 @@ pub struct Compiler {
vector_type: Option<String>,
mutable_globals: Vec<String>,
userdata_types: Vec<String>,
libraries_with_known_members: Vec<String>,
library_constants: Option<LibraryMemberConstantMap>,
disabled_builtins: Vec<String>,
}
#[cfg(any(feature = "luau", doc))]
@@ -242,9 +155,6 @@ impl Compiler {
vector_type: None,
mutable_globals: Vec::new(),
userdata_types: Vec::new(),
libraries_with_known_members: Vec::new(),
library_constants: None,
disabled_builtins: Vec::new(),
}
}
@@ -277,7 +187,6 @@ impl Compiler {
/// Possible values:
/// * 0 - generate for native modules (default)
/// * 1 - generate for all modules
#[must_use]
pub const fn set_type_info_level(mut self, level: u8) -> Self {
self.type_info_level = level;
self
@@ -295,23 +204,20 @@ impl Compiler {
self
}
/// Sets alternative global builtin to construct vectors, in addition to default builtin
/// `vector.create`.
///
/// To set the library and method name, use the `lib.ctor` format.
#[doc(hidden)]
#[must_use]
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
let ctor = ctor.into();
let lib_ctor = ctor.split_once('.');
self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
self.vector_ctor = (lib_ctor.as_ref())
.map(|&(_, ctor)| ctor.to_owned())
.or(Some(ctor));
pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
self.vector_lib = Some(lib.into());
self
}
#[doc(hidden)]
#[must_use]
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
self.vector_ctor = Some(ctor.into());
self
}
/// Sets alternative vector type name for type tables, in addition to default type `vector`.
#[doc(hidden)]
#[must_use]
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
@@ -319,76 +225,19 @@ impl Compiler {
self
}
/// Adds a mutable global.
///
/// It disables the import optimization for fields accessed through it.
#[must_use]
pub fn add_mutable_global(mut self, global: impl Into<String>) -> Self {
self.mutable_globals.push(global.into());
self
}
/// Sets a list of globals that are mutable.
///
/// It disables the import optimization for fields accessed through these.
#[must_use]
pub fn set_mutable_globals<S: Into<String>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
self
}
/// Adds a userdata type to the list that will be included in the type information.
#[must_use]
pub fn add_userdata_type(mut self, r#type: impl Into<String>) -> Self {
self.userdata_types.push(r#type.into());
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
self.mutable_globals = globals;
self
}
/// Sets a list of userdata types that will be included in the type information.
#[must_use]
pub fn set_userdata_types<S: Into<String>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
self
}
/// Adds a constant for a known library member.
///
/// The constants are used by the compiler to optimize the generated bytecode.
/// Optimization level must be at least 2 for this to have any effect.
///
/// The `name` is a string in the format `lib.member`, where `lib` is the library name
/// and `member` is the member (constant) name.
#[must_use]
pub fn add_library_constant(
mut self,
name: impl AsRef<str>,
r#const: impl Into<CompileConstant>,
) -> Self {
let Some((lib, member)) = name.as_ref().split_once('.') else {
return self;
};
let (lib, member) = (lib.to_owned(), member.to_owned());
if !self.libraries_with_known_members.contains(&lib) {
self.libraries_with_known_members.push(lib.clone());
}
self.library_constants
.get_or_insert_default()
.insert((lib, member), r#const.into());
self
}
/// Adds a builtin that should be disabled.
#[must_use]
pub fn add_disabled_builtin(mut self, builtin: impl Into<String>) -> Self {
self.disabled_builtins.push(builtin.into());
self
}
/// Sets a list of builtins that should be disabled.
#[must_use]
pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: impl IntoIterator<Item = S>) -> Self {
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
self.userdata_types = types;
self
}
@@ -396,9 +245,7 @@ impl Compiler {
///
/// Returns [`Error::SyntaxError`] if the source code is invalid.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
use std::cell::RefCell;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use std::ptr;
let vector_lib = self.vector_lib.clone();
@@ -430,44 +277,6 @@ impl Compiler {
vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
vec2cstring_ptr!(userdata_types, userdata_types_ptr);
vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
thread_local! {
static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
}
#[cfg(feature = "luau")]
unsafe extern "C-unwind" fn library_member_constant_callback(
library: *const c_char,
member: *const c_char,
constant: *mut ffi::lua_CompileConstant,
) {
let library = CStr::from_ptr(library).to_string_lossy();
let member = CStr::from_ptr(member).to_string_lossy();
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
match cons {
CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
CompileConstant::Boolean(b) => {
ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
}
CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
CompileConstant::Vector(v) => {
#[cfg(not(feature = "luau-vector4"))]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
#[cfg(feature = "luau-vector4")]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
}
CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
constant,
s.as_ptr() as *const c_char,
s.len(),
),
}
}
})
}
let bytecode = unsafe {
let mut options = ffi::lua_CompileOptions::default();
@@ -480,21 +289,13 @@ impl Compiler {
options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr;
options.librariesWithKnownMembers = libraries_with_known_members_ptr;
if let Some(map) = self.library_constants.as_ref()
&& !self.libraries_with_known_members.is_empty()
{
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
options.libraryMemberConstantCallback = Some(library_member_constant_callback);
}
options.disabledBuiltins = disabled_builtins_ptr;
ffi::luau_compile(source.as_ref(), options)
};
if bytecode.first() == Some(&0) {
// The rest of the bytecode is the error message starting with `:`
// See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336
let message = String::from_utf8_lossy(&bytecode[2..]).into_owned();
let message = String::from_utf8_lossy(&bytecode[2..]).to_string();
return Err(Error::SyntaxError {
incomplete_input: message.ends_with("<eof>"),
message,
@@ -560,6 +361,8 @@ impl Chunk<'_> {
/// Sets or overwrites a Luau compiler used for this chunk.
///
/// See [`Compiler`] for details and possible options.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_compiler(mut self, compiler: Compiler) -> Self {
@@ -578,6 +381,8 @@ impl Chunk<'_> {
///
/// See [`exec`] for more details.
///
/// Requires `feature = "async"`
///
/// [`exec`]: Chunk::exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -608,6 +413,8 @@ impl Chunk<'_> {
///
/// See [`eval`] for more details.
///
/// Requires `feature = "async"`
///
/// [`eval`]: Chunk::eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -635,6 +442,8 @@ impl Chunk<'_> {
///
/// See [`call`] for more details.
///
/// Requires `feature = "async"`
///
/// [`call`]: Chunk::call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -666,19 +475,19 @@ impl Chunk<'_> {
///
/// It does nothing if the chunk is already binary or invalid.
fn compile(&mut self) {
if let Ok(ref source) = self.source
&& self.detect_mode() == ChunkMode::Text
{
#[cfg(feature = "luau")]
if let Ok(data) = self.compiler.get_or_insert_default().compile(source) {
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
#[cfg(not(feature = "luau"))]
if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
let data = func.dump(false);
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
if let Ok(ref source) = self.source {
if self.detect_mode() == ChunkMode::Text {
#[cfg(feature = "luau")]
if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) {
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
#[cfg(not(feature = "luau"))]
if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
let data = func.dump(false);
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
}
}
}
@@ -691,33 +500,33 @@ impl Chunk<'_> {
// Try to fetch compiled chunk from cache
let mut text_source = None;
if let Ok(ref source) = self.source
&& self.detect_mode() == ChunkMode::Text
{
let lua = self.lua.lock();
if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>()
&& let Some(data) = cache.0.get(source.as_ref())
{
self.source = Ok(Cow::Owned(data.clone()));
self.mode = Some(ChunkMode::Binary);
return self;
if let Ok(ref source) = self.source {
if self.detect_mode() == ChunkMode::Text {
let lua = self.lua.lock();
if let Some(cache) = lua.app_data_ref_unguarded::<ChunksCache>() {
if let Some(data) = cache.0.get(source.as_ref()) {
self.source = Ok(Cow::Owned(data.clone()));
self.mode = Some(ChunkMode::Binary);
return self;
}
}
text_source = Some(source.as_ref().to_vec());
}
text_source = Some(source.as_ref().to_vec());
}
// Compile and cache the chunk
if let Some(text_source) = text_source {
self.compile();
if let Ok(ref binary_source) = self.source
&& self.detect_mode() == ChunkMode::Binary
{
let lua = self.lua.lock();
if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
cache.0.insert(text_source, binary_source.to_vec());
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.to_vec());
lua.set_priv_app_data(cache);
if let Ok(ref binary_source) = self.source {
if self.detect_mode() == ChunkMode::Binary {
let lua = self.lua.lock();
if let Some(mut cache) = lua.app_data_mut_unguarded::<ChunksCache>() {
cache.0.insert(text_source, binary_source.as_ref().to_vec());
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.as_ref().to_vec());
let _ = lua.try_set_app_data(cache);
};
}
}
}
@@ -749,20 +558,21 @@ impl Chunk<'_> {
}
fn detect_mode(&self) -> ChunkMode {
if let Some(mode) = self.mode {
return mode;
}
if let Ok(source) = &self.source {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
match (self.mode, &self.source) {
(Some(mode), _) => mode,
(None, Ok(source)) => {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
}
ChunkMode::Text
}
(None, Err(_)) => ChunkMode::Text, // any value is fine
}
ChunkMode::Text
}
fn convert_name(name: String) -> Result<CString> {
@@ -777,26 +587,29 @@ impl Chunk<'_> {
}
}
struct WrappedChunk<T: AsChunk> {
struct WrappedChunk<'a, T: AsChunk<'a>> {
chunk: T,
caller: &'static Location<'static>,
_marker: PhantomData<&'a T>,
}
impl Chunk<'_> {
impl<'a> Chunk<'a> {
/// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait.
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a {
WrappedChunk {
chunk,
caller: Location::caller(),
_marker: PhantomData,
}
}
}
impl<T: AsChunk> IntoLua for WrappedChunk<T> {
impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.load_with_location(self.chunk, self.caller)
.into_function()
+261 -179
View File
@@ -4,19 +4,20 @@ use std::ffi::{CStr, CString, OsStr, OsString};
use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::{slice, str};
use std::string::String as StdString;
use std::{mem, slice, str};
use bstr::{BStr, BString, ByteVec};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use num_traits::cast;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{Lua, RawLua};
use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
use crate::string::{BorrowedBytes, BorrowedStr, String};
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
use crate::types::{Either, LightUserData, MaybeSend, MaybeSync, RegistryKey};
use crate::types::{Either, LightUserData, MaybeSend, RegistryKey};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{Nil, Value};
@@ -46,14 +47,14 @@ impl FromLua for Value {
}
}
impl IntoLua for LuaString {
impl IntoLua for String {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self))
}
}
impl IntoLua for &LuaString {
impl IntoLua for &String {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.clone()))
@@ -66,12 +67,16 @@ impl IntoLua for &LuaString {
}
}
impl FromLua for LuaString {
impl FromLua for String {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<LuaString> {
fn from_lua(value: Value, lua: &Lua) -> Result<String> {
let ty = value.type_name();
lua.coerce_string(value)?
.ok_or_else(|| Error::from_lua_conversion(ty, "string", "expected string or number".to_string()))
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "string".to_string(),
message: Some("expected string or number".to_string()),
})
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -79,86 +84,98 @@ impl FromLua for LuaString {
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TSTRING {
ffi::lua_xpush(state, lua.ref_thread(), idx);
return Ok(LuaString(lua.pop_ref_thread()));
return Ok(String(lua.pop_ref_thread()));
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
}
impl IntoLua for BorrowedStr {
impl IntoLua for BorrowedStr<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(LuaString(self.vref)))
Ok(Value::String(self.borrow.into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.vref);
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl IntoLua for &BorrowedStr {
impl IntoLua for &BorrowedStr<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(LuaString(self.vref.clone())))
Ok(Value::String(self.borrow.clone().into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.vref);
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl FromLua for BorrowedStr {
impl FromLua for BorrowedStr<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?;
BorrowedStr::try_from(&s)
let s = String::from_lua(value, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?;
BorrowedStr::try_from(&s)
let s = String::from_stack(idx, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
}
impl IntoLua for BorrowedBytes {
impl IntoLua for BorrowedBytes<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(LuaString(self.vref)))
Ok(Value::String(self.borrow.into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.vref);
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl IntoLua for &BorrowedBytes {
impl IntoLua for &BorrowedBytes<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(LuaString(self.vref.clone())))
Ok(Value::String(self.borrow.clone().into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.vref);
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl FromLua for BorrowedBytes {
impl FromLua for BorrowedBytes<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?;
Ok(BorrowedBytes::from(&s))
let s = String::from_lua(value, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?;
Ok(BorrowedBytes::from(&s))
let s = String::from_stack(idx, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
}
@@ -187,7 +204,11 @@ impl FromLua for Table {
fn from_lua(value: Value, _: &Lua) -> Result<Table> {
match value {
Value::Table(table) => Ok(table),
_ => Err(Error::from_lua_conversion(value.type_name(), "table", None)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "table".to_string(),
message: None,
}),
}
}
}
@@ -217,7 +238,11 @@ impl FromLua for Function {
fn from_lua(value: Value, _: &Lua) -> Result<Function> {
match value {
Value::Function(table) => Ok(table),
_ => Err(Error::from_lua_conversion(value.type_name(), "function", None)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "function".to_string(),
message: None,
}),
}
}
}
@@ -247,7 +272,11 @@ impl FromLua for Thread {
fn from_lua(value: Value, _: &Lua) -> Result<Thread> {
match value {
Value::Thread(t) => Ok(t),
_ => Err(Error::from_lua_conversion(value.type_name(), "thread", None)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "thread".to_string(),
message: None,
}),
}
}
}
@@ -277,12 +306,16 @@ impl FromLua for AnyUserData {
fn from_lua(value: Value, _: &Lua) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
_ => Err(Error::from_lua_conversion(value.type_name(), "userdata", None)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata".to_string(),
message: None,
}),
}
}
}
impl<T: UserData + MaybeSend + MaybeSync + 'static> IntoLua for T {
impl<T: UserData + MaybeSend + 'static> IntoLua for T {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::UserData(lua.create_userdata(self)?))
@@ -395,11 +428,11 @@ impl FromLua for LightUserData {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::LightUserData(ud) => Ok(ud),
_ => Err(Error::from_lua_conversion(
value.type_name(),
"lightuserdata",
None,
)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "lightuserdata".to_string(),
message: None,
}),
}
}
}
@@ -418,7 +451,11 @@ impl FromLua for crate::Vector {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::Vector(v) => Ok(v),
_ => Err(Error::from_lua_conversion(value.type_name(), "vector", None)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "vector".to_string(),
message: None,
}),
}
}
}
@@ -451,41 +488,37 @@ impl FromLua for crate::Buffer {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::Buffer(buf) => Ok(buf),
_ => Err(Error::from_lua_conversion(value.type_name(), "buffer", None)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "buffer".to_string(),
message: None,
}),
}
}
}
impl IntoLua for String {
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)
}
}
impl FromLua for String {
impl FromLua for StdString {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name();
Ok(lua
.coerce_string(value)?
.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})?
.to_str()?
.to_owned())
@@ -501,7 +534,11 @@ impl FromLua for String {
let bytes = slice::from_raw_parts(data as *const u8, size);
return str::from_utf8(bytes)
.map(|s| s.to_owned())
.map_err(|e| Error::from_lua_conversion("string", Self::type_name(), e.to_string()));
.map_err(|e| Error::FromLuaConversionError {
from: "string",
to: Self::type_name(),
message: Some(e.to_string()),
});
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
@@ -523,10 +560,7 @@ impl IntoLua for &str {
impl IntoLua for Cow<'_, str> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
}
@@ -543,8 +577,10 @@ impl FromLua for Box<str> {
let ty = value.type_name();
Ok(lua
.coerce_string(value)?
.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})?
.to_str()?
.to_owned()
@@ -555,11 +591,6 @@ 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())?))
}
}
@@ -568,12 +599,21 @@ impl FromLua for CString {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name();
let string = lua.coerce_string(value)?.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
})?;
let string = lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})?;
match CStr::from_bytes_with_nul(&string.as_bytes_with_nul()) {
Ok(s) => Ok(s.into()),
Err(err) => Err(Error::from_lua_conversion(ty, Self::type_name(), err.to_string())),
Err(_) => Err(Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("invalid C-style string".to_string()),
}),
}
}
}
@@ -588,21 +628,13 @@ impl IntoLua for &CStr {
impl IntoLua for Cow<'_, CStr> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
Ok(Value::String(lua.create_string(self.to_bytes())?))
}
}
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)?))
}
}
@@ -613,11 +645,13 @@ impl FromLua for BString {
match value {
Value::String(s) => Ok((*s.as_bytes()).into()),
#[cfg(feature = "luau")]
Value::Buffer(buf) => Ok(buf.to_vec().into()),
Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) },
_ => Ok((*lua
.coerce_string(value)?
.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})?
.as_bytes())
.into()),
@@ -668,22 +702,23 @@ impl FromLua for OsString {
let bs = BString::from_lua(value, lua)?;
Vec::from(bs)
.into_os_string()
.map_err(|err| Error::from_lua_conversion(ty, "OsString", err.to_string()))
.map_err(|err| Error::FromLuaConversionError {
from: ty,
to: "OsString".into(),
message: Some(err.to_string()),
})
}
}
impl IntoLua for &OsStr {
#[cfg(unix)]
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
use std::os::unix::ffi::OsStrExt;
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
#[cfg(not(unix))]
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
self.display().to_string().into_lua(lua)
let s = <[u8]>::from_os_str(self).ok_or_else(|| Error::ToLuaConversionError {
from: "OsStr".into(),
to: "string",
message: Some("invalid utf-8 encoding".into()),
})?;
Ok(Value::String(lua.create_string(s)?))
}
}
@@ -721,25 +756,34 @@ impl FromLua for char {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
let ty = value.type_name();
match value {
Value::Integer(i) => cast(i).and_then(char::from_u32).ok_or_else(|| {
let msg = "integer out of range when converting to char";
Error::from_lua_conversion(ty, "char", msg.to_string())
}),
Value::Integer(i) => {
cast(i)
.and_then(char::from_u32)
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "char".to_string(),
message: Some("integer out of range when converting to char".to_string()),
})
}
Value::String(s) => {
let str = s.to_str()?;
let mut str_iter = str.chars();
match (str_iter.next(), str_iter.next()) {
(Some(char), None) => Ok(char),
_ => {
let msg = "expected string to have exactly one char when converting to char";
Err(Error::from_lua_conversion(ty, "char", msg.to_string()))
}
_ => Err(Error::FromLuaConversionError {
from: ty,
to: "char".to_string(),
message: Some(
"expected string to have exactly one char when converting to char".to_string(),
),
}),
}
}
_ => {
let msg = "expected string or integer";
Err(Error::from_lua_conversion(ty, Self::type_name(), msg.to_string()))
}
_ => Err(Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or integer".to_string()),
}),
}
}
}
@@ -764,9 +808,15 @@ macro_rules! lua_convert_int {
impl IntoLua for $x {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(cast(self)
cast(self)
.map(Value::Integer)
.unwrap_or_else(|| Value::Number(self as ffi::lua_Number)))
.or_else(|| cast(self).map(Value::Number))
// This is impossible error because conversion to Number never fails
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x).to_string(),
to: "number",
message: Some("out of range".to_owned()),
})
}
#[inline]
@@ -790,14 +840,24 @@ macro_rules! lua_convert_int {
if let Some(i) = lua.coerce_integer(value.clone())? {
cast(i)
} else {
cast(lua.coerce_number(value)?.ok_or_else(|| {
let msg = "expected number or string coercible to number";
Error::from_lua_conversion(ty, stringify!($x), msg.to_string())
})?)
cast(
lua.coerce_number(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some(
"expected number or string coercible to number".to_string(),
),
})?,
)
}
}
})
.ok_or_else(|| Error::from_lua_conversion(ty, stringify!($x), "out of range".to_string()))
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
})
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -807,18 +867,13 @@ macro_rules! lua_convert_int {
let mut ok = 0;
let i = ffi::lua_tointegerx(state, idx, &mut ok);
if ok != 0 {
return cast(i).ok_or_else(|| {
Error::from_lua_conversion("integer", stringify!($x), "out of range".to_string())
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
from: "integer",
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
});
}
}
#[cfg(feature = "luau")]
if type_id == ffi::LUA_TINTEGER {
let i = ffi::lua_tointeger64(state, idx, std::ptr::null_mut());
return cast(i).ok_or_else(|| {
Error::from_lua_conversion("integer", stringify!($x), "out of range".to_string())
});
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
@@ -844,7 +899,13 @@ macro_rules! lua_convert_float {
impl IntoLua for $x {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::Number(self as _))
cast(self)
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x).to_string(),
to: "number",
message: Some("out of range".to_string()),
})
.map(Value::Number)
}
}
@@ -852,17 +913,34 @@ macro_rules! lua_convert_float {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name();
lua.coerce_number(value)?.map(|n| n as $x).ok_or_else(|| {
let msg = "expected number or string coercible to number";
Error::from_lua_conversion(ty, stringify!($x), msg.to_string())
})
lua.coerce_number(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some("expected number or string coercible to number".to_string()),
})
.and_then(|n| {
cast(n).ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some("number out of range".to_string()),
})
})
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TNUMBER {
return Ok(ffi::lua_tonumber(state, idx) as _);
let mut ok = 0;
let i = ffi::lua_tonumberx(state, idx, &mut ok);
if ok != 0 {
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
from: "number",
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
});
}
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
@@ -915,16 +993,18 @@ where
},
Value::Table(table) => {
let vec = table.sequence_values().collect::<Result<Vec<_>>>()?;
vec.try_into().map_err(|vec: Vec<T>| {
let msg = format!("expected table of length {N}, got {}", vec.len());
Error::from_lua_conversion("table", Self::type_name(), msg)
})
}
_ => {
let msg = format!("expected table of length {N}");
let err = Error::from_lua_conversion(value.type_name(), Self::type_name(), msg.to_string());
Err(err)
vec.try_into()
.map_err(|vec: Vec<T>| Error::FromLuaConversionError {
from: "table",
to: Self::type_name(),
message: Some(format!("expected table of length {N}, got {}", vec.len())),
})
}
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
}
}
}
@@ -955,11 +1035,11 @@ impl<T: FromLua> FromLua for Vec<T> {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
match value {
Value::Table(table) => table.sequence_values().collect(),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
}
}
}
@@ -974,13 +1054,14 @@ impl<K: Eq + Hash + IntoLua, V: IntoLua, S: BuildHasher> IntoLua for HashMap<K,
impl<K: Eq + Hash + FromLua, V: FromLua, S: BuildHasher + Default> FromLua for HashMap<K, V, S> {
#[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::Table(table) => table.pairs().collect(),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
if let Value::Table(table) = value {
table.pairs().collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
})
}
}
}
@@ -995,13 +1076,14 @@ impl<K: Ord + IntoLua, V: IntoLua> IntoLua for BTreeMap<K, V> {
impl<K: Ord + FromLua, V: FromLua> FromLua for BTreeMap<K, V> {
#[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::Table(table) => table.pairs().collect(),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
if let Value::Table(table) = value {
table.pairs().collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
})
}
}
}
@@ -1021,11 +1103,11 @@ impl<T: Eq + Hash + FromLua, S: BuildHasher + Default> FromLua for HashSet<T, S>
match value {
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
}
}
}
@@ -1045,11 +1127,11 @@ impl<T: Ord + FromLua> FromLua for BTreeSet<T> {
match value {
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
}
}
}
@@ -1119,11 +1201,11 @@ impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
// Try the right type
Err(_) => match R::from_lua(value, lua).map(Either::Right) {
Ok(r) => Ok(r),
Err(_) => Err(Error::from_lua_conversion(
value_type_name,
Self::type_name(),
None,
)),
Err(_) => Err(Error::FromLuaConversionError {
from: value_type_name,
to: Self::type_name(),
message: None,
}),
},
}
}
@@ -1135,12 +1217,12 @@ 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 state = lua.state();
let from_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx)))
.to_str()
.unwrap_or("unknown");
let err = Error::from_lua_conversion(from_type_name, Self::type_name(), None);
Err(err)
let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx));
Err(Error::FromLuaConversionError {
from: value_type_name.to_str().unwrap(),
to: Self::type_name(),
message: None,
})
}
},
}
+55 -51
View File
@@ -1,14 +1,10 @@
//! Lua error handling.
//!
//! This module provides the [`Error`] type returned by all fallible `mlua` operations, together
//! with extension traits for adapting Rust errors for use within Lua.
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::result::Result as StdResult;
use std::str::Utf8Error;
use std::string::String as StdString;
use std::sync::Arc;
use crate::private::Sealed;
@@ -26,7 +22,7 @@ pub enum Error {
/// Syntax error while parsing Lua source code.
SyntaxError {
/// The error message as returned by Lua.
message: String,
message: StdString,
/// `true` if the error can likely be fixed by appending more input to the source code.
///
/// This is useful for implementing REPLs as they can query the user for more input if this
@@ -38,20 +34,20 @@ pub enum Error {
/// The Lua VM returns this error when a builtin operation is performed on incompatible types.
/// Among other things, this includes invoking operators on wrong types (such as calling or
/// indexing a `nil` value).
RuntimeError(String),
RuntimeError(StdString),
/// Lua memory error, aka `LUA_ERRMEM`
///
/// The Lua VM returns this error when the allocator does not return the requested memory, aka
/// it is an out-of-memory error.
MemoryError(String),
MemoryError(StdString),
/// Lua garbage collector error, aka `LUA_ERRGCMM`.
///
/// The Lua VM returns this error when there is an error running a `__gc` metamethod.
#[cfg(any(feature = "lua53", feature = "lua52", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua53", feature = "lua52"))))]
GarbageCollectorError(String),
GarbageCollectorError(StdString),
/// Potentially unsafe action in safe mode.
SafetyError(String),
SafetyError(StdString),
/// Memory control is not available.
///
/// This error can only happen when Lua state was not created by us and does not have the
@@ -84,14 +80,23 @@ pub enum Error {
/// (which is stored in the corresponding field).
BadArgument {
/// Function that was called.
to: Option<String>,
to: Option<StdString>,
/// Argument position (usually starts from 1).
pos: usize,
/// Argument name.
name: Option<String>,
name: Option<StdString>,
/// Underlying error returned when converting argument to a Lua value.
cause: Arc<Error>,
},
/// A Rust value could not be converted to a Lua value.
ToLuaConversionError {
/// Name of the Rust type that could not be converted.
from: String,
/// Name of the Lua type that could not be created.
to: &'static str,
/// A message indicating why the conversion failed in more detail.
message: Option<StdString>,
},
/// A Lua value could not be converted to the expected Rust type.
FromLuaConversionError {
/// Name of the Lua type that could not be converted.
@@ -99,7 +104,7 @@ pub enum Error {
/// Name of the Rust type that could not be created.
to: String,
/// A string containing more detailed error information.
message: Option<String>,
message: Option<StdString>,
},
/// [`Thread::resume`] was called on an unresumable coroutine.
///
@@ -149,17 +154,17 @@ pub enum Error {
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
///
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodRestricted(String),
MetaMethodRestricted(StdString),
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
///
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodTypeError {
/// Name of the metamethod.
method: String,
method: StdString,
/// Passed value type.
type_name: &'static str,
/// A string containing more detailed error information.
message: Option<String>,
message: Option<StdString>,
},
/// A [`RegistryKey`] produced from a different Lua state was used.
///
@@ -168,7 +173,7 @@ pub enum Error {
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
CallbackError {
/// Lua call stack backtrace.
traceback: String,
traceback: StdString,
/// Original error returned by the Rust code.
cause: Arc<Error>,
},
@@ -178,13 +183,13 @@ pub enum Error {
/// and returned again.
PreviouslyResumedPanic,
/// Serialization error.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
SerializeError(String),
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
SerializeError(StdString),
/// Deserialization error.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
DeserializeError(String),
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
DeserializeError(StdString),
/// A custom error.
///
/// This can be used for returning user-defined errors from callbacks.
@@ -196,7 +201,7 @@ pub enum Error {
/// An error with additional context.
WithContext {
/// A string containing additional context.
context: String,
context: StdString,
/// Underlying error.
cause: Arc<Error>,
},
@@ -220,7 +225,7 @@ impl fmt::Display for Error {
}
Error::SafetyError(msg) => {
write!(fmt, "safety error: {msg}")
}
},
Error::MemoryControlNotAvailable => {
write!(fmt, "memory control is not available")
}
@@ -233,7 +238,10 @@ impl fmt::Display for Error {
fmt,
"out of Lua stack, too many arguments to a Lua function or too many return values from a callback"
),
Error::BindError => write!(fmt, "too many arguments to Function::bind"),
Error::BindError => write!(
fmt,
"too many arguments to Function::bind"
),
Error::BadArgument { to, pos, name, cause } => {
if let Some(name) = name {
write!(fmt, "bad argument `{name}`")?;
@@ -244,6 +252,13 @@ impl fmt::Display for Error {
write!(fmt, " to `{to}`")?;
}
write!(fmt, ": {cause}")
},
Error::ToLuaConversionError { from, to, message } => {
write!(fmt, "error converting {from} to Lua {to}")?;
match message {
None => Ok(()),
Some(message) => write!(fmt, " ({message})"),
}
}
Error::FromLuaConversionError { from, to, message } => {
write!(fmt, "error converting Lua {from} to {to}")?;
@@ -258,11 +273,7 @@ impl fmt::Display for Error {
Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"),
Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"),
Error::MetaMethodRestricted(method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError {
method,
type_name,
message,
} => {
Error::MetaMethodTypeError { method, type_name, message } => {
write!(fmt, "metamethod {method} has unsupported type {type_name}")?;
match message {
None => Ok(()),
@@ -275,11 +286,7 @@ impl fmt::Display for Error {
Error::CallbackError { cause, traceback } => {
// Trace errors down to the root
let (mut cause, mut full_traceback) = (cause, None);
while let Error::CallbackError {
cause: cause2,
traceback: traceback2,
} = &**cause
{
while let Error::CallbackError { cause: cause2, traceback: traceback2 } = &**cause {
cause = cause2;
full_traceback = Some(traceback2);
}
@@ -290,7 +297,7 @@ impl fmt::Display for Error {
// Try to find local traceback within the full traceback
if let Some(pos) = full_traceback.find(traceback) {
write!(fmt, "{}", &full_traceback[..pos])?;
writeln!(fmt, ">{}", full_traceback[pos..].trim_end())?;
writeln!(fmt, ">{}", &full_traceback[pos..].trim_end())?;
} else {
writeln!(fmt, "{}", full_traceback.trim_end())?;
}
@@ -302,14 +309,14 @@ impl fmt::Display for Error {
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
Error::SerializeError(err) => {
write!(fmt, "serialize error: {err}")
}
#[cfg(feature = "serde")]
},
#[cfg(feature = "serialize")]
Error::DeserializeError(err) => {
write!(fmt, "deserialize error: {err}")
}
},
Error::ExternalError(err) => err.fmt(fmt),
Error::WithContext { context, cause } => {
writeln!(fmt, "{context}")?;
@@ -345,11 +352,7 @@ impl Error {
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
let boxed = err.into();
match boxed.downcast::<Self>() {
Ok(err) => *err,
Err(boxed) => Error::ExternalError(boxed.into()),
}
Error::ExternalError(err.into().into())
}
/// Attempts to downcast the external error object to a concrete type by reference.
@@ -391,7 +394,6 @@ impl Error {
}
}
#[inline]
pub(crate) fn from_lua_conversion(
from: &'static str,
to: impl ToString,
@@ -492,14 +494,14 @@ impl From<Utf8Error> for Error {
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::SerializeError(msg.to_string())
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::DeserializeError(msg.to_string())
@@ -559,8 +561,10 @@ impl<'a> Iterator for Chain<'a> {
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(not(feature = "error-send"))]
static_assertions::assert_not_impl_any!(super::Error: Send, Sync);
static_assertions::assert_not_impl_any!(Error: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(super::Error: Send, Sync);
static_assertions::assert_impl_all!(Error: Send, Sync);
}
+56 -291
View File
@@ -1,100 +1,22 @@
//! Lua function handling.
//!
//! This module provides types for working with Lua functions from Rust, including
//! both Lua-defined functions and native Rust callbacks.
//!
//! # Calling Functions
//!
//! Use [`Function::call`] to invoke a Lua function synchronously:
//!
//! ```
//! # use mlua::{Function, Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! // Get a built-in function
//! let print: Function = lua.globals().get("print")?;
//! print.call::<()>("Hello from Rust!")?;
//!
//! // Call a function that returns values
//! let tonumber: Function = lua.globals().get("tonumber")?;
//! let n: i32 = tonumber.call("42")?;
//! assert_eq!(n, 42);
//! # Ok(())
//! # }
//! ```
//!
//! For asynchronous execution, use `Function::call_async` (requires `async` feature):
//!
//! ```ignore
//! let result: String = my_async_func.call_async(args).await?;
//! ```
//!
//! # Creating Functions
//!
//! Functions can be created from Rust closures using [`Lua::create_function`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! let greet = lua.create_function(|_, name: String| {
//! Ok(format!("Hello, {}!", name))
//! })?;
//!
//! lua.globals().set("greet", greet)?;
//! let result: String = lua.load(r#"greet("World")"#).eval()?;
//! assert_eq!(result, "Hello, World!");
//! # Ok(())
//! # }
//! ```
//!
//! For simpler cases, use [`Function::wrap`] or [`Function::wrap_raw`] to convert a Rust function
//! directly:
//!
//! ```
//! # use mlua::{Function, Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! fn add(a: i32, b: i32) -> i32 { a + b }
//!
//! lua.globals().set("add", Function::wrap_raw(add))?;
//! let sum: i32 = lua.load("add(2, 3)").eval()?;
//! assert_eq!(sum, 5);
//! # Ok(())
//! # }
//! ```
//!
//! # Function Environments
//!
//! Lua functions have an associated environment table that determines how global
//! variables are resolved. Use [`Function::environment`] and [`Function::set_environment`]
//! to inspect or modify this environment.
use std::cell::RefCell;
use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::{mem, ptr, slice};
use crate::error::{Error, ExternalError, ExternalResult, Result};
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::table::Table;
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut};
use crate::types::{Callback, LuaType, MaybeSend, ValueRef};
use crate::util::{
StackGuard, assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard,
};
use crate::value::Value;
#[cfg(feature = "async")]
use {
crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
std::future::{self, Future},
std::pin::{Pin, pin},
std::task::{Context, Poll},
};
/// Handle to an internal Lua function.
@@ -107,7 +29,6 @@ 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>,
@@ -126,16 +47,6 @@ 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.
@@ -217,8 +128,9 @@ impl Function {
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internally it wraps the function to an [`AsyncThread`]. The returned type implements
/// `Future<Output = Result<R>>` and can be awaited.
/// Internally it wraps the function to an [`AsyncThread`].
///
/// Requires `feature = "async"`
///
/// # Examples
///
@@ -240,21 +152,22 @@ impl Function {
/// # }
/// ```
///
/// [`AsyncThread`]: crate::thread::AsyncThread
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
AsyncCallFuture(unsafe {
lua.create_recycled_thread(self).and_then(|th| {
let mut th = th.into_async(args)?;
let thread_res = unsafe {
lua.create_recycled_thread(self).map(|th| {
let mut th = th.into_async(args);
th.set_recyclable(true);
Ok(th)
th
})
})
};
async move { thread_res?.await }
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
@@ -339,7 +252,7 @@ impl Function {
"#,
)
.try_cache()
.set_name("=__mlua_bind")
.set_name("__mlua_bind")
.call((self, args_wrapper))
}
@@ -362,12 +275,12 @@ impl Function {
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_getfenv(state, -1);
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(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) {
s if s.is_null() => break,
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => break,
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => break,
_ => ffi::lua_pop(state, 1),
}
}
@@ -402,11 +315,11 @@ impl Function {
lua.push_ref(&env.0);
ffi::lua_setfenv(state, -2);
}
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(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),
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => {
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => {
ffi::lua_pop(state, 1);
// Create an anonymous function with the new environment
let f_with_env = lua
@@ -429,8 +342,7 @@ impl Function {
/// Returns information about the function.
///
/// Corresponds to the `>Snu` (`>Sn` for Luau) what mask for
/// [`lua_getinfo`] when applied to the function.
/// Corresponds to the `>Sn` 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 {
@@ -442,16 +354,11 @@ 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!(">Snu"), &mut ar);
#[cfg(not(feature = "luau"))]
mlua_assert!(res != 0, "lua_getinfo failed with `>Snu`");
let res = ffi::lua_getinfo(state, cstr!(">Sn"), &mut ar);
#[cfg(feature = "luau")]
let res = ffi::lua_getinfo(state, -1, cstr!("snau"), &mut ar);
#[cfg(feature = "luau")]
mlua_assert!(res != 0, "lua_getinfo failed with `snau`");
let res = ffi::lua_getinfo(state, -1, cstr!("sn"), &mut ar);
mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`");
FunctionInfo {
name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()),
@@ -473,14 +380,6 @@ 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,
}
}
}
@@ -500,14 +399,11 @@ impl Function {
_state: *mut ffi::lua_State,
buf: *const c_void,
buf_len: usize,
data_ptr: *mut c_void,
data: *mut c_void,
) -> c_int {
// 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);
}
let data = &mut *(data as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
0
}
@@ -534,10 +430,12 @@ impl Function {
///
/// Recording of coverage information is controlled by [`Compiler::set_coverage_level`] option.
///
/// Requires `feature = "luau"`
///
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn coverage<F>(&self, func: F)
pub fn coverage<F>(&self, mut func: F)
where
F: FnMut(CoverageInfo),
{
@@ -557,16 +455,13 @@ impl Function {
} else {
None
};
let rust_callback = &*(data as *const RefCell<F>);
if let Ok(mut rust_callback) = rust_callback.try_borrow_mut() {
// Call the Rust callback with CoverageInfo
rust_callback(CoverageInfo {
function,
line_defined,
depth,
hits: slice::from_raw_parts(hits, size).to_vec(),
});
}
let rust_callback = &mut *(data as *mut F);
rust_callback(CoverageInfo {
function,
line_defined,
depth,
hits: slice::from_raw_parts(hits, size).to_vec(),
});
}
let lua = self.0.lua.lock();
@@ -576,8 +471,7 @@ impl Function {
assert_stack(state, 1);
lua.push_ref(&self.0);
let func = RefCell::new(func);
let func_ptr = &func as *const RefCell<F> as *mut c_void;
let func_ptr = &mut func as *mut F as *mut c_void;
ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
}
}
@@ -597,26 +491,20 @@ impl Function {
/// Copies the function prototype and all its upvalues to the
/// newly created function.
/// This function returns shallow clone (same handle) for Rust/C functions.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Result<Self> {
pub fn deep_clone(&self) -> Self {
let lua = self.0.lua.lock();
let state = lua.state();
let ref_thread = lua.ref_thread();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_ref(&self.0);
if ffi::lua_iscfunction(state, -1) != 0 {
return Ok(self.clone());
if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 {
return self.clone();
}
if lua.unlikely_memory_error() {
ffi::lua_clonefunction(state, -1);
} else {
protect_lua!(state, 1, 1, fn(state) ffi::lua_clonefunction(state, -1))?;
}
Ok(Function(lua.pop_ref()))
ffi::lua_clonefunction(ref_thread, self.0.index);
Function(lua.pop_ref_thread())
}
}
}
@@ -630,32 +518,30 @@ impl Function {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
/// trait.
#[inline]
pub fn wrap<F, A, R, E>(func: F) -> impl IntoLua
pub fn wrap<F, A, R>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
F: LuaNativeFn<A, Output = Result<R>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
func.call(args)?.push_into_stack_multi(lua)
}))
}
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
pub fn wrap_mut<F, A, R, E>(func: F) -> impl IntoLua
pub fn wrap_mut<F, A, R>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A, Output = StdResult<R, E>> + MaybeSend + 'static,
F: LuaNativeFnMut<A, Output = Result<R>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
func.call(args)?.push_into_stack_multi(lua)
}))
}
@@ -668,7 +554,6 @@ impl Function {
pub fn wrap_raw<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
@@ -685,7 +570,6 @@ impl Function {
pub fn wrap_raw_mut<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
let func = RefCell::new(func);
@@ -700,12 +584,11 @@ impl Function {
/// trait.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn wrap_async<F, A, R, E>(func: F) -> impl IntoLua
pub fn wrap_async<F, A, R>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
F: LuaNativeAsyncFn<A, Output = Result<R>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, None, rawlua) {
@@ -714,7 +597,7 @@ impl Function {
};
let lua = rawlua.lua();
let fut = func.call(args);
Box::pin(async move { fut.await.into_lua_err()?.push_into_stack_multi(lua.raw_lua()) })
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
}))
}
@@ -728,7 +611,6 @@ impl Function {
pub fn wrap_raw_async<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
@@ -762,120 +644,6 @@ impl LuaType for Function {
const TYPE_ID: c_int = ffi::LUA_TFUNCTION;
}
/// Future for asynchronous function calls.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncCallFuture<R: FromLuaMulti>(Result<AsyncThread<R>>);
#[cfg(feature = "async")]
impl<R: FromLuaMulti> AsyncCallFuture<R> {
pub(crate) fn error(err: Error) -> Self {
AsyncCallFuture(Err(err))
}
}
#[cfg(feature = "async")]
impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match &mut this.0 {
Ok(thread) => pin!(thread).poll(cx),
Err(err) => Poll::Ready(Err(err.clone())),
}
}
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
#[cfg(test)]
mod assertions {
use super::*;
@@ -884,7 +652,4 @@ mod assertions {
static_assertions::assert_not_impl_any!(Function: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Function: Send, Sync);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncCallFuture<()>: Send);
}
+115 -109
View File
@@ -1,39 +1,66 @@
//! Lua debugging interface.
//!
//! This module provides access to the Lua debug interface, allowing inspection of the call stack,
//! and function information. The main types are [`struct@Debug`] for accessing debug information
//! and [`HookTriggers`] for configuring debug hooks.
use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ops::Deref;
#[cfg(not(feature = "luau"))]
use std::ops::{BitOr, BitOrAssign};
use std::os::raw::c_int;
use ffi::{lua_Debug, lua_State};
use ffi::lua_Debug;
use crate::function::Function;
use crate::state::RawLua;
use crate::util::{StackGuard, assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
use crate::types::ReentrantMutexGuard;
use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
/// Contains information about currently executing Lua code.
///
/// You may call the methods on this structure to retrieve information about the Lua code executing
/// at the specific level. Further information can be found in the Lua [documentation].
/// The `Debug` structure is provided as a parameter to the hook function set with
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the
/// Lua code executing at the time that the hook function was called. Further information can be
/// found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
/// [`Lua::set_hook`]: crate::Lua::set_hook
pub struct Debug<'a> {
state: *mut lua_State,
lua: &'a RawLua,
#[cfg_attr(not(feature = "luau"), allow(unused))]
lua: EitherLua<'a>,
ar: ActivationRecord,
#[cfg(feature = "luau")]
level: c_int,
ar: *mut lua_Debug,
}
enum EitherLua<'a> {
Owned(ReentrantMutexGuard<'a, RawLua>),
#[cfg(not(feature = "luau"))]
Borrowed(&'a RawLua),
}
impl Deref for EitherLua<'_> {
type Target = RawLua;
fn deref(&self) -> &Self::Target {
match self {
EitherLua::Owned(guard) => guard,
#[cfg(not(feature = "luau"))]
EitherLua::Borrowed(lua) => lua,
}
}
}
impl<'a> Debug<'a> {
pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self {
// We assume the lock is held when this function is called.
#[cfg(not(feature = "luau"))]
pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self {
Debug {
state: lua.state(),
lua,
ar,
level,
lua: EitherLua::Borrowed(lua),
ar: ActivationRecord::Borrowed(ar),
}
}
pub(crate) fn new_owned(guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug) -> Self {
Debug {
lua: EitherLua::Owned(guard),
ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
#[cfg(feature = "luau")]
level: _level,
}
}
@@ -47,7 +74,7 @@ impl<'a> Debug<'a> {
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn event(&self) -> DebugEvent {
unsafe {
match (*self.ar).event {
match (*self.ar.get()).event {
ffi::LUA_HOOKCALL => DebugEvent::Call,
ffi::LUA_HOOKRET => DebugEvent::Ret,
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
@@ -58,48 +85,24 @@ impl<'a> Debug<'a> {
}
}
/// Returns the function that is running at the given level.
///
/// Corresponds to the `f` "what" mask.
pub fn function(&self) -> Function {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
ffi::lua_xmove(self.state, self.lua.ref_thread(), 1);
Function(self.lua.pop_ref_thread())
}
}
/// Corresponds to the `n` "what" mask.
pub fn names(&self) -> DebugNames<'_> {
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
"lua_getinfo failed with `n`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
"lua_getinfo failed with `n`"
);
DebugNames {
name: ptr_to_lossy_str((*self.ar).name),
name: ptr_to_lossy_str((*self.ar.get()).name),
#[cfg(not(feature = "luau"))]
name_what: match ptr_to_str((*self.ar).namewhat) {
name_what: match ptr_to_str((*self.ar.get()).namewhat) {
Some("") => None,
val => val,
},
@@ -109,107 +112,119 @@ impl<'a> Debug<'a> {
}
}
/// Corresponds to the `S` "what" mask.
pub fn source(&self) -> DebugSource<'_> {
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0,
"lua_getinfo failed with `S`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
"lua_getinfo failed with `s`"
);
DebugSource {
source: ptr_to_lossy_str((*self.ar).source),
source: ptr_to_lossy_str((*self.ar.get()).source),
#[cfg(not(feature = "luau"))]
short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()),
short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
#[cfg(feature = "luau")]
short_src: ptr_to_lossy_str((*self.ar).short_src),
line_defined: linenumber_to_usize((*self.ar).linedefined),
short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
#[cfg(not(feature = "luau"))]
last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined),
last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
#[cfg(feature = "luau")]
last_line_defined: None,
what: ptr_to_str((*self.ar).what).unwrap_or("main"),
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
}
}
}
/// Corresponds to the `l` "what" mask. Returns the current line.
pub fn current_line(&self) -> Option<usize> {
/// Corresponds to the `l` what mask. Returns the current line.
pub fn curr_line(&self) -> i32 {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
"lua_getinfo failed with `l`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
"lua_getinfo failed with `l`"
);
linenumber_to_usize((*self.ar).currentline)
(*self.ar.get()).currentline
}
}
/// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
/// false otherwise.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52")))
)]
/// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false
/// otherwise.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn is_tail_call(&self) -> bool {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
"lua_getinfo failed with `t`"
);
(*self.ar).istailcall != 0
(*self.ar.get()).currentline != 0
}
}
/// Corresponds to the `u` "what" mask.
/// Corresponds to the `u` what mask.
pub fn stack(&self) -> DebugStack {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0,
"lua_getinfo failed with `u`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0,
"lua_getinfo failed with `au`"
);
#[cfg(not(feature = "luau"))]
let stack = DebugStack {
num_upvalues: (*self.ar).nups as _,
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
num_params: (*self.ar).nparams as _,
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
is_vararg: (*self.ar).isvararg != 0,
num_ups: (*self.ar.get()).nups as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar.get()).nparams as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar.get()).isvararg != 0,
};
#[cfg(feature = "luau")]
let stack = DebugStack {
num_upvalues: (*self.ar).nupvals,
num_params: (*self.ar).nparams,
is_vararg: (*self.ar).isvararg != 0,
num_ups: (*self.ar.get()).nupvals,
num_params: (*self.ar.get()).nparams,
is_vararg: (*self.ar.get()).isvararg != 0,
};
stack
}
}
}
enum ActivationRecord {
#[cfg(not(feature = "luau"))]
Borrowed(*mut lua_Debug),
Owned(UnsafeCell<lua_Debug>),
}
impl ActivationRecord {
#[inline]
fn get(&self) -> *mut lua_Debug {
match self {
#[cfg(not(feature = "luau"))]
ActivationRecord::Borrowed(x) => *x,
ActivationRecord::Owned(x) => x.get(),
}
}
}
/// Represents a specific event that triggered the hook.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugEvent {
Call,
@@ -220,9 +235,6 @@ pub enum DebugEvent {
Unknown(c_int),
}
/// Contains the name information of a function in the call stack.
///
/// Returned by the [`Debug::names`] method.
#[derive(Clone, Debug)]
pub struct DebugNames<'a> {
/// A (reasonable) name of the function (`None` if the name cannot be found).
@@ -233,9 +245,6 @@ pub struct DebugNames<'a> {
pub name_what: Option<&'static str>,
}
/// Contains the source information of a function in the call stack.
///
/// Returned by the [`Debug::source`] method.
#[derive(Clone, Debug)]
pub struct DebugSource<'a> {
/// Source of the chunk that created the function.
@@ -251,20 +260,19 @@ pub struct DebugSource<'a> {
pub what: &'static str,
}
/// Contains stack information about a function in the call stack.
///
/// Returned by the [`Debug::stack`] method.
#[derive(Copy, Clone, Debug)]
pub struct DebugStack {
/// 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")))))]
/// Number of upvalues.
pub num_ups: u8,
/// Number of parameters.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
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")))))]
/// Whether the function is a vararg function.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub is_vararg: bool,
}
@@ -342,7 +350,6 @@ impl HookTriggers {
}
// Compute the mask to pass to `lua_sethook`.
#[cfg(not(feature = "luau"))]
pub(crate) const fn mask(&self) -> c_int {
let mut mask: c_int = 0;
if self.on_calls {
@@ -362,7 +369,6 @@ impl HookTriggers {
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
// returned.
#[cfg(not(feature = "luau"))]
pub(crate) const fn count(&self) -> c_int {
match self.every_nth_instruction {
Some(n) => n as c_int,
@@ -372,7 +378,7 @@ impl HookTriggers {
}
#[cfg(not(feature = "luau"))]
impl std::ops::BitOr for HookTriggers {
impl BitOr for HookTriggers {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
@@ -387,7 +393,7 @@ impl std::ops::BitOr for HookTriggers {
}
#[cfg(not(feature = "luau"))]
impl std::ops::BitOrAssign for HookTriggers {
impl BitOrAssign for HookTriggers {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
+127 -77
View File
@@ -35,7 +35,7 @@
//! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing
//! Lua values into Rust values.
//!
//! Requires `feature = "serde"`.
//! Requires `feature = "serialize"`.
//!
//! # Async/await support
//!
@@ -61,111 +61,84 @@
//! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
//! [`AsyncThread`]: crate::thread::AsyncThread
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
#![allow(unsafe_op_in_unsafe_fn)]
#![allow(clippy::ptr_eq)]
#[macro_use]
mod macros;
mod buffer;
mod chunk;
mod conversion;
mod error;
mod function;
mod hook;
#[cfg(feature = "luau")]
mod luau;
mod memory;
mod multi;
mod scope;
mod state;
mod stdlib;
mod string;
mod table;
mod thread;
mod traits;
mod types;
mod userdata;
mod util;
mod value;
mod vector;
pub mod chunk;
pub mod debug;
pub mod error;
pub mod function;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub mod luau;
pub mod prelude;
pub mod state;
pub mod string;
pub mod table;
pub mod thread;
pub mod userdata;
pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State};
#[cfg(feature = "macros")]
#[doc(hidden)]
pub use inventory as __inventory;
#[doc(inline)]
pub use crate::error::{Error, Result};
#[doc(inline)]
pub use crate::function::Function;
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
pub use crate::function::{Function, FunctionInfo};
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope;
#[doc(inline)]
pub use crate::state::{Lua, LuaOptions, WeakLua};
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
#[doc(inline)]
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
#[doc(inline)]
pub use crate::table::Table;
#[doc(inline)]
pub use crate::thread::Thread;
#[doc(inline)]
pub use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
pub use crate::types::{
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey,
VmState,
pub use crate::string::{BorrowedBytes, BorrowedStr, String};
pub use crate::table::{Table, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::traits::{
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike,
};
pub use crate::types::{
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState,
};
#[doc(inline)]
pub use crate::userdata::AnyUserData;
pub use crate::value::{Nil, Value};
// Re-export some types to keep backward compatibility and avoid breaking changes in the public API.
#[doc(hidden)]
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
#[cfg(feature = "luau")]
#[doc(hidden)]
pub use crate::chunk::{CompileConstant, Compiler};
#[doc(hidden)]
pub use crate::error::{ErrorContext, ExternalError, ExternalResult};
#[doc(hidden)]
pub use crate::string::LuaString as String;
#[doc(hidden)]
pub use crate::table::{TablePairs, TableSequence};
#[doc(hidden)]
pub use crate::thread::{ThreadEvent, ThreadStatus, ThreadTriggers};
#[doc(hidden)]
pub use crate::userdata::{
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef,
UserDataRefMut, UserDataRegistry,
};
pub use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))]
#[doc(inline)]
pub use crate::debug::HookTriggers;
pub use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{buffer::Buffer, vector::Vector};
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector};
#[cfg(feature = "serde")]
#[doc(hidden)]
pub use crate::serde::{DeserializeOptions, SerializeOptions};
#[cfg(feature = "serde")]
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
#[cfg(feature = "serialize")]
#[doc(inline)]
pub use crate::{serde::LuaSerdeExt, value::SerializableValue};
pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
#[cfg(feature = "mlua_derive")]
@@ -173,7 +146,54 @@ pub mod serde;
#[macro_use]
extern crate mlua_derive;
#[doc = include_str!("../docs/chunk.md")]
/// Create a type that implements [`AsChunk`] and can capture Rust variables.
///
/// This macro allows to write Lua code directly in Rust code.
///
/// Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
/// User's Rust types needs to implement [`UserData`] or [`IntoLua`] traits.
///
/// Captured variables are **moved** into the chunk.
///
/// ```
/// use mlua::{Lua, Result, chunk};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let name = "Rustacean";
/// lua.load(chunk! {
/// print("hello, " .. $name)
/// }).exec()
/// }
/// ```
///
/// ## Syntax issues
///
/// Since the Rust tokenizer will tokenize Lua code, this imposes some restrictions.
/// The main thing to remember is:
///
/// - Use double quoted strings (`""`) instead of single quoted strings (`''`).
///
/// (Single quoted strings only work if they contain a single character, since in Rust,
/// `'a'` is a character literal).
///
/// - Using Lua comments `--` is not desirable in **stable** Rust and can have bad side effects.
///
/// This is because procedural macros have Line/Column information available only in
/// **nightly** Rust. Instead, Lua chunks represented as a big single line of code in stable Rust.
///
/// As workaround, Rust comments `//` can be used.
///
/// Other minor limitations:
///
/// - Certain escape codes in string literals don't work. (Specifically: `\a`, `\b`, `\f`, `\v`,
/// `\123` (octal escape codes), `\u`, and `\U`).
///
/// These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`.
///
/// - The `//` (floor division) operator is unusable, as its start a comment.
///
/// Everything else should work.
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
@@ -186,17 +206,47 @@ pub use mlua_derive::chunk;
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::FromLua;
#[doc = include_str!("../docs/UserData.md")]
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::UserData;
#[doc(hidden)]
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::userdata_impl;
#[doc = include_str!("../docs/lua_module.md")]
/// Registers Lua module entrypoint.
///
/// You can register multiple entrypoints as required.
///
/// ```ignore
/// use mlua::{Lua, Result, Table};
///
/// #[mlua::lua_module]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// let exports = lua.create_table()?;
/// exports.set("hello", "world")?;
/// Ok(exports)
/// }
/// ```
///
/// Internally in the code above the compiler defines C function `luaopen_my_module`.
///
/// You can also pass options to the attribute:
///
/// * name - name of the module, defaults to the name of the function
///
/// ```ignore
/// #[mlua::lua_module(name = "alt_module")]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// ...
/// }
/// ```
///
/// * skip_memory_check - skip memory allocation checks for some operations.
///
/// In module mode, mlua runs in unknown environment and cannot say are there any memory
/// limits or not. As result, some operations that require memory allocation runs in
/// protected mode. Setting this attribute will improve performance of such operations
/// with risk of having uncaught exceptions and memory leaks.
///
/// ```ignore
/// #[mlua::lua_module(skip_memory_check)]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// ...
/// }
/// ```
#[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
-178
View File
@@ -1,178 +0,0 @@
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
&& 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
&& 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()
&& 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 (count, 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
@@ -1,327 +0,0 @@
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());
}
}
+15 -105
View File
@@ -1,90 +1,16 @@
//! Luau-specific extensions and types.
//!
//! This module provides Luau-specific functionality including custom [`require`] implementations,
//! heap memory analysis, and Luau VM integration utilities.
//!
//! [`require`]: crate::Lua::create_require_function
use std::ffi::{CStr, CString};
use std::ffi::CStr;
use std::os::raw::c_int;
use std::ptr;
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{ExtraData, Lua, callback_error_ext};
use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use heap_dump::HeapDump;
pub use require::{FsRequirer, NavigateError, Require};
use crate::error::Result;
use crate::state::Lua;
// Since Luau has some missing standard functions, we re-implement them here
impl Lua {
/// Create a custom Luau `require` function using provided [`Require`] implementation to find
/// and load modules.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_require_function<R: Require + MaybeSend + 'static>(&self, require: R) -> Result<Function> {
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();
globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?;
globals.raw_set("loadstring", self.create_c_function(lua_loadstring)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
@@ -92,10 +18,11 @@ impl Lua {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
// Enable default `require` implementation
let require = self.create_require_function(FsRequirer::new())?;
self.globals().raw_set("require", require)?;
Ok(())
}
pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}
@@ -104,17 +31,16 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
let is_sandboxed = (*ExtraData::get(state)).sandboxed;
match option.to_str() {
Ok("collect") if !is_sandboxed => {
Ok("collect") => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") if !is_sandboxed => {
Ok("stop") => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") if !is_sandboxed => {
Ok("restart") => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
@@ -124,12 +50,12 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _);
Ok("step") => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") if !is_sandboxed => {
Ok("isrunning") => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
@@ -138,22 +64,6 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int {
callback_error_ext(state, ptr::null_mut(), false, move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let (chunk, chunk_name) =
<(String, Option<String>)>::from_stack_args(nargs, 1, Some("loadstring"), rawlua)?;
let chunk_name = chunk_name.as_deref().unwrap_or("=(loadstring)");
(rawlua.lua())
.load(chunk)
.set_name(chunk_name)
.set_mode(ChunkMode::Text)
.into_function()?
.push_into_stack(rawlua)?;
Ok(1)
})
}
pub(crate) use package::register_package_module;
mod heap_dump;
mod json;
mod require;
mod package;
+271
View File
@@ -0,0 +1,271 @@
use std::ffi::CStr;
use std::fmt::Write;
use std::os::raw::c_int;
use std::path::{PathBuf, MAIN_SEPARATOR_STR};
use std::string::String as StdString;
use std::{env, fs};
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::state::Lua;
use crate::table::Table;
use crate::traits::IntoLua;
use crate::value::Value;
#[cfg(unix)]
use {libloading::Library, rustc_hash::FxHashMap};
//
// Luau package module
//
#[cfg(unix)]
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 3;
#[cfg(all(unix, feature = "module"))]
#[no_mangle]
#[used]
pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION;
// We keep reference to the loaded dylibs in application data
#[cfg(unix)]
struct LoadedDylibs(FxHashMap<PathBuf, Library>);
#[cfg(unix)]
impl std::ops::Deref for LoadedDylibs {
type Target = FxHashMap<PathBuf, Library>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(unix)]
impl std::ops::DerefMut for LoadedDylibs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub(crate) fn register_package_module(lua: &Lua) -> Result<()> {
// Create the package table
let package = lua.create_table()?;
// Set `package.path`
let mut search_path = env::var("LUAU_PATH")
.or_else(|_| env::var("LUA_PATH"))
.unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".to_string();
}
package.raw_set("path", search_path)?;
// Set `package.cpath`
#[cfg(unix)]
{
let mut search_cpath = env::var("LUAU_CPATH")
.or_else(|_| env::var("LUA_CPATH"))
.unwrap_or_default();
if search_cpath.is_empty() {
if cfg!(any(target_os = "macos", target_os = "ios")) {
search_cpath = "?.dylib".to_string();
} else {
search_cpath = "?.so".to_string();
}
}
package.raw_set("cpath", search_cpath)?;
}
// Set `package.loaded` (table with a list of loaded modules)
let loaded = if let Ok(Some(loaded)) = lua.named_registry_value::<Option<Table>>("_LOADED") {
package.raw_set("loaded", &loaded)?;
loaded
} else {
let loaded = lua.create_table()?;
package.raw_set("loaded", &loaded)?;
lua.set_named_registry_value("_LOADED", &loaded)?;
loaded
};
// Set `package.loaders`
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
package.raw_set("loaders", &loaders)?;
#[cfg(unix)]
{
loaders.push(lua.create_function(dylib_loader)?)?;
lua.set_app_data(LoadedDylibs(FxHashMap::default()));
}
lua.set_named_registry_value("_LOADERS", loaders)?;
// Register the module and `require` function in globals
let globals = lua.globals();
globals.raw_set("package", &package)?;
loaded.raw_set("package", package)?;
globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?;
Ok(())
}
#[allow(unused_variables)]
pub(crate) fn disable_dylibs(lua: &Lua) {
// Presence of `LoadedDylibs` in app data is used as a flag
// to check whether binary modules are enabled
#[cfg(unix)]
lua.remove_app_data::<LoadedDylibs>();
}
unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settop(state, 1);
let name = ffi::luaL_checkstring(state, 1);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2
if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL {
return 1; // module is already loaded
}
ffi::lua_pop(state, 1); // remove nil
// load the module
let err_buf = ffi::lua_newuserdata_t::<StdString>(state);
err_buf.write(StdString::new());
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3
for i in 1.. {
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {
// no more loaders?
if (&*err_buf).is_empty() {
ffi::luaL_error(state, cstr!("module '%s' not found"), name);
} else {
let bytes = (&*err_buf).as_bytes();
let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len());
ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra);
}
}
ffi::lua_pushvalue(state, 1); // name arg
ffi::lua_call(state, 1, 2); // call loader
match ffi::lua_type(state, -2) {
ffi::LUA_TFUNCTION => break, // loader found
ffi::LUA_TSTRING => {
// error message
let msg = ffi::lua_tostring(state, -2);
let msg = CStr::from_ptr(msg).to_string_lossy();
_ = write!(&mut *err_buf, "\n\t{msg}");
}
_ => {}
}
ffi::lua_pop(state, 2); // remove both results
}
ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader
ffi::lua_rotate(state, -2, 1); // loader data <-> name
// stack: ...; loader function; module name; loader data
ffi::lua_call(state, 2, 1);
// stack: ...; result from loader function
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::lua_pushboolean(state, 1); // use true as result
}
ffi::lua_pushvalue(state, -1); // make copy of entrypoint result
ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */
1
}
/// Searches for the given `name` in the given `path`.
///
/// `path` is a string containing a sequence of templates separated by semicolons.
fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option<PathBuf> {
let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)];
if try_prefix && name.contains('.') {
let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap();
names.push(prefix.to_string());
}
for path in search_path.split(';') {
for name in &names {
let file_path = PathBuf::from(path.replace('?', name));
if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) {
return Some(file_path);
}
}
}
None
}
//
// Module loaders
//
/// Tries to load a lua (text) file
fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let loaded = lua.named_registry_value::<Table>("_LOADED")?;
loaded.raw_get::<Table>("package")
}?;
let search_path = package.get::<StdString>("path").unwrap_or_default();
if let Some(file_path) = package_searchpath(&modname, &search_path, false) {
match fs::read(&file_path) {
Ok(buf) => {
return lua
.load(buf)
.set_name(format!("={}", file_path.display()))
.set_mode(ChunkMode::Text)
.into_function()
.map(Value::Function);
}
Err(err) => {
return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua);
}
}
}
Ok(Value::Nil)
}
/// Tries to load a dynamic library
#[cfg(unix)]
fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let loaded = lua.named_registry_value::<Table>("_LOADED")?;
loaded.raw_get::<Table>("package")
}?;
let search_cpath = package.get::<StdString>("cpath").unwrap_or_default();
let find_symbol = |lib: &Library| unsafe {
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(format!("luaopen_{modname}\0").as_bytes()) {
return lua.create_c_function(*entry).map(Value::Function);
}
// Try all in one mode
if let Ok(entry) =
lib.get::<ffi::lua_CFunction>(format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes())
{
return lua.create_c_function(*entry).map(Value::Function);
}
"cannot find module entrypoint".into_lua(lua)
};
if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) {
let file_path = file_path.canonicalize()?;
// Load the library and check for symbol
unsafe {
let mut loaded_dylibs = match lua.app_data_mut::<LoadedDylibs>() {
Some(loaded_dylibs) => loaded_dylibs,
None => return "dynamic libraries are disabled in safe mode".into_lua(lua),
};
// Check if it's already loaded
if let Some(lib) = loaded_dylibs.get(&file_path) {
return find_symbol(lib);
}
if let Ok(lib) = Library::new(&file_path) {
// Check version
let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION");
let mod_version = mod_version.map(|v| **v).unwrap_or_default();
if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})");
return err.into_lua(lua);
}
let symbol = find_symbol(&lib);
loaded_dylibs.insert(file_path, lib);
return symbol;
}
}
}
Ok(Value::Nil)
}
-512
View File
@@ -1,512 +0,0 @@
use std::cell::RefCell;
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::result::Result as StdResult;
use std::{fmt, mem, ptr};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{Lua, callback_error_ext};
use crate::table::Table;
use crate::types::MaybeSend;
pub use fs::FsRequirer;
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[derive(Debug, Clone)]
pub enum NavigateError {
Ambiguous,
NotFound,
Other(Error),
}
#[cfg(feature = "luau")]
trait IntoNavigateResult {
fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult>;
}
#[cfg(feature = "luau")]
impl IntoNavigateResult for StdResult<(), NavigateError> {
fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult> {
match self {
Ok(()) => Ok(ffi::luarequire_NavigateResult::Success),
Err(NavigateError::Ambiguous) => Ok(ffi::luarequire_NavigateResult::Ambiguous),
Err(NavigateError::NotFound) => Ok(ffi::luarequire_NavigateResult::NotFound),
Err(NavigateError::Other(err)) => Err(err),
}
}
}
impl From<Error> for NavigateError {
fn from(err: Error) -> Self {
NavigateError::Other(err)
}
}
#[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.
pub trait Require {
/// Returns `true` if "require" is permitted for the given chunk name.
fn is_require_allowed(&self, chunk_name: &str) -> bool;
/// Resets the internal state to point at the requirer module.
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError>;
/// Resets the internal state to point at an aliased module.
///
/// This function received an exact path from a configuration file.
/// It's only called when an alias's path cannot be resolved relative to its
/// configuration file.
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>;
/// Provides an initial alias override opportunity prior to searching for
/// configuration files.
///
/// If `Ok(())` is returned, alias resolution stops here and the internal state
/// must point at the aliased location.
fn to_alias_override(&mut self, _alias: &str) -> StdResult<(), NavigateError> {
Err(NavigateError::NotFound)
}
/// Provides a final opportunity to resolve an alias if it cannot be found in
/// configuration files.
///
/// If `Ok(())` is returned, alias resolution stops here and the internal state
/// must point at the aliased location.
fn to_alias_fallback(&mut self, _alias: &str) -> StdResult<(), NavigateError> {
Err(NavigateError::NotFound)
}
// Navigate to parent directory
fn to_parent(&mut self) -> StdResult<(), NavigateError>;
/// 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.
fn has_module(&self) -> bool;
/// Provides a cache key representing the current module.
///
/// This function is only called if `has_module` returns true.
fn cache_key(&self) -> String;
/// Returns whether a configuration is present in the current context.
fn has_config(&self) -> bool;
/// Returns the contents of the configuration file in the current context.
///
/// This function is only called if `has_config` returns true.
fn config(&self) -> IoResult<Vec<u8>>;
/// Returns a loader function for the current module, that when called, loads the module
/// and returns the result.
///
/// Loader can be sync or async.
/// This function is only called if `has_module` returns true.
fn loader(&self, lua: &Lua) -> Result<Function>;
}
impl fmt::Debug for dyn Require {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<dyn Require>")
}
}
struct Context {
require: Box<dyn Require>,
config_cache: Option<IoResult<Vec<u8>>>,
}
impl Deref for Context {
type Target = dyn Require;
fn deref(&self) -> &Self::Target {
&*self.require
}
}
impl DerefMut for Context {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.require
}
}
impl Context {
fn new(require: impl Require + MaybeSend + 'static) -> Self {
Context {
require: Box::new(require),
config_cache: None,
}
}
}
macro_rules! try_borrow {
($state:expr, $ctx:expr) => {
match (*($ctx as *const RefCell<Context>)).try_borrow() {
Ok(ctx) => ctx,
Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
}
};
}
macro_rules! try_borrow_mut {
($state:expr, $ctx:expr) => {
match (*($ctx as *const RefCell<Context>)).try_borrow_mut() {
Ok(ctx) => ctx,
Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
}
};
}
#[cfg(feature = "luau")]
pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_Configuration) {
if config.is_null() {
return;
}
unsafe extern "C-unwind" fn is_require_allowed(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool {
if requirer_chunkname.is_null() {
return false;
}
let this = try_borrow!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.is_require_allowed(&chunk_name)
}
unsafe extern "C-unwind" fn reset(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.reset(&chunk_name).into_nav_result()
})
}
unsafe extern "C-unwind" fn jump_to_alias(
state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let path = CStr::from_ptr(path).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.jump_to_alias(&path).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_alias_override(
state: *mut ffi::lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let alias = CStr::from_ptr(alias_unprefixed).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_alias_override(&alias).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_alias_fallback(
state: *mut ffi::lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let alias = CStr::from_ptr(alias_unprefixed).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_alias_fallback(&alias).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_parent(
state: *mut ffi::lua_State,
ctx: *mut c_void,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_parent().into_nav_result()
})
}
unsafe extern "C-unwind" fn to_child(
state: *mut ffi::lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let name = CStr::from_ptr(name).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_child(&name).into_nav_result()
})
}
unsafe extern "C-unwind" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = try_borrow!(state, ctx);
this.has_module()
}
unsafe extern "C-unwind" fn get_chunkname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_loadname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_cache_key(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = try_borrow!(state, ctx);
let cache_key = this.cache_key();
write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes())
}
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(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
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)
}
unsafe extern "C-unwind" fn load(
state: *mut ffi::lua_State,
ctx: *mut c_void,
_path: *const c_char,
_chunkname: *const c_char,
_loadname: *const c_char,
) -> c_int {
let this = try_borrow!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |extra, _| {
let rawlua = (*extra).raw_lua();
let loader = this.loader(rawlua.lua())?;
rawlua.push(loader)?;
Ok(1)
})
}
(*config).is_require_allowed = is_require_allowed;
(*config).reset = reset;
(*config).jump_to_alias = jump_to_alias;
(*config).to_alias_override = Some(to_alias_override);
(*config).to_alias_fallback = Some(to_alias_fallback);
(*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).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(
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
data: &[u8],
) -> WriteResult {
// the buffer must be null terminated as it's a c++ `std::string` data() buffer
let is_null_terminated = data.last() == Some(&0);
*size_out = data.len() + if is_null_terminated { 0 } else { 1 };
if *size_out > buffer_size {
return WriteResult::BufferTooSmall;
}
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
if !is_null_terminated {
*buffer.add(data.len()) = 0;
}
WriteResult::Success
}
#[cfg(feature = "luau")]
pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
lua: &Lua,
require: R,
) -> Result<Function> {
unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int {
let mut ar: ffi::lua_Debug = mem::zeroed();
for level in 2.. {
if ffi::lua_getinfo(state, level, cstr!("s"), &mut ar) == 0 {
ffi::luaL_error(state, cstr!("require is not supported in this context"));
}
if CStr::from_ptr(ar.what) != c"C" {
break;
}
}
ffi::lua_pushstring(state, ar.source);
1
}
unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int {
let ctx = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1));
let ctx = try_borrow!(state, ctx);
let cache_key = ctx.cache_key();
ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len());
1
}
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::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"));
ffi::luarequire_pushproxyrequire(state, init_config, context_ptr as *mut _);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE"));
})
}?;
unsafe extern "C-unwind" fn error(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_where(state, 1);
ffi::lua_pushvalue(state, 1);
ffi::lua_concat(state, 2);
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn r#type(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushstring(state, ffi::lua_typename(state, ffi::lua_type(state, 1)));
1
}
unsafe extern "C-unwind" fn to_lowercase(state: *mut ffi::lua_State) -> c_int {
let s = ffi::luaL_checkstring(state, 1);
let s = CStr::from_ptr(s);
if !s.to_bytes().iter().any(|&c| c.is_ascii_uppercase()) {
// If the string does not contain any uppercase ASCII letters, return it as is
return 1;
}
callback_error_ext(state, ptr::null_mut(), true, |extra, _| {
let s = (s.to_bytes().iter())
.map(|&c| c.to_ascii_lowercase())
.collect::<bstr::BString>();
(*extra).raw_lua().push(s).map(|_| 1)
})
}
let (error, r#type, to_lowercase) = unsafe {
lua.exec_raw::<(Function, Function, Function)>((), move |state| {
ffi::lua_pushcfunctiond(state, error, cstr!("error"));
ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
ffi::lua_pushcfunctiond(state, to_lowercase, cstr!("to_lowercase"));
})
}?;
// Prepare environment for the "require" function
let env = lua.create_table_with_capacity(0, 7)?;
env.raw_set("get_cache_key", get_cache_key)?;
env.raw_set("find_current_file", find_current_file)?;
env.raw_set("proxyrequire", proxyrequire)?;
env.raw_set("REGISTERED_MODULES", registered_modules)?;
env.raw_set("LOADER_CACHE", loader_cache)?;
env.raw_set("error", error)?;
env.raw_set("type", r#type)?;
env.raw_set("to_lowercase", to_lowercase)?;
lua.load(
r#"
local path = ...
if type(path) ~= "string" then
error("bad argument #1 to 'require' (string expected, got " .. type(path) .. ")")
end
-- Check if the module (path) is explicitly registered
local maybe_result = REGISTERED_MODULES[to_lowercase(path)]
if maybe_result ~= nil then
return maybe_result
end
local loader = proxyrequire(path, find_current_file())
local cache_key = get_cache_key()
-- Check if the loader result is already cached
local result = LOADER_CACHE[cache_key]
if result ~= nil then
return result
end
-- Call the loader function and cache the result
result = loader()
if result == nil then
result = true
end
LOADER_CACHE[cache_key] = result
return result
"#,
)
.try_cache()
.set_name("=__mlua_require")
.set_environment(env)
.into_function()
}
mod fs;
-278
View File
@@ -1,278 +0,0 @@
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 FsRequirer {
/// 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 FsRequirer {
/// 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 `FsRequirer` instance.
pub fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.rsplit_once(':')
&& 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 FsRequirer {
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::FsRequirer;
#[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 = FsRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
"wrong normalization for {input}"
);
}
}
}
+15 -2
View File
@@ -28,7 +28,9 @@ impl MemoryState {
}
#[cfg(not(feature = "luau"))]
#[rustversion::since(1.85)]
#[inline]
#[allow(clippy::incompatible_msrv)]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) {
@@ -37,6 +39,17 @@ impl MemoryState {
mem_state as *mut MemoryState
}
#[cfg(not(feature = "luau"))]
#[rustversion::before(1.85)]
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR {
mem_state = ptr::null_mut();
}
mem_state as *mut MemoryState
}
#[inline]
pub(crate) fn used_memory(&self) -> usize {
self.used_memory as usize
@@ -70,7 +83,7 @@ impl MemoryState {
}
// Does nothing apart from calling `f()`, we don't need to bypass any limits
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
#[inline]
pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) {
f();
@@ -84,7 +97,7 @@ impl MemoryState {
}
}
unsafe extern "C" fn allocator(
unsafe extern "C-unwind" fn allocator(
extra: *mut c_void,
ptr: *mut c_void,
osize: usize,
+1 -27
View File
@@ -1,4 +1,4 @@
use std::collections::{VecDeque, vec_deque};
use std::collections::{vec_deque, VecDeque};
use std::iter::FromIterator;
use std::mem;
use std::ops::{Deref, DerefMut};
@@ -204,23 +204,6 @@ 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> {
@@ -314,15 +297,6 @@ impl<T: IntoLua> IntoLuaMulti for Variadic<T> {
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
MultiValue::from_lua_iter(lua, self)
}
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 {
value.push_into_stack(lua)?;
}
Ok(nresults)
}
}
impl<T: FromLua> FromLuaMulti for Variadic<T> {
+16 -31
View File
@@ -2,49 +2,34 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Either as LuaEither, Error as LuaError, FromLua, FromLuaMulti, Function as LuaFunction,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions,
LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields,
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
UserDataOwned as LuaUserDataOwned, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut,
UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic,
VmState as LuaVmState, WeakLua, chunk::AsChunk as AsLuaChunk, chunk::Chunk as LuaChunk,
chunk::ChunkMode as LuaChunkMode, error::ErrorContext as LuaErrorContext,
error::ExternalError as LuaExternalError, error::ExternalResult as LuaExternalResult,
function::FunctionInfo as LuaFunctionInfo, function::LuaNativeFn, function::LuaNativeFnMut,
state::GcIncParams as LuaGcIncParams, state::GcMode as LuaGcMode, table::TablePairs as LuaTablePairs,
table::TableSequence as LuaTableSequence, thread::ThreadStatus as LuaThreadStatus,
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Either as LuaEither, Error as LuaError,
ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult,
FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn,
LuaNativeFnMut, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil,
Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult,
StdLib as LuaStdLib, String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs,
TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus,
UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua,
};
#[cfg(not(feature = "luau"))]
#[doc(no_inline)]
pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(any(feature = "lua54", feature = "lua55"))]
#[doc(no_inline)]
pub use crate::state::GcGenParams as LuaGcGenParams;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{
Vector as LuaVector,
chunk::{CompileConstant as LuaCompileConstant, Compiler as LuaCompiler},
luau::{
FsRequirer as LuaFsRequirer, HeapDump as LuaHeapDump, NavigateError as LuaNavigateError,
Require as LuaRequire,
},
};
pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::{function::LuaNativeAsyncFn, thread::AsyncThread as LuaAsyncThread};
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
#[doc(no_inline)]
pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializableValue as LuaSerializableValue,
SerializeOptions as LuaSerializeOptions,
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions,
};
+10 -4
View File
@@ -8,7 +8,9 @@ use crate::state::{Lua, LuaGuard, RawLua};
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef};
use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage};
use crate::util::{self, StackGuard, check_stack, get_metatable_ptr, get_userdata, take_userdata};
use crate::util::{
self, assert_stack, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard,
};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be `Send` or `'static`.
@@ -282,18 +284,22 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
/// Shortens the lifetime of the userdata to the lifetime of the scope.
fn seal_userdata<T: 'env>(&self, ud: &AnyUserData) {
let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe {
let state = rawlua.state();
let _sg = StackGuard::new(state);
assert_stack(state, 2);
// Ensure that userdata is not destructed
match rawlua.get_userdata_ref_type_id(&vref) {
match rawlua.push_userdata_ref(&vref) {
Ok(Some(_)) => {}
Ok(None) => {
// Deregister metatable
let mt_ptr = get_metatable_ptr(rawlua.ref_thread(), vref.index);
let mt_ptr = get_metatable_ptr(state, -1);
rawlua.deregister_userdata_metatable(mt_ptr);
}
Err(_) => return vec![],
}
let data = take_userdata::<UserDataStorage<T>>(rawlua.ref_thread(), vref.index);
let data = take_userdata::<UserDataStorage<T>>(state);
vec![Box::new(move || drop(data))]
});
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
+14 -48
View File
@@ -4,6 +4,7 @@ use std::cell::RefCell;
use std::os::raw::c_void;
use std::rc::Rc;
use std::result::Result as StdResult;
use std::string::String as StdString;
use rustc_hash::FxHashSet;
use serde::de::{self, IntoDeserializer};
@@ -14,12 +15,11 @@ use crate::userdata::AnyUserData;
use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
#[derive(Debug, Default)]
#[derive(Debug)]
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,19 +54,6 @@ 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 {
@@ -83,7 +70,6 @@ impl Options {
deny_recursive_tables: true,
sort_keys: false,
encode_empty_tables_as_array: false,
detect_mixed_tables: false,
}
}
@@ -122,15 +108,6 @@ 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 {
@@ -144,7 +121,7 @@ impl Deserializer {
Deserializer {
value,
options,
..Default::default()
visited: Rc::new(RefCell::new(FxHashSet::default())),
}
}
@@ -153,14 +130,8 @@ 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 {
@@ -184,22 +155,17 @@ 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 let Some(len) = t.encode_as_array(self.options) {
self.with_len(len).deserialize_seq(visitor)
} else {
self.deserialize_map(visitor)
}
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(_) => 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))
}
#[cfg(feature = "luau")]
Value::Buffer(buf) => {
let lua = buf.0.lua.lock();
visitor.visit_bytes(buf.as_slice(&lua))
}
Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }),
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
@@ -242,14 +208,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Value::Table(table) => {
let _guard = RecursionGuard::new(&table, &self.visited);
let mut iter = table.pairs::<String, Value>();
let mut iter = table.pairs::<StdString, Value>();
let (variant, value) = match iter.next() {
Some(v) => v?,
None => {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
));
))
}
};
@@ -301,14 +267,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Value::Table(t) => {
let _guard = RecursionGuard::new(&t, &self.visited);
let len = self.len.unwrap_or_else(|| t.raw_len());
let len = t.raw_len();
let mut deserializer = SeqDeserializer {
seq: t.sequence_values().with_len(len),
seq: t.sequence_values(),
options: self.options,
visited: self.visited,
};
let seq = visitor.visit_seq(&mut deserializer)?;
if deserializer.seq.next().is_none() {
if deserializer.seq.count() == 0 {
Ok(seq)
} else {
Err(de::Error::invalid_length(len, &"fewer elements in the table"))
@@ -620,7 +586,7 @@ impl<'de> de::MapAccess<'de> for MapDeserializer<'_> {
}
struct EnumDeserializer {
variant: String,
variant: StdString,
value: Option<Value>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
+19 -5
View File
@@ -13,10 +13,12 @@ use crate::util::check_stack;
use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde.
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -37,8 +39,10 @@ pub trait LuaSerdeExt: Sealed {
fn null(&self) -> Value;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map).
/// As a result, encoded Array will contain only sequence part of the table, with the same
/// length as the `#` operator on that table.
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
@@ -67,6 +71,8 @@ pub trait LuaSerdeExt: Sealed {
/// Converts `T` into a [`Value`] instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
@@ -98,6 +104,8 @@ pub trait LuaSerdeExt: Sealed {
/// Converts `T` into a [`Value`] instance with options.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -121,6 +129,8 @@ pub trait LuaSerdeExt: Sealed {
/// Deserializes a [`Value`] into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -148,6 +158,8 @@ pub trait LuaSerdeExt: Sealed {
/// Deserializes a [`Value`] into any serde deserializable object with options.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -242,5 +254,7 @@ static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de;
pub mod ser;
pub use de::{Deserializer, Options as DeserializeOptions};
pub use ser::{Options as SerializeOptions, Serializer};
#[doc(inline)]
pub use de::Deserializer;
#[doc(inline)]
pub use ser::Serializer;
+8 -8
View File
@@ -1,6 +1,6 @@
//! Serialize a Rust data structure into Lua value.
use serde::{Serialize, ser};
use serde::{ser, Serialize};
use super::LuaSerdeExt;
use crate::error::{Error, Result};
@@ -256,7 +256,7 @@ impl<'a> ser::Serializer for Serializer<'a> {
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?;
if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable()))?;
table.set_metatable(Some(self.lua.array_metatable()));
}
Ok(SerializeSeq::new(self.lua, table, self.options))
}
@@ -529,12 +529,12 @@ impl ser::SerializeStruct for SerializeStruct<'_> {
fn end(self) -> Result<Value> {
match self.inner {
Some(table @ Value::Table(_)) => Ok(table),
Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.to_string()?;
if number_s.contains(['.', 'e', 'E'])
&& let Ok(number) = number_s.parse().map(Value::Number)
{
return Ok(number);
Some(value) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.as_str().expect("not an arbitrary precision number");
if number_s.contains(['.', 'e', 'E']) {
if let Ok(number) = number_s.parse().map(Value::Number) {
return Ok(number);
}
}
Ok(number_s
.parse()
+383 -787
View File
File diff suppressed because it is too large Load Diff
+15 -55
View File
@@ -12,10 +12,9 @@ use rustc_hash::FxHashMap;
use crate::error::Result;
use crate::state::RawLua;
use crate::stdlib::StdLib;
use crate::thread::ThreadTriggers;
use crate::types::{AppData, ReentrantMutex, ThreadEventCallback, XRc};
use crate::types::{AppData, ReentrantMutex, XRc};
use crate::userdata::RawUserDataRegistry;
use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata};
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure};
#[cfg(any(feature = "luau", doc))]
use crate::chunk::Compiler;
@@ -29,7 +28,7 @@ use super::{Lua, WeakLua};
static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
const REF_STACK_RESERVE: c_int = 3;
const REF_STACK_RESERVE: c_int = 2;
/// Data associated with the Lua state.
pub(crate) struct ExtraData {
@@ -45,9 +44,8 @@ pub(crate) struct ExtraData {
// When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s
pub(super) registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
// Containers to store arbitrary data (extensions)
// Container to store arbitrary data (extensions)
pub(super) app_data: AppData,
pub(super) app_data_priv: AppData,
pub(super) safe: bool,
pub(super) libs: StdLib,
@@ -65,7 +63,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<crate::types::ValueRefIndex>,
pub(super) thread_pool: Vec<c_int>,
// Address of `WrappedFailure` metatable
pub(super) wrapped_failure_mt_ptr: *const c_void,
@@ -77,24 +75,18 @@ pub(crate) struct ExtraData {
#[cfg(not(feature = "luau"))]
pub(super) hook_callback: Option<crate::types::HookCallback>,
#[cfg(not(feature = "luau"))]
pub(super) hook_triggers: crate::debug::HookTriggers,
#[cfg(any(feature = "lua55", feature = "lua54"))]
pub(super) hook_thread: *mut ffi::lua_State,
#[cfg(feature = "lua54")]
pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
pub(super) thread_triggers: ThreadTriggers,
pub(super) thread_event_callback: Option<ThreadEventCallback>,
#[cfg(feature = "luau")]
pub(crate) running_gc: bool,
#[cfg(feature = "luau")]
pub(crate) sandboxed: bool,
pub(super) sandboxed: bool,
#[cfg(feature = "luau")]
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 {
@@ -161,7 +153,6 @@ impl ExtraData {
last_checked_userdata_mt: (ptr::null(), None),
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
app_data: AppData::default(),
app_data_priv: AppData::default(),
safe: false,
libs: StdLib::NONE,
skip_memory_check: false,
@@ -180,23 +171,17 @@ impl ExtraData {
#[cfg(not(feature = "luau"))]
hook_callback: None,
#[cfg(not(feature = "luau"))]
hook_triggers: Default::default(),
#[cfg(any(feature = "lua55", feature = "lua54"))]
hook_thread: ptr::null_mut(),
#[cfg(feature = "lua54")]
warn_callback: None,
#[cfg(feature = "luau")]
interrupt_callback: None,
thread_triggers: ThreadTriggers::default(),
thread_event_callback: None,
#[cfg(feature = "luau")]
sandboxed: false,
#[cfg(feature = "luau")]
compiler: None,
#[cfg(feature = "luau-jit")]
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
@@ -210,10 +195,13 @@ impl ExtraData {
raw: XRc::clone(raw),
collect_garbage: false,
});
if self.owned {
XRc::decrement_strong_count(XRc::as_ptr(raw));
}
self.weak.write(WeakLua(XRc::downgrade(raw)));
}
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
#[cfg(feature = "luau")]
if cfg!(not(feature = "module")) {
// In the main app we can use `lua_callbacks` to access ExtraData
@@ -252,7 +240,7 @@ impl ExtraData {
}
#[inline(always)]
pub(crate) unsafe fn raw_lua(&self) -> &RawLua {
pub(super) unsafe fn raw_lua(&self) -> &RawLua {
&*self.lua.assume_init_ref().raw.data_ptr()
}
@@ -260,32 +248,4 @@ impl ExtraData {
pub(super) unsafe fn weak(&self) -> &WeakLua {
self.weak.assume_init_ref()
}
/// Pops a reference from top of the auxiliary stack and move it to a first free slot.
pub(super) unsafe fn ref_stack_pop(&mut self) -> c_int {
if let Some(free) = self.ref_free.pop() {
ffi::lua_replace(self.ref_thread, free);
return free;
}
// Try to grow max stack size
if self.ref_stack_top >= self.ref_stack_size {
let mut inc = self.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc + REF_STACK_RESERVE) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(self.ref_thread, 1);
let top = self.ref_stack_top;
// It is a user error to create too many references to exhaust the Lua max stack size
// for the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
self.ref_stack_size += inc;
}
self.ref_stack_top += 1;
self.ref_stack_top
}
}
+202 -442
View File
File diff suppressed because it is too large Load Diff
+32 -12
View File
@@ -1,11 +1,11 @@
use std::os::raw::c_int;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::state::{ExtraData, RawLua};
use crate::util::{self, WrappedFailure, get_internal_metatable};
use crate::util::{self, get_internal_metatable, WrappedFailure};
struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
@@ -24,10 +24,9 @@ impl Drop for StateGuard<'_> {
// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata
// and instead reuses unused values from previous calls (or allocates new).
pub(crate) unsafe fn callback_error_ext<F, R>(
pub(super) unsafe fn callback_error_ext<F, R>(
state: *mut ffi::lua_State,
mut extra: *mut ExtraData,
wrap_error: bool,
f: F,
) -> R
where
@@ -89,7 +88,7 @@ where
PreallocatedFailure::New(_) => {
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = (*extra).ref_stack_pop();
let index = ref_stack_pop(extra);
(*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1;
}
@@ -115,13 +114,6 @@ where
Ok(Err(err)) => {
let wrapped_error = prealloc_failure.r#use(state, extra);
if !wrap_error {
ptr::write(wrapped_error, WrappedFailure::Error(err));
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
// Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0);
@@ -150,3 +142,31 @@ where
}
}
}
pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int {
let extra = &mut *extra;
if let Some(free) = extra.ref_free.pop() {
ffi::lua_replace(extra.ref_thread, free);
return free;
}
// Try to grow max stack size
if extra.ref_stack_top >= extra.ref_stack_size {
let mut inc = extra.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(extra.ref_thread, 1);
let top = extra.ref_stack_top;
// It is a user error to create enough references to exhaust the Lua max stack size for
// the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
extra.ref_stack_size += inc;
}
extra.ref_stack_top += 1;
extra.ref_stack_top
}
+14 -42
View File
@@ -1,4 +1,4 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
/// Flags describing the set of lua standard libraries to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -6,23 +6,9 @@ pub struct StdLib(u32);
impl StdLib {
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
)))
)]
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
@@ -40,27 +26,21 @@ 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 = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau")))
)]
///
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(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
///
/// Requires `feature = "lua52/luajit/luau"`
#[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua52", feature = "luajit", feature = "luau")))
)]
pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`buffer`](https://luau.org/library#buffer-library) library
@@ -73,17 +53,16 @@ impl StdLib {
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`integer`](https://luau.org/library#integer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const INTEGER: StdLib = StdLib(1 << 11);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 12);
pub const JIT: StdLib = StdLib(1 << 11);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const FFI: StdLib = StdLib(1 << 30);
@@ -144,10 +123,3 @@ impl BitXorAssign for StdLib {
*self = StdLib(self.0 ^ rhs.0)
}
}
impl Not for StdLib {
type Output = Self;
fn not(self) -> Self::Output {
StdLib(!self.0)
}
}
+87 -90
View File
@@ -1,12 +1,9 @@
//! Lua string handling.
//!
//! This module provides types for working with Lua strings from Rust.
use std::borrow::Borrow;
use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::os::raw::{c_int, c_void};
use std::{cmp, fmt, mem, slice, str};
use std::string::String as StdString;
use std::{cmp, fmt, slice, str};
use crate::error::{Error, Result};
use crate::state::Lua;
@@ -14,7 +11,7 @@ use crate::traits::IntoLua;
use crate::types::{LuaType, ValueRef};
use crate::value::Value;
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, Serializer},
std::result::Result as StdResult,
@@ -23,27 +20,24 @@ use {
/// Handle to an internal Lua string.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone, PartialEq)]
pub struct LuaString(pub(crate) ValueRef);
#[derive(Clone)]
pub struct String(pub(crate) ValueRef);
impl LuaString {
impl String {
/// Get a [`BorrowedStr`] if the Lua string is valid UTF-8.
///
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data.
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, LuaString, Result};
/// # use mlua::{Lua, Result, String};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let globals = lua.globals();
///
/// let version: LuaString = globals.get("_VERSION")?;
/// let version: String = globals.get("_VERSION")?;
/// assert!(version.to_str()?.contains("Lua"));
///
/// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?;
/// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?;
/// assert!(non_utf8.to_str().is_err());
/// # Ok(())
/// # }
@@ -53,11 +47,11 @@ impl LuaString {
BorrowedStr::try_from(self)
}
/// Converts this Lua string to a [`String`].
/// Converts this string to a [`StdString`].
///
/// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
///
/// This method returns [`String`] instead of [`Cow<'_, str>`] because lifetime cannot be
/// This method returns [`StdString`] instead of [`Cow<'_, str>`] because lifetime cannot be
/// bound to a weak Lua object.
///
/// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
@@ -76,11 +70,11 @@ impl LuaString {
/// # }
/// ```
#[inline]
pub fn to_string_lossy(&self) -> String {
String::from_utf8_lossy(&self.as_bytes()).into_owned()
pub fn to_string_lossy(&self) -> StdString {
StdString::from_utf8_lossy(&self.as_bytes()).into_owned()
}
/// Returns an object that implements [`Display`] for safely printing a [`LuaString`] that may
/// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may
/// contain non-Unicode data.
///
/// This may perform lossy conversion.
@@ -92,17 +86,16 @@ impl LuaString {
/// Get the bytes that make up this string.
///
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data. The data will not contain the terminating null byte, but
/// will contain any null bytes embedded into the Lua string.
/// The returned slice will not contain the terminating nul byte, but will contain any nul
/// bytes embedded into the Lua string.
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, LuaString, Result};
/// # use mlua::{Lua, Result, String};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?;
/// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?;
/// assert!(non_utf8.to_str().is_err()); // oh no :(
/// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]);
/// # Ok(())
@@ -113,15 +106,15 @@ impl LuaString {
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing null byte.
/// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(self);
// Include the trailing null byte (it's always present but excluded by default)
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
// Include the trailing nul byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, vref, _lua }
BorrowedBytes { buf, borrow, _lua }
}
// Does not return the terminating null byte
// Does not return the terminating nul byte
unsafe fn to_slice(&self) -> (&[u8], Lua) {
let lua = self.0.lua.upgrade();
let slice = {
@@ -142,21 +135,18 @@ impl LuaString {
(slice, lua)
}
/// Converts this Lua string to a generic C pointer.
/// Converts this string to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
// In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach
let lua = self.0.lua.lock();
unsafe { ffi::lua_tostring(lua.ref_thread(), self.0.index) as *const c_void }
self.0.to_pointer()
}
}
impl fmt::Debug for LuaString {
impl fmt::Debug for String {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.as_bytes();
// Check if the string is valid utf8
@@ -172,12 +162,12 @@ impl fmt::Debug for LuaString {
// Lua strings are basically `&[u8]` slices, so implement `PartialEq` for anything resembling that.
//
// This makes our `LuaString` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`.
// This makes our `String` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`.
//
// The only downside is that this disallows a comparison with `Cow<str>`, as that only implements
// `AsRef<str>`, which collides with this impl. Requiring `AsRef<str>` would fix that, but limit us
// in other ways.
impl<T> PartialEq<T> for LuaString
impl<T> PartialEq<T> for String
where
T: AsRef<[u8]> + ?Sized,
{
@@ -186,37 +176,43 @@ where
}
}
impl Eq for LuaString {}
impl PartialEq for String {
fn eq(&self, other: &String) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<T> PartialOrd<T> for LuaString
impl Eq for String {}
impl<T> PartialOrd<T> for String
where
T: AsRef<[u8]> + ?Sized,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
<[u8]>::partial_cmp(&self.as_bytes(), other.as_ref())
self.as_bytes().partial_cmp(&other.as_ref())
}
}
impl PartialOrd for LuaString {
fn partial_cmp(&self, other: &LuaString) -> Option<cmp::Ordering> {
impl PartialOrd for String {
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for LuaString {
fn cmp(&self, other: &LuaString) -> cmp::Ordering {
impl Ord for String {
fn cmp(&self, other: &String) -> cmp::Ordering {
self.as_bytes().cmp(&other.as_bytes())
}
}
impl Hash for LuaString {
impl Hash for String {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}
#[cfg(feature = "serde")]
impl Serialize for LuaString {
#[cfg(feature = "serialize")]
impl Serialize for String {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
@@ -228,7 +224,7 @@ impl Serialize for LuaString {
}
}
struct Display<'a>(&'a LuaString);
struct Display<'a>(&'a String);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -238,14 +234,14 @@ impl fmt::Display for Display<'_> {
}
/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
pub struct BorrowedStr {
pub struct BorrowedStr<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'static str,
pub(crate) vref: ValueRef,
pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedStr {
impl Deref for BorrowedStr<'_> {
type Target = str;
#[inline(always)]
@@ -254,33 +250,33 @@ impl Deref for BorrowedStr {
}
}
impl Borrow<str> for BorrowedStr {
impl Borrow<str> for BorrowedStr<'_> {
#[inline(always)]
fn borrow(&self) -> &str {
self.buf
}
}
impl AsRef<str> for BorrowedStr {
impl AsRef<str> for BorrowedStr<'_> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.buf
}
}
impl fmt::Display for BorrowedStr {
impl fmt::Display for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl fmt::Debug for BorrowedStr {
impl fmt::Debug for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedStr
impl<T> PartialEq<T> for BorrowedStr<'_>
where
T: AsRef<str>,
{
@@ -289,9 +285,9 @@ where
}
}
impl Eq for BorrowedStr {}
impl Eq for BorrowedStr<'_> {}
impl<T> PartialOrd<T> for BorrowedStr
impl<T> PartialOrd<T> for BorrowedStr<'_>
where
T: AsRef<str>,
{
@@ -300,33 +296,36 @@ where
}
}
impl Ord for BorrowedStr {
impl Ord for BorrowedStr<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl TryFrom<&LuaString> for BorrowedStr {
impl<'a> TryFrom<&'a String> for BorrowedStr<'a> {
type Error = Error;
#[inline]
fn try_from(value: &LuaString) -> Result<Self> {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(value);
let buf =
str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?;
Ok(Self { buf, vref, _lua })
fn try_from(value: &'a String) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value);
let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError {
from: "string",
to: "&str".to_string(),
message: Some(e.to_string()),
})?;
Ok(Self { buf, borrow, _lua })
}
}
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
pub struct BorrowedBytes {
pub struct BorrowedBytes<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'static [u8],
pub(crate) vref: ValueRef,
pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedBytes {
impl Deref for BorrowedBytes<'_> {
type Target = [u8];
#[inline(always)]
@@ -335,27 +334,27 @@ impl Deref for BorrowedBytes {
}
}
impl Borrow<[u8]> for BorrowedBytes {
impl Borrow<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.buf
}
}
impl AsRef<[u8]> for BorrowedBytes {
impl AsRef<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.buf
}
}
impl fmt::Debug for BorrowedBytes {
impl fmt::Debug for BorrowedBytes<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedBytes
impl<T> PartialEq<T> for BorrowedBytes<'_>
where
T: AsRef<[u8]>,
{
@@ -364,9 +363,9 @@ where
}
}
impl Eq for BorrowedBytes {}
impl Eq for BorrowedBytes<'_> {}
impl<T> PartialOrd<T> for BorrowedBytes
impl<T> PartialOrd<T> for BorrowedBytes<'_>
where
T: AsRef<[u8]>,
{
@@ -375,13 +374,13 @@ where
}
}
impl Ord for BorrowedBytes {
impl Ord for BorrowedBytes<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> IntoIterator for &'a BorrowedBytes {
impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>;
@@ -390,20 +389,18 @@ impl<'a> IntoIterator for &'a BorrowedBytes {
}
}
impl From<&LuaString> for BorrowedBytes {
impl<'a> From<&'a String> for BorrowedBytes<'a> {
#[inline]
fn from(value: &LuaString) -> Self {
fn from(value: &'a String) -> Self {
let (buf, _lua) = unsafe { value.to_slice() };
let vref = value.0.clone();
// SAFETY: The `buf` is valid for the lifetime of the Lua state and occupied slot index
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
Self { buf, vref, _lua }
let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua }
}
}
struct WrappedString<T: AsRef<[u8]>>(T);
impl LuaString {
impl String {
/// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_string`] under the hood.
@@ -418,7 +415,7 @@ impl<T: AsRef<[u8]>> IntoLua for WrappedString<T> {
}
}
impl LuaType for LuaString {
impl LuaType for String {
const TYPE_ID: c_int = ffi::LUA_TSTRING;
}
@@ -427,9 +424,9 @@ mod assertions {
use super::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(LuaString: Send);
static_assertions::assert_not_impl_any!(String: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(LuaString: Send, Sync);
static_assertions::assert_impl_all!(String: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync);
#[cfg(feature = "send")]
+104 -313
View File
@@ -1,173 +1,21 @@
//! Lua table handling.
//!
//! Tables are Lua's primary data structure, used for arrays, dictionaries, objects, modules,
//! and more. This module provides types for creating and manipulating Lua tables from Rust.
//!
//! # Basic Operations
//!
//! Tables support key-value access similar to Rust's `HashMap`:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let table = lua.create_table()?;
//!
//! // Set and get values
//! table.set("key", "value")?;
//! let value: String = table.get("key")?;
//! assert_eq!(value, "value");
//!
//! // Keys and values can be any Lua-compatible type
//! table.set(1, "first")?;
//! table.set("nested", lua.create_table()?)?;
//! # Ok(())
//! # }
//! ```
//!
//! # Array Operations
//!
//! Tables can be used as arrays with 1-based indexing:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let array = lua.create_table()?;
//!
//! // Push values to the end (like Vec::push)
//! array.push("first")?;
//! array.push("second")?;
//! array.push("third")?;
//!
//! // Pop from the end
//! let last: String = array.pop()?;
//! assert_eq!(last, "third");
//!
//! // Get length
//! assert_eq!(array.raw_len(), 2);
//! # Ok(())
//! # }
//! ```
//!
//! # Iteration
//!
//! Iterate over all key-value pairs with [`Table::pairs`]:
//!
//! ```
//! # use mlua::{Lua, Result, Value};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let table = lua.create_table()?;
//! table.set("a", 1)?;
//! table.set("b", 2)?;
//!
//! for pair in table.pairs::<String, i32>() {
//! let (key, value) = pair?;
//! println!("{key} = {value}");
//! }
//! # Ok(())
//! # }
//! ```
//!
//! For array portions, use [`Table::sequence_values`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let array = lua.create_sequence_from(["a", "b", "c"])?;
//!
//! for value in array.sequence_values::<String>() {
//! println!("{}", value?);
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Raw vs Normal Access
//!
//! Methods prefixed with `raw_` (like [`Table::raw_get`], [`Table::raw_set`]) bypass
//! metamethods, directly accessing the table's contents. Normal methods may trigger
//! `__index`, `__newindex`, and other metamethods:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! // raw_set bypasses __newindex metamethod
//! let t = lua.create_table()?;
//! t.raw_set("key", "value")?;
//!
//! // raw_get bypasses __index metamethod
//! let v: String = t.raw_get("key")?;
//! # Ok(())
//! # }
//! ```
//!
//! # Metatables
//!
//! Tables can have metatables that customize their behavior:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! let table = lua.create_table()?;
//! let metatable = lua.create_table()?;
//!
//! // Set a default value via __index
//! metatable.set("__index", lua.create_function(|_, _: ()| Ok("default"))?)?;
//! table.set_metatable(Some(metatable))?;
//!
//! // Accessing missing keys returns "default"
//! let value: String = table.get("missing")?;
//! assert_eq!(value, "default");
//! # Ok(())
//! # }
//! ```
//!
//! # Global Table
//!
//! The Lua global environment is itself a table, accessible via [`Lua::globals`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let globals = lua.globals();
//!
//! // Set a global variable
//! globals.set("my_var", 42)?;
//!
//! // Now accessible from Lua code
//! let result: i32 = lua.load("my_var + 8").eval()?;
//! assert_eq!(result, 50);
//! # Ok(())
//! # }
//! ```
//!
//! [`Lua::globals`]: crate::Lua::globals
use std::collections::HashSet;
use std::fmt;
use std::marker::PhantomData;
use std::os::raw::c_void;
use std::os::raw::{c_int, c_void};
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{LuaGuard, RawLua, WeakLua};
use crate::state::{LuaGuard, RawLua};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::types::{Integer, ValueRef};
use crate::util::{StackGuard, assert_stack, check_stack, get_metatable_ptr};
use crate::types::{Integer, LuaType, ValueRef};
use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
use crate::value::{Nil, Value};
#[cfg(feature = "async")]
use crate::function::AsyncCallFuture;
use futures_util::future::{self, Either, Future};
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
use {
rustc_hash::FxHashSet,
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
@@ -363,7 +211,7 @@ impl Table {
///
/// let always_equals_mt = lua.create_table()?;
/// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
/// table2.set_metatable(Some(always_equals_mt))?;
/// table2.set_metatable(Some(always_equals_mt));
///
/// assert!(table1.equals(&table1.clone())?);
/// assert!(table1.equals(&table2)?);
@@ -378,15 +226,15 @@ impl Table {
// Compare using `__eq` metamethod if exists
// First, check the self for the metamethod.
// If self does not define it, then check the other table.
if let Some(mt) = self.metatable()
&& let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
{
return eq_func.call((self, other));
if let Some(mt) = self.metatable() {
if mt.contains_key("__eq")? {
return mt.get::<Function>("__eq")?.call((self, other));
}
}
if let Some(mt) = other.metatable()
&& let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
{
return eq_func.call((self, other));
if let Some(mt) = other.metatable() {
if mt.contains_key("__eq")? {
return mt.get::<Function>("__eq")?.call((self, other));
}
}
Ok(false)
@@ -568,7 +416,14 @@ impl Table {
lua.push_ref(&self.0);
// This is safe as long as we don't assign new keys
// 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
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
ffi::lua_pop(state, 1); // pop value
@@ -632,37 +487,52 @@ impl Table {
/// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
pub fn metatable(&self) -> Option<Table> {
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
let state = lua.state();
unsafe {
if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(state, -1) == 0 {
None
} else {
Some(Table(lua.pop_ref_thread()))
Some(Table(lua.pop_ref()))
}
}
}
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
#[cfg(not(tarpaulin_include))]
pub fn get_metatable(&self) -> Option<Table> {
self.metatable()
}
/// Sets or removes the metatable of this table.
///
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
/// nothing).
pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
pub fn set_metatable(&self, metatable: Option<Table>) {
// Workaround to throw readonly error without returning Result
#[cfg(feature = "luau")]
if self.is_readonly() {
return Err(Error::runtime("attempt to modify a readonly table"));
panic!("attempt to modify a readonly table");
}
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
let state = lua.state();
unsafe {
if let Some(metatable) = &metatable {
ffi::lua_pushvalue(ref_thread, metatable.0.index);
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(&self.0);
if let Some(metatable) = metatable {
lua.push_ref(&metatable.0);
} else {
ffi::lua_pushnil(ref_thread);
ffi::lua_pushnil(state);
}
ffi::lua_setmetatable(ref_thread, self.0.index);
ffi::lua_setmetatable(state, -2);
}
Ok(())
}
/// Returns true if the table has metatable attached.
@@ -674,6 +544,8 @@ impl Table {
}
/// Sets `readonly` attribute on the table.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_readonly(&self, enabled: bool) {
@@ -689,6 +561,8 @@ impl Table {
}
/// Returns `readonly` attribute of the table.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn is_readonly(&self) -> bool {
@@ -706,6 +580,8 @@ impl Table {
/// - Fast-path for some built-in functions (fastcall).
///
/// For `safeenv` environments, monkey patching or modifying values may not work as expected.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_safeenv(&self, enabled: bool) {
@@ -750,7 +626,7 @@ impl Table {
/// ```
///
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
TablePairs {
guard: self.0.lua.lock(),
table: self,
@@ -778,8 +654,10 @@ impl Table {
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
let k = K::from_stack(-2, &lua)?;
let v = lua.pop::<V>()?;
let v = V::from_stack(-1, &lua)?;
f(k, v)?;
// Keep key for next iteration
ffi::lua_pop(state, 1);
}
}
Ok(())
@@ -813,30 +691,21 @@ impl Table {
/// # Ok(())
/// # }
/// ```
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
TableSequence {
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: 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();
pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
where
V: FromLua,
{
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -844,15 +713,11 @@ impl Table {
check_stack(state, 4)?;
lua.push_ref(&self.0);
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(lua.pop::<V>()?)?;
let len = ffi::lua_rawlen(state, -1);
for i in 1..=len {
ffi::lua_rawgeti(state, -1, i as _);
f(V::from_stack(-1, &lua)?)?;
ffi::lua_pop(state, 1);
}
}
Ok(())
@@ -883,9 +748,8 @@ impl Table {
Ok(())
}
/// Checks if the table has the array metatable attached.
#[cfg(feature = "serde")]
fn has_array_metatable(&self) -> bool {
#[cfg(feature = "serialize")]
pub(crate) fn is_array(&self) -> bool {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -901,70 +765,6 @@ 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<()> {
@@ -1084,6 +884,10 @@ 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> {
@@ -1101,16 +905,16 @@ impl ObjectLike for Table {
R: FromLuaMulti,
{
// Convert table to a function and call via pcall that respects the `__call` metamethod.
Function(self.0.clone()).call(args)
Function(self.0.copy()).call(args)
}
#[cfg(feature = "async")]
#[inline]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
where
R: FromLuaMulti,
{
Function(self.0.clone()).call_async(args)
Function(self.0.copy()).call_async(args)
}
#[inline]
@@ -1122,7 +926,7 @@ impl ObjectLike for Table {
}
#[cfg(feature = "async")]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
where
R: FromLuaMulti,
{
@@ -1142,45 +946,35 @@ impl ObjectLike for Table {
#[cfg(feature = "async")]
#[inline]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
where
R: FromLuaMulti,
{
match self.get(name) {
Ok(Value::Function(func)) => func.call_async(args),
Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
Ok(val) => {
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
AsyncCallFuture::error(Error::RuntimeError(msg))
Either::Right(future::ready(Err(Error::RuntimeError(msg))))
}
Err(err) => AsyncCallFuture::error(err),
Err(err) => Either::Right(future::ready(Err(err))),
}
}
#[inline]
fn to_string(&self) -> Result<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
fn to_string(&self) -> Result<StdString> {
Value::Table(Table(self.0.copy())).to_string()
}
}
/// A wrapped [`Table`] with customized serialization behavior.
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
pub(crate) struct SerializableTable<'a> {
table: &'a Table,
options: crate::serde::de::Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl Serialize for Table {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
@@ -1188,7 +982,7 @@ impl Serialize for Table {
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl<'a> SerializableTable<'a> {
#[inline]
pub(crate) fn new(
@@ -1204,22 +998,13 @@ 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")]
#[cfg(feature = "serialize")]
impl Serialize for SerializableTable<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
use crate::serde::de::{MapPairs, RecursionGuard, check_value_for_skip};
use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard};
use crate::value::SerializableValue;
let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
@@ -1234,10 +1019,14 @@ impl Serialize for SerializableTable<'_> {
let _guard = RecursionGuard::new(self.table, visited);
// Array
if let Some(len) = self.table.encode_as_array(self.options) {
let len = self.table.raw_len();
if len > 0
|| self.table.is_array()
|| (self.options.encode_empty_tables_as_array && self.table.is_empty())
{
let mut seq = serializer.serialize_seq(Some(len))?;
let mut serialize_err = None;
let res = self.table.for_each_value_by_len::<Value>(len, |value| {
let res = self.table.for_each_value::<Value>(|value| {
let skip = check_value_for_skip(&value, self.options, visited)
.map_err(|err| Error::SerializeError(err.to_string()))?;
if skip {
@@ -1247,7 +1036,7 @@ impl Serialize for SerializableTable<'_> {
seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
.map_err(|err| {
serialize_err = Some(err);
Error::SerializeError(String::new())
Error::SerializeError(StdString::new())
})
});
convert_result(res, serialize_err)?;
@@ -1272,7 +1061,7 @@ impl Serialize for SerializableTable<'_> {
)
.map_err(|err| {
serialize_err = Some(err);
Error::SerializeError(String::new())
Error::SerializeError(StdString::new())
})
};
@@ -1361,11 +1150,13 @@ pub struct TableSequence<'a, V> {
guard: LuaGuard,
table: &'a Table,
index: Integer,
len: Option<usize>,
_phantom: PhantomData<V>,
}
impl<V: FromLua> Iterator for TableSequence<'_, V> {
impl<V> Iterator for TableSequence<'_, V>
where
V: FromLua,
{
type Item = Result<V>;
fn next(&mut self) -> Option<Self::Item> {
@@ -1379,7 +1170,7 @@ impl<V: FromLua> Iterator for TableSequence<'_, V> {
lua.push_ref(&self.table.0);
match ffi::lua_rawgeti(state, -1, self.index) {
ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None,
ffi::LUA_TNIL => None,
_ => {
self.index += 1;
Some(V::from_stack(-1, lua))
+125 -427
View File
@@ -1,40 +1,3 @@
//! Lua thread (coroutine) handling.
//!
//! This module provides types for creating and working with Lua coroutines from Rust.
//! Coroutines allow cooperative multitasking within a single Lua state by suspending and
//! resuming execution at well-defined yield points.
//!
//! # Basic Usage
//!
//! Threads are created via [`Lua::create_thread`] and driven by calling [`Thread::resume`]:
//!
//! ```rust
//! # use mlua::{Lua, Result, Thread};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let thread: Thread = lua.load(r#"
//! coroutine.create(function(a, b)
//! coroutine.yield(a + b)
//! return a * b
//! end)
//! "#).eval()?;
//!
//! assert_eq!(thread.resume::<i32>((3, 4))?, 7);
//! assert_eq!(thread.resume::<i32>(())?, 12);
//! # Ok(())
//! # }
//! ```
//!
//! # Async Support
//!
//! When the `async` feature is enabled, a [`Thread`] can be converted into an [`AsyncThread`]
//! via [`Thread::into_async`], which implements both [`Future`] and [`Stream`].
//! This integrates Lua coroutines naturally with Rust async runtimes such as Tokio.
//!
//! [`Lua::create_thread`]: crate::Lua::create_thread
//! [`Future`]: std::future::Future
//! [`Stream`]: futures_util::stream::Stream
use std::fmt;
use std::os::raw::{c_int, c_void};
@@ -42,13 +5,13 @@ use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::RawLua;
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{LuaType, ValueRef, XRc};
use crate::util::{StackGuard, check_stack, error_traceback_thread, pop_error};
use crate::types::{LuaType, ValueRef};
use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
#[cfg(not(feature = "luau"))]
use crate::{
debug::{Debug, HookTriggers},
types::HookKind,
hook::{Debug, HookTriggers},
types::MaybeSend,
};
#[cfg(feature = "async")]
@@ -63,85 +26,6 @@ use {
},
};
/// Controls which thread lifecycle events trigger the callback.
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct ThreadTriggers {
/// Trigger the callback when a new thread is created.
pub on_create: bool,
/// Trigger the callback before a thread is resumed (via [`Thread::resume`]).
pub on_resume: bool,
/// Trigger the callback after a thread yields.
pub on_yield: bool,
}
impl ThreadTriggers {
/// An instance of [`ThreadTriggers`] with `on_create` trigger set.
pub const ON_CREATE: Self = Self::new().on_create();
/// An instance of [`ThreadTriggers`] with `on_resume` trigger set.
pub const ON_RESUME: Self = Self::new().on_resume();
/// An instance of [`ThreadTriggers`] with `on_yield` trigger set.
pub const ON_YIELD: Self = Self::new().on_yield();
/// Returns a new instance of `ThreadTriggers` with all triggers disabled.
pub const fn new() -> Self {
Self {
on_create: false,
on_resume: false,
on_yield: false,
}
}
/// Returns an instance of `ThreadTriggers` with `on_create` trigger set.
pub const fn on_create(mut self) -> Self {
self.on_create = true;
self
}
/// Returns an instance of `ThreadTriggers` with `on_resume` trigger set.
pub const fn on_resume(mut self) -> Self {
self.on_resume = true;
self
}
/// Returns an instance of `ThreadTriggers` with `on_yield` trigger set.
pub const fn on_yield(mut self) -> Self {
self.on_yield = true;
self
}
}
impl std::ops::BitOr for ThreadTriggers {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
self.on_create |= rhs.on_create;
self.on_resume |= rhs.on_resume;
self.on_yield |= rhs.on_yield;
self
}
}
impl std::ops::BitOrAssign for ThreadTriggers {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
/// Represents a thread (coroutine) event.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ThreadEvent {
/// A new thread was created.
Create(Thread),
/// A thread is about to be resumed via [`Thread::resume`].
Resume(Thread),
/// A thread has just yielded.
Yield(Thread),
}
/// Status of a Lua thread (coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus {
@@ -163,28 +47,22 @@ pub enum ThreadStatus {
/// to the thread stack.
#[derive(Clone, Copy)]
enum ThreadStatusInner {
New(c_int),
New,
Running,
Yielded(c_int),
Yielded,
Finished,
Error,
}
impl ThreadStatusInner {
#[cfg(feature = "async")]
#[inline(always)]
fn is_resumable(self) -> bool {
matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
}
#[inline(always)]
fn is_yielded(self) -> bool {
matches!(self, ThreadStatusInner::Yielded(_))
matches!(self, ThreadStatusInner::New | ThreadStatusInner::Yielded)
}
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone, PartialEq)]
#[derive(Clone)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
#[cfg(feature = "send")]
@@ -194,21 +72,23 @@ unsafe impl Sync for Thread {}
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<R> {
pub struct AsyncThread<A, R> {
thread: Thread,
ret: PhantomData<fn() -> R>,
init_args: Option<A>,
ret: PhantomData<R>,
recycle: bool,
}
impl Thread {
/// Returns reference to the Lua state that this thread is associated with.
#[inline(always)]
pub fn state(&self) -> *mut ffi::lua_State {
fn state(&self) -> *mut ffi::lua_State {
self.1
}
@@ -261,94 +141,17 @@ impl Thread {
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
let mut pushed_nargs = match self.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.clone()))?;
}
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
pushed_nargs += nargs;
}
let _thread_sg = StackGuard::with_top(thread_state, 0);
let (status, nresults) = self.resume_inner(&lua, pushed_nargs)?;
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& status.is_yielded()
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.clone()))?;
}
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
R::from_stack_multi(nresults, &lua)
if !self.status_inner(&lua).is_resumable() {
return Err(Error::CoroutineUnresumable);
}
}
/// Resumes execution of this thread, immediately raising an error.
///
/// This is a Luau specific extension.
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn resume_error<R>(&self, error: impl crate::IntoLua) -> Result<R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
match self.status_inner(&lua) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {}
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.clone()))?;
}
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 (status, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& status.is_yielded()
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.clone()))?;
}
let nresults = self.resume_inner(&lua, args)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -359,33 +162,35 @@ impl Thread {
/// Resumes execution of this thread.
///
/// It's similar to `resume()` but leaves `nresults` values on the thread stack.
unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
unsafe fn resume_inner(&self, lua: &RawLua, args: impl IntoLuaMulti) -> Result<c_int> {
let state = lua.state();
let thread_state = self.state();
let mut nresults = 0;
#[cfg(not(feature = "luau"))]
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
#[cfg(feature = "luau")]
let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int);
match ret {
ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)),
ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)),
ffi::LUA_ERRMEM => {
// Don't call error handler for memory errors
Err(pop_error(thread_state, ret))
}
_ => {
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
Err(pop_error(state, ret))
}
let nargs = args.push_into_stack_multi(lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
let mut nresults = 0;
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
if ret == ffi::LUA_ERRMEM {
// Don't call error handler for memory errors
return Err(pop_error(thread_state, ret));
}
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
return Err(pop_error(state, ret));
}
Ok(nresults)
}
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
match self.status_inner(&self.0.lua.lock()) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => ThreadStatus::Resumable,
ThreadStatusInner::New | ThreadStatusInner::Yielded => ThreadStatus::Resumable,
ThreadStatusInner::Running => ThreadStatus::Running,
ThreadStatusInner::Finished => ThreadStatus::Finished,
ThreadStatusInner::Error => ThreadStatus::Error,
@@ -400,77 +205,34 @@ impl Thread {
return ThreadStatusInner::Running;
}
let status = unsafe { ffi::lua_status(thread_state) };
let top = unsafe { ffi::lua_gettop(thread_state) };
match status {
ffi::LUA_YIELD => ThreadStatusInner::Yielded(top),
ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1),
ffi::LUA_YIELD => ThreadStatusInner::Yielded,
ffi::LUA_OK if unsafe { ffi::lua_gettop(thread_state) } > 0 => ThreadStatusInner::New,
ffi::LUA_OK => ThreadStatusInner::Finished,
_ => ThreadStatusInner::Error,
}
}
/// Returns `true` if this thread is resumable (meaning it can be resumed by calling
/// [`Thread::resume`]).
#[inline(always)]
pub fn is_resumable(&self) -> bool {
self.status() == ThreadStatus::Resumable
}
/// Returns `true` if this thread is currently running.
#[inline(always)]
pub fn is_running(&self) -> bool {
self.status() == ThreadStatus::Running
}
/// Returns `true` if this thread has finished executing.
#[inline(always)]
pub fn is_finished(&self) -> bool {
self.status() == ThreadStatus::Finished
}
/// Returns `true` if this thread has raised a Lua error during execution.
#[inline(always)]
pub fn is_error(&self) -> bool {
self.status() == ThreadStatus::Error
}
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
/// You can have multiple hooks for different threads.
///
/// To remove a hook call [`Thread::remove_hook`].
///
/// [`Lua::set_hook`]: crate::Lua::set_hook
/// To remove a hook call [`Lua::remove_hook`].
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
where
F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
{
let lua = self.0.lua.lock();
unsafe {
lua.set_thread_hook(
self.state(),
HookKind::Thread(triggers, crate::types::XRc::new(callback)),
)
}
}
/// Removes any hook function from this thread.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_hook(&self) {
let _lua = self.0.lua.lock();
unsafe {
ffi::lua_sethook(self.state(), None, 0, 0);
lua.set_thread_hook(self.state(), triggers, callback);
}
}
/// Resets a thread
///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
/// Returns an error in case of either the original error that stopped the thread or errors
/// Returns a error in case of either the original error that stopped the thread or errors
/// in closing methods.
///
/// In Luau: resets to the initial state of a newly created Lua thread.
@@ -484,10 +246,29 @@ impl Thread {
pub fn reset(&self, func: Function) -> Result<()> {
let lua = self.0.lua.lock();
let thread_state = self.state();
unsafe {
let status = self.status_inner(&lua);
self.reset_inner(status)?;
match self.status_inner(&lua) {
ThreadStatusInner::Running => return Err(Error::runtime("cannot reset a running thread")),
// Any Lua can reuse new or finished thread
ThreadStatusInner::New => unsafe { ffi::lua_settop(thread_state, 0) },
ThreadStatusInner::Finished => {}
#[cfg(not(any(feature = "lua54", feature = "luau")))]
_ => return Err(Error::runtime("cannot reset non-finished thread")),
#[cfg(any(feature = "lua54", feature = "luau"))]
_ => unsafe {
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = ffi::lua_closethread(thread_state, lua.state());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
},
}
unsafe {
// Push function to the top of the thread stack
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
@@ -502,47 +283,9 @@ impl Thread {
}
}
unsafe fn reset_inner(&self, status: ThreadStatusInner) -> Result<()> {
match status {
ThreadStatusInner::New(_) => {
// The thread is new, so we can just set the top to 0
ffi::lua_settop(self.state(), 0);
Ok(())
}
ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")),
ThreadStatusInner::Finished => Ok(()),
#[cfg(not(any(feature = "lua55", feature = "lua54", feature = "luau")))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
Err(Error::runtime("cannot reset non-finished thread"))
}
#[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(any(feature = "lua55", all(feature = "lua54", feature = "vendored")))]
let status = {
let lua = self.0.lua.lock();
ffi::lua_closethread(thread_state, lua.state())
};
#[cfg(any(feature = "lua55", feature = "lua54"))]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
Ok(())
}
}
}
/// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
///
/// Only resumable threads can be converted to [`AsyncThread`].
///
/// `args` are pushed to the thread stack and will be used when the thread is resumed.
/// `args` are passed as arguments to the thread function for first call.
/// The object calls [`resume`] while polling and also allow to run Rust futures
/// to completion using an executor.
///
@@ -550,6 +293,8 @@ impl Thread {
/// values whereas [`Future`] version discards that values and poll until the final
/// one (returned from the thread function).
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
/// [`resume`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
@@ -573,7 +318,7 @@ impl Thread {
/// end)
/// "#).eval()?;
///
/// let mut stream = thread.into_async::<i64>(1)?;
/// let mut stream = thread.into_async::<i64>(1);
/// let mut sum = 0;
/// while let Some(n) = stream.try_next().await? {
/// sum += n;
@@ -586,31 +331,15 @@ impl Thread {
/// ```
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> Result<AsyncThread<R>>
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> AsyncThread<impl IntoLuaMulti, R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
if !self.status_inner(&lua).is_resumable() {
return Err(Error::CoroutineUnresumable);
}
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
Ok(AsyncThread {
thread: self,
ret: PhantomData,
recycle: false,
})
AsyncThread {
thread: self,
init_args: Some(args),
ret: PhantomData,
recycle: false,
}
}
@@ -624,8 +353,6 @@ impl Thread {
/// Please note that Luau links environment table with chunk when loading it into Lua state.
/// Therefore you need to load chunks into a thread to link with the thread environment.
///
/// [`Lua::sandbox`]: crate::Lua::sandbox
///
/// # Examples
///
/// ```
@@ -649,8 +376,11 @@ impl Thread {
/// # #[cfg(not(feature = "luau"))]
/// # fn main() { }
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua.lock();
let state = lua.state();
@@ -679,12 +409,18 @@ impl fmt::Debug for Thread {
}
}
impl PartialEq for Thread {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl LuaType for Thread {
const TYPE_ID: c_int = ffi::LUA_TTHREAD;
}
#[cfg(feature = "async")]
impl<R> AsyncThread<R> {
impl<A, R> AsyncThread<A, R> {
#[inline(always)]
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
self.recycle = recyclable;
@@ -692,42 +428,26 @@ impl<R> AsyncThread<R> {
}
#[cfg(feature = "async")]
impl<R> Drop for AsyncThread<R> {
impl<A, R> Drop for AsyncThread<A, R> {
fn drop(&mut self) {
#[allow(clippy::collapsible_if)]
if self.recycle {
if let Some(lua) = self.thread.0.lua.try_lock() {
unsafe {
let mut status = self.thread.status_inner(&lua);
if matches!(status, ThreadStatusInner::Yielded(0)) {
// The thread is dropped while yielded, resume it with the "terminate" signal
ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0);
if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) {
// `new_status` should always be `ThreadStatusInner::Yielded(0)`
status = new_status;
}
}
// For Lua 5.4 this also closes all pending to-be-closed variables
if self.thread.reset_inner(status).is_ok() {
lua.recycle_thread(&mut self.thread);
}
}
// For Lua 5.4 this also closes all pending to-be-closed variables
unsafe { lua.recycle_thread(&mut self.thread) };
}
}
}
}
#[cfg(feature = "async")]
impl<R: FromLuaMulti> Stream for AsyncThread<R> {
impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua.lock();
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(None),
};
if !self.thread.status_inner(&lua).is_resumable() {
return Poll::Ready(None);
}
let state = lua.state();
let thread_state = self.thread.state();
@@ -736,50 +456,36 @@ impl<R: FromLuaMulti> Stream for AsyncThread<R> {
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker());
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.thread.clone()))?;
}
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(&lua, args)?
} else {
this.thread.resume_inner(&lua, ())?
};
let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?;
if status.is_yielded() {
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.thread.clone()))?;
}
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
// Continue polling
cx.waker().wake_by_ref();
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_stack_multi(nresults, &lua)))
}
}
}
#[cfg(feature = "async")]
impl<R: FromLuaMulti> Future for AsyncThread<R> {
impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let lua = self.thread.0.lua.lock();
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(Err(Error::CoroutineUnresumable)),
};
if !self.thread.status_inner(&lua).is_resumable() {
return Poll::Ready(Err(Error::CoroutineUnresumable));
}
let state = lua.state();
let thread_state = self.thread.state();
@@ -788,29 +494,21 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker());
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.thread.clone()))?;
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(&lua, args)?
} else {
this.thread.resume_inner(&lua, ())?
};
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
let (status, nresults) = self.thread.resume_inner(&lua, nargs)?;
if status.is_yielded() {
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.thread.clone()))?;
}
if !(nresults == 1 && is_poll_pending(thread_state)) {
// Ignore values returned via yield()
cx.waker().wake_by_ref();
}
if ffi::lua_status(thread_state) == ffi::LUA_YIELD {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
}
@@ -839,7 +537,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 = lua.set_waker(NonNull::from(waker));
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
Ok(WakerGuard {
lua,
prev,
@@ -851,7 +549,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[cfg(feature = "async")]
impl Drop for WakerGuard<'_, '_> {
fn drop(&mut self) {
self.lua.set_waker(self.prev);
unsafe { self.lua.set_waker(self.prev) };
}
}
@@ -864,7 +562,7 @@ mod assertions {
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Thread: Send, Sync);
#[cfg(all(feature = "async", not(feature = "send")))]
static_assertions::assert_not_impl_any!(AsyncThread<()>: Send);
static_assertions::assert_not_impl_any!(AsyncThread<(), ()>: Send);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync);
static_assertions::assert_impl_all!(AsyncThread<(), ()>: Send, Sync);
}
+105 -53
View File
@@ -1,20 +1,17 @@
//! Core conversion and extension traits.
//!
//! This module provides the fundamental traits for converting values between Rust and Lua,
//! and for defining native Lua callable functions.
use std::os::raw::c_int;
use std::string::String as StdString;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::multi::MultiValue;
use crate::private::Sealed;
use crate::state::{Lua, RawLua, WeakLua};
use crate::util::{check_stack, parse_lookup_path, short_type_name};
use crate::state::{Lua, RawLua};
use crate::types::MaybeSend;
use crate::util::{check_stack, short_type_name};
use crate::value::Value;
#[cfg(feature = "async")]
use crate::function::AsyncCallFuture;
use std::future::Future;
/// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized {
@@ -165,7 +162,7 @@ pub trait ObjectLike: Sealed {
/// arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
where
R: FromLuaMulti;
@@ -178,10 +175,12 @@ pub trait ObjectLike: Sealed {
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing the object itself along with `args` as function arguments.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
where
R: FromLuaMulti;
@@ -196,62 +195,115 @@ pub trait ObjectLike: Sealed {
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing `args` as function arguments.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
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<String>;
/// 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;
fn to_string(&self) -> Result<StdString>;
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
pub(crate) trait ShortTypeName {
#[inline(always)]
fn type_name() -> String {
fn type_name() -> StdString {
short_type_name::<Self>()
}
}
+12 -44
View File
@@ -1,9 +1,10 @@
use std::cell::UnsafeCell;
use std::os::raw::{c_int, c_void};
use std::rc::Rc;
#[cfg(not(feature = "luau"))]
use crate::debug::{Debug, HookTriggers};
use crate::error::Result;
#[cfg(not(feature = "luau"))]
use crate::hook::Debug;
use crate::state::{ExtraData, Lua, RawLua};
// Re-export mutex wrappers
@@ -20,9 +21,6 @@ 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.
@@ -38,13 +36,10 @@ unsafe impl Send for LightUserData {}
unsafe impl Sync for LightUserData {}
#[cfg(feature = "send")]
type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'a;
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'static>;
#[cfg(not(feature = "send"))]
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 Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 'static>;
pub(crate) type ScopedCallback<'s> = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 's>;
@@ -67,7 +62,7 @@ pub(crate) type AsyncCallback =
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback>;
#[cfg(feature = "async")]
pub(crate) type AsyncPollUpvalue = Upvalue<Option<BoxFuture<'static, Result<c_int>>>>;
pub(crate) type AsyncPollUpvalue = Upvalue<BoxFuture<'static, Result<c_int>>>;
/// Type to set next Lua VM action after executing interrupt or hook function.
pub enum VmState {
@@ -78,36 +73,22 @@ pub enum VmState {
Yield,
}
#[cfg(not(feature = "luau"))]
pub(crate) enum HookKind {
Global,
Thread(HookTriggers, HookCallback),
}
#[cfg(all(feature = "send", not(feature = "luau")))]
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState> + Send>;
pub(crate) type HookCallback = Rc<dyn Fn(&Lua, Debug) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), not(feature = "luau")))]
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState>>;
pub(crate) type HookCallback = Rc<dyn Fn(&Lua, Debug) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(feature = "send")]
pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()> + Send>;
#[cfg(not(feature = "send"))]
pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()>>;
#[cfg(feature = "send")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
#[cfg(not(feature = "send"))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg(all(not(feature = "send"), feature = "lua54"))]
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()>>;
/// A trait that adds `Send` requirement if `send` feature is enabled.
@@ -116,24 +97,11 @@ 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"))]
impl<T> MaybeSend for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(feature = "send")]
pub trait MaybeSync: Sync {}
#[cfg(feature = "send")]
impl<T: Sync> MaybeSync for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(not(feature = "send"))]
pub trait MaybeSync {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSync for T {}
pub(crate) struct DestructedUserdata;
pub(crate) trait LuaType {
+4 -4
View File
@@ -43,7 +43,7 @@ impl AppData {
#[inline]
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<'_, T>> {
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<T>> {
match self.try_borrow(guard) {
Ok(data) => data,
Err(err) => panic!("already mutably borrowed: {err:?}"),
@@ -53,7 +53,7 @@ impl AppData {
pub(crate) fn try_borrow<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRef<'_, T>>, BorrowError> {
) -> Result<Option<AppDataRef<T>>, BorrowError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow())
@@ -74,7 +74,7 @@ impl AppData {
#[inline]
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<'_, T>> {
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<T>> {
match self.try_borrow_mut(guard) {
Ok(data) => data,
Err(err) => panic!("already borrowed: {err:?}"),
@@ -84,7 +84,7 @@ impl AppData {
pub(crate) fn try_borrow_mut<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
) -> Result<Option<AppDataRefMut<T>>, BorrowMutError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow_mut())
+2 -2
View File
@@ -31,7 +31,7 @@ mod inner {
}
#[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, T> {
pub(crate) fn lock(&self) -> ReentrantMutexGuard<T> {
ReentrantMutexGuard(&self.0)
}
@@ -69,7 +69,7 @@ mod inner {
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0.0
&self.0 .0
}
}
}
+23 -28
View File
@@ -1,39 +1,22 @@
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,
// 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))
}
pub(crate) drop: bool,
}
impl ValueRef {
#[inline]
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
let index = index.into();
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
ValueRef {
lua: lua.weak().clone(),
index: *index.0,
index_count: Some(index),
index,
drop: true,
}
}
@@ -42,6 +25,16 @@ 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 {
@@ -50,15 +43,17 @@ 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 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()
&& let Some(lua) = self.lua.try_lock()
{
unsafe { lua.drop_ref(self) }
if self.drop {
if let Some(lua) = self.lua.try_lock() {
unsafe { lua.drop_ref(self) };
}
}
}
+120 -256
View File
@@ -1,28 +1,24 @@
//! Lua userdata handling.
//!
//! This module provides types for creating and working with Lua userdata from Rust.
use std::any::TypeId;
use std::ffi::CStr;
use std::fmt;
use std::hash::Hash;
use std::os::raw::{c_char, c_void};
use std::string::String as StdString;
use crate::Either;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::Lua;
use crate::string::LuaString;
use crate::string::String;
use crate::table::{Table, TablePairs};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{MaybeSend, MaybeSync, ValueRef};
use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata};
use crate::types::{MaybeSend, ValueRef};
use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard};
use crate::value::Value;
#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
@@ -30,12 +26,12 @@ use {
// Re-export for convenience
pub(crate) use cell::UserDataStorage;
pub use r#ref::{UserDataOwned, UserDataRef, UserDataRefMut};
pub use r#ref::{UserDataRef, UserDataRefMut};
pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
pub(crate) use util::{
TypeIdHints, borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata,
init_userdata_metatable,
borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, init_userdata_metatable,
TypeIdHints,
};
/// Kinds of metamethods that can be overridden.
@@ -60,53 +56,30 @@ pub enum MetaMethod {
/// The unary minus (`-`) operator.
Unm,
/// The floor division (//) operator.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau")))
)]
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
IDiv,
/// The bitwise AND (&) operator.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
BAnd,
/// The bitwise OR (|) operator.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
BOr,
/// The bitwise XOR (binary ~) operator.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
BXor,
/// The bitwise NOT (unary ~) operator.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
BNot,
/// The bitwise left shift (<<) operator.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
#[cfg(any(feature = "lua54", feature = "lua53"))]
Shl,
/// The bitwise right shift (>>) operator.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
#[cfg(any(feature = "lua54", feature = "lua53"))]
Shr,
/// The string concatenation operator `..`.
Concat,
@@ -128,36 +101,19 @@ pub enum MetaMethod {
///
/// This is not an operator, but will be called by methods such as `tostring` and `print`.
ToString,
/// The `__todebugstring` metamethod for debug purposes.
///
/// This is an mlua-specific metamethod that can be used to provide debug representation for
/// userdata.
ToDebugString,
/// The `__pairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
)))
)]
///
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52",))]
Pairs,
/// The `__ipairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in [`ipairs`] function.
///
/// Requires `feature = "lua52"`
///
/// [`ipairs`]: https://www.lua.org/manual/5.2/manual.html#pdf-ipairs
#[cfg(any(feature = "lua52", feature = "luajit52", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua52", feature = "luajit52"))))]
@@ -166,6 +122,8 @@ pub enum MetaMethod {
///
/// Executed before the iteration begins, and should return an iterator function like `next`
/// (or a custom one).
///
/// Requires `feature = "lua"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Iter,
@@ -176,9 +134,11 @@ pub enum MetaMethod {
/// More information about to-be-closed variables can be found in the Lua 5.4
/// [documentation][lua_doc].
///
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
Close,
/// The `__name`/`__type` metafield.
///
@@ -194,7 +154,7 @@ impl PartialEq<MetaMethod> for &str {
}
}
impl PartialEq<MetaMethod> for String {
impl PartialEq<MetaMethod> for StdString {
fn eq(&self, other: &MetaMethod) -> bool {
self == other.name()
}
@@ -218,19 +178,19 @@ impl MetaMethod {
MetaMethod::Pow => "__pow",
MetaMethod::Unm => "__unm",
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
MetaMethod::IDiv => "__idiv",
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BAnd => "__band",
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BOr => "__bor",
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BXor => "__bxor",
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BNot => "__bnot",
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::Shl => "__shl",
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::Shr => "__shr",
MetaMethod::Concat => "__concat",
@@ -242,22 +202,15 @@ impl MetaMethod {
MetaMethod::NewIndex => "__newindex",
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
MetaMethod::ToDebugString => "__todebugstring",
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
#[cfg(any(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(any(feature = "lua55", feature = "lua54"))]
#[cfg(feature = "lua54")]
MetaMethod::Close => "__close",
#[rustfmt::skip]
@@ -268,7 +221,9 @@ impl MetaMethod {
pub(crate) const fn as_cstr(self) -> &'static CStr {
match self {
#[rustfmt::skip]
MetaMethod::Type => if cfg!(feature = "luau") { c"__type" } else { c"__name" },
MetaMethod::Type => unsafe {
CStr::from_bytes_with_nul_unchecked(if cfg!(feature = "luau") { b"__type\0" } else { b"__name\0" })
},
_ => unreachable!(),
}
}
@@ -289,13 +244,6 @@ impl AsRef<str> for MetaMethod {
}
}
impl From<MetaMethod> for String {
#[inline]
fn from(method: MetaMethod) -> Self {
method.name().to_owned()
}
}
/// Method registry for [`UserData`] implementors.
pub trait UserDataMethods<T> {
/// Add a regular method which accepts a `&T` as the first parameter.
@@ -305,7 +253,7 @@ pub trait UserDataMethods<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular method is found.
fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -316,42 +264,22 @@ pub trait UserDataMethods<T> {
/// Refer to [`add_method`] for more information about the implementation.
///
/// [`add_method`]: UserDataMethods::add_method
fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
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.
fn add_method_once<M, A, R>(&mut self, name: impl Into<String>, 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.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -363,10 +291,12 @@ pub trait UserDataMethods<T> {
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -374,39 +304,12 @@ 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")))]
fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<String>, 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
/// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first
/// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`.
fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -417,7 +320,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_function`] that accepts a `FnMut` argument.
///
/// [`add_function`]: UserDataMethods::add_function
fn add_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -428,10 +331,12 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_function`].
///
/// Requires `feature = "async"`
///
/// [`add_function`]: UserDataMethods::add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -446,7 +351,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -460,7 +365,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -470,13 +375,12 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_method`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method`]: UserDataMethods::add_meta_method
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -489,10 +393,12 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_method_mut`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -505,7 +411,7 @@ pub trait UserDataMethods<T> {
/// Metamethods for binary operators can be triggered if either the left or right argument to
/// the binary operator has a metatable, so the first argument here is not necessarily a
/// userdata of type `T`.
fn add_meta_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -516,7 +422,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_meta_function`] that accepts a `FnMut` argument.
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -526,13 +432,12 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_function`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -551,7 +456,7 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, it will
/// be used as a fall-back if no regular field or method are found.
fn add_field<V>(&mut self, name: impl Into<String>, value: V)
fn add_field<V>(&mut self, name: impl ToString, value: V)
where
V: IntoLua + 'static;
@@ -562,7 +467,7 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular field or method are found.
fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
@@ -575,21 +480,21 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod
/// will be used as a fall-back if no regular field is found.
fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
/// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T`
/// argument.
fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
/// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T`
/// first argument.
fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
fn add_field_function_set<F, A>(&mut self, name: impl ToString, function: F)
where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
@@ -602,7 +507,7 @@ pub trait UserDataFields<T> {
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field<V>(&mut self, name: impl Into<String>, value: V)
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
where
V: IntoLua + 'static;
@@ -614,7 +519,7 @@ pub trait UserDataFields<T> {
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field_with<F, R>(&mut self, name: impl Into<String>, f: F)
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F)
where
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua;
@@ -714,7 +619,7 @@ pub trait UserData: Sized {
///
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct AnyUserData(pub(crate) ValueRef);
impl AnyUserData {
@@ -726,14 +631,6 @@ 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
@@ -796,16 +693,22 @@ impl AnyUserData {
/// Keeps associated user values unchanged (they will be collected by Lua's GC).
pub fn take<T: 'static>(&self) -> Result<T> {
let lua = self.0.lua.lock();
match lua.get_userdata_ref_type_id(&self.0)? {
Some(type_id) if type_id == TypeId::of::<T>() => unsafe {
let ref_thread = lua.ref_thread();
if (*get_userdata::<UserDataStorage<T>>(ref_thread, self.0.index)).has_exclusive_access() {
take_userdata::<UserDataStorage<T>>(ref_thread, self.0.index).into_inner()
} else {
Err(Error::UserDataBorrowMutError)
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let type_id = lua.push_userdata_ref(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
if (*get_userdata::<UserDataStorage<T>>(state, -1)).has_exclusive_access() {
take_userdata::<UserDataStorage<T>>(state).into_inner()
} else {
Err(Error::UserDataBorrowMutError)
}
}
},
_ => Err(Error::UserDataTypeMismatch),
_ => Err(Error::UserDataTypeMismatch),
}
}
}
@@ -994,17 +897,22 @@ impl AnyUserData {
self.raw_metatable().map(UserDataMetatable)
}
/// Returns a raw metatable of this [`AnyUserData`].
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
pub fn get_metatable(&self) -> Result<UserDataMetatable> {
self.metatable()
}
fn raw_metatable(&self) -> Result<Table> {
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
let state = lua.state();
unsafe {
// Check that userdata is registered and not destructed
// All registered userdata types have a non-empty metatable
let _type_id = lua.get_userdata_ref_type_id(&self.0)?;
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
ffi::lua_getmetatable(ref_thread, self.0.index);
Ok(Table(lua.pop_ref_thread()))
lua.push_userdata_ref(&self.0)?;
ffi::lua_getmetatable(state, -1); // Checked that non-empty on the previous call
Ok(Table(lua.pop_ref()))
}
}
@@ -1027,10 +935,8 @@ impl AnyUserData {
lua.get_userdata_ref_type_id(&self.0).ok().flatten()
}
/// Returns a type name of this userdata (from a metatable field).
///
/// If no type name is set, returns `userdata`.
pub fn type_name(&self) -> Result<LuaString> {
/// Returns a type name of this `UserData` (from a metatable field).
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -1047,8 +953,8 @@ impl AnyUserData {
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
};
match name_type {
ffi::LUA_TSTRING => Ok(LuaString(lua.pop_ref())),
_ => lua.create_string(b"userdata"),
ffi::LUA_TSTRING => Ok(Some(String(lua.pop_ref()).to_str()?.to_owned())),
_ => Ok(None),
}
}
}
@@ -1073,7 +979,7 @@ impl AnyUserData {
/// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using
/// [`Lua::create_ser_userdata`]).
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
pub(crate) fn is_serializable(&self) -> bool {
let lua = self.0.lua.lock();
let is_serializable = || unsafe {
@@ -1084,48 +990,6 @@ impl AnyUserData {
};
is_serializable().unwrap_or(false)
}
unsafe fn invoke_tostring_dbg(&self) -> Result<Option<String>> {
let lua = self.0.lua.lock();
let state = lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(&self.0);
protect_lua!(state, 1, 1, fn(state) {
// Try `__todebugstring` metamethod first, then `__tostring`
#[allow(clippy::collapsible_if)]
if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 {
if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 {
ffi::lua_pushnil(state);
}
}
})?;
Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy()))
}
pub(crate) fn fmt_pretty(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
// Try converting to a (debug) string first, with fallback to `__name/__type`
match unsafe { self.invoke_tostring_dbg() } {
Ok(Some(s)) => write!(fmt, "{s}"),
_ => {
let name = self.type_name().ok();
let name = (name.as_ref())
.map(|s| Either::Left(s.display()))
.unwrap_or(Either::Right("userdata"));
write!(fmt, "{name}: {:?}", self.to_pointer())
}
}
}
}
impl fmt::Debug for AnyUserData {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
return self.fmt_pretty(fmt);
}
fmt.debug_tuple("AnyUserData").field(&self.0).finish()
}
}
/// Handle to a [`AnyUserData`] metatable.
@@ -1166,7 +1030,7 @@ impl UserDataMetatable {
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
///
/// [`Result`]: crate::Result
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<'_, V> {
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<V> {
UserDataMetatablePairs(self.0.pairs())
}
}
@@ -1176,13 +1040,13 @@ impl UserDataMetatable {
/// It skips restricted metamethods, such as `__gc` or `__metatable`.
///
/// This struct is created by the [`UserDataMetatable::pairs`] method.
pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, String, V>);
pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>);
impl<V> Iterator for UserDataMetatablePairs<'_, V>
where
V: FromLua,
{
type Item = Result<(String, V)>;
type Item = Result<(StdString, V)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
@@ -1199,7 +1063,7 @@ where
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl Serialize for AnyUserData {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
@@ -1222,7 +1086,7 @@ impl AnyUserData {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_any_userdata`] under the hood.
pub fn wrap<T: MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
pub fn wrap<T: MaybeSend + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_any_userdata(data))
}
@@ -1230,9 +1094,9 @@ impl AnyUserData {
/// [`IntoLua`] trait.
///
/// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn wrap_ser<T: Serialize + MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
}
}
+77 -45
View File
@@ -1,19 +1,19 @@
use std::cell::RefCell;
use std::cell::{RefCell, UnsafeCell};
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
use crate::types::XRc;
use super::lock::{RawLock, RwLock, UserDataLock};
use super::lock::{RawLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serde", not(feature = "send")))]
#[cfg(all(feature = "serialize", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serde", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send + Sync;
#[cfg(all(feature = "serialize", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send;
pub(crate) enum UserDataStorage<T> {
Owned(UserDataVariant<T>),
@@ -23,9 +23,9 @@ pub(crate) enum UserDataStorage<T> {
// A enum for storing userdata values.
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> {
Default(XRc<RwLock<T>>),
#[cfg(feature = "serde")]
Serializable(XRc<RwLock<Box<DynSerialize>>>),
Default(XRc<UserDataCell<T>>),
#[cfg(feature = "serialize")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
}
impl<T> Clone for UserDataVariant<T> {
@@ -33,8 +33,8 @@ impl<T> Clone for UserDataVariant<T> {
fn clone(&self) -> Self {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serde")]
Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)),
#[cfg(feature = "serialize")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
}
}
}
@@ -42,12 +42,10 @@ impl<T> Clone for UserDataVariant<T> {
impl<T> UserDataVariant<T> {
#[inline(always)]
pub(super) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
// Shared (read) lock is always correct for in-place borrows:
// - this method is called internally while the Lua mutex is held, ensuring exclusive Lua-level
// access per call frame
// - with `send` feature, all owned userdata satisfies `T: Sync`, so simultaneous shared references
// from multiple threads are sound
// - without `send` feature, single-threaded execution makes shared lock safe for any `T`
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() }))
}
@@ -80,12 +78,10 @@ impl<T> UserDataVariant<T> {
return Err(Error::UserDataBorrowMutError);
}
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().into_inner(),
#[cfg(feature = "serde")]
Self::Serializable(inner) => unsafe {
// The serde variant erases `T` to `Box<DynSerialize>`, so we
// must cast the raw pointer back to recover the concrete type.
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().into_inner());
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
#[cfg(feature = "serialize")]
Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
*Box::from_raw(raw as *mut T)
},
})
@@ -95,44 +91,79 @@ impl<T> UserDataVariant<T> {
fn strong_count(&self) -> usize {
match self {
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serde")]
Self::Serializable(inner) => XRc::strong_count(inner),
#[cfg(feature = "serialize")]
Self::Serializable(inner, _) => XRc::strong_count(inner),
}
}
#[inline(always)]
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => unsafe { inner.raw() },
#[cfg(feature = "serde")]
Self::Serializable(inner) => unsafe { inner.raw() },
Self::Default(inner) => &inner.raw_lock,
#[cfg(feature = "serialize")]
Self::Serializable(inner, _) => &inner.raw_lock,
}
}
#[inline(always)]
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.data_ptr(),
#[cfg(feature = "serde")]
Self::Serializable(inner) => unsafe { (&mut **inner.data_ptr()) as *mut DynSerialize as *mut T },
Self::Default(inner) => inner.value.get(),
#[cfg(feature = "serialize")]
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
}
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self {
Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.data_ptr()).serialize(serializer)
Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe {
#[cfg(feature = "send")]
if *is_sync {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
} else {
let _guard = (variant.raw_lock().try_lock_exclusive_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
#[cfg(not(feature = "send"))]
{
let _ = is_sync;
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
},
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")),
}
}
}
/// A type that provides interior mutability for a userdata value (thread-safe).
pub(crate) struct UserDataCell<T> {
raw_lock: RawLock,
value: UnsafeCell<T>,
}
#[cfg(feature = "send")]
unsafe impl<T: Send> Send for UserDataCell<T> {}
#[cfg(feature = "send")]
unsafe impl<T: Send> Sync for UserDataCell<T> {}
impl<T> UserDataCell<T> {
#[inline(always)]
fn new(value: T) -> Self {
UserDataCell {
raw_lock: RawLock::INIT,
value: UnsafeCell::new(value),
}
}
}
pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T),
RefMut(RefCell<*mut T>),
@@ -142,10 +173,10 @@ pub(crate) enum ScopedUserDataVariant<T> {
impl<T> Drop for ScopedUserDataVariant<T> {
#[inline]
fn drop(&mut self) {
if let Self::Boxed(value) = self
&& let Ok(value) = value.try_borrow_mut()
{
unsafe { drop(Box::from_raw(*value)) }
if let Self::Boxed(value) = self {
if let Ok(value) = value.try_borrow_mut() {
unsafe { drop(Box::from_raw(*value)) };
}
}
}
}
@@ -153,7 +184,7 @@ impl<T> Drop for ScopedUserDataVariant<T> {
impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new(data: T) -> Self {
Self::Owned(UserDataVariant::Default(XRc::new(RwLock::new(data))))
Self::Owned(UserDataVariant::Default(XRc::new(UserDataCell::new(data))))
}
#[inline(always)]
@@ -166,18 +197,19 @@ impl<T: 'static> UserDataStorage<T> {
Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data)))
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
#[inline(always)]
pub(crate) fn new_ser(data: T) -> Self
where
T: Serialize + crate::types::MaybeSend + crate::types::MaybeSync,
T: Serialize + crate::types::MaybeSend,
{
let data = Box::new(data) as Box<DynSerialize>;
let variant = UserDataVariant::Serializable(XRc::new(RwLock::new(data)));
let is_sync = super::util::is_sync::<T>();
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
Self::Owned(variant)
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
#[inline(always)]
pub(crate) fn is_serializable(&self) -> bool {
matches!(self, Self::Owned(UserDataVariant::Serializable(..)))

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