mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
213 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 247208edb1 | |||
| e08768cc5e | |||
| 5b38af9746 | |||
| 54907f80c5 | |||
| ae512f2b49 | |||
| 53c159b6cb | |||
| 2beca6ebe1 | |||
| 09da7a41e5 | |||
| bad20374ad | |||
| 40b507c3ec | |||
| 537cc995f6 | |||
| 5d27cb91b2 | |||
| c70a636ca9 | |||
| 13ff0ca798 | |||
| 44f49e35d6 | |||
| e1ee4058a6 | |||
| f06d0020ea | |||
| d399559d30 | |||
| 30735d5ff1 | |||
| 75c23e5853 | |||
| 347856b806 | |||
| 774a63bece | |||
| c481c87eac | |||
| 85b280a9d6 | |||
| db7b782d3c | |||
| 5f38445558 | |||
| df0a44d405 | |||
| f0806a6d62 | |||
| 3516f4c6ca | |||
| ca73583714 | |||
| 36560435f7 | |||
| 763c2b2564 | |||
| bafdb6138c | |||
| c9d6a610e1 | |||
| bd63f63bc9 | |||
| c035c23a15 | |||
| cb153a52b2 | |||
| b1c69d3005 | |||
| 841bd332e4 | |||
| 815d1bd7c9 | |||
| 78331ceebe | |||
| f945a35cbd | |||
| 459edb6816 | |||
| 00328b0b64 | |||
| 928d94d255 | |||
| 583c35a172 | |||
| 1791c599f4 | |||
| 1e48817a64 | |||
| 8d219503dd | |||
| 95367855c1 | |||
| 49389c4aa4 | |||
| 13dc2b5352 | |||
| 7afbf74128 | |||
| 06c3bd9d69 | |||
| 1ddaea60ce | |||
| a653d08768 | |||
| 2b6b0144a1 | |||
| 4cfe0be945 | |||
| a9a4814c3c | |||
| b1f73ec29d | |||
| 61a2141151 | |||
| dea38f27a5 | |||
| 04aaa18dc8 | |||
| d8455c038a | |||
| ca22ea3be7 | |||
| cf05593d66 | |||
| d011a1f851 | |||
| 1ec4661bf9 | |||
| d3b2999d2f | |||
| 72f6536efb | |||
| 646827a6bb | |||
| 1882931cd9 | |||
| c90cac5189 | |||
| c0d839d8d2 | |||
| 80471c6dad | |||
| 4b9d1cf271 | |||
| 55c07f3b28 | |||
| ef4eabd327 | |||
| a3302afdc1 | |||
| dfb4e9a668 | |||
| 92db0f6d3a | |||
| a3697ab1db | |||
| 052740db15 | |||
| faf547c154 | |||
| 0de7cd1c7d | |||
| 58953e5635 | |||
| 6406de405d | |||
| 727096dd3b | |||
| 2445230759 | |||
| 1cd2bdc808 | |||
| f8ed33a2aa | |||
| 25955893e0 | |||
| 3f0c69b70b | |||
| 9da98d42c7 | |||
| f539f60987 | |||
| aa187e6663 | |||
| ec10bf2a39 | |||
| 3ea80b763d | |||
| 2e0e86dab2 | |||
| 0ac7aebb27 | |||
| 634e5d4550 | |||
| 05d6c20520 | |||
| 63e7cfd31b | |||
| 9c24c99cbe | |||
| 62f84828f2 | |||
| 7bc72be7d3 | |||
| 2fbbbe4238 | |||
| f00208373e | |||
| a2dc662a92 | |||
| caeac2e9a3 | |||
| 3d5261640d | |||
| b57a6239a6 | |||
| 031424f6ce | |||
| c4c9609ac6 | |||
| 6fcd18e434 | |||
| 39cac5699a | |||
| e6e1ef014f | |||
| 942a443592 | |||
| 65e292dac4 | |||
| 1a82f83644 | |||
| 38fbd08c72 | |||
| b3854d2f1d | |||
| 00a56b115b | |||
| 2fefaafaa6 | |||
| 76a8f8cc71 | |||
| 13395e9c3d | |||
| c61219dd93 | |||
| 2e7c654cfe | |||
| f36aaa5ce1 | |||
| df38878278 | |||
| c5c1fe3b85 | |||
| 0cc4b15f6b | |||
| f16aca687d | |||
| 9b45663afd | |||
| c0c802262e | |||
| dda344bfc6 | |||
| 93adfa42d1 | |||
| 5dc6c3214b | |||
| da08f7210f | |||
| 8b7a85076a | |||
| a4ff68a120 | |||
| 5454cfa5ed | |||
| f9d9dc74a7 | |||
| ed796fff51 | |||
| 65e5be81ba | |||
| 4fa8e645b7 | |||
| 05a0abb8c2 | |||
| f0c0527c2b | |||
| 2490dfeb38 | |||
| 4a3dbafb6e | |||
| 97ca3e2c08 | |||
| d6fb328660 | |||
| edd508ed1f | |||
| a30a736291 | |||
| 8e1df48e81 | |||
| efd192b816 | |||
| 5e1451a465 | |||
| 64b1d152b9 | |||
| a8a4aa8c93 | |||
| 9fda2ecfcc | |||
| 21fc924457 | |||
| 30d5e08a8a | |||
| 560a30ca02 | |||
| 117f8377e2 | |||
| 8173ef2fa8 | |||
| 4e5677ae54 | |||
| 49958f4827 | |||
| 39b3af2ff2 | |||
| b805939320 | |||
| bd13300288 | |||
| 12e61e01f6 | |||
| 53ff494ab9 | |||
| 1cb081d20a | |||
| c1a018a60d | |||
| 4d2ff45a8b | |||
| 72682198eb | |||
| 87fa663ac1 | |||
| 1f970824b4 | |||
| 8d864e2687 | |||
| b0140cdb9e | |||
| 806817212e | |||
| 95c623e94d | |||
| 0a1a1fe0b6 | |||
| 4fe7d151a1 | |||
| edbf1e9150 | |||
| 69011a89d2 | |||
| 0ed11e4134 | |||
| 0ce599aff2 | |||
| 1ebb4b468a | |||
| 5ab97666d7 | |||
| e706ae4fcb | |||
| ae7cdcb934 | |||
| dacddfa967 | |||
| a01b032c35 | |||
| 18497f1528 | |||
| 28e8f56989 | |||
| c1f8abba9e | |||
| d1a587f49a | |||
| 5bbd23ed1a | |||
| 74b4601b48 | |||
| a89800b949 | |||
| 2b6172ef38 | |||
| 20f7ce097d | |||
| 6882e0434e | |||
| fca38a6637 | |||
| cdd6a99136 | |||
| 21d39a069d | |||
| 58965c6255 | |||
| 8574682ffc | |||
| 47bc372096 | |||
| d376cb9403 | |||
| ea5ecccf02 | |||
| cf71edc492 |
+14
-14
@@ -27,8 +27,8 @@ jobs:
|
||||
- name: Build ${{ matrix.lua }} vendored
|
||||
run: |
|
||||
cargo build --features "${{ matrix.lua }},vendored"
|
||||
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"
|
||||
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"
|
||||
shell: bash
|
||||
- name: Build ${{ matrix.lua }} pkg-config
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
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"
|
||||
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
|
||||
|
||||
build_aarch64_cross_ubuntu:
|
||||
name: Cross-compile to aarch64-unknown-linux-gnu
|
||||
@@ -72,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,serialize,macros,anyhow,userdata-wrappers"
|
||||
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
|
||||
shell: bash
|
||||
|
||||
build_armv7_cross_ubuntu:
|
||||
@@ -94,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,serialize,macros,anyhow,userdata-wrappers"
|
||||
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
|
||||
shell: bash
|
||||
|
||||
test:
|
||||
@@ -123,14 +123,14 @@ jobs:
|
||||
- name: Run ${{ matrix.lua }} tests
|
||||
run: |
|
||||
cargo test --features "${{ matrix.lua }},vendored"
|
||||
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"
|
||||
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
|
||||
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
|
||||
shell: bash
|
||||
- name: Run compile tests (macos lua54)
|
||||
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
|
||||
run: |
|
||||
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored
|
||||
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" --tests -- --ignored
|
||||
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serde,macros" --tests -- --ignored
|
||||
shell: bash
|
||||
|
||||
test_with_sanitizer:
|
||||
@@ -154,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,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
|
||||
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
|
||||
shell: bash
|
||||
env:
|
||||
RUSTFLAGS: -Z sanitizer=address
|
||||
@@ -181,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,serialize,macros,anyhow,userdata-wrappers"
|
||||
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
|
||||
shell: bash
|
||||
env:
|
||||
RUSTFLAGS: --cfg=force_memory_limit
|
||||
@@ -194,7 +194,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust: [stable]
|
||||
lua: [lua54, lua53, lua52, lua51, luajit, luau]
|
||||
lua: [lua54, lua53, lua52, lua51, luajit]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
@@ -254,7 +254,7 @@ jobs:
|
||||
- name: Run ${{ matrix.lua }} tests
|
||||
run: |
|
||||
cargo test --tests --features "${{ matrix.lua }},vendored"
|
||||
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
|
||||
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
|
||||
|
||||
rustfmt:
|
||||
name: Rustfmt
|
||||
@@ -281,4 +281,4 @@ jobs:
|
||||
- uses: giraffate/clippy-action@v1
|
||||
with:
|
||||
reporter: 'github-pr-review'
|
||||
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
|
||||
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Typos Check
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@master
|
||||
with:
|
||||
config: ./typos.toml
|
||||
+73
-6
@@ -1,13 +1,80 @@
|
||||
## v0.10.5 (May 24th, 2025)
|
||||
## v0.11.4 (Sep 29, 2025)
|
||||
|
||||
- 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)
|
||||
- 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`
|
||||
|
||||
## 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
|
||||
|
||||
+17
-17
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.10.5" # remember to update mlua_derive
|
||||
version = "0.11.4" # remember to update mlua_derive
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
|
||||
rust-version = "1.79.0"
|
||||
edition = "2021"
|
||||
@@ -16,7 +16,7 @@ with async/await features and support of writing native Lua modules in Rust.
|
||||
"""
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["lua54", "vendored", "async", "send", "serialize", "macros"]
|
||||
features = ["lua54", "vendored", "async", "send", "serde", "macros"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[workspace]
|
||||
@@ -32,21 +32,24 @@ lua52 = ["ffi/lua52"]
|
||||
lua51 = ["ffi/lua51"]
|
||||
luajit = ["ffi/luajit"]
|
||||
luajit52 = ["luajit", "ffi/luajit52"]
|
||||
luau = ["ffi/luau", "dep:libloading"]
|
||||
luau = ["ffi/luau"]
|
||||
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 = ["parking_lot/send_guard", "error-send"]
|
||||
send = ["error-send"]
|
||||
error-send = []
|
||||
serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
|
||||
serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
|
||||
macros = ["mlua_derive/macros"]
|
||||
anyhow = ["dep:anyhow", "error-send"]
|
||||
userdata-wrappers = []
|
||||
userdata-wrappers = ["parking_lot/send_guard"]
|
||||
|
||||
# deprecated features
|
||||
serialize = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" }
|
||||
mlua_derive = { version = "=0.11.0", optional = true, path = "mlua_derive" }
|
||||
bstr = { version = "1.0", features = ["std"], default-features = false }
|
||||
either = "1.0"
|
||||
num-traits = { version = "0.2.14" }
|
||||
@@ -59,10 +62,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] }
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
rustversion = "1.0"
|
||||
|
||||
ffi = { package = "mlua-sys", version = "0.6.8", path = "mlua-sys" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libloading = { version = "0.8", optional = true }
|
||||
ffi = { package = "mlua-sys", version = "0.8.3", path = "mlua-sys" }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
@@ -78,8 +78,8 @@ tempfile = "3"
|
||||
static_assertions = "1.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["async_tokio"] }
|
||||
rustyline = "15.0"
|
||||
criterion = { version = "0.7", features = ["async_tokio"] }
|
||||
rustyline = "17.0"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
[lints.rust]
|
||||
@@ -93,7 +93,7 @@ required-features = ["async"]
|
||||
[[bench]]
|
||||
name = "serde"
|
||||
harness = false
|
||||
required-features = ["serialize"]
|
||||
required-features = ["serde"]
|
||||
|
||||
[[example]]
|
||||
name = "async_http_client"
|
||||
@@ -101,7 +101,7 @@ required-features = ["async", "macros"]
|
||||
|
||||
[[example]]
|
||||
name = "async_http_reqwest"
|
||||
required-features = ["async", "serialize", "macros"]
|
||||
required-features = ["async", "serde", "macros"]
|
||||
|
||||
[[example]]
|
||||
name = "async_http_server"
|
||||
@@ -116,8 +116,8 @@ name = "guided_tour"
|
||||
required-features = ["macros"]
|
||||
|
||||
[[example]]
|
||||
name = "serialize"
|
||||
required-features = ["serialize"]
|
||||
name = "serde"
|
||||
required-features = ["serde"]
|
||||
|
||||
[[example]]
|
||||
name = "userdata"
|
||||
|
||||
@@ -17,18 +17,14 @@
|
||||
[Benchmarks]: https://github.com/khvzak/script-bench-rs
|
||||
[FAQ]: FAQ.md
|
||||
|
||||
> **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.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 `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
|
||||
WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
|
||||
|
||||
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions
|
||||
[Luau]: https://luau.org
|
||||
@@ -37,7 +33,7 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a
|
||||
|
||||
### Feature flags
|
||||
|
||||
`mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only required set of features.
|
||||
`mlua` uses feature flags to reduce the number of dependencies and compiled code, and allow choosing only the required set of features.
|
||||
Below is a list of the available feature flags. By default `mlua` does not enable any features.
|
||||
|
||||
* `lua54`: enable Lua [5.4] support
|
||||
@@ -49,12 +45,12 @@ 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) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
|
||||
* `vendored`: build static Lua(JIT) libraries from sources during `mlua` compilation using [lua-src] or [luajit-src]
|
||||
* `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`
|
||||
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
|
||||
* `serde`: add serialization and deserialization support to `mlua` types using [serde]
|
||||
* `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`
|
||||
@@ -76,7 +72,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 require 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 requires 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 +87,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,serialize
|
||||
cargo run --example async_http_reqwest --features=lua54,async,macros,serde
|
||||
|
||||
# async http server
|
||||
cargo run --example async_http_server --features=lua54,async,macros,send
|
||||
curl -v http://localhost:3000
|
||||
```
|
||||
|
||||
### Serialization (serde) support
|
||||
### Serde 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).
|
||||
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).
|
||||
|
||||
[Example](examples/serialize.rs)
|
||||
[Example](examples/serde.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 +108,28 @@ With `serialize` 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` 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.
|
||||
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.
|
||||
`LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
|
||||
|
||||
An example how to use them:
|
||||
An example of 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 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 the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
|
||||
|
||||
### Standalone mode
|
||||
In a standalone mode `mlua` allows to add to your application scripting support with a gently configured Lua runtime to ensure safety and soundness.
|
||||
In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness.
|
||||
|
||||
Add to `Cargo.toml` :
|
||||
Add to `Cargo.toml`:
|
||||
|
||||
``` toml
|
||||
[dependencies]
|
||||
mlua = { version = "0.10", features = ["lua54", "vendored"] }
|
||||
mlua = { version = "0.11", features = ["lua54", "vendored"] }
|
||||
```
|
||||
|
||||
`main.rs`
|
||||
@@ -157,21 +153,21 @@ fn main() -> LuaResult<()> {
|
||||
```
|
||||
|
||||
### Module mode
|
||||
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).
|
||||
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).
|
||||
|
||||
[Example](examples/module)
|
||||
|
||||
Add to `Cargo.toml` :
|
||||
Add to `Cargo.toml`:
|
||||
|
||||
``` toml
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10", features = ["lua54", "module"] }
|
||||
mlua = { version = "0.11", features = ["lua54", "module"] }
|
||||
```
|
||||
|
||||
`lib.rs` :
|
||||
`lib.rs`:
|
||||
|
||||
``` rust
|
||||
use mlua::prelude::*;
|
||||
@@ -214,14 +210,14 @@ rustflags = [
|
||||
```
|
||||
On Linux you can build modules normally with `cargo build --release`.
|
||||
|
||||
On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags).
|
||||
On Windows the target module will be linked with the `lua5x.dll` library (depending on your feature flags).
|
||||
Your main application should provide this library.
|
||||
|
||||
Module builds don't require Lua lib or headers to be installed on the system.
|
||||
Module builds don't require Lua binaries 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 +230,10 @@ Modules written in Rust and published to luarocks:
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
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 +241,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 could be
|
||||
resumed then 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 can then be
|
||||
resumed by returning or propagating the Lua error to Rust code.
|
||||
|
||||
For example:
|
||||
``` rust
|
||||
@@ -265,16 +261,16 @@ let _ = lua.load(r#"
|
||||
unreachable!()
|
||||
```
|
||||
|
||||
Optionally `mlua` can disable Rust panics catching in Lua via `pcall`/`xpcall` and automatically resume
|
||||
Optionally, `mlua` can disable Rust panic 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 on a way to prevent catching errors that are wrapped Rust panics.
|
||||
functions 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
|
||||
remains usable after a user generated panic, and such panics should not break internal invariants or
|
||||
remain 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 a bug.
|
||||
Below is a list of `mlua` behaviors that should be considered bugs.
|
||||
If you encounter them, a bug report would be very welcome:
|
||||
|
||||
+ If you can cause UB with `mlua` without typing the word "unsafe", this is a bug.
|
||||
@@ -287,12 +283,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 controlled environment.
|
||||
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in a controlled environment.
|
||||
|
||||
`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only).
|
||||
`mlua` provides the `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).
|
||||
|
||||
@@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn table_ref_clone(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
let t = lua.create_table().unwrap();
|
||||
|
||||
c.bench_function("table [ref clone]", |b| {
|
||||
b.iter_batched(
|
||||
|| collect_gc_twice(&lua),
|
||||
|_| {
|
||||
let _t2 = t.clone();
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn function_create(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
@@ -350,6 +366,42 @@ fn userdata_call_method(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
// A userdata method call that goes through an implicit `__index` function
|
||||
fn userdata_call_method_complex(c: &mut Criterion) {
|
||||
struct UserData(u64);
|
||||
impl LuaUserData for UserData {
|
||||
fn register(registry: &mut LuaUserDataRegistry<Self>) {
|
||||
registry.add_field_method_get("val", |_, this| Ok(this.0));
|
||||
registry.add_method_mut("inc_by", |_, this, by: u64| {
|
||||
this.0 += by;
|
||||
Ok(this.0)
|
||||
});
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
registry.enable_namecall();
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
let ud = lua.create_userdata(UserData(0)).unwrap();
|
||||
let inc_by = lua
|
||||
.load("function(ud, s) return ud:inc_by(s) end")
|
||||
.eval::<LuaFunction>()
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("userdata [call method complex]", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
collect_gc_twice(&lua);
|
||||
},
|
||||
|_| {
|
||||
inc_by.call::<()>((&ud, 1)).unwrap();
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn userdata_async_call_method(c: &mut Criterion) {
|
||||
struct UserData(i64);
|
||||
impl LuaUserData for UserData {
|
||||
@@ -399,6 +451,7 @@ criterion_group! {
|
||||
table_traversal_pairs,
|
||||
table_traversal_for_each,
|
||||
table_traversal_sequence,
|
||||
table_ref_clone,
|
||||
|
||||
function_create,
|
||||
function_call_sum,
|
||||
@@ -413,6 +466,7 @@ criterion_group! {
|
||||
userdata_create,
|
||||
userdata_call_index,
|
||||
userdata_call_method,
|
||||
userdata_call_method_complex,
|
||||
userdata_async_call_method,
|
||||
}
|
||||
|
||||
|
||||
+10
-8
@@ -20,14 +20,16 @@ fn main() {
|
||||
match lua.load(&line).eval::<MultiValue>() {
|
||||
Ok(values) => {
|
||||
editor.add_history_entry(line).unwrap();
|
||||
println!(
|
||||
"{}",
|
||||
values
|
||||
.iter()
|
||||
.map(|value| format!("{:#?}", value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\t")
|
||||
);
|
||||
if values.len() > 0 {
|
||||
println!(
|
||||
"{}",
|
||||
values
|
||||
.iter()
|
||||
.map(|value| format!("{:#?}", value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\t")
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(Error::SyntaxError {
|
||||
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua-sys"
|
||||
version = "0.6.8"
|
||||
version = "0.8.3"
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
|
||||
rust-version = "1.71"
|
||||
edition = "2021"
|
||||
@@ -30,6 +30,7 @@ luau = ["luau0-src"]
|
||||
luau-codegen = ["luau"]
|
||||
luau-vector4 = ["luau"]
|
||||
vendored = ["lua-src", "luajit-src"]
|
||||
external = []
|
||||
module = []
|
||||
|
||||
[dependencies]
|
||||
@@ -38,9 +39,9 @@ module = []
|
||||
cc = "1.0"
|
||||
cfg-if = "1.0"
|
||||
pkg-config = "0.3.17"
|
||||
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 }
|
||||
lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true }
|
||||
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
|
||||
luau0-src = { version = "0.15.6", optional = true }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
|
||||
|
||||
@@ -14,22 +14,29 @@ fn main() {
|
||||
#[cfg(all(feature = "luau", feature = "module", windows))]
|
||||
compile_error!("Luau does not support `module` mode on Windows");
|
||||
|
||||
#[cfg(all(feature = "module", feature = "vendored"))]
|
||||
compile_error!("`vendored` and `module` features are mutually exclusive");
|
||||
#[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");
|
||||
|
||||
println!("cargo:rerun-if-changed=build");
|
||||
|
||||
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;
|
||||
// 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");
|
||||
}
|
||||
|
||||
println!("cargo:rustc-cfg=raw_dylib");
|
||||
#[cfg(not(feature = "module"))]
|
||||
find::probe_lua();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "module"))]
|
||||
find::probe_lua();
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
//! 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, dead_code)]
|
||||
#![allow(non_camel_case_types, non_snake_case)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![allow(unsafe_op_in_unsafe_fn)]
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
|
||||
} else if compat53_findfield(L, objidx, level - 1) != 0 {
|
||||
// try recursively
|
||||
lua_remove(L, -2); // remove table (but keep name)
|
||||
lua_pushliteral(L, ".");
|
||||
lua_pushliteral(L, c".");
|
||||
lua_insert(L, -2); // place '.' between the two names
|
||||
lua_concat(L, 3);
|
||||
return 1;
|
||||
@@ -121,13 +121,13 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, ar: *mut lua_Debug) {
|
||||
lua_pushfstring(L, cstr!("function '%s'"), (*ar).name);
|
||||
} else if *(*ar).what == b'm' as c_char {
|
||||
// main?
|
||||
lua_pushliteral(L, "main chunk");
|
||||
lua_pushliteral(L, c"main chunk");
|
||||
} else if *(*ar).what == b'C' as c_char {
|
||||
if compat53_pushglobalfuncname(L, ar) != 0 {
|
||||
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
|
||||
lua_remove(L, -2); // remove name
|
||||
} else {
|
||||
lua_pushliteral(L, "?");
|
||||
lua_pushliteral(L, c"?");
|
||||
}
|
||||
} else {
|
||||
lua_pushfstring(
|
||||
@@ -186,7 +186,8 @@ 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);
|
||||
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
|
||||
// Lua 5.3+ returns "false" for `-0.0`
|
||||
if n.to_bits() == (i as lua_Number).to_bits() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -377,7 +378,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, "stack overflow");
|
||||
lua_pushliteral(L, c"stack overflow");
|
||||
lua_error(L);
|
||||
}
|
||||
}
|
||||
@@ -467,12 +468,12 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
|
||||
if !msg.is_null() {
|
||||
lua_pushfstring(L, cstr!("%s\n"), msg);
|
||||
}
|
||||
lua_pushliteral(L, "stack traceback:");
|
||||
lua_pushliteral(L, c"stack traceback:");
|
||||
while lua_getstack(L1, level, &mut ar) != 0 {
|
||||
level += 1;
|
||||
if level == mark {
|
||||
// too many levels?
|
||||
lua_pushliteral(L, "\n\t..."); // add a '...'
|
||||
lua_pushliteral(L, c"\n\t..."); // add a '...'
|
||||
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
|
||||
} else {
|
||||
lua_getinfo(L1, cstr!("Slnt"), &mut ar);
|
||||
@@ -480,7 +481,7 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
|
||||
if ar.currentline > 0 {
|
||||
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
|
||||
}
|
||||
lua_pushliteral(L, " in ");
|
||||
lua_pushliteral(L, c" in ");
|
||||
compat53_pushfuncname(L, &mut ar);
|
||||
lua_concat(L, lua_gettop(L) - top);
|
||||
}
|
||||
@@ -493,16 +494,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, "nil");
|
||||
lua_pushliteral(L, c"nil");
|
||||
}
|
||||
LUA_TSTRING | LUA_TNUMBER => {
|
||||
lua_pushvalue(L, idx);
|
||||
}
|
||||
LUA_TBOOLEAN => {
|
||||
if lua_toboolean(L, idx) == 0 {
|
||||
lua_pushliteral(L, "false");
|
||||
lua_pushliteral(L, c"false");
|
||||
} else {
|
||||
lua_pushliteral(L, "true");
|
||||
lua_pushliteral(L, c"true");
|
||||
}
|
||||
}
|
||||
t => {
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct luaL_Reg {
|
||||
}
|
||||
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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"))]
|
||||
extern "C-unwind" {
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! 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;
|
||||
@@ -83,13 +84,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
|
||||
/// Type for memory-allocation functions (no unwinding)
|
||||
#[rustfmt::skip]
|
||||
pub type lua_Alloc =
|
||||
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
|
||||
unsafe extern "C" 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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// State manipulation
|
||||
//
|
||||
@@ -219,7 +220,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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
|
||||
}
|
||||
|
||||
@@ -227,7 +228,7 @@ extern "C-unwind" {
|
||||
// Miscellaneous functions
|
||||
//
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
@@ -312,10 +313,8 @@ 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 str) {
|
||||
use std::ffi::CString;
|
||||
let c_str = CString::new(s).unwrap();
|
||||
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
|
||||
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
|
||||
lua_pushstring_(L, s.as_ptr());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -371,7 +370,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"))]
|
||||
extern "C-unwind" {
|
||||
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;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
//! Contains definitions from `lualib.h`.
|
||||
|
||||
use std::os::raw::c_int;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use super::lua::lua_State;
|
||||
|
||||
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";
|
||||
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");
|
||||
|
||||
#[cfg(feature = "luajit")]
|
||||
pub const LUA_BITLIBNAME: &str = "bit";
|
||||
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit");
|
||||
#[cfg(feature = "luajit")]
|
||||
pub const LUA_JITLIBNAME: &str = "jit";
|
||||
pub const LUA_JITLIBNAME: *const c_char = cstr!("jit");
|
||||
#[cfg(feature = "luajit")]
|
||||
pub const LUA_FFILIBNAME: &str = "ffi";
|
||||
pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi");
|
||||
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
|
||||
@@ -51,7 +51,8 @@ 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);
|
||||
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
|
||||
// Lua 5.3+ returns "false" for `-0.0`
|
||||
if n.to_bits() == (i as lua_Number).to_bits() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -199,16 +200,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, "nil");
|
||||
lua_pushliteral(L, c"nil");
|
||||
}
|
||||
LUA_TSTRING | LUA_TNUMBER => {
|
||||
lua_pushvalue(L, idx);
|
||||
}
|
||||
LUA_TBOOLEAN => {
|
||||
if lua_toboolean(L, idx) == 0 {
|
||||
lua_pushliteral(L, "false");
|
||||
lua_pushliteral(L, c"false");
|
||||
} else {
|
||||
lua_pushliteral(L, "true");
|
||||
lua_pushliteral(L, c"true");
|
||||
}
|
||||
}
|
||||
t => {
|
||||
|
||||
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
|
||||
}
|
||||
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
|
||||
|
||||
#[link_name = "luaL_getmetafield"]
|
||||
@@ -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"))]
|
||||
extern "C-unwind" {
|
||||
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);
|
||||
|
||||
@@ -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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
pub fn luaL_loadbufferx(
|
||||
L: *mut lua_State,
|
||||
buff: *const c_char,
|
||||
|
||||
+12
-13
@@ -1,5 +1,6 @@
|
||||
//! 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;
|
||||
@@ -88,13 +89,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
|
||||
/// Type for memory-allocation functions (no unwinding)
|
||||
#[rustfmt::skip]
|
||||
pub type lua_Alloc =
|
||||
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
|
||||
unsafe extern "C" 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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// State manipulation
|
||||
//
|
||||
@@ -160,14 +161,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"))]
|
||||
extern "C-unwind" {
|
||||
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 = "lua52", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Push functions (C -> stack)
|
||||
//
|
||||
@@ -256,7 +257,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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Coroutine functions
|
||||
//
|
||||
@@ -288,12 +289,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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Miscellaneous functions
|
||||
//
|
||||
@@ -395,10 +396,8 @@ 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 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())
|
||||
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
|
||||
lua_pushstring(L, s.as_ptr());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -449,7 +448,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"))]
|
||||
extern "C-unwind" {
|
||||
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;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
//! Contains definitions from `lualib.h`.
|
||||
|
||||
use std::os::raw::c_int;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use super::lua::lua_State;
|
||||
|
||||
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";
|
||||
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");
|
||||
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
|
||||
@@ -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: &str = "_LOADED";
|
||||
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
|
||||
|
||||
// Key, in the registry, for table of preloaded loaders
|
||||
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
|
||||
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_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"))]
|
||||
extern "C-unwind" {
|
||||
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;
|
||||
@@ -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"))]
|
||||
extern "C-unwind" {
|
||||
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);
|
||||
|
||||
@@ -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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
pub fn luaL_loadbufferx(
|
||||
L: *mut lua_State,
|
||||
buff: *const c_char,
|
||||
|
||||
+12
-13
@@ -1,5 +1,6 @@
|
||||
//! 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};
|
||||
@@ -95,13 +96,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
|
||||
/// Type for memory-allocation functions (no unwinding)
|
||||
#[rustfmt::skip]
|
||||
pub type lua_Alloc =
|
||||
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
|
||||
unsafe extern "C" 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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// State manipulation
|
||||
//
|
||||
@@ -171,14 +172,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"))]
|
||||
extern "C-unwind" {
|
||||
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 = "lua53", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Push functions (C -> stack)
|
||||
//
|
||||
@@ -264,7 +265,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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Coroutine functions
|
||||
//
|
||||
@@ -299,12 +300,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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Miscellaneous functions
|
||||
//
|
||||
@@ -407,10 +408,8 @@ 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 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())
|
||||
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
|
||||
lua_pushstring(L, s.as_ptr());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -478,7 +477,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"))]
|
||||
extern "C-unwind" {
|
||||
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;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
//! Contains definitions from `lualib.h`.
|
||||
|
||||
use std::os::raw::c_int;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use super::lua::lua_State;
|
||||
|
||||
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";
|
||||
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");
|
||||
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
|
||||
@@ -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: &str = "_LOADED";
|
||||
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
|
||||
|
||||
// Key, in the registry, for table of preloaded loaders
|
||||
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
|
||||
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_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"))]
|
||||
extern "C-unwind" {
|
||||
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;
|
||||
@@ -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"))]
|
||||
extern "C-unwind" {
|
||||
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);
|
||||
|
||||
@@ -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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
pub fn luaL_loadbufferx(
|
||||
L: *mut lua_State,
|
||||
buff: *const c_char,
|
||||
|
||||
+13
-14
@@ -1,5 +1,6 @@
|
||||
//! 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};
|
||||
@@ -94,16 +95,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
|
||||
/// Type for memory-allocation functions (no unwinding)
|
||||
#[rustfmt::skip]
|
||||
pub type lua_Alloc =
|
||||
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
|
||||
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 = "lua54", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// State manipulation
|
||||
//
|
||||
@@ -185,14 +186,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"))]
|
||||
extern "C-unwind" {
|
||||
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 = "lua54", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Push functions (C -> stack)
|
||||
//
|
||||
@@ -278,7 +279,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"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Coroutine functions
|
||||
//
|
||||
@@ -302,7 +303,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"))]
|
||||
extern "C-unwind" {
|
||||
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);
|
||||
}
|
||||
@@ -323,12 +324,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"))]
|
||||
extern "C-unwind" {
|
||||
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 = "lua54", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// Miscellaneous functions
|
||||
//
|
||||
@@ -434,10 +435,8 @@ 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 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())
|
||||
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
|
||||
lua_pushstring(L, s.as_ptr());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -520,7 +519,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"))]
|
||||
extern "C-unwind" {
|
||||
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;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
//! Contains definitions from `lualib.h`.
|
||||
|
||||
use std::os::raw::c_int;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use super::lua::lua_State;
|
||||
|
||||
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";
|
||||
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");
|
||||
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
|
||||
+68
-20
@@ -10,6 +10,8 @@ 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);
|
||||
@@ -41,7 +43,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
|
||||
} else if compat53_findfield(L, objidx, level - 1) != 0 {
|
||||
// try recursively
|
||||
lua_remove(L, -2); // remove table (but keep name)
|
||||
lua_pushliteral(L, ".");
|
||||
lua_pushliteral(L, c".");
|
||||
lua_insert(L, -2); // place '.' between the two names
|
||||
lua_concat(L, 3);
|
||||
return 1;
|
||||
@@ -75,7 +77,7 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_De
|
||||
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
|
||||
lua_remove(L, -2); // remove name
|
||||
} else {
|
||||
lua_pushliteral(L, "?");
|
||||
lua_pushliteral(L, c"?");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,13 +120,19 @@ 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);
|
||||
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
|
||||
// Lua 5.3+ returns "false" for `-0.0`
|
||||
if n.to_bits() == (i as lua_Number).to_bits() {
|
||||
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())
|
||||
@@ -176,6 +184,7 @@ 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)
|
||||
}
|
||||
|
||||
@@ -190,7 +199,7 @@ pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_
|
||||
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, "__mlua_uservalues");
|
||||
lua_pushliteral(L, c"__mlua_uservalues");
|
||||
if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
|
||||
return LUA_TNIL;
|
||||
}
|
||||
@@ -211,6 +220,7 @@ pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
|
||||
let n = n.try_into().expect("cannot convert index from lua_Integer");
|
||||
lua_rawseti_(L, idx, n)
|
||||
}
|
||||
|
||||
@@ -227,13 +237,13 @@ pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
|
||||
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, "__mlua_uservalues");
|
||||
lua_pushliteral(L, c"__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, "k");
|
||||
lua_pushliteral(L, c"k");
|
||||
lua_setfield(L, -2, cstr!("__mode"));
|
||||
lua_setmetatable(L, -2);
|
||||
lua_pushvalue(L, -2);
|
||||
@@ -284,6 +294,19 @@ 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
|
||||
//
|
||||
@@ -294,12 +317,30 @@ 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, "stack overflow");
|
||||
lua_pushliteral(L, c"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 {
|
||||
@@ -328,11 +369,11 @@ pub unsafe fn luaL_loadbufferenv(
|
||||
mode: *const c_char,
|
||||
mut env: c_int,
|
||||
) -> c_int {
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
fn free(p: *mut c_void);
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) {
|
||||
unsafe extern "C" fn data_dtor(_: *mut lua_State, data: *mut c_void) {
|
||||
free(*(data as *mut *mut c_char) as *mut c_void);
|
||||
}
|
||||
|
||||
@@ -348,7 +389,7 @@ pub unsafe fn luaL_loadbufferenv(
|
||||
}
|
||||
}
|
||||
|
||||
if chunk_is_text {
|
||||
let status = if chunk_is_text {
|
||||
if env < 0 {
|
||||
env -= 1;
|
||||
}
|
||||
@@ -357,14 +398,21 @@ 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 ok = luau_load(L, name, data, size, env) == 0;
|
||||
let status = luau_load(L, name, data, size, env);
|
||||
lua_replace(L, -2); // replace data with the result
|
||||
if !ok {
|
||||
return LUA_ERRSYNTAX;
|
||||
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;
|
||||
}
|
||||
} else if luau_load(L, name, data, size, env) != 0 {
|
||||
return LUA_ERRSYNTAX;
|
||||
}
|
||||
|
||||
LUA_OK
|
||||
}
|
||||
|
||||
@@ -415,11 +463,11 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
|
||||
if !msg.is_null() {
|
||||
lua_pushfstring(L, cstr!("%s\n"), msg);
|
||||
}
|
||||
lua_pushliteral(L, "stack traceback:");
|
||||
lua_pushliteral(L, c"stack traceback:");
|
||||
while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 {
|
||||
if level + 1 == mark {
|
||||
// too many levels?
|
||||
lua_pushliteral(L, "\n\t..."); // add a '...'
|
||||
lua_pushliteral(L, c"\n\t..."); // add a '...'
|
||||
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
|
||||
} else {
|
||||
lua_getinfo(L1, level, cstr!("sln"), &mut ar);
|
||||
@@ -427,7 +475,7 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
|
||||
if ar.currentline > 0 {
|
||||
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
|
||||
}
|
||||
lua_pushliteral(L, " in ");
|
||||
lua_pushliteral(L, c" in ");
|
||||
compat53_pushfuncname(L, level, &mut ar);
|
||||
lua_concat(L, lua_gettop(L) - top);
|
||||
}
|
||||
@@ -441,16 +489,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, "nil");
|
||||
lua_pushliteral(L, c"nil");
|
||||
}
|
||||
LUA_TSTRING | LUA_TNUMBER => {
|
||||
lua_pushvalue(L, idx);
|
||||
}
|
||||
LUA_TBOOLEAN => {
|
||||
if lua_toboolean(L, idx) == 0 {
|
||||
lua_pushliteral(L, "false");
|
||||
lua_pushliteral(L, c"false");
|
||||
} else {
|
||||
lua_pushliteral(L, "true");
|
||||
lua_pushliteral(L, c"true");
|
||||
}
|
||||
}
|
||||
t => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::os::raw::{c_char, c_float, c_int, c_void};
|
||||
use std::ptr;
|
||||
|
||||
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX};
|
||||
use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct luaL_Reg {
|
||||
@@ -11,7 +11,7 @@ pub struct luaL_Reg {
|
||||
pub func: lua_CFunction,
|
||||
}
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
@@ -33,8 +33,10 @@ 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;
|
||||
|
||||
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;
|
||||
#[link_name = "luaL_checkinteger"]
|
||||
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int;
|
||||
#[link_name = "luaL_optinteger"]
|
||||
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
|
||||
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;
|
||||
|
||||
@@ -69,10 +71,17 @@ extern "C-unwind" {
|
||||
|
||||
pub fn luaL_newstate() -> *mut lua_State;
|
||||
|
||||
// TODO: luaL_findtable
|
||||
pub fn luaL_findtable(
|
||||
L: *mut lua_State,
|
||||
idx: c_int,
|
||||
fname: *const c_char,
|
||||
szhint: c_int,
|
||||
) -> *const c_char;
|
||||
|
||||
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;
|
||||
|
||||
// sandbox libraries and globals
|
||||
#[link_name = "luaL_sandbox"]
|
||||
pub fn luaL_sandbox_(L: *mut lua_State);
|
||||
@@ -141,7 +150,7 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
|
||||
}
|
||||
|
||||
// set all builtin metatables to read-only
|
||||
lua_pushliteral(L, "");
|
||||
lua_pushliteral(L, c"");
|
||||
if lua_getmetatable(L, -1) != 0 {
|
||||
lua_setreadonly(L, -1, enabled);
|
||||
lua_pop(L, 2);
|
||||
@@ -173,7 +182,7 @@ pub struct luaL_Strbuf {
|
||||
// For compatibility
|
||||
pub type luaL_Buffer = luaL_Strbuf;
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
|
||||
+37
-32
@@ -1,5 +1,6 @@
|
||||
//! 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};
|
||||
@@ -11,10 +12,10 @@ pub const LUA_MULTRET: c_int = -1;
|
||||
const LUAI_MAXCSTACK: c_int = 1000000;
|
||||
|
||||
// Number of valid Lua userdata tags
|
||||
const LUA_UTAG_LIMIT: c_int = 128;
|
||||
pub const LUA_UTAG_LIMIT: c_int = 128;
|
||||
|
||||
// Number of valid Lua lightuserdata tags
|
||||
const LUA_LUTAG_LIMIT: c_int = 128;
|
||||
pub const LUA_LUTAG_LIMIT: c_int = 128;
|
||||
|
||||
//
|
||||
// Pseudo-indices
|
||||
@@ -69,8 +70,11 @@ pub const LUA_MINSTACK: c_int = 20;
|
||||
/// A Lua number, usually equivalent to `f64`.
|
||||
pub type lua_Number = c_double;
|
||||
|
||||
/// A Lua integer, equivalent to `i32`.
|
||||
pub type lua_Integer = c_int;
|
||||
/// 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 unsigned integer, equivalent to `u32`.
|
||||
pub type lua_Unsigned = c_uint;
|
||||
@@ -79,20 +83,19 @@ 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.
|
||||
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 userdata destructor functions (no unwinding).
|
||||
pub type lua_Destructor = unsafe extern "C" fn(L: *mut lua_State, *mut c_void);
|
||||
|
||||
/// Type for memory-allocation functions.
|
||||
/// Type for memory-allocation functions (no unwinding).
|
||||
pub type lua_Alloc =
|
||||
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
|
||||
unsafe extern "C" 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")
|
||||
}
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
//
|
||||
// State manipulation
|
||||
//
|
||||
@@ -136,7 +139,7 @@ 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) -> lua_Integer;
|
||||
pub fn lua_tointegerx_(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> c_int;
|
||||
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;
|
||||
@@ -160,7 +163,8 @@ extern "C-unwind" {
|
||||
//
|
||||
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);
|
||||
#[link_name = "lua_pushinteger"]
|
||||
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int);
|
||||
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);
|
||||
@@ -186,7 +190,7 @@ 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_Udestructor) -> *mut c_void;
|
||||
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Destructor) -> *mut c_void;
|
||||
|
||||
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
|
||||
|
||||
@@ -231,6 +235,7 @@ 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
|
||||
@@ -260,14 +265,14 @@ pub const LUA_GCSETGOAL: c_int = 7;
|
||||
pub const LUA_GCSETSTEPMUL: c_int = 8;
|
||||
pub const LUA_GCSETSTEPSIZE: c_int = 9;
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
|
||||
}
|
||||
|
||||
//
|
||||
// Memory statistics
|
||||
//
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
}
|
||||
@@ -275,7 +280,7 @@ extern "C-unwind" {
|
||||
//
|
||||
// Miscellaneous functions
|
||||
//
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
@@ -285,7 +290,7 @@ extern "C-unwind" {
|
||||
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, idx: c_int);
|
||||
pub fn lua_setuserdatametatable(L: *mut lua_State, tag: 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;
|
||||
@@ -300,7 +305,7 @@ extern "C-unwind" {
|
||||
pub const LUA_NOREF: c_int = -1;
|
||||
pub const LUA_REFNIL: c_int = 0;
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe 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);
|
||||
}
|
||||
@@ -310,13 +315,13 @@ extern "C-unwind" {
|
||||
//
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number {
|
||||
lua_tonumberx(L, i, ptr::null_mut())
|
||||
pub unsafe fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number {
|
||||
lua_tonumberx(L, idx, 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())
|
||||
pub unsafe fn lua_tointeger_(L: *mut lua_State, idx: c_int) -> c_int {
|
||||
lua_tointegerx_(L, idx, ptr::null_mut())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -340,12 +345,14 @@ 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) -> *mut T {
|
||||
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
|
||||
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) {
|
||||
ptr::drop_in_place(ud as *mut T);
|
||||
}
|
||||
|
||||
lua_newuserdatadtor(L, mem::size_of::<T>(), destructor::<T>) 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
|
||||
}
|
||||
|
||||
// TODO: lua_strlen
|
||||
@@ -401,10 +408,8 @@ 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 str) {
|
||||
use std::ffi::CString;
|
||||
let c_str = CString::new(s).unwrap();
|
||||
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
|
||||
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
|
||||
lua_pushstring_(L, s.as_ptr());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -466,7 +471,7 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
|
||||
size: usize,
|
||||
);
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
@@ -532,12 +537,12 @@ pub struct lua_Callbacks {
|
||||
pub onallocate: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, osize: usize, nsize: usize)>,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks;
|
||||
}
|
||||
|
||||
// Functions from customization lib
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
|
||||
pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn(
|
||||
constant: *mut lua_CompileConstant,
|
||||
);
|
||||
|
||||
extern "C" {
|
||||
unsafe 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);
|
||||
@@ -84,7 +84,7 @@ extern "C" {
|
||||
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
|
||||
}
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe extern "C-unwind" {
|
||||
#[link_name = "luau_compile"]
|
||||
pub fn luau_compile_(
|
||||
source: *const c_char,
|
||||
@@ -94,7 +94,7 @@ extern "C-unwind" {
|
||||
) -> *mut c_char;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
fn free(p: *mut c_void);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
|
||||
|
||||
use super::lua::lua_State;
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe 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
-12
@@ -1,21 +1,21 @@
|
||||
//! Contains definitions from `lualib.h`.
|
||||
|
||||
use std::os::raw::c_int;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use super::lua::lua_State;
|
||||
|
||||
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";
|
||||
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");
|
||||
|
||||
extern "C-unwind" {
|
||||
unsafe 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;
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
//! 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,
|
||||
}
|
||||
|
||||
#[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,
|
||||
|
||||
// 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.
|
||||
// If not, require-by-string will call to_parent until either a configuration file is present or
|
||||
// NAVIGATE_FAILURE is returned (at root).
|
||||
pub is_config_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
|
||||
|
||||
// Parses the configuration file in the current context for the given alias and returns its
|
||||
// value or WRITE_FAILURE if not found. This function is only called if is_config_present
|
||||
// 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 is_config_present returns true. If this function pointer is set, get_alias must not be set. Opting
|
||||
// in to this function pointer enables parsing configuration files internally.
|
||||
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,
|
||||
>,
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ pub use lua::*;
|
||||
pub use luacode::*;
|
||||
pub use luacodegen::*;
|
||||
pub use lualib::*;
|
||||
pub use luarequire::*;
|
||||
|
||||
pub mod compat;
|
||||
pub mod lauxlib;
|
||||
@@ -13,3 +14,4 @@ pub mod lua;
|
||||
pub mod luacode;
|
||||
pub mod luacodegen;
|
||||
pub mod lualib;
|
||||
pub mod luarequire;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua_derive"
|
||||
version = "0.10.1"
|
||||
version = "0.11.0"
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
|
||||
edition = "2021"
|
||||
description = "Procedural macros for the mlua crate."
|
||||
|
||||
@@ -103,7 +103,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
|
||||
|
||||
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
|
||||
|
||||
impl<F> AsChunk<'static> for InnerChunk<F>
|
||||
impl<F> AsChunk for InnerChunk<F>
|
||||
where
|
||||
F: FnOnce(&Lua) -> Result<Table>,
|
||||
{
|
||||
@@ -120,7 +120,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
|
||||
Some(ChunkMode::Text)
|
||||
}
|
||||
|
||||
fn source(self) -> IoResult<Cow<'static, [u8]>> {
|
||||
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
|
||||
Ok(Cow::Borrowed((#source).as_bytes()))
|
||||
}
|
||||
}
|
||||
@@ -129,13 +129,13 @@ pub fn chunk(input: TokenStream) -> TokenStream {
|
||||
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)?;
|
||||
meta.raw_set("__index", &globals)?;
|
||||
meta.raw_set("__newindex", &globals)?;
|
||||
|
||||
// Add captured variables
|
||||
#(#caps)*
|
||||
|
||||
env.set_metatable(Some(meta));
|
||||
env.set_metatable(Some(meta))?;
|
||||
Ok(env)
|
||||
};
|
||||
|
||||
|
||||
+98
-17
@@ -1,6 +1,9 @@
|
||||
#[cfg(feature = "serialize")]
|
||||
use std::io;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
|
||||
use crate::state::RawLua;
|
||||
use crate::types::ValueRef;
|
||||
|
||||
/// A Luau buffer type.
|
||||
@@ -16,16 +19,17 @@ pub struct Buffer(pub(crate) ValueRef);
|
||||
impl Buffer {
|
||||
/// Copies the buffer data into a new `Vec<u8>`.
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
unsafe { self.as_slice().to_vec() }
|
||||
let lua = self.0.lua.lock();
|
||||
self.as_slice(&lua).to_vec()
|
||||
}
|
||||
|
||||
/// Returns the length of the buffer.
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { self.as_slice().len() }
|
||||
let lua = self.0.lua.lock();
|
||||
self.as_slice(&lua).len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the buffer is empty.
|
||||
#[doc(hidden)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
@@ -35,7 +39,8 @@ impl Buffer {
|
||||
/// Offset is 0-based.
|
||||
#[track_caller]
|
||||
pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
|
||||
let data = unsafe { self.as_slice() };
|
||||
let lua = self.0.lua.lock();
|
||||
let data = self.as_slice(&lua);
|
||||
let mut bytes = [0u8; N];
|
||||
bytes.copy_from_slice(&data[offset..offset + N]);
|
||||
bytes
|
||||
@@ -46,21 +51,36 @@ impl Buffer {
|
||||
/// Offset is 0-based.
|
||||
#[track_caller]
|
||||
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
|
||||
let data = unsafe {
|
||||
let (buf, size) = self.as_raw_parts();
|
||||
std::slice::from_raw_parts_mut(buf, size)
|
||||
};
|
||||
let lua = self.0.lua.lock();
|
||||
let data = self.as_slice_mut(&lua);
|
||||
data[offset..offset + bytes.len()].copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn as_slice(&self) -> &[u8] {
|
||||
let (buf, size) = self.as_raw_parts();
|
||||
std::slice::from_raw_parts(buf, size)
|
||||
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
|
||||
/// buffer.
|
||||
///
|
||||
/// Buffer operations are infallible, none of the read/write functions will return a Err.
|
||||
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
|
||||
BufferCursor(self, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
|
||||
unsafe {
|
||||
let (buf, size) = self.as_raw_parts(lua);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
|
||||
let lua = self.0.lua.lock();
|
||||
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
|
||||
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");
|
||||
@@ -68,15 +88,76 @@ impl Buffer {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
|
||||
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
struct BufferCursor(Buffer, usize);
|
||||
|
||||
impl io::Read for BufferCursor {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice(&lua);
|
||||
if self.1 == data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let len = buf.len().min(data.len() - self.1);
|
||||
buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
|
||||
self.1 += len;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for BufferCursor {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice_mut(&lua);
|
||||
if self.1 == data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let len = buf.len().min(data.len() - self.1);
|
||||
data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
|
||||
self.1 += len;
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for BufferCursor {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice(&lua);
|
||||
let new_offset = match pos {
|
||||
io::SeekFrom::Start(offset) => offset as i64,
|
||||
io::SeekFrom::End(offset) => data.len() as i64 + offset,
|
||||
io::SeekFrom::Current(offset) => self.1 as i64 + offset,
|
||||
};
|
||||
if new_offset < 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid seek to a negative position",
|
||||
));
|
||||
}
|
||||
if new_offset as usize > data.len() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid seek to a position beyond the end of the buffer",
|
||||
));
|
||||
}
|
||||
self.1 = new_offset as usize;
|
||||
Ok(self.1 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for Buffer {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
|
||||
serializer.serialize_bytes(unsafe { self.as_slice() })
|
||||
let lua = self.0.lua.lock();
|
||||
serializer.serialize_bytes(self.as_slice(&lua))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+269
-85
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -17,7 +16,7 @@ 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<'a> {
|
||||
pub trait AsChunk {
|
||||
/// Returns optional chunk name
|
||||
///
|
||||
/// See [`Chunk::set_name`] for possible name prefixes.
|
||||
@@ -39,65 +38,100 @@ pub trait AsChunk<'a> {
|
||||
}
|
||||
|
||||
/// Returns chunk data (can be text or binary)
|
||||
fn source(self) -> IoResult<Cow<'a, [u8]>>;
|
||||
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]>> {
|
||||
impl AsChunk for &str {
|
||||
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Ok(Cow::Borrowed(self.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsChunk<'a> for &'a [u8] {
|
||||
fn source(self) -> IoResult<Cow<'a, [u8]>> {
|
||||
impl AsChunk for StdString {
|
||||
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
|
||||
Ok(Cow::Owned(self.clone().into_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsChunk for &StdString {
|
||||
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,
|
||||
{
|
||||
Ok(Cow::Borrowed(self))
|
||||
}
|
||||
}
|
||||
|
||||
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]>> {
|
||||
Ok(Cow::Owned(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsChunk<'a> for &'a Vec<u8> {
|
||||
fn source(self) -> IoResult<Cow<'a, [u8]>> {
|
||||
Ok(Cow::Borrowed(self.as_ref()))
|
||||
impl AsChunk for &Vec<u8> {
|
||||
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Ok(Cow::Borrowed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsChunk<'static> for &Path {
|
||||
impl AsChunk for &Path {
|
||||
fn name(&self) -> Option<StdString> {
|
||||
Some(format!("@{}", self.display()))
|
||||
}
|
||||
|
||||
fn source(self) -> IoResult<Cow<'static, [u8]>> {
|
||||
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
|
||||
std::fs::read(self).map(Cow::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsChunk<'static> for PathBuf {
|
||||
impl AsChunk for PathBuf {
|
||||
fn name(&self) -> Option<StdString> {
|
||||
Some(format!("@{}", self.display()))
|
||||
}
|
||||
|
||||
fn source(self) -> IoResult<Cow<'static, [u8]>> {
|
||||
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
|
||||
std::fs::read(self).map(Cow::Owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
|
||||
fn name(&self) -> Option<StdString> {
|
||||
(**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> {
|
||||
@@ -117,6 +151,49 @@ 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")))]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CompileConstant {
|
||||
Nil,
|
||||
Boolean(bool),
|
||||
Number(crate::Number),
|
||||
Vector(crate::Vector),
|
||||
String(StdString),
|
||||
}
|
||||
|
||||
#[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<(StdString, StdString), CompileConstant>;
|
||||
|
||||
/// Luau compiler
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
@@ -126,11 +203,14 @@ pub struct Compiler {
|
||||
debug_level: u8,
|
||||
type_info_level: u8,
|
||||
coverage_level: u8,
|
||||
vector_lib: Option<String>,
|
||||
vector_ctor: Option<String>,
|
||||
vector_type: Option<String>,
|
||||
mutable_globals: Vec<String>,
|
||||
userdata_types: Vec<String>,
|
||||
vector_lib: Option<StdString>,
|
||||
vector_ctor: Option<StdString>,
|
||||
vector_type: Option<StdString>,
|
||||
mutable_globals: Vec<StdString>,
|
||||
userdata_types: Vec<StdString>,
|
||||
libraries_with_known_members: Vec<StdString>,
|
||||
library_constants: Option<LibraryMemberConstantMap>,
|
||||
disabled_builtins: Vec<StdString>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
@@ -155,6 +235,9 @@ 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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +270,7 @@ 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
|
||||
@@ -204,40 +288,103 @@ 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_lib(mut self, lib: impl Into<String>) -> Self {
|
||||
self.vector_lib = Some(lib.into());
|
||||
pub fn set_vector_ctor(mut self, ctor: impl Into<StdString>) -> 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));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets alternative vector type name for type tables, in addition to default type `vector`.
|
||||
#[doc(hidden)]
|
||||
#[must_use]
|
||||
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
|
||||
self.vector_ctor = Some(ctor.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use]
|
||||
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
|
||||
pub fn set_vector_type(mut self, r#type: impl Into<StdString>) -> Self {
|
||||
self.vector_type = Some(r#type.into());
|
||||
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<StdString>) -> 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(mut self, globals: Vec<String>) -> Self {
|
||||
self.mutable_globals = globals;
|
||||
pub fn set_mutable_globals<S: Into<StdString>>(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<StdString>) -> Self {
|
||||
self.userdata_types.push(r#type.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a list of userdata types that will be included in the type information.
|
||||
#[must_use]
|
||||
pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
|
||||
self.userdata_types = types;
|
||||
pub fn set_userdata_types<S: Into<StdString>>(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_with(HashMap::new)
|
||||
.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<StdString>) -> 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<StdString>>(
|
||||
mut self,
|
||||
builtins: impl IntoIterator<Item = S>,
|
||||
) -> Self {
|
||||
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -245,7 +392,9 @@ impl Compiler {
|
||||
///
|
||||
/// Returns [`Error::SyntaxError`] if the source code is invalid.
|
||||
pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
|
||||
use std::os::raw::c_int;
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::ptr;
|
||||
|
||||
let vector_lib = self.vector_lib.clone();
|
||||
@@ -277,6 +426,44 @@ 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();
|
||||
@@ -289,13 +476,21 @@ 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() {
|
||||
if !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..]).to_string();
|
||||
let message = StdString::from_utf8_lossy(&bytecode[2..]).into_owned();
|
||||
return Err(Error::SyntaxError {
|
||||
incomplete_input: message.ends_with("<eof>"),
|
||||
message,
|
||||
@@ -318,7 +513,7 @@ impl Chunk<'_> {
|
||||
/// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is
|
||||
/// more useful for identifying the file)
|
||||
/// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept)
|
||||
pub fn set_name(mut self, name: impl Into<String>) -> Self {
|
||||
pub fn set_name(mut self, name: impl Into<StdString>) -> Self {
|
||||
self.name = name.into();
|
||||
self
|
||||
}
|
||||
@@ -361,8 +556,6 @@ 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 {
|
||||
@@ -381,8 +574,6 @@ impl Chunk<'_> {
|
||||
///
|
||||
/// See [`exec`] for more details.
|
||||
///
|
||||
/// Requires `feature = "async"`
|
||||
///
|
||||
/// [`exec`]: Chunk::exec
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
@@ -413,8 +604,6 @@ impl Chunk<'_> {
|
||||
///
|
||||
/// See [`eval`] for more details.
|
||||
///
|
||||
/// Requires `feature = "async"`
|
||||
///
|
||||
/// [`eval`]: Chunk::eval
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
@@ -442,8 +631,6 @@ impl Chunk<'_> {
|
||||
///
|
||||
/// See [`call`] for more details.
|
||||
///
|
||||
/// Requires `feature = "async"`
|
||||
///
|
||||
/// [`call`]: Chunk::call
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
@@ -503,7 +690,7 @@ impl Chunk<'_> {
|
||||
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(cache) = lua.priv_app_data_ref::<ChunksCache>() {
|
||||
if let Some(data) = cache.0.get(source.as_ref()) {
|
||||
self.source = Ok(Cow::Owned(data.clone()));
|
||||
self.mode = Some(ChunkMode::Binary);
|
||||
@@ -520,12 +707,12 @@ impl Chunk<'_> {
|
||||
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());
|
||||
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.as_ref().to_vec());
|
||||
let _ = lua.try_set_app_data(cache);
|
||||
cache.0.insert(text_source, binary_source.to_vec());
|
||||
lua.set_priv_app_data(cache);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -558,24 +745,23 @@ impl Chunk<'_> {
|
||||
}
|
||||
|
||||
fn detect_mode(&self) -> ChunkMode {
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
ChunkMode::Text
|
||||
}
|
||||
|
||||
fn convert_name(name: String) -> Result<CString> {
|
||||
fn convert_name(name: StdString) -> Result<CString> {
|
||||
CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
|
||||
}
|
||||
|
||||
@@ -587,29 +773,27 @@ impl Chunk<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
struct WrappedChunk<'a, T: AsChunk<'a>> {
|
||||
struct WrappedChunk<T: AsChunk> {
|
||||
chunk: T,
|
||||
caller: &'static Location<'static>,
|
||||
_marker: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a> Chunk<'a> {
|
||||
impl Chunk<'_> {
|
||||
/// 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<'a> + 'a) -> impl IntoLua + 'a {
|
||||
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
|
||||
WrappedChunk {
|
||||
chunk,
|
||||
caller: Location::caller(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> {
|
||||
impl<T: AsChunk> IntoLua for WrappedChunk<T> {
|
||||
fn into_lua(self, lua: &Lua) -> Result<Value> {
|
||||
lua.load_with_location(self.chunk, self.caller)
|
||||
.into_function()
|
||||
|
||||
+6
-32
@@ -645,7 +645,7 @@ impl FromLua for BString {
|
||||
match value {
|
||||
Value::String(s) => Ok((*s.as_bytes()).into()),
|
||||
#[cfg(feature = "luau")]
|
||||
Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) },
|
||||
Value::Buffer(buf) => Ok(buf.to_vec().into()),
|
||||
_ => Ok((*lua
|
||||
.coerce_string(value)?
|
||||
.ok_or_else(|| Error::FromLuaConversionError {
|
||||
@@ -808,15 +808,9 @@ macro_rules! lua_convert_int {
|
||||
impl IntoLua for $x {
|
||||
#[inline]
|
||||
fn into_lua(self, _: &Lua) -> Result<Value> {
|
||||
cast(self)
|
||||
Ok(cast(self)
|
||||
.map(Value::Integer)
|
||||
.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()),
|
||||
})
|
||||
.unwrap_or_else(|| Value::Number(self as ffi::lua_Number)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -899,13 +893,7 @@ macro_rules! lua_convert_float {
|
||||
impl IntoLua for $x {
|
||||
#[inline]
|
||||
fn into_lua(self, _: &Lua) -> Result<Value> {
|
||||
cast(self)
|
||||
.ok_or_else(|| Error::ToLuaConversionError {
|
||||
from: stringify!($x).to_string(),
|
||||
to: "number",
|
||||
message: Some("out of range".to_string()),
|
||||
})
|
||||
.map(Value::Number)
|
||||
Ok(Value::Number(self as _))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -914,33 +902,19 @@ macro_rules! lua_convert_float {
|
||||
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(|| 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 {
|
||||
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()),
|
||||
});
|
||||
}
|
||||
return Ok(ffi::lua_tonumber(state, idx) as _);
|
||||
}
|
||||
// Fallback to default
|
||||
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
|
||||
|
||||
+93
-106
@@ -1,66 +1,33 @@
|
||||
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;
|
||||
use ffi::{lua_Debug, lua_State};
|
||||
|
||||
use crate::function::Function;
|
||||
use crate::state::RawLua;
|
||||
use crate::types::ReentrantMutexGuard;
|
||||
use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
|
||||
use crate::util::{assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str, StackGuard};
|
||||
|
||||
/// Contains information about currently executing Lua code.
|
||||
///
|
||||
/// 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].
|
||||
/// 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].
|
||||
///
|
||||
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
|
||||
/// [`Lua::set_hook`]: crate::Lua::set_hook
|
||||
pub struct Debug<'a> {
|
||||
lua: EitherLua<'a>,
|
||||
ar: ActivationRecord,
|
||||
#[cfg(feature = "luau")]
|
||||
state: *mut lua_State,
|
||||
lua: &'a RawLua,
|
||||
#[cfg_attr(not(feature = "luau"), allow(unused))]
|
||||
level: c_int,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
ar: *mut lua_Debug,
|
||||
}
|
||||
|
||||
impl<'a> Debug<'a> {
|
||||
// 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 {
|
||||
pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self {
|
||||
Debug {
|
||||
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,
|
||||
state: lua.state(),
|
||||
lua,
|
||||
ar,
|
||||
level,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +41,7 @@ impl<'a> Debug<'a> {
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn event(&self) -> DebugEvent {
|
||||
unsafe {
|
||||
match (*self.ar.get()).event {
|
||||
match (*self.ar).event {
|
||||
ffi::LUA_HOOKCALL => DebugEvent::Call,
|
||||
ffi::LUA_HOOKRET => DebugEvent::Ret,
|
||||
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
|
||||
@@ -85,24 +52,48 @@ impl<'a> Debug<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `n` what mask.
|
||||
pub fn names(&self) -> DebugNames {
|
||||
/// 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<'_> {
|
||||
unsafe {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
|
||||
"lua_getinfo failed with `n`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0,
|
||||
"lua_getinfo failed with `n`"
|
||||
);
|
||||
|
||||
DebugNames {
|
||||
name: ptr_to_lossy_str((*self.ar.get()).name),
|
||||
name: ptr_to_lossy_str((*self.ar).name),
|
||||
#[cfg(not(feature = "luau"))]
|
||||
name_what: match ptr_to_str((*self.ar.get()).namewhat) {
|
||||
name_what: match ptr_to_str((*self.ar).namewhat) {
|
||||
Some("") => None,
|
||||
val => val,
|
||||
},
|
||||
@@ -112,118 +103,110 @@ 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.lua.state(), cstr!("S"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
|
||||
"lua_getinfo failed with `S`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0,
|
||||
"lua_getinfo failed with `s`"
|
||||
);
|
||||
|
||||
DebugSource {
|
||||
source: ptr_to_lossy_str((*self.ar.get()).source),
|
||||
source: ptr_to_lossy_str((*self.ar).source),
|
||||
#[cfg(not(feature = "luau"))]
|
||||
short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
|
||||
short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()),
|
||||
#[cfg(feature = "luau")]
|
||||
short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
|
||||
line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
|
||||
short_src: ptr_to_lossy_str((*self.ar).short_src),
|
||||
line_defined: linenumber_to_usize((*self.ar).linedefined),
|
||||
#[cfg(not(feature = "luau"))]
|
||||
last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
|
||||
last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined),
|
||||
#[cfg(feature = "luau")]
|
||||
last_line_defined: None,
|
||||
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
|
||||
what: ptr_to_str((*self.ar).what).unwrap_or("main"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `l` what mask. Returns the current line.
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "Use `current_line` instead")]
|
||||
pub fn curr_line(&self) -> i32 {
|
||||
self.current_line().map(|n| n as i32).unwrap_or(-1)
|
||||
}
|
||||
|
||||
/// Corresponds to the `l` "what" mask. Returns the current line.
|
||||
pub fn current_line(&self) -> Option<usize> {
|
||||
unsafe {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
|
||||
"lua_getinfo failed with `l`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0,
|
||||
"lua_getinfo failed with `l`"
|
||||
);
|
||||
|
||||
(*self.ar.get()).currentline
|
||||
linenumber_to_usize((*self.ar).currentline)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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"))))]
|
||||
/// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
|
||||
/// false otherwise.
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52")))
|
||||
)]
|
||||
pub fn is_tail_call(&self) -> bool {
|
||||
unsafe {
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
|
||||
"lua_getinfo failed with `t`"
|
||||
);
|
||||
(*self.ar.get()).currentline != 0
|
||||
(*self.ar).istailcall != 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.lua.state(), cstr!("u"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
|
||||
"lua_getinfo failed with `u`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0,
|
||||
"lua_getinfo failed with `au`"
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
let stack = DebugStack {
|
||||
num_ups: (*self.ar.get()).nups as _,
|
||||
num_ups: (*self.ar).nups as _,
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
num_params: (*self.ar.get()).nparams as _,
|
||||
num_params: (*self.ar).nparams as _,
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
is_vararg: (*self.ar.get()).isvararg != 0,
|
||||
is_vararg: (*self.ar).isvararg != 0,
|
||||
};
|
||||
#[cfg(feature = "luau")]
|
||||
let stack = DebugStack {
|
||||
num_ups: (*self.ar.get()).nupvals,
|
||||
num_params: (*self.ar.get()).nparams,
|
||||
is_vararg: (*self.ar.get()).isvararg != 0,
|
||||
num_ups: (*self.ar).nupvals,
|
||||
num_params: (*self.ar).nparams,
|
||||
is_vararg: (*self.ar).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.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum DebugEvent {
|
||||
@@ -265,14 +248,18 @@ pub struct DebugStack {
|
||||
/// 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"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
|
||||
)]
|
||||
pub num_params: u8,
|
||||
/// Whether the function is a vararg function.
|
||||
///
|
||||
/// Requires `feature = "lua54/lua53/lua52/luau"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
|
||||
)]
|
||||
pub is_vararg: bool,
|
||||
}
|
||||
|
||||
@@ -378,7 +365,7 @@ impl HookTriggers {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
impl BitOr for HookTriggers {
|
||||
impl std::ops::BitOr for HookTriggers {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: Self) -> Self::Output {
|
||||
@@ -393,7 +380,7 @@ impl BitOr for HookTriggers {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
impl BitOrAssign for HookTriggers {
|
||||
impl std::ops::BitOrAssign for HookTriggers {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
*self = *self | rhs;
|
||||
}
|
||||
+8
-8
@@ -183,12 +183,12 @@ pub enum Error {
|
||||
/// and returned again.
|
||||
PreviouslyResumedPanic,
|
||||
/// Serialization error.
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
SerializeError(StdString),
|
||||
/// Deserialization error.
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
DeserializeError(StdString),
|
||||
/// A custom error.
|
||||
///
|
||||
@@ -309,11 +309,11 @@ impl fmt::Display for Error {
|
||||
Error::PreviouslyResumedPanic => {
|
||||
write!(fmt, "previously resumed panic returned again")
|
||||
}
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Error::SerializeError(err) => {
|
||||
write!(fmt, "serialize error: {err}")
|
||||
},
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Error::DeserializeError(err) => {
|
||||
write!(fmt, "deserialize error: {err}")
|
||||
},
|
||||
@@ -494,14 +494,14 @@ impl From<Utf8Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::ser::Error for Error {
|
||||
fn custom<T: fmt::Display>(msg: T) -> Self {
|
||||
Self::SerializeError(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::de::Error for Error {
|
||||
fn custom<T: fmt::Display>(msg: T) -> Self {
|
||||
Self::DeserializeError(msg.to_string())
|
||||
|
||||
+68
-32
@@ -14,9 +14,12 @@ 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.
|
||||
@@ -128,9 +131,8 @@ 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`].
|
||||
///
|
||||
/// Requires `feature = "async"`
|
||||
/// Internally it wraps the function to an [`AsyncThread`]. The returned type implements
|
||||
/// `Future<Output = Result<R>>` and can be awaited.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -155,19 +157,18 @@ impl Function {
|
||||
/// [`AsyncThread`]: crate::AsyncThread
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
let lua = self.0.lua.lock();
|
||||
let thread_res = unsafe {
|
||||
lua.create_recycled_thread(self).map(|th| {
|
||||
let mut th = th.into_async(args);
|
||||
AsyncCallFuture(unsafe {
|
||||
lua.create_recycled_thread(self).and_then(|th| {
|
||||
let mut th = th.into_async(args)?;
|
||||
th.set_recyclable(true);
|
||||
th
|
||||
Ok(th)
|
||||
})
|
||||
};
|
||||
async move { thread_res?.await }
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
|
||||
@@ -252,7 +253,7 @@ impl Function {
|
||||
"#,
|
||||
)
|
||||
.try_cache()
|
||||
.set_name("__mlua_bind")
|
||||
.set_name("=__mlua_bind")
|
||||
.call((self, args_wrapper))
|
||||
}
|
||||
|
||||
@@ -280,7 +281,7 @@ impl Function {
|
||||
// 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 _).to_bytes() == b"_ENV" => break,
|
||||
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => break,
|
||||
_ => ffi::lua_pop(state, 1),
|
||||
}
|
||||
}
|
||||
@@ -319,7 +320,7 @@ impl Function {
|
||||
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 _).to_bytes() == b"_ENV" => {
|
||||
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => {
|
||||
ffi::lua_pop(state, 1);
|
||||
// Create an anonymous function with the new environment
|
||||
let f_with_env = lua
|
||||
@@ -430,12 +431,10 @@ 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, mut func: F)
|
||||
pub fn coverage<F>(&self, func: F)
|
||||
where
|
||||
F: FnMut(CoverageInfo),
|
||||
{
|
||||
@@ -455,13 +454,16 @@ impl Function {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
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 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 lua = self.0.lua.lock();
|
||||
@@ -471,7 +473,8 @@ impl Function {
|
||||
assert_stack(state, 1);
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
let func_ptr = &mut func as *mut F as *mut c_void;
|
||||
let func = RefCell::new(func);
|
||||
let func_ptr = &func as *const RefCell<F> as *mut c_void;
|
||||
ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
|
||||
}
|
||||
}
|
||||
@@ -491,20 +494,26 @@ 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) -> Self {
|
||||
pub fn deep_clone(&self) -> Result<Self> {
|
||||
let lua = self.0.lua.lock();
|
||||
let ref_thread = lua.ref_thread();
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 {
|
||||
return self.clone();
|
||||
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());
|
||||
}
|
||||
|
||||
ffi::lua_clonefunction(ref_thread, self.0.index);
|
||||
Function(lua.pop_ref_thread())
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,6 +653,30 @@ impl LuaType for Function {
|
||||
const TYPE_ID: c_int = ffi::LUA_TFUNCTION;
|
||||
}
|
||||
|
||||
#[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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod assertions {
|
||||
use super::*;
|
||||
@@ -652,4 +685,7 @@ 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);
|
||||
}
|
||||
|
||||
+20
-10
@@ -35,7 +35,7 @@
|
||||
//! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing
|
||||
//! Lua values into Rust values.
|
||||
//!
|
||||
//! Requires `feature = "serialize"`.
|
||||
//! Requires `feature = "serde"`.
|
||||
//!
|
||||
//! # Async/await support
|
||||
//!
|
||||
@@ -67,6 +67,7 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
|
||||
#![allow(clippy::ptr_eq)]
|
||||
#![allow(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
@@ -74,10 +75,10 @@ mod macros;
|
||||
mod buffer;
|
||||
mod chunk;
|
||||
mod conversion;
|
||||
mod debug;
|
||||
mod error;
|
||||
mod function;
|
||||
mod hook;
|
||||
#[cfg(feature = "luau")]
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
mod luau;
|
||||
mod memory;
|
||||
mod multi;
|
||||
@@ -100,9 +101,9 @@ pub use bstr::BString;
|
||||
pub use ffi::{self, lua_CFunction, lua_State};
|
||||
|
||||
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
|
||||
pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
|
||||
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;
|
||||
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
|
||||
@@ -123,22 +124,31 @@ pub use crate::userdata::{
|
||||
pub use crate::value::{Nil, Value};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub use crate::hook::HookTriggers;
|
||||
pub use crate::debug::HookTriggers;
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector};
|
||||
pub use crate::{
|
||||
buffer::Buffer,
|
||||
chunk::{CompileConstant, Compiler},
|
||||
function::CoverageInfo,
|
||||
luau::{NavigateError, Require, TextRequirer},
|
||||
vector::Vector,
|
||||
};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
#[doc(inline)]
|
||||
pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt};
|
||||
pub use crate::{
|
||||
serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt},
|
||||
value::SerializableValue,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
pub mod serde;
|
||||
|
||||
#[cfg(feature = "mlua_derive")]
|
||||
|
||||
+43
-13
@@ -1,16 +1,32 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr;
|
||||
|
||||
use crate::chunk::ChunkMode;
|
||||
use crate::error::Result;
|
||||
use crate::state::Lua;
|
||||
use crate::function::Function;
|
||||
use crate::state::{callback_error_ext, ExtraData, Lua};
|
||||
use crate::traits::{FromLuaMulti, IntoLua};
|
||||
use crate::types::MaybeSend;
|
||||
|
||||
pub use require::{NavigateError, Require, TextRequirer};
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -18,11 +34,10 @@ impl Lua {
|
||||
globals.raw_set("_VERSION", format!("Luau {version}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// Enable default `require` implementation
|
||||
let require = self.create_require_function(require::TextRequirer::new())?;
|
||||
self.globals().raw_set("require", require)?;
|
||||
|
||||
pub(crate) fn disable_c_modules(&self) -> Result<()> {
|
||||
package::disable_dylibs(self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -31,16 +46,17 @@ 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") => {
|
||||
Ok("collect") if !is_sandboxed => {
|
||||
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
|
||||
0
|
||||
}
|
||||
Ok("stop") => {
|
||||
Ok("stop") if !is_sandboxed => {
|
||||
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
|
||||
0
|
||||
}
|
||||
Ok("restart") => {
|
||||
Ok("restart") if !is_sandboxed => {
|
||||
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
|
||||
0
|
||||
}
|
||||
@@ -50,12 +66,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") => {
|
||||
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
|
||||
Ok("step") if !is_sandboxed => {
|
||||
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _);
|
||||
ffi::lua_pushboolean(state, res);
|
||||
1
|
||||
}
|
||||
Ok("isrunning") => {
|
||||
Ok("isrunning") if !is_sandboxed => {
|
||||
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
|
||||
ffi::lua_pushboolean(state, res);
|
||||
1
|
||||
@@ -64,6 +80,20 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use package::register_package_module;
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
mod package;
|
||||
mod require;
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::CStr;
|
||||
use std::io::Result as IoResult;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::result::Result as StdResult;
|
||||
use std::{env, fmt, fs, mem, ptr};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::function::Function;
|
||||
use crate::state::{callback_error_ext, Lua};
|
||||
use crate::table::Table;
|
||||
use crate::types::MaybeSend;
|
||||
|
||||
/// An error that can occur during navigation in the Luau `require-by-string` system.
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NavigateError {
|
||||
Ambiguous,
|
||||
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;
|
||||
|
||||
/// A trait for handling modules loading and navigation in the Luau `require-by-string` system.
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub trait Require {
|
||||
/// Returns `true` if "require" is permitted for the given chunk name.
|
||||
fn is_require_allowed(&self, chunk_name: &str) -> bool;
|
||||
|
||||
/// 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>;
|
||||
|
||||
// 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>")
|
||||
}
|
||||
}
|
||||
|
||||
/// The standard implementation of Luau `require-by-string` navigation.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct TextRequirer {
|
||||
/// An absolute path to the current Luau module (not mapped to a physical file)
|
||||
abs_path: PathBuf,
|
||||
/// A relative path to the current Luau module (not mapped to a physical file)
|
||||
rel_path: PathBuf,
|
||||
/// A physical path to the current Luau module, which is a file or a directory with an
|
||||
/// `init.lua(u)` file
|
||||
resolved_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl TextRequirer {
|
||||
/// The prefix used for chunk names in the require system.
|
||||
/// Only chunk names starting with this prefix are allowed to be used in `require`.
|
||||
const CHUNK_PREFIX: &str = "@";
|
||||
|
||||
/// The file extensions that are considered valid for Luau modules.
|
||||
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
|
||||
|
||||
/// Creates a new `TextRequirer` instance.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn normalize_chunk_name(chunk_name: &str) -> &str {
|
||||
if let Some((path, line)) = chunk_name.rsplit_once(':') {
|
||||
if line.parse::<u32>().is_ok() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
chunk_name
|
||||
}
|
||||
|
||||
// Normalizes the path by removing unnecessary components
|
||||
fn normalize_path(path: &Path) -> PathBuf {
|
||||
let mut components = VecDeque::new();
|
||||
|
||||
for comp in path.components() {
|
||||
match comp {
|
||||
Component::Prefix(..) | Component::RootDir => {
|
||||
components.push_back(comp);
|
||||
}
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
if matches!(components.back(), None | Some(Component::ParentDir)) {
|
||||
components.push_back(Component::ParentDir);
|
||||
} else if matches!(components.back(), Some(Component::Normal(..))) {
|
||||
components.pop_back();
|
||||
}
|
||||
}
|
||||
Component::Normal(..) => components.push_back(comp),
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(components.front(), None | Some(Component::Normal(..))) {
|
||||
components.push_front(Component::CurDir);
|
||||
}
|
||||
|
||||
// Join the components back together
|
||||
components.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Resolve a Luau module path to a physical file or directory.
|
||||
///
|
||||
/// Empty directories without init files are considered valid as "intermediate" directories.
|
||||
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
|
||||
let mut found_path = None;
|
||||
|
||||
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
|
||||
let current_ext = (path.extension().and_then(|s| s.to_str()))
|
||||
.map(|s| format!("{s}."))
|
||||
.unwrap_or_default();
|
||||
for ext in Self::FILE_EXTENSIONS {
|
||||
let candidate = path.with_extension(format!("{current_ext}{ext}"));
|
||||
if candidate.is_file() && found_path.replace(candidate).is_some() {
|
||||
return Err(NavigateError::Ambiguous);
|
||||
}
|
||||
}
|
||||
}
|
||||
if path.is_dir() {
|
||||
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
|
||||
let candidate = path.join(component);
|
||||
if candidate.is_file() && found_path.replace(candidate).is_some() {
|
||||
return Err(NavigateError::Ambiguous);
|
||||
}
|
||||
}
|
||||
|
||||
if found_path.is_none() {
|
||||
// Directories without init files are considered valid "intermediate" path
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Require for TextRequirer {
|
||||
fn is_require_allowed(&self, chunk_name: &str) -> bool {
|
||||
chunk_name.starts_with(Self::CHUNK_PREFIX)
|
||||
}
|
||||
|
||||
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
|
||||
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
|
||||
return Err(NavigateError::NotFound);
|
||||
}
|
||||
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
|
||||
let chunk_path = Self::normalize_path(chunk_name.as_ref());
|
||||
|
||||
if chunk_path.extension() == Some("rs".as_ref()) {
|
||||
// Special case for Rust source files, reset to the current directory
|
||||
let chunk_filename = chunk_path.file_name().unwrap();
|
||||
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
|
||||
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
|
||||
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
|
||||
self.resolved_path = None;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if chunk_path.is_absolute() {
|
||||
let resolved_path = Self::resolve_module(&chunk_path)?;
|
||||
self.abs_path = chunk_path.clone();
|
||||
self.rel_path = chunk_path;
|
||||
self.resolved_path = resolved_path;
|
||||
} else {
|
||||
// Relative path
|
||||
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
|
||||
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
|
||||
let resolved_path = Self::resolve_module(&abs_path)?;
|
||||
self.abs_path = abs_path;
|
||||
self.rel_path = chunk_path;
|
||||
self.resolved_path = resolved_path;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
|
||||
let path = Self::normalize_path(path.as_ref());
|
||||
let resolved_path = Self::resolve_module(&path)?;
|
||||
|
||||
self.abs_path = path.clone();
|
||||
self.rel_path = path;
|
||||
self.resolved_path = resolved_path;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
|
||||
let mut abs_path = self.abs_path.clone();
|
||||
if !abs_path.pop() {
|
||||
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
|
||||
// cannot go beyond the root directory.
|
||||
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
|
||||
return Err(NavigateError::NotFound);
|
||||
}
|
||||
let mut rel_parent = self.rel_path.clone();
|
||||
rel_parent.pop();
|
||||
let resolved_path = Self::resolve_module(&abs_path)?;
|
||||
|
||||
self.abs_path = abs_path;
|
||||
self.rel_path = Self::normalize_path(&rel_parent);
|
||||
self.resolved_path = resolved_path;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
|
||||
let abs_path = self.abs_path.join(name);
|
||||
let rel_path = self.rel_path.join(name);
|
||||
let resolved_path = Self::resolve_module(&abs_path)?;
|
||||
|
||||
self.abs_path = abs_path;
|
||||
self.rel_path = rel_path;
|
||||
self.resolved_path = resolved_path;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_module(&self) -> bool {
|
||||
(self.resolved_path.as_deref())
|
||||
.map(Path::is_file)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn cache_key(&self) -> String {
|
||||
self.resolved_path.as_deref().unwrap().display().to_string()
|
||||
}
|
||||
|
||||
fn has_config(&self) -> bool {
|
||||
self.abs_path.is_dir() && self.abs_path.join(".luaurc").is_file()
|
||||
}
|
||||
|
||||
fn config(&self) -> IoResult<Vec<u8>> {
|
||||
fs::read(self.abs_path.join(".luaurc"))
|
||||
}
|
||||
|
||||
fn loader(&self, lua: &Lua) -> Result<Function> {
|
||||
let name = format!("@{}", self.rel_path.display());
|
||||
lua.load(self.resolved_path.as_deref().unwrap())
|
||||
.set_name(name)
|
||||
.into_function()
|
||||
}
|
||||
}
|
||||
|
||||
struct Context(Box<dyn Require>);
|
||||
|
||||
impl Deref for Context {
|
||||
type Target = dyn Require;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Context {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut *self.0
|
||||
}
|
||||
}
|
||||
|
||||
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_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 is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
|
||||
let this = try_borrow!(state, ctx);
|
||||
this.has_config()
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn get_config(
|
||||
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 config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| Ok(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_parent = to_parent;
|
||||
(*config).to_child = to_child;
|
||||
(*config).is_module_present = is_module_present;
|
||||
(*config).get_chunkname = get_chunkname;
|
||||
(*config).get_loadname = get_loadname;
|
||||
(*config).get_cache_key = get_cache_key;
|
||||
(*config).is_config_present = is_config_present;
|
||||
(*config).get_alias = None;
|
||||
(*config).get_config = Some(get_config);
|
||||
(*config).load = load;
|
||||
}
|
||||
|
||||
/// 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(Box::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()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use super::TextRequirer;
|
||||
|
||||
#[test]
|
||||
fn test_path_normalize() {
|
||||
for (input, expected) in [
|
||||
// Basic formatting checks
|
||||
("", "./"),
|
||||
(".", "./"),
|
||||
("a/relative/path", "./a/relative/path"),
|
||||
// Paths containing extraneous '.' and '/' symbols
|
||||
("./remove/extraneous/symbols/", "./remove/extraneous/symbols"),
|
||||
("./remove/extraneous//symbols", "./remove/extraneous/symbols"),
|
||||
("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"),
|
||||
("./remove/extraneous/./symbols", "./remove/extraneous/symbols"),
|
||||
("../remove/extraneous/symbols/", "../remove/extraneous/symbols"),
|
||||
("../remove/extraneous//symbols", "../remove/extraneous/symbols"),
|
||||
("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"),
|
||||
("../remove/extraneous/./symbols", "../remove/extraneous/symbols"),
|
||||
("/remove/extraneous/symbols/", "/remove/extraneous/symbols"),
|
||||
("/remove/extraneous//symbols", "/remove/extraneous/symbols"),
|
||||
("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"),
|
||||
("/remove/extraneous/./symbols", "/remove/extraneous/symbols"),
|
||||
// Paths containing '..'
|
||||
("./remove/me/..", "./remove"),
|
||||
("./remove/me/../", "./remove"),
|
||||
("../remove/me/..", "../remove"),
|
||||
("../remove/me/../", "../remove"),
|
||||
("/remove/me/..", "/remove"),
|
||||
("/remove/me/../", "/remove"),
|
||||
("./..", "../"),
|
||||
("./../", "../"),
|
||||
("../..", "../../"),
|
||||
("../../", "../../"),
|
||||
// '..' disappears if path is absolute and component is non-erasable
|
||||
("/../", "/"),
|
||||
] {
|
||||
let path = TextRequirer::normalize_path(input.as_ref());
|
||||
assert_eq!(
|
||||
&path,
|
||||
expected.as_ref() as &Path,
|
||||
"wrong normalization for {input}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -97,7 +97,7 @@ impl MemoryState {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn allocator(
|
||||
unsafe extern "C" fn allocator(
|
||||
extra: *mut c_void,
|
||||
ptr: *mut c_void,
|
||||
osize: usize,
|
||||
|
||||
@@ -297,6 +297,15 @@ 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> {
|
||||
|
||||
+18
-12
@@ -2,15 +2,16 @@
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use crate::{
|
||||
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,
|
||||
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
|
||||
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,
|
||||
@@ -22,14 +23,19 @@ pub use crate::HookTriggers as LuaHookTriggers;
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector};
|
||||
pub use crate::{
|
||||
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
|
||||
NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer,
|
||||
Vector as LuaVector,
|
||||
};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::{
|
||||
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions,
|
||||
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializableValue as LuaSerializableValue,
|
||||
SerializeOptions as LuaSerializeOptions,
|
||||
};
|
||||
|
||||
+4
-10
@@ -8,9 +8,7 @@ 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, assert_stack, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard,
|
||||
};
|
||||
use crate::util::{self, 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`.
|
||||
@@ -284,22 +282,18 @@ 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.push_userdata_ref(&vref) {
|
||||
match rawlua.get_userdata_ref_type_id(&vref) {
|
||||
Ok(Some(_)) => {}
|
||||
Ok(None) => {
|
||||
// Deregister metatable
|
||||
let mt_ptr = get_metatable_ptr(state, -1);
|
||||
let mt_ptr = get_metatable_ptr(rawlua.ref_thread(), vref.index);
|
||||
rawlua.deregister_userdata_metatable(mt_ptr);
|
||||
}
|
||||
Err(_) => return vec![],
|
||||
}
|
||||
|
||||
let data = take_userdata::<UserDataStorage<T>>(state);
|
||||
let data = take_userdata::<UserDataStorage<T>>(rawlua.ref_thread(), vref.index);
|
||||
vec![Box::new(move || drop(data))]
|
||||
});
|
||||
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
|
||||
|
||||
+45
-10
@@ -15,11 +15,12 @@ use crate::userdata::AnyUserData;
|
||||
use crate::value::Value;
|
||||
|
||||
/// A struct for deserializing Lua values into Rust values.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Deserializer {
|
||||
value: Value,
|
||||
options: Options,
|
||||
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
|
||||
len: Option<usize>, // A length hint for sequences
|
||||
}
|
||||
|
||||
/// A struct with options to change default deserializer behavior.
|
||||
@@ -54,6 +55,19 @@ pub struct Options {
|
||||
///
|
||||
/// Default: **false**
|
||||
pub encode_empty_tables_as_array: bool,
|
||||
|
||||
/// If true, enable detection of mixed tables.
|
||||
///
|
||||
/// A mixed table is a table that has both array-like and map-like entries or several borders.
|
||||
/// See [`The Length Operator`] documentation for details about borders.
|
||||
///
|
||||
/// When this option is disabled, a table with a non-zero length (with one or more borders) will
|
||||
/// be always encoded as an array.
|
||||
///
|
||||
/// Default: **false**
|
||||
///
|
||||
/// [`The Length Operator`]: https://www.lua.org/manual/5.4/manual.html#3.4.7
|
||||
pub detect_mixed_tables: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
@@ -70,6 +84,7 @@ impl Options {
|
||||
deny_recursive_tables: true,
|
||||
sort_keys: false,
|
||||
encode_empty_tables_as_array: false,
|
||||
detect_mixed_tables: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +123,15 @@ impl Options {
|
||||
self.encode_empty_tables_as_array = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets [`detect_mixed_tables`] option.
|
||||
///
|
||||
/// [`detect_mixed_tables`]: #structfield.detect_mixed_tables
|
||||
#[must_use]
|
||||
pub const fn detect_mixed_tables(mut self, enable: bool) -> Self {
|
||||
self.detect_mixed_tables = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializer {
|
||||
@@ -121,7 +145,7 @@ impl Deserializer {
|
||||
Deserializer {
|
||||
value,
|
||||
options,
|
||||
visited: Rc::new(RefCell::new(FxHashSet::default())),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +154,14 @@ impl Deserializer {
|
||||
value,
|
||||
options,
|
||||
visited,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn with_len(mut self, len: usize) -> Self {
|
||||
self.len = Some(len);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserializer<'de> for Deserializer {
|
||||
@@ -155,17 +185,22 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
|
||||
Ok(s) => visitor.visit_str(&s),
|
||||
Err(_) => visitor.visit_bytes(&s.as_bytes()),
|
||||
},
|
||||
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
|
||||
Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => {
|
||||
self.deserialize_seq(visitor)
|
||||
Value::Table(ref t) => {
|
||||
if let Some(len) = t.encode_as_array(self.options) {
|
||||
self.with_len(len).deserialize_seq(visitor)
|
||||
} else {
|
||||
self.deserialize_map(visitor)
|
||||
}
|
||||
}
|
||||
Value::Table(_) => self.deserialize_map(visitor),
|
||||
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
|
||||
Value::UserData(ud) if ud.is_serializable() => {
|
||||
serde_userdata(ud, |value| value.deserialize_any(visitor))
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }),
|
||||
Value::Buffer(buf) => {
|
||||
let lua = buf.0.lua.lock();
|
||||
visitor.visit_bytes(buf.as_slice(&lua))
|
||||
}
|
||||
Value::Function(_)
|
||||
| Value::Thread(_)
|
||||
| Value::UserData(_)
|
||||
@@ -267,14 +302,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
|
||||
Value::Table(t) => {
|
||||
let _guard = RecursionGuard::new(&t, &self.visited);
|
||||
|
||||
let len = t.raw_len();
|
||||
let len = self.len.unwrap_or_else(|| t.raw_len());
|
||||
let mut deserializer = SeqDeserializer {
|
||||
seq: t.sequence_values(),
|
||||
seq: t.sequence_values().with_len(len),
|
||||
options: self.options,
|
||||
visited: self.visited,
|
||||
};
|
||||
let seq = visitor.visit_seq(&mut deserializer)?;
|
||||
if deserializer.seq.count() == 0 {
|
||||
if deserializer.seq.next().is_none() {
|
||||
Ok(seq)
|
||||
} else {
|
||||
Err(de::Error::invalid_length(len, &"fewer elements in the table"))
|
||||
|
||||
+1
-13
@@ -13,12 +13,10 @@ use crate::util::check_stack;
|
||||
use crate::value::Value;
|
||||
|
||||
/// Trait for serializing/deserializing Lua values using Serde.
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
pub trait LuaSerdeExt: Sealed {
|
||||
/// A special value (lightuserdata) to encode/decode optional (none) values.
|
||||
///
|
||||
/// Requires `feature = "serialize"`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@@ -42,8 +40,6 @@ pub trait LuaSerdeExt: Sealed {
|
||||
/// 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
|
||||
///
|
||||
/// ```
|
||||
@@ -71,8 +67,6 @@ pub trait LuaSerdeExt: Sealed {
|
||||
|
||||
/// Converts `T` into a [`Value`] instance.
|
||||
///
|
||||
/// Requires `feature = "serialize"`
|
||||
///
|
||||
/// [`Value`]: crate::Value
|
||||
///
|
||||
/// # Example
|
||||
@@ -104,8 +98,6 @@ pub trait LuaSerdeExt: Sealed {
|
||||
|
||||
/// Converts `T` into a [`Value`] instance with options.
|
||||
///
|
||||
/// Requires `feature = "serialize"`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@@ -129,8 +121,6 @@ pub trait LuaSerdeExt: Sealed {
|
||||
|
||||
/// Deserializes a [`Value`] into any serde deserializable object.
|
||||
///
|
||||
/// Requires `feature = "serialize"`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@@ -158,8 +148,6 @@ pub trait LuaSerdeExt: Sealed {
|
||||
|
||||
/// Deserializes a [`Value`] into any serde deserializable object with options.
|
||||
///
|
||||
/// Requires `feature = "serialize"`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
|
||||
+3
-3
@@ -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,8 +529,8 @@ impl ser::SerializeStruct for SerializeStruct<'_> {
|
||||
fn end(self) -> Result<Value> {
|
||||
match self.inner {
|
||||
Some(table @ Value::Table(_)) => Ok(table),
|
||||
Some(value) if self.options.detect_serde_json_arbitrary_precision => {
|
||||
let number_s = value.as_str().expect("not an arbitrary precision number");
|
||||
Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => {
|
||||
let number_s = value.to_string()?;
|
||||
if number_s.contains(['.', 'e', 'E']) {
|
||||
if let Ok(number) = number_s.parse().map(Value::Number) {
|
||||
return Ok(number);
|
||||
|
||||
+450
-189
File diff suppressed because it is too large
Load Diff
+50
-11
@@ -28,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 = 2;
|
||||
const REF_STACK_RESERVE: c_int = 3;
|
||||
|
||||
/// Data associated with the Lua state.
|
||||
pub(crate) struct ExtraData {
|
||||
@@ -44,8 +44,9 @@ 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>>>>,
|
||||
|
||||
// Container to store arbitrary data (extensions)
|
||||
// Containers to store arbitrary data (extensions)
|
||||
pub(super) app_data: AppData,
|
||||
pub(super) app_data_priv: AppData,
|
||||
|
||||
pub(super) safe: bool,
|
||||
pub(super) libs: StdLib,
|
||||
@@ -63,7 +64,7 @@ pub(crate) struct ExtraData {
|
||||
pub(super) wrapped_failure_top: usize,
|
||||
// Pool of `Thread`s (coroutines) for async execution
|
||||
#[cfg(feature = "async")]
|
||||
pub(super) thread_pool: Vec<c_int>,
|
||||
pub(super) thread_pool: Vec<crate::types::ValueRefIndex>,
|
||||
|
||||
// Address of `WrappedFailure` metatable
|
||||
pub(super) wrapped_failure_mt_ptr: *const c_void,
|
||||
@@ -75,14 +76,20 @@ pub(crate) struct ExtraData {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub(super) hook_callback: Option<crate::types::HookCallback>,
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub(super) hook_thread: *mut ffi::lua_State,
|
||||
pub(super) hook_triggers: crate::debug::HookTriggers,
|
||||
#[cfg(feature = "lua54")]
|
||||
pub(super) warn_callback: Option<crate::types::WarnCallback>,
|
||||
#[cfg(feature = "luau")]
|
||||
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
|
||||
#[cfg(feature = "luau")]
|
||||
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>,
|
||||
#[cfg(feature = "luau")]
|
||||
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
pub(super) sandboxed: bool,
|
||||
pub(crate) running_gc: bool,
|
||||
#[cfg(feature = "luau")]
|
||||
pub(crate) sandboxed: bool,
|
||||
#[cfg(feature = "luau")]
|
||||
pub(super) compiler: Option<Compiler>,
|
||||
#[cfg(feature = "luau-jit")]
|
||||
@@ -153,6 +160,7 @@ 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,
|
||||
@@ -171,17 +179,23 @@ impl ExtraData {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
hook_callback: None,
|
||||
#[cfg(not(feature = "luau"))]
|
||||
hook_thread: ptr::null_mut(),
|
||||
hook_triggers: Default::default(),
|
||||
#[cfg(feature = "lua54")]
|
||||
warn_callback: None,
|
||||
#[cfg(feature = "luau")]
|
||||
interrupt_callback: None,
|
||||
#[cfg(feature = "luau")]
|
||||
thread_creation_callback: None,
|
||||
#[cfg(feature = "luau")]
|
||||
thread_collection_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,
|
||||
}));
|
||||
|
||||
// Store it in the registry
|
||||
@@ -195,13 +209,10 @@ 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(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
|
||||
pub(crate) 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
|
||||
@@ -240,7 +251,7 @@ impl ExtraData {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) unsafe fn raw_lua(&self) -> &RawLua {
|
||||
pub(crate) unsafe fn raw_lua(&self) -> &RawLua {
|
||||
&*self.lua.assume_init_ref().raw.data_ptr()
|
||||
}
|
||||
|
||||
@@ -248,4 +259,32 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
+262
-147
@@ -1,18 +1,17 @@
|
||||
use std::any::TypeId;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ffi::CStr;
|
||||
use std::mem;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::panic::resume_unwind;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::result::Result as StdResult;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::chunk::ChunkMode;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::function::Function;
|
||||
use crate::memory::{MemoryState, ALLOCATOR};
|
||||
use crate::state::util::{callback_error_ext, ref_stack_pop};
|
||||
use crate::state::util::callback_error_ext;
|
||||
use crate::stdlib::StdLib;
|
||||
use crate::string::String;
|
||||
use crate::table::Table;
|
||||
@@ -29,8 +28,8 @@ use crate::userdata::{
|
||||
use crate::util::{
|
||||
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
|
||||
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error,
|
||||
push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name,
|
||||
StackGuard, WrappedFailure,
|
||||
push_internal_userdata, push_string, push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall,
|
||||
short_type_name, StackGuard, WrappedFailure,
|
||||
};
|
||||
use crate::value::{Nil, Value};
|
||||
|
||||
@@ -38,7 +37,10 @@ use super::extra::ExtraData;
|
||||
use super::{Lua, LuaOptions, WeakLua};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::hook::{Debug, HookTriggers};
|
||||
use crate::{
|
||||
debug::Debug,
|
||||
types::{HookCallback, HookKind, VmState},
|
||||
};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use {
|
||||
@@ -55,17 +57,25 @@ pub struct RawLua {
|
||||
pub(super) state: Cell<*mut ffi::lua_State>,
|
||||
pub(super) main_state: Option<NonNull<ffi::lua_State>>,
|
||||
pub(super) extra: XRc<UnsafeCell<ExtraData>>,
|
||||
owned: bool,
|
||||
}
|
||||
|
||||
impl Drop for RawLua {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !(*self.extra.get()).owned {
|
||||
if !self.owned {
|
||||
return;
|
||||
}
|
||||
|
||||
let mem_state = MemoryState::get(self.main_state());
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
// Reset any callbacks
|
||||
(*ffi::lua_callbacks(self.main_state())).interrupt = None;
|
||||
(*ffi::lua_callbacks(self.main_state())).userthread = None;
|
||||
}
|
||||
|
||||
ffi::lua_close(self.main_state());
|
||||
|
||||
// Deallocate `MemoryState`
|
||||
@@ -110,7 +120,7 @@ impl RawLua {
|
||||
unsafe { (*self.extra.get()).ref_thread }
|
||||
}
|
||||
|
||||
pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> XRc<ReentrantMutex<Self>> {
|
||||
pub(super) unsafe fn new(libs: StdLib, options: &LuaOptions) -> XRc<ReentrantMutex<Self>> {
|
||||
let mem_state: *mut MemoryState = Box::into_raw(Box::default());
|
||||
let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void);
|
||||
// If state is null then switch to Lua internal allocator
|
||||
@@ -187,6 +197,8 @@ impl RawLua {
|
||||
init_internal_metatable::<XRc<UnsafeCell<ExtraData>>>(state, None)?;
|
||||
init_internal_metatable::<Callback>(state, None)?;
|
||||
init_internal_metatable::<CallbackUpvalue>(state, None)?;
|
||||
#[cfg(not(feature = "luau"))]
|
||||
init_internal_metatable::<HookCallback>(state, None)?;
|
||||
#[cfg(feature = "async")]
|
||||
{
|
||||
init_internal_metatable::<AsyncCallback>(state, None)?;
|
||||
@@ -196,7 +208,7 @@ impl RawLua {
|
||||
}
|
||||
|
||||
// Init serde metatables
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
crate::serde::init_metatables(state)?;
|
||||
|
||||
Ok::<_, Error>(())
|
||||
@@ -228,8 +240,17 @@ impl RawLua {
|
||||
// Make sure that we don't store current state as main state (if it's not available)
|
||||
main_state: get_main_state(state).and_then(NonNull::new),
|
||||
extra: XRc::clone(&extra),
|
||||
owned,
|
||||
}));
|
||||
(*extra.get()).set_lua(&rawlua);
|
||||
if owned {
|
||||
// If Lua state is managed by us, then make internal `RawLua` reference "weak"
|
||||
XRc::decrement_strong_count(XRc::as_ptr(&rawlua));
|
||||
} else {
|
||||
// If Lua state is not managed by us, then keep internal `RawLua` reference "strong"
|
||||
// but `Extra` reference weak (it will be collected from registry at lua_close time)
|
||||
XRc::decrement_strong_count(XRc::as_ptr(&extra));
|
||||
}
|
||||
|
||||
rawlua
|
||||
}
|
||||
@@ -271,36 +292,41 @@ impl RawLua {
|
||||
let res = load_std_libs(self.main_state(), libs);
|
||||
|
||||
// If `package` library loaded into a safe lua state then disable C modules
|
||||
let curr_libs = (*self.extra.get()).libs;
|
||||
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
|
||||
mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules");
|
||||
#[cfg(not(feature = "luau"))]
|
||||
if is_safe {
|
||||
let curr_libs = (*self.extra.get()).libs;
|
||||
if (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
|
||||
mlua_expect!(self.lua().disable_c_modules(), "Error disabling C modules");
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
let _ = is_safe;
|
||||
unsafe { (*self.extra.get()).libs |= libs };
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// See [`Lua::try_set_app_data`]
|
||||
/// Private version of [`Lua::try_set_app_data`]
|
||||
#[inline]
|
||||
pub(crate) fn try_set_app_data<T: MaybeSend + 'static>(&self, data: T) -> StdResult<Option<T>, T> {
|
||||
pub(crate) fn set_priv_app_data<T: MaybeSend + 'static>(&self, data: T) -> Option<T> {
|
||||
let extra = unsafe { &*self.extra.get() };
|
||||
extra.app_data.try_insert(data)
|
||||
extra.app_data_priv.insert(data)
|
||||
}
|
||||
|
||||
/// See [`Lua::app_data_ref`]
|
||||
/// Private version of [`Lua::app_data_ref`]
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
pub(crate) fn app_data_ref_unguarded<T: 'static>(&self) -> Option<AppDataRef<T>> {
|
||||
pub(crate) fn priv_app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
|
||||
let extra = unsafe { &*self.extra.get() };
|
||||
extra.app_data.borrow(None)
|
||||
extra.app_data_priv.borrow(None)
|
||||
}
|
||||
|
||||
/// See [`Lua::app_data_mut`]
|
||||
/// Private version of [`Lua::app_data_mut`]
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
pub(crate) fn app_data_mut_unguarded<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
|
||||
pub(crate) fn priv_app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
|
||||
let extra = unsafe { &*self.extra.get() };
|
||||
extra.app_data.borrow_mut(None)
|
||||
extra.app_data_priv.borrow_mut(None)
|
||||
}
|
||||
|
||||
/// See [`Lua::create_registry_value`]
|
||||
@@ -374,48 +400,29 @@ impl RawLua {
|
||||
status
|
||||
}
|
||||
|
||||
/// Sets a 'hook' function for a thread (coroutine).
|
||||
/// Sets a hook for a thread (coroutine).
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub(crate) unsafe fn set_thread_hook<F>(
|
||||
pub(crate) unsafe fn set_thread_hook(
|
||||
&self,
|
||||
state: *mut ffi::lua_State,
|
||||
triggers: HookTriggers,
|
||||
callback: F,
|
||||
) where
|
||||
F: Fn(&Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
|
||||
{
|
||||
use crate::types::VmState;
|
||||
use std::rc::Rc;
|
||||
thread_state: *mut ffi::lua_State,
|
||||
hook: HookKind,
|
||||
) -> Result<()> {
|
||||
// Key to store hooks in the registry
|
||||
const HOOKS_KEY: *const c_char = cstr!("__mlua_hooks");
|
||||
|
||||
unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
|
||||
let extra = ExtraData::get(state);
|
||||
if (*extra).hook_thread != state {
|
||||
// Hook was destined for a different thread, ignore
|
||||
ffi::lua_sethook(state, None, 0, 0);
|
||||
return;
|
||||
}
|
||||
let result = callback_error_ext(state, extra, move |extra, _| {
|
||||
let hook_cb = (*extra).hook_callback.clone();
|
||||
let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc");
|
||||
if Rc::strong_count(&hook_cb) > 2 {
|
||||
return Ok(VmState::Continue); // Don't allow recursion
|
||||
}
|
||||
let rawlua = (*extra).raw_lua();
|
||||
let debug = Debug::new(rawlua, ar);
|
||||
hook_cb((*extra).lua(), debug)
|
||||
});
|
||||
match result {
|
||||
unsafe fn process_status(state: *mut ffi::lua_State, event: c_int, status: VmState) {
|
||||
match status {
|
||||
VmState::Continue => {}
|
||||
VmState::Yield => {
|
||||
// Only count and line events can yield
|
||||
if (*ar).event == ffi::LUA_HOOKCOUNT || (*ar).event == ffi::LUA_HOOKLINE {
|
||||
if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE {
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
if ffi::lua_isyieldable(state) != 0 {
|
||||
ffi::lua_yield(state, 0);
|
||||
}
|
||||
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
|
||||
{
|
||||
ffi::lua_pushliteral(state, "attempt to yield from a hook");
|
||||
ffi::lua_pushliteral(state, c"attempt to yield from a hook");
|
||||
ffi::lua_error(state);
|
||||
}
|
||||
}
|
||||
@@ -423,9 +430,83 @@ impl RawLua {
|
||||
}
|
||||
}
|
||||
|
||||
(*self.extra.get()).hook_callback = Some(Rc::new(callback));
|
||||
(*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set
|
||||
ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count());
|
||||
unsafe extern "C-unwind" fn global_hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
|
||||
let status = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
|
||||
match (*extra).hook_callback.clone() {
|
||||
Some(hook_callback) => {
|
||||
let rawlua = (*extra).raw_lua();
|
||||
let debug = Debug::new(rawlua, 0, ar);
|
||||
hook_callback((*extra).lua(), &debug)
|
||||
}
|
||||
None => {
|
||||
ffi::lua_sethook(state, None, 0, 0);
|
||||
Ok(VmState::Continue)
|
||||
}
|
||||
}
|
||||
});
|
||||
process_status(state, (*ar).event, status);
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
|
||||
let top = ffi::lua_gettop(state);
|
||||
let mut hook_callback_ptr = ptr::null();
|
||||
ffi::luaL_checkstack(state, 3, ptr::null());
|
||||
if ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == ffi::LUA_TTABLE {
|
||||
ffi::lua_pushthread(state);
|
||||
if ffi::lua_rawget(state, -2) == ffi::LUA_TUSERDATA {
|
||||
hook_callback_ptr = get_internal_userdata::<HookCallback>(state, -1, ptr::null());
|
||||
}
|
||||
}
|
||||
ffi::lua_settop(state, top);
|
||||
if hook_callback_ptr.is_null() {
|
||||
ffi::lua_sethook(state, None, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| {
|
||||
let rawlua = (*extra).raw_lua();
|
||||
let debug = Debug::new(rawlua, 0, ar);
|
||||
let hook_callback = (*hook_callback_ptr).clone();
|
||||
hook_callback((*extra).lua(), &debug)
|
||||
});
|
||||
process_status(state, (*ar).event, status)
|
||||
}
|
||||
|
||||
let (triggers, callback) = match hook {
|
||||
HookKind::Global if (*self.extra.get()).hook_callback.is_none() => {
|
||||
return Ok(());
|
||||
}
|
||||
HookKind::Global => {
|
||||
let triggers = (*self.extra.get()).hook_triggers;
|
||||
let (mask, count) = (triggers.mask(), triggers.count());
|
||||
ffi::lua_sethook(thread_state, Some(global_hook_proc), mask, count);
|
||||
return Ok(());
|
||||
}
|
||||
HookKind::Thread(triggers, callback) => (triggers, callback),
|
||||
};
|
||||
|
||||
// Hooks for threads stored in the registry (in a weak table)
|
||||
let state = self.state();
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
protect_lua!(state, 0, 0, |state| {
|
||||
if ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == 0 {
|
||||
// Table just created, initialize it
|
||||
ffi::lua_pushliteral(state, c"k");
|
||||
ffi::lua_setfield(state, -2, cstr!("__mode")); // hooktable.__mode = "k"
|
||||
ffi::lua_pushvalue(state, -1);
|
||||
ffi::lua_setmetatable(state, -2); // metatable(hooktable) = hooktable
|
||||
}
|
||||
|
||||
ffi::lua_pushthread(thread_state);
|
||||
ffi::lua_xmove(thread_state, state, 1); // key (thread)
|
||||
let _ = push_internal_userdata(state, callback, false); // value (hook callback)
|
||||
ffi::lua_rawset(state, -3); // hooktable[thread] = hook callback
|
||||
})?;
|
||||
|
||||
ffi::lua_sethook(thread_state, Some(hook_proc), triggers.mask(), triggers.count());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// See [`Lua::create_string`]
|
||||
@@ -442,6 +523,20 @@ impl RawLua {
|
||||
Ok(String(self.pop_ref()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> {
|
||||
let state = self.state();
|
||||
if self.unlikely_memory_error() {
|
||||
let ptr = crate::util::push_buffer(state, size, false)?;
|
||||
return Ok((ptr, crate::Buffer(self.pop_ref())));
|
||||
}
|
||||
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
let ptr = crate::util::push_buffer(state, size, true)?;
|
||||
Ok((ptr, crate::Buffer(self.pop_ref())))
|
||||
}
|
||||
|
||||
/// See [`Lua::create_table_with_capacity`]
|
||||
pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result<Table> {
|
||||
let state = self.state();
|
||||
@@ -456,6 +551,34 @@ impl RawLua {
|
||||
Ok(Table(self.pop_ref()))
|
||||
}
|
||||
|
||||
/// See [`Lua::create_table_from`]
|
||||
pub(crate) unsafe fn create_table_from<I, K, V>(&self, iter: I) -> Result<Table>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: IntoLua,
|
||||
V: IntoLua,
|
||||
{
|
||||
let state = self.state();
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 6)?;
|
||||
|
||||
let iter = iter.into_iter();
|
||||
let lower_bound = iter.size_hint().0;
|
||||
let protect = !self.unlikely_memory_error();
|
||||
push_table(state, 0, lower_bound, protect)?;
|
||||
for (k, v) in iter {
|
||||
self.push(k)?;
|
||||
self.push(v)?;
|
||||
if protect {
|
||||
protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?;
|
||||
} else {
|
||||
ffi::lua_rawset(state, -3);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Table(self.pop_ref()))
|
||||
}
|
||||
|
||||
/// See [`Lua::create_sequence_from`]
|
||||
pub(crate) unsafe fn create_sequence_from<T, I>(&self, iter: I) -> Result<Table>
|
||||
where
|
||||
@@ -492,11 +615,20 @@ impl RawLua {
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
|
||||
let thread_state = if self.unlikely_memory_error() {
|
||||
let protect = !self.unlikely_memory_error();
|
||||
#[cfg(feature = "luau")]
|
||||
let protect = protect || (*self.extra.get()).thread_creation_callback.is_some();
|
||||
|
||||
let thread_state = if !protect {
|
||||
ffi::lua_newthread(state)
|
||||
} else {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))?
|
||||
};
|
||||
|
||||
// Inherit global hook if set
|
||||
#[cfg(not(feature = "luau"))]
|
||||
self.set_thread_hook(thread_state, HookKind::Global)?;
|
||||
|
||||
let thread = Thread(self.pop_ref(), thread_state);
|
||||
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
|
||||
Ok(thread)
|
||||
@@ -506,7 +638,7 @@ impl RawLua {
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
|
||||
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
|
||||
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
|
||||
let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
|
||||
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
@@ -522,48 +654,14 @@ impl RawLua {
|
||||
self.create_thread(func)
|
||||
}
|
||||
|
||||
/// Resets thread (coroutine) and returns it to the pool for later use.
|
||||
/// Returns the thread to the pool for later use.
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
|
||||
let thread_state = thread.1;
|
||||
let extra = &mut *self.extra.get();
|
||||
if extra.thread_pool.len() == extra.thread_pool.capacity() {
|
||||
#[cfg(feature = "lua54")]
|
||||
if ffi::lua_status(thread_state) != ffi::LUA_OK {
|
||||
// Close all to-be-closed variables without returning thread to the pool
|
||||
#[cfg(not(feature = "vendored"))]
|
||||
ffi::lua_resetthread(thread_state);
|
||||
#[cfg(feature = "vendored")]
|
||||
ffi::lua_closethread(thread_state, self.state());
|
||||
if extra.thread_pool.len() < extra.thread_pool.capacity() {
|
||||
if let Some(index) = thread.0.index_count.take() {
|
||||
extra.thread_pool.push(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mut reset_ok = false;
|
||||
if ffi::lua_status(thread_state) == ffi::LUA_OK {
|
||||
if ffi::lua_gettop(thread_state) > 0 {
|
||||
ffi::lua_settop(thread_state, 0);
|
||||
}
|
||||
reset_ok = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "lua54")]
|
||||
if !reset_ok {
|
||||
#[cfg(not(feature = "vendored"))]
|
||||
let status = ffi::lua_resetthread(thread_state);
|
||||
#[cfg(feature = "vendored")]
|
||||
let status = ffi::lua_closethread(thread_state, self.state());
|
||||
reset_ok = status == ffi::LUA_OK;
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
if !reset_ok {
|
||||
ffi::lua_resetthread(thread_state);
|
||||
reset_ok = true;
|
||||
}
|
||||
|
||||
if reset_ok {
|
||||
extra.thread_pool.push(thread.0.index);
|
||||
thread.0.drop = false; // Prevent thread from being garbage collected
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,7 +709,7 @@ impl RawLua {
|
||||
|
||||
/// Pops a value from the Lua stack.
|
||||
///
|
||||
/// Uses 2 stack spaces, does not call `checkstack`.
|
||||
/// Uses up to 1 stack spaces, does not call `checkstack`.
|
||||
pub(crate) unsafe fn pop_value(&self) -> Value {
|
||||
let value = self.stack_value(-1, None);
|
||||
ffi::lua_pop(self.state(), 1);
|
||||
@@ -620,7 +718,7 @@ impl RawLua {
|
||||
|
||||
/// Returns value at given stack index without popping it.
|
||||
///
|
||||
/// Uses 2 stack spaces, does not call checkstack.
|
||||
/// Uses up to 1 stack spaces, does not call `checkstack`.
|
||||
pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value {
|
||||
let state = self.state();
|
||||
match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) {
|
||||
@@ -645,7 +743,7 @@ impl RawLua {
|
||||
|
||||
let n = ffi::lua_tonumber(state, idx);
|
||||
match num_traits::cast(n) {
|
||||
Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i),
|
||||
Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i),
|
||||
_ => Value::Number(n),
|
||||
}
|
||||
}
|
||||
@@ -733,21 +831,14 @@ impl RawLua {
|
||||
#[inline]
|
||||
pub(crate) unsafe fn pop_ref(&self) -> ValueRef {
|
||||
ffi::lua_xmove(self.state(), self.ref_thread(), 1);
|
||||
let index = ref_stack_pop(self.extra.get());
|
||||
let index = (*self.extra.get()).ref_stack_pop();
|
||||
ValueRef::new(self, index)
|
||||
}
|
||||
|
||||
// Same as `pop_ref` but assumes the value is already on the reference thread
|
||||
#[inline]
|
||||
pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef {
|
||||
let index = ref_stack_pop(self.extra.get());
|
||||
ValueRef::new(self, index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
|
||||
ffi::lua_pushvalue(self.ref_thread(), vref.index);
|
||||
let index = ref_stack_pop(self.extra.get());
|
||||
let index = (*self.extra.get()).ref_stack_pop();
|
||||
ValueRef::new(self, index)
|
||||
}
|
||||
|
||||
@@ -837,7 +928,7 @@ impl RawLua {
|
||||
// We generate metatable first to make sure it *always* available when userdata pushed
|
||||
let mt_id = get_metatable_id()?;
|
||||
let protect = !self.unlikely_memory_error();
|
||||
crate::util::push_userdata(state, data, protect)?;
|
||||
push_userdata(state, data, protect)?;
|
||||
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
|
||||
@@ -965,6 +1056,18 @@ impl RawLua {
|
||||
field_setters_index = Some(ffi::lua_absindex(state, -1));
|
||||
}
|
||||
|
||||
// Create methods namecall table
|
||||
#[cfg_attr(not(feature = "luau"), allow(unused_mut))]
|
||||
let mut methods_map = None;
|
||||
#[cfg(feature = "luau")]
|
||||
if registry.enable_namecall {
|
||||
let map: &mut rustc_hash::FxHashMap<_, crate::types::CallbackPtr> =
|
||||
methods_map.get_or_insert_with(Default::default);
|
||||
for (k, m) in ®istry.methods {
|
||||
map.insert(k.as_bytes().to_vec(), &**m);
|
||||
}
|
||||
}
|
||||
|
||||
let mut methods_index = None;
|
||||
let methods_nrec = registry.methods.len();
|
||||
#[cfg(feature = "async")]
|
||||
@@ -1012,6 +1115,7 @@ impl RawLua {
|
||||
field_getters_index,
|
||||
field_setters_index,
|
||||
methods_index,
|
||||
methods_map,
|
||||
)?;
|
||||
|
||||
// Update stack guard to keep metatable after return
|
||||
@@ -1100,7 +1204,7 @@ impl RawLua {
|
||||
pub(crate) fn create_callback(&self, func: Callback) -> Result<Function> {
|
||||
unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int {
|
||||
let upvalue = get_userdata::<CallbackUpvalue>(state, ffi::lua_upvalueindex(1));
|
||||
callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| {
|
||||
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
|
||||
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
|
||||
// The lock must be already held as the callback is executed
|
||||
let rawlua = (*extra).raw_lua();
|
||||
@@ -1147,13 +1251,13 @@ impl RawLua {
|
||||
// Async functions cannot be scoped and therefore destroyed,
|
||||
// so the first upvalue is always valid
|
||||
let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1));
|
||||
callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| {
|
||||
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
|
||||
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
|
||||
// The lock must be already held as the callback is executed
|
||||
let rawlua = (*extra).raw_lua();
|
||||
|
||||
let func = &*(*upvalue).data;
|
||||
let fut = func(rawlua, nargs);
|
||||
let fut = Some(func(rawlua, nargs));
|
||||
let extra = XRc::clone(&(*upvalue).extra);
|
||||
let protect = !rawlua.unlikely_memory_error();
|
||||
push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?;
|
||||
@@ -1171,20 +1275,34 @@ impl RawLua {
|
||||
|
||||
unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int {
|
||||
let upvalue = get_userdata::<AsyncPollUpvalue>(state, ffi::lua_upvalueindex(1));
|
||||
callback_error_ext(state, (*upvalue).extra.get(), |extra, _| {
|
||||
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
|
||||
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
|
||||
// The lock must be already held as the future is polled
|
||||
let rawlua = (*extra).raw_lua();
|
||||
|
||||
if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
|
||||
// Destroy the future and terminate the Lua thread
|
||||
(*upvalue).data.take();
|
||||
ffi::lua_pushinteger(state, -1);
|
||||
return Ok(1);
|
||||
}
|
||||
|
||||
let fut = &mut (*upvalue).data;
|
||||
let mut ctx = Context::from_waker(rawlua.waker());
|
||||
match fut.as_mut().poll(&mut ctx) {
|
||||
Poll::Pending => {
|
||||
match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) {
|
||||
Some(Poll::Pending) => {
|
||||
let fut_nvals = ffi::lua_gettop(state);
|
||||
if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 {
|
||||
// We have some values to yield
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_replace(state, -4);
|
||||
return Ok(3);
|
||||
}
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_pushlightuserdata(state, Lua::poll_pending().0);
|
||||
Ok(2)
|
||||
}
|
||||
Poll::Ready(nresults) => {
|
||||
Some(Poll::Ready(nresults)) => {
|
||||
match nresults? {
|
||||
nresults if nresults < 3 => {
|
||||
// Fast path for up to 2 results without creating a table
|
||||
@@ -1202,6 +1320,7 @@ impl RawLua {
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Err(Error::CallbackDestructed),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1247,8 +1366,9 @@ impl RawLua {
|
||||
lua.load(
|
||||
r#"
|
||||
local poll = get_poll(...)
|
||||
local nres, res, res2 = poll()
|
||||
while true do
|
||||
local nres, res, res2 = poll()
|
||||
-- Poll::Ready branch, `nres` is the number of results
|
||||
if nres ~= nil then
|
||||
if nres == 0 then
|
||||
return
|
||||
@@ -1256,45 +1376,60 @@ impl RawLua {
|
||||
return res
|
||||
elseif nres == 2 then
|
||||
return res, res2
|
||||
elseif nres < 0 then
|
||||
-- Negative `nres` means that the future is terminated
|
||||
-- It must stay yielded and never be resumed again
|
||||
yield()
|
||||
else
|
||||
return unpack(res, nres)
|
||||
end
|
||||
end
|
||||
yield(res) -- `res` is a "pending" value
|
||||
|
||||
-- Poll::Pending branch
|
||||
if res2 == nil then
|
||||
-- `res` is a "pending" value
|
||||
-- `yield` can return a signal to drop the future that we should propagate
|
||||
-- to the poller
|
||||
nres, res, res2 = poll(yield(res))
|
||||
elseif res2 == 0 then
|
||||
nres, res, res2 = poll(yield())
|
||||
elseif res2 == 1 then
|
||||
nres, res, res2 = poll(yield(res))
|
||||
else
|
||||
nres, res, res2 = poll(yield(unpack(res, res2)))
|
||||
end
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.try_cache()
|
||||
.set_name("__mlua_async_poll")
|
||||
.set_name("=__mlua_async_poll")
|
||||
.set_environment(env)
|
||||
.into_function()
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn waker(&self) -> &Waker {
|
||||
(*self.extra.get()).waker.as_ref()
|
||||
pub(crate) fn waker(&self) -> &Waker {
|
||||
unsafe { (*self.extra.get()).waker.as_ref() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
|
||||
mem::replace(&mut (*self.extra.get()).waker, waker)
|
||||
pub(crate) fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
|
||||
unsafe { mem::replace(&mut (*self.extra.get()).waker, waker) }
|
||||
}
|
||||
}
|
||||
|
||||
// Uses 3 stack spaces
|
||||
unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> {
|
||||
#[inline(always)]
|
||||
pub unsafe fn requiref(
|
||||
unsafe fn requiref(
|
||||
state: *mut ffi::lua_State,
|
||||
modname: &str,
|
||||
modname: *const c_char,
|
||||
openf: ffi::lua_CFunction,
|
||||
glb: c_int,
|
||||
) -> Result<()> {
|
||||
let modname = mlua_expect!(CString::new(modname), "modname contains nil byte");
|
||||
protect_lua!(state, 0, 1, |state| {
|
||||
ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb)
|
||||
protect_lua!(state, 0, 0, |state| {
|
||||
ffi::luaL_requiref(state, modname, openf, glb)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1325,36 +1460,30 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
|
||||
{
|
||||
if libs.contains(StdLib::COROUTINE) {
|
||||
requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if libs.contains(StdLib::TABLE) {
|
||||
requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
if libs.contains(StdLib::IO) {
|
||||
requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
if libs.contains(StdLib::OS) {
|
||||
requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
if libs.contains(StdLib::STRING) {
|
||||
requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
|
||||
{
|
||||
if libs.contains(StdLib::UTF8) {
|
||||
requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1362,7 +1491,6 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
|
||||
{
|
||||
if libs.contains(StdLib::BIT) {
|
||||
requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1370,53 +1498,40 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
|
||||
{
|
||||
if libs.contains(StdLib::BIT) {
|
||||
requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
if libs.contains(StdLib::BUFFER) {
|
||||
requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
if libs.contains(StdLib::VECTOR) {
|
||||
requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
if libs.contains(StdLib::MATH) {
|
||||
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
if libs.contains(StdLib::DEBUG) {
|
||||
requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
if libs.contains(StdLib::PACKAGE) {
|
||||
requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
if libs.contains(StdLib::PACKAGE) {
|
||||
let lua = (*ExtraData::get(state)).lua();
|
||||
crate::luau::register_package_module(lua)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "luajit")]
|
||||
if libs.contains(StdLib::JIT) {
|
||||
requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
#[cfg(feature = "luajit")]
|
||||
if libs.contains(StdLib::FFI) {
|
||||
requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+10
-30
@@ -24,9 +24,10 @@ 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(super) unsafe fn callback_error_ext<F, R>(
|
||||
pub(crate) unsafe fn callback_error_ext<F, R>(
|
||||
state: *mut ffi::lua_State,
|
||||
mut extra: *mut ExtraData,
|
||||
wrap_error: bool,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
@@ -88,7 +89,7 @@ where
|
||||
PreallocatedFailure::New(_) => {
|
||||
ffi::lua_rotate(state, 1, -1);
|
||||
ffi::lua_xmove(state, ref_thread, 1);
|
||||
let index = ref_stack_pop(extra);
|
||||
let index = (*extra).ref_stack_pop();
|
||||
(*extra).wrapped_failure_pool.push(index);
|
||||
(*extra).wrapped_failure_top += 1;
|
||||
}
|
||||
@@ -114,6 +115,13 @@ 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);
|
||||
@@ -142,31 +150,3 @@ 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
|
||||
}
|
||||
|
||||
+11
-10
@@ -6,9 +6,11 @@ pub struct StdLib(u32);
|
||||
|
||||
impl StdLib {
|
||||
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
|
||||
///
|
||||
/// Requires `feature = "lua54/lua53/lua52/luau"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(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
|
||||
@@ -26,21 +28,24 @@ impl StdLib {
|
||||
pub const STRING: StdLib = StdLib(1 << 4);
|
||||
|
||||
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
|
||||
///
|
||||
/// Requires `feature = "lua54/lua53/luau"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(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
|
||||
@@ -54,15 +59,11 @@ impl StdLib {
|
||||
pub const VECTOR: StdLib = StdLib(1 << 10);
|
||||
|
||||
/// [`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 << 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);
|
||||
|
||||
+9
-9
@@ -11,7 +11,7 @@ use crate::traits::IntoLua;
|
||||
use crate::types::{LuaType, ValueRef};
|
||||
use crate::value::Value;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
use {
|
||||
serde::ser::{Serialize, Serializer},
|
||||
std::result::Result as StdResult,
|
||||
@@ -43,7 +43,7 @@ impl String {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn to_str(&self) -> Result<BorrowedStr> {
|
||||
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
|
||||
BorrowedStr::try_from(self)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ impl String {
|
||||
|
||||
/// Get the bytes that make up this string.
|
||||
///
|
||||
/// The returned slice will not contain the terminating nul byte, but will contain any nul
|
||||
/// The returned slice will not contain the terminating null byte, but will contain any null
|
||||
/// bytes embedded into the Lua string.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -102,19 +102,19 @@ impl String {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> BorrowedBytes {
|
||||
pub fn as_bytes(&self) -> BorrowedBytes<'_> {
|
||||
BorrowedBytes::from(self)
|
||||
}
|
||||
|
||||
/// Get the bytes that make up this string, including the trailing nul byte.
|
||||
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
|
||||
/// Get the bytes that make up this string, including the trailing null byte.
|
||||
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
|
||||
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
|
||||
// Include the trailing nul byte (it's always present but excluded by default)
|
||||
// Include the trailing null byte (it's always present but excluded by default)
|
||||
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
|
||||
BorrowedBytes { buf, borrow, _lua }
|
||||
}
|
||||
|
||||
// Does not return the terminating nul byte
|
||||
// Does not return the terminating null byte
|
||||
unsafe fn to_slice(&self) -> (&[u8], Lua) {
|
||||
let lua = self.0.lua.upgrade();
|
||||
let slice = {
|
||||
@@ -211,7 +211,7 @@ impl Hash for String {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for String {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
+143
-79
@@ -6,16 +6,16 @@ use std::string::String as StdString;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::function::Function;
|
||||
use crate::state::{LuaGuard, RawLua};
|
||||
use crate::state::{LuaGuard, RawLua, WeakLua};
|
||||
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
|
||||
use crate::types::{Integer, LuaType, ValueRef};
|
||||
use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
|
||||
use crate::value::{Nil, Value};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use futures_util::future::{self, Either, Future};
|
||||
use crate::function::AsyncCallFuture;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
use {
|
||||
rustc_hash::FxHashSet,
|
||||
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
|
||||
@@ -211,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)?);
|
||||
@@ -416,14 +416,7 @@ impl Table {
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
|
||||
// Clear array part
|
||||
for i in 1..=ffi::lua_rawlen(state, -1) {
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_rawseti(state, -2, i as Integer);
|
||||
}
|
||||
|
||||
// Clear hash part
|
||||
// It must be safe as long as we don't use invalid keys
|
||||
// This is safe as long as we don't assign new keys
|
||||
ffi::lua_pushnil(state);
|
||||
while ffi::lua_next(state, -2) != 0 {
|
||||
ffi::lua_pop(state, 1); // pop value
|
||||
@@ -487,52 +480,37 @@ 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 state = lua.state();
|
||||
let ref_thread = lua.ref_thread();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 2);
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
if ffi::lua_getmetatable(state, -1) == 0 {
|
||||
if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Table(lua.pop_ref()))
|
||||
Some(Table(lua.pop_ref_thread()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>) {
|
||||
// Workaround to throw readonly error without returning Result
|
||||
pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
|
||||
#[cfg(feature = "luau")]
|
||||
if self.is_readonly() {
|
||||
panic!("attempt to modify a readonly table");
|
||||
return Err(Error::runtime("attempt to modify a readonly table"));
|
||||
}
|
||||
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
let ref_thread = lua.ref_thread();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 2);
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
if let Some(metatable) = metatable {
|
||||
lua.push_ref(&metatable.0);
|
||||
if let Some(metatable) = &metatable {
|
||||
ffi::lua_pushvalue(ref_thread, metatable.0.index);
|
||||
} else {
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_pushnil(ref_thread);
|
||||
}
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
ffi::lua_setmetatable(ref_thread, self.0.index);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if the table has metatable attached.
|
||||
@@ -544,8 +522,6 @@ 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) {
|
||||
@@ -561,8 +537,6 @@ 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 {
|
||||
@@ -580,8 +554,6 @@ 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) {
|
||||
@@ -626,7 +598,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,
|
||||
@@ -691,21 +663,30 @@ 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>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
|
||||
where
|
||||
V: FromLua,
|
||||
{
|
||||
pub fn for_each_value<V: FromLua>(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> {
|
||||
self.for_each_value_by_len(None, f)
|
||||
}
|
||||
|
||||
fn for_each_value_by_len<V: FromLua>(
|
||||
&self,
|
||||
len: impl Into<Option<usize>>,
|
||||
mut f: impl FnMut(V) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
let len = len.into();
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
@@ -713,9 +694,14 @@ impl Table {
|
||||
check_stack(state, 4)?;
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
let len = ffi::lua_rawlen(state, -1);
|
||||
for i in 1..=len {
|
||||
ffi::lua_rawgeti(state, -1, i as _);
|
||||
for i in 1.. {
|
||||
if len.map(|len| i > len).unwrap_or(false) {
|
||||
break;
|
||||
}
|
||||
let t = ffi::lua_rawgeti(state, -1, i as _);
|
||||
if len.is_none() && t == ffi::LUA_TNIL {
|
||||
break;
|
||||
}
|
||||
f(V::from_stack(-1, &lua)?)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
@@ -748,8 +734,9 @@ impl Table {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
pub(crate) fn is_array(&self) -> bool {
|
||||
/// Checks if the table has the array metatable attached.
|
||||
#[cfg(feature = "serde")]
|
||||
fn has_array_metatable(&self) -> bool {
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
@@ -765,6 +752,70 @@ impl Table {
|
||||
}
|
||||
}
|
||||
|
||||
/// If the table is an array, returns the number of non-nil elements and max index.
|
||||
///
|
||||
/// Returns `None` if the table is not an array.
|
||||
///
|
||||
/// This operation has O(n) complexity.
|
||||
#[cfg(feature = "serde")]
|
||||
fn find_array_len(&self) -> Option<(usize, usize)> {
|
||||
let lua = self.0.lua.lock();
|
||||
let ref_thread = lua.ref_thread();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(ref_thread);
|
||||
|
||||
let (mut count, mut max_index) = (0, 0);
|
||||
ffi::lua_pushnil(ref_thread);
|
||||
while ffi::lua_next(ref_thread, self.0.index) != 0 {
|
||||
if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER {
|
||||
return None;
|
||||
}
|
||||
|
||||
let k = ffi::lua_tonumber(ref_thread, -2);
|
||||
if k.trunc() != k || k < 1.0 {
|
||||
return None;
|
||||
}
|
||||
max_index = std::cmp::max(max_index, k as usize);
|
||||
count += 1;
|
||||
ffi::lua_pop(ref_thread, 1);
|
||||
}
|
||||
Some((count, max_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if the table should be encoded as an array or a map.
|
||||
///
|
||||
/// The algorithm is the following:
|
||||
/// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they
|
||||
/// all are positive integers. If non-array key is found, return `None` (encode as map).
|
||||
/// Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps.
|
||||
///
|
||||
/// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the
|
||||
/// array metatable. If so, encode as array. If the table is empty and
|
||||
/// `encode_empty_tables_as_array` is enabled, encode as array.
|
||||
///
|
||||
/// Returns the length of the array if it should be encoded as an array.
|
||||
#[cfg(feature = "serde")]
|
||||
pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option<usize> {
|
||||
if options.detect_mixed_tables {
|
||||
if let Some((len, max_idx)) = self.find_array_len() {
|
||||
// If the array is too sparse, serialize it as a map instead
|
||||
if len < 10 || len * 2 >= max_idx {
|
||||
return Some(max_idx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let len = self.raw_len();
|
||||
if len > 0 || self.has_array_metatable() {
|
||||
return Some(len);
|
||||
}
|
||||
if options.encode_empty_tables_as_array && self.is_empty() {
|
||||
return Some(0);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline(always)]
|
||||
fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
|
||||
@@ -905,16 +956,16 @@ impl ObjectLike for Table {
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
// Convert table to a function and call via pcall that respects the `__call` metamethod.
|
||||
Function(self.0.copy()).call(args)
|
||||
Function(self.0.clone()).call(args)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call_async(args)
|
||||
Function(self.0.clone()).call_async(args)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -926,7 +977,7 @@ impl ObjectLike for Table {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
@@ -946,35 +997,45 @@ impl ObjectLike for Table {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
match self.get(name) {
|
||||
Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
|
||||
Ok(Value::Function(func)) => func.call_async(args),
|
||||
Ok(val) => {
|
||||
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
|
||||
Either::Right(future::ready(Err(Error::RuntimeError(msg))))
|
||||
AsyncCallFuture::error(Error::RuntimeError(msg))
|
||||
}
|
||||
Err(err) => Either::Right(future::ready(Err(err))),
|
||||
Err(err) => AsyncCallFuture::error(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_string(&self) -> Result<StdString> {
|
||||
Value::Table(Table(self.0.copy())).to_string()
|
||||
Value::Table(Table(self.0.clone())).to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Table(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn weak_lua(&self) -> &WeakLua {
|
||||
&self.0.lua
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapped [`Table`] with customized serialization behavior.
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
pub(crate) struct SerializableTable<'a> {
|
||||
table: &'a Table,
|
||||
options: crate::serde::de::Options,
|
||||
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for Table {
|
||||
#[inline]
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
|
||||
@@ -982,7 +1043,7 @@ impl Serialize for Table {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'a> SerializableTable<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
@@ -998,7 +1059,16 @@ impl<'a> SerializableTable<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<V> TableSequence<'_, V> {
|
||||
/// Sets the length (hint) of the sequence.
|
||||
#[cfg(feature = "serde")]
|
||||
pub(crate) fn with_len(mut self, len: usize) -> Self {
|
||||
self.len = Some(len);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for SerializableTable<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
|
||||
where
|
||||
@@ -1019,14 +1089,10 @@ impl Serialize for SerializableTable<'_> {
|
||||
let _guard = RecursionGuard::new(self.table, visited);
|
||||
|
||||
// Array
|
||||
let len = self.table.raw_len();
|
||||
if len > 0
|
||||
|| self.table.is_array()
|
||||
|| (self.options.encode_empty_tables_as_array && self.table.is_empty())
|
||||
{
|
||||
if let Some(len) = self.table.encode_as_array(self.options) {
|
||||
let mut seq = serializer.serialize_seq(Some(len))?;
|
||||
let mut serialize_err = None;
|
||||
let res = self.table.for_each_value::<Value>(|value| {
|
||||
let res = self.table.for_each_value_by_len::<Value>(len, |value| {
|
||||
let skip = check_value_for_skip(&value, self.options, visited)
|
||||
.map_err(|err| Error::SerializeError(err.to_string()))?;
|
||||
if skip {
|
||||
@@ -1150,13 +1216,11 @@ pub struct TableSequence<'a, V> {
|
||||
guard: LuaGuard,
|
||||
table: &'a Table,
|
||||
index: Integer,
|
||||
len: Option<usize>,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V> Iterator for TableSequence<'_, V>
|
||||
where
|
||||
V: FromLua,
|
||||
{
|
||||
impl<V: FromLua> Iterator for TableSequence<'_, V> {
|
||||
type Item = Result<V>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1170,7 +1234,7 @@ where
|
||||
|
||||
lua.push_ref(&self.table.0);
|
||||
match ffi::lua_rawgeti(state, -1, self.index) {
|
||||
ffi::LUA_TNIL => None,
|
||||
ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None,
|
||||
_ => {
|
||||
self.index += 1;
|
||||
Some(V::from_stack(-1, lua))
|
||||
|
||||
+212
-114
@@ -10,8 +10,8 @@ use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::{
|
||||
hook::{Debug, HookTriggers},
|
||||
types::MaybeSend,
|
||||
debug::{Debug, HookTriggers},
|
||||
types::HookKind,
|
||||
};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -47,17 +47,24 @@ pub enum ThreadStatus {
|
||||
/// to the thread stack.
|
||||
#[derive(Clone, Copy)]
|
||||
enum ThreadStatusInner {
|
||||
New,
|
||||
New(c_int),
|
||||
Running,
|
||||
Yielded,
|
||||
Yielded(c_int),
|
||||
Finished,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl ThreadStatusInner {
|
||||
#[cfg(feature = "async")]
|
||||
#[inline(always)]
|
||||
fn is_resumable(self) -> bool {
|
||||
matches!(self, ThreadStatusInner::New | ThreadStatusInner::Yielded)
|
||||
matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline(always)]
|
||||
fn is_yielded(self) -> bool {
|
||||
matches!(self, ThreadStatusInner::Yielded(_))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,23 +79,22 @@ 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<A, R> {
|
||||
pub struct AsyncThread<R> {
|
||||
thread: Thread,
|
||||
init_args: Option<A>,
|
||||
ret: PhantomData<R>,
|
||||
ret: PhantomData<fn() -> R>,
|
||||
recycle: bool,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
/// Returns reference to the Lua state that this thread is associated with.
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
fn state(&self) -> *mut ffi::lua_State {
|
||||
pub fn state(&self) -> *mut ffi::lua_State {
|
||||
self.1
|
||||
}
|
||||
|
||||
@@ -141,17 +147,58 @@ impl Thread {
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
let lua = self.0.lua.lock();
|
||||
if !self.status_inner(&lua).is_resumable() {
|
||||
return Err(Error::CoroutineUnresumable);
|
||||
}
|
||||
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);
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
|
||||
let nresults = self.resume_inner(&lua, args)?;
|
||||
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 (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
|
||||
check_stack(state, nresults + 1)?;
|
||||
ffi::lua_xmove(thread_state, state, nresults);
|
||||
|
||||
R::from_stack_multi(nresults, &lua)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
check_stack(state, 1)?;
|
||||
error.push_into_stack(&lua)?;
|
||||
ffi::lua_xmove(state, thread_state, 1);
|
||||
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
|
||||
check_stack(state, nresults + 1)?;
|
||||
ffi::lua_xmove(thread_state, state, nresults);
|
||||
|
||||
@@ -162,35 +209,33 @@ 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, args: impl IntoLuaMulti) -> Result<c_int> {
|
||||
unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
|
||||
let state = lua.state();
|
||||
let thread_state = self.state();
|
||||
|
||||
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;
|
||||
#[cfg(not(feature = "luau"))]
|
||||
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 {
|
||||
#[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
|
||||
return Err(pop_error(thread_state, ret));
|
||||
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))
|
||||
}
|
||||
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,
|
||||
@@ -205,9 +250,10 @@ 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,
|
||||
ffi::LUA_OK if unsafe { ffi::lua_gettop(thread_state) } > 0 => ThreadStatusInner::New,
|
||||
ffi::LUA_YIELD => ThreadStatusInner::Yielded(top),
|
||||
ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1),
|
||||
ffi::LUA_OK => ThreadStatusInner::Finished,
|
||||
_ => ThreadStatusInner::Error,
|
||||
}
|
||||
@@ -216,16 +262,33 @@ impl Thread {
|
||||
/// 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.
|
||||
/// To remove a hook call [`Lua::remove_hook`].
|
||||
/// You can have multiple hooks for different threads.
|
||||
///
|
||||
/// To remove a hook call [`Thread::remove_hook`].
|
||||
///
|
||||
/// [`Lua::set_hook`]: crate::Lua::set_hook
|
||||
#[cfg(not(feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
|
||||
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
|
||||
F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
|
||||
{
|
||||
let lua = self.0.lua.lock();
|
||||
unsafe {
|
||||
lua.set_thread_hook(self.state(), triggers, callback);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,29 +309,10 @@ impl Thread {
|
||||
pub fn reset(&self, func: Function) -> Result<()> {
|
||||
let lua = self.0.lua.lock();
|
||||
let thread_state = self.state();
|
||||
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 {
|
||||
let status = self.status_inner(&lua);
|
||||
self.reset_inner(status)?;
|
||||
|
||||
// Push function to the top of the thread stack
|
||||
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
|
||||
|
||||
@@ -283,9 +327,47 @@ 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 = "lua54", feature = "luau")))]
|
||||
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
|
||||
Err(Error::runtime("cannot reset non-finished thread"))
|
||||
}
|
||||
#[cfg(any(feature = "lua54", feature = "luau"))]
|
||||
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
|
||||
let thread_state = self.state();
|
||||
|
||||
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
|
||||
let status = ffi::lua_resetthread(thread_state);
|
||||
#[cfg(all(feature = "lua54", feature = "vendored"))]
|
||||
let status = {
|
||||
let lua = self.0.lua.lock();
|
||||
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);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
|
||||
///
|
||||
/// `args` are passed as arguments to the thread function for first call.
|
||||
/// Only resumable threads can be converted to [`AsyncThread`].
|
||||
///
|
||||
/// `args` are pushed to the thread stack and will be used when the thread is resumed.
|
||||
/// The object calls [`resume`] while polling and also allow to run Rust futures
|
||||
/// to completion using an executor.
|
||||
///
|
||||
@@ -293,8 +375,6 @@ 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
|
||||
@@ -318,7 +398,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;
|
||||
@@ -331,15 +411,31 @@ impl Thread {
|
||||
/// ```
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> AsyncThread<impl IntoLuaMulti, R>
|
||||
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> Result<AsyncThread<R>>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
AsyncThread {
|
||||
thread: self,
|
||||
init_args: Some(args),
|
||||
ret: PhantomData,
|
||||
recycle: false,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,11 +472,8 @@ 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();
|
||||
@@ -420,7 +513,7 @@ impl LuaType for Thread {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl<A, R> AsyncThread<A, R> {
|
||||
impl<R> AsyncThread<R> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
|
||||
self.recycle = recyclable;
|
||||
@@ -428,26 +521,41 @@ impl<A, R> AsyncThread<A, R> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl<A, R> Drop for AsyncThread<A, R> {
|
||||
impl<R> Drop for AsyncThread<R> {
|
||||
fn drop(&mut self) {
|
||||
if self.recycle {
|
||||
if let Some(lua) = self.thread.0.lua.try_lock() {
|
||||
// For Lua 5.4 this also closes all pending to-be-closed variables
|
||||
unsafe { lua.recycle_thread(&mut self.thread) };
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
|
||||
impl<R: FromLuaMulti> Stream for AsyncThread<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();
|
||||
if !self.thread.status_inner(&lua).is_resumable() {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
let nargs = match self.thread.status_inner(&lua) {
|
||||
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
|
||||
_ => return Poll::Ready(None),
|
||||
};
|
||||
|
||||
let state = lua.state();
|
||||
let thread_state = self.thread.state();
|
||||
@@ -456,36 +564,34 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
let _wg = WakerGuard::new(&lua, cx.waker());
|
||||
|
||||
// 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 nresults == 1 && is_poll_pending(thread_state) {
|
||||
return Poll::Pending;
|
||||
if status.is_yielded() {
|
||||
if nresults == 1 && is_poll_pending(thread_state) {
|
||||
return Poll::Pending;
|
||||
}
|
||||
// Continue polling
|
||||
cx.waker().wake_by_ref();
|
||||
}
|
||||
|
||||
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<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
|
||||
impl<R: FromLuaMulti> Future for AsyncThread<R> {
|
||||
type Output = Result<R>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let lua = self.thread.0.lua.lock();
|
||||
if !self.thread.status_inner(&lua).is_resumable() {
|
||||
return Poll::Ready(Err(Error::CoroutineUnresumable));
|
||||
}
|
||||
let nargs = match self.thread.status_inner(&lua) {
|
||||
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
|
||||
_ => return Poll::Ready(Err(Error::CoroutineUnresumable)),
|
||||
};
|
||||
|
||||
let state = lua.state();
|
||||
let thread_state = self.thread.state();
|
||||
@@ -494,21 +600,13 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
let _wg = WakerGuard::new(&lua, cx.waker());
|
||||
|
||||
// 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 nresults == 1 && is_poll_pending(thread_state) {
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
if ffi::lua_status(thread_state) == ffi::LUA_YIELD {
|
||||
// Ignore value returned via yield()
|
||||
cx.waker().wake_by_ref();
|
||||
if status.is_yielded() {
|
||||
if !(nresults == 1 && is_poll_pending(thread_state)) {
|
||||
// Ignore values returned via yield()
|
||||
cx.waker().wake_by_ref();
|
||||
}
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
@@ -537,7 +635,7 @@ struct WakerGuard<'lua, 'a> {
|
||||
impl<'lua, 'a> WakerGuard<'lua, 'a> {
|
||||
#[inline]
|
||||
pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
|
||||
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
|
||||
let prev = lua.set_waker(NonNull::from(waker));
|
||||
Ok(WakerGuard {
|
||||
lua,
|
||||
prev,
|
||||
@@ -549,7 +647,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
|
||||
#[cfg(feature = "async")]
|
||||
impl Drop for WakerGuard<'_, '_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.lua.set_waker(self.prev) };
|
||||
self.lua.set_waker(self.prev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +660,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);
|
||||
}
|
||||
|
||||
+46
-10
@@ -5,13 +5,13 @@ use std::sync::Arc;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::multi::MultiValue;
|
||||
use crate::private::Sealed;
|
||||
use crate::state::{Lua, RawLua};
|
||||
use crate::state::{Lua, RawLua, WeakLua};
|
||||
use crate::types::MaybeSend;
|
||||
use crate::util::{check_stack, short_type_name};
|
||||
use crate::util::{check_stack, parse_lookup_path, short_type_name};
|
||||
use crate::value::Value;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use std::future::Future;
|
||||
use {crate::function::AsyncCallFuture, std::future::Future};
|
||||
|
||||
/// Trait for types convertible to [`Value`].
|
||||
pub trait IntoLua: Sized {
|
||||
@@ -162,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) -> impl Future<Output = Result<R>>
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti;
|
||||
|
||||
@@ -175,12 +175,10 @@ 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) -> impl Future<Output = Result<R>>
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti;
|
||||
|
||||
@@ -195,19 +193,57 @@ 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) -> impl Future<Output = Result<R>>
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<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<StdString>;
|
||||
|
||||
/// Converts the object to a Lua value.
|
||||
fn to_value(&self) -> Value;
|
||||
|
||||
/// Gets a reference to the associated Lua state.
|
||||
#[doc(hidden)]
|
||||
fn weak_lua(&self) -> &WeakLua;
|
||||
}
|
||||
|
||||
/// A trait for types that can be used as Lua functions.
|
||||
|
||||
+33
-10
@@ -1,10 +1,9 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::error::Result;
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::hook::Debug;
|
||||
use crate::debug::{Debug, HookTriggers};
|
||||
use crate::error::Result;
|
||||
use crate::state::{ExtraData, Lua, RawLua};
|
||||
|
||||
// Re-export mutex wrappers
|
||||
@@ -21,6 +20,9 @@ pub use either::Either;
|
||||
pub use registry_key::RegistryKey;
|
||||
pub(crate) use value_ref::ValueRef;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) use value_ref::ValueRefIndex;
|
||||
|
||||
/// Type of Lua integer numbers.
|
||||
pub type Integer = ffi::lua_Integer;
|
||||
/// Type of Lua floating point numbers.
|
||||
@@ -36,10 +38,13 @@ unsafe impl Send for LightUserData {}
|
||||
unsafe impl Sync for LightUserData {}
|
||||
|
||||
#[cfg(feature = "send")]
|
||||
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'static>;
|
||||
type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'a;
|
||||
|
||||
#[cfg(not(feature = "send"))]
|
||||
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 'static>;
|
||||
type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + 'a;
|
||||
|
||||
pub(crate) type Callback = Box<CallbackFn<'static>>;
|
||||
pub(crate) type CallbackPtr = *const CallbackFn<'static>;
|
||||
|
||||
pub(crate) type ScopedCallback<'s> = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 's>;
|
||||
|
||||
@@ -62,7 +67,7 @@ pub(crate) type AsyncCallback =
|
||||
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback>;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) type AsyncPollUpvalue = Upvalue<BoxFuture<'static, Result<c_int>>>;
|
||||
pub(crate) type AsyncPollUpvalue = Upvalue<Option<BoxFuture<'static, Result<c_int>>>>;
|
||||
|
||||
/// Type to set next Lua VM action after executing interrupt or hook function.
|
||||
pub enum VmState {
|
||||
@@ -73,17 +78,35 @@ 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 = Rc<dyn Fn(&Lua, Debug) -> Result<VmState> + Send>;
|
||||
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState> + Send>;
|
||||
|
||||
#[cfg(all(not(feature = "send"), not(feature = "luau")))]
|
||||
pub(crate) type HookCallback = Rc<dyn Fn(&Lua, Debug) -> Result<VmState>>;
|
||||
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState>>;
|
||||
|
||||
#[cfg(all(feature = "send", feature = "luau"))]
|
||||
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState> + Send>;
|
||||
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
|
||||
|
||||
#[cfg(all(not(feature = "send"), feature = "luau"))]
|
||||
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState>>;
|
||||
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
|
||||
|
||||
#[cfg(all(feature = "send", feature = "luau"))]
|
||||
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()> + Send>;
|
||||
|
||||
#[cfg(all(not(feature = "send"), feature = "luau"))]
|
||||
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()>>;
|
||||
|
||||
#[cfg(all(feature = "send", feature = "luau"))]
|
||||
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Send>;
|
||||
|
||||
#[cfg(all(not(feature = "send"), feature = "luau"))]
|
||||
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
|
||||
|
||||
#[cfg(all(feature = "send", feature = "lua54"))]
|
||||
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
|
||||
|
||||
@@ -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())
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+28
-23
@@ -1,22 +1,39 @@
|
||||
use std::fmt;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
use super::XRc;
|
||||
use crate::state::{RawLua, WeakLua};
|
||||
|
||||
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
|
||||
#[derive(Clone)]
|
||||
pub struct ValueRef {
|
||||
pub(crate) lua: WeakLua,
|
||||
// Keep index separate to avoid additional indirection when accessing it.
|
||||
pub(crate) index: c_int,
|
||||
pub(crate) drop: bool,
|
||||
// If `index_count` is `None`, the value does not need to be destroyed.
|
||||
pub(crate) index_count: Option<ValueRefIndex>,
|
||||
}
|
||||
|
||||
/// A reference to a Lua value index in the auxiliary thread.
|
||||
/// It's cheap to clone and can be used to track the number of references to a value.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ValueRefIndex(pub(crate) XRc<c_int>);
|
||||
|
||||
impl From<c_int> for ValueRefIndex {
|
||||
#[inline]
|
||||
fn from(index: c_int) -> Self {
|
||||
ValueRefIndex(XRc::new(index))
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueRef {
|
||||
#[inline]
|
||||
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
|
||||
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
|
||||
let index = index.into();
|
||||
ValueRef {
|
||||
lua: lua.weak().clone(),
|
||||
index,
|
||||
drop: true,
|
||||
index: *index.0,
|
||||
index_count: Some(index),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +42,6 @@ impl ValueRef {
|
||||
let lua = self.lua.lock();
|
||||
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
|
||||
}
|
||||
|
||||
/// Returns a copy of the value, which is valid as long as the original value is held.
|
||||
#[inline]
|
||||
pub(crate) fn copy(&self) -> Self {
|
||||
ValueRef {
|
||||
lua: self.lua.clone(),
|
||||
index: self.index,
|
||||
drop: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ValueRef {
|
||||
@@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ValueRef {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { self.lua.lock().clone_ref(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ValueRef {
|
||||
fn drop(&mut self) {
|
||||
if self.drop {
|
||||
if let Some(lua) = self.lua.try_lock() {
|
||||
unsafe { lua.drop_ref(self) };
|
||||
if let Some(ValueRefIndex(index)) = self.index_count.take() {
|
||||
// It's guaranteed that the inner value returns exactly once.
|
||||
// This means in particular that the value is not dropped.
|
||||
if XRc::into_inner(index).is_some() {
|
||||
if let Some(lua) = self.lua.try_lock() {
|
||||
unsafe { lua.drop_ref(self) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+71
-85
@@ -18,7 +18,7 @@ use crate::value::Value;
|
||||
#[cfg(feature = "async")]
|
||||
use std::future::Future;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
use {
|
||||
serde::ser::{self, Serialize, Serializer},
|
||||
std::result::Result as StdResult,
|
||||
@@ -56,30 +56,32 @@ pub enum MetaMethod {
|
||||
/// The unary minus (`-`) operator.
|
||||
Unm,
|
||||
/// The floor division (//) operator.
|
||||
/// Requires `feature = "lua54/lua53/luau"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
|
||||
IDiv,
|
||||
/// The bitwise AND (&) operator.
|
||||
/// Requires `feature = "lua54/lua53"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
|
||||
BAnd,
|
||||
/// The bitwise OR (|) operator.
|
||||
/// Requires `feature = "lua54/lua53"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
|
||||
BOr,
|
||||
/// The bitwise XOR (binary ~) operator.
|
||||
/// Requires `feature = "lua54/lua53"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
|
||||
BXor,
|
||||
/// The bitwise NOT (unary ~) operator.
|
||||
/// Requires `feature = "lua54/lua53"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
|
||||
BNot,
|
||||
/// The bitwise left shift (<<) operator.
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
|
||||
Shl,
|
||||
/// The bitwise right shift (>>) operator.
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
|
||||
Shr,
|
||||
/// The string concatenation operator `..`.
|
||||
Concat,
|
||||
@@ -104,16 +106,16 @@ pub enum MetaMethod {
|
||||
/// The `__pairs` metamethod.
|
||||
///
|
||||
/// This is not an operator, but it will be called by the built-in `pairs` function.
|
||||
///
|
||||
/// Requires `feature = "lua54/lua53/lua52"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52",))]
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(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"))))]
|
||||
@@ -122,8 +124,6 @@ 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,
|
||||
@@ -134,8 +134,6 @@ 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(feature = "lua54")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
|
||||
@@ -221,9 +219,7 @@ impl MetaMethod {
|
||||
pub(crate) const fn as_cstr(self) -> &'static CStr {
|
||||
match self {
|
||||
#[rustfmt::skip]
|
||||
MetaMethod::Type => unsafe {
|
||||
CStr::from_bytes_with_nul_unchecked(if cfg!(feature = "luau") { b"__type\0" } else { b"__name\0" })
|
||||
},
|
||||
MetaMethod::Type => if cfg!(feature = "luau") { c"__type" } else { c"__name" },
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -244,6 +240,13 @@ impl AsRef<str> for MetaMethod {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MetaMethod> for StdString {
|
||||
#[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.
|
||||
@@ -253,7 +256,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 ToString, method: M)
|
||||
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -264,7 +267,7 @@ 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 ToString, method: M)
|
||||
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -274,12 +277,10 @@ 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<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -291,12 +292,10 @@ 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 ToString, method: M)
|
||||
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -309,7 +308,7 @@ pub trait UserDataMethods<T> {
|
||||
/// 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 ToString, function: F)
|
||||
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -320,7 +319,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 ToString, function: F)
|
||||
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -331,12 +330,10 @@ 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 ToString, function: F)
|
||||
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -351,7 +348,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 ToString, method: M)
|
||||
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -365,7 +362,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 ToString, method: M)
|
||||
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -375,12 +372,13 @@ 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(feature = "async")))]
|
||||
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
#[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<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -393,12 +391,10 @@ 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 ToString, method: M)
|
||||
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -411,7 +407,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 ToString, function: F)
|
||||
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -422,7 +418,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 ToString, function: F)
|
||||
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -432,12 +428,13 @@ 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(feature = "async")))]
|
||||
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
|
||||
#[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<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -456,7 +453,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 ToString, value: V)
|
||||
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static;
|
||||
|
||||
@@ -467,7 +464,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 ToString, method: M)
|
||||
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
|
||||
R: IntoLua;
|
||||
@@ -480,21 +477,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 ToString, method: M)
|
||||
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, 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 ToString, function: F)
|
||||
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, 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 ToString, function: F)
|
||||
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
|
||||
A: FromLua;
|
||||
@@ -507,7 +504,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 ToString, value: V)
|
||||
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static;
|
||||
|
||||
@@ -519,7 +516,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 ToString, f: F)
|
||||
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
|
||||
where
|
||||
F: FnOnce(&Lua) -> Result<R> + 'static,
|
||||
R: IntoLua;
|
||||
@@ -693,22 +690,16 @@ 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();
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
_ => Err(Error::UserDataTypeMismatch),
|
||||
}
|
||||
},
|
||||
_ => Err(Error::UserDataTypeMismatch),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,22 +888,17 @@ impl AnyUserData {
|
||||
self.raw_metatable().map(UserDataMetatable)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
|
||||
pub fn get_metatable(&self) -> Result<UserDataMetatable> {
|
||||
self.metatable()
|
||||
}
|
||||
|
||||
/// Returns a raw metatable of this [`AnyUserData`].
|
||||
fn raw_metatable(&self) -> Result<Table> {
|
||||
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, 3)?;
|
||||
// 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)?;
|
||||
|
||||
lua.push_userdata_ref(&self.0)?;
|
||||
ffi::lua_getmetatable(state, -1); // Checked that non-empty on the previous call
|
||||
Ok(Table(lua.pop_ref()))
|
||||
ffi::lua_getmetatable(ref_thread, self.0.index);
|
||||
Ok(Table(lua.pop_ref_thread()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,7 +965,7 @@ impl AnyUserData {
|
||||
|
||||
/// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using
|
||||
/// [`Lua::create_ser_userdata`]).
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
pub(crate) fn is_serializable(&self) -> bool {
|
||||
let lua = self.0.lua.lock();
|
||||
let is_serializable = || unsafe {
|
||||
@@ -1030,7 +1016,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())
|
||||
}
|
||||
}
|
||||
@@ -1063,7 +1049,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for AnyUserData {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
|
||||
where
|
||||
@@ -1094,8 +1080,8 @@ impl AnyUserData {
|
||||
/// [`IntoLua`] trait.
|
||||
///
|
||||
/// This function uses [`Lua::create_ser_any_userdata`] under the hood.
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
|
||||
WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
@@ -9,10 +9,10 @@ use crate::types::XRc;
|
||||
use super::lock::{RawLock, UserDataLock};
|
||||
use super::r#ref::{UserDataRef, UserDataRefMut};
|
||||
|
||||
#[cfg(all(feature = "serialize", not(feature = "send")))]
|
||||
#[cfg(all(feature = "serde", not(feature = "send")))]
|
||||
type DynSerialize = dyn erased_serde::Serialize;
|
||||
|
||||
#[cfg(all(feature = "serialize", feature = "send"))]
|
||||
#[cfg(all(feature = "serde", feature = "send"))]
|
||||
type DynSerialize = dyn erased_serde::Serialize + Send;
|
||||
|
||||
pub(crate) enum UserDataStorage<T> {
|
||||
@@ -24,7 +24,7 @@ pub(crate) enum UserDataStorage<T> {
|
||||
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
|
||||
pub(crate) enum UserDataVariant<T> {
|
||||
Default(XRc<UserDataCell<T>>),
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ impl<T> Clone for UserDataVariant<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Default(inner) => Self::Default(XRc::clone(inner)),
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ impl<T> UserDataVariant<T> {
|
||||
}
|
||||
Ok(match self {
|
||||
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Self::Serializable(inner, _) => unsafe {
|
||||
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
|
||||
*Box::from_raw(raw as *mut T)
|
||||
@@ -91,7 +91,7 @@ impl<T> UserDataVariant<T> {
|
||||
fn strong_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Default(inner) => XRc::strong_count(inner),
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Self::Serializable(inner, _) => XRc::strong_count(inner),
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ impl<T> UserDataVariant<T> {
|
||||
pub(super) fn raw_lock(&self) -> &RawLock {
|
||||
match self {
|
||||
Self::Default(inner) => &inner.raw_lock,
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Self::Serializable(inner, _) => &inner.raw_lock,
|
||||
}
|
||||
}
|
||||
@@ -109,13 +109,13 @@ impl<T> UserDataVariant<T> {
|
||||
pub(super) fn as_ptr(&self) -> *mut T {
|
||||
match self {
|
||||
Self::Default(inner) => inner.value.get(),
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for UserDataStorage<()> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
@@ -197,7 +197,7 @@ impl<T: 'static> UserDataStorage<T> {
|
||||
Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data)))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
#[inline(always)]
|
||||
pub(crate) fn new_ser(data: T) -> Self
|
||||
where
|
||||
@@ -209,7 +209,7 @@ impl<T: 'static> UserDataStorage<T> {
|
||||
Self::Owned(variant)
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_serializable(&self) -> bool {
|
||||
matches!(self, Self::Owned(UserDataVariant::Serializable(..)))
|
||||
|
||||
+23
-12
@@ -1,6 +1,7 @@
|
||||
use std::string::String as StdString;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::state::WeakLua;
|
||||
use crate::table::Table;
|
||||
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
|
||||
use crate::userdata::AnyUserData;
|
||||
@@ -8,21 +9,21 @@ use crate::value::Value;
|
||||
use crate::Function;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use futures_util::future::{self, Either, Future};
|
||||
use crate::function::AsyncCallFuture;
|
||||
|
||||
impl ObjectLike for AnyUserData {
|
||||
#[inline]
|
||||
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
|
||||
// `lua_gettable` method used under the hood can work with any Lua value
|
||||
// that has `__index` metamethod
|
||||
Table(self.0.copy()).get_protected(key)
|
||||
Table(self.0.clone()).get_protected(key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
|
||||
// `lua_settable` method used under the hood can work with any Lua value
|
||||
// that has `__newindex` metamethod
|
||||
Table(self.0.copy()).set_protected(key, value)
|
||||
Table(self.0.clone()).set_protected(key, value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -30,16 +31,16 @@ impl ObjectLike for AnyUserData {
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call(args)
|
||||
Function(self.0.clone()).call(args)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call_async(args)
|
||||
Function(self.0.clone()).call_async(args)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -51,7 +52,7 @@ impl ObjectLike for AnyUserData {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
@@ -72,22 +73,32 @@ impl ObjectLike for AnyUserData {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
match self.get(name) {
|
||||
Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
|
||||
Ok(Value::Function(func)) => func.call_async(args),
|
||||
Ok(val) => {
|
||||
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
|
||||
Either::Right(future::ready(Err(Error::RuntimeError(msg))))
|
||||
AsyncCallFuture::error(Error::RuntimeError(msg))
|
||||
}
|
||||
Err(err) => Either::Right(future::ready(Err(err))),
|
||||
Err(err) => AsyncCallFuture::error(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_string(&self) -> Result<StdString> {
|
||||
Value::UserData(AnyUserData(self.0.copy())).to_string()
|
||||
Value::UserData(self.clone()).to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_value(&self) -> Value {
|
||||
Value::UserData(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn weak_lua(&self) -> &WeakLua {
|
||||
&self.0.lua
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
use std::any::TypeId;
|
||||
use std::any::{type_name, TypeId};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::raw::c_int;
|
||||
use std::{fmt, mem};
|
||||
@@ -7,7 +7,7 @@ use crate::error::{Error, Result};
|
||||
use crate::state::{Lua, RawLua};
|
||||
use crate::traits::FromLua;
|
||||
use crate::userdata::AnyUserData;
|
||||
use crate::util::{get_userdata, short_type_name};
|
||||
use crate::util::get_userdata;
|
||||
use crate::value::Value;
|
||||
|
||||
use super::cell::{UserDataStorage, UserDataVariant};
|
||||
@@ -63,7 +63,7 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
|
||||
|
||||
#[inline]
|
||||
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
|
||||
let guard = if !cfg!(feature = "send") || is_sync::<T>() {
|
||||
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
|
||||
variant.raw_lock().try_lock_shared_guarded()
|
||||
} else {
|
||||
variant.raw_lock().try_lock_exclusive_guarded()
|
||||
@@ -449,7 +449,7 @@ fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
|
||||
_ => Err(Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "userdata".to_string(),
|
||||
message: Some(format!("expected userdata of type {}", short_type_name::<T>())),
|
||||
message: Some(format!("expected userdata of type {}", type_name::<T>())),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
+64
-42
@@ -56,6 +56,9 @@ pub(crate) struct RawUserDataRegistry {
|
||||
pub(crate) destructor: ffi::lua_CFunction,
|
||||
pub(crate) type_id: Option<TypeId>,
|
||||
pub(crate) type_name: StdString,
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
pub(crate) enable_namecall: bool,
|
||||
}
|
||||
|
||||
impl UserDataType {
|
||||
@@ -100,6 +103,8 @@ impl<T> UserDataRegistry<T> {
|
||||
destructor: super::util::destroy_userdata_storage::<T>,
|
||||
type_id: r#type.type_id(),
|
||||
type_name: short_type_name::<T>(),
|
||||
#[cfg(feature = "luau")]
|
||||
enable_namecall: false,
|
||||
};
|
||||
|
||||
UserDataRegistry {
|
||||
@@ -110,6 +115,23 @@ impl<T> UserDataRegistry<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables support for the namecall optimization in Luau.
|
||||
///
|
||||
/// This enables methods resolution optimization in Luau for complex userdata types with methods
|
||||
/// and field getters. When enabled, Luau will use a faster lookup path for method calls when a
|
||||
/// specific syntax is used (e.g. `obj:method()`.
|
||||
///
|
||||
/// This optimization does not play well with async methods, custom `__index` metamethod and
|
||||
/// field getters as functions. So, it is disabled by default.
|
||||
///
|
||||
/// Use with caution.
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "luau")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn enable_namecall(&mut self) {
|
||||
self.raw.enable_namecall = true;
|
||||
}
|
||||
|
||||
fn box_method<M, A, R>(&self, name: &str, method: M) -> Callback
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
@@ -365,101 +387,101 @@ fn get_function_name<T>(name: &str) -> StdString {
|
||||
}
|
||||
|
||||
impl<T> UserDataFields<T> for UserDataRegistry<T> {
|
||||
fn add_field<V>(&mut self, name: impl ToString, value: V)
|
||||
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
self.raw.fields.push((name, value.into_lua(self.lua.lua())));
|
||||
}
|
||||
|
||||
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
|
||||
R: IntoLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method(&name, move |lua, data, ()| method(lua, data));
|
||||
self.raw.field_getters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
|
||||
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
|
||||
A: FromLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method_mut(&name, method);
|
||||
self.raw.field_setters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
|
||||
R: IntoLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function(&name, function);
|
||||
self.raw.field_getters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_field_function_set<F, A>(&mut self, name: impl ToString, mut function: F)
|
||||
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, mut function: F)
|
||||
where
|
||||
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
|
||||
A: FromLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val));
|
||||
self.raw.field_setters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
|
||||
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static,
|
||||
{
|
||||
let lua = self.lua.lua();
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua));
|
||||
self.raw.meta_fields.push((name, field));
|
||||
}
|
||||
|
||||
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F)
|
||||
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
|
||||
where
|
||||
F: FnOnce(&Lua) -> Result<R> + 'static,
|
||||
R: IntoLua,
|
||||
{
|
||||
let lua = self.lua.lua();
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua)));
|
||||
self.raw.meta_fields.push((name, field));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method(&name, method);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method_mut(&name, method);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -467,13 +489,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method(&name, method);
|
||||
self.raw.async_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -481,70 +503,70 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method_mut(&name, method);
|
||||
self.raw.async_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function(&name, function);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function_mut(&name, function);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
FR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_function(&name, function);
|
||||
self.raw.async_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method(&name, method);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method_mut(&name, method);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
|
||||
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -552,13 +574,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method(&name, method);
|
||||
self.raw.async_meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
|
||||
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -566,42 +588,42 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method_mut(&name, method);
|
||||
self.raw.async_meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function(&name, function);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function_mut(&name, function);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
|
||||
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
FR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_function(&name, function);
|
||||
self.raw.async_meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
+54
-3
@@ -4,8 +4,11 @@ use std::marker::PhantomData;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::UserDataStorage;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::types::CallbackPtr;
|
||||
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
|
||||
|
||||
// This is a trick to check if a type is `Sync` or not.
|
||||
@@ -244,6 +247,7 @@ pub(crate) unsafe fn init_userdata_metatable(
|
||||
field_getters: Option<c_int>,
|
||||
field_setters: Option<c_int>,
|
||||
methods: Option<c_int>,
|
||||
_methods_map: Option<FxHashMap<Vec<u8>, CallbackPtr>>, // Used only in Luau for `__namecall`
|
||||
) -> Result<()> {
|
||||
if field_getters.is_some() || methods.is_some() {
|
||||
// Push `__index` generator function
|
||||
@@ -267,6 +271,13 @@ pub(crate) unsafe fn init_userdata_metatable(
|
||||
}
|
||||
|
||||
rawset_field(state, metatable, "__index")?;
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
if let Some(methods_map) = _methods_map {
|
||||
// In Luau we can speedup method calls by providing a dedicated `__namecall` metamethod
|
||||
push_userdata_metatable_namecall(state, methods_map)?;
|
||||
rawset_field(state, metatable, "__namecall")?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(field_setters) = field_setters {
|
||||
@@ -425,6 +436,36 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn push_userdata_metatable_namecall(
|
||||
state: *mut ffi::lua_State,
|
||||
methods_map: FxHashMap<Vec<u8>, CallbackPtr>,
|
||||
) -> Result<()> {
|
||||
unsafe extern "C-unwind" fn namecall(state: *mut ffi::lua_State) -> c_int {
|
||||
let name = ffi::lua_namecallatom(state, ptr::null_mut());
|
||||
if name.is_null() {
|
||||
ffi::luaL_error(state, cstr!("attempt to call an unknown method"));
|
||||
}
|
||||
let name_cs = std::ffi::CStr::from_ptr(name);
|
||||
let methods_map = get_userdata::<FxHashMap<Vec<u8>, CallbackPtr>>(state, ffi::lua_upvalueindex(1));
|
||||
let callback_ptr = match (*methods_map).get(name_cs.to_bytes()) {
|
||||
Some(ptr) => *ptr,
|
||||
#[rustfmt::skip]
|
||||
None => ffi::luaL_error(state, cstr!("attempt to call an unknown method '%s'"), name),
|
||||
};
|
||||
crate::state::callback_error_ext(state, ptr::null_mut(), true, |extra, nargs| {
|
||||
let rawlua = (*extra).raw_lua();
|
||||
(*callback_ptr)(rawlua, nargs)
|
||||
})
|
||||
}
|
||||
|
||||
// Automatic destructor is provided for any Luau userdata
|
||||
crate::util::push_userdata(state, methods_map, true)?;
|
||||
protect_lua!(state, 1, 1, |state| {
|
||||
ffi::lua_pushcclosured(state, namecall, cstr!("__namecall"), 1);
|
||||
})
|
||||
}
|
||||
|
||||
// This method is called by Lua GC when it's time to collect the userdata.
|
||||
//
|
||||
// This method is usually used to collect internal userdata.
|
||||
@@ -437,17 +478,27 @@ pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(state: *mut ffi::lua_
|
||||
|
||||
// This method is called by Luau GC when it's time to collect the userdata.
|
||||
#[cfg(feature = "luau")]
|
||||
pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(ud: *mut std::os::raw::c_void) {
|
||||
pub(crate) unsafe extern "C" fn collect_userdata<T>(
|
||||
state: *mut ffi::lua_State,
|
||||
ud: *mut std::os::raw::c_void,
|
||||
) {
|
||||
// Almost none Lua operations are allowed when destructor is running,
|
||||
// so we need to set a flag to prevent calling any Lua functions
|
||||
let extra = (*ffi::lua_callbacks(state)).userdata as *mut crate::state::ExtraData;
|
||||
(*extra).running_gc = true;
|
||||
// Luau does not support _any_ panics in destructors (they are declared as "C", NOT as "C-unwind"),
|
||||
// so any panics will trigger `abort()`.
|
||||
ptr::drop_in_place(ud as *mut T);
|
||||
(*extra).running_gc = false;
|
||||
}
|
||||
|
||||
// This method can be called by user or Lua GC to destroy the userdata.
|
||||
// It checks if the userdata is safe to destroy and sets the "destroyed" metatable
|
||||
// to prevent further GC collection.
|
||||
pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage<T>(state: *mut ffi::lua_State) -> c_int {
|
||||
let ud = get_userdata::<UserDataStorage<T>>(state, -1);
|
||||
let ud = get_userdata::<UserDataStorage<T>>(state, 1);
|
||||
if (*ud).is_safe_to_destroy() {
|
||||
take_userdata::<UserDataStorage<T>>(state);
|
||||
take_userdata::<UserDataStorage<T>>(state, 1);
|
||||
ffi::lua_pushboolean(state, 1);
|
||||
} else {
|
||||
ffi::lua_pushboolean(state, 0);
|
||||
|
||||
+8
-2
@@ -315,7 +315,7 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
|
||||
let _ = write!(&mut (*err_buf), "{error}");
|
||||
Ok(err_buf)
|
||||
}
|
||||
Some(WrappedFailure::Panic(Some(ref panic))) => {
|
||||
Some(WrappedFailure::Panic(Some(panic))) => {
|
||||
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
|
||||
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
|
||||
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
|
||||
@@ -349,7 +349,11 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
|
||||
state,
|
||||
Some(|state| {
|
||||
ffi::lua_pushcfunction(state, error_tostring);
|
||||
rawset_field(state, -2, "__tostring")
|
||||
ffi::lua_setfield(state, -2, cstr!("__tostring"));
|
||||
|
||||
// This is mostly for Luau typeof() function
|
||||
ffi::lua_pushstring(state, cstr!("error"));
|
||||
ffi::lua_setfield(state, -2, cstr!("__type"));
|
||||
}),
|
||||
)?;
|
||||
|
||||
@@ -398,6 +402,8 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
|
||||
"__ipairs",
|
||||
#[cfg(feature = "luau")]
|
||||
"__iter",
|
||||
#[cfg(feature = "luau")]
|
||||
"__namecall",
|
||||
#[cfg(feature = "lua54")]
|
||||
"__close",
|
||||
] {
|
||||
|
||||
+9
-9
@@ -9,6 +9,7 @@ pub(crate) use error::{
|
||||
error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call,
|
||||
protect_lua_closure, WrappedFailure,
|
||||
};
|
||||
pub(crate) use path::parse_path as parse_lookup_path;
|
||||
pub(crate) use short_names::short_type_name;
|
||||
pub(crate) use types::TypeKey;
|
||||
pub(crate) use userdata::{
|
||||
@@ -88,7 +89,7 @@ impl Drop for StackGuard {
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
|
||||
// Always use protected mode if the string is too long
|
||||
if protect || s.len() > (1 << 30) {
|
||||
if protect || s.len() >= const { 1 << 30 } {
|
||||
protect_lua!(state, 0, 1, |state| {
|
||||
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
|
||||
})
|
||||
@@ -101,15 +102,13 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect:
|
||||
// Uses 3 stack spaces (when protect), does not call checkstack.
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> {
|
||||
let data = if protect {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))?
|
||||
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, size: usize, protect: bool) -> Result<*mut u8> {
|
||||
let data = if protect || size > const { 1024 * 1024 * 1024 } {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, size))?
|
||||
} else {
|
||||
ffi::lua_newbuffer(state, b.len())
|
||||
ffi::lua_newbuffer(state, size)
|
||||
};
|
||||
let buf = slice::from_raw_parts_mut(data as *mut u8, b.len());
|
||||
buf.copy_from_slice(b);
|
||||
Ok(())
|
||||
Ok(data as *mut u8)
|
||||
}
|
||||
|
||||
// Uses 3 stack spaces, does not call checkstack.
|
||||
@@ -122,7 +121,7 @@ pub(crate) unsafe fn push_table(
|
||||
) -> Result<()> {
|
||||
let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
|
||||
let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
|
||||
if protect {
|
||||
if protect || narr >= const { 1 << 26 } || nrec >= const { 1 << 26 } {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
|
||||
} else {
|
||||
ffi::lua_createtable(state, narr, nrec);
|
||||
@@ -329,6 +328,7 @@ pub(crate) fn linenumber_to_usize(n: c_int) -> Option<usize> {
|
||||
}
|
||||
|
||||
mod error;
|
||||
mod path;
|
||||
mod short_names;
|
||||
mod types;
|
||||
mod userdata;
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::iter::Peekable;
|
||||
use std::str::CharIndices;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::state::Lua;
|
||||
use crate::traits::IntoLua;
|
||||
use crate::types::Integer;
|
||||
use crate::value::Value;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PathKey<'a> {
|
||||
Str(Cow<'a, str>),
|
||||
Int(Integer),
|
||||
}
|
||||
|
||||
impl fmt::Display for PathKey<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
PathKey::Str(s) => write!(f, "{}", s),
|
||||
PathKey::Int(i) => write!(f, "{}", i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for PathKey<'_> {
|
||||
fn into_lua(self, lua: &Lua) -> Result<Value> {
|
||||
match self {
|
||||
PathKey::Str(s) => Ok(Value::String(lua.create_string(s.as_ref())?)),
|
||||
PathKey::Int(i) => Ok(Value::Integer(i)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a path like `a.b[3]?.c["d"]` into segments of `(key, safe_nil)`.
|
||||
pub(crate) fn parse_path<'a>(path: &'a str) -> Result<Vec<(PathKey<'a>, bool)>> {
|
||||
fn read_ident<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> (Cow<'a, str>, bool) {
|
||||
let mut safe_nil = false;
|
||||
let start = chars.peek().map(|&(i, _)| i).unwrap_or(path.len());
|
||||
let mut end = start;
|
||||
while let Some(&(pos, c)) = chars.peek() {
|
||||
if c == '.' || c == '?' || c.is_ascii_whitespace() || c == '[' {
|
||||
if c == '?' {
|
||||
safe_nil = true;
|
||||
chars.next(); // consume '?'
|
||||
}
|
||||
break;
|
||||
}
|
||||
end = pos + c.len_utf8();
|
||||
chars.next();
|
||||
}
|
||||
(Cow::Borrowed(&path[start..end]), safe_nil)
|
||||
}
|
||||
|
||||
let mut segments = Vec::new();
|
||||
let mut chars = path.char_indices().peekable();
|
||||
while let Some(&(pos, next)) = chars.peek() {
|
||||
match next {
|
||||
'.' => {
|
||||
// Dot notation: identifier
|
||||
chars.next();
|
||||
let (key, safe_nil) = read_ident(path, &mut chars);
|
||||
if key.is_empty() {
|
||||
return Err(Error::runtime(format!("empty key in path at position {pos}")));
|
||||
}
|
||||
segments.push((PathKey::Str(key), safe_nil));
|
||||
}
|
||||
'[' => {
|
||||
// Bracket notation: either integer or quoted string
|
||||
chars.next();
|
||||
let key = match chars.peek() {
|
||||
Some(&(pos, c @ '0'..='9' | c @ '-')) => {
|
||||
// Integer key
|
||||
let negative = c == '-';
|
||||
if negative {
|
||||
chars.next(); // consume '-'
|
||||
}
|
||||
let mut num: Option<Integer> = None;
|
||||
while let Some(&(_, c @ '0'..='9')) = chars.peek() {
|
||||
let new_num = num
|
||||
.unwrap_or(0)
|
||||
.checked_mul(10)
|
||||
.and_then(|n| n.checked_add((c as u8 - b'0') as Integer))
|
||||
.ok_or_else(|| {
|
||||
Error::runtime(format!("integer overflow in path at position {pos}"))
|
||||
})?;
|
||||
num = Some(new_num);
|
||||
chars.next(); // consume digit
|
||||
}
|
||||
match num {
|
||||
Some(n) if negative => PathKey::Int(-n),
|
||||
Some(n) => PathKey::Int(n),
|
||||
None => {
|
||||
let err = format!("invalid integer in path at position {pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some((_, '\'' | '"')) => {
|
||||
// Quoted string
|
||||
PathKey::Str(unquote_string(path, &mut chars)?)
|
||||
}
|
||||
Some((_, ']')) => {
|
||||
return Err(Error::runtime(format!("empty key in path at position {pos}")));
|
||||
}
|
||||
Some((pos, c)) => {
|
||||
let err = format!("unexpected character '{c}' in path at position {pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
None => {
|
||||
return Err(Error::runtime("unexpected end of path"));
|
||||
}
|
||||
};
|
||||
// Expect closing bracket
|
||||
let mut safe_nil = false;
|
||||
match chars.next() {
|
||||
Some((_, ']')) => {
|
||||
// Check for optional safe-nil operator
|
||||
if let Some(&(_, '?')) = chars.peek() {
|
||||
safe_nil = true;
|
||||
chars.next(); // consume '?'
|
||||
}
|
||||
}
|
||||
Some((pos, c)) => {
|
||||
let err = format!("expected ']' in path at position {pos}, found '{c}'");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
None => {
|
||||
return Err(Error::runtime("unexpected end of path"));
|
||||
}
|
||||
}
|
||||
segments.push((key, safe_nil));
|
||||
}
|
||||
c if c.is_ascii_whitespace() => {
|
||||
chars.next(); // Skip whitespace
|
||||
}
|
||||
_ if segments.is_empty() => {
|
||||
// First segment without dot/bracket notation
|
||||
let (key_cow, safe_nil) = read_ident(path, &mut chars);
|
||||
if key_cow.is_empty() {
|
||||
return Err(Error::runtime(format!("empty key in path at position {pos}")));
|
||||
}
|
||||
segments.push((PathKey::Str(key_cow), safe_nil));
|
||||
}
|
||||
c => {
|
||||
let err = format!("unexpected character '{c}' in path at position {pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(segments)
|
||||
}
|
||||
|
||||
fn unquote_string<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> Result<Cow<'a, str>> {
|
||||
let (start_pos, first_quote) = chars.next().unwrap();
|
||||
let mut result = String::new();
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some((pos, '\\')) => {
|
||||
if result.is_empty() {
|
||||
// First escape found, copy everything up to this point
|
||||
result.push_str(&path[start_pos + 1..pos]);
|
||||
}
|
||||
match chars.next() {
|
||||
Some((_, '\\')) => result.push('\\'),
|
||||
Some((_, '"')) => result.push('"'),
|
||||
Some((_, '\'')) => result.push('\''),
|
||||
Some((_, other)) => {
|
||||
result.push('\\');
|
||||
result.push(other);
|
||||
}
|
||||
None => continue, // will be handled by outer loop
|
||||
}
|
||||
}
|
||||
Some((pos, c)) if c == first_quote => {
|
||||
if !result.is_empty() {
|
||||
return Ok(Cow::Owned(result));
|
||||
}
|
||||
// No escapes, return borrowed slice
|
||||
return Ok(Cow::Borrowed(&path[start_pos + 1..pos]));
|
||||
}
|
||||
Some((_, c)) => {
|
||||
if !result.is_empty() {
|
||||
result.push(c);
|
||||
}
|
||||
// If no escapes yet, continue tracking for potential borrowed slice
|
||||
}
|
||||
None => {
|
||||
let err = format!("unexpected end of string at position {start_pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_path, PathKey};
|
||||
|
||||
#[test]
|
||||
fn test_parse_path() {
|
||||
// Test valid paths
|
||||
let path = parse_path("a.b[3]?.c['d']").unwrap();
|
||||
assert_eq!(path.len(), 5);
|
||||
assert!(matches!(path[0], (PathKey::Str(ref s), false) if s == "a"));
|
||||
assert!(matches!(path[1], (PathKey::Str(ref s), false) if s == "b"));
|
||||
assert!(matches!(path[2], (PathKey::Int(3), true)));
|
||||
assert!(matches!(path[3], (PathKey::Str(ref s), false) if s == "c"));
|
||||
assert!(matches!(path[4], (PathKey::Str(ref s), false) if s == "d"));
|
||||
|
||||
// Test empty path
|
||||
let path = parse_path("").unwrap();
|
||||
assert_eq!(path.len(), 0);
|
||||
let path = parse_path(" ").unwrap();
|
||||
assert_eq!(path.len(), 0);
|
||||
|
||||
// Test invalid dot syntax
|
||||
let err = parse_path("a..b").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: empty key in path at position 1");
|
||||
let err = parse_path("a.b.").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: empty key in path at position 3");
|
||||
|
||||
// Test invalid bracket syntax
|
||||
let err = parse_path("a[unclosed").unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err,
|
||||
"runtime error: unexpected character 'u' in path at position 2"
|
||||
);
|
||||
let err = parse_path("a[]").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: empty key in path at position 1");
|
||||
let err = parse_path(r#"a["unclosed"#).unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: unexpected end of string at position 2");
|
||||
let err = parse_path(r#"a["#).unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: unexpected end of path");
|
||||
let err = parse_path(r#"a[123"#).unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: unexpected end of path");
|
||||
let err = parse_path(r#"a['bla'123"#).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err,
|
||||
"runtime error: expected ']' in path at position 7, found '1'"
|
||||
);
|
||||
let err = parse_path(r#"a["bla"]x"#).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err,
|
||||
"runtime error: unexpected character 'x' in path at position 8"
|
||||
);
|
||||
|
||||
// Test bad integers
|
||||
let err = parse_path("a[99999999999999999999]").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: integer overflow in path at position 2");
|
||||
let err = parse_path("a[-]").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: invalid integer in path at position 2");
|
||||
}
|
||||
}
|
||||
+18
-8
@@ -1,6 +1,6 @@
|
||||
//! Mostly copied from [bevy_utils]
|
||||
//! Inspired by bevy's [disqualified]
|
||||
//!
|
||||
//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
|
||||
//! [disqualified]: https://github.com/bevyengine/disqualified/blob/main/src/short_name.rs
|
||||
|
||||
use std::any::type_name;
|
||||
|
||||
@@ -23,8 +23,7 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
while index < end_of_string {
|
||||
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
|
||||
|
||||
// Collapse everything up to the next special character,
|
||||
// then skip over it
|
||||
// Collapse everything up to the next special character, then skip over it
|
||||
if let Some(special_character_index) =
|
||||
rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
|
||||
{
|
||||
@@ -32,11 +31,16 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
parsed_name += collapse_type_name(segment_to_collapse);
|
||||
// Insert the special character
|
||||
let special_character = &rest_of_string[special_character_index..=special_character_index];
|
||||
parsed_name.push_str(special_character);
|
||||
parsed_name += special_character;
|
||||
|
||||
// Remove lifetimes like <'_> or <'_, '_, ...>
|
||||
if parsed_name.ends_with("<'_>") || parsed_name.ends_with("<'_, ") {
|
||||
_ = parsed_name.split_off(parsed_name.len() - 4);
|
||||
}
|
||||
|
||||
match special_character {
|
||||
">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => {
|
||||
parsed_name.push_str("::");
|
||||
parsed_name += "::";
|
||||
// Move the index past the "::"
|
||||
index += special_character_index + 3;
|
||||
}
|
||||
@@ -53,14 +57,18 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn collapse_type_name(string: &str) -> &str {
|
||||
string.rsplit("::").next().unwrap()
|
||||
fn collapse_type_name(segment: &str) -> &str {
|
||||
segment.rsplit("::").next().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::short_type_name;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
struct MyData<'a, 'b>(PhantomData<&'a &'b ()>);
|
||||
struct MyDataT<'a, T>(PhantomData<&'a T>);
|
||||
|
||||
#[test]
|
||||
fn tests() {
|
||||
@@ -73,5 +81,7 @@ mod tests {
|
||||
"HashMap<String, Option<[i32; 3]>>"
|
||||
);
|
||||
assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32");
|
||||
assert_eq!(short_type_name::<MyDataT<&str>>(), "MyDataT<&str>");
|
||||
assert_eq!(short_type_name::<(&MyData, [MyData])>(), "(MyData, [MyData])");
|
||||
}
|
||||
}
|
||||
|
||||
+15
-17
@@ -10,73 +10,71 @@ pub(crate) trait TypeKey: Any {
|
||||
fn type_key() -> *const c_void;
|
||||
}
|
||||
|
||||
static STRING_TYPE_KEY: u8 = 0;
|
||||
|
||||
impl TypeKey for String {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static STRING_TYPE_KEY: u8 = 0;
|
||||
&STRING_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
static CALLBACK_TYPE_KEY: u8 = 0;
|
||||
|
||||
impl TypeKey for Callback {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static CALLBACK_TYPE_KEY: u8 = 0;
|
||||
&CALLBACK_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
|
||||
|
||||
impl TypeKey for CallbackUpvalue {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
|
||||
&CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
static ASYNC_CALLBACK_TYPE_KEY: u8 = 0;
|
||||
#[cfg(not(feature = "luau"))]
|
||||
impl TypeKey for crate::types::HookCallback {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static HOOK_CALLBACK_TYPE_KEY: u8 = 0;
|
||||
&HOOK_CALLBACK_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl TypeKey for AsyncCallback {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static ASYNC_CALLBACK_TYPE_KEY: u8 = 0;
|
||||
&ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl TypeKey for AsyncCallbackUpvalue {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
|
||||
&ASYNC_CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl TypeKey for AsyncPollUpvalue {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0;
|
||||
&ASYNC_POLL_UPVALUE_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
static WAKER_TYPE_KEY: u8 = 0;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl TypeKey for Option<std::task::Waker> {
|
||||
#[inline(always)]
|
||||
fn type_key() -> *const c_void {
|
||||
static WAKER_TYPE_KEY: u8 = 0;
|
||||
&WAKER_TYPE_KEY as *const u8 as *const c_void
|
||||
}
|
||||
}
|
||||
|
||||
+30
-24
@@ -15,20 +15,23 @@ pub(crate) unsafe fn push_internal_userdata<T: TypeKey>(
|
||||
#[cfg(not(feature = "luau"))]
|
||||
let ud_ptr = if protect {
|
||||
protect_lua!(state, 0, 1, move |state| {
|
||||
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
|
||||
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
|
||||
ptr::write(ud_ptr, t);
|
||||
ud_ptr
|
||||
})?
|
||||
} else {
|
||||
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
|
||||
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
|
||||
ptr::write(ud_ptr, t);
|
||||
ud_ptr
|
||||
};
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
let ud_ptr = if protect {
|
||||
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::<T>(state))?
|
||||
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::<T>(state, t))?
|
||||
} else {
|
||||
ffi::lua_newuserdata_t::<T>(state)
|
||||
ffi::lua_newuserdata_t::<T>(state, t)
|
||||
};
|
||||
|
||||
ptr::write(ud_ptr, t);
|
||||
get_internal_metatable::<T>(state);
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
Ok(ud_ptr)
|
||||
@@ -44,7 +47,7 @@ pub(crate) unsafe fn get_internal_metatable<T: TypeKey>(state: *mut ffi::lua_Sta
|
||||
// Uses 6 stack spaces and calls checkstack.
|
||||
pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
|
||||
state: *mut ffi::lua_State,
|
||||
customize_fn: Option<fn(*mut ffi::lua_State) -> Result<()>>,
|
||||
customize_fn: Option<fn(*mut ffi::lua_State)>,
|
||||
) -> Result<()> {
|
||||
check_stack(state, 6)?;
|
||||
|
||||
@@ -59,18 +62,18 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
|
||||
ffi::lua_pushboolean(state, 0);
|
||||
rawset_field(state, -2, "__metatable")?;
|
||||
|
||||
if let Some(f) = customize_fn {
|
||||
f(state)?;
|
||||
}
|
||||
|
||||
protect_lua!(state, 1, 0, |state| {
|
||||
if let Some(f) = customize_fn {
|
||||
f(state);
|
||||
}
|
||||
|
||||
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Uses 2 stack spaces, does not call checkstack
|
||||
// Uses up to 1 stack space, does not call `checkstack`
|
||||
pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
|
||||
state: *mut ffi::lua_State,
|
||||
index: c_int,
|
||||
@@ -138,24 +141,27 @@ pub(crate) unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -
|
||||
ud
|
||||
}
|
||||
|
||||
// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua
|
||||
// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been
|
||||
// previously invalidated, and this method does not check for this.
|
||||
// Uses 1 extra stack space and does not call checkstack.
|
||||
pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
|
||||
// We set the metatable of userdata on __gc to a special table with no __gc method and with
|
||||
// metamethods that trigger an error on access. We do this so that it will not be double
|
||||
// dropped, and also so that it cannot be used or identified as any particular userdata type
|
||||
// after the first call to __gc.
|
||||
/// Unwraps `T` from the Lua userdata and invalidating it by setting the special "destructed"
|
||||
/// metatable.
|
||||
///
|
||||
/// This method does not check that userdata is of type `T` and was not previously invalidated.
|
||||
///
|
||||
/// Uses 1 extra stack space, does not call checkstack.
|
||||
pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State, idx: c_int) -> T {
|
||||
#[rustfmt::skip]
|
||||
let idx = if idx < 0 { ffi::lua_absindex(state, idx) } else { idx };
|
||||
|
||||
// Update the metatable of this userdata to a special one with no `__gc` method and with
|
||||
// metamethods that trigger an error on access.
|
||||
// We do this so that it will not be double dropped or used after being dropped.
|
||||
get_destructed_userdata_metatable(state);
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
let ud = get_userdata::<T>(state, -1);
|
||||
ffi::lua_setmetatable(state, idx);
|
||||
let ud = get_userdata::<T>(state, idx);
|
||||
|
||||
// Update userdata tag to disable destructor and mark as destructed
|
||||
#[cfg(feature = "luau")]
|
||||
ffi::lua_setuserdatatag(state, -1, 1);
|
||||
ffi::lua_setuserdatatag(state, idx, 1);
|
||||
|
||||
ffi::lua_pop(state, 1);
|
||||
ptr::read(ud)
|
||||
}
|
||||
|
||||
|
||||
+39
-23
@@ -15,7 +15,7 @@ use crate::types::{Integer, LightUserData, Number, ValueRef};
|
||||
use crate::userdata::AnyUserData;
|
||||
use crate::util::{check_stack, StackGuard};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
use {
|
||||
crate::table::SerializableTable,
|
||||
rustc_hash::FxHashSet,
|
||||
@@ -28,9 +28,10 @@ use {
|
||||
/// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types
|
||||
/// into the internal Lua state. It is a logic error to mix handle types between separate
|
||||
/// `Lua` instances, and doing so will result in a panic.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub enum Value {
|
||||
/// The Lua value `nil`.
|
||||
#[default]
|
||||
Nil,
|
||||
/// The Lua value `true` or `false`.
|
||||
Boolean(bool),
|
||||
@@ -272,7 +273,10 @@ impl Value {
|
||||
/// If the value is a Lua [`Integer`], try to convert it to `i64` or return `None` otherwise.
|
||||
#[inline]
|
||||
pub fn as_i64(&self) -> Option<i64> {
|
||||
self.as_integer().map(i64::from)
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
return self.as_integer();
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
return self.as_integer().map(i64::from);
|
||||
}
|
||||
|
||||
/// Cast the value to `u64`.
|
||||
@@ -353,14 +357,22 @@ impl Value {
|
||||
///
|
||||
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None`
|
||||
/// otherwise.
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> Option<BorrowedStr> {
|
||||
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
|
||||
self.as_string().and_then(|s| s.to_str().ok())
|
||||
}
|
||||
|
||||
/// Cast the value to [`StdString`].
|
||||
///
|
||||
/// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise.
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn as_string_lossy(&self) -> Option<StdString> {
|
||||
self.as_string().map(|s| s.to_string_lossy())
|
||||
@@ -478,10 +490,9 @@ impl Value {
|
||||
/// Wrap reference to this Value into [`SerializableValue`].
|
||||
///
|
||||
/// This allows customizing serialization behavior using serde.
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[doc(hidden)]
|
||||
pub fn to_serializable(&self) -> SerializableValue {
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
pub fn to_serializable(&self) -> SerializableValue<'_> {
|
||||
SerializableValue::new(self, Default::default(), None)
|
||||
}
|
||||
|
||||
@@ -569,12 +580,6 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Self::Nil
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Value {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
if fmt.alternate() {
|
||||
@@ -627,8 +632,8 @@ impl PartialEq for Value {
|
||||
}
|
||||
|
||||
/// A wrapped [`Value`] with customized serialization behavior.
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
pub struct SerializableValue<'a> {
|
||||
value: &'a Value,
|
||||
options: crate::serde::de::Options,
|
||||
@@ -636,7 +641,7 @@ pub struct SerializableValue<'a> {
|
||||
visited: Option<Rc<RefCell<FxHashSet<*const c_void>>>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for Value {
|
||||
#[inline]
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
|
||||
@@ -644,7 +649,7 @@ impl Serialize for Value {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'a> SerializableValue<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
@@ -673,7 +678,7 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **true**
|
||||
#[must_use]
|
||||
pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self {
|
||||
pub fn deny_unsupported_types(mut self, enabled: bool) -> Self {
|
||||
self.options.deny_unsupported_types = enabled;
|
||||
self
|
||||
}
|
||||
@@ -684,7 +689,7 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **true**
|
||||
#[must_use]
|
||||
pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self {
|
||||
pub fn deny_recursive_tables(mut self, enabled: bool) -> Self {
|
||||
self.options.deny_recursive_tables = enabled;
|
||||
self
|
||||
}
|
||||
@@ -693,7 +698,7 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **false**
|
||||
#[must_use]
|
||||
pub const fn sort_keys(mut self, enabled: bool) -> Self {
|
||||
pub fn sort_keys(mut self, enabled: bool) -> Self {
|
||||
self.options.sort_keys = enabled;
|
||||
self
|
||||
}
|
||||
@@ -702,13 +707,24 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **false**
|
||||
#[must_use]
|
||||
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
|
||||
pub fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
|
||||
self.options.encode_empty_tables_as_array = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// If true, enable detection of mixed tables.
|
||||
///
|
||||
/// A mixed table is a table that has both array-like and map-like entries or several borders.
|
||||
///
|
||||
/// Default: **false**
|
||||
#[must_use]
|
||||
pub fn detect_mixed_tables(mut self, enabled: bool) -> Self {
|
||||
self.options.detect_mixed_tables = enabled;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for SerializableValue<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
+2
-3
@@ -1,6 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::ser::{Serialize, SerializeTupleStruct, Serializer};
|
||||
|
||||
/// A Luau vector type.
|
||||
@@ -38,7 +38,6 @@ impl Vector {
|
||||
}
|
||||
|
||||
/// Creates a new vector with all components set to `0.0`.
|
||||
#[doc(hidden)]
|
||||
pub const fn zero() -> Self {
|
||||
Self([0.0; Self::SIZE])
|
||||
}
|
||||
@@ -66,7 +65,7 @@ impl Vector {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for Vector {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
|
||||
let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?;
|
||||
|
||||
+7
-7
@@ -1,23 +1,23 @@
|
||||
[lua54]
|
||||
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
|
||||
features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
|
||||
|
||||
[lua54_non_send]
|
||||
features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers"
|
||||
features = "lua54,vendored,async,serde,macros,anyhow,userdata-wrappers"
|
||||
|
||||
[lua54_with_memory_limit]
|
||||
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
|
||||
features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
|
||||
rustflags = "--cfg force_memory_limit"
|
||||
|
||||
[lua51]
|
||||
features = "lua51,vendored,async,send,serialize,macros"
|
||||
features = "lua51,vendored,async,send,serde,macros"
|
||||
|
||||
[lua51_with_memory_limit]
|
||||
features = "lua51,vendored,async,send,serialize,macros"
|
||||
features = "lua51,vendored,async,send,serde,macros"
|
||||
rustflags = "--cfg force_memory_limit"
|
||||
|
||||
[luau]
|
||||
features = "luau,async,send,serialize,macros"
|
||||
features = "luau,async,send,serde,macros"
|
||||
|
||||
[luau_with_memory_limit]
|
||||
features = "luau,async,send,serialize,macros"
|
||||
features = "luau,async,send,serde,macros"
|
||||
rustflags = "--cfg force_memory_limit"
|
||||
|
||||
+138
-6
@@ -8,8 +8,8 @@ use futures_util::stream::TryStreamExt;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use mlua::{
|
||||
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
|
||||
UserDataMethods, Value,
|
||||
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
|
||||
UserDataMethods, UserDataRef, Value,
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -273,7 +273,7 @@ async fn test_async_lua54_to_be_closed() -> Result<()> {
|
||||
|
||||
// Don't close by default when awaiting async threads
|
||||
let co = lua.create_thread(f.clone())?;
|
||||
let _ = co.clone().into_async::<()>(()).await;
|
||||
let _ = co.clone().into_async::<()>(())?.await;
|
||||
assert_eq!(globals.get::<usize>("close_count")?, 1);
|
||||
let _ = co.reset(f);
|
||||
assert_eq!(globals.get::<usize>("close_count")?, 2);
|
||||
@@ -300,7 +300,7 @@ async fn test_async_thread_stream() -> Result<()> {
|
||||
.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;
|
||||
@@ -325,7 +325,7 @@ async fn test_async_thread() -> Result<()> {
|
||||
}
|
||||
})?;
|
||||
|
||||
let res: String = lua.create_thread(f)?.into_async(()).await?;
|
||||
let res: String = lua.create_thread(f)?.into_async(())?.await?;
|
||||
|
||||
assert_eq!(res, "done");
|
||||
|
||||
@@ -386,7 +386,7 @@ async fn test_async_table_object_like() -> Result<()> {
|
||||
table.get::<i64>("val")
|
||||
})?,
|
||||
)?;
|
||||
table.set_metatable(Some(metatable));
|
||||
table.set_metatable(Some(metatable))?;
|
||||
assert_eq!(table.call_async::<i64>(()).await.unwrap(), 15);
|
||||
|
||||
match table.call_async_method::<()>("non_existent", ()).await {
|
||||
@@ -547,6 +547,7 @@ async fn test_async_thread_error() -> Result<()> {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_terminate() -> Result<()> {
|
||||
// Future captures `Lua` instance and dropped all together
|
||||
let mutex = Arc::new(Mutex::new(0u32));
|
||||
{
|
||||
let lua = Lua::new();
|
||||
@@ -565,5 +566,136 @@ async fn test_async_terminate() -> Result<()> {
|
||||
}
|
||||
assert!(mutex.try_lock().is_ok());
|
||||
|
||||
// Future is dropped, but `Lua` instance is still alive
|
||||
let lua = Lua::new();
|
||||
let func = lua.create_async_function(move |_, mutex: UserDataRef<Arc<Mutex<u32>>>| async move {
|
||||
let _guard = mutex.lock().await;
|
||||
sleep_ms(100).await;
|
||||
Ok(())
|
||||
})?;
|
||||
let mutex2 = lua.create_any_userdata(mutex.clone())?;
|
||||
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(mutex2)).await;
|
||||
assert!(mutex.try_lock().is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_task() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let delay = lua.create_function(|lua, (secs, f, args): (f32, Function, MultiValue)| {
|
||||
let thread = lua.create_thread(f)?;
|
||||
let thread2 = thread.clone().into_async::<()>(args)?;
|
||||
tokio::task::spawn_local(async move {
|
||||
tokio::time::sleep(Duration::from_secs_f32(secs)).await;
|
||||
_ = thread2.await;
|
||||
});
|
||||
Ok(thread)
|
||||
})?;
|
||||
|
||||
lua.globals().set("delay", delay)?;
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local
|
||||
.run_until(async {
|
||||
_ = lua
|
||||
.load("delay(0.1, function(msg) global_msg = msg end, 'done')")
|
||||
.exec_async()
|
||||
.await;
|
||||
})
|
||||
.await;
|
||||
local.await;
|
||||
assert_eq!(lua.globals().get::<String>("global_msg")?, "done");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_task_abort() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
|
||||
sleep_ms(n).await;
|
||||
Ok(())
|
||||
})?;
|
||||
lua.globals().set("sleep", sleep)?;
|
||||
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local
|
||||
.run_until(async {
|
||||
let lua2 = lua.clone();
|
||||
let jh = tokio::task::spawn_local(async move {
|
||||
lua2.load("sleep(200) result = 'done'")
|
||||
.exec_async()
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
sleep_ms(100).await; // Wait for the task to start
|
||||
jh.abort();
|
||||
})
|
||||
.await;
|
||||
local.await;
|
||||
assert_eq!(lua.globals().get::<Value>("result")?, Value::Nil);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(not(feature = "luau"))]
|
||||
async fn test_async_hook() -> Result<()> {
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
let lua = Lua::new();
|
||||
|
||||
static HOOK_CALLED: AtomicBool = AtomicBool::new(false);
|
||||
lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| {
|
||||
if !HOOK_CALLED.swap(true, Ordering::Relaxed) {
|
||||
#[cfg(any(feature = "lu53", feature = "lua54"))]
|
||||
return Ok(mlua::VmState::Yield);
|
||||
}
|
||||
Ok(mlua::VmState::Continue)
|
||||
})?;
|
||||
|
||||
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
|
||||
sleep_ms(n).await;
|
||||
Ok(())
|
||||
})?;
|
||||
lua.globals().set("sleep", sleep)?;
|
||||
|
||||
lua.load(r"sleep(100)").exec_async().await?;
|
||||
assert!(HOOK_CALLED.load(Ordering::Relaxed));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_yield_with() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let func = lua.create_async_function(|lua, (mut a, mut b): (i32, i32)| async move {
|
||||
let zero = lua.yield_with::<MultiValue>(()).await?;
|
||||
assert!(zero.is_empty());
|
||||
let one = lua.yield_with::<MultiValue>(a + b).await?;
|
||||
assert_eq!(one.len(), 1);
|
||||
|
||||
for _ in 0..3 {
|
||||
(a, b) = lua.yield_with((a + b, a * b)).await?;
|
||||
}
|
||||
Ok((0, 0))
|
||||
})?;
|
||||
|
||||
let thread = lua.create_thread(func)?;
|
||||
|
||||
let zero = thread.resume::<MultiValue>((2, 3))?; // function arguments
|
||||
assert!(zero.is_empty());
|
||||
let one = thread.resume::<i32>(())?; // value of "zero" is passed here
|
||||
assert_eq!(one, 5);
|
||||
|
||||
assert_eq!(thread.resume::<(i32, i32)>(1)?, (5, 6)); // value of "one" is passed here
|
||||
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
|
||||
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
|
||||
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
|
||||
assert_eq!(thread.status(), ThreadStatus::Finished);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+68
-2
@@ -1,5 +1,7 @@
|
||||
#![cfg(feature = "luau")]
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use mlua::{Lua, Result, Value};
|
||||
|
||||
#[test]
|
||||
@@ -41,7 +43,7 @@ fn test_buffer() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "range end index 14 out of range for slice of length 13")]
|
||||
#[should_panic(expected = "out of range for slice of length 13")]
|
||||
fn test_buffer_out_of_bounds_read() {
|
||||
let lua = Lua::new();
|
||||
let buf = lua.create_buffer(b"hello, world!").unwrap();
|
||||
@@ -49,9 +51,73 @@ fn test_buffer_out_of_bounds_read() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "range end index 16 out of range for slice of length 13")]
|
||||
#[should_panic(expected = "out of range for slice of length 13")]
|
||||
fn test_buffer_out_of_bounds_write() {
|
||||
let lua = Lua::new();
|
||||
let buf = lua.create_buffer(b"hello, world!").unwrap();
|
||||
buf.write_bytes(14, b"!!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_large_buffer() {
|
||||
let lua = Lua::new();
|
||||
let err = lua.create_buffer_with_capacity(1_073_741_824 + 1).unwrap_err(); // 1GB
|
||||
assert!(err.to_string().contains("memory allocation error"));
|
||||
|
||||
// Normal buffer is okay
|
||||
let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap();
|
||||
assert_eq!(buf.len(), 1024 * 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_cursor() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let mut cursor = lua.create_buffer(b"hello, world")?.cursor();
|
||||
|
||||
let mut data = Vec::new();
|
||||
cursor.read_to_end(&mut data)?;
|
||||
assert_eq!(data, b"hello, world");
|
||||
|
||||
// No more data to read
|
||||
let mut one = [0u8; 1];
|
||||
assert_eq!(cursor.read(&mut one)?, 0);
|
||||
|
||||
// Seek to start
|
||||
cursor.seek(SeekFrom::Start(0))?;
|
||||
cursor.read_exact(&mut one)?;
|
||||
assert_eq!(one, [b'h']);
|
||||
|
||||
// Seek to end -5
|
||||
cursor.seek(SeekFrom::End(-5))?;
|
||||
let mut five = [0u8; 5];
|
||||
cursor.read_exact(&mut five)?;
|
||||
assert_eq!(&five, b"world");
|
||||
|
||||
// Seek to current -1
|
||||
cursor.seek(SeekFrom::Current(-1))?;
|
||||
cursor.read_exact(&mut one)?;
|
||||
assert_eq!(one, [b'd']);
|
||||
|
||||
// Invalid seek
|
||||
assert!(cursor.seek(SeekFrom::Current(-100)).is_err());
|
||||
assert!(cursor.seek(SeekFrom::End(1)).is_err());
|
||||
|
||||
// Write data
|
||||
let buf = lua.create_buffer_with_capacity(100)?;
|
||||
cursor = buf.clone().cursor();
|
||||
|
||||
cursor.write_all(b"hello, ...")?;
|
||||
cursor.seek(SeekFrom::Current(-3))?;
|
||||
cursor.write_all(b"Rust!")?;
|
||||
|
||||
assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!");
|
||||
|
||||
// Writing beyond the end of the buffer does nothing
|
||||
cursor.seek(SeekFrom::End(0))?;
|
||||
assert_eq!(cursor.write(b".")?, 0);
|
||||
|
||||
// Flush is no-op
|
||||
cursor.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+30
-8
@@ -7,7 +7,7 @@ fn test_chunk_methods() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
#[cfg(unix)]
|
||||
assert!(lua.load("return 123").name().contains("tests/chunk.rs"));
|
||||
assert!(lua.load("return 123").name().starts_with("@tests/chunk.rs"));
|
||||
let chunk2 = lua.load("return 123").set_name("@new_name");
|
||||
assert_eq!(chunk2.name(), "@new_name");
|
||||
|
||||
@@ -114,20 +114,18 @@ fn test_chunk_macro() -> Result<()> {
|
||||
#[cfg(feature = "luau")]
|
||||
#[test]
|
||||
fn test_compiler() -> Result<()> {
|
||||
use std::vec;
|
||||
|
||||
let compiler = mlua::Compiler::new()
|
||||
.set_optimization_level(2)
|
||||
.set_debug_level(2)
|
||||
.set_type_info_level(1)
|
||||
.set_coverage_level(2)
|
||||
.set_vector_lib("vector")
|
||||
.set_vector_ctor("new")
|
||||
.set_vector_ctor("vector.new")
|
||||
.set_vector_type("vector")
|
||||
.set_mutable_globals(vec!["mutable_global".into()])
|
||||
.set_userdata_types(vec!["MyUserdata".into()]);
|
||||
.set_mutable_globals(["mutable_global"])
|
||||
.set_userdata_types(["MyUserdata"])
|
||||
.set_disabled_builtins(["tostring"]);
|
||||
|
||||
assert!(compiler.compile("return vector.new(1, 2, 3)").is_ok());
|
||||
assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok());
|
||||
|
||||
// Error
|
||||
match compiler.compile("%") {
|
||||
@@ -140,6 +138,30 @@ fn test_compiler() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
#[test]
|
||||
fn test_compiler_library_constants() {
|
||||
use mlua::{Compiler, Vector};
|
||||
|
||||
let compiler = Compiler::new()
|
||||
.set_optimization_level(2)
|
||||
.add_library_constant("mylib.const_bool", true)
|
||||
.add_library_constant("mylib.const_num", 123.0)
|
||||
.add_library_constant("mylib.const_vec", Vector::zero())
|
||||
.add_library_constant("mylib.const_str", "value1");
|
||||
|
||||
let lua = Lua::new();
|
||||
lua.set_compiler(compiler);
|
||||
let const_bool = lua.load("return mylib.const_bool").eval::<bool>().unwrap();
|
||||
assert_eq!(const_bool, true);
|
||||
let const_num = lua.load("return mylib.const_num").eval::<f64>().unwrap();
|
||||
assert_eq!(const_num, 123.0);
|
||||
let const_vec = lua.load("return mylib.const_vec").eval::<Vector>().unwrap();
|
||||
assert_eq!(const_vec, Vector::zero());
|
||||
let const_str = lua.load("return mylib.const_str").eval::<String>();
|
||||
assert_eq!(const_str.unwrap(), "value1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunk_wrap() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
+9
-9
@@ -1,6 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::ffi::{CStr, CString, OsString};
|
||||
use std::ffi::{CString, OsString};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bstr::BString;
|
||||
@@ -267,7 +267,7 @@ fn test_registry_value_into_lua() -> Result<()> {
|
||||
let r = lua.create_registry_value(&s)?;
|
||||
let value1 = lua.pack(&r)?;
|
||||
let value2 = lua.pack(r)?;
|
||||
assert_eq!(value1.as_str().as_deref(), Some("hello, world"));
|
||||
assert_eq!(value1.to_string()?, "hello, world");
|
||||
assert_eq!(value1.to_pointer(), value2.to_pointer());
|
||||
|
||||
// Push into stack
|
||||
@@ -450,8 +450,8 @@ fn test_conv_cstring() -> Result<()> {
|
||||
let s2: CString = lua.globals().get("s")?;
|
||||
assert_eq!(s, s2);
|
||||
|
||||
let cs = CStr::from_bytes_with_nul(b"hello\0").unwrap();
|
||||
lua.globals().set("cs", cs)?;
|
||||
let cs = c"hello";
|
||||
lua.globals().set("cs", c"hello")?;
|
||||
let cs2: CString = lua.globals().get("cs")?;
|
||||
assert_eq!(cs, cs2.as_c_str());
|
||||
|
||||
@@ -560,11 +560,11 @@ fn test_osstring_into_from_lua() -> Result<()> {
|
||||
|
||||
let v = lua.pack(s.as_os_str())?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), "hello, world");
|
||||
assert_eq!(v.as_string().unwrap(), "hello, world");
|
||||
|
||||
let v = lua.pack(s)?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), "hello, world");
|
||||
assert_eq!(v.as_string().unwrap(), "hello, world");
|
||||
|
||||
let s = lua.create_string("hello, world")?;
|
||||
let bstr = lua.unpack::<OsString>(Value::String(s))?;
|
||||
@@ -588,11 +588,11 @@ fn test_pathbuf_into_from_lua() -> Result<()> {
|
||||
|
||||
let v = lua.pack(pb.as_path())?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), pb_str);
|
||||
assert_eq!(v.to_string().unwrap(), pb_str);
|
||||
|
||||
let v = lua.pack(pb.clone())?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), pb_str);
|
||||
assert_eq!(v.to_string().unwrap(), pb_str);
|
||||
|
||||
let s = lua.create_string(pb_str)?;
|
||||
let bstr = lua.unpack::<PathBuf>(Value::String(s))?;
|
||||
@@ -724,7 +724,7 @@ fn test_char_into_lua() -> Result<()> {
|
||||
|
||||
let v = '🦀';
|
||||
let v2 = v.into_lua(&lua)?;
|
||||
assert_eq!(Some(v.to_string()), v2.as_string_lossy());
|
||||
assert_eq!(*v2.as_string().unwrap(), v.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-2
@@ -306,7 +306,7 @@ fn test_function_deep_clone() -> Result<()> {
|
||||
|
||||
lua.globals().set("a", 1)?;
|
||||
let func1 = lua.load("a += 1; return a").into_function()?;
|
||||
let func2 = func1.deep_clone();
|
||||
let func2 = func1.deep_clone()?;
|
||||
|
||||
assert_ne!(func1.to_pointer(), func2.to_pointer());
|
||||
assert_eq!(func1.call::<i32>(())?, 2);
|
||||
@@ -314,7 +314,7 @@ fn test_function_deep_clone() -> Result<()> {
|
||||
|
||||
// Check that for Rust functions deep_clone is just a clone
|
||||
let rust_func = lua.create_function(|_, ()| Ok(42))?;
|
||||
let rust_func2 = rust_func.deep_clone();
|
||||
let rust_func2 = rust_func.deep_clone()?;
|
||||
assert_eq!(rust_func.to_pointer(), rust_func2.to_pointer());
|
||||
|
||||
Ok(())
|
||||
|
||||
+47
-19
@@ -1,6 +1,5 @@
|
||||
#![cfg(not(feature = "luau"))]
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -25,9 +24,9 @@ fn test_line_counts() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
lua.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
|
||||
assert_eq!(debug.event(), DebugEvent::Line);
|
||||
hook_output.lock().unwrap().push(debug.curr_line());
|
||||
hook_output.lock().unwrap().push(debug.current_line().unwrap());
|
||||
Ok(VmState::Continue)
|
||||
});
|
||||
})?;
|
||||
lua.load(
|
||||
r#"
|
||||
local x = 2 + 3
|
||||
@@ -62,7 +61,7 @@ fn test_function_calls() -> Result<()> {
|
||||
let name = names.name.map(|s| s.into_owned());
|
||||
hook_output.lock().unwrap().push((name, source.what));
|
||||
Ok(VmState::Continue)
|
||||
});
|
||||
})?;
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
@@ -101,17 +100,13 @@ fn test_error_within_hook() -> Result<()> {
|
||||
|
||||
lua.set_hook(HookTriggers::EVERY_LINE, |_lua, _debug| {
|
||||
Err(Error::runtime("Something happened in there!"))
|
||||
});
|
||||
})?;
|
||||
|
||||
let err = lua.load("x = 1").exec().expect_err("panic didn't propagate");
|
||||
|
||||
match err {
|
||||
Error::CallbackError { cause, .. } => match cause.deref() {
|
||||
Error::RuntimeError(s) => assert_eq!(s, "Something happened in there!"),
|
||||
_ => panic!("wrong callback error kind caught"),
|
||||
},
|
||||
_ => panic!("wrong error kind caught"),
|
||||
};
|
||||
Error::RuntimeError(msg) => assert_eq!(msg, "Something happened in there!"),
|
||||
err => panic!("expected `RuntimeError` with a specific message, got {err:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -135,7 +130,7 @@ fn test_limit_execution_instructions() -> Result<()> {
|
||||
Ok(VmState::Continue)
|
||||
}
|
||||
},
|
||||
);
|
||||
)?;
|
||||
|
||||
lua.globals().set("x", Value::Integer(0))?;
|
||||
let _ = lua
|
||||
@@ -158,7 +153,7 @@ fn test_hook_removal() -> Result<()> {
|
||||
|
||||
lua.set_hook(HookTriggers::new().every_nth_instruction(1), |_lua, _debug| {
|
||||
Err(Error::runtime("this hook should've been removed by this time"))
|
||||
});
|
||||
})?;
|
||||
|
||||
assert!(lua.load("local x = 1").exec().is_err());
|
||||
lua.remove_hook();
|
||||
@@ -205,10 +200,10 @@ fn test_hook_swap_within_hook() -> Result<()> {
|
||||
});
|
||||
Ok(VmState::Continue)
|
||||
})
|
||||
});
|
||||
})?;
|
||||
Ok(VmState::Continue)
|
||||
})
|
||||
});
|
||||
})?;
|
||||
|
||||
TL_LUA.with(|tl| {
|
||||
let tl = tl.borrow();
|
||||
@@ -245,9 +240,9 @@ fn test_hook_threads() -> Result<()> {
|
||||
let hook_output = output.clone();
|
||||
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
|
||||
assert_eq!(debug.event(), DebugEvent::Line);
|
||||
hook_output.lock().unwrap().push(debug.curr_line());
|
||||
hook_output.lock().unwrap().push(debug.current_line().unwrap());
|
||||
Ok(VmState::Continue)
|
||||
});
|
||||
})?;
|
||||
|
||||
co.resume::<()>(())?;
|
||||
lua.remove_hook();
|
||||
@@ -277,7 +272,7 @@ fn test_hook_yield() -> Result<()> {
|
||||
.into_function()?;
|
||||
let co = lua.create_thread(func)?;
|
||||
|
||||
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield));
|
||||
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield))?;
|
||||
|
||||
#[cfg(any(feature = "lua54", feature = "lua53"))]
|
||||
{
|
||||
@@ -297,3 +292,36 @@ fn test_hook_yield() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_global_hook() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let counter = Arc::new(AtomicI64::new(0));
|
||||
let hook_counter = counter.clone();
|
||||
lua.set_global_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
|
||||
assert_eq!(debug.event(), DebugEvent::Line);
|
||||
hook_counter.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(VmState::Continue)
|
||||
})?;
|
||||
|
||||
let thread = lua.create_thread(
|
||||
lua.load(
|
||||
r#"
|
||||
local x = 2 + 3
|
||||
local y = x * 63
|
||||
coroutine.yield()
|
||||
local z = string.len(x..", "..y)
|
||||
"#,
|
||||
)
|
||||
.into_function()?,
|
||||
)?;
|
||||
|
||||
thread.resume::<()>(()).unwrap();
|
||||
lua.remove_global_hook();
|
||||
thread.resume::<()>(()).unwrap();
|
||||
assert_eq!(thread.status(), ThreadStatus::Finished);
|
||||
assert_eq!(counter.load(Ordering::Relaxed), 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+151
-91
@@ -1,12 +1,14 @@
|
||||
#![cfg(feature = "luau")]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use mlua::{Compiler, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState};
|
||||
use mlua::{
|
||||
Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_version() -> Result<()> {
|
||||
@@ -15,83 +17,6 @@ fn test_version() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_require() -> Result<()> {
|
||||
// Ensure that require() is not available if package module is not loaded
|
||||
let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?;
|
||||
assert!(lua.globals().get::<Option<Value>>("require")?.is_none());
|
||||
assert!(lua.globals().get::<Option<Value>>("package")?.is_none());
|
||||
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
// TODO: figure out why emscripten fails on file operations
|
||||
// Also see https://github.com/rust-lang/rust/issues/119250
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
lua = Lua::new();
|
||||
|
||||
// Check that require() can load stdlib modules (including `package`)
|
||||
lua.load(
|
||||
r#"
|
||||
local math = require("math")
|
||||
assert(math == _G.math, "math module does not match _G.math")
|
||||
local package = require("package")
|
||||
assert(package == _G.package, "package module does not match _G.package")
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
fs::write(
|
||||
temp_dir.path().join("module.luau"),
|
||||
r#"
|
||||
counter = (counter or 0) + 1
|
||||
return {
|
||||
counter = counter,
|
||||
error = function() error("test") end,
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
lua.globals()
|
||||
.get::<Table>("package")?
|
||||
.set("path", temp_dir.path().join("?.luau").to_string_lossy())?;
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
local module = require("module")
|
||||
assert(module.counter == 1)
|
||||
module = require("module")
|
||||
assert(module.counter == 1)
|
||||
|
||||
local ok, err = pcall(module.error)
|
||||
assert(not ok and string.find(err, "module.luau") ~= nil)
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
// Require non-existent module
|
||||
match lua.load("require('non-existent')").exec() {
|
||||
Err(Error::RuntimeError(e)) if e.contains("module 'non-existent' not found") => {}
|
||||
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
|
||||
}
|
||||
|
||||
// Require binary module in safe mode
|
||||
lua.globals()
|
||||
.get::<Table>("package")?
|
||||
.set("cpath", temp_dir.path().join("?.so").to_string_lossy())?;
|
||||
fs::write(temp_dir.path().join("dylib.so"), "")?;
|
||||
match lua.load("require('dylib')").exec() {
|
||||
Err(Error::RuntimeError(e)) if cfg!(unix) && e.contains("module 'dylib' not found") => {
|
||||
assert!(e.contains("dynamic libraries are disabled in safe mode"))
|
||||
}
|
||||
Err(Error::RuntimeError(e)) if e.contains("module 'dylib' not found") => {}
|
||||
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau-vector4"))]
|
||||
#[test]
|
||||
fn test_vectors() -> Result<()> {
|
||||
@@ -194,11 +119,13 @@ fn test_vector_metatable() -> Result<()> {
|
||||
"#,
|
||||
)
|
||||
.eval::<Table>()?;
|
||||
vector_mt.set_metatable(Some(vector_mt.clone()));
|
||||
vector_mt.set_metatable(Some(vector_mt.clone()))?;
|
||||
lua.set_type_metatable::<Vector>(Some(vector_mt.clone()));
|
||||
lua.globals().set("Vector3", vector_mt)?;
|
||||
|
||||
let compiler = Compiler::new().set_vector_lib("Vector3").set_vector_ctor("new");
|
||||
let compiler = Compiler::new()
|
||||
.set_vector_ctor("Vector3.new")
|
||||
.set_vector_type("Vector3");
|
||||
|
||||
// Test vector methods (fastcall)
|
||||
lua.load(
|
||||
@@ -241,9 +168,9 @@ fn test_readonly_table() -> Result<()> {
|
||||
check_readonly_error(t.raw_pop::<Value>());
|
||||
|
||||
// Special case
|
||||
match catch_unwind(AssertUnwindSafe(|| t.set_metatable(None))) {
|
||||
Ok(_) => panic!("expected panic, got nothing"),
|
||||
Err(_) => {}
|
||||
match t.set_metatable(None) {
|
||||
Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
|
||||
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -270,6 +197,14 @@ fn test_sandbox() -> Result<()> {
|
||||
co.sandbox()?;
|
||||
assert_eq!(co.resume::<Option<i32>>(())?, Some(123));
|
||||
|
||||
// collectgarbage should be restricted in sandboxed mode
|
||||
let collectgarbage = lua.globals().get::<Function>("collectgarbage")?;
|
||||
for arg in ["collect", "stop", "restart", "step", "isrunning"] {
|
||||
let err = collectgarbage.call::<()>(arg).err().unwrap().to_string();
|
||||
assert!(err.contains("collectgarbage called with invalid option"));
|
||||
}
|
||||
assert!(collectgarbage.call::<u64>("count").unwrap() > 0);
|
||||
|
||||
lua.sandbox(false)?;
|
||||
|
||||
// Previously set variable `global` should be cleared now
|
||||
@@ -279,6 +214,11 @@ fn test_sandbox() -> Result<()> {
|
||||
let table = lua.globals().get::<Table>("table")?;
|
||||
table.set("test", "test")?;
|
||||
|
||||
// collectgarbage should work now
|
||||
for arg in ["collect", "stop", "restart", "count", "step", "isrunning"] {
|
||||
collectgarbage.call::<()>(arg).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -390,16 +330,22 @@ fn test_interrupts() -> Result<()> {
|
||||
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
|
||||
assert_eq!(co.status(), ThreadStatus::Finished);
|
||||
|
||||
// Test no yielding at non-yieldable points
|
||||
yield_count.store(0, Ordering::Relaxed);
|
||||
let co = lua.create_thread(lua.create_function(|lua, arg: Value| {
|
||||
(lua.load("return (function(x) return x end)(...)")).call::<Value>(arg)
|
||||
})?)?;
|
||||
let res = co.resume::<String>("abc")?;
|
||||
assert_eq!(res, "abc".to_string());
|
||||
assert_eq!(yield_count.load(Ordering::Relaxed), 3);
|
||||
|
||||
//
|
||||
// Test errors in interrupts
|
||||
//
|
||||
lua.set_interrupt(|_| Err(Error::runtime("error from interrupt")));
|
||||
match f.call::<()>(()) {
|
||||
Err(Error::CallbackError { cause, .. }) => match *cause {
|
||||
Error::RuntimeError(ref m) if m == "error from interrupt" => {}
|
||||
ref e => panic!("expected RuntimeError with a specific message, got {:?}", e),
|
||||
},
|
||||
r => panic!("expected CallbackError, got {:?}", r),
|
||||
Err(Error::RuntimeError(ref msg)) => assert_eq!(msg, "error from interrupt"),
|
||||
res => panic!("expected `RuntimeError` with a specific message, got {res:?}"),
|
||||
}
|
||||
|
||||
lua.remove_interrupt();
|
||||
@@ -412,3 +358,117 @@ fn test_fflags() {
|
||||
// We cannot really on any particular feature flag to be present
|
||||
assert!(Lua::set_fflag("UnknownFlag", true).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thread_events() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let count = Arc::new(AtomicU64::new(0));
|
||||
let thread_data: Arc<(AtomicPtr<c_void>, AtomicBool)> = Arc::new(Default::default());
|
||||
|
||||
let (count2, thread_data2) = (count.clone(), thread_data.clone());
|
||||
lua.set_thread_creation_callback(move |_, thread| {
|
||||
count2.fetch_add(1, Ordering::Relaxed);
|
||||
(thread_data2.0).store(thread.to_pointer() as *mut _, Ordering::Relaxed);
|
||||
thread_data2.1.store(false, Ordering::Relaxed);
|
||||
Ok(())
|
||||
});
|
||||
let (count3, thread_data3) = (count.clone(), thread_data.clone());
|
||||
lua.set_thread_collection_callback(move |thread_ptr| {
|
||||
count3.fetch_add(1, Ordering::Relaxed);
|
||||
if thread_data3.0.load(Ordering::Relaxed) == thread_ptr.0 {
|
||||
thread_data3.1.store(true, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
|
||||
assert_eq!(count.load(Ordering::Relaxed), 1);
|
||||
let t_ptr = t.to_pointer();
|
||||
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
|
||||
assert!(!thread_data.1.load(Ordering::Relaxed));
|
||||
|
||||
// Thead will be destroyed after GC cycle
|
||||
drop(t);
|
||||
lua.gc_collect()?;
|
||||
assert_eq!(count.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
|
||||
assert!(thread_data.1.load(Ordering::Relaxed));
|
||||
|
||||
// Check that recursion is not allowed
|
||||
let count4 = count.clone();
|
||||
lua.set_thread_creation_callback(move |lua, _value| {
|
||||
count4.fetch_add(1, Ordering::Relaxed);
|
||||
let _ = lua.create_thread(lua.load("return 123").into_function().unwrap())?;
|
||||
Ok(())
|
||||
});
|
||||
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
|
||||
assert_eq!(count.load(Ordering::Relaxed), 3);
|
||||
|
||||
lua.remove_thread_callbacks();
|
||||
drop(t);
|
||||
lua.gc_collect()?;
|
||||
assert_eq!(count.load(Ordering::Relaxed), 3);
|
||||
|
||||
// Test error inside callback
|
||||
lua.set_thread_creation_callback(move |_, _| Err(Error::runtime("error when processing thread event")));
|
||||
let result = lua.create_thread(lua.load("return 123").into_function()?);
|
||||
assert!(result.is_err());
|
||||
assert!(
|
||||
matches!(result, Err(Error::RuntimeError(err)) if err.contains("error when processing thread event"))
|
||||
);
|
||||
|
||||
// Test context switch when running Lua script
|
||||
let count = Cell::new(0);
|
||||
lua.set_thread_creation_callback(move |_, _| {
|
||||
count.set(count.get() + 1);
|
||||
if count.get() == 2 {
|
||||
return Err(Error::runtime("thread limit exceeded"));
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
let result = lua
|
||||
.load(
|
||||
r#"
|
||||
local co = coroutine.wrap(function() return coroutine.create(print) end)
|
||||
co()
|
||||
"#,
|
||||
)
|
||||
.exec();
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("thread limit exceeded")));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loadstring() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let f = lua.load(r#"loadstring("return 123")"#).eval::<Function>()?;
|
||||
assert_eq!(f.call::<i32>(())?, 123);
|
||||
|
||||
let err = lua
|
||||
.load(r#"loadstring("retur 123", "chunk")"#)
|
||||
.exec()
|
||||
.err()
|
||||
.unwrap();
|
||||
assert!(err.to_string().contains(
|
||||
r#"syntax error: [string "chunk"]:1: Incomplete statement: expected assignment or a function call"#
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typeof_error() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let err = Error::runtime("just a test error");
|
||||
let res = lua.load("return typeof(...)").call::<String>(err)?;
|
||||
assert_eq!(res, "error");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[path = "luau/require.rs"]
|
||||
mod require;
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
use std::io::Result as IoResult;
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
use mlua::{Error, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};
|
||||
|
||||
fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
|
||||
lua.load(r#"return require(...)"#).call(path)
|
||||
}
|
||||
|
||||
fn run_require_pcall(lua: &Lua, path: impl IntoLua) -> Result<MultiValue> {
|
||||
lua.load(r#"return pcall(require, ...)"#).call(path)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn get_str(value: &Value, key: impl IntoLua) -> String {
|
||||
value.as_table().unwrap().get::<String>(key).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_require_errors() {
|
||||
let lua = Lua::new();
|
||||
|
||||
// RequireAbsolutePath
|
||||
let res = run_require(&lua, "/an/absolute/path");
|
||||
assert!(res.is_err());
|
||||
assert!(
|
||||
(res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
|
||||
);
|
||||
|
||||
// RequireUnprefixedPath
|
||||
let res = run_require(&lua, "an/unprefixed/path");
|
||||
assert!(res.is_err());
|
||||
assert!(
|
||||
(res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
|
||||
);
|
||||
|
||||
// Pass non-string to require
|
||||
let res = run_require(&lua, true);
|
||||
assert!(res.is_err());
|
||||
assert!((res.unwrap_err().to_string())
|
||||
.contains("bad argument #1 to 'require' (string expected, got boolean)"));
|
||||
|
||||
// Require from loadstring
|
||||
let res = lua
|
||||
.load(r#"return loadstring("require('./a/relative/path')")()"#)
|
||||
.eval::<Value>();
|
||||
assert!(res.is_err());
|
||||
assert!((res.unwrap_err().to_string()).contains("require is not supported in this context"));
|
||||
|
||||
// Test throwing mlua::Error
|
||||
struct MyRequire(TextRequirer);
|
||||
|
||||
impl Require for MyRequire {
|
||||
fn is_require_allowed(&self, chunk_name: &str) -> bool {
|
||||
self.0.is_require_allowed(chunk_name)
|
||||
}
|
||||
|
||||
fn reset(&mut self, _chunk_name: &str) -> StdResult<(), NavigateError> {
|
||||
Err(Error::runtime("test error"))?
|
||||
}
|
||||
|
||||
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
|
||||
self.0.jump_to_alias(path)
|
||||
}
|
||||
|
||||
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
|
||||
self.0.to_parent()
|
||||
}
|
||||
|
||||
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
|
||||
self.0.to_child(name)
|
||||
}
|
||||
|
||||
fn has_module(&self) -> bool {
|
||||
self.0.has_module()
|
||||
}
|
||||
|
||||
fn cache_key(&self) -> String {
|
||||
self.0.cache_key()
|
||||
}
|
||||
|
||||
fn has_config(&self) -> bool {
|
||||
self.0.has_config()
|
||||
}
|
||||
|
||||
fn config(&self) -> IoResult<Vec<u8>> {
|
||||
self.0.config()
|
||||
}
|
||||
|
||||
fn loader(&self, lua: &Lua) -> Result<mlua::Function> {
|
||||
self.0.loader(lua)
|
||||
}
|
||||
}
|
||||
|
||||
let require = lua
|
||||
.create_require_function(MyRequire(TextRequirer::new()))
|
||||
.unwrap();
|
||||
lua.globals().set("require", require).unwrap();
|
||||
let res = lua.load(r#"return require('./a/relative/path')"#).exec();
|
||||
assert!((res.unwrap_err().to_string()).contains("test error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_require_without_config() {
|
||||
let lua = Lua::new();
|
||||
|
||||
// RequireSimpleRelativePath
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/dependency").unwrap();
|
||||
assert_eq!("result from dependency", get_str(&res, 1));
|
||||
|
||||
// RequireSimpleRelativePathWithinPcall
|
||||
let res = run_require_pcall(&lua, "./tests/luau/require/without_config/dependency").unwrap();
|
||||
assert!(res[0].as_boolean().unwrap());
|
||||
assert_eq!("result from dependency", get_str(&res[1], 1));
|
||||
|
||||
// RequireRelativeToRequiringFile
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/module").unwrap();
|
||||
assert_eq!("result from dependency", get_str(&res, 1));
|
||||
assert_eq!("required into module", get_str(&res, 2));
|
||||
|
||||
// RequireLua
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/lua_dependency").unwrap();
|
||||
assert_eq!("result from lua_dependency", get_str(&res, 1));
|
||||
|
||||
// RequireInitLuau
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/luau").unwrap();
|
||||
assert_eq!("result from init.luau", get_str(&res, 1));
|
||||
|
||||
// RequireInitLua
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/lua").unwrap();
|
||||
assert_eq!("result from init.lua", get_str(&res, 1));
|
||||
|
||||
// RequireSubmoduleUsingSelfIndirectly
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/nested_module_requirer").unwrap();
|
||||
assert_eq!("result from submodule", get_str(&res, 1));
|
||||
|
||||
// RequireSubmoduleUsingSelfDirectly
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/nested").unwrap();
|
||||
assert_eq!("result from submodule", get_str(&res, 1));
|
||||
|
||||
// CannotRequireInitLuauDirectly
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/nested/init");
|
||||
assert!(res.is_err());
|
||||
assert!((res.unwrap_err().to_string()).contains("could not resolve child component \"init\""));
|
||||
|
||||
// RequireNestedInits
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/nested_inits_requirer").unwrap();
|
||||
assert_eq!("result from nested_inits/init", get_str(&res, 1));
|
||||
assert_eq!("required into module", get_str(&res, 2));
|
||||
|
||||
// RequireWithFileAmbiguity
|
||||
let res = run_require(
|
||||
&lua,
|
||||
"./tests/luau/require/without_config/ambiguous_file_requirer",
|
||||
);
|
||||
assert!(res.is_err());
|
||||
assert!((res.unwrap_err().to_string())
|
||||
.contains("could not resolve child component \"dependency\" (ambiguous)"));
|
||||
|
||||
// RequireWithDirectoryAmbiguity
|
||||
let res = run_require(
|
||||
&lua,
|
||||
"./tests/luau/require/without_config/ambiguous_directory_requirer",
|
||||
);
|
||||
assert!(res.is_err());
|
||||
assert!((res.unwrap_err().to_string())
|
||||
.contains("could not resolve child component \"dependency\" (ambiguous)"));
|
||||
|
||||
// CheckCachedResult
|
||||
let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap();
|
||||
assert!(res.is_table());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_require_with_config() {
|
||||
let lua = Lua::new();
|
||||
|
||||
// RequirePathWithAlias
|
||||
let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap();
|
||||
assert_eq!("result from dependency", get_str(&res, 1));
|
||||
|
||||
// RequirePathWithAlias (case-insensitive)
|
||||
let res2 = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer_uc").unwrap();
|
||||
assert_eq!("result from dependency", get_str(&res2, 1));
|
||||
assert_eq!(res.to_pointer(), res2.to_pointer());
|
||||
|
||||
// RequirePathWithParentAlias
|
||||
let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap();
|
||||
assert_eq!("result from other_dependency", get_str(&res, 1));
|
||||
|
||||
// RequirePathWithAliasPointingToDirectory
|
||||
let res = run_require(
|
||||
&lua,
|
||||
"./tests/luau/require/with_config/src/directory_alias_requirer",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!("result from subdirectory_dependency", get_str(&res, 1));
|
||||
|
||||
// RequireAliasThatDoesNotExist
|
||||
let res = run_require(&lua, "@this.alias.does.not.exist");
|
||||
assert!(res.is_err());
|
||||
assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias"));
|
||||
|
||||
// IllegalAlias
|
||||
let res = run_require(&lua, "@");
|
||||
assert!(res.is_err());
|
||||
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", not(windows)))]
|
||||
#[tokio::test]
|
||||
async fn test_async_require() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let temp_path = temp_dir.path().join("async_chunk.luau");
|
||||
std::fs::write(
|
||||
&temp_path,
|
||||
r#"
|
||||
sleep_ms(10)
|
||||
return "result_after_async_sleep"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
lua.globals().set(
|
||||
"sleep_ms",
|
||||
lua.create_async_function(|_, ms: u64| async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
lua.globals().set("tmp_dir", temp_dir.path().to_str().unwrap())?;
|
||||
lua.globals().set(
|
||||
"curr_dir_components",
|
||||
std::env::current_dir().unwrap().components().count(),
|
||||
)?;
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
local path_to_root = string.rep("/..", curr_dir_components - 1)
|
||||
local result = require(`.{path_to_root}{tmp_dir}/async_chunk`)
|
||||
assert(result == "result_after_async_sleep")
|
||||
"#,
|
||||
)
|
||||
.exec_async()
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"aliases": {
|
||||
"dep": "./this_should_be_overwritten_by_child_luaurc",
|
||||
"otherdep": "./src/other_dependency"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"aliases": {
|
||||
"dep": "./dependency",
|
||||
"subdir": "./subdirectory"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
return require("@dep")
|
||||
@@ -0,0 +1 @@
|
||||
return require("@DeP")
|
||||
@@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
||||
@@ -0,0 +1 @@
|
||||
return(require("@subdir/subdirectory_dependency"))
|
||||
@@ -0,0 +1 @@
|
||||
return {"result from other_dependency"}
|
||||
@@ -0,0 +1 @@
|
||||
return require("@otherdep")
|
||||
@@ -0,0 +1 @@
|
||||
return {"result from subdirectory_dependency"}
|
||||
@@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
||||
@@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
||||
@@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user