Compare commits

...

154 Commits

Author SHA1 Message Date
Alex Orlenko 8e57e6fa5a v0.6.0-beta.1 2021-05-04 00:43:26 +01:00
Alex Orlenko f2dbbb091f Update dependencies 2021-05-04 00:31:51 +01:00
Alex Orlenko 13cfb4bd51 Add CHANGELOG for v0.6.0-beta.1 2021-05-03 23:36:18 +01:00
Alex Orlenko 33ebacab49 Update README 2021-05-03 22:33:18 +01:00
Alex Orlenko 3829b72212 Update code docs 2021-05-03 22:33:18 +01:00
Alex Orlenko af67971e0d Make SerializeOptions non_exhaustive.
Add builder implementation similar to `LuaOptions` to set individual options.
2021-05-03 22:33:12 +01:00
Alex Orlenko 0f4bcca7ce Add LuaOptions to customize Lua/Rust behaviour.
The only option is `catch_rust_panics` to optionally disable catching Rust panics via pcall/xpcall.
2021-05-03 21:33:49 +01:00
Alex Orlenko 585c0a25d8 Replace 'Thread already finished' error with common Error::CoroutineInactive 2021-05-03 13:11:59 +01:00
Alex Orlenko 64346ce56c Implement/Derive Debug for Lua and few other structs 2021-05-03 13:05:43 +01:00
Alex Orlenko 108682cc71 Store safe property in Extra state to inherit into Lua structs made from pointers 2021-05-03 12:40:14 +01:00
Alex Orlenko 4af7bcf0d9 Implement ToLua/FromLua for Box<str> and Box<[T]> 2021-05-03 12:22:11 +01:00
Alex Orlenko 1bb3c5c19f Fix typo in README 2021-05-02 23:53:48 +01:00
Alex Orlenko a4567cb5f7 Improve growing the auxiliary stack of the ref thread:
Try to double size first, if not fulfilled try halving in a loop till 0.
Fix unwinding after panic in ref_stack_pop.
Add test to check the stack exhaustion.
2021-05-02 23:26:02 +01:00
Alex Orlenko 26d8d899f2 Allocate Waker slot in Registry in re-use it (instead of creating new userdata for it) 2021-05-02 13:04:02 +01:00
Alex Orlenko 67bc0b1196 Make AsyncPollPending internal value as LightUserData 2021-05-02 12:06:32 +01:00
Alex Orlenko e8505b5239 Change signature of Function::dump() to remove Result 2021-05-02 11:48:42 +01:00
Alex Orlenko 3f55958bdd Stack assertions review
Other minor code and documentation updates
2021-05-02 11:42:03 +01:00
Alex Orlenko 463fc646bc Refactor UserDataCell 2021-04-27 21:55:31 +01:00
Alex Orlenko b5f1325f2f Store nonstatic UserData pointer in self userdata (instead of metatable) 2021-04-27 17:06:37 +01:00
Alex Orlenko 0625991a48 Update github action to fix modules test on Windows (lua53 -> lua54) 2021-04-27 10:38:20 +01:00
Alex Orlenko 269ef9c55d Fix lua51 scope compilation 2021-04-27 10:11:37 +01:00
Alex Orlenko f5b88624ce Hide Deserializer inner fields
Improve documentation
2021-04-27 10:04:18 +01:00
Alex Orlenko 2fae94586d Remove Result from lua.null() and lua.array_metatable(). They never fail. 2021-04-27 00:38:13 +01:00
Alex Orlenko bc81d1016f Add SerializeOptions to to change default Lua serializer behaviour 2021-04-27 00:35:53 +01:00
Alex Orlenko c19f12898d Replace lazy_static with once_cell 2021-04-27 00:29:38 +01:00
Alex Orlenko c7541ef7d3 More tests 2021-04-27 00:29:38 +01:00
Alex Orlenko 41a1a0d15a Add more checks for destructed userdata in AnyUserData 2021-04-27 00:29:38 +01:00
Alex Orlenko c10169a380 cargo fmt and other minor fixes 2021-04-27 00:29:38 +01:00
Alex Orlenko ced808d5ab Don't trigger longjmp in rust.
Motivation behind this change is upcoming breaking change in Rust
compiler v1.52.0 to prevent unwinding across FFI boundaries.
https://github.com/rust-lang/rust/pull/76570
The new functionality requires nightly compiler to declare FFI
functions as "C-unwind".
The fundamental solution is to use C shim to wrap "e" and "m"
Lua functions in pcall.
Additionally define Rust calling convention to trigger lua_error
on Rust behalf.
2021-04-27 00:29:38 +01:00
Alex Orlenko c95ac32741 Add inline(always) to some compat53 functions 2021-04-27 00:29:37 +01:00
Alex Orlenko 14169eadb1 Include nightly to github CI tests 2021-04-27 00:29:37 +01:00
Alex Orlenko 5a7ad9f7cd Fix some clippy warnings & minor changes 2021-04-27 00:29:37 +01:00
Alex Orlenko decb5b9e37 Make MetaMethod::name() public
Tests for UserDataMetatable::pairs()
2021-04-27 00:29:37 +01:00
Alex Orlenko 1635903d3f Improve/fix scoped UserData drop 2021-04-27 00:29:37 +01:00
Alex Orlenko 2b2df708f9 Add UserDataFields API.
Provide safe access to UserData metatable and allow to define custom metamethods..
2021-04-27 00:29:37 +01:00
Alex Orlenko cb1ac28f2a v0.5.4 2021-04-20 02:05:05 +01:00
Alex Orlenko 3e7f25670a Don't read lua.h from build script (was not used) 2021-04-20 02:05:00 +01:00
Alex Orlenko 0d404ce4c3 Don't fail CI on coverage 2021-04-16 22:43:21 +01:00
Alex Orlenko e26cec5db9 Drop 'feature(link_args)' (removed from nightly). Don't run tests for LuaJIT 2.0.5 2021-04-16 22:27:28 +01:00
Alex Orlenko 0bd36b42e7 Make error::Error non_exhaustive 2021-04-16 22:10:10 +01:00
Alex Orlenko e0da6ac929 Hide raw_sequence_values_by_len under async/serialize feature flags 2021-04-16 22:09:49 +01:00
Alex Orlenko 0c7db4916c Serialize only known (registered) userdata.
This reverts commit 7332c6a.
Non-static userdata is a special case and can cause segfault if try to serialize it.
Now it should be safe, plus I added non-static userdata destructor to generate better error messages
in case of accessing destructed userdata.
2021-04-16 22:01:55 +01:00
Alex Orlenko b9589491e4 Improve panic handling (check for twice resumed panics) 2021-04-15 23:04:36 +01:00
Alex Orlenko 58cb371f06 Add rerun-if-changed instructions to build script 2021-04-14 22:33:23 +01:00
Alex Orlenko 8add60b019 Don't check LUA_LIB_NAME if it's not needed 2021-04-14 22:25:49 +01:00
Alex Orlenko c363fb9288 v0.5.3 2021-03-04 00:01:06 +00:00
Alex Orlenko 3900e23839 Fix compilation warnings on nightly 2021-03-03 23:36:28 +00:00
Alex Orlenko 726fde7e1f Optimise async callbacks (polling)
call async Rust callback [sum] 3 10
                        time:   [59.338 us 59.729 us 60.097 us]
                        change: [-10.336% -8.6212% -6.8003%] (p = 0.00 < 0.05)
                        Performance has improved.
2021-03-03 23:21:56 +00:00
Alex Orlenko 7cb9c4f39c Fix bug in returning nil-prefixed multi values from async function 2021-03-03 22:32:22 +00:00
Alex Orlenko b93ace0224 v0.5.2 2021-02-27 19:32:25 +00:00
Alex Orlenko 5f37bf812d Fix/whitelist some clippy warnings 2021-02-27 19:23:08 +00:00
Alex Orlenko 1f7e760d20 Add codecov coverage report 2021-02-27 18:03:53 +00:00
Alex Orlenko 90bea4aa34 Update README and keywords 2021-02-27 13:28:47 +00:00
Alex Orlenko 7775b4a99c Bump copyright year 2021-02-26 10:35:00 +00:00
Alex Orlenko 1d9cda10eb Add ToLua implementation for Cow<str> and Cow<CStr> 2021-02-26 10:23:36 +00:00
Alex Orlenko 7332c6a28c Remove registered_userdata_mt check 2021-02-22 20:38:36 +00:00
Alex Orlenko 94670e3fdb rustfmt 2021-02-22 20:13:56 +00:00
Alex Orlenko 335f433df4 Optimize callbacks 2021-02-21 23:52:20 +00:00
Alex Orlenko 2aed548747 Fix scoped async destruction of partially polled futures 2021-02-21 23:52:07 +00:00
Alex Orlenko 6a77b5f003 Update benchmarks:
- Refactor
- Add async benchmarks
2021-02-21 18:48:45 +00:00
Alex Orlenko aeb66115f7 v0.5.1 2021-01-20 11:04:03 +00:00
Alex Orlenko ce873a40bf Update CHANGELOG 2021-01-20 11:00:47 +00:00
Alex Orlenko 8de75d1c18 Update tokio to 1.0 for async examples 2021-01-20 10:47:27 +00:00
Alex Orlenko b6ff501b8c Fix numeric types conversion for 32bit lua. Fix #27 2021-01-20 10:46:23 +00:00
Alex Orlenko 0e73ae18f4 Update CI 2021-01-16 13:32:38 +00:00
Alex Orlenko e62fd400d7 Remove unused exports from glue.{c,rs} && Fix some clippy warnings 2021-01-16 13:31:45 +00:00
Alex Orlenko 1c79f646de Update README 2021-01-16 13:31:34 +00:00
Alex Orlenko 7f5fd36a2b Merge pull request #26 from wez/cross
Support cross compilation
2021-01-14 15:35:35 +00:00
Wez Furlong faf19e4a06 Allow luajit to build in the pointer size cross compilation case 2021-01-13 20:01:12 -08:00
Wez Furlong 24d9099ef7 install more bits for cross compilation jobs 2021-01-13 10:38:08 -08:00
Wez Furlong 84003f31e7 Add CI for cross compilation cases 2021-01-13 10:18:53 -08:00
Wez Furlong e0d9ec41e2 Support cross compilation
This commit teaches the build script to recognize when it is
cross-compiling and switch to an alternative approach for generating
the `glue.rs` module.

It defaults to the equivalent logic found in the lua headers to
set the default types and parameters.

Notably: it doesn't statically produce the default lua paths as we
cannot know these without either executing the code (not guaranteed
possible when cross compiling) or regexing out the paths from the
headers (a bit brittle).  An alternative approach might be to use
something like `lazy_static` to ask the library for its compiled in
values once at runtime.

I've tested this with:

```
cargo build --target armv7-unknown-linux-gnueabihf --features lua51,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features lua52,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features lua53,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features lua54,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features luajit,vendored
```

All except luajit compile.  Luajit itself doesn't cross compile, so I
don't think we can ever reasonably get that to work.

I haven't tried to run any of this yet; my use case is actually for mac
(https://github.com/wez/wezterm/pull/426) so I need to commit this and
try patching it in over there before I can see if that truly worked
end-to-end.

refs: https://github.com/khvzak/mlua/issues/14
2021-01-13 09:55:53 -08:00
Alex Orlenko 618874ef3c v0.5.0 2020-12-31 13:39:42 +00:00
Alex Orlenko 9f82cbe0c5 Update documentation 2020-12-31 13:39:38 +00:00
Alex Orlenko 1a81f8d447 Update compile tests 2020-12-29 23:20:06 +00:00
Alex Orlenko bedd430eb5 Re-export mlua_derive 2020-12-29 22:26:15 +00:00
Alex Orlenko afc41ab23c Add serialization example & update others 2020-12-29 21:39:34 +00:00
Alex Orlenko c5d0ccc433 Add reqwest http client example to fetch json 2020-12-28 15:21:45 +00:00
Alex Orlenko ce8955f5b9 Don't try to disable c modules without package loaded. Fix #24 2020-12-22 23:05:08 +00:00
Alex Orlenko 368c4428eb Wider lua-src and luajit-src versions 2020-12-14 00:51:35 +00:00
Alex Orlenko 6e2b687cb7 Serde support (serialize feature flag) 2020-12-14 00:51:26 +00:00
Alex Orlenko 661f8e592a Update num-traits dependency
Fix f32-to-f64 conversion tests
2020-11-04 21:22:24 +00:00
Alex Orlenko c8a7aa947c Update compile tests (temporary set TRYBUILD=overwrite) 2020-10-27 23:39:52 +00:00
Alex Orlenko a52135b62d Update compile tests error messages 2020-09-28 23:24:53 +01:00
Alex Orlenko 68378a3957 Merge pull request #15 from kaikalii/master
impl `ToLua` and `FromLua` for `HashSet` and `BTreeSet`
2020-09-28 23:14:17 +01:00
Kai Schmidt c65058a40a impl ToLua and FromLua for HashSet and BTreeSet 2020-09-27 17:54:47 -07:00
Alex Orlenko 93d63cef35 v0.4.2 2020-08-17 12:17:08 +01:00
Alex Orlenko b743245aba Update CHANGELOG 2020-08-17 11:14:40 +01:00
Alex Orlenko a172c021c9 Update set_memory_limit doc 2020-07-30 12:16:25 +01:00
Alex Orlenko de2c5cd9a9 Fix compilation warnings on nightly rust 2020-07-28 21:10:55 +01:00
Alex Orlenko d201beadc9 Add ChunkMode enum to mark chunks as text or binary 2020-07-28 21:04:21 +01:00
Alex Orlenko dd58cdad52 Add Function::dump() to dump lua function to a binary chunk 2020-07-27 23:26:33 +01:00
Alex Orlenko 5c8a5e0a5a Merge pull request #9 from HybridEidolon/bytecode-chunks
Make Lua::load load binary chunks when unsafe
2020-07-27 14:19:33 +01:00
Alex Orlenko e07c53eafe Update compile tests (2) 2020-07-27 13:51:21 +01:00
Alex Orlenko ad619390e1 Run compile tests on macos (was ubuntu-18.04) 2020-07-27 11:34:48 +01:00
Alex Orlenko 350602ab6e Update lua-src dependency to 5.4.0 2020-07-27 10:52:28 +01:00
Alex Orlenko 4b1bc88273 Update compile tests 2020-07-27 10:49:01 +01:00
Eidolon 883bf082b9 Make Lua::load load binary chunks when unsafe 2020-07-27 03:37:38 -05:00
Alex Orlenko 4265250cfd 0.4.1 release
Fix docs.rs build features
Update Cargo.toml description
2020-06-08 14:08:07 +01:00
Alex Orlenko 1d9fed2207 0.4.0 release 2020-06-08 02:28:26 +01:00
Alex Orlenko a67cbafff1 Run LuaJIT 2.0.5 tests in 1 thread
Seems there is a race in a LuaJIT 2.0 that makes the tests unstable.
Works ok on LuaJIT 2.1 and all Lua.
2020-06-08 02:19:53 +01:00
Alex Orlenko 39bcb19ae7 Update dependencies 2020-06-08 00:22:53 +01:00
Alex Orlenko efcaef3db7 Enable hooks support for LuaJIT 2020-06-07 20:38:19 +01:00
Alex Orlenko 0a13a9631d Fix LuaJIT 2.1 libraries loading 2020-06-07 20:38:19 +01:00
Alex Orlenko 3d42bc4ca6 Refactor main_state handling
Don't allow to set hook if main_state is not available
Remove Lua 5.1 dirty hack
2020-06-07 20:38:19 +01:00
Alex Orlenko 2eb40deafd Add test to check loading module from lua coroutine (thread) 2020-06-07 20:38:19 +01:00
Alex Orlenko 2595fe9885 Update CHANGELOG 2020-06-07 20:38:19 +01:00
Alex Orlenko 4865089a78 Update README 2020-06-07 20:38:19 +01:00
Alex Orlenko a35b8dda68 Raise compile_error! instead of panic! in the build script 2020-06-07 20:38:19 +01:00
Alex Orlenko 95b9fe8dac Fix clippy warnings 2020-06-07 20:38:19 +01:00
Alex Orlenko 5952a1f709 New module feature
Don't link module with Lua core (see: http://lua-users.org/wiki/BuildingModules)
Example and tests for modules
2020-06-07 20:38:11 +01:00
Alex Orlenko 73c7c94d75 Remove default feature lua53 2020-06-06 16:07:16 +01:00
Alex Orlenko d5c22d989a Dirty hack to get lua 5.1 main state 2020-06-06 16:07:16 +01:00
Alex Orlenko a9a4cf13f1 Add has_metamethod to AnyUserData 2020-06-06 16:07:16 +01:00
Alex Orlenko c3822219e0 Add hooks support (based on rlua v0.17 implementation)
This feature works on lua54, lua53, lua52 and lua51 only.
LuaJIT is unstable.
2020-06-06 16:07:16 +01:00
Alex Orlenko f6da437d8b Update docs 2020-06-06 16:07:16 +01:00
Alex Orlenko 2fd6757f39 Add LuaJIT 2.0.5 stable support 2020-06-06 16:07:16 +01:00
Alex Orlenko 56407fac0d Update CHANGELOG 2020-06-06 16:07:13 +01:00
Alex Orlenko d0fa03e9c8 Bump version to 0.4.0-beta.1 2020-06-06 16:05:35 +01:00
Alex Orlenko cccec914ca Remove deprecated Table::call 2020-05-15 01:48:57 +01:00
Alex Orlenko 687ecc9247 Update documentation 2020-05-15 01:48:57 +01:00
Alex Orlenko 79bfb112aa Remove redundant lifetimes 2020-05-15 01:48:57 +01:00
Alex Orlenko 60c659ecff Add async http server example 2020-05-15 01:48:57 +01:00
Alex Orlenko 9f2d598517 Add clippy check and fix clippy warnings 2020-05-15 01:48:56 +01:00
Alex Orlenko 1492790ece Update compile tests to include async/send features 2020-05-12 03:16:29 +01:00
Alex Orlenko e88f086ed8 Update github workflow 2020-05-12 02:14:48 +01:00
Alex Orlenko 5a9a308790 Provide safe and unsafe Lua modes:
- In safe mode Lua would not have ability to load C code via `require` or `package.loadlib`
- Unsafe mode allows everything.
2020-05-12 02:14:48 +01:00
Alex Orlenko 1b2b94c808 Use Rust allocator for new Lua states that allows to set memory limit 2020-05-12 02:14:48 +01:00
Alex Orlenko 526e7418d8 Add MetaMethod::Close to support Lua 5.4 to-be-closed variables 2020-05-12 02:14:47 +01:00
Alex Orlenko 24d4f04c0d Add Lua 5.4 to github workflow 2020-05-11 02:43:34 +01:00
Alex Orlenko 539b569ff4 Add Lua 5.4 (rc2) support 2020-05-11 02:43:34 +01:00
Alex Orlenko 5c226b4915 Lua 5.4 FFI 2020-05-11 02:43:34 +01:00
Alex Orlenko d366ce0dd4 Scope support (including async) 2020-05-11 02:43:34 +01:00
Alex Orlenko 2bd5c2f6ca Hide Lua "Send" capability under the optional "send" feature flag 2020-05-11 02:43:34 +01:00
Alex Orlenko 7b0e4b4280 Add Send capability to Lua 2020-05-11 02:43:34 +01:00
Alex Orlenko 6e2bb73cff Bind Futures lifetimes to 'lua rather than 'static.
Fix async examples.
2020-05-11 02:43:34 +01:00
Alex Orlenko 7efcee853d Don't allow 'callback outlive 'lua 2020-05-11 02:43:33 +01:00
Alex Orlenko e9c2b8d306 0.3.2 release 2020-04-29 16:32:16 +01:00
Alex Orlenko d672e19365 Add ToLua implementation for slices and arrays 2020-04-29 16:12:58 +01:00
Alex Orlenko bda399a5b4 Get yield function in the moment of async callback creation.
This is usefull to temporary override `coroutine.yield` prior to create_async_callback()
and then restore to original value.
2020-04-28 14:29:36 +01:00
Alex Orlenko fe5e87b0f5 Fix bugs with metatable cache:
- Don't use thread_local as Lua can be created in one thread and executed in another (in module mode);
- Make per state storage without luaL_ref;
2020-04-28 14:29:14 +01:00
Alex Orlenko 0f32e9cb43 0.3.1 release 2020-04-20 01:55:57 +01:00
Alex Orlenko 0efa0fcb6a Update documentation 2020-04-20 01:52:01 +01:00
Alex Orlenko 4e19ae6ccf Update tests (async and table) 2020-04-20 01:14:34 +01:00
Alex Orlenko c826798a6d Minor refactor 2020-04-19 16:51:35 +01:00
Alex Orlenko ee08050c1f Add TableExt trait with call_method/function methods 2020-04-19 16:15:16 +01:00
Alex Orlenko d8897d867b Update examples 2020-04-19 01:23:42 +01:00
Alex Orlenko 222f4df668 Add family of call_async function
Update documentation
Move async tests to a separate file
2020-04-18 21:26:12 +01:00
Alex Orlenko afaa0eb639 0.3.0 release 2020-04-17 23:55:58 +01:00
Alex Orlenko db5ad6bc60 Include async build/test to github workflow 2020-04-17 23:55:58 +01:00
Alex Orlenko ef06c5eec9 Update Cargo.toml 2020-04-17 23:55:57 +01:00
Alex Orlenko f7dc9da107 Update README
Cargo fmt and minor changes
2020-04-17 22:52:34 +01:00
Alex Orlenko 47e8a80c1c v0.3.0-alpha.1 with async support
Squashed commit of the async branch.
2020-04-17 22:39:50 +01:00
Alex Orlenko 1a788c48f1 Cherry-pick changes from rlua:
- Make Value::type_name() public
- Update CallbackError and ExternalError Display impl
2020-04-15 21:23:00 +01:00
101 changed files with 11595 additions and 2262 deletions
+23
View File
@@ -0,0 +1,23 @@
name: coverage
on: [push]
jobs:
test:
name: coverage
runs-on: ubuntu-latest
container:
image: xd009642/tarpaulin
options: --security-opt seccomp=unconfined
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Generate code coverage
run: |
cargo tarpaulin --verbose --features lua53,vendored,async,send,serialize --out xml --exclude-files benches --exclude-files tests --exclude-files build --exclude-files src/ffi
- name: Upload to codecov.io
uses: codecov/codecov-action@v1
with:
token: ${{secrets.CODECOV_TOKEN}}
fail_ci_if_error: false
+174 -108
View File
@@ -7,141 +7,207 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
thing:
- stable
- macos-x86_64
- x86_64-msvc
os: [ubuntu-18.04, macos-latest, windows-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- thing: stable
- os: ubuntu-18.04
target: x86_64-unknown-linux-gnu
rust: stable
os: ubuntu-latest
- thing: macos-x86_64
- os: macos-latest
target: x86_64-apple-darwin
rust: stable
os: macos-latest
- thing: x86_64-msvc
- os: windows-latest
target: x86_64-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust (rustup)
if: matrix.os != 'macos-latest'
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
shell: bash
- name: Install Lua (ubuntu)
if: matrix.os == 'ubuntu-latest'
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Build ${{ matrix.lua }} vendored
run: |
sudo apt-get update -y
cargo build --release --features "${{ matrix.lua }} vendored"
cargo build --release --features "${{ matrix.lua }} vendored async send serialize"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-18.04' && matrix.lua != 'lua54' }}
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
- name: Install Rust (macos)
if: matrix.os == 'macos-latest'
cargo build --release --features "${{ matrix.lua }}"
build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin
runs-on: macos-11.0
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
target: aarch64-apple-darwin
override: true
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }} async send serialize vendored"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-18.04
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
target: aarch64-unknown-linux-gnu
override: true
- name: Install ARM compiler toolchain
run: |
curl https://sh.rustup.rs | sh -s -- -y
echo ::add-path::$HOME/.cargo/bin
shell: bash
- name: Install GCC (aarch64-linux)
run: |
sudo apt-get update -y
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
if: matrix.thing == 'aarch64-linux'
shell: bash
- run: rustup target add ${{ matrix.target }}
- name: Build (Lua 5.3/5.2/5.1 and LuaJIT vendored)
run: |
for FEATURE in lua53 lua52 lua51 luajit; do
echo "Building $FEATURE"
cargo build --target ${{ matrix.target }} --release --no-default-features --features "$FEATURE vendored"
done
shell: bash
- name: Build (Lua 5.3/5.2/5.1 and LuaJIT via pkg-config)
if: matrix.os == 'ubuntu-latest'
run: |
for FEATURE in lua53 lua52 lua51 luajit; do
echo "Building $FEATURE"
cargo build --target ${{ matrix.target }} --release --no-default-features --features $FEATURE
done
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }} async send serialize vendored"
shell: bash
test_linux:
name: Test on Linux
runs-on: ubuntu-latest
build_armv7_cross_ubuntu:
name: Cross-compile to armv7-unknown-linux-gnueabihf
runs-on: ubuntu-18.04
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@v1
- name: Install Rust
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- name: Run tests (Lua 5.3 vendored)
run: cargo test --release --no-default-features --features "lua53 vendored"
shell: bash
- name: Run tests (Lua 5.2 vendored)
run: cargo test --release --no-default-features --features "lua52 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored)
run: cargo test --release --no-default-features --features "lua51 vendored"
shell: bash
- name: Run tests (LuaJIT vendored)
run: cargo test --release --no-default-features --features "luajit vendored"
shell: bash
- name: Run compile test (Lua 5.3)
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
target: armv7-unknown-linux-gnueabihf
override: true
- name: Install ARM compiler toolchain
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends liblua5.3-dev
cargo test --release --no-default-features --features "lua53 vendored" -- --ignored
sudo apt-get update
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 }} async send serialize vendored"
shell: bash
test_macos:
name: Test on MacOS
runs-on: macos-latest
test:
name: Test
runs-on: ${{ matrix.os }}
needs: build
strategy:
matrix:
os: [ubuntu-18.04, macos-latest, windows-latest]
rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-18.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v1
- name: Install Rust
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Run ${{ matrix.lua }} tests
run: |
curl https://sh.rustup.rs | sh -s -- -y
echo ::add-path::$HOME/.cargo/bin
cargo test --release --features "${{ matrix.lua }} vendored"
cargo test --release --features "${{ matrix.lua }} vendored async send serialize"
shell: bash
- name: Run tests (Lua 5.3 vendored)
run: cargo test --release --no-default-features --features "lua53 vendored"
shell: bash
- name: Run tests (Lua 5.2 vendored)
run: cargo test --release --no-default-features --features "lua52 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored)
run: cargo test --release --no-default-features --features "lua51 vendored"
shell: bash
- name: Run tests (LuaJIT vendored)
run: cargo test --release --no-default-features --features "luajit vendored"
- name: Run compile tests (macos lua53)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua53' }}
run: |
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }} vendored" -- --ignored
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }} vendored async send serialize" -- --ignored
shell: bash
test_windows:
name: Test on Windows
test_modules:
name: Test modules
runs-on: ${{ matrix.os }}
needs: build
strategy:
matrix:
os: [ubuntu-18.04, macos-latest]
rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-18.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Run ${{ matrix.lua }} module tests
run: |
(cd examples/module && cargo build --release --features "${{ matrix.lua }} vendored")
(cd tests/module && cargo test --release --features "${{ matrix.lua }} vendored")
shell: bash
test_modules_windows:
name: Test modules on Windows
runs-on: windows-latest
needs: build
strategy:
matrix:
lua: [lua54, luajit]
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v1
- name: Install Rust
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- name: Run tests (Lua 5.3 vendored)
run: cargo test --release --no-default-features --features "lua53 vendored"
shell: bash
- name: Run tests (Lua 5.2 vendored)
run: cargo test --release --no-default-features --features "lua52 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored)
run: cargo test --release --no-default-features --features "lua51 vendored"
shell: bash
- name: Run tests (LuaJIT vendored)
run: cargo test --release --no-default-features --features "luajit vendored"
shell: bash
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v2
- name: Install Rust & Lua
run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
- name: Run ${{ matrix.lua }} module tests
run: |
(cd examples/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module && cargo test --release --features "${{ matrix.lua }}")
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: Install Rust
run: rustup update stable && rustup default stable && rustup component add rustfmt
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
override: true
- run: cargo fmt -- --check
clippy:
name: Clippy check
runs-on: ubuntu-18.04
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --features "${{ matrix.lua }},vendored,async,send,serialize
+65
View File
@@ -0,0 +1,65 @@
## v0.6.0-beta.1
- New `UserDataFields` API
- Allow to define arbitrary MetaMethods
- `MetaMethods::name()` is public
- Do not trigger longjmp in Rust to prevent unwinding across FFI boundaries. See https://github.com/rust-lang/rust/issues/83541
- Added `SerializeOptions` to to change default Lua serializer behaviour (eg. nil/null/array serialization)
- [**Breaking**] Removed `Result` from `LuaSerdeExt::null()` and `LuaSerdeExt::array_metatable()` (never fails)
- [**Breaking**] Removed `Result` from `Function::dump()` (never fails)
- `ToLua`/`FromLua` implementation for `Box<str>` and `Box<[T]>`
- [**Breaking**] Added `LuaOptions` to customize Lua/Rust behaviour (currently panic handling)
- Various bugfixes and performance improvements
## v0.5.4
- Build script improvements
- Improvements in panic handling (resume panic on value popping)
- Fixed bug serializing 3rd party userdata (causes segfault)
- Make error::Error non exhaustive
## v0.5.3
- Fixed bug when returning nil-prefixed multi values from async function (+ test)
- Performance optimisation for async callbacks (polling)
## v0.5.2
- Some performance optimisations (callbacks)
- `ToLua` implementation for `Cow<str>` and `Cow<CStr>`
- Fixed bug with `Scope` destruction of partially polled futures
## v0.5.1
- Support cross compilation that should work well for vendored builds (including LuaJIT with some restrictions)
- Fix numeric types conversion for 32bit Lua
- Update tokio to 1.0 for async examples
## v0.5.0
- Serde support under `serialize` feature flag.
- Re-export `mlua_derive`.
- impl `ToLua` and `FromLua` for `HashSet` and `BTreeSet`
## v0.4.2
- Added `Function::dump()` to dump lua function to a binary chunk
- Added `ChunkMode` enum to mark chunks as text or binary
- Updated `set_memory_limit` doc
## v0.4.0
- Lua 5.4 support with `MetaMethod::Close`.
- `lua53` feature is disabled by default. Now preferred Lua version have to be chosen explicitly.
- Provide safety guaraness for Lua state, which means that potenially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equalient for the previous `Lua::new()` function is `Lua::unsafe_new()`.
- New `send` feature to require `Send`.
- New `module` feature, that disables linking to Lua Core Libraries. Required for modules.
- Don't allow `'callback` outlive `'lua` in `Lua::create_function()` to fix [the unsoundness](tests/compile/static_callback_args.rs).
- Added `Lua::into_static()` to make `'static` Lua state. This is useful to spawn async Lua threads that requires `'static`.
- New function `Lua::set_memory_limit()` (similar to `rlua`) to enable memory restrictions in Lua VM (requires Lua >= 5.2).
- `Scope`, temporary removed in v0.3, is back with async support.
- Removed deprecated `Table::call()` function.
- Added hooks support (backported from rlua 0.17).
- New `AnyUserData::has_metamethod()` function.
- LuaJIT 2.0.5 (the latest stable) support.
- Various bug fixes and improvements.
+56 -14
View File
@@ -1,52 +1,94 @@
[package]
name = "mlua"
version = "0.2.2"
version = "0.6.0-beta.1" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
keywords = ["lua", "luajit"]
categories = ["api-bindings"]
keywords = ["lua", "luajit", "async", "futures", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.1/5.2/5.3 (including LuaJIT)
with support of writing native lua modules in Rust.
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT)
with async/await features and support of writing native lua modules in Rust.
"""
[badges]
# github-actions = { repository = "khvzak/mlua", workflow = "CI" }
[package.metadata.docs.rs]
features = ["lua53", "async", "send", "serialize"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
members = [
"mlua_derive",
"examples/module",
"tests/module",
]
[features]
default = ["lua53"]
lua54 = []
lua53 = []
lua52 = []
lua51 = []
luajit = []
vendored = ["lua-src", "luajit-src"]
module = ["mlua_derive"]
async = ["futures-core", "futures-task", "futures-util"]
send = []
serialize = ["serde", "erased-serde"]
[dependencies]
num-traits = { version = "0.2.6" }
mlua_derive = { version = "0.5", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false }
once_cell = { version = "1.7" }
num-traits = { version = "0.2.14" }
futures-core = { version = "0.3.5", optional = true }
futures-task = { version = "0.3.5", optional = true }
futures-util = { version = "0.3.5", optional = true }
serde = { version = "1.0", optional = true }
erased-serde = { version = "0.3", optional = true }
[build-dependencies]
cc = { version = "1.0" }
pkg-config = { version = "0.3.11" }
lua-src = { version = "535.0.1", optional = true }
luajit-src = { version = "210.0.0", optional = true }
pkg-config = { version = "0.3.17" }
lua-src = { version = ">= 540.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.1.2, < 220.0.0", optional = true }
[dev-dependencies]
rustyline = "5.0"
criterion = "0.2.0"
rustyline = "8.0"
criterion = { version = "0.3.4", features = ["html_reports", "async_tokio"] }
trybuild = "1.0"
futures = "0.3.5"
hyper = { version = "0.14", features = ["client", "server"] }
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
futures-timer = "3.0"
serde_json = "1.0"
maplit = "1.0"
[[bench]]
name = "benchmark"
harness = false
required-features = ["async"]
[[example]]
name = "async_http_client"
required-features = ["async"]
[[example]]
name = "async_http_reqwest"
required-features = ["async", "serialize"]
[[example]]
name = "async_http_server"
required-features = ["async", "send"]
[[example]]
name = "async_tcp_server"
required-features = ["async"]
[[example]]
name = "serialize"
required-features = ["serialize"]
+1 -1
View File
@@ -3,7 +3,7 @@ below:
MIT License
Copyright (c) 2019 A. Orlenko
Copyright (c) 2019-2021 A. Orlenko
Copyright (c) 2017 rlua
Permission is hereby granted, free of charge, to any person obtaining a copy
+111 -33
View File
@@ -1,5 +1,5 @@
# mlua
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs]
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io]
[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/mlua/actions
@@ -7,22 +7,75 @@
[crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg
[docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua
[Guided Tour](examples/guided_tour.rs)
A fork of [rlua 0.15](https://github.com/kyren/rlua/tree/0.15.3) which provides a high level
interface between Rust and Lua. Unlike `rlua` , `mlua` supports Lua 5.1 (including LuaJIT), 5.2 and 5.3.
The `mlua` goal is to be an easy to use, practical and flexible API between Rust and Lua but
*__not__* always 100% safe due to the Lua VM nature. Also, `mlua` provides a way to write native lua
modules in Rust.
`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 [rlua](https://github.com/amethyst/rlua/tree/0.15.3) fork, `mlua` supports Lua 5.4, 5.3, 5.2 and 5.1 including LuaJIT (2.0.5 and 2.1 beta) and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targes are also supported).
[GitHub Actions]: https://github.com/khvzak/mlua/actions
## Usage
### Choosing Lua version
### Feature flags
The following features could be used to choose Lua version: `lua53` (default), `lua52`, `lua51` and `luajit`.
`mlua` uses feature flags to reduce the amount of depenendies, compiled code and allow to choose only required set of features.
Below is a list of the available feature flags. By default `mlua` does not enable any features.
By default mlua uses `pkg-config` tool to find lua includes and lib.
* `lua54`: activate Lua [5.4] support
* `lua53`: activate Lua [5.3] support
* `lua52`: activate Lua [5.2] support
* `lua51`: activate Lua [5.1] support
* `luajit`: activate [LuaJIT] support
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
* `module`: enable module mode (building loadable `cdylib` library for Lua)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
* `send`: make `mlua::Lua` transferable across thread boundaries (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
[5.2]: https://www.lua.org/manual/5.2/manual.html
[5.1]: https://www.lua.org/manual/5.1/manual.html
[LuaJIT]: https://luajit.org/
[lua-src]: https://github.com/khvzak/lua-src-rs
[luajit-src]: https://github.com/khvzak/luajit-src-rs
[tokio]: https://github.com/tokio-rs/tokio
[async-std]: https://github.com/async-rs/async-std
[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
[serde]: https://github.com/serde-rs/serde
### Async/await support
`mlua` supports async/await for all Lua versions. This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
- [HTTP Client (json)](examples/async_http_reqwest.rs)
- [HTTP Server](examples/async_http_server.rs)
- [TCP Server](examples/async_tcp_server.rs)
### Serialization (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).
[Example](examples/serialize.rs)
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
[`mlua::Value`]: https://docs.rs/mlua/latest/mlua/enum.Value.html
### Compiling
You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit`, according to the choosen 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_INC`, `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).
@@ -34,14 +87,16 @@ my_project $ LUA_INC=$HOME/tmp/lua-5.2.4/src LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA
`mlua` also supports vendored lua/luajit using the auxilary 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 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.
Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = "0.2"
mlua = { version = "0.5", features = ["lua53", "vendored"] }
```
`main.rs`
@@ -65,6 +120,9 @@ 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.3/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.3/manual.html#6.10).
[Example](examples/module)
Add to `Cargo.toml` :
@@ -73,15 +131,12 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = "0.2"
mlua_derive = "0.2"
mlua = { version = "0.5", features = ["lua53", "vendored", "module"] }
```
`lib.rs` :
``` rust
#[macro_use]
extern crate mlua_derive;
use mlua::prelude::*;
fn hello(_: &Lua, name: String) -> LuaResult<()> {
@@ -89,7 +144,7 @@ fn hello(_: &Lua, name: String) -> LuaResult<()> {
Ok(())
}
#[lua_module]
#[mlua::lua_module]
fn my_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("hello", lua.create_function(hello)?)?;
@@ -97,32 +152,53 @@ fn my_module(lua: &Lua) -> LuaResult<LuaTable> {
}
```
And then (macos example):
And then (**macOS** example):
``` sh
$ cargo build
$ cargo rustc -- -C link-arg=-undefined -C link-arg=dynamic_lookup
$ ln -s ./target/debug/libmy_module.dylib ./my_module.so
$ lua5.3 -e 'require("my_module").hello("world")'
hello, world!
```
On macOS, you need to set additional linker arguments. One option is to compile with `cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup`, the other is to create a `.cargo/config` with the following content:
``` toml
[target.x86_64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
[target.aarch64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
```
On Linux you can build modules normally with `cargo build --release`.
Vendored and non-vendored builds are supported for these OS.
On Windows `vendored` mode for modules is not supported since you need to link to a Lua dll.
Easiest way is to use either MinGW64 (as part of [MSYS2](https://github.com/msys2/msys2) package) with `pkg-config` or
MSVC with `LUA_INC` / `LUA_LIB` / `LUA_LIB_NAME` environment variables.
More details about compiling and linking Lua modules can be found on the [Building Modules](http://lua-users.org/wiki/BuildingModules) page.
## 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.
Every place where the Lua C API may trigger an error longjmp in any way is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack,
and there is overhead associated with this safety.
Unfortunately, `mlua` does not provide absolute safety even without using `unsafe` .
This library contains a huge amount of unsafe code. There are almost
certainly bugs still lurking in this library! It is surprisingly, fiendishly
difficult to use the Lua C API without the potential for unsafety.
This library contains a huge amount of unsafe code. There are almost certainly bugs still lurking in this library!
It is surprisingly, fiendishly difficult to use the Lua C API without the potential for unsafety.
## Panic handling
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics could be
resumed then by propagating the Lua error to Rust code.
resumed then by returning or propagating the Lua error to Rust code.
For example:
``` rust
@@ -141,18 +217,21 @@ let _ = lua.load(r#"
unreachable!()
```
`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 leak Lua stack space. This is
mostly important to safely use `mlua` types in Drop impls, as you should not be
Optionally `mlua` can disable Rust panics catching in Lua via `pcall`/`xpcall` and automatically resume
them across the Lua API boundary. This is controlled via `LuaOptions` and done by wrapping the Lua `pcall`/`xpcall`
functions on a way to prevent catching errors that are wrapped Rust panics.
`mlua` should also be panic safe in another way as well, which is that any `Lua` instances or handles
remains usable after a user generated panic, and such panics should not break internal invariants or
leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be
using panics for general error handling.
Below is a list of `mlua` behaviors that should be considered a bug.
If you encounter them, a bug report would be very welcome:
+ If your program panics with a message that contains the string "mlua internal error", this is a bug.
+ If you can cause UB with `mlua` without typing the word "unsafe", this is a bug.
+ The above is true even for the internal panic about running out of stack space! There are a few ways to generate normal script errors by running out of stack, but if you encounter a *panic* based on running out of stack, this is a bug.
+ If your program panics with a message that contains the string "mlua internal error", this is a bug.
+ Lua C API errors are handled by lonjmp. All instances where the Lua C API would otherwise longjmp over calling stack frames should be guarded against, except in internal callbacks where this is intentional. If you detect that `mlua` is triggering a longjmp over your Rust stack frames, this is a bug!
@@ -161,4 +240,3 @@ If you encounter them, a bug report would be very welcome:
## License
This project is licensed under the [MIT license](LICENSE)
+171 -110
View File
@@ -1,22 +1,22 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::task;
use mlua::prelude::*;
fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
lua.gc_collect().unwrap();
}
fn create_table(c: &mut Criterion) {
c.bench_function("create table", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [table empty]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
lua.create_table().unwrap();
},
BatchSize::SmallInput,
@@ -25,12 +25,14 @@ fn create_table(c: &mut Criterion) {
}
fn create_array(c: &mut Criterion) {
c.bench_function("create array 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [array] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for i in 1..11 {
for i in 1..=10 {
table.set(i, i).unwrap();
}
},
@@ -40,10 +42,12 @@ fn create_array(c: &mut Criterion) {
}
fn create_string_table(c: &mut Criterion) {
c.bench_function("create string table 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [table string] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for &s in &["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] {
let s = lua.create_string(s).unwrap();
@@ -55,30 +59,20 @@ fn create_string_table(c: &mut Criterion) {
});
}
fn call_add_function(c: &mut Criterion) {
c.bench_function("call add function 3 10", |b| {
fn call_lua_function(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("call Lua function [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
let lua = Lua::new();
let f = {
let f: LuaFunction = lua
.load(
r#"
function(a, b, c)
return a + b + c
end
"#,
)
.eval()
.unwrap();
lua.create_registry_value(f).unwrap()
};
(lua, f)
collect_gc_twice(&lua);
lua.load("function(a, b, c) return a + b + c end")
.eval::<LuaFunction>()
.unwrap()
},
|(lua, f)| {
let add_function: LuaFunction = lua.registry_value(f).unwrap();
|function| {
for i in 0..10 {
let _result: i64 = add_function.call((i, i + 1, i + 2)).unwrap();
let _result: i64 = function.call((i, i + 1, i + 2)).unwrap();
}
},
BatchSize::SmallInput,
@@ -86,72 +80,75 @@ fn call_add_function(c: &mut Criterion) {
});
}
fn call_add_callback(c: &mut Criterion) {
c.bench_function("call callback add 2 10", |b| {
fn call_sum_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c))
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call Rust callback [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
let lua = Lua::new();
let f = {
let c: LuaFunction = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c))
.unwrap();
lua.globals().set("callback", c).unwrap();
let f: LuaFunction = lua
.load(
r#"
function()
for i = 1,10 do
callback(i, i, i)
end
end
"#,
)
.eval()
.unwrap();
lua.create_registry_value(f).unwrap()
};
(lua, f)
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|(lua, f)| {
let entry_function: LuaFunction = lua.registry_value(f).unwrap();
entry_function.call::<_, ()>(()).unwrap();
|function| {
function.call::<_, ()>(()).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_append_callback(c: &mut Criterion) {
c.bench_function("call callback append 10", |b| {
fn call_async_sum_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
Ok(a + b + c)
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call async Rust callback [sum] 3 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_concat_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b): (LuaString, LuaString)| {
Ok(format!("{}{}", a.to_str()?, b.to_str()?))
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call Rust callback [concat string] 10", |b| {
b.iter_batched_ref(
|| {
let lua = Lua::new();
let f = {
let c: LuaFunction = lua
.create_function(|_, (a, b): (LuaString, LuaString)| {
Ok(format!("{}{}", a.to_str()?, b.to_str()?))
})
.unwrap();
lua.globals().set("callback", c).unwrap();
let f: LuaFunction = lua
.load(
r#"
function()
for _ = 1,10 do
callback("a", "b")
end
end
"#,
)
.eval()
.unwrap();
lua.create_registry_value(f).unwrap()
};
(lua, f)
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback('a', tostring(i)) end end")
.eval::<LuaFunction>()
.unwrap()
},
|(lua, f)| {
let entry_function: LuaFunction = lua.registry_value(f).unwrap();
entry_function.call::<_, ()>(()).unwrap();
|function| {
function.call::<_, ()>(()).unwrap();
},
BatchSize::SmallInput,
);
@@ -159,10 +156,12 @@ fn call_append_callback(c: &mut Criterion) {
}
fn create_registry_values(c: &mut Criterion) {
c.bench_function("create registry 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [registry value] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for _ in 0..10 {
lua.create_registry_value(lua.pack(true).unwrap()).unwrap();
}
@@ -177,10 +176,12 @@ fn create_userdata(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {}
c.bench_function("create userdata 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [table userdata] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table: LuaTable = lua.create_table().unwrap();
for i in 1..11 {
table.set(i, UserData(i)).unwrap();
@@ -191,20 +192,80 @@ fn create_userdata(c: &mut Criterion) {
});
}
fn call_userdata_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, this, ()| Ok(this.0));
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
c.bench_function("call [userdata method] 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_async_userdata_method(c: &mut Criterion) {
#[derive(Clone, Copy)]
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("method", |_, this, ()| async move { Ok(this.0) });
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
c.bench_function("call async [userdata method] 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
},
BatchSize::SmallInput,
);
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(200)
.sample_size(300)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
create_table,
create_array,
create_string_table,
call_add_function,
call_add_callback,
call_append_callback,
call_lua_function,
call_sum_callback,
call_async_sum_callback,
call_concat_callback,
create_registry_values,
create_userdata,
call_userdata_method,
call_async_userdata_method,
}
criterion_main!(benches);
+5
View File
@@ -0,0 +1,5 @@
use std::path::PathBuf;
pub fn probe_lua() -> PathBuf {
unreachable!()
}
+65 -52
View File
@@ -1,106 +1,119 @@
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::{BufRead, BufReader, Result};
use std::ops::Bound;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
fn get_env_var(name: &str) -> String {
match env::var(name) {
Ok(val) => val,
Err(env::VarError::NotPresent) => String::new(),
Err(err) => panic!("cannot get {}: {}", name, err),
}
}
pub fn probe_lua() -> PathBuf {
let include_dir = env::var_os("LUA_INC").unwrap_or(OsString::new());
let lib_dir = env::var_os("LUA_LIB").unwrap_or(OsString::new());
let lua_lib = env::var_os("LUA_LIB_NAME").unwrap_or(OsString::new());
let include_dir = get_env_var("LUA_INC");
let lib_dir = get_env_var("LUA_LIB");
let lua_lib = get_env_var("LUA_LIB_NAME");
println!("cargo:rerun-if-env-changed=LUA_INC");
println!("cargo:rerun-if-env-changed=LUA_LIB");
println!("cargo:rerun-if-env-changed=LUA_LIB_NAME");
println!("cargo:rerun-if-env-changed=LUA_LINK");
if include_dir != "" && lib_dir != "" && lua_lib != "" {
let _version = use_custom_lua(&include_dir, &lib_dir, &lua_lib).unwrap();
let need_lua_lib = cfg!(any(not(feature = "module"), target_os = "windows"));
if include_dir != "" {
if need_lua_lib {
if lib_dir == "" {
panic!("LUA_LIB is not set");
}
if lua_lib == "" {
panic!("LUA_LIB_NAME is not set");
}
let mut link_lib = "";
if get_env_var("LUA_LINK") == "static" {
link_lib = "static=";
};
println!("cargo:rustc-link-search=native={}", lib_dir);
println!("cargo:rustc-link-lib={}{}", link_lib, lua_lib);
}
return PathBuf::from(include_dir);
}
// Find using via pkg-config
// Find using `pkg-config`
#[cfg(feature = "lua54")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.4"), Bound::Excluded("5.5")))
.cargo_metadata(need_lua_lib)
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new()
.cargo_metadata(need_lua_lib)
.probe("lua5.4");
}
lua.unwrap().include_paths[0].clone()
}
#[cfg(feature = "lua53")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.3"), Bound::Excluded("5.4")))
.cargo_metadata(need_lua_lib)
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.3");
lua = pkg_config::Config::new()
.cargo_metadata(need_lua_lib)
.probe("lua5.3");
}
return lua.unwrap().include_paths[0].clone();
lua.unwrap().include_paths[0].clone()
}
#[cfg(feature = "lua52")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.2"), Bound::Excluded("5.3")))
.cargo_metadata(need_lua_lib)
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.2");
lua = pkg_config::Config::new()
.cargo_metadata(need_lua_lib)
.probe("lua5.2");
}
return lua.unwrap().include_paths[0].clone();
lua.unwrap().include_paths[0].clone()
}
#[cfg(feature = "lua51")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.1"), Bound::Excluded("5.2")))
.cargo_metadata(need_lua_lib)
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.1");
lua = pkg_config::Config::new()
.cargo_metadata(need_lua_lib)
.probe("lua5.1");
}
return lua.unwrap().include_paths[0].clone();
lua.unwrap().include_paths[0].clone()
}
#[cfg(feature = "luajit")]
{
let lua = pkg_config::Config::new()
.range_version((Bound::Included("2.1.0"), Bound::Unbounded))
.range_version((Bound::Included("2.0.5"), Bound::Unbounded))
.cargo_metadata(need_lua_lib)
.probe("luajit");
return lua.unwrap().include_paths[0].clone();
lua.unwrap().include_paths[0].clone()
}
}
fn use_custom_lua<S: AsRef<Path>>(include_dir: &S, lib_dir: &S, lua_lib: &S) -> Result<String> {
let mut version_found = String::new();
// Find LUA_VERSION_NUM
let mut lua_h_path = include_dir.as_ref().to_owned();
lua_h_path.push("lua.h");
let f = File::open(lua_h_path)?;
let reader = BufReader::new(f);
for line in reader.lines() {
let line = line?;
let parts = line.split_whitespace().collect::<Vec<_>>();
if parts.len() == 3 && parts[1] == "LUA_VERSION_NUM" {
version_found = parts[2].to_string();
}
}
let mut link_lib = String::new();
if env::var("LUA_LINK").unwrap_or(String::new()) == "static" {
link_lib = "static=".to_string();
}
println!(
"cargo:rustc-link-search=native={}",
lib_dir.as_ref().display()
);
println!(
"cargo:rustc-link-lib={}{}",
link_lib,
lua_lib.as_ref().display()
);
Ok(version_found)
}
+4 -5
View File
@@ -1,11 +1,8 @@
use std::path::PathBuf;
#[cfg(any(feature = "lua53", feature = "lua52", feature = "lua51"))]
use lua_src;
#[cfg(feature = "luajit")]
use luajit_src;
pub fn probe_lua() -> PathBuf {
#[cfg(feature = "lua54")]
let artifacts = lua_src::Build::new().build(lua_src::Lua54);
#[cfg(feature = "lua53")]
let artifacts = lua_src::Build::new().build(lua_src::Lua53);
#[cfg(feature = "lua52")]
@@ -15,6 +12,8 @@ pub fn probe_lua() -> PathBuf {
#[cfg(feature = "luajit")]
let artifacts = luajit_src::Build::new().build();
#[cfg(not(feature = "module"))]
artifacts.print_cargo_metadata();
artifacts.include_dir().to_owned()
}
+176 -10
View File
@@ -1,12 +1,47 @@
#![allow(unreachable_code)]
use std::env;
use std::io::{Error, ErrorKind, Result};
use std::fs::File;
use std::io::{Error, ErrorKind, Result, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
#[cfg_attr(feature = "vendored", path = "find_vendored.rs")]
#[cfg_attr(not(feature = "vendored"), path = "find_normal.rs")]
#[cfg_attr(
all(
feature = "vendored",
any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)
),
path = "find_vendored.rs"
)]
#[cfg_attr(
all(
not(feature = "vendored"),
any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)
),
path = "find_normal.rs"
)]
#[cfg_attr(
not(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)),
path = "find_dummy.rs"
)]
mod find;
trait CommandExt {
@@ -57,30 +92,161 @@ fn build_glue<P: AsRef<Path> + std::fmt::Debug>(include_path: &P) {
.unwrap();
}
// When cross-compiling, we cannot use `build_glue` as we cannot run the generated
// executable. Instead, let's take a stab at synthesizing the likely values.
// If you're cross-compiling and using a non-vendored library then there is a chance
// that the values selected here may be incorrect, but we have no way to determine
// that here.
fn generate_glue() -> Result<()> {
let build_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let mut glue = File::create(build_dir.join("glue.rs"))?;
writeln!(
glue,
"/* This file was generated by build/main.rs; do not modify by hand */"
)?;
writeln!(glue, "use std::os::raw::*;")?;
writeln!(glue, "/* luaconf.h */")?;
let pointer_bit_width: usize = env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
.unwrap()
.parse()
.unwrap();
writeln!(
glue,
"pub const LUA_EXTRASPACE: c_int = {} / 8;",
pointer_bit_width
)?;
// This is generally hardcoded to this size
writeln!(glue, "pub const LUA_IDSIZE: c_int = 60;")?;
// Unless the target is restricted, the defaults are 64 bit
writeln!(glue, "pub type LUA_NUMBER = c_double;")?;
writeln!(glue, "pub type LUA_INTEGER = i64;")?;
writeln!(glue, "pub type LUA_UNSIGNED = u64;")?;
writeln!(glue, "/* lua.h */")?;
let version = if cfg!(any(feature = "luajit", feature = "lua51")) {
(5, 1, 0)
} else if cfg!(feature = "lua52") {
(5, 2, 0)
} else if cfg!(feature = "lua53") {
(5, 3, 0)
} else if cfg!(feature = "lua54") {
(5, 4, 0)
} else {
unreachable!();
};
writeln!(
glue,
"pub const LUA_VERSION_NUM: c_int = {};",
(version.0 * 100) + version.1
)?;
let max_stack = if pointer_bit_width >= 32 {
1_000_000
} else {
15_000
};
writeln!(
glue,
"pub const LUA_REGISTRYINDEX: c_int = -{} - 1000;",
max_stack
)?;
// These two are only defined in lua 5.1
writeln!(glue, "pub const LUA_ENVIRONINDEX: c_int = -10001;")?;
writeln!(glue, "pub const LUA_GLOBALSINDEX: c_int = -10002;")?;
writeln!(glue, "/* lauxlib.h */")?;
// This is only defined in lua 5.3 and up, but we can always generate its value here,
// even if we don't use it.
// This matches the default definition in lauxlib.h
writeln!(glue, "pub const LUAL_NUMSIZES: c_int = std::mem::size_of::<LUA_INTEGER>() as c_int * 16 + std::mem::size_of::<LUA_NUMBER>() as c_int;")?;
writeln!(glue, "/* lualib.h */")?;
write!(
glue,
r#"
#[cfg(feature = "luajit")]
pub const LUA_BITLIBNAME: &str = "bit";
#[cfg(not(feature = "luajit"))]
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_JITLIBNAME: &str = "jit";
pub const LUA_FFILIBNAME: &str = "ffi";
"#
)?;
Ok(())
}
fn main() {
#[cfg(not(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)))]
panic!("You must enable one of the features: lua53, lua52, lua51, luajit");
compile_error!("You must enable one of the features: lua54, lua53, lua52, lua51, luajit");
#[cfg(all(
feature = "lua54",
any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)
))]
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
#[cfg(all(
feature = "lua53",
any(feature = "lua52", feature = "lua51", feature = "luajit")
))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua52", any(feature = "lua51", feature = "luajit")))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua51", feature = "luajit"))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua51", feature = "luajit"))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
// We don't support "vendored module" mode on windows
#[cfg(all(feature = "vendored", feature = "module", target_os = "windows"))]
compile_error!(
"Vendored (static) builds are not supported for modules on Windows.\n"
+ "Please, use `pkg-config` or custom mode to link to a Lua dll."
);
let include_dir = find::probe_lua();
build_glue(&include_dir);
if env::var("TARGET").unwrap() != env::var("HOST").unwrap() {
generate_glue().unwrap();
} else {
build_glue(&include_dir);
println!("cargo:rerun-if-changed=src/ffi/glue/glue.c");
}
let mut shim_cc = cc::Build::new();
shim_cc
.include(include_dir)
.define("COMPAT53_INCLUDE_SOURCE", None);
#[cfg(feature = "luajit")]
shim_cc.define("COMPAT53_LUAJIT", None);
shim_cc.file("src/ffi/shim/shim.c").compile("shim");
println!("cargo:rerun-if-changed=src/ffi/shim");
println!("cargo:rerun-if-changed=build");
}
+83
View File
@@ -0,0 +1,83 @@
use std::collections::HashMap;
use std::sync::Arc;
use bstr::BString;
use hyper::body::{Body as HyperBody, HttpBody as _};
use hyper::Client as HyperClient;
use tokio::sync::Mutex;
use mlua::{Error, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct BodyReader(Arc<Mutex<HyperBody>>);
impl BodyReader {
fn new(body: HyperBody) -> Self {
BodyReader(Arc::new(Mutex::new(body)))
}
}
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("read", |_, reader, ()| async move {
let mut reader = reader.0.lock().await;
if let Some(bytes) = reader.data().await {
let bytes = bytes.map_err(Error::external)?;
return Ok(Some(BString::from(bytes.as_ref())));
}
Ok(None)
});
}
}
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new();
let fetch_url = lua.create_async_function(|lua, uri: String| async move {
let client = HyperClient::new();
let uri = uri.parse().map_err(Error::external)?;
let resp = client.get(uri).await.map_err(Error::external)?;
let lua_resp = lua.create_table()?;
lua_resp.set("status", resp.status().as_u16())?;
let mut headers = HashMap::new();
for (key, value) in resp.headers().iter() {
headers
.entry(key.as_str())
.or_insert(Vec::new())
.push(value.to_str().unwrap());
}
lua_resp.set("headers", headers)?;
lua_resp.set("body", BodyReader::new(resp.into_body()))?;
Ok(lua_resp)
})?;
let globals = lua.globals();
globals.set("fetch_url", fetch_url)?;
let f = lua
.load(
r#"
local res = fetch_url(...)
print(res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
print(key..": "..val)
end
end
repeat
local body = res.body:read()
if body then
print(body)
end
until not body
"#,
)
.into_function()?;
f.call_async("http://httpbin.org/ip").await
}
+41
View File
@@ -0,0 +1,41 @@
use mlua::{Error, Lua, LuaSerdeExt, Result};
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null())?;
let fetch_json = lua.create_async_function(|lua, uri: String| async move {
let resp = reqwest::get(&uri)
.await
.and_then(|resp| resp.error_for_status())
.map_err(Error::external)?;
let json = resp
.json::<serde_json::Value>()
.await
.map_err(Error::external)?;
lua.to_value(&json)
})?;
globals.set("fetch_json", fetch_json)?;
let f = lua
.load(
r#"
function print_r(t, indent)
local indent = indent or ''
for k, v in pairs(t) do
io.write(indent, tostring(k))
if type(v) == "table" then io.write(':\n') print_r(v, indent..' ')
else io.write(': ', v == null and "null" or tostring(v), '\n') end
end
end
local res = fetch_json(...)
print_r(res)
"#,
)
.into_function()?;
f.call_async("https://httpbin.org/anything?arg0=val0").await
}
+101
View File
@@ -0,0 +1,101 @@
use std::net::SocketAddr;
use std::sync::Arc;
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use mlua::{Error, Function, Lua, Result, Table, UserData, UserDataMethods};
#[derive(Clone)]
struct LuaRequest(Arc<(SocketAddr, Request<Body>)>);
impl UserData for LuaRequest {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("remote_addr", |_lua, req, ()| Ok((req.0).0.to_string()));
methods.add_method("method", |_lua, req, ()| Ok((req.0).1.method().to_string()));
}
}
async fn run_server(handler: Function<'static>) -> Result<()> {
let make_svc = make_service_fn(|socket: &AddrStream| {
let remote_addr = socket.remote_addr();
let handler = handler.clone();
async move {
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
let handler = handler.clone();
async move {
let lua_req = LuaRequest(Arc::new((remote_addr, req)));
let lua_resp: Table = handler.call_async(lua_req).await?;
let body = lua_resp
.get::<_, Option<String>>("body")?
.unwrap_or_default();
let mut resp = Response::builder()
.status(lua_resp.get::<_, Option<u16>>("status")?.unwrap_or(200));
if let Some(headers) = lua_resp.get::<_, Option<Table>>("headers")? {
for pair in headers.pairs::<String, String>() {
let (h, v) = pair?;
resp = resp.header(&h, v);
}
}
Ok::<_, Error>(resp.body(Body::from(body)).unwrap())
}
}))
}
});
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr).executor(LocalExec).serve(make_svc);
println!("Listening on http://{}", addr);
tokio::task::LocalSet::new()
.run_until(server)
.await
.map_err(Error::external)
}
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new().into_static();
let handler: Function = lua
.load(
r#"
function(req)
return {
status = 200,
headers = {
["X-Req-Method"] = req:method(),
["X-Remote-Addr"] = req:remote_addr(),
},
body = "Hello, World!"
}
end
"#,
)
.eval()?;
run_server(handler).await?;
// Consume the static reference and drop it.
// This is safe as long as we don't hold any other references to Lua
// or alive resources.
unsafe { Lua::from_static(lua) };
Ok(())
}
#[derive(Clone, Copy, Debug)]
struct LocalExec;
impl<F> hyper::rt::Executor<F> for LocalExec
where
F: std::future::Future + 'static, // not requiring `Send`
{
fn execute(&self, fut: F) {
tokio::task::spawn_local(fut);
}
}
+128
View File
@@ -0,0 +1,128 @@
use std::sync::Arc;
use bstr::BString;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::Mutex;
use tokio::task;
use mlua::{Function, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct LuaTcp;
#[derive(Clone)]
struct LuaTcpListener(Arc<Mutex<TcpListener>>);
#[derive(Clone)]
struct LuaTcpStream(Arc<Mutex<TcpStream>>);
impl UserData for LuaTcp {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("bind", |_, addr: String| async move {
let listener = TcpListener::bind(addr).await?;
Ok(LuaTcpListener(Arc::new(Mutex::new(listener))))
});
}
}
impl UserData for LuaTcpListener {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("accept", |_, listener, ()| async move {
let (stream, _) = listener.0.lock().await.accept().await?;
Ok(LuaTcpStream(Arc::new(Mutex::new(stream))))
});
}
}
impl UserData for LuaTcpStream {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("peer_addr", |_, stream, ()| async move {
Ok(stream.0.lock().await.peer_addr()?.to_string())
});
methods.add_async_method("read", |_, stream, size: usize| async move {
let mut buf = vec![0; size];
let n = stream.0.lock().await.read(&mut buf).await?;
buf.truncate(n);
Ok(BString::from(buf))
});
methods.add_async_method("write", |_, stream, data: BString| async move {
let n = stream.0.lock().await.write(&data).await?;
Ok(n)
});
methods.add_async_method("close", |_, stream, ()| async move {
stream.0.lock().await.shutdown().await?;
Ok(())
});
}
}
async fn run_server(lua: &'static Lua) -> Result<()> {
let spawn = lua.create_function(move |_, func: Function| {
task::spawn_local(async move { func.call_async::<_, ()>(()).await });
Ok(())
})?;
let globals = lua.globals();
globals.set("tcp", LuaTcp)?;
globals.set("spawn", spawn)?;
let server = lua
.load(
r#"
local addr = ...
local listener = tcp.bind(addr)
print("listening on "..addr)
local accept_new = true
while true do
local stream = listener:accept()
local peer_addr = stream:peer_addr()
print("connected from "..peer_addr)
if not accept_new then
return
end
spawn(function()
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") -- trim
print("["..peer_addr.."] "..data)
if data == "bye" then
stream:write("bye bye\n")
stream:close()
return
end
if data == "exit" then
stream:close()
accept_new = false
return
end
stream:write("echo: "..data.."\n")
end
end)
end
"#,
)
.into_function()?;
task::LocalSet::new()
.run_until(server.call_async::<_, ()>("0.0.0.0:1234"))
.await
}
#[tokio::main]
async fn main() {
let lua = Lua::new().into_static();
run_server(lua).await.unwrap();
// Consume the static reference and drop it.
// This is safe as long as we don't hold any other references to Lua
// or alive resources.
unsafe { Lua::from_static(lua) };
}
+4 -5
View File
@@ -164,11 +164,10 @@ fn main() -> Result<()> {
< f32::EPSILON
);
// Normally, Rust types passed to `Lua` must be `Send`, because `Lua` itself is `Send`, and
// must be `'static`, because there is no way to be sure of their lifetime inside the Lua
// state. There is, however, a limited way to lift both of these requirements. You can
// call `Lua::scope` to create userdata and callbacks types that only live for as long
// as the call to scope, but do not have to be `Send` OR `'static`.
// Normally, Rust types passed to `Lua` must be `'static`, because there is no way to be
// sure of their lifetime inside the Lua state. There is, however, a limited way to lift this
// requirement. You can call `Lua::scope` to create userdata and callbacks types that only live
// for as long as the call to scope, but do not have to be `'static` (and `Send`).
{
let mut rust_val = 0;
+5
View File
@@ -0,0 +1,5 @@
[target.x86_64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "rust_module"
version = "0.0.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[features]
lua54 = ["mlua/lua54"]
lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
lua51 = ["mlua/lua51"]
luajit = ["mlua/luajit"]
vendored = ["mlua/vendored"]
[dependencies]
mlua = { path = "../..", features = ["module"] }
+17
View File
@@ -0,0 +1,17 @@
use mlua::prelude::*;
fn sum(_: &Lua, (a, b): (i64, i64)) -> LuaResult<i64> {
Ok(a + b)
}
fn used_memory(lua: &Lua, _: ()) -> LuaResult<usize> {
Ok(lua.used_memory())
}
#[mlua::lua_module]
fn rust_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("sum", lua.create_function(sum)?)?;
exports.set("used_memory", lua.create_function(used_memory)?)?;
Ok(exports)
}
+66
View File
@@ -0,0 +1,66 @@
use mlua::{Error, Lua, LuaSerdeExt, Result, UserData, Value};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
enum Transmission {
Manual,
Automatic,
}
#[derive(Serialize, Deserialize)]
struct Engine {
v: u32,
kw: u32,
}
#[derive(Serialize, Deserialize)]
struct Car {
active: bool,
model: String,
transmission: Transmission,
engine: Engine,
}
impl UserData for Car {}
fn main() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
// Create Car struct from a Lua table
let car: Car = lua.from_value(lua.load(r#"
{active = true, model = "Volkswagen Golf", transmission = "Automatic", engine = {v = 1499, kw = 90}}
"#).eval()?)?;
// Set it as (serializable) userdata
globals.set("null", lua.null())?;
globals.set("array_mt", lua.array_metatable())?;
globals.set("car", lua.create_ser_userdata(car)?)?;
// Create a Lua table with multiple data types
let val: Value = lua
.load(r#"{driver = "Boris", car = car, price = null, points = setmetatable({}, array_mt)}"#)
.eval()?;
// Serialize the table above to JSON
let json_str = serde_json::to_string(&val).map_err(Error::external)?;
println!("{}", json_str);
// Create Lua Value from JSON (or any serializable type)
let json = serde_json::json!({
"key": "value",
"null": null,
"array": [],
});
globals.set("json_value", lua.to_value(&json)?)?;
lua.load(
r#"
assert(json_value["key"] == "value")
assert(json_value["null"] == null)
assert(#(json_value["array"]) == 0)
"#,
)
.exec()?;
Ok(())
}
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "mlua_derive"
version = "0.2.0"
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
version = "0.5.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua"
+2
View File
@@ -21,6 +21,8 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let ext_entrypoint_name = Ident::new(&format!("luaopen_{}", item.sig.ident), Span::call_site());
let wrapped = quote_spanned! { span =>
mlua::require_module_feature!();
#[no_mangle]
unsafe extern "C" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> std::os::raw::c_int {
#item
+165 -10
View File
@@ -1,4 +1,5 @@
use std::collections::{BTreeMap, HashMap};
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::{CStr, CString};
use std::hash::{BuildHasher, Hash};
use std::string::String as StdString;
@@ -12,7 +13,7 @@ use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{LightUserData, Number};
use crate::types::{LightUserData, MaybeSend};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{FromLua, Nil, ToLua, Value};
@@ -122,7 +123,7 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
}
}
impl<'lua, T: 'static + Send + UserData> ToLua<'lua> for T {
impl<'lua, T: 'static + MaybeSend + UserData> ToLua<'lua> for T {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(lua.create_userdata(self)?))
}
@@ -167,7 +168,7 @@ impl<'lua> ToLua<'lua> for bool {
}
impl<'lua> FromLua<'lua> for bool {
fn from_lua(v: Value, _: &'lua Lua) -> Result<Self> {
fn from_lua(v: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match v {
Value::Nil => Ok(false),
Value::Boolean(b) => Ok(b),
@@ -183,7 +184,7 @@ impl<'lua> ToLua<'lua> for LightUserData {
}
impl<'lua> FromLua<'lua> for LightUserData {
fn from_lua(value: Value, _: &'lua Lua) -> Result<Self> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::LightUserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
@@ -216,12 +217,40 @@ impl<'lua> FromLua<'lua> for StdString {
}
}
impl<'lua, 'a> ToLua<'lua> for &'a str {
impl<'lua> ToLua<'lua> for &str {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self)?))
}
}
impl<'lua> ToLua<'lua> for Cow<'_, str> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
}
impl<'lua> ToLua<'lua> for Box<str> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&*self)?))
}
}
impl<'lua> FromLua<'lua> for Box<str> {
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self> {
let ty = value.type_name();
Ok(lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "Box<str>",
message: Some("expected string or number".to_string()),
})?
.to_str()?
.to_owned()
.into_boxed_str())
}
}
impl<'lua> ToLua<'lua> for CString {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
@@ -250,13 +279,19 @@ impl<'lua> FromLua<'lua> for CString {
}
}
impl<'lua, 'a> ToLua<'lua> for &'a CStr {
impl<'lua> ToLua<'lua> for &CStr {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
}
}
impl<'lua, 'a> ToLua<'lua> for BString {
impl<'lua> ToLua<'lua> for Cow<'_, CStr> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
}
}
impl<'lua> ToLua<'lua> for BString {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?))
}
@@ -278,7 +313,7 @@ impl<'lua> FromLua<'lua> for BString {
}
}
impl<'lua, 'a> ToLua<'lua> for &BStr {
impl<'lua> ToLua<'lua> for &BStr {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?))
}
@@ -345,7 +380,13 @@ macro_rules! lua_convert_float {
($x:ty) => {
impl<'lua> ToLua<'lua> for $x {
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Number(self as Number))
cast(self)
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x),
to: "number",
message: Some("out of range".to_string()),
})
.map(Value::Number)
}
}
@@ -373,6 +414,70 @@ macro_rules! lua_convert_float {
lua_convert_float!(f32);
lua_convert_float!(f64);
impl<'lua, T> ToLua<'lua> for &[T]
where
T: Clone + ToLua<'lua>,
{
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(
lua.create_sequence_from(self.iter().cloned())?,
))
}
}
macro_rules! lua_convert_array {
($($N:literal)+) => {
$(
impl<'lua, T> ToLua<'lua> for [T; $N]
where
T: Clone + ToLua<'lua>,
{
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
(&self).to_lua(lua)
}
}
impl<'lua, T> ToLua<'lua> for &[T; $N]
where
T: Clone + ToLua<'lua>,
{
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(
lua.create_sequence_from(self.iter().cloned())?,
))
}
}
)+
}
}
lua_convert_array! {
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Box<[T]> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self.into_vec())?))
}
}
impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Box<[T]> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table.sequence_values().collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "Box<[T]>",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Vec<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self)?))
@@ -437,6 +542,56 @@ impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap<
}
}
impl<'lua, T: Eq + Hash + ToLua<'lua>, S: BuildHasher> ToLua<'lua> for HashSet<T, S> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
}
}
impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> for HashSet<T, S> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "HashSet",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: Ord + ToLua<'lua>> ToLua<'lua> for BTreeSet<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
}
}
impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "BTreeSet",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Option<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
match self {
+133 -18
View File
@@ -1,11 +1,15 @@
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::result::Result as StdResult;
use std::str::Utf8Error;
use std::string::String as StdString;
use std::sync::Arc;
/// Error type returned by `mlua` methods.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Error {
/// Syntax error while parsing Lua source code.
SyntaxError {
@@ -33,6 +37,18 @@ pub enum Error {
/// The Lua VM returns this error when there is an error running a `__gc` metamethod.
#[cfg(any(feature = "lua53", feature = "lua52"))]
GarbageCollectorError(StdString),
/// Potentially unsafe action in safe mode.
SafetyError(StdString),
/// Setting memory limit is not available.
///
/// This error can only happen when Lua state was not created by us and does not have the
/// custom allocator attached.
MemoryLimitNotAvailable,
/// Main thread is not available.
///
/// This error can only happen in Lua5.1/LuaJIT module mode, when module loaded within a coroutine.
/// These Lua versions does not have `LUA_RIDX_MAINTHREAD` registry key.
MainThreadNotAvailable,
/// A mutable callback has triggered Lua code that has called the same mutable callback again.
///
/// This is an error because a mutable callback can only be borrowed mutably once.
@@ -90,6 +106,13 @@ pub enum Error {
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
UserDataTypeMismatch,
/// An [`AnyUserData`] borrow failed because it has been destructed.
///
/// This error can happen either due to to being destructed in a previous __gc, or due to being
/// destructed from exiting a `Lua::scope` call.
///
/// [`AnyUserData`]: struct.AnyUserData.html
UserDataDestructed,
/// An [`AnyUserData`] immutable borrow failed because it is already borrowed mutably.
///
/// This error can occur when a method on a [`UserData`] type calls back into Lua, which then
@@ -108,6 +131,18 @@ pub enum Error {
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserData`]: trait.UserData.html
UserDataBorrowMutError,
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
///
/// [`MetaMethod`]: enum.MetaMethod.html
MetaMethodRestricted(StdString),
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
///
/// [`MetaMethod`]: enum.MetaMethod.html
MetaMethodTypeError {
method: StdString,
type_name: &'static str,
message: Option<StdString>,
},
/// A `RegistryKey` produced from a different Lua state was used.
MismatchedRegistryKey,
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
@@ -117,6 +152,19 @@ pub enum Error {
/// Original error returned by the Rust code.
cause: Arc<Error>,
},
/// A Rust panic that was previosly resumed, returned again.
///
/// This error can occur only when a Rust panic resumed previously was recovered
/// and returned again.
PreviouslyResumedPanic,
/// Serialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
SerializeError(StdString),
/// Deserialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
DeserializeError(StdString),
/// A custom error.
///
/// This can be used for returning user-defined errors from callbacks.
@@ -124,7 +172,10 @@ pub enum Error {
/// Returning `Err(ExternalError(...))` from a Rust callback will raise the error as a Lua
/// error. The Rust code that originally invoked the Lua code then receives a `CallbackError`,
/// from which the original error (and a stack traceback) can be recovered.
#[cfg(feature = "send")]
ExternalError(Arc<dyn StdError + Send + Sync>),
#[cfg(not(feature = "send"))]
ExternalError(Arc<dyn StdError>),
}
/// A specialized `Result` type used by `mlua`'s API.
@@ -142,6 +193,15 @@ impl fmt::Display for Error {
Error::GarbageCollectorError(ref msg) => {
write!(fmt, "garbage collector error: {}", msg)
}
Error::SafetyError(ref msg) => {
write!(fmt, "safety error: {}", msg)
},
Error::MemoryLimitNotAvailable => {
write!(fmt, "setting memory limit is not available")
}
Error::MainThreadNotAvailable => {
write!(fmt, "main thread is not available in Lua 5.1")
}
Error::RecursiveMutCallback => write!(fmt, "mutable callback called recursively"),
Error::CallbackDestructed => write!(
fmt,
@@ -155,22 +215,14 @@ impl fmt::Display for Error {
fmt,
"too many arguments to Function::bind"
),
Error::ToLuaConversionError {
from,
to,
ref message,
} => {
Error::ToLuaConversionError { from, to, ref message } => {
write!(fmt, "error converting {} to Lua {}", from, to)?;
match *message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message),
}
}
Error::FromLuaConversionError {
from,
to,
ref message,
} => {
Error::FromLuaConversionError { from, to, ref message } => {
write!(fmt, "error converting Lua {} to {}", from, to)?;
match *message {
None => Ok(()),
@@ -179,15 +231,35 @@ impl fmt::Display for Error {
}
Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"),
Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"),
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"),
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"),
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {} is restricted", method),
Error::MetaMethodTypeError { ref method, type_name, ref message } => {
write!(fmt, "metamethod {} has unsupported type {}", method, type_name)?;
match *message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message),
}
}
Error::MismatchedRegistryKey => {
write!(fmt, "RegistryKey used from different Lua state")
}
Error::CallbackError { ref traceback, ref cause } => {
write!(fmt, "callback error: {}: {}", cause, traceback)
Error::CallbackError { ref traceback, .. } => {
write!(fmt, "callback error: {}", traceback)
}
Error::ExternalError(ref err) => write!(fmt, "external error: {}", err),
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
}
#[cfg(feature = "serialize")]
Error::SerializeError(ref err) => {
write!(fmt, "serialize error: {}", err)
},
#[cfg(feature = "serialize")]
Error::DeserializeError(ref err) => {
write!(fmt, "deserialize error: {}", err)
},
Error::ExternalError(ref err) => write!(fmt, "{}", err),
}
}
}
@@ -196,26 +268,37 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
Error::CallbackError { ref cause, .. } => Some(cause.as_ref()),
Error::ExternalError(ref err) => Some(err.as_ref()),
Error::ExternalError(ref err) => err.source(),
_ => None,
}
}
}
impl Error {
#[cfg(feature = "send")]
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Error {
Error::ExternalError(err.into().into())
}
#[cfg(not(feature = "send"))]
pub fn external<T: Into<Box<dyn StdError>>>(err: T) -> Error {
Error::ExternalError(err.into().into())
}
}
pub trait ExternalError {
fn to_lua_err(self) -> Error;
}
impl<E> ExternalError for E
where
E: Into<Box<dyn StdError + Send + Sync>>,
{
#[cfg(feature = "send")]
impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E {
fn to_lua_err(self) -> Error {
Error::external(self)
}
}
#[cfg(not(feature = "send"))]
impl<E: Into<Box<dyn StdError>>> ExternalError for E {
fn to_lua_err(self) -> Error {
Error::external(self)
}
@@ -233,3 +316,35 @@ where
self.map_err(|e| e.to_lua_err())
}
}
impl std::convert::From<AddrParseError> for Error {
fn from(err: AddrParseError) -> Self {
Error::external(err)
}
}
impl std::convert::From<IoError> for Error {
fn from(err: IoError) -> Self {
Error::external(err)
}
}
impl std::convert::From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self {
Error::external(err)
}
}
#[cfg(feature = "serialize")]
impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::SerializeError(msg.to_string())
}
}
#[cfg(feature = "serialize")]
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::DeserializeError(msg.to_string())
}
}
+80 -33
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -22,6 +22,8 @@
// Based on github.com/keplerproject/lua-compat-5.3
#![allow(clippy::needless_return)]
use std::ffi::CStr;
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
@@ -42,18 +44,18 @@ use super::lua::{
lua_createtable, lua_dump_old, lua_error, lua_getfield_old, lua_getstack, lua_gettable_old,
lua_gettop, lua_insert, lua_isstring, lua_istable, lua_newuserdata, lua_pop, lua_pushboolean,
lua_pushcfunction, lua_pushfstring, lua_pushinteger, lua_pushliteral, lua_pushlstring_old,
lua_pushnumber, lua_pushthread, lua_pushvalue, lua_rawget_old, lua_rawgeti_old, lua_rawset,
lua_replace, lua_setfield, lua_setglobal, lua_setmetatable, lua_settable, lua_toboolean,
lua_tointeger, lua_tolstring, lua_tonumber, lua_topointer, lua_tostring, lua_touserdata,
lua_type, lua_typename,
lua_pushnil, lua_pushnumber, lua_pushthread, lua_pushvalue, lua_rawget_old, lua_rawgeti_old,
lua_rawset, lua_replace, lua_setfield, lua_setglobal, lua_setmetatable, lua_settable,
lua_toboolean, lua_tointeger, lua_tolstring, lua_tonumber, lua_topointer, lua_tostring,
lua_touserdata, lua_type, lua_typename,
};
#[cfg(any(feature = "lua51", feature = "luajit"))]
use super::lua::{
lua_checkstack, lua_concat, lua_equal, lua_getfenv, lua_getinfo, lua_getmetatable,
lua_isnumber, lua_lessthan, lua_newtable, lua_next, lua_objlen, lua_pushcclosure,
lua_pushlightuserdata, lua_pushnil, lua_pushstring_old, lua_rawequal, lua_remove,
lua_resume_old, lua_setfenv, lua_settop, LUA_OPADD, LUA_OPUNM,
lua_pushlightuserdata, lua_pushstring_old, lua_rawequal, lua_remove, lua_resume_old,
lua_setfenv, lua_settop, LUA_OPADD, LUA_OPUNM,
};
#[cfg(feature = "lua52")]
@@ -114,7 +116,7 @@ unsafe fn compat53_checkmode(
}
}
if mode != ptr::null() && strchr(mode, *modename) == ptr::null() {
if !mode.is_null() && strchr(mode, *modename).is_null() {
lua_pushfstring(
L,
cstr!("attempt to load a %s chunk (mode is '%s')"),
@@ -226,6 +228,7 @@ pub fn lua_upvalueindex(i: c_int) -> c_int {
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_absindex(L: *mut lua_State, mut idx: c_int) -> c_int {
if idx < 0 && idx > lua::LUA_REGISTRYINDEX {
idx += lua_gettop(L) + 1;
@@ -234,7 +237,7 @@ pub unsafe fn lua_absindex(L: *mut lua_State, mut idx: c_int) -> c_int {
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
static COMPAT53_ARITH_CODE: &'static str = r#"
static COMPAT53_ARITH_CODE: &str = r#"
local op,a,b=...
if op == 0 then return a+b
elseif op == 1 then return a-b
@@ -248,6 +251,7 @@ end
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub unsafe fn lua_arith(L: *mut lua_State, op: c_int) {
#[allow(clippy::manual_range_contains)]
if op < LUA_OPADD || op > LUA_OPUNM {
luaL_error(L, cstr!("invalid 'op' argument for lua_arith"));
}
@@ -276,6 +280,7 @@ pub unsafe fn lua_rotate(L: *mut lua_State, mut idx: c_int, mut n: c_int) {
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int) {
let abs_to = lua_absindex(L, toidx);
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
@@ -283,11 +288,12 @@ pub unsafe fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int) {
lua_replace(L, abs_to);
}
#[inline(always)]
pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == lua::LUA_TNUMBER {
let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx);
if i as f64 == n {
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
return 1;
}
}
@@ -295,9 +301,10 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_tonumberx(L: *mut lua_State, i: c_int, isnum: *mut c_int) -> lua_Number {
let n = lua_tonumber(L, i);
if isnum != ptr::null_mut() {
if !isnum.is_null() {
*isnum = if n != 0.0 || lua_isnumber(L, i) != 0 {
1
} else {
@@ -309,29 +316,31 @@ pub unsafe fn lua_tonumberx(L: *mut lua_State, i: c_int, isnum: *mut c_int) -> l
// Implemented for Lua 5.2 as well
// See https://github.com/keplerproject/lua-compat-5.3/issues/40
#[inline(always)]
pub unsafe fn lua_tointegerx(L: *mut lua_State, i: c_int, isnum: *mut c_int) -> lua_Integer {
let mut ok = 0;
let n = lua_tonumberx(L, i, &mut ok);
if ok != 0 {
if n == n as lua_Integer as lua_Number {
if isnum != ptr::null_mut() {
*isnum = 1;
}
return n as lua_Integer;
let n_int = n as lua_Integer;
if ok != 0 && (n - n_int as lua_Number).abs() < lua_Number::EPSILON {
if !isnum.is_null() {
*isnum = 1;
}
return n_int;
}
if isnum != ptr::null_mut() {
if !isnum.is_null() {
*isnum = 0;
}
return 0;
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize {
lua_objlen(L, idx)
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_compare(L: *mut lua_State, mut idx1: c_int, mut idx2: c_int, op: c_int) -> c_int {
match op {
lua::LUA_OPEQ => lua_equal(L, idx1, idx2),
@@ -352,6 +361,7 @@ pub unsafe fn lua_compare(L: *mut lua_State, mut idx1: c_int, mut idx2: c_int, o
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_pushlstring(L: *mut lua_State, s: *const c_char, l: usize) -> *const c_char {
if l == 0 {
lua_pushlstring_old(L, cstr!(""), 0);
@@ -362,6 +372,7 @@ pub unsafe fn lua_pushlstring(L: *mut lua_State, s: *const c_char, l: usize) ->
}
#[cfg(feature = "lua52")]
#[inline(always)]
pub unsafe fn lua_pushlstring(L: *mut lua_State, s: *const c_char, l: usize) -> *const c_char {
if l == 0 {
lua_pushlstring_old(L, cstr!(""), 0)
@@ -371,27 +382,32 @@ pub unsafe fn lua_pushlstring(L: *mut lua_State, s: *const c_char, l: usize) ->
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char {
lua_pushstring_old(L, s);
lua_tostring(L, -1)
}
#[cfg(feature = "lua52")]
#[inline(always)]
pub unsafe fn lua_getglobal(L: *mut lua_State, var: *const c_char) -> c_int {
lua_getglobal_old(L, var);
lua_type(L, -1)
}
#[inline(always)]
pub unsafe fn lua_gettable(L: *mut lua_State, idx: c_int) -> c_int {
lua_gettable_old(L, idx);
lua_type(L, -1)
}
#[inline(always)]
pub unsafe fn lua_getfield(L: *mut lua_State, idx: c_int, k: *const c_char) -> c_int {
lua_getfield_old(L, idx, k);
lua_type(L, -1)
}
#[inline(always)]
pub unsafe fn lua_geti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) -> c_int {
idx = lua_absindex(L, idx);
lua_pushinteger(L, n);
@@ -400,18 +416,21 @@ pub unsafe fn lua_geti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) -> c_i
}
// A new version which returns c_int
#[inline(always)]
pub unsafe fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int {
lua_rawget_old(L, idx);
lua_type(L, -1)
}
// A new version which returns c_int
#[inline(always)]
pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int {
lua_rawgeti_old(L, idx, n);
lua_type(L, -1)
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int {
let abs_i = lua_absindex(L, idx);
lua_pushlightuserdata(L, p as *mut c_void);
@@ -420,23 +439,27 @@ pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_
}
#[cfg(feature = "lua52")]
#[inline(always)]
pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int {
lua_rawgetp_old(L, idx, p);
lua_type(L, -1)
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_getuservalue(L: *mut lua_State, idx: c_int) -> c_int {
lua_getfenv(L, idx);
lua_type(L, -1)
}
#[cfg(feature = "lua52")]
#[inline(always)]
pub unsafe fn lua_getuservalue(L: *mut lua_State, idx: c_int) -> c_int {
lua_getuservalue_old(L, idx);
lua_type(L, -1)
}
#[inline(always)]
pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
idx = lua_absindex(L, idx);
@@ -446,6 +469,7 @@ pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
let abs_i = lua_absindex(L, idx);
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
@@ -455,11 +479,13 @@ pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_setuservalue(L: *mut lua_State, idx: c_int) {
luaL_checktype(L, -1, lua::LUA_TTABLE);
lua_setfenv(L, idx);
}
#[inline(always)]
pub unsafe fn lua_dump(
L: *mut lua_State,
writer: lua_Writer,
@@ -470,11 +496,13 @@ pub unsafe fn lua_dump(
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_resume(L: *mut lua_State, _from: *mut lua_State, narg: c_int) -> c_int {
lua_resume_old(L, narg)
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn lua_len(L: *mut lua_State, idx: c_int) {
match lua_type(L, idx) {
lua::LUA_TSTRING => {
@@ -496,6 +524,7 @@ pub unsafe fn lua_len(L: *mut lua_State, idx: c_int) {
}
}
#[inline(always)]
pub unsafe fn lua_stringtonumber(L: *mut lua_State, s: *const c_char) -> usize {
use std::str::FromStr;
@@ -531,7 +560,7 @@ pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void {
let is_main = lua_pushthread(L);
lua_rawget(L, -2);
let mut _ptr = lua_touserdata(L, -1);
if _ptr == ptr::null_mut() {
if _ptr.is_null() {
lua_pop(L, 1);
_ptr = lua_newuserdata(L, LUA_EXTRASPACE as usize);
if is_main != 0 {
@@ -547,7 +576,7 @@ pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void {
lua_pushboolean(L, 1);
lua_rawget(L, -3);
let mptr = lua_touserdata(L, -1);
if mptr != ptr::null_mut() {
if !mptr.is_null() {
ptr::copy_nonoverlapping(mptr, _ptr, LUA_EXTRASPACE as usize)
} else {
ptr::write_bytes(_ptr, 0, LUA_EXTRASPACE as usize);
@@ -573,9 +602,10 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) {
//
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char) {
if lua_checkstack(L, sz + lua::LUA_MINSTACK) == 0 {
if msg != ptr::null() {
if !msg.is_null() {
luaL_error(L, cstr!("stack overflow (%s)"), msg);
} else {
lua_pushliteral(L, "stack overflow");
@@ -588,6 +618,7 @@ pub unsafe fn luaL_checkversion(_L: *mut lua_State) {
// Void
}
#[inline(always)]
pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int {
if luaL_getmetafield_old(L, obj, e) != 0 {
lua_type(L, -1)
@@ -596,6 +627,7 @@ pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char)
}
}
#[inline(always)]
pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int {
if luaL_newmetatable_old(L, tname) != 0 {
lua_pushstring(L, tname);
@@ -607,6 +639,7 @@ pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_in
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -626,6 +659,7 @@ pub unsafe fn luaL_loadbufferx(
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
let mut isnum = 0;
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
@@ -654,7 +688,7 @@ pub unsafe fn luaL_traceback(
0
};
if msg != ptr::null() {
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
}
lua_pushliteral(L, "stack traceback:");
@@ -666,7 +700,7 @@ pub unsafe fn luaL_traceback(
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else {
lua_getinfo(L1, cstr!("Slnt"), &mut ar);
lua_pushfstring(L, cstr!("\n\t%s:"), cstr!("ok") /*ar.short_src*/);
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src.as_ptr());
if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
}
@@ -708,15 +742,14 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
}
}
};
} else {
if lua_isstring(L, -1) == 0 {
luaL_error(L, cstr!("'__tostring' must return a string"));
}
} else if lua_isstring(L, -1) == 0 {
luaL_error(L, cstr!("'__tostring' must return a string"));
}
lua_tolstring(L, -1, len)
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char) {
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_getmetatable(L, tname);
@@ -724,10 +757,11 @@ pub unsafe fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char) {
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn luaL_testudata(L: *mut lua_State, i: c_int, tname: *const c_char) -> *mut c_void {
let mut p = lua_touserdata(L, i);
luaL_checkstack(L, 2, cstr!("not enough stack slots"));
if p == ptr::null_mut() || lua_getmetatable(L, i) == 0 {
if p.is_null() || lua_getmetatable(L, i) == 0 {
return ptr::null_mut();
} else {
luaL_getmetatable(L, tname);
@@ -741,9 +775,10 @@ pub unsafe fn luaL_testudata(L: *mut lua_State, i: c_int, tname: *const c_char)
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline(always)]
pub unsafe fn luaL_setfuncs(L: *mut lua_State, mut l: *const luaL_Reg, nup: c_int) {
luaL_checkstack(L, nup + 1, cstr!("too many upvalues"));
while (*l).name != ptr::null() {
while !(*l).name.is_null() {
// fill the table with given functions
l = l.offset(1);
lua_pushstring(L, (*l).name);
@@ -786,13 +821,25 @@ pub unsafe fn luaL_requiref(
lua_pop(L, 1);
lua_pushcfunction(L, openf);
lua_pushstring(L, modname);
lua_call(L, 1, 1);
lua_pushvalue(L, -1);
lua_setfield(L, -3, modname);
#[cfg(any(feature = "lua52", feature = "lua51"))]
{
lua_call(L, 1, 1);
lua_pushvalue(L, -1);
lua_setfield(L, -3, modname);
}
#[cfg(feature = "luajit")]
{
lua_call(L, 1, 0);
lua_getfield(L, -1, modname);
}
}
if glb != 0 {
if cfg!(any(feature = "lua52", feature = "lua51")) && glb != 0 {
lua_pushvalue(L, -1);
lua_setglobal(L, modname);
}
if cfg!(feature = "luajit") && glb == 0 {
lua_pushnil(L);
lua_setglobal(L, modname);
}
lua_replace(L, -2);
}
+10 -12
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -53,7 +53,7 @@ typedef struct rs_item {
#define RS_INT(name, val) \
{ TY_INT, name, .int_val = val }
#if LUA_VERSION_NUM == 503
#if LUA_VERSION_NUM >= 503
#define TY_LUAINT 1
#define RS_LUAINT(name, val) \
{ TY_LUAINT, name, .lua_int_val = val }
@@ -84,6 +84,8 @@ const char *rs_int_type(int width) {
return "i32";
case 8:
return "i64";
case 16:
return "i128";
}
}
@@ -96,6 +98,8 @@ const char *rs_uint_type(int width) {
return "u32";
case 8:
return "u64";
case 16:
return "u128";
}
}
@@ -139,7 +143,7 @@ int write_int_item(FILE *f, const char *name, int value) {
return fprintf(f, "pub const %s: c_int = %d;\n", name, value);
}
#if LUA_VERSION_NUM == 503
#if LUA_VERSION_NUM >= 503
int write_lua_int_item(FILE *f, const char *name, LUA_INTEGER value) {
return fprintf(f, "pub const %s: LUA_INTEGER = " LUA_INTEGER_FMT ";\n", name,
value);
@@ -152,7 +156,7 @@ int write_str_item(FILE *f, const char *name, const char *value) {
char *buf = malloc(bufsz);
int ret;
escape(value, buf, bufsz);
ret = fprintf(f, "pub const %s: &'static str = \"%s\";\n", name, buf);
ret = fprintf(f, "pub const %s: &str = \"%s\";\n", name, buf);
free(buf);
return ret;
}
@@ -171,7 +175,7 @@ int write_item(FILE *f, const rs_item *c) {
switch (c->type) {
case TY_INT:
return write_int_item(f, c->name, c->int_val);
#if LUA_VERSION_NUM == 503
#if LUA_VERSION_NUM >= 503
case TY_LUAINT:
return write_lua_int_item(f, c->name, c->lua_int_val);
#endif
@@ -221,12 +225,8 @@ int main(int argc, const char **argv) {
// == luaconf.h ==========================================================
RS_COMMENT("luaconf.h"),
RS_STR("LUA_PATH_DEFAULT", LUA_PATH_DEFAULT),
RS_STR("LUA_CPATH_DEFAULT", LUA_CPATH_DEFAULT),
RS_STR("LUA_DIRSEP", LUA_DIRSEP),
RS_INT("LUA_EXTRASPACE", LUA_EXTRASPACE),
RS_INT("LUA_IDSIZE", LUA_IDSIZE),
RS_INT("LUAL_BUFFERSIZE", LUAL_BUFFERSIZE),
RS_TYPE("LUA_NUMBER",
sizeof(LUA_NUMBER) > sizeof(float) ? "c_double" : "c_float"),
RS_TYPE("LUA_INTEGER", rs_int_type(sizeof(LUA_INTEGER))),
@@ -240,8 +240,6 @@ int main(int argc, const char **argv) {
RS_COMMENT("lua.h"),
RS_INT("LUA_VERSION_NUM", LUA_VERSION_NUM),
RS_STR("LUA_VERSION", LUA_VERSION),
RS_STR("LUA_RELEASE", LUA_RELEASE),
RS_INT("LUA_REGISTRYINDEX", LUA_REGISTRYINDEX),
#if LUA_VERSION_NUM == 501
RS_INT("LUA_ENVIRONINDEX", LUA_ENVIRONINDEX),
@@ -251,7 +249,7 @@ int main(int argc, const char **argv) {
// == lauxlib.h ==========================================================
RS_COMMENT("lauxlib.h"),
#if LUA_VERSION_NUM == 503
#if LUA_VERSION_NUM >= 503
RS_INT("LUAL_NUMSIZES", LUAL_NUMSIZES),
#endif
+25 -23
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -28,7 +28,7 @@ use std::ptr;
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use super::glue::LUAL_NUMSIZES;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
@@ -49,10 +49,10 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn luaL_checkversion(L: *mut lua_State) {
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
luaL_checkversion_(
L,
lua::LUA_VERSION_NUM as lua_Number,
@@ -63,19 +63,19 @@ pub unsafe fn luaL_checkversion(L: *mut lua_State) {
}
extern "C" {
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
#[cfg(feature = "lua52")]
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_old(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, l: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
@@ -90,20 +90,20 @@ extern "C" {
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
pub fn luaL_optinteger(L: *mut lua_State, arg: c_int, def: lua_Integer) -> lua_Integer;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char);
pub fn luaL_checktype(L: *mut lua_State, arg: c_int, t: c_int);
pub fn luaL_checkany(L: *mut lua_State, arg: c_int);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
#[link_name = "luaL_newmetatable"]
pub fn luaL_newmetatable_old(L: *mut lua_State, tname: *const c_char) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char);
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_testudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
@@ -118,9 +118,9 @@ extern "C" {
lst: *const *const c_char,
) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_fileresult(L: *mut lua_State, stat: c_int, fname: *const c_char) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_execresult(L: *mut lua_State, stat: c_int) -> c_int;
}
@@ -132,21 +132,21 @@ extern "C" {
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: c_int);
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char)
-> c_int;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub fn luaL_loadfile(L: *mut lua_State, filename: *const c_char) -> c_int;
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
luaL_loadfilex(L, f, ptr::null())
}
extern "C" {
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -165,9 +165,11 @@ extern "C" {
pub fn luaL_newstate() -> *mut lua_State;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer;
// TODO (lua54): luaL_addgsub
pub fn luaL_gsub(
L: *mut lua_State,
s: *const c_char,
@@ -175,17 +177,17 @@ extern "C" {
r: *const c_char,
) -> *const c_char;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_setfuncs(L: *mut lua_State, l: *const luaL_Reg, nup: c_int);
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
// Skip Lua 5.2 implementation in favor of the compat53 one
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn luaL_requiref(
L: *mut lua_State,
modname: *const c_char,
@@ -288,7 +290,7 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn luaL_loadbuffer(
L: *mut lua_State,
@@ -301,4 +303,4 @@ pub unsafe fn luaL_loadbuffer(
// TODO: Add buffer API
// omitted: old module system compatibility
// omitted: old module system compatibility (removed in 5.4)
+174 -71
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,20 +23,23 @@
//! Contains definitions from `lua.h`.
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
use std::os::raw::c_uchar;
use std::os::raw::{c_char, c_int, c_void};
#[cfg(feature = "lua54")]
use std::os::raw::{c_uint, c_ushort};
use std::ptr;
use super::luaconf;
pub use super::glue::{LUA_RELEASE, LUA_VERSION, LUA_VERSION_NUM};
pub use super::glue::LUA_REGISTRYINDEX;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use super::glue::{LUA_ENVIRONINDEX, LUA_GLOBALSINDEX};
pub use super::glue::{LUA_REGISTRYINDEX, LUA_VERSION_NUM};
pub const LUA_SIGNATURE: &'static [u8] = b"\x1bLua";
#[cfg(not(feature = "luajit"))]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
#[cfg(feature = "luajit")]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLJ";
// option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
@@ -51,13 +54,14 @@ pub use super::compat53::{
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use super::compat53::{
lua_absindex, lua_arith, lua_compare, lua_copy, lua_len, lua_pushglobaltable, lua_pushstring,
lua_rawlen, lua_rawsetp, lua_resume, lua_setuservalue, lua_tonumberx, lua_upvalueindex,
lua_rawlen, lua_rawsetp, lua_resume as lua_resume_53, lua_setuservalue, lua_tonumberx,
lua_upvalueindex,
};
#[cfg(feature = "lua52")]
pub use super::compat53::lua_getglobal;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub fn lua_upvalueindex(i: c_int) -> c_int {
LUA_REGISTRYINDEX - i
@@ -71,7 +75,7 @@ pub const LUA_ERRSYNTAX: c_int = 3;
pub const LUA_ERRMEM: c_int = 4;
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub const LUA_ERRGCMM: c_int = 5;
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[cfg(any(feature = "lua54", feature = "lua51", feature = "luajit"))]
pub const LUA_ERRERR: c_int = 5;
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub const LUA_ERRERR: c_int = 6;
@@ -91,6 +95,8 @@ pub const LUA_TFUNCTION: c_int = 6;
pub const LUA_TUSERDATA: c_int = 7;
pub const LUA_TTHREAD: c_int = 8;
#[cfg(feature = "lua54")]
pub const LUA_NUMTYPES: c_int = 9;
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub const LUA_NUMTAGS: c_int = 9;
@@ -98,11 +104,11 @@ pub const LUA_NUMTAGS: c_int = 9;
pub const LUA_MINSTACK: c_int = 20;
// predefined values in the registry
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub const LUA_RIDX_MAINTHREAD: lua_Integer = 1;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub const LUA_RIDX_GLOBALS: lua_Integer = 2;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub const LUA_RIDX_LAST: lua_Integer = LUA_RIDX_GLOBALS;
/// A Lua number, usually equivalent to `f64`.
@@ -115,14 +121,14 @@ pub type lua_Integer = luaconf::LUA_INTEGER;
pub type lua_Unsigned = luaconf::LUA_UNSIGNED;
// type for continuation-function contexts
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub type lua_KContext = luaconf::LUA_KCONTEXT;
/// Type for native functions that can be passed to Lua.
pub type lua_CFunction = unsafe extern "C" fn(L: *mut lua_State) -> c_int;
// Type for continuation functions
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub type lua_KFunction =
unsafe extern "C" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int;
@@ -132,7 +138,7 @@ pub type lua_Reader =
pub type lua_Writer =
unsafe extern "C" 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.
pub type lua_Alloc = unsafe extern "C" fn(
ud: *mut c_void,
ptr: *mut c_void,
@@ -140,19 +146,28 @@ pub type lua_Alloc = unsafe extern "C" fn(
nsize: usize,
) -> *mut c_void;
// Type for warning functions
#[cfg(feature = "lua54")]
pub type lua_WarnFunction =
unsafe extern "C" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
extern "C" {
// state manipulation
pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State;
pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
#[cfg(feature = "lua54")]
pub fn lua_resetthread(L: *mut lua_State) -> c_int;
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
#[cfg(feature = "lua54")]
pub fn lua_version(L: *mut lua_State) -> lua_Number;
#[cfg(feature = "lua53")]
pub fn lua_version(L: *mut lua_State) -> *const lua_Number;
// basic stack manipulation
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_absindex(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_gettop(L: *mut lua_State) -> c_int;
pub fn lua_settop(L: *mut lua_State, idx: c_int);
@@ -163,9 +178,9 @@ extern "C" {
pub fn lua_insert(L: *mut lua_State, idx: c_int);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
pub fn lua_replace(L: *mut lua_State, idx: c_int);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_rotate(L: *mut lua_State, idx: c_int, n: c_int);
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int);
pub fn lua_checkstack(L: *mut lua_State, sz: c_int) -> c_int;
@@ -175,7 +190,7 @@ extern "C" {
pub fn lua_isnumber(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isstring(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_iscfunction(L: *mut lua_State, idx: c_int) -> c_int;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isuserdata(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_type(L: *mut lua_State, idx: c_int) -> c_int;
@@ -183,17 +198,17 @@ extern "C" {
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub fn lua_tointeger(L: *mut lua_State, idx: c_int) -> lua_Integer;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize;
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> lua_CFunction;
pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
@@ -215,31 +230,31 @@ pub const LUA_OPPOW: c_int = 5;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
pub const LUA_OPUNM: c_int = 6;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPMOD: c_int = 3;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPPOW: c_int = 4;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPDIV: c_int = 5;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPIDIV: c_int = 6;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPBAND: c_int = 7;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPBOR: c_int = 8;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPBXOR: c_int = 9;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPSHL: c_int = 10;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPSHR: c_int = 11;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPUNM: c_int = 12;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const LUA_OPBNOT: c_int = 13;
extern "C" {
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_arith(L: *mut lua_State, op: c_int);
}
@@ -253,7 +268,7 @@ extern "C" {
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub fn lua_lessthan(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
@@ -263,13 +278,13 @@ extern "C" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_pushlstring(L: *mut lua_State, s: *const c_char, l: usize) -> *const c_char;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
#[link_name = "lua_pushlstring"]
pub fn lua_pushlstring_old(L: *mut lua_State, s: *const c_char, l: usize) -> *const c_char;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char;
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[link_name = "lua_pushstring"]
@@ -286,49 +301,59 @@ extern "C" {
// get functions (Lua -> stack)
extern "C" {
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_getglobal(L: *mut lua_State, var: *const c_char) -> c_int;
#[cfg(feature = "lua52")]
#[link_name = "lua_getglobal"]
pub fn lua_getglobal_old(L: *mut lua_State, var: *const c_char);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_gettable(L: *mut lua_State, idx: c_int) -> c_int;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
#[link_name = "lua_gettable"]
pub fn lua_gettable_old(L: *mut lua_State, idx: c_int);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_getfield(L: *mut lua_State, idx: c_int, k: *const c_char) -> c_int;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
#[link_name = "lua_getfield"]
pub fn lua_getfield_old(L: *mut lua_State, idx: c_int, k: *const c_char);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_geti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
#[link_name = "lua_rawget"]
pub fn lua_rawget_old(L: *mut lua_State, idx: c_int);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
#[link_name = "lua_rawgeti"]
pub fn lua_rawgeti_old(L: *mut lua_State, idx: c_int, n: lua_Integer);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int;
#[cfg(feature = "lua52")]
#[link_name = "lua_rawgetp"]
pub fn lua_rawgetp_old(L: *mut lua_State, idx: c_int, p: *const c_void);
pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int);
#[cfg(feature = "lua54")]
pub fn lua_newuserdatauv(L: *mut lua_State, sz: usize, nuvalue: c_int) -> *mut c_void;
#[cfg(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
))]
pub fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void;
pub fn lua_getmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
#[cfg(feature = "lua54")]
pub fn lua_getiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
#[cfg(feature = "lua53")]
pub fn lua_getuservalue(L: *mut lua_State, idx: c_int) -> c_int;
#[cfg(feature = "lua52")]
@@ -338,28 +363,48 @@ extern "C" {
pub fn lua_getfenv(L: *mut lua_State, idx: c_int);
}
#[cfg(feature = "lua54")]
#[inline(always)]
pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
lua_newuserdatauv(L, sz, 1)
}
#[cfg(feature = "lua54")]
#[inline(always)]
pub unsafe fn lua_getuservalue(L: *mut lua_State, idx: c_int) -> c_int {
lua_getiuservalue(L, idx, 1)
}
// set functions (stack -> Lua)
extern "C" {
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_setglobal(L: *mut lua_State, var: *const c_char);
pub fn lua_settable(L: *mut lua_State, idx: c_int);
pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_seti(L: *mut lua_State, idx: c_int, n: lua_Integer);
pub fn lua_rawset(L: *mut lua_State, idx: c_int);
pub fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer);
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void);
pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
#[cfg(feature = "lua54")]
pub fn lua_setiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub fn lua_setuservalue(L: *mut lua_State, idx: c_int);
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub fn lua_setfenv(L: *mut lua_State, idx: c_int) -> c_int;
}
#[cfg(feature = "lua54")]
#[inline(always)]
pub unsafe fn lua_setuservalue(L: *mut lua_State, idx: c_int) {
lua_setiuservalue(L, idx, 1);
}
// 'load' and 'call' functions (load and run Lua code)
extern "C" {
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_callk(
L: *mut lua_State,
nargs: c_int,
@@ -376,7 +421,7 @@ extern "C" {
k: Option<lua_CFunction>,
);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_pcallk(
L: *mut lua_State,
nargs: c_int,
@@ -412,7 +457,7 @@ extern "C" {
mode: *const c_char,
) -> c_int;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_dump(
L: *mut lua_State,
writer: lua_Writer,
@@ -424,13 +469,13 @@ extern "C" {
pub fn lua_dump_old(L: *mut lua_State, writer: lua_Writer, data: *mut c_void) -> c_int;
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn lua_call(L: *mut lua_State, n: c_int, r: c_int) {
lua_callk(L, n, r, 0, None)
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_int {
lua_pcallk(L, n, r, f, 0, None)
@@ -438,7 +483,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
// coroutine functions
extern "C" {
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_yieldk(
L: *mut lua_State,
nresults: c_int,
@@ -455,23 +500,58 @@ extern "C" {
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub fn lua_yield(L: *mut lua_State, nresults: c_int) -> c_int;
#[cfg(feature = "lua54")]
pub fn lua_resume(
L: *mut lua_State,
from: *mut lua_State,
narg: c_int,
nres: *mut c_int,
) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int) -> c_int;
#[link_name = "lua_resume"]
pub fn lua_resume_53(L: *mut lua_State, from: *mut lua_State, narg: c_int) -> c_int;
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[link_name = "lua_resume"]
pub fn lua_resume_old(L: *mut lua_State, narg: c_int) -> c_int;
pub fn lua_status(L: *mut lua_State) -> c_int;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_isyieldable(L: *mut lua_State) -> c_int;
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
lua_yieldk(L, n, 0, None)
}
#[cfg(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
))]
#[inline(always)]
pub unsafe fn lua_resume(
L: *mut lua_State,
from: *mut lua_State,
narg: c_int,
nres: *mut c_int,
) -> c_int {
let ret = lua_resume_53(L, from, narg);
if ret == LUA_OK || ret == LUA_YIELD {
*nres = lua_gettop(L);
}
ret
}
// warning-related functions
#[cfg(feature = "lua54")]
extern "C" {
pub fn lua_setwarnf(L: *mut lua_State, f: lua_WarnFunction, ud: *mut c_void);
pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
}
// garbage-collection function and options
pub const LUA_GCSTOP: c_int = 0;
pub const LUA_GCRESTART: c_int = 1;
@@ -481,10 +561,22 @@ pub const LUA_GCCOUNTB: c_int = 4;
pub const LUA_GCSTEP: c_int = 5;
pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub const LUA_GCISRUNNING: c_int = 9;
#[cfg(feature = "lua54")]
pub const LUA_GCGEN: c_int = 10;
#[cfg(feature = "lua54")]
pub const LUA_GCINC: c_int = 11;
extern "C" {
#[cfg(feature = "lua54")]
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
#[cfg(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
))]
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
@@ -493,29 +585,31 @@ extern "C" {
pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_len(L: *mut lua_State, idx: c_int);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn lua_stringtonumber(L: *mut lua_State, s: *const c_char) -> usize;
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
#[cfg(feature = "lua54")]
pub fn lua_toclose(L: *mut lua_State, idx: c_int);
}
// some useful macros
// here, implemented as Rust functions
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[inline(always)]
pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void {
L.offset(-super::glue::LUA_EXTRASPACE as isize) as *mut c_void
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number {
lua_tonumberx(L, i, ptr::null_mut())
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx(L, i, ptr::null_mut())
@@ -601,7 +695,7 @@ pub unsafe fn lua_getglobal(L: *mut lua_State, var: *const c_char) -> c_int {
lua_getfield(L, LUA_GLOBALSINDEX, var)
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int {
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
@@ -612,20 +706,20 @@ pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
}
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[inline(always)]
pub unsafe fn lua_insert(L: *mut lua_State, idx: c_int) {
lua_rotate(L, idx, 1)
}
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[inline(always)]
pub unsafe fn lua_remove(L: *mut lua_State, idx: c_int) {
lua_rotate(L, idx, -1);
lua_pop(L, 1)
}
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[inline(always)]
pub unsafe fn lua_replace(L: *mut lua_State, idx: c_int) {
lua_copy(L, -1, idx);
@@ -647,7 +741,7 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events.
pub type lua_Hook = extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
pub type lua_Hook = unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
extern "C" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
@@ -657,18 +751,21 @@ extern "C" {
pub fn lua_getupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
pub fn lua_sethook(L: *mut lua_State, func: lua_Hook, mask: c_int, count: c_int);
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int);
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
#[cfg(feature = "lua54")]
pub fn lua_setcstacklimit(L: *mut lua_State, limit: c_uint) -> c_int;
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[repr(C)]
pub struct lua_Debug {
pub event: c_int,
@@ -676,6 +773,8 @@ pub struct lua_Debug {
pub namewhat: *const c_char,
pub what: *const c_char,
pub source: *const c_char,
#[cfg(feature = "lua54")]
pub srclen: usize,
pub currentline: c_int,
pub linedefined: c_int,
pub lastlinedefined: c_int,
@@ -683,6 +782,10 @@ pub struct lua_Debug {
pub nparams: c_uchar,
pub isvararg: c_char,
pub istailcall: c_char,
#[cfg(feature = "lua54")]
pub ftransfer: c_ushort,
#[cfg(feature = "lua54")]
pub ntransfer: c_ushort,
pub short_src: [c_char; luaconf::LUA_IDSIZE as usize],
// lua.h mentions this is for private use
i_ci: *mut c_void,
+3 -4
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,15 +23,14 @@
//! Contains definitions from `luaconf.h`.
pub use super::glue::LUAL_BUFFERSIZE;
pub use super::glue::LUA_INTEGER;
pub use super::glue::LUA_NUMBER;
pub use super::glue::LUA_UNSIGNED;
pub use super::glue::LUA_IDSIZE;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use super::glue::LUAL_NUMSIZES;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub type LUA_KCONTEXT = isize; // intptr_t
+4 -4
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -32,7 +32,7 @@ pub use super::glue::{
LUA_STRLIBNAME, LUA_TABLIBNAME,
};
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use super::glue::LUA_UTF8LIBNAME;
#[cfg(any(feature = "lua52", feature = "luajit"))]
@@ -43,13 +43,13 @@ pub use super::glue::{LUA_FFILIBNAME, LUA_JITLIBNAME};
extern "C" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub fn luaopen_coroutine(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;
pub fn luaopen_os(L: *mut lua_State) -> c_int;
pub fn luaopen_string(L: *mut lua_State) -> c_int;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
#[cfg(feature = "lua52")]
pub fn luaopen_bit32(L: *mut lua_State) -> c_int;
+52 -12
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -36,11 +36,14 @@ pub use self::lua::{
lua_Unsigned, lua_Writer,
};
#[cfg(feature = "lua53")]
#[cfg(feature = "lua54")]
pub use self::lua::lua_WarnFunction;
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use self::lua::{lua_KContext, lua_KFunction};
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use self::lua::lua_setfenv;
pub use self::lua::{lua_getfenv, lua_setfenv};
// C API functions
pub use self::lua::{
@@ -155,10 +158,16 @@ pub use self::lua::{
lua_yield,
};
#[cfg(feature = "lua53")]
#[cfg(feature = "lua54")]
pub use self::lua::{
lua_getiuservalue, lua_newuserdatauv, lua_resetthread, lua_setcstacklimit, lua_setiuservalue,
lua_setwarnf, lua_toclose, lua_warning,
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use self::lua::{lua_isyieldable, lua_version};
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub use self::lua::{lua_callk, lua_pcallk, lua_upvalueid, lua_upvaluejoin, lua_yieldk};
// auxiliary library types
@@ -177,7 +186,7 @@ pub use self::lauxlib::{
luaL_where,
};
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub use self::lauxlib::{luaL_execresult, luaL_fileresult, luaL_loadfilex};
// lualib.h functions
@@ -186,7 +195,7 @@ pub use self::lualib::{
luaopen_package, luaopen_string, luaopen_table,
};
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use self::lualib::{luaopen_coroutine, luaopen_utf8};
#[cfg(feature = "lua52")]
@@ -202,17 +211,23 @@ pub use self::lua::{
LUA_HOOKCOUNT, LUA_HOOKLINE, LUA_HOOKRET, LUA_HOOKTAILCALL, LUA_MASKCALL, LUA_MASKCOUNT,
LUA_MASKLINE, LUA_MASKRET, LUA_MINSTACK, LUA_MULTRET, LUA_OK, LUA_OPADD, LUA_OPDIV, LUA_OPEQ,
LUA_OPLE, LUA_OPLT, LUA_OPMOD, LUA_OPMUL, LUA_OPPOW, LUA_OPSUB, LUA_OPUNM, LUA_REGISTRYINDEX,
LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE, LUA_TNUMBER, LUA_TSTRING,
LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
LUA_SIGNATURE, LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE,
LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
};
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use self::lua::{
LUA_OPBAND, LUA_OPBNOT, LUA_OPBOR, LUA_OPBXOR, LUA_OPIDIV, LUA_OPSHL, LUA_OPSHR,
};
#[cfg(feature = "lua54")]
pub use self::lua::{LUA_GCGEN, LUA_GCINC};
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub use self::lua::{LUA_GCISRUNNING, LUA_RIDX_GLOBALS, LUA_RIDX_MAINTHREAD};
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub use self::lua::{LUA_ERRGCMM, LUA_GCISRUNNING, LUA_RIDX_GLOBALS, LUA_RIDX_MAINTHREAD};
pub use self::lua::LUA_ERRGCMM;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use self::lua::{LUA_ENVIRONINDEX, LUA_GLOBALSINDEX};
@@ -226,7 +241,7 @@ pub use self::lualib::{
LUA_STRLIBNAME, LUA_TABLIBNAME,
};
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub use self::lualib::LUA_UTF8LIBNAME;
#[cfg(any(feature = "lua52", feature = "luajit"))]
@@ -238,7 +253,30 @@ pub use self::lualib::{LUA_FFILIBNAME, LUA_JITLIBNAME};
// Not actually defined in lua.h / luaconf.h
pub const LUA_MAX_UPVALUES: c_int = 255;
// Copied from https://github.com/rust-lang/rust/blob/master/src/libstd/sys_common/alloc.rs
#[cfg(all(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "mips",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "asmjs",
target_arch = "wasm32",
target_arch = "hexagon"
)))]
pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(all(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "mips64",
target_arch = "s390x",
target_arch = "sparc64",
target_arch = "riscv64"
)))]
pub const SYS_MIN_ALIGN: usize = 16;
#[allow(unused_imports, dead_code, non_camel_case_types)]
#[allow(clippy::unreadable_literal)]
mod glue {
include!(concat!(env!("OUT_DIR"), "/glue.rs"));
}
@@ -250,3 +288,5 @@ mod lauxlib;
mod lua;
mod luaconf;
mod lualib;
pub mod safe;
+278
View File
@@ -0,0 +1,278 @@
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_void};
use crate::error::Result;
use crate::util::protect_lua;
use super::lua::{lua_CFunction, lua_Debug, lua_Integer, lua_State};
extern "C" {
#[link_name = "MLUA_WRAPPED_ERROR_SIZE"]
pub static mut WRAPPED_ERROR_SIZE: usize;
#[link_name = "MLUA_WRAPPED_PANIC_SIZE"]
pub static mut WRAPPED_PANIC_SIZE: usize;
#[link_name = "MLUA_WRAPPED_ERROR_KEY"]
pub static mut WRAPPED_ERROR_KEY: *const c_void;
#[link_name = "MLUA_WRAPPED_PANIC_KEY"]
pub static mut WRAPPED_PANIC_KEY: *const c_void;
pub fn lua_call_mlua_hook_proc(L: *mut lua_State, ar: *mut lua_Debug);
pub fn meta_index_impl(state: *mut lua_State) -> c_int;
pub fn meta_newindex_impl(state: *mut lua_State) -> c_int;
pub fn bind_call_impl(state: *mut lua_State) -> c_int;
pub fn error_traceback(state: *mut lua_State) -> c_int;
pub fn lua_nopanic_pcall(state: *mut lua_State) -> c_int;
pub fn lua_nopanic_xpcall(state: *mut lua_State) -> c_int;
fn lua_gc_s(L: *mut lua_State) -> c_int;
fn luaL_ref_s(L: *mut lua_State) -> c_int;
fn lua_pushlstring_s(L: *mut lua_State) -> c_int;
fn lua_tolstring_s(L: *mut lua_State) -> c_int;
fn lua_newthread_s(L: *mut lua_State) -> c_int;
fn lua_newuserdata_s(L: *mut lua_State) -> c_int;
fn lua_newwrappederror_s(L: *mut lua_State) -> c_int;
fn lua_pushcclosure_s(L: *mut lua_State) -> c_int;
fn lua_pushrclosure_s(L: *mut lua_State) -> c_int;
fn luaL_requiref_s(L: *mut lua_State) -> c_int;
fn error_traceback_s(L: *mut lua_State) -> c_int;
fn lua_newtable_s(L: *mut lua_State) -> c_int;
fn lua_createtable_s(L: *mut lua_State) -> c_int;
fn lua_gettable_s(L: *mut lua_State) -> c_int;
fn lua_settable_s(L: *mut lua_State) -> c_int;
fn lua_geti_s(L: *mut lua_State) -> c_int;
fn lua_rawset_s(L: *mut lua_State) -> c_int;
fn lua_rawseti_s(L: *mut lua_State) -> c_int;
fn lua_rawsetp_s(L: *mut lua_State) -> c_int;
fn lua_rawsetfield_s(L: *mut lua_State) -> c_int;
fn lua_rawinsert_s(L: *mut lua_State) -> c_int;
fn lua_rawremove_s(L: *mut lua_State) -> c_int;
fn luaL_len_s(L: *mut lua_State) -> c_int;
fn lua_next_s(L: *mut lua_State) -> c_int;
}
#[repr(C)]
struct StringArg {
data: *const c_char,
len: usize,
}
//
// Common functions
//
// Uses 4 stack spaces
pub unsafe fn lua_gc(state: *mut lua_State, what: c_int, data: c_int) -> Result<c_int> {
super::lua_pushinteger(state, what as lua_Integer);
super::lua_pushinteger(state, data as lua_Integer);
protect_lua(state, 2, lua_gc_s)?;
let ret = super::lua_tointeger(state, -1) as c_int;
super::lua_pop(state, 1);
Ok(ret)
}
// Uses 3 stack spaces
pub unsafe fn luaL_ref(state: *mut lua_State, table: c_int) -> Result<c_int> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -2, 1);
protect_lua(state, 2, luaL_ref_s)?;
let ret = super::lua_tointeger(state, -1) as c_int;
super::lua_pop(state, 1);
Ok(ret)
}
// Uses 3 stack spaces
pub unsafe fn lua_pushstring<S: AsRef<[u8]> + ?Sized>(state: *mut lua_State, s: &S) -> Result<()> {
let s = s.as_ref();
let s = StringArg {
data: s.as_ptr() as *const c_char,
len: s.len(),
};
super::lua_pushlightuserdata(state, &s as *const StringArg as *mut c_void);
protect_lua(state, 1, lua_pushlstring_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_tolstring(
state: *mut lua_State,
index: c_int,
len: *mut usize,
) -> Result<*const c_char> {
let index = super::lua_absindex(state, index);
super::lua_pushvalue(state, index);
super::lua_pushlightuserdata(state, len as *mut c_void);
protect_lua(state, 2, lua_tolstring_s)?;
let s = super::lua_touserdata(state, -1);
super::lua_pop(state, 1);
super::lua_replace(state, index);
Ok(s as *const c_char)
}
// Uses 2 stack spaces
pub unsafe fn lua_newthread(state: *mut lua_State) -> Result<*mut lua_State> {
protect_lua(state, 0, lua_newthread_s)?;
Ok(super::lua_tothread(state, -1))
}
// Uses 3 stack spaces
pub unsafe fn lua_newuserdata(state: *mut lua_State, size: usize) -> Result<*mut c_void> {
super::lua_pushinteger(state, size as lua_Integer);
protect_lua(state, 1, lua_newuserdata_s)?;
Ok(super::lua_touserdata(state, -1))
}
// Uses 2 stack spaces
pub unsafe fn lua_newwrappederror(state: *mut lua_State) -> Result<*mut c_void> {
protect_lua(state, 0, lua_newwrappederror_s)?;
Ok(super::lua_touserdata(state, -1))
}
// Uses 3 stack spaces
pub unsafe fn lua_pushcclosure(state: *mut lua_State, f: lua_CFunction, n: c_int) -> Result<()> {
super::lua_pushlightuserdata(state, f as *mut c_void);
protect_lua(state, n + 1, lua_pushcclosure_s)
}
// Uses 3 stack spaces
pub unsafe fn lua_pushrclosure(state: *mut lua_State, f: lua_CFunction, n: c_int) -> Result<()> {
super::lua_pushlightuserdata(state, f as *mut c_void);
if n > 0 {
super::lua_rotate(state, -n - 1, 1);
}
protect_lua(state, n + 1, lua_pushrclosure_s)
}
// Uses 5 stack spaces
pub unsafe fn luaL_requiref<S: AsRef<[u8]> + ?Sized>(
state: *mut lua_State,
modname: &S,
openf: lua_CFunction,
glb: c_int,
) -> Result<()> {
let modname = mlua_expect!(CString::new(modname.as_ref()), "modname contains nil bytes");
super::lua_pushlightuserdata(state, modname.as_ptr() as *mut c_void);
super::lua_pushlightuserdata(state, openf as *mut c_void);
super::lua_pushinteger(state, glb as lua_Integer);
protect_lua(state, 3, luaL_requiref_s)
}
// Uses 3 stack spaces
pub unsafe fn error_traceback2(state: *mut lua_State, state2: *mut lua_State) -> Result<()> {
mlua_assert!(
state != state2,
"error_traceback2 must be used with two different states"
);
super::lua_pushlightuserdata(state, state2);
protect_lua(state, 1, error_traceback_s)
}
//
// Table functions
//
// Uses 2 stack spaces
pub unsafe fn lua_newtable(state: *mut lua_State) -> Result<()> {
protect_lua(state, 0, lua_newtable_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_createtable(state: *mut lua_State, narr: c_int, nrec: c_int) -> Result<()> {
super::lua_pushinteger(state, narr as lua_Integer);
super::lua_pushinteger(state, nrec as lua_Integer);
protect_lua(state, 2, lua_createtable_s)
}
// Uses 3 stack spaces
pub unsafe fn lua_gettable(state: *mut lua_State, table: c_int) -> Result<()> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -2, 1);
protect_lua(state, 2, lua_gettable_s)
}
// Uses 3 stack spaces
pub unsafe fn lua_settable(state: *mut lua_State, table: c_int) -> Result<()> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -3, 1);
protect_lua(state, 3, lua_settable_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_geti(state: *mut lua_State, table: c_int, i: lua_Integer) -> Result<c_int> {
super::lua_pushvalue(state, table);
super::lua_pushinteger(state, i);
protect_lua(state, 2, lua_geti_s).map(|_| super::lua_type(state, -1))
}
// Uses 3 stack spaces
pub unsafe fn lua_rawset(state: *mut lua_State, table: c_int) -> Result<()> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -3, 1);
protect_lua(state, 3, lua_rawset_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_rawseti(state: *mut lua_State, table: c_int, i: lua_Integer) -> Result<()> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -2, 1);
super::lua_pushinteger(state, i);
protect_lua(state, 3, lua_rawseti_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_rawsetp(state: *mut lua_State, table: c_int, ptr: *const c_void) -> Result<()> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -2, 1);
super::lua_pushlightuserdata(state, ptr as *mut c_void);
protect_lua(state, 3, lua_rawsetp_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_rawsetfield<S>(state: *mut lua_State, table: c_int, field: &S) -> Result<()>
where
S: AsRef<[u8]> + ?Sized,
{
let field = field.as_ref();
let s = StringArg {
data: field.as_ptr() as *const c_char,
len: field.len(),
};
super::lua_pushvalue(state, table);
super::lua_pushlightuserdata(state, &s as *const StringArg as *mut c_void);
super::lua_rotate(state, -3, 2);
protect_lua(state, 3, lua_rawsetfield_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_rawinsert(state: *mut lua_State, table: c_int, i: lua_Integer) -> Result<()> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -2, 1);
super::lua_pushinteger(state, i);
protect_lua(state, 3, lua_rawinsert_s)
}
// Uses 4 stack spaces
pub unsafe fn lua_rawremove(state: *mut lua_State, table: c_int, i: lua_Integer) -> Result<()> {
super::lua_pushvalue(state, table);
super::lua_pushinteger(state, i);
protect_lua(state, 2, lua_rawremove_s)
}
// Uses 3 stack spaces
pub unsafe fn luaL_len(state: *mut lua_State, table: c_int) -> Result<lua_Integer> {
super::lua_pushvalue(state, table);
protect_lua(state, 1, luaL_len_s)?;
let ret = super::lua_tointeger(state, -1);
super::lua_pop(state, 1);
Ok(ret)
}
// Uses 3 stack spaces
pub unsafe fn lua_next(state: *mut lua_State, table: c_int) -> Result<lua_Integer> {
super::lua_pushvalue(state, table);
super::lua_rotate(state, -2, 1);
protect_lua(state, 2, lua_next_s)?;
let ret = super::lua_tointeger(state, -1);
super::lua_pop(state, 1);
Ok(ret)
}
+953
View File
@@ -0,0 +1,953 @@
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include "compat-5.3.h"
/* don't compile it again if it already is included via compat53.h */
#ifndef COMPAT53_C_
#define COMPAT53_C_
/* definitions for Lua 5.1 only */
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501
#ifndef COMPAT53_FOPEN_NO_LOCK
# if defined(_MSC_VER)
# define COMPAT53_FOPEN_NO_LOCK 1
# else /* otherwise */
# define COMPAT53_FOPEN_NO_LOCK 0
# endif /* VC++ only so far */
#endif /* No-lock fopen_s usage if possible */
#if defined(_MSC_VER) && COMPAT53_FOPEN_NO_LOCK
# include <share.h>
#endif /* VC++ _fsopen for share-allowed file read */
#ifndef COMPAT53_HAVE_STRERROR_R
# if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
(defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) || \
defined(__APPLE__)
# define COMPAT53_HAVE_STRERROR_R 1
# else /* none of the defines matched: define to 0 */
# define COMPAT53_HAVE_STRERROR_R 0
# endif /* have strerror_r of some form */
#endif /* strerror_r */
#ifndef COMPAT53_HAVE_STRERROR_S
# if defined(_MSC_VER) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && \
defined(__STDC_LIB_EXT1__) && __STDC_LIB_EXT1__)
# define COMPAT53_HAVE_STRERROR_S 1
# else /* not VC++ or C11 */
# define COMPAT53_HAVE_STRERROR_S 0
# endif /* strerror_s from VC++ or C11 */
#endif /* strerror_s */
#ifndef COMPAT53_LUA_FILE_BUFFER_SIZE
# define COMPAT53_LUA_FILE_BUFFER_SIZE 4096
#endif /* Lua File Buffer Size */
static char* compat53_strerror (int en, char* buff, size_t sz) {
#if COMPAT53_HAVE_STRERROR_R
/* use strerror_r here, because it's available on these specific platforms */
if (sz > 0) {
buff[0] = '\0';
/* we don't care whether the GNU version or the XSI version is used: */
if (strerror_r(en, buff, sz)) {
/* Yes, we really DO want to ignore the return value!
* GCC makes that extra hard, not even a (void) cast will do. */
}
if (buff[0] == '\0') {
/* Buffer is unchanged, so we probably have called GNU strerror_r which
* returned a static constant string. Chances are that strerror will
* return the same static constant string and therefore be thread-safe. */
return strerror(en);
}
}
return buff; /* sz is 0 *or* strerror_r wrote into the buffer */
#elif COMPAT53_HAVE_STRERROR_S
/* for MSVC and other C11 implementations, use strerror_s since it's
* provided by default by the libraries */
strerror_s(buff, sz, en);
return buff;
#else
/* fallback, but strerror is not guaranteed to be threadsafe due to modifying
* errno itself and some impls not locking a static buffer for it ... but most
* known systems have threadsafe errno: this might only change if the locale
* is changed out from under someone while this function is being called */
(void)buff;
(void)sz;
return strerror(en);
#endif
}
COMPAT53_API int lua_absindex (lua_State *L, int i) {
if (i < 0 && i > LUA_REGISTRYINDEX)
i += lua_gettop(L) + 1;
return i;
}
static void compat53_call_lua (lua_State *L, char const code[], size_t len,
int nargs, int nret) {
lua_rawgetp(L, LUA_REGISTRYINDEX, (void*)code);
if (lua_type(L, -1) != LUA_TFUNCTION) {
lua_pop(L, 1);
if (luaL_loadbuffer(L, code, len, "=none"))
lua_error(L);
lua_pushvalue(L, -1);
lua_rawsetp(L, LUA_REGISTRYINDEX, (void*)code);
}
lua_insert(L, -nargs-1);
lua_call(L, nargs, nret);
}
static const char compat53_arith_code[] =
"local op,a,b=...\n"
"if op==0 then return a+b\n"
"elseif op==1 then return a-b\n"
"elseif op==2 then return a*b\n"
"elseif op==3 then return a/b\n"
"elseif op==4 then return a%b\n"
"elseif op==5 then return a^b\n"
"elseif op==6 then return -a\n"
"end\n";
COMPAT53_API void lua_arith (lua_State *L, int op) {
if (op < LUA_OPADD || op > LUA_OPUNM)
luaL_error(L, "invalid 'op' argument for lua_arith");
luaL_checkstack(L, 5, "not enough stack slots");
if (op == LUA_OPUNM)
lua_pushvalue(L, -1);
lua_pushnumber(L, op);
lua_insert(L, -3);
compat53_call_lua(L, compat53_arith_code,
sizeof(compat53_arith_code)-1, 3, 1);
}
static const char compat53_compare_code[] =
"local a,b=...\n"
"return a<=b\n";
COMPAT53_API int lua_compare (lua_State *L, int idx1, int idx2, int op) {
int result = 0;
switch (op) {
case LUA_OPEQ:
return lua_equal(L, idx1, idx2);
case LUA_OPLT:
return lua_lessthan(L, idx1, idx2);
case LUA_OPLE:
luaL_checkstack(L, 5, "not enough stack slots");
idx1 = lua_absindex(L, idx1);
idx2 = lua_absindex(L, idx2);
lua_pushvalue(L, idx1);
lua_pushvalue(L, idx2);
compat53_call_lua(L, compat53_compare_code,
sizeof(compat53_compare_code)-1, 2, 1);
result = lua_toboolean(L, -1);
lua_pop(L, 1);
return result;
default:
luaL_error(L, "invalid 'op' argument for lua_compare");
}
return 0;
}
COMPAT53_API void lua_copy (lua_State *L, int from, int to) {
int abs_to = lua_absindex(L, to);
luaL_checkstack(L, 1, "not enough stack slots");
lua_pushvalue(L, from);
lua_replace(L, abs_to);
}
COMPAT53_API void lua_len (lua_State *L, int i) {
switch (lua_type(L, i)) {
case LUA_TSTRING:
lua_pushnumber(L, (lua_Number)lua_objlen(L, i));
break;
case LUA_TTABLE:
if (!luaL_callmeta(L, i, "__len"))
lua_pushnumber(L, (lua_Number)lua_objlen(L, i));
break;
case LUA_TUSERDATA:
if (luaL_callmeta(L, i, "__len"))
break;
/* FALLTHROUGH */
default:
luaL_error(L, "attempt to get length of a %s value",
lua_typename(L, lua_type(L, i)));
}
}
COMPAT53_API int lua_rawgetp (lua_State *L, int i, const void *p) {
int abs_i = lua_absindex(L, i);
lua_pushlightuserdata(L, (void*)p);
lua_rawget(L, abs_i);
return lua_type(L, -1);
}
COMPAT53_API void lua_rawsetp (lua_State *L, int i, const void *p) {
int abs_i = lua_absindex(L, i);
luaL_checkstack(L, 1, "not enough stack slots");
lua_pushlightuserdata(L, (void*)p);
lua_insert(L, -2);
lua_rawset(L, abs_i);
}
COMPAT53_API lua_Number lua_tonumberx (lua_State *L, int i, int *isnum) {
lua_Number n = lua_tonumber(L, i);
if (isnum != NULL) {
*isnum = (n != 0 || lua_isnumber(L, i));
}
return n;
}
COMPAT53_API void luaL_checkversion (lua_State *L) {
(void)L;
}
COMPAT53_API void luaL_checkstack (lua_State *L, int sp, const char *msg) {
if (!lua_checkstack(L, sp+LUA_MINSTACK)) {
if (msg != NULL)
luaL_error(L, "stack overflow (%s)", msg);
else {
lua_pushliteral(L, "stack overflow");
lua_error(L);
}
}
}
COMPAT53_API int luaL_getsubtable (lua_State *L, int i, const char *name) {
int abs_i = lua_absindex(L, i);
luaL_checkstack(L, 3, "not enough stack slots");
lua_pushstring(L, name);
lua_gettable(L, abs_i);
if (lua_istable(L, -1))
return 1;
lua_pop(L, 1);
lua_newtable(L);
lua_pushstring(L, name);
lua_pushvalue(L, -2);
lua_settable(L, abs_i);
return 0;
}
COMPAT53_API lua_Integer luaL_len (lua_State *L, int i) {
lua_Integer res = 0;
int isnum = 0;
luaL_checkstack(L, 1, "not enough stack slots");
lua_len(L, i);
res = lua_tointegerx(L, -1, &isnum);
lua_pop(L, 1);
if (!isnum)
luaL_error(L, "object length is not an integer");
return res;
}
COMPAT53_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
luaL_checkstack(L, nup+1, "too many upvalues");
for (; l->name != NULL; l++) { /* fill the table with given functions */
int i;
lua_pushstring(L, l->name);
for (i = 0; i < nup; i++) /* copy upvalues to the top */
lua_pushvalue(L, -(nup + 1));
lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
lua_settable(L, -(nup + 3)); /* table must be below the upvalues, the name and the closure */
}
lua_pop(L, nup); /* remove upvalues */
}
COMPAT53_API void luaL_setmetatable (lua_State *L, const char *tname) {
luaL_checkstack(L, 1, "not enough stack slots");
luaL_getmetatable(L, tname);
lua_setmetatable(L, -2);
}
COMPAT53_API void *luaL_testudata (lua_State *L, int i, const char *tname) {
void *p = lua_touserdata(L, i);
luaL_checkstack(L, 2, "not enough stack slots");
if (p == NULL || !lua_getmetatable(L, i))
return NULL;
else {
int res = 0;
luaL_getmetatable(L, tname);
res = lua_rawequal(L, -1, -2);
lua_pop(L, 2);
if (!res)
p = NULL;
}
return p;
}
static int compat53_countlevels (lua_State *L) {
lua_Debug ar;
int li = 1, le = 1;
/* find an upper bound */
while (lua_getstack(L, le, &ar)) { li = le; le *= 2; }
/* do a binary search */
while (li < le) {
int m = (li + le)/2;
if (lua_getstack(L, m, &ar)) li = m + 1;
else le = m;
}
return le - 1;
}
static int compat53_findfield (lua_State *L, int objidx, int level) {
if (level == 0 || !lua_istable(L, -1))
return 0; /* not found */
lua_pushnil(L); /* start 'next' loop */
while (lua_next(L, -2)) { /* for each pair in table */
if (lua_type(L, -2) == LUA_TSTRING) { /* ignore non-string keys */
if (lua_rawequal(L, objidx, -1)) { /* found object? */
lua_pop(L, 1); /* remove value (but keep name) */
return 1;
}
else if (compat53_findfield(L, objidx, level - 1)) { /* try recursively */
lua_remove(L, -2); /* remove table (but keep name) */
lua_pushliteral(L, ".");
lua_insert(L, -2); /* place '.' between the two names */
lua_concat(L, 3);
return 1;
}
}
lua_pop(L, 1); /* remove value */
}
return 0; /* not found */
}
static int compat53_pushglobalfuncname (lua_State *L, lua_Debug *ar) {
int top = lua_gettop(L);
lua_getinfo(L, "f", ar); /* push function */
lua_pushvalue(L, LUA_GLOBALSINDEX);
if (compat53_findfield(L, top + 1, 2)) {
lua_copy(L, -1, top + 1); /* move name to proper place */
lua_pop(L, 2); /* remove pushed values */
return 1;
}
else {
lua_settop(L, top); /* remove function and global table */
return 0;
}
}
static void compat53_pushfuncname (lua_State *L, lua_Debug *ar) {
if (*ar->namewhat != '\0') /* is there a name? */
lua_pushfstring(L, "function " LUA_QS, ar->name);
else if (*ar->what == 'm') /* main? */
lua_pushliteral(L, "main chunk");
else if (*ar->what == 'C') {
if (compat53_pushglobalfuncname(L, ar)) {
lua_pushfstring(L, "function " LUA_QS, lua_tostring(L, -1));
lua_remove(L, -2); /* remove name */
}
else
lua_pushliteral(L, "?");
}
else
lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined);
}
#define COMPAT53_LEVELS1 12 /* size of the first part of the stack */
#define COMPAT53_LEVELS2 10 /* size of the second part of the stack */
COMPAT53_API void luaL_traceback (lua_State *L, lua_State *L1,
const char *msg, int level) {
lua_Debug ar;
int top = lua_gettop(L);
int numlevels = compat53_countlevels(L1);
int mark = (numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2) ? COMPAT53_LEVELS1 : 0;
if (msg) lua_pushfstring(L, "%s\n", msg);
lua_pushliteral(L, "stack traceback:");
while (lua_getstack(L1, level++, &ar)) {
if (level == mark) { /* too many levels? */
lua_pushliteral(L, "\n\t..."); /* add a '...' */
level = numlevels - COMPAT53_LEVELS2; /* and skip to last ones */
}
else {
lua_getinfo(L1, "Slnt", &ar);
lua_pushfstring(L, "\n\t%s:", ar.short_src);
if (ar.currentline > 0)
lua_pushfstring(L, "%d:", ar.currentline);
lua_pushliteral(L, " in ");
compat53_pushfuncname(L, &ar);
lua_concat(L, lua_gettop(L) - top);
}
}
lua_concat(L, lua_gettop(L) - top);
}
COMPAT53_API int luaL_fileresult (lua_State *L, int stat, const char *fname) {
const char *serr = NULL;
int en = errno; /* calls to Lua API may change this value */
char buf[512] = { 0 };
if (stat) {
lua_pushboolean(L, 1);
return 1;
}
else {
lua_pushnil(L);
serr = compat53_strerror(en, buf, sizeof(buf));
if (fname)
lua_pushfstring(L, "%s: %s", fname, serr);
else
lua_pushstring(L, serr);
lua_pushnumber(L, (lua_Number)en);
return 3;
}
}
static int compat53_checkmode (lua_State *L, const char *mode, const char *modename, int err) {
if (mode && strchr(mode, modename[0]) == NULL) {
lua_pushfstring(L, "attempt to load a %s chunk (mode is '%s')", modename, mode);
return err;
}
return LUA_OK;
}
typedef struct {
lua_Reader reader;
void *ud;
int has_peeked_data;
const char *peeked_data;
size_t peeked_data_size;
} compat53_reader_data;
static const char *compat53_reader (lua_State *L, void *ud, size_t *size) {
compat53_reader_data *data = (compat53_reader_data *)ud;
if (data->has_peeked_data) {
data->has_peeked_data = 0;
*size = data->peeked_data_size;
return data->peeked_data;
} else
return data->reader(L, data->ud, size);
}
COMPAT53_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *source, const char *mode) {
int status = LUA_OK;
compat53_reader_data compat53_data = { 0, NULL, 1, 0, 0 };
compat53_data.reader = reader;
compat53_data.ud = data;
compat53_data.peeked_data = reader(L, data, &(compat53_data.peeked_data_size));
if (compat53_data.peeked_data && compat53_data.peeked_data_size &&
compat53_data.peeked_data[0] == LUA_SIGNATURE[0]) /* binary file? */
status = compat53_checkmode(L, mode, "binary", LUA_ERRSYNTAX);
else
status = compat53_checkmode(L, mode, "text", LUA_ERRSYNTAX);
if (status != LUA_OK)
return status;
/* we need to call the original 5.1 version of lua_load! */
#undef lua_load
return lua_load(L, compat53_reader, &compat53_data, source);
#define lua_load COMPAT53_CONCAT(COMPAT53_PREFIX, _load_53)
}
typedef struct {
int n; /* number of pre-read characters */
FILE *f; /* file being read */
char buff[COMPAT53_LUA_FILE_BUFFER_SIZE]; /* area for reading file */
} compat53_LoadF;
static const char *compat53_getF (lua_State *L, void *ud, size_t *size) {
compat53_LoadF *lf = (compat53_LoadF *)ud;
(void)L; /* not used */
if (lf->n > 0) { /* are there pre-read characters to be read? */
*size = lf->n; /* return them (chars already in buffer) */
lf->n = 0; /* no more pre-read characters */
}
else { /* read a block from file */
/* 'fread' can return > 0 *and* set the EOF flag. If next call to
'compat53_getF' called 'fread', it might still wait for user input.
The next check avoids this problem. */
if (feof(lf->f)) return NULL;
*size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); /* read block */
}
return lf->buff;
}
static int compat53_errfile (lua_State *L, const char *what, int fnameindex) {
char buf[512] = {0};
const char *serr = compat53_strerror(errno, buf, sizeof(buf));
const char *filename = lua_tostring(L, fnameindex) + 1;
lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr);
lua_remove(L, fnameindex);
return LUA_ERRFILE;
}
static int compat53_skipBOM (compat53_LoadF *lf) {
const char *p = "\xEF\xBB\xBF"; /* UTF-8 BOM mark */
int c;
lf->n = 0;
do {
c = getc(lf->f);
if (c == EOF || c != *(const unsigned char *)p++) return c;
lf->buff[lf->n++] = (char)c; /* to be read by the parser */
} while (*p != '\0');
lf->n = 0; /* prefix matched; discard it */
return getc(lf->f); /* return next character */
}
/*
** reads the first character of file 'f' and skips an optional BOM mark
** in its beginning plus its first line if it starts with '#'. Returns
** true if it skipped the first line. In any case, '*cp' has the
** first "valid" character of the file (after the optional BOM and
** a first-line comment).
*/
static int compat53_skipcomment (compat53_LoadF *lf, int *cp) {
int c = *cp = compat53_skipBOM(lf);
if (c == '#') { /* first line is a comment (Unix exec. file)? */
do { /* skip first line */
c = getc(lf->f);
} while (c != EOF && c != '\n');
*cp = getc(lf->f); /* skip end-of-line, if present */
return 1; /* there was a comment */
}
else return 0; /* no comment */
}
COMPAT53_API int luaL_loadfilex (lua_State *L, const char *filename, const char *mode) {
compat53_LoadF lf;
int status, readstatus;
int c;
int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */
if (filename == NULL) {
lua_pushliteral(L, "=stdin");
lf.f = stdin;
}
else {
lua_pushfstring(L, "@%s", filename);
#if defined(_MSC_VER)
/* This code is here to stop a deprecation error that stops builds
* if a certain macro is defined. While normally not caring would
* be best, some header-only libraries and builds can't afford to
* dictate this to the user. A quick check shows that fopen_s this
* goes back to VS 2005, and _fsopen goes back to VS 2003 .NET,
* possibly even before that so we don't need to do any version
* number checks, since this has been there since forever. */
/* TO USER: if you want the behavior of typical fopen_s/fopen,
* which does lock the file on VC++, define the macro used below to 0 */
#if COMPAT53_FOPEN_NO_LOCK
lf.f = _fsopen(filename, "r", _SH_DENYNO); /* do not lock the file in any way */
if (lf.f == NULL)
return compat53_errfile(L, "open", fnameindex);
#else /* use default locking version */
if (fopen_s(&lf.f, filename, "r") != 0)
return compat53_errfile(L, "open", fnameindex);
#endif /* Locking vs. No-locking fopen variants */
#else
lf.f = fopen(filename, "r"); /* default stdlib doesn't forcefully lock files here */
if (lf.f == NULL) return compat53_errfile(L, "open", fnameindex);
#endif
}
if (compat53_skipcomment(&lf, &c)) /* read initial portion */
lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */
if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */
#if defined(_MSC_VER)
if (freopen_s(&lf.f, filename, "rb", lf.f) != 0)
return compat53_errfile(L, "reopen", fnameindex);
#else
lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */
if (lf.f == NULL) return compat53_errfile(L, "reopen", fnameindex);
#endif
compat53_skipcomment(&lf, &c); /* re-read initial portion */
}
if (c != EOF)
lf.buff[lf.n++] = (char)c; /* 'c' is the first character of the stream */
status = lua_load(L, &compat53_getF, &lf, lua_tostring(L, -1), mode);
readstatus = ferror(lf.f);
if (filename) fclose(lf.f); /* close file (even in case of errors) */
if (readstatus) {
lua_settop(L, fnameindex); /* ignore results from 'lua_load' */
return compat53_errfile(L, "read", fnameindex);
}
lua_remove(L, fnameindex);
return status;
}
COMPAT53_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t sz, const char *name, const char *mode) {
int status = LUA_OK;
if (sz > 0 && buff[0] == LUA_SIGNATURE[0]) {
status = compat53_checkmode(L, mode, "binary", LUA_ERRSYNTAX);
}
else {
status = compat53_checkmode(L, mode, "text", LUA_ERRSYNTAX);
}
if (status != LUA_OK)
return status;
return luaL_loadbuffer(L, buff, sz, name);
}
#if !defined(l_inspectstat) && \
(defined(unix) || defined(__unix) || defined(__unix__) || \
defined(__TOS_AIX__) || defined(_SYSTYPE_BSD) || \
(defined(__APPLE__) && defined(__MACH__)))
/* some form of unix; check feature macros in unistd.h for details */
# include <unistd.h>
/* check posix version; the relevant include files and macros probably
* were available before 2001, but I'm not sure */
# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
# include <sys/wait.h>
# define l_inspectstat(stat,what) \
if (WIFEXITED(stat)) { stat = WEXITSTATUS(stat); } \
else if (WIFSIGNALED(stat)) { stat = WTERMSIG(stat); what = "signal"; }
# endif
#endif
/* provide default (no-op) version */
#if !defined(l_inspectstat)
# define l_inspectstat(stat,what) ((void)0)
#endif
COMPAT53_API int luaL_execresult (lua_State *L, int stat) {
const char *what = "exit";
if (stat == -1)
return luaL_fileresult(L, 0, NULL);
else {
l_inspectstat(stat, what);
if (*what == 'e' && stat == 0)
lua_pushboolean(L, 1);
else
lua_pushnil(L);
lua_pushstring(L, what);
lua_pushinteger(L, stat);
return 3;
}
}
COMPAT53_API void luaL_buffinit (lua_State *L, luaL_Buffer_53 *B) {
/* make it crash if used via pointer to a 5.1-style luaL_Buffer */
B->b.p = NULL;
B->b.L = NULL;
B->b.lvl = 0;
/* reuse the buffer from the 5.1-style luaL_Buffer though! */
B->ptr = B->b.buffer;
B->capacity = LUAL_BUFFERSIZE;
B->nelems = 0;
B->L2 = L;
}
COMPAT53_API char *luaL_prepbuffsize (luaL_Buffer_53 *B, size_t s) {
if (B->capacity - B->nelems < s) { /* needs to grow */
char* newptr = NULL;
size_t newcap = B->capacity * 2;
if (newcap - B->nelems < s)
newcap = B->nelems + s;
if (newcap < B->capacity) /* overflow */
luaL_error(B->L2, "buffer too large");
newptr = (char*)lua_newuserdata(B->L2, newcap);
memcpy(newptr, B->ptr, B->nelems);
if (B->ptr != B->b.buffer)
lua_replace(B->L2, -2); /* remove old buffer */
B->ptr = newptr;
B->capacity = newcap;
}
return B->ptr+B->nelems;
}
COMPAT53_API void luaL_addlstring (luaL_Buffer_53 *B, const char *s, size_t l) {
memcpy(luaL_prepbuffsize(B, l), s, l);
luaL_addsize(B, l);
}
COMPAT53_API void luaL_addvalue (luaL_Buffer_53 *B) {
size_t len = 0;
const char *s = lua_tolstring(B->L2, -1, &len);
if (!s)
luaL_error(B->L2, "cannot convert value to string");
if (B->ptr != B->b.buffer)
lua_insert(B->L2, -2); /* userdata buffer must be at stack top */
luaL_addlstring(B, s, len);
lua_remove(B->L2, B->ptr != B->b.buffer ? -2 : -1);
}
void luaL_pushresult (luaL_Buffer_53 *B) {
lua_pushlstring(B->L2, B->ptr, B->nelems);
if (B->ptr != B->b.buffer)
lua_replace(B->L2, -2); /* remove userdata buffer */
}
#endif /* Lua 5.1 */
/* definitions for Lua 5.1 and Lua 5.2 */
#if defined( LUA_VERSION_NUM ) && LUA_VERSION_NUM <= 502
COMPAT53_API int lua_geti (lua_State *L, int index, lua_Integer i) {
index = lua_absindex(L, index);
lua_pushinteger(L, i);
lua_gettable(L, index);
return lua_type(L, -1);
}
#ifndef LUA_EXTRASPACE
#define LUA_EXTRASPACE (sizeof(void*))
#endif
COMPAT53_API void *lua_getextraspace (lua_State *L) {
int is_main = 0;
void *ptr = NULL;
luaL_checkstack(L, 4, "not enough stack slots available");
lua_pushliteral(L, "__compat53_extraspace");
lua_pushvalue(L, -1);
lua_rawget(L, LUA_REGISTRYINDEX);
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_createtable(L, 0, 2);
lua_createtable(L, 0, 1);
lua_pushliteral(L, "k");
lua_setfield(L, -2, "__mode");
lua_setmetatable(L, -2);
lua_pushvalue(L, -2);
lua_pushvalue(L, -2);
lua_rawset(L, LUA_REGISTRYINDEX);
}
lua_replace(L, -2);
is_main = lua_pushthread(L);
lua_rawget(L, -2);
ptr = lua_touserdata(L, -1);
if (!ptr) {
lua_pop(L, 1);
ptr = lua_newuserdata(L, LUA_EXTRASPACE);
if (is_main) {
memset(ptr, '\0', LUA_EXTRASPACE);
lua_pushthread(L);
lua_pushvalue(L, -2);
lua_rawset(L, -4);
lua_pushboolean(L, 1);
lua_pushvalue(L, -2);
lua_rawset(L, -4);
} else {
void* mptr = NULL;
lua_pushboolean(L, 1);
lua_rawget(L, -3);
mptr = lua_touserdata(L, -1);
if (mptr)
memcpy(ptr, mptr, LUA_EXTRASPACE);
else
memset(ptr, '\0', LUA_EXTRASPACE);
lua_pop(L, 1);
lua_pushthread(L);
lua_pushvalue(L, -2);
lua_rawset(L, -4);
}
}
lua_pop(L, 2);
return ptr;
}
COMPAT53_API int lua_isinteger (lua_State *L, int index) {
if (lua_type(L, index) == LUA_TNUMBER) {
lua_Number n = lua_tonumber(L, index);
lua_Integer i = lua_tointeger(L, index);
if (i == n)
return 1;
}
return 0;
}
COMPAT53_API lua_Integer lua_tointegerx (lua_State *L, int i, int *isnum) {
int ok = 0;
lua_Number n = lua_tonumberx(L, i, &ok);
if (ok) {
if (n == (lua_Integer)n) {
if (isnum)
*isnum = 1;
return (lua_Integer)n;
}
}
if (isnum)
*isnum = 0;
return 0;
}
static void compat53_reverse (lua_State *L, int a, int b) {
for (; a < b; ++a, --b) {
lua_pushvalue(L, a);
lua_pushvalue(L, b);
lua_replace(L, a);
lua_replace(L, b);
}
}
COMPAT53_API void lua_rotate (lua_State *L, int idx, int n) {
int n_elems = 0;
idx = lua_absindex(L, idx);
n_elems = lua_gettop(L)-idx+1;
if (n < 0)
n += n_elems;
if ( n > 0 && n < n_elems) {
luaL_checkstack(L, 2, "not enough stack slots available");
n = n_elems - n;
compat53_reverse(L, idx, idx+n-1);
compat53_reverse(L, idx+n, idx+n_elems-1);
compat53_reverse(L, idx, idx+n_elems-1);
}
}
COMPAT53_API void lua_seti (lua_State *L, int index, lua_Integer i) {
luaL_checkstack(L, 1, "not enough stack slots available");
index = lua_absindex(L, index);
lua_pushinteger(L, i);
lua_insert(L, -2);
lua_settable(L, index);
}
#if !defined(lua_str2number)
# define lua_str2number(s, p) strtod((s), (p))
#endif
COMPAT53_API size_t lua_stringtonumber (lua_State *L, const char *s) {
char* endptr;
lua_Number n = lua_str2number(s, &endptr);
if (endptr != s) {
while (*endptr != '\0' && isspace((unsigned char)*endptr))
++endptr;
if (*endptr == '\0') {
lua_pushnumber(L, n);
return endptr - s + 1;
}
}
return 0;
}
COMPAT53_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) {
if (!luaL_callmeta(L, idx, "__tostring")) {
int t = lua_type(L, idx), tt = 0;
char const* name = NULL;
switch (t) {
case LUA_TNIL:
lua_pushliteral(L, "nil");
break;
case LUA_TSTRING:
case LUA_TNUMBER:
lua_pushvalue(L, idx);
break;
case LUA_TBOOLEAN:
if (lua_toboolean(L, idx))
lua_pushliteral(L, "true");
else
lua_pushliteral(L, "false");
break;
default:
tt = luaL_getmetafield(L, idx, "__name");
name = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : lua_typename(L, t);
lua_pushfstring(L, "%s: %p", name, lua_topointer(L, idx));
if (tt != LUA_TNIL)
lua_replace(L, -2);
break;
}
} else {
if (!lua_isstring(L, -1))
luaL_error(L, "'__tostring' must return a string");
}
return lua_tolstring(L, -1, len);
}
COMPAT53_API void luaL_requiref (lua_State *L, const char *modname,
lua_CFunction openf, int glb) {
luaL_checkstack(L, 3, "not enough stack slots available");
luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED");
if (lua_getfield(L, -1, modname) == LUA_TNIL) {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
lua_pushstring(L, modname);
#ifndef COMPAT53_LUAJIT
lua_call(L, 1, 1);
lua_pushvalue(L, -1);
lua_setfield(L, -3, modname);
#else
lua_call(L, 1, 0);
lua_getfield(L, -1, modname);
#endif /* COMPAT53_LUAJIT */
}
if (glb) {
lua_pushvalue(L, -1);
lua_setglobal(L, modname);
}
lua_replace(L, -2);
}
#endif /* Lua 5.1 and 5.2 */
#endif /* COMPAT53_C_ */
/*********************************************************************
* This file contains parts of Lua 5.2's and Lua 5.3's source code:
*
* Copyright (C) 1994-2014 Lua.org, PUC-Rio.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*********************************************************************/
+424
View File
@@ -0,0 +1,424 @@
#ifndef COMPAT53_H_
#define COMPAT53_H_
#include <stddef.h>
#include <limits.h>
#include <string.h>
#if defined(__cplusplus) && !defined(COMPAT53_LUA_CPP)
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#if defined(__cplusplus) && !defined(COMPAT53_LUA_CPP)
}
#endif
#undef COMPAT53_INCLUDE_SOURCE
#if defined(COMPAT53_PREFIX)
/* - change the symbol names of functions to avoid linker conflicts
* - compat-5.3.c needs to be compiled (and linked) separately
*/
# if !defined(COMPAT53_API)
# define COMPAT53_API extern
# endif
#else /* COMPAT53_PREFIX */
/* - make all functions static and include the source.
* - compat-5.3.c doesn't need to be compiled (and linked) separately
*/
# define COMPAT53_PREFIX compat53
# undef COMPAT53_API
# if defined(__GNUC__) || defined(__clang__)
# define COMPAT53_API __attribute__((__unused__)) static
# else
# define COMPAT53_API static
# endif
# define COMPAT53_INCLUDE_SOURCE
#endif /* COMPAT53_PREFIX */
#define COMPAT53_CONCAT_HELPER(a, b) a##b
#define COMPAT53_CONCAT(a, b) COMPAT53_CONCAT_HELPER(a, b)
/* declarations for Lua 5.1 */
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501
/* XXX not implemented:
* lua_arith (new operators)
* lua_upvalueid
* lua_upvaluejoin
* lua_version
* lua_yieldk
*/
#ifndef LUA_OK
# define LUA_OK 0
#endif
#ifndef LUA_OPADD
# define LUA_OPADD 0
#endif
#ifndef LUA_OPSUB
# define LUA_OPSUB 1
#endif
#ifndef LUA_OPMUL
# define LUA_OPMUL 2
#endif
#ifndef LUA_OPDIV
# define LUA_OPDIV 3
#endif
#ifndef LUA_OPMOD
# define LUA_OPMOD 4
#endif
#ifndef LUA_OPPOW
# define LUA_OPPOW 5
#endif
#ifndef LUA_OPUNM
# define LUA_OPUNM 6
#endif
#ifndef LUA_OPEQ
# define LUA_OPEQ 0
#endif
#ifndef LUA_OPLT
# define LUA_OPLT 1
#endif
#ifndef LUA_OPLE
# define LUA_OPLE 2
#endif
/* LuaJIT/Lua 5.1 does not have the updated
* error codes for thread status/function returns (but some patched versions do)
* define it only if it's not found
*/
#if !defined(LUA_ERRGCMM)
/* Use + 2 because in some versions of Lua (Lua 5.1)
* LUA_ERRFILE is defined as (LUA_ERRERR+1)
* so we need to avoid it (LuaJIT might have something at this
* integer value too)
*/
# define LUA_ERRGCMM (LUA_ERRERR + 2)
#endif /* LUA_ERRGCMM define */
typedef size_t lua_Unsigned;
typedef struct luaL_Buffer_53 {
luaL_Buffer b; /* make incorrect code crash! */
char *ptr;
size_t nelems;
size_t capacity;
lua_State *L2;
} luaL_Buffer_53;
#define luaL_Buffer luaL_Buffer_53
/* In PUC-Rio 5.1, userdata is a simple FILE*
* In LuaJIT, it's a struct where the first member is a FILE*
* We can't support the `closef` member
*/
typedef struct luaL_Stream {
FILE *f;
} luaL_Stream;
#define lua_absindex COMPAT53_CONCAT(COMPAT53_PREFIX, _absindex)
COMPAT53_API int lua_absindex (lua_State *L, int i);
#define lua_arith COMPAT53_CONCAT(COMPAT53_PREFIX, _arith)
COMPAT53_API void lua_arith (lua_State *L, int op);
#define lua_compare COMPAT53_CONCAT(COMPAT53_PREFIX, _compare)
COMPAT53_API int lua_compare (lua_State *L, int idx1, int idx2, int op);
#define lua_copy COMPAT53_CONCAT(COMPAT53_PREFIX, _copy)
COMPAT53_API void lua_copy (lua_State *L, int from, int to);
#define lua_getuservalue(L, i) \
(lua_getfenv((L), (i)), lua_type((L), -1))
#define lua_setuservalue(L, i) \
(luaL_checktype((L), -1, LUA_TTABLE), lua_setfenv((L), (i)))
#define lua_len COMPAT53_CONCAT(COMPAT53_PREFIX, _len)
COMPAT53_API void lua_len (lua_State *L, int i);
#define lua_pushstring(L, s) \
(lua_pushstring((L), (s)), lua_tostring((L), -1))
#define lua_pushlstring(L, s, len) \
((((len) == 0) ? lua_pushlstring((L), "", 0) : lua_pushlstring((L), (s), (len))), lua_tostring((L), -1))
#ifndef luaL_newlibtable
# define luaL_newlibtable(L, l) \
(lua_createtable((L), 0, sizeof((l))/sizeof(*(l))-1))
#endif
#ifndef luaL_newlib
# define luaL_newlib(L, l) \
(luaL_newlibtable((L), (l)), luaL_register((L), NULL, (l)))
#endif
#define lua_pushglobaltable(L) \
lua_pushvalue((L), LUA_GLOBALSINDEX)
#define lua_rawgetp COMPAT53_CONCAT(COMPAT53_PREFIX, _rawgetp)
COMPAT53_API int lua_rawgetp (lua_State *L, int i, const void *p);
#define lua_rawsetp COMPAT53_CONCAT(COMPAT53_PREFIX, _rawsetp)
COMPAT53_API void lua_rawsetp(lua_State *L, int i, const void *p);
#define lua_rawlen(L, i) lua_objlen((L), (i))
#define lua_tointeger(L, i) lua_tointegerx((L), (i), NULL)
#define lua_tonumberx COMPAT53_CONCAT(COMPAT53_PREFIX, _tonumberx)
COMPAT53_API lua_Number lua_tonumberx (lua_State *L, int i, int *isnum);
#define luaL_checkversion COMPAT53_CONCAT(COMPAT53_PREFIX, L_checkversion)
COMPAT53_API void luaL_checkversion (lua_State *L);
#define lua_load COMPAT53_CONCAT(COMPAT53_PREFIX, _load_53)
COMPAT53_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char* source, const char* mode);
#define luaL_loadfilex COMPAT53_CONCAT(COMPAT53_PREFIX, L_loadfilex)
COMPAT53_API int luaL_loadfilex (lua_State *L, const char *filename, const char *mode);
#define luaL_loadbufferx COMPAT53_CONCAT(COMPAT53_PREFIX, L_loadbufferx)
COMPAT53_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t sz, const char *name, const char *mode);
#define luaL_checkstack COMPAT53_CONCAT(COMPAT53_PREFIX, L_checkstack_53)
COMPAT53_API void luaL_checkstack (lua_State *L, int sp, const char *msg);
#define luaL_getsubtable COMPAT53_CONCAT(COMPAT53_PREFIX, L_getsubtable)
COMPAT53_API int luaL_getsubtable (lua_State* L, int i, const char *name);
#define luaL_len COMPAT53_CONCAT(COMPAT53_PREFIX, L_len)
COMPAT53_API lua_Integer luaL_len (lua_State *L, int i);
#define luaL_setfuncs COMPAT53_CONCAT(COMPAT53_PREFIX, L_setfuncs)
COMPAT53_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);
#define luaL_setmetatable COMPAT53_CONCAT(COMPAT53_PREFIX, L_setmetatable)
COMPAT53_API void luaL_setmetatable (lua_State *L, const char *tname);
#define luaL_testudata COMPAT53_CONCAT(COMPAT53_PREFIX, L_testudata)
COMPAT53_API void *luaL_testudata (lua_State *L, int i, const char *tname);
#define luaL_traceback COMPAT53_CONCAT(COMPAT53_PREFIX, L_traceback)
COMPAT53_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level);
#define luaL_fileresult COMPAT53_CONCAT(COMPAT53_PREFIX, L_fileresult)
COMPAT53_API int luaL_fileresult (lua_State *L, int stat, const char *fname);
#define luaL_execresult COMPAT53_CONCAT(COMPAT53_PREFIX, L_execresult)
COMPAT53_API int luaL_execresult (lua_State *L, int stat);
#define lua_callk(L, na, nr, ctx, cont) \
((void)(ctx), (void)(cont), lua_call((L), (na), (nr)))
#define lua_pcallk(L, na, nr, err, ctx, cont) \
((void)(ctx), (void)(cont), lua_pcall((L), (na), (nr), (err)))
#define lua_resume(L, from, nargs) \
((void)(from), lua_resume((L), (nargs)))
#define luaL_buffinit COMPAT53_CONCAT(COMPAT53_PREFIX, _buffinit_53)
COMPAT53_API void luaL_buffinit (lua_State *L, luaL_Buffer_53 *B);
#define luaL_prepbuffsize COMPAT53_CONCAT(COMPAT53_PREFIX, _prepbufsize_53)
COMPAT53_API char *luaL_prepbuffsize (luaL_Buffer_53 *B, size_t s);
#define luaL_addlstring COMPAT53_CONCAT(COMPAT53_PREFIX, _addlstring_53)
COMPAT53_API void luaL_addlstring (luaL_Buffer_53 *B, const char *s, size_t l);
#define luaL_addvalue COMPAT53_CONCAT(COMPAT53_PREFIX, _addvalue_53)
COMPAT53_API void luaL_addvalue (luaL_Buffer_53 *B);
#define luaL_pushresult COMPAT53_CONCAT(COMPAT53_PREFIX, _pushresult_53)
COMPAT53_API void luaL_pushresult (luaL_Buffer_53 *B);
#undef luaL_buffinitsize
#define luaL_buffinitsize(L, B, s) \
(luaL_buffinit((L), (B)), luaL_prepbuffsize((B), (s)))
#undef luaL_prepbuffer
#define luaL_prepbuffer(B) \
luaL_prepbuffsize((B), LUAL_BUFFERSIZE)
#undef luaL_addchar
#define luaL_addchar(B, c) \
((void)((B)->nelems < (B)->capacity || luaL_prepbuffsize((B), 1)), \
((B)->ptr[(B)->nelems++] = (c)))
#undef luaL_addsize
#define luaL_addsize(B, s) \
((B)->nelems += (s))
#undef luaL_addstring
#define luaL_addstring(B, s) \
luaL_addlstring((B), (s), strlen((s)))
#undef luaL_pushresultsize
#define luaL_pushresultsize(B, s) \
(luaL_addsize((B), (s)), luaL_pushresult((B)))
#if defined(LUA_COMPAT_APIINTCASTS)
#define lua_pushunsigned(L, n) \
lua_pushinteger((L), (lua_Integer)(n))
#define lua_tounsignedx(L, i, is) \
((lua_Unsigned)lua_tointegerx((L), (i), (is)))
#define lua_tounsigned(L, i) \
lua_tounsignedx((L), (i), NULL)
#define luaL_checkunsigned(L, a) \
((lua_Unsigned)luaL_checkinteger((L), (a)))
#define luaL_optunsigned(L, a, d) \
((lua_Unsigned)luaL_optinteger((L), (a), (lua_Integer)(d)))
#endif
#endif /* Lua 5.1 only */
/* declarations for Lua 5.1 and 5.2 */
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 502
typedef int lua_KContext;
typedef int (*lua_KFunction)(lua_State *L, int status, lua_KContext ctx);
#define lua_dump(L, w, d, s) \
((void)(s), lua_dump((L), (w), (d)))
#define lua_getfield(L, i, k) \
(lua_getfield((L), (i), (k)), lua_type((L), -1))
#define lua_gettable(L, i) \
(lua_gettable((L), (i)), lua_type((L), -1))
#define lua_geti COMPAT53_CONCAT(COMPAT53_PREFIX, _geti)
COMPAT53_API int lua_geti (lua_State *L, int index, lua_Integer i);
#define lua_getextraspace COMPAT53_CONCAT(COMPAT53_PREFIX, _getextraspace)
COMPAT53_API void *lua_getextraspace (lua_State *L);
#define lua_isinteger COMPAT53_CONCAT(COMPAT53_PREFIX, _isinteger)
COMPAT53_API int lua_isinteger (lua_State *L, int index);
#define lua_tointegerx COMPAT53_CONCAT(COMPAT53_PREFIX, _tointegerx_53)
COMPAT53_API lua_Integer lua_tointegerx (lua_State *L, int i, int *isnum);
#define lua_numbertointeger(n, p) \
((*(p) = (lua_Integer)(n)), 1)
#define lua_rawget(L, i) \
(lua_rawget((L), (i)), lua_type((L), -1))
#define lua_rawgeti(L, i, n) \
(lua_rawgeti((L), (i), (n)), lua_type((L), -1))
#define lua_rotate COMPAT53_CONCAT(COMPAT53_PREFIX, _rotate)
COMPAT53_API void lua_rotate (lua_State *L, int idx, int n);
#define lua_seti COMPAT53_CONCAT(COMPAT53_PREFIX, _seti)
COMPAT53_API void lua_seti (lua_State *L, int index, lua_Integer i);
#define lua_stringtonumber COMPAT53_CONCAT(COMPAT53_PREFIX, _stringtonumber)
COMPAT53_API size_t lua_stringtonumber (lua_State *L, const char *s);
#define luaL_tolstring COMPAT53_CONCAT(COMPAT53_PREFIX, L_tolstring)
COMPAT53_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len);
#define luaL_getmetafield(L, o, e) \
(luaL_getmetafield((L), (o), (e)) ? lua_type((L), -1) : LUA_TNIL)
#define luaL_newmetatable(L, tn) \
(luaL_newmetatable((L), (tn)) ? (lua_pushstring((L), (tn)), lua_setfield((L), -2, "__name"), 1) : 0)
#define luaL_requiref COMPAT53_CONCAT(COMPAT53_PREFIX, L_requiref_53)
COMPAT53_API void luaL_requiref (lua_State *L, const char *modname,
lua_CFunction openf, int glb );
#endif /* Lua 5.1 and Lua 5.2 */
/* declarations for Lua 5.2 */
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 502
/* XXX not implemented:
* lua_isyieldable
* lua_arith (new operators)
* lua_pushfstring (new formats)
*/
#define lua_getglobal(L, n) \
(lua_getglobal((L), (n)), lua_type((L), -1))
#define lua_getuservalue(L, i) \
(lua_getuservalue((L), (i)), lua_type((L), -1))
#define lua_pushlstring(L, s, len) \
(((len) == 0) ? lua_pushlstring((L), "", 0) : lua_pushlstring((L), (s), (len)))
#define lua_rawgetp(L, i, p) \
(lua_rawgetp((L), (i), (p)), lua_type((L), -1))
#define LUA_KFUNCTION(_name) \
static int (_name)(lua_State *L, int status, lua_KContext ctx); \
static int (_name ## _52)(lua_State *L) { \
lua_KContext ctx; \
int status = lua_getctx(L, &ctx); \
return (_name)(L, status, ctx); \
} \
static int (_name)(lua_State *L, int status, lua_KContext ctx)
#define lua_pcallk(L, na, nr, err, ctx, cont) \
lua_pcallk((L), (na), (nr), (err), (ctx), cont ## _52)
#define lua_callk(L, na, nr, ctx, cont) \
lua_callk((L), (na), (nr), (ctx), cont ## _52)
#define lua_yieldk(L, nr, ctx, cont) \
lua_yieldk((L), (nr), (ctx), cont ## _52)
#ifdef lua_call
# undef lua_call
# define lua_call(L, na, nr) \
(lua_callk)((L), (na), (nr), 0, NULL)
#endif
#ifdef lua_pcall
# undef lua_pcall
# define lua_pcall(L, na, nr, err) \
(lua_pcallk)((L), (na), (nr), (err), 0, NULL)
#endif
#ifdef lua_yield
# undef lua_yield
# define lua_yield(L, nr) \
(lua_yieldk)((L), (nr), 0, NULL)
#endif
#endif /* Lua 5.2 only */
/* other Lua versions */
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 501 || LUA_VERSION_NUM > 504
# error "unsupported Lua version (i.e. not Lua 5.1, 5.2, 5.3, or 5.4)"
#endif /* other Lua versions except 5.1, 5.2, 5.3, and 5.4 */
/* helper macro for defining continuation functions (for every version
* *except* Lua 5.2) */
#ifndef LUA_KFUNCTION
#define LUA_KFUNCTION(_name) \
static int (_name)(lua_State *L, int status, lua_KContext ctx)
#endif
#if defined(COMPAT53_INCLUDE_SOURCE)
# include "compat-5.3.c"
#endif
#endif /* COMPAT53_H_ */
+509
View File
@@ -0,0 +1,509 @@
// The MIT License (MIT)
//
// Copyright (c) 2019-2021 A. Orlenko
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <lauxlib.h>
#include <lua.h>
#include "compat-5.3.h"
size_t MLUA_WRAPPED_ERROR_SIZE = 0;
size_t MLUA_WRAPPED_PANIC_SIZE = 0;
const void *MLUA_WRAPPED_ERROR_KEY = NULL;
const void *MLUA_WRAPPED_PANIC_KEY = NULL;
extern void wrapped_error_traceback(lua_State *L, int error_idx, void *error_ud,
int has_traceback);
extern int mlua_hook_proc(lua_State *L, lua_Debug *ar);
#define max(a, b) (a > b ? a : b)
typedef struct {
const char *data;
size_t len;
} StringArg;
// A wrapper around Rust function to protect from triggering longjmp in Rust.
// Rust callback expected to return -1 in case of errors or number of output
// values.
static int lua_call_rust(lua_State *L) {
int nargs = lua_gettop(L);
// We need one extra stack space to store preallocated memory, and at least 2
// stack spaces overall for handling error metatables in rust fn
int extra_stack = 1;
if (nargs < 2) {
extra_stack = 2 - nargs;
}
luaL_checkstack(L, extra_stack,
"not enough stack space for callback error handling");
// We cannot shadow rust errors with Lua ones, we pre-allocate enough memory
// to store a wrapped error or panic *before* we proceed.
lua_newuserdata(L, max(MLUA_WRAPPED_ERROR_SIZE, MLUA_WRAPPED_PANIC_SIZE));
lua_rotate(L, 1, 1);
lua_CFunction rust_callback = lua_touserdata(L, lua_upvalueindex(1));
int ret = rust_callback(L);
if (ret == -1) {
lua_error(L);
}
return ret;
}
void lua_call_mlua_hook_proc(lua_State *L, lua_Debug *ar) {
luaL_checkstack(L, 2, "not enough stack space for callback error handling");
lua_newuserdata(L, max(MLUA_WRAPPED_ERROR_SIZE, MLUA_WRAPPED_PANIC_SIZE));
lua_rotate(L, 1, 1);
int ret = mlua_hook_proc(L, ar);
if (ret == -1) {
lua_error(L);
}
}
static inline lua_Integer lua_popinteger(lua_State *L) {
lua_Integer index = lua_tointeger(L, -1);
lua_pop(L, 1);
return index;
}
//
// Common functions
//
int lua_gc_s(lua_State *L) {
int data = lua_popinteger(L);
int what = lua_popinteger(L);
int ret = lua_gc(L, what, data);
lua_pushinteger(L, ret);
return 1;
}
int luaL_ref_s(lua_State *L) {
int ret = luaL_ref(L, -2);
lua_pushinteger(L, ret);
return 1;
}
int lua_pushlstring_s(lua_State *L) {
StringArg *s = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_pushlstring(L, s->data, s->len);
return 1;
}
int lua_tolstring_s(lua_State *L) {
void *len = lua_touserdata(L, -1);
lua_pop(L, 1);
const char *s = lua_tolstring(L, -1, len);
lua_pushlightuserdata(L, (void *)s);
return 2;
}
int lua_newthread_s(lua_State *L) {
lua_newthread(L);
return 1;
}
int lua_newuserdata_s(lua_State *L) {
size_t size = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_newuserdata(L, size);
return 1;
}
int lua_newwrappederror_s(lua_State *L) {
lua_newuserdata(L, MLUA_WRAPPED_ERROR_SIZE);
return 1;
}
int lua_pushcclosure_s(lua_State *L) {
int n = lua_gettop(L) - 1;
lua_CFunction fn = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_pushcclosure(L, fn, n);
return 1;
}
int lua_pushrclosure_s(lua_State *L) {
int n = lua_gettop(L);
lua_pushcclosure(L, lua_call_rust, n);
return 1;
}
int luaL_requiref_s(lua_State *L) {
const char *modname = lua_touserdata(L, -3);
lua_CFunction openf = lua_touserdata(L, -2);
int glb = lua_tointeger(L, -1);
lua_pop(L, 3);
luaL_requiref(L, modname, openf, glb);
return 1;
}
//
// Table functions
//
int lua_newtable_s(lua_State *L) {
lua_createtable(L, 0, 0);
return 1;
}
int lua_createtable_s(lua_State *L) {
int nrec = lua_popinteger(L);
int narr = lua_popinteger(L);
lua_createtable(L, narr, nrec);
return 1;
}
int lua_gettable_s(lua_State *L) {
lua_gettable(L, -2);
return 1;
}
int lua_settable_s(lua_State *L) {
lua_settable(L, -3);
return 0;
}
int lua_geti_s(lua_State *L) {
lua_Integer index = lua_popinteger(L);
lua_geti(L, -1, index);
return 1;
}
int lua_rawset_s(lua_State *L) {
lua_rawset(L, -3);
return 0;
}
int lua_rawseti_s(lua_State *L) {
lua_Integer index = lua_popinteger(L);
lua_rawseti(L, -2, index);
return 0;
}
int lua_rawsetp_s(lua_State *L) {
void *p = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_rawsetp(L, -2, p);
return 0;
}
int lua_rawsetfield_s(lua_State *L) {
StringArg *s = lua_touserdata(L, -2);
lua_pushlstring(L, s->data, s->len);
lua_replace(L, -3);
lua_rawset(L, -3);
return 0;
}
int lua_rawinsert_s(lua_State *L) {
lua_Integer index = lua_popinteger(L);
lua_Integer size = lua_rawlen(L, -2);
for (lua_Integer i = size; i >= index; i--) {
// table[i+1] = table[i]
lua_rawgeti(L, -2, i);
lua_rawseti(L, -3, i + 1);
}
lua_rawseti(L, -2, index);
return 0;
}
int lua_rawremove_s(lua_State *L) {
lua_Integer index = lua_popinteger(L);
lua_Integer size = lua_rawlen(L, -1);
for (lua_Integer i = index; i < size; i++) {
lua_rawgeti(L, -1, i + 1);
lua_rawseti(L, -2, i);
}
lua_pushnil(L);
lua_rawseti(L, -2, size);
return 0;
}
int luaL_len_s(lua_State *L) {
lua_pushinteger(L, luaL_len(L, -1));
return 1;
}
int lua_next_s(lua_State *L) {
int ret = lua_next(L, -2);
lua_pushinteger(L, ret);
return ret == 0 ? 1 : 3;
}
//
// Moved from Rust to C
//
// Wrapper to lookup in `field_getters` first, then `methods`, ending
// original `__index`. Used only if `field_getters` or `methods` set.
int meta_index_impl(lua_State *state) {
// stack: self, key
luaL_checkstack(state, 2, NULL);
// lookup in `field_getters` table
if (lua_isnil(state, lua_upvalueindex(2)) == 0) {
lua_pushvalue(state, -1); // `key` arg
if (lua_rawget(state, lua_upvalueindex(2)) != LUA_TNIL) {
lua_insert(state, -3); // move function
lua_pop(state, 1); // remove `key`
lua_call(state, 1, 1);
return 1;
}
lua_pop(state, 1); // pop the nil value
}
// lookup in `methods` table
if (lua_isnil(state, lua_upvalueindex(3)) == 0) {
lua_pushvalue(state, -1); // `key` arg
if (lua_rawget(state, lua_upvalueindex(3)) != LUA_TNIL) {
lua_insert(state, -3);
lua_pop(state, 2);
return 1;
}
lua_pop(state, 1); // pop the nil value
}
// lookup in `__index`
lua_pushvalue(state, lua_upvalueindex(1));
switch (lua_type(state, -1)) {
case LUA_TNIL:
lua_pop(state, 1); // pop the nil value
const char *field = lua_tostring(state, -1);
luaL_error(state, "attempt to get an unknown field '%s'", field);
break;
case LUA_TTABLE:
lua_insert(state, -2);
lua_gettable(state, -2);
break;
case LUA_TFUNCTION:
lua_insert(state, -3);
lua_call(state, 2, 1);
break;
}
return 1;
}
// Similar to `meta_index_impl`, checks `field_setters` table first, then
// `__newindex` metamethod. Used only if `field_setters` set.
int meta_newindex_impl(lua_State *state) {
// stack: self, key, value
luaL_checkstack(state, 2, NULL);
// lookup in `field_setters` table
lua_pushvalue(state, -2); // `key` arg
if (lua_rawget(state, lua_upvalueindex(2)) != LUA_TNIL) {
lua_remove(state, -3); // remove `key`
lua_insert(state, -3); // move function
lua_call(state, 2, 0);
return 0;
}
lua_pop(state, 1); // pop the nil value
// lookup in `__newindex`
lua_pushvalue(state, lua_upvalueindex(1));
switch (lua_type(state, -1)) {
case LUA_TNIL:
lua_pop(state, 1); // pop the nil value
const char *field = lua_tostring(state, -2);
luaL_error(state, "attempt to set an unknown field '%s'", field);
break;
case LUA_TTABLE:
lua_insert(state, -3);
lua_settable(state, -3);
break;
case LUA_TFUNCTION:
lua_insert(state, -4);
lua_call(state, 3, 0);
break;
}
return 0;
}
// See Function::bind
int bind_call_impl(lua_State *state) {
int nargs = lua_gettop(state);
int nbinds = lua_tointeger(state, lua_upvalueindex(2));
luaL_checkstack(state, nbinds + 2, NULL);
lua_settop(state, nargs + nbinds + 1);
lua_rotate(state, -(nargs + nbinds + 1), nbinds + 1);
lua_pushvalue(state, lua_upvalueindex(1));
lua_replace(state, 1);
for (int i = 0; i < nbinds; i++) {
lua_pushvalue(state, lua_upvalueindex(i + 3));
lua_replace(state, i + 2);
}
lua_call(state, nargs + nbinds, LUA_MULTRET);
return lua_gettop(state);
}
// Returns 1 if a value at index `index` is a special wrapped struct identified
// by `key`
int is_wrapped_struct(lua_State *state, int index, const void *key) {
if (key == NULL) {
// Not yet initialized?
return 0;
}
void *ud = lua_touserdata(state, index);
if (ud == NULL || lua_getmetatable(state, index) == 0) {
return 0;
}
lua_rawgetp(state, LUA_REGISTRYINDEX, key);
int res = lua_rawequal(state, -1, -2);
lua_pop(state, 2);
return res;
}
// Takes an error at the top of the stack, and if it is a WrappedError, converts
// it to an Error::CallbackError with a traceback, if it is some lua type,
// prints the error along with a traceback, and if it is a WrappedPanic, does
// not modify it. This function does its best to avoid triggering another error
// and shadowing previous rust errors, but it may trigger Lua errors that shadow
// rust errors under certain memory conditions. This function ensures that such
// behavior will *never* occur with a rust panic, however.
int error_traceback(lua_State *state) {
// I believe luaL_traceback < 5.4 requires this much free stack to not error.
// 5.4 uses luaL_Buffer
const int LUA_TRACEBACK_STACK = 11;
if (lua_checkstack(state, 2) == 0) {
// If we don't have enough stack space to even check the error type, do
// nothing so we don't risk shadowing a rust panic.
return 1;
}
if (is_wrapped_struct(state, -1, MLUA_WRAPPED_ERROR_KEY) != 0) {
int error_idx = lua_absindex(state, -1);
// lua_newuserdata and luaL_traceback may error
void *error_ud = lua_newuserdata(state, MLUA_WRAPPED_ERROR_SIZE);
int has_traceback = 0;
if (lua_checkstack(state, LUA_TRACEBACK_STACK) != 0) {
luaL_traceback(state, state, NULL, 0);
has_traceback = 1;
}
wrapped_error_traceback(state, error_idx, error_ud, has_traceback);
return 1;
}
if (MLUA_WRAPPED_PANIC_KEY != NULL &&
!is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY) &&
lua_checkstack(state, LUA_TRACEBACK_STACK) != 0) {
const char *s = luaL_tolstring(state, -1, NULL);
luaL_traceback(state, state, s, 0);
lua_remove(state, -2);
}
return 1;
}
int error_traceback_s(lua_State *L) {
lua_State *L1 = lua_touserdata(L, -1);
lua_pop(L, 1);
return error_traceback(L1);
}
// A `pcall` implementation that does not allow Lua to catch Rust panics.
// Instead, panics automatically resumed.
int lua_nopanic_pcall(lua_State *state) {
luaL_checkstack(state, 2, NULL);
int top = lua_gettop(state);
if (top == 0) {
lua_pushstring(state, "not enough arguments to pcall");
lua_error(state);
}
if (lua_pcall(state, top - 1, LUA_MULTRET, 0) == LUA_OK) {
lua_pushboolean(state, 1);
lua_insert(state, 1);
return lua_gettop(state);
}
if (is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY)) {
lua_error(state);
}
lua_pushboolean(state, 0);
lua_insert(state, -2);
return 2;
}
// A `xpcall` implementation that does not allow Lua to catch Rust panics.
// Instead, panics automatically resumed.
static int xpcall_msgh(lua_State *state) {
luaL_checkstack(state, 2, NULL);
if (is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY)) {
return 1;
}
lua_pushvalue(state, lua_upvalueindex(1));
lua_insert(state, 1);
lua_call(state, lua_gettop(state) - 1, LUA_MULTRET);
return lua_gettop(state);
}
int lua_nopanic_xpcall(lua_State *state) {
luaL_checkstack(state, 2, NULL);
int top = lua_gettop(state);
if (top < 2) {
lua_pushstring(state, "not enough arguments to xpcall");
lua_error(state);
}
lua_pushvalue(state, 2);
lua_pushcclosure(state, xpcall_msgh, 1);
lua_copy(state, 1, 2);
lua_replace(state, 1);
if (lua_pcall(state, lua_gettop(state) - 2, LUA_MULTRET, 1) == LUA_OK) {
lua_pushboolean(state, 1);
lua_insert(state, 2);
return lua_gettop(state) - 1;
}
if (is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY)) {
lua_error(state);
}
lua_pushboolean(state, 0);
lua_insert(state, -2);
return 2;
}
+86 -30
View File
@@ -1,14 +1,15 @@
use std::os::raw::c_int;
use std::ptr;
use std::os::raw::{c_int, c_void};
use std::slice;
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{
assert_stack, check_stack, error_traceback, pop_error, protect_lua_closure, StackGuard,
};
use crate::util::{assert_stack, check_stack, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// Handle to an internal Lua function.
#[derive(Clone, Debug)]
pub struct Function<'lua>(pub(crate) LuaRef<'lua>);
@@ -64,7 +65,7 @@ impl<'lua> Function<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, nargs + 3)?;
ffi::lua_pushcfunction(lua.state, error_traceback);
ffi::lua_pushcfunction(lua.state, ffi::safe::error_traceback);
let stack_start = ffi::lua_gettop(lua.state);
lua.push_ref(&self.0);
for arg in args {
@@ -86,6 +87,50 @@ impl<'lua> Function<'lua> {
R::from_lua_multi(results, lua)
}
/// Returns a Feature that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internaly it wraps the function to an [`AsyncThread`].
///
/// Requires `feature = "async"`
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use futures_timer::Delay;
/// # use mlua::{Lua, Result};
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
///
/// let sleep = lua.create_async_function(move |_lua, n: u64| async move {
/// Delay::new(Duration::from_millis(n)).await;
/// Ok(())
/// })?;
///
/// sleep.call_async(10).await?;
///
/// # Ok(())
/// # }
/// ```
///
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
match lua.create_thread(self.clone()) {
Ok(t) => Box::pin(t.into_async(args)),
Err(e) => Box::pin(future::err(e)),
}
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
/// arguments.
///
@@ -114,26 +159,6 @@ impl<'lua> Function<'lua> {
/// # }
/// ```
pub fn bind<A: ToLuaMulti<'lua>>(&self, args: A) -> Result<Function<'lua>> {
unsafe extern "C" fn bind_call_impl(state: *mut ffi::lua_State) -> c_int {
let nargs = ffi::lua_gettop(state);
let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(2)) as c_int;
ffi::luaL_checkstack(state, nbinds + 2, ptr::null());
ffi::lua_settop(state, nargs + nbinds + 1);
ffi::lua_rotate(state, -(nargs + nbinds + 1), nbinds + 1);
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
ffi::lua_replace(state, 1);
for i in 0..nbinds {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(i + 3));
ffi::lua_replace(state, i + 2);
}
ffi::lua_call(state, nargs + nbinds, ffi::LUA_MULTRET);
ffi::lua_gettop(state)
}
let lua = self.0.lua;
let args = args.to_lua_multi(lua)?;
@@ -146,19 +171,50 @@ impl<'lua> Function<'lua> {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, nargs + 5)?;
lua.push_ref(&self.0);
ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer);
for arg in args {
lua.push_value(arg)?;
}
protect_lua_closure(lua.state, nargs + 2, 1, |state| {
ffi::lua_pushcclosure(state, bind_call_impl, nargs + 2);
})?;
ffi::safe::lua_pushcclosure(lua.state, ffi::safe::bind_call_impl, nargs + 2)?;
Ok(Function(lua.pop_ref()))
}
}
/// Dumps the function as a binary chunk.
///
/// If `strip` is true, the binary representation may not include all debug information
/// about the function, to save space.
pub fn dump(&self, strip: bool) -> Vec<u8> {
unsafe extern "C" fn writer(
_state: *mut ffi::lua_State,
buf: *const c_void,
buf_len: usize,
data: *mut c_void,
) -> c_int {
let data = &mut *(data as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
0
}
let lua = self.0.lua;
let mut data: Vec<u8> = Vec::new();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
let data_ptr = &mut data as *mut Vec<u8> as *mut c_void;
let strip = if strip { 1 } else { 0 };
ffi::lua_dump(lua.state, writer, data_ptr, strip);
ffi::lua_pop(lua.state, 1);
}
data
}
}
impl<'lua> PartialEq for Function<'lua> {
+196
View File
@@ -0,0 +1,196 @@
use std::ffi::CStr;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int};
use crate::ffi::{self, lua_Debug, lua_State};
use crate::lua::Lua;
use crate::util::callback_error;
/// 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 5.3 documentaton][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.3/manual.html#lua_Debug
/// [`Lua::set_hook`]: struct.Lua.html#method.set_hook
#[derive(Clone)]
pub struct Debug<'a> {
ar: *mut lua_Debug,
state: *mut lua_State,
_phantom: PhantomData<&'a ()>,
}
impl<'a> Debug<'a> {
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames<'a> {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`"
);
DebugNames {
name: ptr_to_str((*self.ar).name),
name_what: ptr_to_str((*self.ar).namewhat),
}
}
}
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource<'a> {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
"lua_getinfo failed with `S`"
);
DebugSource {
source: ptr_to_str((*self.ar).source),
short_src: ptr_to_str((*self.ar).short_src.as_ptr()),
line_defined: (*self.ar).linedefined as i32,
last_line_defined: (*self.ar).lastlinedefined as i32,
what: ptr_to_str((*self.ar).what),
}
}
}
/// Corresponds to the `l` what mask. Returns the current line.
pub fn curr_line(&self) -> i32 {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`"
);
(*self.ar).currentline as i32
}
}
/// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false
/// otherwise.
pub fn is_tail_call(&self) -> bool {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
"lua_getinfo failed with `t`"
);
(*self.ar).currentline != 0
}
}
/// Corresponds to the `u` what mask.
pub fn stack(&self) -> DebugStack {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
"lua_getinfo failed with `u`"
);
DebugStack {
num_ups: (*self.ar).nups as i32,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar).nparams as i32,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar).isvararg != 0,
}
}
}
}
#[derive(Clone, Debug)]
pub struct DebugNames<'a> {
pub name: Option<&'a [u8]>,
pub name_what: Option<&'a [u8]>,
}
#[derive(Clone, Debug)]
pub struct DebugSource<'a> {
pub source: Option<&'a [u8]>,
pub short_src: Option<&'a [u8]>,
pub line_defined: i32,
pub last_line_defined: i32,
pub what: Option<&'a [u8]>,
}
#[derive(Copy, Clone, Debug)]
pub struct DebugStack {
pub num_ups: i32,
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
pub num_params: i32,
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
pub is_vararg: bool,
}
/// Determines when a hook function will be called by Lua.
#[derive(Clone, Copy, Debug, Default)]
pub struct HookTriggers {
/// Before a function call.
pub on_calls: bool,
/// When Lua returns from a function.
pub on_returns: bool,
/// Before executing a new line, or returning from a function call.
pub every_line: bool,
/// After a certain number of VM instructions have been executed. When set to `Some(count)`,
/// `count` is the number of VM instructions to execute before calling the hook.
///
/// # Performance
///
/// Setting this option to a low value can incur a very high overhead.
pub every_nth_instruction: Option<u32>,
}
impl HookTriggers {
// Compute the mask to pass to `lua_sethook`.
pub(crate) fn mask(&self) -> c_int {
let mut mask: c_int = 0;
if self.on_calls {
mask |= ffi::LUA_MASKCALL
}
if self.on_returns {
mask |= ffi::LUA_MASKRET
}
if self.every_line {
mask |= ffi::LUA_MASKLINE
}
if self.every_nth_instruction.is_some() {
mask |= ffi::LUA_MASKCOUNT
}
mask
}
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
// returned.
pub(crate) fn count(&self) -> c_int {
self.every_nth_instruction.unwrap_or(0) as c_int
}
}
#[no_mangle]
pub unsafe extern "C" fn mlua_hook_proc(state: *mut lua_State, ar: *mut lua_Debug) -> c_int {
callback_error(state, |_| {
let debug = Debug {
ar,
state,
_phantom: PhantomData,
};
let lua = Lua::make_from_ptr(state);
let hook_cb = mlua_expect!(lua.hook_callback(), "no hook callback set in hook_proc");
#[allow(clippy::match_wild_err_arm)]
match hook_cb.try_borrow_mut() {
Ok(mut b) => (&mut *b)(&lua, debug),
Err(_) => mlua_panic!("Lua should not allow hooks to be called within another hook"),
}?;
Ok(0)
})
}
unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a [u8]> {
if input.is_null() {
None
} else {
Some(CStr::from_ptr(input).to_bytes())
}
}
+64 -5
View File
@@ -23,22 +23,60 @@
//!
//! The [`UserData`] trait can be implemented by user-defined types to make them available to Lua.
//! Methods and operators to be used from Lua can be added using the [`UserDataMethods`] API.
//! Fields are supported using the [`UserDataFields`] API.
//!
//! # Serde support
//!
//! The [`LuaSerdeExt`] trait implemented for [`Lua`] allows conversion from Rust types to Lua values
//! and vice versa using serde. Any user defined data type that implements [`serde::Serialize`] or
//! [`serde::Deserialize`] can be converted.
//! For convenience, additional functionality to handle `NULL` values and arrays is provided.
//!
//! The [`Value`] enum implements [`serde::Serialize`] trait to support serializing Lua values
//! (including [`UserData`]) into Rust values.
//!
//! Requires `feature = "serialize"`.
//!
//! # Async/await support
//!
//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`].
//! Lua code with async capabilities can be executed by [`call_async`] family of functions or polling
//! [`AsyncThread`] using any runtime (eg. Tokio).
//!
//! Requires `feature = "async"`.
//!
//! # `Send` requirement
//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds `Send` requirement
//! to [`Function`]s and [`UserData`].
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: struct.Lua.html
//! [executing]: struct.Lua.html#method.exec
//! [evaluating]: struct.Lua.html#method.eval
//! [executing]: struct.Chunk.html#method.exec
//! [evaluating]: struct.Chunk.html#method.eval
//! [globals]: struct.Lua.html#method.globals
//! [`ToLua`]: trait.ToLua.html
//! [`FromLua`]: trait.FromLua.html
//! [`ToLuaMulti`]: trait.ToLuaMulti.html
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
//! [`Function`]: struct.Function.html
//! [`UserData`]: trait.UserData.html
//! [`UserDataFields`]: trait.UserDataFields.html
//! [`UserDataMethods`]: trait.UserDataMethods.html
//! [`LuaSerdeExt`]: serde/trait.LuaSerdeExt.html
//! [`Value`]: enum.Value.html
//! [`create_async_function`]: struct.Lua.html#method.create_async_function
//! [`call_async`]: struct.Function.html#method.call_async
//! [`AsyncThread`]: struct.AsyncThread.html
//! [`Future`]: ../futures_core/future/trait.Future.html
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.6.0-beta.1")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[macro_use]
mod macros;
@@ -47,6 +85,7 @@ mod conversion;
mod error;
mod ffi;
mod function;
mod hook;
mod lua;
mod multi;
mod scope;
@@ -63,15 +102,35 @@ pub use crate::ffi::lua_State;
pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::lua::{Chunk, Lua};
pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers};
pub use crate::lua::{Chunk, ChunkMode, GCMode, Lua, LuaOptions};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
pub use crate::stdlib::StdLib;
pub use crate::string::String;
pub use crate::table::{Table, TablePairs, TableSequence};
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
pub use crate::thread::AsyncThread;
#[cfg(feature = "serialize")]
#[doc(inline)]
pub use crate::serde::{ser::Options as SerializeOptions, LuaSerdeExt};
pub mod prelude;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
// Re-export #[mlua_derive::lua_module].
#[cfg(feature = "mlua_derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate mlua_derive;
#[cfg(feature = "mlua_derive")]
#[doc(hidden)]
pub use mlua_derive::*;
+1593 -449
View File
File diff suppressed because it is too large Load Diff
+16
View File
@@ -78,3 +78,19 @@ macro_rules! mlua_expect {
mlua_expect!($res, $msg)
};
}
#[cfg(feature = "module")]
#[doc(hidden)]
#[macro_export]
macro_rules! require_module_feature {
() => {};
}
#[cfg(not(feature = "module"))]
#[doc(hidden)]
#[macro_export]
macro_rules! require_module_feature {
() => {
compile_error!("Feature `module` must be enabled in the `mlua` crate");
};
}
+2 -2
View File
@@ -34,7 +34,7 @@ impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T {
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T {
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
Ok(T::from_lua(values.pop_front().unwrap_or(Nil), lua)?)
T::from_lua(values.pop_front().unwrap_or(Nil), lua)
}
}
@@ -146,7 +146,7 @@ macro_rules! impl_tuple {
}
impl<'lua> FromLuaMulti<'lua> for () {
fn from_lua_multi(_: MultiValue, _: &'lua Lua) -> Result<Self> {
fn from_lua_multi(_: MultiValue<'lua>, _: &'lua Lua) -> Result<Self> {
Ok(())
}
}
+14 -6
View File
@@ -3,10 +3,18 @@
pub use crate::{
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, Integer as LuaInteger, LightUserData as LuaLightUserData, Lua,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
RegistryKey as LuaRegistryKey, Result as LuaResult, Scope as LuaScope, String as LuaString,
Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, ToLua, ToLuaMulti,
UserData as LuaUserData, UserDataMethods as LuaUserDataMethods, Value as LuaValue,
Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger,
LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod,
MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, RegistryKey as LuaRegistryKey,
Result as LuaResult, String as LuaString, Table as LuaTable, TableExt as LuaTableExt,
TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread,
ThreadStatus as LuaThreadStatus, ToLua, ToLuaMulti, UserData as LuaUserData,
UserDataMethods as LuaUserDataMethods, Value as LuaValue,
};
#[cfg(feature = "async")]
pub use crate::AsyncThread as LuaAsyncThread;
#[cfg(feature = "serialize")]
#[doc(inline)]
pub use crate::{LuaSerdeExt, SerializeOptions as LuaSerializeOptions};
+490 -131
View File
@@ -1,22 +1,32 @@
use std::any::Any;
use std::cell::Cell;
use std::cell::RefCell;
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use std::rc::Rc;
use std::os::raw::{c_int, c_void};
#[cfg(feature = "serialize")]
use serde::Serialize;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, LuaRef};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::util::{
assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata,
take_userdata, StackGuard,
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{
assert_stack, check_stack, get_userdata, init_userdata_metatable, push_userdata, take_userdata,
StackGuard,
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
futures_core::future::{Future, LocalBoxFuture},
futures_util::future::{self, TryFutureExt},
};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be Send or 'static.
@@ -26,10 +36,12 @@ use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value};
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub struct Scope<'lua, 'scope> {
lua: &'lua Lua,
destructors: RefCell<Vec<(LuaRef<'lua>, fn(LuaRef<'lua>) -> Box<dyn Any>)>>,
destructors: RefCell<Vec<(LuaRef<'lua>, DestructorCallback<'lua>)>>,
_scope_invariant: PhantomData<Cell<&'scope ()>>,
}
type DestructorCallback<'lua> = Box<dyn Fn(LuaRef<'lua>) -> Vec<Box<dyn Any>> + 'lua>;
impl<'lua, 'scope> Scope<'lua, 'scope> {
pub(crate) fn new(lua: &'lua Lua) -> Scope<'lua, 'scope> {
Scope {
@@ -42,7 +54,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// Wraps a Rust function or closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_function`] that creates a callback which expires on
/// scope drop. See [`Lua::scope`] for more details.
/// scope drop. See [`Lua::scope`] for more details.
///
/// [`Lua::create_function`]: struct.Lua.html#method.create_function
/// [`Lua::scope`]: struct.Lua.html#method.scope
@@ -54,7 +66,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
{
// Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the
// callback itself must be 'scope lifetime, so the function should not be able to capture
// anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and
// anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and
// the 'callback lifetime here can't be enlarged due to coming from a universal
// quantification in Lua::scope.
//
@@ -71,7 +83,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_function_mut`] that creates a callback which expires
/// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
/// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
///
/// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut
/// [`Lua::scope`]: struct.Lua.html#method.scope
@@ -93,31 +105,109 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
})
}
/// Wraps a Rust async function or closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_async_function`] that creates a callback which expires on
/// scope drop. See [`Lua::scope`] and [`Lua::async_scope`] for more details.
///
/// Requires `feature = "async"`
///
/// [`Lua::create_async_function`]: struct.Lua.html#method.create_async_function
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::async_scope`]: struct.Lua.html#method.async_scope
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<'callback, A, R, F, FR>(
&'callback self,
func: F,
) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'scope + Fn(&'callback Lua, A) -> FR,
FR: 'callback + Future<Output = Result<R>>,
{
unsafe {
self.create_async_callback(Box::new(move |lua, args| {
let args = match A::from_lua_multi(args, lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
Box::pin(func(lua, args).and_then(move |ret| future::ready(ret.to_lua_multi(lua))))
}))
}
}
/// Create a Lua userdata object from a custom userdata type.
///
/// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send (but still requires that the
/// UserData be 'static). See [`Lua::scope`] for more details.
/// UserData be 'static).
/// See [`Lua::scope`] for more details.
///
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub fn create_static_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
self.create_userdata_inner(UserDataCell::new(data))
}
/// Create a Lua userdata object from a custom serializable userdata type.
///
/// This is a version of [`Lua::create_ser_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send (but still requires that the
/// UserData be 'static).
/// See [`Lua::scope`] for more details.
///
/// Requires `feature = "serialize"`
///
/// [`Lua::create_ser_userdata`]: struct.Lua.html#method.create_ser_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData + Serialize,
{
self.create_userdata_inner(UserDataCell::new_ser(data))
}
fn create_userdata_inner<T>(&self, data: UserDataCell<T>) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
// Safe even though T may not be Send, because the parent Lua cannot be sent to another
// thread while the Scope is alive (or the returned AnyUserData handle even).
unsafe {
let u = self.lua.make_userdata(data)?;
self.destructors.borrow_mut().push((u.0.clone(), |u| {
let state = u.lua.state;
let ud = self.lua.make_userdata(data)?;
#[cfg(any(feature = "lua51", feature = "luajit"))]
let newtable = self.lua.create_table()?;
let destructor: DestructorCallback = Box::new(move |ud| {
let state = ud.lua.state;
let _sg = StackGuard::new(state);
assert_stack(state, 2);
u.lua.push_ref(&u);
// We know the destructor has not run yet because we hold a reference to the
// userdata.
Box::new(take_userdata::<RefCell<T>>(state))
}));
Ok(u)
ud.lua.push_ref(&ud);
// We know the destructor has not run yet because we hold a reference to the userdata.
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushnil(state);
#[cfg(any(feature = "lua51", feature = "luajit"))]
ud.lua.push_ref(&newtable.0);
ffi::lua_setuservalue(state, -2);
vec![Box::new(take_userdata::<UserDataCell<T>>(state))]
});
self.destructors
.borrow_mut()
.push((ud.0.clone(), destructor));
Ok(ud)
}
}
@@ -129,18 +219,18 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
///
/// Lifting the requirement that the UserData type be 'static comes with some important
/// limitations, so if you only need to eliminate the Send requirement, it is probably better to
/// use [`Scope::create_static_userdata`] instead.
/// use [`Scope::create_userdata`] instead.
///
/// The main limitation that comes from using non-'static userdata is that the produced userdata
/// will no longer have a `TypeId` associated with it, becuase `TypeId` can only work for
/// 'static types. This means that it is impossible, once the userdata is created, to get a
/// reference to it back *out* of an `AnyUserData` handle. This also implies that the
/// 'static types. This means that it is impossible, once the userdata is created, to get a
/// reference to it back *out* of an `AnyUserData` handle. This also implies that the
/// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept
/// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use
/// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use
/// a single metatable for multiple non-'static types, so there is a higher cost associated with
/// creating the userdata metatable each time a new userdata is created.
///
/// [`create_static_userdata`]: #method.create_static_userdata
/// [`Scope::create_userdata`]: #method.create_userdata
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserDataMethods`]: trait.UserDataMethods.html
@@ -148,57 +238,47 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
where
T: 'scope + UserData,
{
let data = Rc::new(RefCell::new(data));
let data = UserDataCell::new_arc(data);
// 'callback outliving 'scope is a lie to make the types work out, required due to the
// inability to work with the more correct callback type that is universally quantified over
// 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua
// 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua
// lifetime, so none of the static methods UserData types can add can possibly capture
// parameters.
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
data: Rc<RefCell<T>>,
data: UserDataCell<T>,
data_ptr: *mut c_void,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
// On methods that actually receive the userdata, we fake a type check on the passed in
// userdata, where we pretend there is a unique type per call to
// `Scope::create_nonstatic_userdata`. You can grab a method from a userdata and call
// `Scope::create_nonstatic_userdata`. You can grab a method from a userdata and call
// it on a mismatched userdata type, which when using normal 'static userdata will fail
// with a type mismatch, but here without this check would proceed as though you had
// called the method on the original value (since we otherwise completely ignore the
// first argument).
let check_data = data.clone();
let check_ud_type = move |lua: &'callback Lua, value| {
if let Some(value) = value {
if let Value::UserData(u) = value {
unsafe {
assert_stack(lua.state, 1);
lua.push_ref(&u.0);
ffi::lua_getuservalue(lua.state, -1);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_pushinteger(lua.state, 1);
ffi::lua_gettable(lua.state, -2);
ffi::lua_remove(lua.state, -2);
}
return ffi::lua_touserdata(lua.state, -1)
== check_data.as_ptr() as *mut c_void;
if let Some(Value::UserData(ud)) = value {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&ud.0)?;
if get_userdata(lua.state, -1) == data_ptr {
return Ok(());
}
}
}
false
};
Err(Error::UserDataTypeMismatch)
};
match method {
NonStaticMethod::Method(method) => {
let method_data = data.clone();
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
let data = method_data
check_ud_type(lua, args.pop_front())?;
let data = data
.try_borrow()
.map(|cell| Ref::map(cell, AsRef::as_ref))
.map_err(|_| Error::UserDataBorrowError)?;
method(lua, &*data, args)
});
@@ -206,16 +286,14 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
}
NonStaticMethod::MethodMut(method) => {
let method = RefCell::new(method);
let method_data = data.clone();
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
check_ud_type(lua, args.pop_front())?;
let mut method = method
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let mut data = method_data
let mut data = data
.try_borrow_mut()
.map(|cell| RefMut::map(cell, AsMut::as_mut))
.map_err(|_| Error::UserDataBorrowMutError)?;
(&mut *method)(lua, &mut *data, args)
});
@@ -236,68 +314,133 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
}
}
let mut ud_fields = NonStaticUserDataFields::default();
let mut ud_methods = NonStaticUserDataMethods::default();
T::add_fields(&mut ud_fields);
T::add_methods(&mut ud_methods);
unsafe {
let lua = self.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
check_stack(lua.state, 13)?;
push_userdata(lua.state, ())?;
#[cfg(feature = "lua53")]
ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
protect_lua_closure(lua.state, 0, 1, |state| {
// Lua 5.2/5.1 allows to store only table. Then we will wrap the value.
ffi::lua_createtable(state, 1, 0);
ffi::lua_pushinteger(state, 1);
ffi::lua_pushlightuserdata(state, data.as_ptr() as *mut c_void);
ffi::lua_settable(state, -3);
})?;
ffi::lua_setuservalue(lua.state, -2);
push_userdata(lua.state, data.clone())?;
let data_ptr = ffi::lua_touserdata(lua.state, -1);
protect_lua_closure(lua.state, 0, 1, move |state| {
ffi::lua_newtable(state);
})?;
// Prepare metatable, add meta methods first and then meta fields
let meta_methods_nrec = ud_methods.meta_methods.len() + ud_fields.meta_fields.len() + 1;
ffi::safe::lua_createtable(lua.state, 0, meta_methods_nrec as c_int)?;
for (k, m) in ud_methods.meta_methods {
push_string(lua.state, k.name())?;
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
let data = data.clone();
lua.push_value(Value::Function(wrap_method(self, data, data_ptr, m)?))?;
ffi::safe::lua_rawsetfield(lua.state, -2, k.validate()?.name())?;
}
for (k, f) in ud_fields.meta_fields {
lua.push_value(f(mem::transmute(lua))?)?;
ffi::safe::lua_rawsetfield(lua.state, -2, k.validate()?.name())?;
}
let metatable_index = ffi::lua_absindex(lua.state, -1);
protect_lua_closure(lua.state, 3, 1, |state| {
ffi::lua_rawset(state, -3);
})?;
let mut field_getters_index = None;
let field_getters_nrec = ud_fields.field_getters.len();
if field_getters_nrec > 0 {
ffi::safe::lua_createtable(lua.state, 0, field_getters_nrec as c_int)?;
for (k, m) in ud_fields.field_getters {
let data = data.clone();
lua.push_value(Value::Function(wrap_method(self, data, data_ptr, m)?))?;
ffi::safe::lua_rawsetfield(lua.state, -2, &k)?;
}
field_getters_index = Some(ffi::lua_absindex(lua.state, -1));
}
if ud_methods.methods.is_empty() {
init_userdata_metatable::<()>(lua.state, -1, None)?;
} else {
protect_lua_closure(lua.state, 0, 1, |state| {
ffi::lua_newtable(state);
})?;
let mut field_setters_index = None;
let field_setters_nrec = ud_fields.field_setters.len();
if field_setters_nrec > 0 {
ffi::safe::lua_createtable(lua.state, 0, field_setters_nrec as c_int)?;
for (k, m) in ud_fields.field_setters {
let data = data.clone();
lua.push_value(Value::Function(wrap_method(self, data, data_ptr, m)?))?;
ffi::safe::lua_rawsetfield(lua.state, -2, &k)?;
}
field_setters_index = Some(ffi::lua_absindex(lua.state, -1));
}
let mut methods_index = None;
let methods_nrec = ud_methods.methods.len();
if methods_nrec > 0 {
// Create table used for methods lookup
ffi::safe::lua_createtable(lua.state, 0, methods_nrec as c_int)?;
for (k, m) in ud_methods.methods {
push_string(lua.state, &k)?;
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
protect_lua_closure(lua.state, 3, 1, |state| {
ffi::lua_rawset(state, -3);
})?;
let data = data.clone();
lua.push_value(Value::Function(wrap_method(self, data, data_ptr, m)?))?;
ffi::safe::lua_rawsetfield(lua.state, -2, &k)?;
}
methods_index = Some(ffi::lua_absindex(lua.state, -1));
}
init_userdata_metatable::<()>(
lua.state,
metatable_index,
field_getters_index,
field_setters_index,
methods_index,
)?;
let count = field_getters_index.map(|_| 1).unwrap_or(0)
+ field_setters_index.map(|_| 1).unwrap_or(0)
+ methods_index.map(|_| 1).unwrap_or(0);
ffi::lua_pop(lua.state, count);
let mt_id = ffi::lua_topointer(lua.state, -1);
ffi::lua_setmetatable(lua.state, -2);
let ud = AnyUserData(lua.pop_ref());
lua.register_userdata_metatable(mt_id as isize);
#[cfg(any(feature = "lua51", feature = "luajit"))]
let newtable = lua.create_table()?;
let destructor: DestructorCallback = Box::new(move |ud| {
let state = ud.lua.state;
let _sg = StackGuard::new(state);
assert_stack(state, 2);
ud.lua.push_ref(&ud);
// We know the destructor has not run yet because we hold a reference to the userdata.
// Deregister metatable
ffi::lua_getmetatable(state, -1);
let mt_id = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
ud.lua.deregister_userdata_metatable(mt_id as isize);
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushnil(state);
#[cfg(any(feature = "lua51", feature = "luajit"))]
ud.lua.push_ref(&newtable.0);
ffi::lua_setuservalue(state, -2);
// A hack to drop non-static `T`
unsafe fn seal<T>(t: T) -> Box<dyn FnOnce() + 'static> {
let f: Box<dyn FnOnce()> = Box::new(move || drop(t));
mem::transmute(f)
}
init_userdata_metatable::<()>(lua.state, -2, Some(-1))?;
ffi::lua_pop(lua.state, 1);
}
vec![Box::new(seal(take_userdata::<UserDataCell<T>>(state)))]
});
self.destructors
.borrow_mut()
.push((ud.0.clone(), destructor));
ffi::lua_setmetatable(lua.state, -2);
Ok(AnyUserData(lua.pop_ref()))
Ok(ud)
}
}
// Unsafe, because the callback can improperly capture any value with 'callback scope, such as
// improperly capturing an argument. Since the 'callback lifetime is chosen by the user and the
// lifetime of the callback itself is 'scope (non-'static), the borrow checker will happily pick
// a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback
// a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback
// must NOT capture any parameters.
unsafe fn create_callback<'callback>(
&self,
@@ -306,22 +449,100 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let f = mem::transmute::<Callback<'callback, 'scope>, Callback<'lua, 'static>>(f);
let f = self.lua.create_callback(f)?;
let mut destructors = self.destructors.borrow_mut();
destructors.push((f.0.clone(), |f| {
let destructor: DestructorCallback = Box::new(|f| {
let state = f.lua.state;
let _sg = StackGuard::new(state);
assert_stack(state, 3);
f.lua.push_ref(&f);
ffi::lua_getupvalue(state, -1, 1);
// We know the destructor has not run yet because we hold a reference to the callback.
let ud = take_userdata::<Callback>(state);
ffi::lua_getupvalue(state, -1, 2);
let ud1 = take_userdata::<Callback>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
ffi::lua_setupvalue(state, -2, 2);
ffi::lua_getupvalue(state, -1, 3);
let ud2 = take_userdata::<Lua>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 3);
vec![Box::new(ud1), Box::new(ud2)]
});
self.destructors
.borrow_mut()
.push((f.0.clone(), destructor));
Ok(f)
}
#[cfg(feature = "async")]
unsafe fn create_async_callback<'callback>(
&self,
f: AsyncCallback<'callback, 'scope>,
) -> Result<Function<'lua>> {
let f = mem::transmute::<AsyncCallback<'callback, 'scope>, AsyncCallback<'lua, 'static>>(f);
let f = self.lua.create_async_callback(f)?;
// We need to pre-allocate strings to avoid failures in destructor.
let get_poll_str = self.lua.create_string("get_poll")?;
let poll_str = self.lua.create_string("poll")?;
let destructor: DestructorCallback = Box::new(move |f| {
let state = f.lua.state;
let _sg = StackGuard::new(state);
assert_stack(state, 5);
f.lua.push_ref(&f);
// We know the destructor has not run yet because we hold a reference to the callback.
// First, get the environment table
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_getupvalue(state, -1, 1);
#[cfg(any(feature = "lua51", feature = "luajit"))]
ffi::lua_getfenv(state, -1);
// Second, get the `get_poll()` closure using the corresponding key
f.lua.push_ref(&get_poll_str.0);
ffi::lua_rawget(state, -2);
// Destroy all upvalues
ffi::lua_getupvalue(state, -1, 2);
let ud1 = take_userdata::<AsyncCallback>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 2);
ffi::lua_getupvalue(state, -1, 3);
let ud2 = take_userdata::<Lua>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 3);
ffi::lua_pop(state, 1);
Box::new(ud)
}));
let mut data: Vec<Box<dyn Any>> = vec![Box::new(ud1), Box::new(ud2)];
// Finally, get polled future and destroy it
f.lua.push_ref(&poll_str.0);
if ffi::lua_rawget(state, -2) == ffi::LUA_TFUNCTION {
ffi::lua_getupvalue(state, -1, 2);
let ud3 = take_userdata::<LocalBoxFuture<Result<MultiValue>>>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 2);
data.push(Box::new(ud3));
ffi::lua_getupvalue(state, -1, 3);
let ud4 = take_userdata::<Lua>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 3);
data.push(Box::new(ud4));
}
data
});
self.destructors
.borrow_mut()
.push((f.0.clone(), destructor));
Ok(f)
}
}
@@ -329,7 +550,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
fn drop(&mut self) {
// We separate the action of invalidating the userdata in Lua and actually dropping the
// userdata type into two phases. This is so that, in the event a userdata drop panics, we
// userdata type into two phases. This is so that, in the event a userdata drop panics, we
// can be sure that all of the userdata in Lua is actually invalidated.
// All destructors are non-panicking, so this is fine
@@ -337,7 +558,7 @@ impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
.destructors
.get_mut()
.drain(..)
.map(|(r, dest)| dest(r))
.flat_map(|(r, dest)| dest(r))
.collect::<Vec<_>>();
drop(to_drop);
@@ -368,10 +589,10 @@ impl<'lua, T: UserData> Default for NonStaticUserDataMethods<'lua, T> {
impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'lua, T> {
fn add_method<S, A, R, M>(&mut self, name: &S, method: M)
where
S: ?Sized + AsRef<[u8]>,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
@@ -383,10 +604,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
fn add_method_mut<S, A, R, M>(&mut self, name: &S, mut method: M)
where
S: ?Sized + AsRef<[u8]>,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
@@ -396,12 +617,27 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
));
}
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
#[cfg(feature = "async")]
fn add_async_method<S, A, R, M, MR>(&mut self, _name: &S, _method: M)
where
S: ?Sized + AsRef<[u8]>,
T: Clone,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR,
MR: 'lua + Future<Output = Result<R>>,
{
// The panic should never happen as async non-static code wouldn't compile
// Non-static lifetime must be bounded to 'lua lifetime
mlua_panic!("asynchronous methods are not supported for non-static userdata")
}
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
where
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
@@ -413,10 +649,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
fn add_function_mut<S, A, R, F>(&mut self, name: &S, mut function: F)
where
S: ?Sized + AsRef<[u8]>,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
@@ -426,59 +662,182 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
));
}
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
#[cfg(feature = "async")]
fn add_async_function<S, A, R, F, FR>(&mut self, _name: &S, _function: F)
where
T: Clone,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR,
FR: 'lua + Future<Output = Result<R>>,
{
// The panic should never happen as async non-static code wouldn't compile
// Non-static lifetime must be bounded to 'lua lifetime
mlua_panic!("asynchronous functions are not supported for non-static userdata")
}
fn add_meta_method<S, A, R, M>(&mut self, meta: S, method: M)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.meta_methods.push((
meta,
meta.into(),
NonStaticMethod::Method(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, mut method: M)
fn add_meta_method_mut<S, A, R, M>(&mut self, meta: S, mut method: M)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.meta_methods.push((
meta,
meta.into(),
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F)
fn add_meta_function<S, A, R, F>(&mut self, meta: S, function: F)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
{
self.meta_methods.push((
meta,
meta.into(),
NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, mut function: F)
fn add_meta_function_mut<S, A, R, F>(&mut self, meta: S, mut function: F)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>,
{
self.meta_methods.push((
meta,
meta.into(),
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
}
struct NonStaticUserDataFields<'lua, T: UserData> {
field_getters: Vec<(Vec<u8>, NonStaticMethod<'lua, T>)>,
field_setters: Vec<(Vec<u8>, NonStaticMethod<'lua, T>)>,
#[allow(clippy::type_complexity)]
meta_fields: Vec<(MetaMethod, Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>>>)>,
}
impl<'lua, T: UserData> Default for NonStaticUserDataFields<'lua, T> {
fn default() -> NonStaticUserDataFields<'lua, T> {
NonStaticUserDataFields {
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
}
}
}
impl<'lua, T: UserData> UserDataFields<'lua, T> for NonStaticUserDataFields<'lua, T> {
fn add_field_method_get<S, R, M>(&mut self, name: &S, method: M)
where
S: AsRef<[u8]> + ?Sized,
R: ToLua<'lua>,
M: 'static + MaybeSend + Fn(&'lua Lua, &T) -> Result<R>,
{
self.field_getters.push((
name.as_ref().to_vec(),
NonStaticMethod::Method(Box::new(move |lua, ud, _| {
method(lua, ud)?.to_lua_multi(lua)
})),
));
}
fn add_field_method_set<S, A, M>(&mut self, name: &S, mut method: M)
where
S: AsRef<[u8]> + ?Sized,
A: FromLua<'lua>,
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<()>,
{
self.field_setters.push((
name.as_ref().to_vec(),
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_field_function_get<S, R, F>(&mut self, name: &S, function: F)
where
S: AsRef<[u8]> + ?Sized,
R: ToLua<'lua>,
F: 'static + MaybeSend + Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R>,
{
self.field_getters.push((
name.as_ref().to_vec(),
NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, AnyUserData::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_field_function_set<S, A, F>(&mut self, name: &S, mut function: F)
where
S: AsRef<[u8]> + ?Sized,
A: FromLua<'lua>,
F: 'static + MaybeSend + FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()>,
{
self.field_setters.push((
name.as_ref().to_vec(),
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
let (ud, val) = <_>::from_lua_multi(args, lua)?;
function(lua, ud, val)?.to_lua_multi(lua)
})),
));
}
fn add_meta_field_with<S, R, F>(&mut self, meta: S, f: F)
where
S: Into<MetaMethod>,
F: 'static + MaybeSend + Fn(&'lua Lua) -> Result<R>,
R: ToLua<'lua>,
{
let meta = meta.into();
self.meta_fields.push((
meta.clone(),
Box::new(move |lua| {
let value = f(lua)?.to_lua(lua)?;
if meta == MetaMethod::Index || meta == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: meta.to_string(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
Ok(value)
}),
));
}
}
+337
View File
@@ -0,0 +1,337 @@
use std::string::String as StdString;
use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result};
use crate::table::{TablePairs, TableSequence};
use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
#[derive(Debug)]
pub struct Deserializer<'lua>(Value<'lua>);
impl<'lua> Deserializer<'lua> {
/// Creates a new Lua Deserializer for the `Value`.
pub fn new(value: Value<'lua>) -> Self {
Deserializer(value)
}
}
impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
type Error = Error;
#[inline]
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Nil => visitor.visit_unit(),
Value::Boolean(b) => visitor.visit_bool(b),
#[allow(clippy::useless_conversion)]
Value::Integer(i) => visitor.visit_i64(i.into()),
#[allow(clippy::useless_conversion)]
Value::Number(n) => visitor.visit_f64(n.into()),
Value::String(s) => match s.to_str() {
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(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_) => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Nil => visitor.visit_none(),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
_ => visitor.visit_some(self),
}
}
#[inline]
fn deserialize_enum<V>(
self,
_name: &str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
let (variant, value) = match self.0 {
Value::Table(value) => {
let mut iter = value.pairs::<StdString, Value>();
let (variant, value) = match iter.next() {
Some(v) => v?,
None => {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
))
}
};
if iter.next().is_some() {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
));
}
(variant, Some(value))
}
Value::String(variant) => (variant.to_str()?.to_owned(), None),
_ => return Err(de::Error::custom("bad enum value")),
};
visitor.visit_enum(EnumDeserializer { variant, value })
}
#[inline]
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Table(t) => {
let len = t.raw_len() as usize;
let mut deserializer = SeqDeserializer(t.raw_sequence_values());
let seq = visitor.visit_seq(&mut deserializer)?;
if deserializer.0.count() == 0 {
Ok(seq)
} else {
Err(de::Error::invalid_length(
len,
&"fewer elements in the table",
))
}
}
_ => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
#[inline]
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
#[inline]
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Table(t) => {
let mut deserializer = MapDeserializer::new(t.pairs());
let map = visitor.visit_map(&mut deserializer)?;
let count = deserializer.pairs.count();
if count == 0 {
Ok(map)
} else {
Err(de::Error::invalid_length(
deserializer.processed + count,
&"fewer elements in the table",
))
}
}
_ => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_map(visitor)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf unit unit_struct newtype_struct
identifier ignored_any
}
}
struct SeqDeserializer<'lua>(TableSequence<'lua, Value<'lua>>);
impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.0.next() {
Some(value) => seed.deserialize(Deserializer(value?)).map(Some),
None => Ok(None),
}
}
fn size_hint(&self) -> Option<usize> {
match self.0.size_hint() {
(lower, Some(upper)) if lower == upper => Some(upper),
_ => None,
}
}
}
struct MapDeserializer<'lua> {
pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>,
value: Option<Value<'lua>>,
processed: usize,
}
impl<'lua> MapDeserializer<'lua> {
fn new(pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>) -> Self {
MapDeserializer {
pairs,
value: None,
processed: 0,
}
}
}
impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
type Error = Error;
fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.pairs.next() {
Some(item) => {
let (key, value) = item?;
self.processed += 1;
self.value = Some(value);
let key_de = Deserializer(key);
seed.deserialize(key_de).map(Some)
}
None => Ok(None),
}
}
fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value.take() {
Some(value) => seed.deserialize(Deserializer(value)),
None => Err(de::Error::custom("value is missing")),
}
}
fn size_hint(&self) -> Option<usize> {
match self.pairs.size_hint() {
(lower, Some(upper)) if lower == upper => Some(upper),
_ => None,
}
}
}
struct EnumDeserializer<'lua> {
variant: StdString,
value: Option<Value<'lua>>,
}
impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
type Error = Error;
type Variant = VariantDeserializer<'lua>;
fn variant_seed<T>(self, seed: T) -> Result<(T::Value, Self::Variant)>
where
T: de::DeserializeSeed<'de>,
{
let variant = self.variant.into_deserializer();
let variant_access = VariantDeserializer { value: self.value };
seed.deserialize(variant).map(|v| (v, variant_access))
}
}
struct VariantDeserializer<'lua> {
value: Option<Value<'lua>>,
}
impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
type Error = Error;
fn unit_variant(self) -> Result<()> {
match self.value {
Some(_) => Err(de::Error::invalid_type(
de::Unexpected::NewtypeVariant,
&"unit variant",
)),
None => Ok(()),
}
}
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value {
Some(value) => seed.deserialize(Deserializer(value)),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"newtype variant",
)),
}
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_seq(Deserializer(value), visitor),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"tuple variant",
)),
}
}
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_map(Deserializer(value), visitor),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"struct variant",
)),
}
}
}
+227
View File
@@ -0,0 +1,227 @@
//! (De)Serialization support using serde.
use std::os::raw::c_void;
use std::ptr;
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::ffi;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::LightUserData;
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde.
pub trait LuaSerdeExt<'lua> {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
/// use std::collections::HashMap;
/// use mlua::{Lua, Result, LuaSerdeExt};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("null", lua.null())?;
///
/// let val = lua.load(r#"{a = null}"#).eval()?;
/// let map: HashMap<String, Option<String>> = lua.from_value(val)?;
/// assert_eq!(map["a"], None);
///
/// Ok(())
/// }
/// ```
fn null(&'lua self) -> Value<'lua>;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map).
/// 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
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde_json::Value as JsonValue;
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("array_mt", lua.array_metatable())?;
///
/// // Encode as an empty array (no sequence part in the lua table)
/// let val = lua.load("setmetatable({a = 5}, array_mt)").eval()?;
/// let j: JsonValue = lua.from_value(val)?;
/// assert_eq!(j.to_string(), "[]");
///
/// // Encode as object
/// let val = lua.load("{a = 5}").eval()?;
/// let j: JsonValue = lua.from_value(val)?;
/// assert_eq!(j.to_string(), r#"{"a":5}"#);
///
/// Ok(())
/// }
/// ```
fn array_metatable(&'lua self) -> Table<'lua>;
/// Converts `T` into a `Value` instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct User {
/// name: String,
/// age: u8,
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let u = User {
/// name: "John Smith".into(),
/// age: 20,
/// };
/// lua.globals().set("user", lua.to_value(&u)?)?;
/// lua.load(r#"
/// assert(user["name"] == "John Smith")
/// assert(user["age"] == 20)
/// "#).exec()
/// }
/// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Converts `T` into a `Value` instance with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt, SerializeOptions};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let v = vec![1, 2, 3];
/// let options = SerializeOptions::new().set_array_metatable(false);
/// lua.globals().set("v", lua.to_value_with(&v, options)?)?;
///
/// lua.load(r#"
/// assert(#v == 3 and v[1] == 1 and v[2] == 2 and v[3] == 3)
/// assert(getmetatable(v) == nil)
/// "#).exec()
/// }
/// ```
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where
T: Serialize + ?Sized;
/// Deserializes a `Value` into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde::Deserialize;
///
/// #[derive(Deserialize, Debug, PartialEq)]
/// struct User {
/// name: String,
/// age: u8,
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let val = lua.load(r#"{name = "John Smith", age = 20}"#).eval()?;
/// let u: User = lua.from_value(val)?;
///
/// assert_eq!(u, User { name: "John Smith".into(), age: 20 });
///
/// Ok(())
/// }
/// ```
fn from_value<T: Deserialize<'lua>>(&'lua self, value: Value<'lua>) -> Result<T>;
}
impl<'lua> LuaSerdeExt<'lua> for Lua {
fn null(&'lua self) -> Value<'lua> {
Value::LightUserData(LightUserData(ptr::null_mut()))
}
fn array_metatable(&'lua self) -> Table<'lua> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
push_array_metatable(self.state);
Table(self.pop_ref())
}
}
fn to_value<T>(&'lua self, t: &T) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer::new(self))
}
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer::new_with_options(self, options))
}
fn from_value<T>(&'lua self, value: Value<'lua>) -> Result<T>
where
T: Deserialize<'lua>,
{
T::deserialize(de::Deserializer::new(value))
}
}
// Uses 6 stack spaces and calls checkstack.
pub(crate) unsafe fn init_metatables(state: *mut ffi::lua_State) -> Result<()> {
check_stack(state, 6)?;
ffi::safe::lua_createtable(state, 0, 1)?;
ffi::lua_pushboolean(state, 0);
ffi::safe::lua_rawsetfield(state, -2, "__metatable")?;
let array_metatable_key = &ARRAY_METATABLE_REGISTRY_KEY as *const u8 as *const c_void;
ffi::safe::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, array_metatable_key)
}
pub(crate) unsafe fn push_array_metatable(state: *mut ffi::lua_State) {
let array_metatable_key = &ARRAY_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, array_metatable_key);
}
static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de;
pub mod ser;
#[doc(inline)]
pub use de::Deserializer;
#[doc(inline)]
pub use ser::Serializer;
+478
View File
@@ -0,0 +1,478 @@
use std::os::raw::c_int;
use serde::{ser, Serialize};
use super::LuaSerdeExt;
use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::types::Integer;
use crate::util::{check_stack, StackGuard};
use crate::value::{ToLua, Value};
/// A struct for serializing Rust values into Lua values.
#[derive(Debug)]
pub struct Serializer<'lua> {
lua: &'lua Lua,
options: Options,
}
/// A struct with options to change default serializer behaviour.
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Options {
/// If true, sequence serialization to a Lua table will create table
/// with the [`array_metatable`] attached.
///
/// Default: **true**
///
/// [`array_metatable`]: ../trait.LuaSerdeExt.html#tymethod.array_metatable
pub set_array_metatable: bool,
/// If true, serialize `None` (part of `Option` type) to [`null`].
/// Otherwise it will be set to Lua [`Nil`].
///
/// Default: **true**
///
/// [`null`]: ../trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../../enum.Value.html#variant.Nil
pub serialize_none_to_null: bool,
/// If true, serialize `Unit` (type of `()` in Rust) and Unit structs to [`null`].
/// Otherwise it will be set to Lua [`Nil`].
///
/// Default: **true**
///
/// [`null`]: ../trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../../enum.Value.html#variant.Nil
pub serialize_unit_to_null: bool,
}
impl Default for Options {
fn default() -> Self {
Options {
set_array_metatable: true,
serialize_none_to_null: true,
serialize_unit_to_null: true,
}
}
}
impl Options {
/// Retruns a new instance of `Options` with default parameters.
pub fn new() -> Self {
Self::default()
}
/// Sets [`set_array_metatable`] option.
///
/// [`set_array_metatable`]: #structfield.set_array_metatable
pub fn set_array_metatable(mut self, enabled: bool) -> Self {
self.set_array_metatable = enabled;
self
}
/// Sets [`serialize_none_to_null`] option.
///
/// [`serialize_none_to_null`]: #structfield.serialize_none_to_null
pub fn serialize_none_to_null(mut self, enabled: bool) -> Self {
self.serialize_none_to_null = enabled;
self
}
/// Sets [`serialize_unit_to_null`] option.
///
/// [`serialize_unit_to_null`]: #structfield.serialize_unit_to_null
pub fn serialize_unit_to_null(mut self, enabled: bool) -> Self {
self.serialize_unit_to_null = enabled;
self
}
}
impl<'lua> Serializer<'lua> {
/// Creates a new Lua Serializer with default options.
pub fn new(lua: &'lua Lua) -> Self {
Self::new_with_options(lua, Options::default())
}
/// Creates a new Lua Serializer with custom options.
pub fn new_with_options(lua: &'lua Lua, options: Options) -> Self {
Serializer { lua, options }
}
}
macro_rules! lua_serialize_number {
($name:ident, $t:ty) => {
#[inline]
fn $name(self, value: $t) -> Result<Value<'lua>> {
value.to_lua(self.lua)
}
};
}
impl<'lua> ser::Serializer for Serializer<'lua> {
type Ok = Value<'lua>;
type Error = Error;
// Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps.
type SerializeSeq = SerializeVec<'lua>;
type SerializeTuple = SerializeVec<'lua>;
type SerializeTupleStruct = SerializeVec<'lua>;
type SerializeTupleVariant = SerializeTupleVariant<'lua>;
type SerializeMap = SerializeMap<'lua>;
type SerializeStruct = SerializeMap<'lua>;
type SerializeStructVariant = SerializeStructVariant<'lua>;
#[inline]
fn serialize_bool(self, value: bool) -> Result<Value<'lua>> {
Ok(Value::Boolean(value))
}
lua_serialize_number!(serialize_i8, i8);
lua_serialize_number!(serialize_u8, u8);
lua_serialize_number!(serialize_i16, i16);
lua_serialize_number!(serialize_u16, u16);
lua_serialize_number!(serialize_i32, i32);
lua_serialize_number!(serialize_u32, u32);
lua_serialize_number!(serialize_i64, i64);
lua_serialize_number!(serialize_u64, u64);
lua_serialize_number!(serialize_f32, f32);
lua_serialize_number!(serialize_f64, f64);
#[inline]
fn serialize_char(self, value: char) -> Result<Value<'lua>> {
self.serialize_str(&value.to_string())
}
#[inline]
fn serialize_str(self, value: &str) -> Result<Value<'lua>> {
self.lua.create_string(value).map(Value::String)
}
#[inline]
fn serialize_bytes(self, value: &[u8]) -> Result<Value<'lua>> {
self.lua.create_string(value).map(Value::String)
}
#[inline]
fn serialize_none(self) -> Result<Value<'lua>> {
if self.options.serialize_none_to_null {
Ok(self.lua.null())
} else {
Ok(Value::Nil)
}
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
value.serialize(self)
}
#[inline]
fn serialize_unit(self) -> Result<Value<'lua>> {
if self.options.serialize_unit_to_null {
Ok(self.lua.null())
} else {
Ok(Value::Nil)
}
}
#[inline]
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value<'lua>> {
if self.options.serialize_unit_to_null {
Ok(self.lua.null())
} else {
Ok(Value::Nil)
}
}
#[inline]
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Value<'lua>> {
self.serialize_str(variant)
}
#[inline]
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
value.serialize(self)
}
#[inline]
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
let table = self.lua.create_table()?;
let variant = self.lua.create_string(variant)?;
let value = self.lua.to_value_with(value, self.options)?;
table.raw_set(variant, value)?;
Ok(Value::Table(table))
}
#[inline]
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let len = len.unwrap_or(0) as c_int;
let table = self.lua.create_table_with_capacity(len, 0)?;
if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable()));
}
let options = self.options;
Ok(SerializeVec { table, options })
}
#[inline]
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
#[inline]
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
self.serialize_seq(Some(len))
}
#[inline]
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
Ok(SerializeTupleVariant {
name: self.lua.create_string(variant)?,
table: self.lua.create_table()?,
options: self.options,
})
}
#[inline]
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
let len = len.unwrap_or(0) as c_int;
Ok(SerializeMap {
key: None,
table: self.lua.create_table_with_capacity(0, len)?,
options: self.options,
})
}
#[inline]
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
self.serialize_map(Some(len))
}
#[inline]
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant> {
Ok(SerializeStructVariant {
name: self.lua.create_string(variant)?,
table: self.lua.create_table_with_capacity(0, len as c_int)?,
options: self.options,
})
}
}
#[doc(hidden)]
pub struct SerializeVec<'lua> {
table: Table<'lua>,
options: Options,
}
impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
let value = lua.to_value_with(value, self.options)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 6)?;
lua.push_ref(&self.table.0);
lua.push_value(value)?;
let len = ffi::lua_rawlen(lua.state, -2) as Integer;
ffi::safe::lua_rawseti(lua.state, -2, len + 1)
}
}
fn end(self) -> Result<Value<'lua>> {
Ok(Value::Table(self.table))
}
}
impl<'lua> ser::SerializeTuple for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeSeq::end(self)
}
}
impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeSeq::end(self)
}
}
#[doc(hidden)]
pub struct SerializeTupleVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
options: Options,
}
impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
let idx = self.table.raw_len() + 1;
self.table
.raw_insert(idx, lua.to_value_with(value, self.options)?)
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
Ok(Value::Table(table))
}
}
#[doc(hidden)]
pub struct SerializeMap<'lua> {
table: Table<'lua>,
key: Option<Value<'lua>>,
options: Options,
}
impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
self.key = Some(lua.to_value_with(key, self.options)?);
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
let key = mlua_expect!(
self.key.take(),
"serialize_value called before serialize_key"
);
let value = lua.to_value_with(value, self.options)?;
self.table.raw_set(key, value)
}
fn end(self) -> Result<Value<'lua>> {
Ok(Value::Table(self.table))
}
}
impl<'lua> ser::SerializeStruct for SerializeMap<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
ser::SerializeMap::serialize_key(self, key)?;
ser::SerializeMap::serialize_value(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeMap::end(self)
}
}
#[doc(hidden)]
pub struct SerializeStructVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
options: Options,
}
impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
self.table
.raw_set(key, lua.to_value_with(value, self.options)?)?;
Ok(())
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
Ok(Value::Table(table))
}
}
+37 -10
View File
@@ -1,31 +1,58 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
use std::u32;
/// Flags describing the set of lua modules to load.
/// Flags describing the set of lua standard libraries to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct StdLib(u32);
impl StdLib {
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub const COROUTINE: StdLib = StdLib(1 << 0);
/// [`coroutine`](https://www.lua.org/manual/5.3/manual.html#6.2) library
///
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.3/manual.html#6.6) library
pub const TABLE: StdLib = StdLib(1 << 1);
/// [`io`](https://www.lua.org/manual/5.3/manual.html#6.8) library
pub const IO: StdLib = StdLib(1 << 2);
/// [`os`](https://www.lua.org/manual/5.3/manual.html#6.9) library
pub const OS: StdLib = StdLib(1 << 3);
/// [`string`](https://www.lua.org/manual/5.3/manual.html#6.4) library
pub const STRING: StdLib = StdLib(1 << 4);
#[cfg(feature = "lua53")]
/// [`utf8`](https://www.lua.org/manual/5.3/manual.html#6.5) library
///
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
pub const UTF8: StdLib = StdLib(1 << 5);
#[cfg(any(feature = "lua52", feature = "luajit"))]
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
///
/// Requires `feature = "lua52/luajit"`
#[cfg(any(feature = "lua52", feature = "luajit", doc))]
pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.3/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.3/manual.html#6.3) library
pub const PACKAGE: StdLib = StdLib(1 << 8);
#[cfg(feature = "luajit")]
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
pub const JIT: StdLib = StdLib(1 << 9);
#[cfg(feature = "luajit")]
pub const FFI: StdLib = StdLib(1 << 10);
pub const DEBUG: StdLib = StdLib(1 << 31); // always highest bit
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
pub const FFI: StdLib = StdLib(1 << 30);
/// (**unsafe**) [`debug`](https://www.lua.org/manual/5.3/manual.html#6.10) library
pub const DEBUG: StdLib = StdLib(1 << 31);
/// No libraries
pub const NONE: StdLib = StdLib(0);
/// (**unsafe**) All standard libraries
pub const ALL: StdLib = StdLib(u32::MAX);
pub const ALL_NO_DEBUG: StdLib = StdLib((1 << 31) - 1);
/// The safe subset of the standard libraries
pub const ALL_SAFE: StdLib = StdLib((1 << 30) - 1);
pub fn contains(self, lib: Self) -> bool {
(self & lib).0 != 0
+19
View File
@@ -1,5 +1,11 @@
use std::{slice, str};
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
@@ -105,3 +111,16 @@ where
self.as_bytes() == other.as_ref()
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for String<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self.to_str() {
Ok(s) => serializer.serialize_str(s),
Err(_) => serializer.serialize_bytes(self.as_bytes()),
}
}
}
+274 -142
View File
@@ -1,17 +1,26 @@
use std::marker::PhantomData;
use std::os::raw::c_int;
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::types::{Integer, LuaRef};
use crate::util::{assert_stack, protect_lua, protect_lua_closure, StackGuard};
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::{FromLua, FromLuaMulti, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// Handle to an internal Lua table.
#[derive(Clone, Debug)]
pub struct Table<'lua>(pub(crate) LuaRef<'lua>);
#[allow(clippy::len_without_is_empty)]
impl<'lua> Table<'lua> {
/// Sets a key-value pair in the table.
///
@@ -50,19 +59,15 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
check_stack(lua.state, 6)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
unsafe extern "C" fn set_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settable(state, -3);
1
}
protect_lua(lua.state, 3, set_table)
ffi::safe::lua_settable(lua.state, -3)
}
}
@@ -93,18 +98,15 @@ impl<'lua> Table<'lua> {
pub fn get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 5);
check_stack(lua.state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
ffi::safe::lua_gettable(lua.state, -2)?;
unsafe extern "C" fn get_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_gettable(state, -2);
1
}
protect_lua(lua.state, 2, get_table)?;
lua.pop_value()
};
V::from_lua(value, lua)
@@ -117,55 +119,16 @@ impl<'lua> Table<'lua> {
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 5);
check_stack(lua.state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
ffi::safe::lua_gettable(lua.state, -2)?;
unsafe extern "C" fn get_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_gettable(state, -2);
1
}
protect_lua(lua.state, 2, get_table)?;
let has = ffi::lua_isnil(lua.state, -1) == 0;
Ok(has)
Ok(ffi::lua_isnil(lua.state, -1) == 0)
}
}
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself as the first argument.
///
/// # Examples
///
/// Execute the table method with name "concat":
///
/// ```
/// # use mlua::{Lua, Result, Table};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// # let object = lua.create_table()?;
/// # let concat = lua.create_function(|_, (_, a, b): (Table, String, String)| Ok(a + &b))?;
/// # object.set("concat", concat)?;
/// // simiar to: object:concat("param1", "param2")
/// object.call("concat", ("param1", "param2"))?;
/// # Ok(())
/// # }
/// ```
///
/// This might invoke the `__index` metamethod.
pub fn call<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let mut args = args.to_lua_multi(lua)?;
args.push_front(Value::Table(self.clone()));
self.get::<_, Function>(key)?.call(args)
}
/// Compares two tables for equality.
///
/// Tables are compared by reference first.
@@ -230,19 +193,12 @@ impl<'lua> Table<'lua> {
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
check_stack(lua.state, 6)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
unsafe extern "C" fn raw_set(state: *mut ffi::lua_State) -> c_int {
ffi::lua_rawset(state, -3);
0
}
protect_lua(lua.state, 3, raw_set)?;
Ok(())
ffi::safe::lua_rawset(lua.state, -3)
}
}
@@ -250,19 +206,21 @@ impl<'lua> Table<'lua> {
pub fn raw_get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
check_stack(lua.state, 3)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
ffi::lua_rawget(lua.state, -2);
lua.pop_value()
};
V::from_lua(value, lua)
}
/// Inserts element value at position idx to the table, shifting up the elements from table[idx].
/// Inserts element value at position `idx` to the table, shifting up the elements from `table[idx]`.
/// The worst case complexity is O(n), where n is the table length.
pub fn raw_insert<V: ToLua<'lua>>(&self, idx: Integer, value: V) -> Result<()> {
let lua = self.0.lua;
@@ -274,29 +232,21 @@ impl<'lua> Table<'lua> {
let value = value.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
check_stack(lua.state, 6)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
protect_lua_closure(lua.state, 2, 0, |state| {
for i in (idx..size + 1).rev() {
// table[i+1] = table[i]
ffi::lua_rawgeti(state, -2, i);
ffi::lua_rawseti(state, -3, i + 1);
}
ffi::lua_rawseti(state, -2, idx);
})
ffi::safe::lua_rawinsert(lua.state, -2, idx)
}
}
/// Removes a key from the table.
///
/// If `key` is an integer, mlua shifts down the elements from table[key+1],
/// and erases element table[key]. The complexity is O(n) in worst case,
/// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
/// and erases element `table[key]`. The complexity is O(n) in worst case,
/// where n is the table length.
///
/// For othey key types this is equivalent to setting table[key] = nil.
/// For othey key types this is equivalent to setting `table[key] = nil`.
pub fn raw_remove<K: ToLua<'lua>>(&self, key: K) -> Result<()> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
@@ -308,18 +258,10 @@ impl<'lua> Table<'lua> {
}
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
check_stack(lua.state, 5)?;
lua.push_ref(&self.0);
protect_lua_closure(lua.state, 1, 0, |state| {
for i in idx..size {
ffi::lua_rawgeti(state, -1, i + 1);
ffi::lua_rawseti(state, -2, i);
}
ffi::lua_pushnil(state);
ffi::lua_rawseti(state, -2, size);
})
ffi::safe::lua_rawremove(lua.state, -1, idx)
}
}
_ => self.raw_set(key, Nil),
@@ -335,9 +277,10 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 4);
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
protect_lua_closure(lua.state, 1, 0, |state| ffi::luaL_len(state, -1))
ffi::safe::luaL_len(lua.state, -1)
}
}
@@ -347,9 +290,9 @@ impl<'lua> Table<'lua> {
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(lua.state, -1);
len as Integer
ffi::lua_rawlen(lua.state, -1) as Integer
}
}
@@ -360,13 +303,13 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
assert_stack(lua.state, 2);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
None
} else {
let table = Table(lua.pop_ref());
Some(table)
Some(Table(lua.pop_ref()))
}
}
}
@@ -379,7 +322,8 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
assert_stack(lua.state, 2);
lua.push_ref(&self.0);
if let Some(metatable) = metatable {
lua.push_ref(&metatable.0);
@@ -426,7 +370,7 @@ impl<'lua> Table<'lua> {
pub fn pairs<K: FromLua<'lua>, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> {
TablePairs {
table: self.0,
next_key: Some(Nil),
key: Some(Nil),
_phantom: PhantomData,
}
}
@@ -476,9 +420,57 @@ impl<'lua> Table<'lua> {
TableSequence {
table: self.0,
index: Some(1),
len: None,
raw: false,
_phantom: PhantomData,
}
}
/// Consume this table and return an iterator over all values in the sequence part of the table.
///
/// Unlike the `sequence_values`, does not invoke `__index` metamethod when iterating.
///
/// [`sequence_values`]: #method.sequence_values
pub fn raw_sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
TableSequence {
table: self.0,
index: Some(1),
len: None,
raw: true,
_phantom: PhantomData,
}
}
#[cfg(any(feature = "async", feature = "serialize"))]
pub(crate) fn raw_sequence_values_by_len<V: FromLua<'lua>>(
self,
len: Option<Integer>,
) -> TableSequence<'lua, V> {
let len = len.unwrap_or_else(|| self.raw_len());
TableSequence {
table: self.0,
index: Some(1),
len: Some(len),
raw: true,
_phantom: PhantomData,
}
}
#[cfg(feature = "serialize")]
pub(crate) fn is_array(&self) -> bool {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
return false;
}
crate::serde::push_array_metatable(lua.state);
ffi::lua_rawequal(lua.state, -1, -2) != 0
}
}
}
impl<'lua> PartialEq for Table<'lua> {
@@ -494,6 +486,148 @@ impl<'lua> AsRef<Table<'lua>> for Table<'lua> {
}
}
/// An extension trait for `Table`s that provides a variety of convenient functionality.
pub trait TableExt<'lua> {
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself along with `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call((table.clone(), arg1, ..., argN))`
///
/// This might invoke the `__index` metamethod.
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and executes it,
/// passing `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing the table itself along with `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<'fut, K, A, R>(
&self,
key: K,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
}
impl<'lua> TableExt<'lua> for Table<'lua> {
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let mut args = args.to_lua_multi(lua)?;
args.push_front(Value::Table(self.clone()));
self.get::<_, Function>(key)?.call(args)
}
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.get::<_, Function>(key)?.call(args)
}
#[cfg(feature = "async")]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
let mut args = match args.to_lua_multi(lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
args.push_front(Value::Table(self.clone()));
self.call_async_function(key, args)
}
#[cfg(feature = "async")]
fn call_async_function<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.get::<_, Function>(key) {
Ok(func) => func.call_async(args),
Err(e) => Box::pin(future::err(e)),
}
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Table<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let len = self.raw_len() as usize;
if len > 0 || self.is_array() {
let mut seq = serializer.serialize_seq(Some(len))?;
for v in self.clone().raw_sequence_values_by_len::<Value>(None) {
let v = v.map_err(serde::ser::Error::custom)?;
seq.serialize_element(&v)?;
}
return seq.end();
}
let mut map = serializer.serialize_map(None)?;
for kv in self.clone().pairs::<Value, Value>() {
let (k, v) = kv.map_err(serde::ser::Error::custom)?;
map.serialize_entry(&k, &v)?;
}
map.end()
}
}
/// An iterator over the pairs of a Lua table.
///
/// This struct is created by the [`Table::pairs`] method.
@@ -501,7 +635,7 @@ impl<'lua> AsRef<Table<'lua>> for Table<'lua> {
/// [`Table::pairs`]: struct.Table.html#method.pairs
pub struct TablePairs<'lua, K, V> {
table: LuaRef<'lua>,
next_key: Option<Value<'lua>>,
key: Option<Value<'lua>>,
_phantom: PhantomData<(K, V)>,
}
@@ -513,40 +647,34 @@ where
type Item = Result<(K, V)>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(next_key) = self.next_key.take() {
if let Some(prev_key) = self.key.take() {
let lua = self.table.lua;
let res = (|| {
let res = unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
let res = (|| unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
lua.push_ref(&self.table);
lua.push_value(next_key)?;
lua.push_ref(&self.table);
lua.push_value(prev_key)?;
if protect_lua_closure(lua.state, 2, ffi::LUA_MULTRET, |state| {
ffi::lua_next(state, -2) != 0
})? {
ffi::lua_pushvalue(lua.state, -2);
let key = lua.pop_value();
let value = lua.pop_value();
self.next_key = Some(lua.pop_value());
Some((key, value))
} else {
None
}
};
Ok(if let Some((key, value)) = res {
Some((K::from_lua(key, lua)?, V::from_lua(value, lua)?))
if ffi::safe::lua_next(lua.state, -2)? != 0 {
let value = lua.pop_value();
let key = lua.pop_value();
Ok(Some((
key.clone(),
K::from_lua(key, lua)?,
V::from_lua(value, lua)?,
)))
} else {
None
})
Ok(None)
}
})();
match res {
Ok(Some((key, value))) => Some(Ok((key, value))),
Ok(Some((key, ret_key, value))) => {
self.key = Some(key);
Some(Ok((ret_key, value)))
}
Ok(None) => None,
Err(e) => Some(Err(e)),
}
@@ -564,6 +692,8 @@ where
pub struct TableSequence<'lua, V> {
table: LuaRef<'lua>,
index: Option<Integer>,
len: Option<Integer>,
raw: bool,
_phantom: PhantomData<V>,
}
@@ -577,27 +707,29 @@ where
if let Some(index) = self.index.take() {
let lua = self.table.lua;
let res = unsafe {
let res = (|| unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 5);
check_stack(lua.state, 1 + if self.raw { 0 } else { 4 })?;
lua.push_ref(&self.table);
match protect_lua_closure(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index))
{
Ok(ffi::LUA_TNIL) => None,
Ok(_) => {
let value = lua.pop_value();
self.index = Some(index + 1);
Some(Ok(value))
}
Err(err) => Some(Err(err)),
let res = if self.raw {
ffi::lua_rawgeti(lua.state, -1, index)
} else {
ffi::safe::lua_geti(lua.state, -1, index)?
};
match res {
ffi::LUA_TNIL if index > self.len.unwrap_or(0) => Ok(None),
_ => Ok(Some((index, lua.pop_value()))),
}
};
})();
match res {
Some(Ok(r)) => Some(V::from_lua(r, lua)),
Some(Err(err)) => Some(Err(err)),
None => None,
Ok(Some((index, r))) => {
self.index = Some(index + 1);
Some(V::from_lua(r, lua))
}
Ok(None) => None,
Err(err) => Some(Err(err)),
}
} else {
None
+213 -17
View File
@@ -1,13 +1,30 @@
use std::cmp;
use std::os::raw::c_int;
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{
assert_stack, check_stack, error_traceback, pop_error, protect_lua_closure, StackGuard,
};
use crate::util::{assert_stack, check_stack, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(feature = "async")]
use {
crate::{
lua::{ASYNC_POLL_PENDING, WAKER_REGISTRY_KEY},
util::get_gc_userdata,
value::Value,
},
futures_core::{future::Future, stream::Stream},
std::{
cell::RefCell,
marker::PhantomData,
mem,
os::raw::c_void,
pin::Pin,
task::{Context, Poll, Waker},
},
};
/// Status of a Lua thread (or coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus {
@@ -27,6 +44,21 @@ pub enum ThreadStatus {
#[derive(Clone, Debug)]
pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
/// Requires `feature = "async"`
///
/// [`Future`]: ../futures_core/future/trait.Future.html
/// [`Stream`]: ../futures_core/stream/trait.Stream.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug)]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
args0: RefCell<Option<Result<MultiValue<'lua>>>>,
ret: PhantomData<R>,
}
impl<'lua> Thread<'lua> {
/// Resumes execution of this thread.
///
@@ -76,43 +108,38 @@ impl<'lua> Thread<'lua> {
{
let lua = self.0.lua;
let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int;
let results = unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
check_stack(lua.state, cmp::min(nargs + 1, 3))?;
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1);
ffi::lua_pop(lua.state, 1);
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 {
return Err(Error::CoroutineInactive);
}
ffi::lua_pop(lua.state, 1);
let nargs = args.len() as c_int;
check_stack(lua.state, nargs)?;
check_stack(thread_state, nargs + 1)?;
check_stack(thread_state, nargs)?;
for arg in args {
lua.push_value(arg)?;
}
ffi::lua_xmove(lua.state, thread_state, nargs);
let ret = ffi::lua_resume(thread_state, lua.state, nargs);
let mut nresults = 0;
let ret = ffi::lua_resume(thread_state, lua.state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
protect_lua_closure(lua.state, 0, 0, |_| {
error_traceback(thread_state);
0
})?;
ffi::safe::error_traceback2(lua.state, thread_state)?;
return Err(pop_error(thread_state, ret));
}
let nresults = ffi::lua_gettop(thread_state);
let mut results = MultiValue::new();
check_stack(lua.state, nresults + 2)?; // 2 is extra for `lua.pop_value()` below
ffi::lua_xmove(thread_state, lua.state, nresults);
assert_stack(lua.state, 2);
for _ in 0..nresults {
results.push_front(lua.pop_value());
}
@@ -142,6 +169,62 @@ impl<'lua> Thread<'lua> {
}
}
}
/// Converts Thread to an AsyncThread which implements Future and Stream traits.
///
/// `args` are passed as arguments to the thread function for first call.
/// The object call `resume()` while polling and also allows to run rust futures
/// to completion using an executor.
///
/// Using AsyncThread as a Stream allows to iterate through `coroutine.yield()`
/// values whereas Future version discards that values and poll until the final
/// one (returned from the thread function).
///
/// Requires `feature = "async"`
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result, Thread};
/// use futures::stream::TryStreamExt;
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let thread: Thread = lua.load(r#"
/// coroutine.create(function (sum)
/// for i = 1,10 do
/// sum = sum + i
/// coroutine.yield(sum)
/// end
/// return sum
/// end)
/// "#).eval()?;
///
/// let mut stream = thread.into_async::<_, i64>(1);
/// let mut sum = 0;
/// while let Some(n) = stream.try_next().await? {
/// sum += n;
/// }
///
/// assert_eq!(sum, 286);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R>
where
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let args = args.to_lua_multi(&self.0.lua);
AsyncThread {
thread: self,
args0: RefCell::new(Some(args)),
ret: PhantomData,
}
}
}
impl<'lua> PartialEq for Thread<'lua> {
@@ -149,3 +232,116 @@ impl<'lua> PartialEq for Thread<'lua> {
self.0 == other.0
}
}
#[cfg(feature = "async")]
impl<'lua, R> Stream for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua;
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(None),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
self.thread.resume(())?
};
if is_poll_pending(&ret) {
return Poll::Pending;
}
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_lua_multi(ret, lua)))
}
}
#[cfg(feature = "async")]
impl<'lua, R> Future for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let lua = self.thread.0.lua;
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(Err(Error::CoroutineInactive)),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
self.thread.resume(())?
};
if is_poll_pending(&ret) {
return Poll::Pending;
}
if let ThreadStatus::Resumable = self.thread.status() {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
}
Poll::Ready(R::from_lua_multi(ret, lua))
}
}
#[cfg(feature = "async")]
fn is_poll_pending(val: &MultiValue) -> bool {
match val.iter().enumerate().last() {
Some((1, Value::LightUserData(ud))) => {
ud.0 == &ASYNC_POLL_PENDING as *const u8 as *mut c_void
}
_ => false,
}
}
#[cfg(feature = "async")]
struct WakerGuard(*mut ffi::lua_State, Option<Waker>);
#[cfg(feature = "async")]
impl WakerGuard {
pub fn new(state: *mut ffi::lua_State, waker: Waker) -> Result<WakerGuard> {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
let waker_slot = get_gc_userdata::<Option<Waker>>(state, -1).as_mut();
let old = mlua_expect!(waker_slot, "Waker is destroyed").replace(waker);
Ok(WakerGuard(state, old))
}
}
}
#[cfg(feature = "async")]
impl Drop for WakerGuard {
fn drop(&mut self) {
let state = self.0;
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 3);
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
let waker_slot = get_gc_userdata::<Option<Waker>>(state, -1).as_mut();
mem::swap(mlua_expect!(waker_slot, "Waker is destroyed"), &mut self.1);
}
}
}
+27 -10
View File
@@ -1,9 +1,14 @@
use std::cell::RefCell;
use std::os::raw::{c_int, c_void};
use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr};
#[cfg(feature = "async")]
use futures_core::future::LocalBoxFuture;
use crate::error::Result;
use crate::ffi;
use crate::hook::Debug;
use crate::lua::Lua;
use crate::util::{assert_stack, StackGuard};
use crate::value::MultiValue;
@@ -20,24 +25,35 @@ pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
#[cfg(feature = "async")]
pub(crate) type AsyncCallback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<MultiValue<'lua>>> + 'a>;
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>;
#[cfg(feature = "send")]
pub trait MaybeSend: Send {}
#[cfg(feature = "send")]
impl<T: Send> MaybeSend for T {}
#[cfg(not(feature = "send"))]
pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
/// An auto generated key into the Lua registry.
///
/// This is a handle to a value stored inside the Lua registry. It is not directly usable like the
/// `Table` or `Function` handle types, but since it doesn't hold a reference to a parent Lua and is
/// Send + Sync + 'static, it is much more flexible and can be used in many situations where it is
/// impossible to directly store a normal handle type. It is not automatically garbage collected on
/// Drop, but it can be removed with [`Lua::remove_registry_value`], and instances not manually
/// removed can be garbage collected with [`Lua::expire_registry_values`].
/// This is a handle to a value stored inside the Lua registry. It is not automatically
/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`],
/// and instances not manually removed can be garbage collected with [`Lua::expire_registry_values`].
///
/// Be warned, If you place this into Lua via a `UserData` type or a rust callback, it is *very
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
/// Instead of placing a `RegistryKey` into a `UserData` type, prefer instead to use
/// [`UserData::set_user_value`] / [`UserData::get_user_value`], and instead of moving a RegistryKey
/// into a callback, prefer [`Lua::scope`].
/// [`UserData::set_user_value`] / [`UserData::get_user_value`].
///
/// [`Lua::remove_registry_value`]: struct.Lua.html#method.remove_registry_value
/// [`Lua::expire_registry_values`]: struct.Lua.html#method.expire_registry_values
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserData::set_user_value`]: struct.UserData.html#method.set_user_value
/// [`UserData::get_user_value`]: struct.UserData.html#method.get_user_value
pub struct RegistryKey {
@@ -53,7 +69,8 @@ impl fmt::Debug for RegistryKey {
impl Drop for RegistryKey {
fn drop(&mut self) {
if let Some(list) = mlua_expect!(self.unref_list.lock(), "unref_list poisoned").as_mut() {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
}
}
+604 -112
View File
@@ -1,13 +1,27 @@
use std::cell::{Ref, RefCell, RefMut};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::string::String as StdString;
use std::sync::Arc;
#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "serialize")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::LuaRef;
use crate::util::{assert_stack, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
use crate::table::{Table, TablePairs};
use crate::types::{LuaRef, MaybeSend};
use crate::util::{check_stack, get_destructed_userdata_metatable, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti, Value};
/// Kinds of metamethods that can be overridden.
///
@@ -15,7 +29,7 @@ use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
/// generally no need to do so: [`UserData`] implementors can instead just implement `Drop`.
///
/// [`UserData`]: trait.UserData.html
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Clone)]
pub enum MetaMethod {
/// The `+` operator.
Add,
@@ -31,26 +45,31 @@ pub enum MetaMethod {
Pow,
/// The unary minus (`-`) operator.
Unm,
#[cfg(feature = "lua53")]
/// The floor division (//) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
IDiv,
#[cfg(feature = "lua53")]
/// The bitwise AND (&) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
BAnd,
#[cfg(feature = "lua53")]
/// The bitwise OR (|) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
BOr,
#[cfg(feature = "lua53")]
/// The bitwise XOR (binary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
BXor,
#[cfg(feature = "lua53")]
/// The bitwise NOT (unary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
BNot,
#[cfg(feature = "lua53")]
/// The bitwise left shift (<<) operator.
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
Shl,
#[cfg(feature = "lua53")]
/// The bitwise right shift (>>) operator.
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
Shr,
/// The string concatenation operator `..`.
Concat,
@@ -72,57 +91,170 @@ pub enum MetaMethod {
///
/// This is not an operator, but will be called by methods such as `tostring` and `print`.
ToString,
#[cfg(any(feature = "lua53", feature = "lua52"))]
/// 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", doc))]
Pairs,
/// The `__close` metamethod.
///
/// Executed when a variable, that marked as to-be-closed, goes out of scope.
///
/// More information about to-be-closed variabled can be found in the Lua 5.4
/// [documentation][lua_doc].
///
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(any(feature = "lua54", doc))]
Close,
/// A custom metamethod.
///
/// Must not be in the protected list: `__gc`, `__metatable`, `__mlua*`.
Custom(StdString),
}
impl PartialEq for MetaMethod {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
impl Eq for MetaMethod {}
impl Hash for MetaMethod {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name().hash(state);
}
}
impl fmt::Display for MetaMethod {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}", self.name())
}
}
impl MetaMethod {
pub(crate) fn name(self) -> &'static [u8] {
/// Returns Lua metamethod name, usually prefixed by two underscores.
pub fn name(&self) -> &str {
match self {
MetaMethod::Add => b"__add",
MetaMethod::Sub => b"__sub",
MetaMethod::Mul => b"__mul",
MetaMethod::Div => b"__div",
MetaMethod::Mod => b"__mod",
MetaMethod::Pow => b"__pow",
MetaMethod::Unm => b"__unm",
#[cfg(feature = "lua53")]
MetaMethod::IDiv => b"__idiv",
#[cfg(feature = "lua53")]
MetaMethod::BAnd => b"__band",
#[cfg(feature = "lua53")]
MetaMethod::BOr => b"__bor",
#[cfg(feature = "lua53")]
MetaMethod::BXor => b"__bxor",
#[cfg(feature = "lua53")]
MetaMethod::BNot => b"__bnot",
#[cfg(feature = "lua53")]
MetaMethod::Shl => b"__shl",
#[cfg(feature = "lua53")]
MetaMethod::Shr => b"__shr",
MetaMethod::Concat => b"__concat",
MetaMethod::Len => b"__len",
MetaMethod::Eq => b"__eq",
MetaMethod::Lt => b"__lt",
MetaMethod::Le => b"__le",
MetaMethod::Index => b"__index",
MetaMethod::NewIndex => b"__newindex",
MetaMethod::Call => b"__call",
MetaMethod::ToString => b"__tostring",
#[cfg(any(feature = "lua53", feature = "lua52"))]
MetaMethod::Pairs => b"__pairs",
MetaMethod::Add => "__add",
MetaMethod::Sub => "__sub",
MetaMethod::Mul => "__mul",
MetaMethod::Div => "__div",
MetaMethod::Mod => "__mod",
MetaMethod::Pow => "__pow",
MetaMethod::Unm => "__unm",
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::IDiv => "__idiv",
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BAnd => "__band",
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BOr => "__bor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BXor => "__bxor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::BNot => "__bnot",
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::Shl => "__shl",
#[cfg(any(feature = "lua54", feature = "lua53"))]
MetaMethod::Shr => "__shr",
MetaMethod::Concat => "__concat",
MetaMethod::Len => "__len",
MetaMethod::Eq => "__eq",
MetaMethod::Lt => "__lt",
MetaMethod::Le => "__le",
MetaMethod::Index => "__index",
MetaMethod::NewIndex => "__newindex",
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
MetaMethod::Pairs => "__pairs",
#[cfg(feature = "lua54")]
MetaMethod::Close => "__close",
MetaMethod::Custom(ref name) => name,
}
}
pub(crate) fn validate(self) -> Result<Self> {
match self {
MetaMethod::Custom(name) if name == "__gc" => Err(Error::MetaMethodRestricted(name)),
MetaMethod::Custom(name) if name == "__metatable" => {
Err(Error::MetaMethodRestricted(name))
}
MetaMethod::Custom(name) if name.starts_with("__mlua") => {
Err(Error::MetaMethodRestricted(name))
}
_ => Ok(self),
}
}
}
impl From<StdString> for MetaMethod {
fn from(name: StdString) -> Self {
match name.as_str() {
"__add" => MetaMethod::Add,
"__sub" => MetaMethod::Sub,
"__mul" => MetaMethod::Mul,
"__div" => MetaMethod::Div,
"__mod" => MetaMethod::Mod,
"__pow" => MetaMethod::Pow,
"__unm" => MetaMethod::Unm,
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__idiv" => MetaMethod::IDiv,
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__band" => MetaMethod::BAnd,
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__bor" => MetaMethod::BOr,
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__bxor" => MetaMethod::BXor,
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__bnot" => MetaMethod::BNot,
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__shl" => MetaMethod::Shl,
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__shr" => MetaMethod::Shr,
"__concat" => MetaMethod::Concat,
"__len" => MetaMethod::Len,
"__eq" => MetaMethod::Eq,
"__lt" => MetaMethod::Lt,
"__le" => MetaMethod::Le,
"__index" => MetaMethod::Index,
"__newindex" => MetaMethod::NewIndex,
"__call" => MetaMethod::Call,
"__tostring" => MetaMethod::ToString,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
"__pairs" => MetaMethod::Pairs,
#[cfg(feature = "lua54")]
"__close" => MetaMethod::Close,
_ => MetaMethod::Custom(name),
}
}
}
impl From<&str> for MetaMethod {
fn from(name: &str) -> Self {
MetaMethod::from(name.to_owned())
}
}
/// Method registry for [`UserData`] implementors.
///
/// [`UserData`]: trait.UserData.html
pub trait UserDataMethods<'lua, T: UserData> {
/// Add a method which accepts a `&T` as the first parameter.
/// Add a regular method which accepts a `&T` as the first parameter.
///
/// Regular methods are implemented by overriding the `__index` metamethod and returning the
/// accessed method. This allows them to be used with the expected `userdata:method()` syntax.
@@ -131,10 +263,10 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// be used as a fall-back if no regular method is found.
fn add_method<S, A, R, M>(&mut self, name: &S, method: M)
where
S: ?Sized + AsRef<[u8]>,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>;
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>;
/// Add a regular method which accepts a `&mut T` as the first parameter.
///
@@ -143,26 +275,46 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// [`add_method`]: #method.add_method
fn add_method_mut<S, A, R, M>(&mut self, name: &S, method: M)
where
S: ?Sized + AsRef<[u8]>,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
/// Add an async method which accepts a `T` as the first parameter and returns Future.
/// The passed `T` is cloned from the original value.
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: #method.add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<S, A, R, M, MR>(&mut self, name: &S, method: M)
where
T: Clone,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR,
MR: 'lua + Future<Output = Result<R>>;
/// Add a regular method as a function which accepts generic arguments, the first argument will
/// be a `UserData` of type T if the method is called with Lua method syntax:
/// 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)`.
///
/// Prefer to use [`add_method`] or [`add_method_mut`] as they are easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`add_method`]: #method.add_method
/// [`add_method_mut`]: #method.add_method_mut
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
where
S: ?Sized + AsRef<[u8]>,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>;
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>;
/// Add a regular method as a mutable function which accepts generic arguments.
///
@@ -171,10 +323,29 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// [`add_function`]: #method.add_function
fn add_function_mut<S, A, R, F>(&mut self, name: &S, function: F)
where
S: ?Sized + AsRef<[u8]>,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>;
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>;
/// Add a regular method as an async function which accepts generic arguments
/// and returns Future.
///
/// This is an async version of [`add_function`].
///
/// Requires `feature = "async"`
///
/// [`add_function`]: #method.add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR,
FR: 'lua + Future<Output = Result<R>>;
/// Add a metamethod which accepts a `&T` as the first parameter.
///
@@ -184,11 +355,12 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: #method.add_meta_function
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
fn add_meta_method<S, A, R, M>(&mut self, meta: S, method: M)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>;
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>;
/// Add a metamethod as a function which accepts a `&mut T` as the first parameter.
///
@@ -198,33 +370,107 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: #method.add_meta_function
fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, method: M)
fn add_meta_method_mut<S, A, R, M>(&mut self, meta: S, method: M)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
/// Add a metamethod which accepts generic arguments.
///
/// 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<A, R, F>(&mut self, meta: MetaMethod, function: F)
fn add_meta_function<S, A, R, F>(&mut self, meta: S, function: F)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>;
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>;
/// Add a metamethod as a mutable function which accepts generic arguments.
///
/// This is a version of [`add_meta_function`] that accepts a FnMut argument.
///
/// [`add_meta_function`]: #method.add_meta_function
fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F)
fn add_meta_function_mut<S, A, R, F>(&mut self, meta: S, function: F)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>;
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>;
}
/// Field registry for [`UserData`] implementors.
///
/// [`UserData`]: trait.UserData.html
pub trait UserDataFields<'lua, T: UserData> {
/// Add a regular field getter as a method which accepts a `&T` as the parameter.
///
/// Regular field getters are implemented by overriding the `__index` metamethod and returning the
/// accessed field. This allows them to be used with the expected `userdata.field` syntax.
///
/// 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<S, R, M>(&mut self, name: &S, method: M)
where
S: AsRef<[u8]> + ?Sized,
R: ToLua<'lua>,
M: 'static + MaybeSend + Fn(&'lua Lua, &T) -> Result<R>;
/// Add a regular field setter as a method which accepts a `&mut T` as the first parameter.
///
/// Regular field setters are implemented by overriding the `__newindex` metamethod and setting the
/// accessed field. This allows them to be used with the expected `userdata.field = value` syntax.
///
/// 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<S, A, M>(&mut self, name: &S, method: M)
where
S: AsRef<[u8]> + ?Sized,
A: FromLua<'lua>,
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<()>;
/// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T`
/// argument.
///
/// Prefer to use [`add_field_method_get`] as it is easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`add_field_method_get`]: #method.add_field_method_get
fn add_field_function_get<S, R, F>(&mut self, name: &S, function: F)
where
S: AsRef<[u8]> + ?Sized,
R: ToLua<'lua>,
F: 'static + MaybeSend + Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R>;
/// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T`
/// first argument.
///
/// Prefer to use [`add_field_method_set`] as it is easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`add_field_method_set`]: #method.add_field_method_set
fn add_field_function_set<S, A, F>(&mut self, name: &S, function: F)
where
S: AsRef<[u8]> + ?Sized,
A: FromLua<'lua>,
F: 'static + MaybeSend + FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()>;
/// Add a metamethod value computed from `f`.
///
/// This will initialize the metamethod value from `f` on `UserData` creation.
///
/// # Note
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field_with<S, R, F>(&mut self, meta: S, f: F)
where
S: Into<MetaMethod>,
F: 'static + MaybeSend + Fn(&'lua Lua) -> Result<R>,
R: ToLua<'lua>;
}
/// Trait for custom userdata types.
@@ -250,21 +496,21 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// # }
/// ```
///
/// Custom methods and operators can be provided by implementing `add_methods` (refer to
/// [`UserDataMethods`] for more information):
/// Custom fields, methods and operators can be provided by implementing `add_fields` or `add_methods`
/// (refer to [`UserDataFields`] and [`UserDataMethods`] for more information):
///
/// ```
/// # use mlua::{Lua, MetaMethod, Result, UserData, UserDataMethods};
/// # use mlua::{Lua, MetaMethod, Result, UserData, UserDataFields, UserDataMethods};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// struct MyUserData(i32);
///
/// impl UserData for MyUserData {
/// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
/// methods.add_method("get", |_, this, _: ()| {
/// Ok(this.0)
/// });
/// fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
/// fields.add_field_method_get("val", |_, this| Ok(this.0));
/// }
///
/// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
/// methods.add_method_mut("add", |_, this, value: i32| {
/// this.0 += value;
/// Ok(())
@@ -279,9 +525,9 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// lua.globals().set("myobject", MyUserData(123))?;
///
/// lua.load(r#"
/// assert(myobject:get() == 123)
/// assert(myobject.val == 123)
/// myobject:add(7)
/// assert(myobject:get() == 130)
/// assert(myobject.val == 130)
/// assert(myobject + 10 == 140)
/// "#).exec()?;
/// # Ok(())
@@ -290,10 +536,113 @@ pub trait UserDataMethods<'lua, T: UserData> {
///
/// [`ToLua`]: trait.ToLua.html
/// [`FromLua`]: trait.FromLua.html
/// [`UserDataFields`]: trait.UserDataFields.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
pub trait UserData: Sized {
/// Adds custom fields specific to this userdata.
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(_fields: &mut F) {}
/// Adds custom methods and operators specific to this userdata.
fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(_methods: &mut T) {}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(_methods: &mut M) {}
}
// Wraps UserData in a way to always implement `serde::Serialize` trait.
pub(crate) enum UserDataCell<T> {
Arc(Arc<RefCell<UserDataWrapped<T>>>),
Plain(RefCell<UserDataWrapped<T>>),
}
impl<T> UserDataCell<T> {
pub(crate) fn new(data: T) -> Self {
UserDataCell::Plain(RefCell::new(UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
}))
}
pub(crate) fn new_arc(data: T) -> Self {
UserDataCell::Arc(Arc::new(RefCell::new(UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
})))
}
#[cfg(feature = "serialize")]
pub(crate) fn new_ser(data: T) -> Self
where
T: 'static + Serialize,
{
let data_raw = Box::into_raw(Box::new(data));
UserDataCell::Plain(RefCell::new(UserDataWrapped {
data: data_raw,
ser: data_raw,
}))
}
}
impl<T> Deref for UserDataCell<T> {
type Target = RefCell<UserDataWrapped<T>>;
fn deref(&self) -> &Self::Target {
match self {
UserDataCell::Arc(t) => &*t,
UserDataCell::Plain(t) => &*t,
}
}
}
impl<T> Clone for UserDataCell<T> {
fn clone(&self) -> Self {
match self {
UserDataCell::Arc(t) => UserDataCell::Arc(t.clone()),
UserDataCell::Plain(_) => mlua_panic!("cannot clone non-arc userdata"),
}
}
}
pub(crate) struct UserDataWrapped<T> {
pub(crate) data: *mut T,
#[cfg(feature = "serialize")]
ser: *mut dyn erased_serde::Serialize,
}
impl<T> Drop for UserDataWrapped<T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.data));
#[cfg(feature = "serialize")]
if self.data as *mut () != self.ser as *mut () {
drop(Box::from_raw(self.ser));
}
}
}
}
impl<T> AsRef<T> for UserDataWrapped<T> {
fn as_ref(&self) -> &T {
unsafe { &*self.data }
}
}
impl<T> AsMut<T> for UserDataWrapped<T> {
fn as_mut(&mut self) -> &mut T {
unsafe { &mut *self.data }
}
}
#[cfg(feature = "serialize")]
pub(crate) struct UserDataSerializeError;
#[cfg(feature = "serialize")]
impl Serialize for UserDataSerializeError {
fn serialize<S>(&self, _serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
Err(ser::Error::custom("cannot serialize <userdata>"))
}
}
/// Handle to an internal Lua userdata for any type that implements [`UserData`].
@@ -318,7 +667,7 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
impl<'lua> AnyUserData<'lua> {
/// Checks whether the type of this userdata is `T`.
pub fn is<T: 'static + UserData>(&self) -> bool {
match self.inspect(|_: &RefCell<T>| Ok(())) {
match self.inspect(|_: &UserDataCell<T>| Ok(())) {
Ok(()) => true,
Err(Error::UserDataTypeMismatch) => false,
Err(_) => unreachable!(),
@@ -332,7 +681,10 @@ impl<'lua> AnyUserData<'lua> {
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow<T: 'static + UserData>(&self) -> Result<Ref<T>> {
self.inspect(|cell| Ok(cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?))
self.inspect(|cell| {
let cell_ref = cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(Ref::map(cell_ref, |x| unsafe { &*x.data }))
})
}
/// Borrow this userdata mutably if it is of type `T`.
@@ -343,80 +695,124 @@ impl<'lua> AnyUserData<'lua> {
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<RefMut<T>> {
self.inspect(|cell| {
Ok(cell
let cell_ref = cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?)
.map_err(|_| Error::UserDataBorrowMutError)?;
Ok(RefMut::map(cell_ref, |x| unsafe { &mut *x.data }))
})
}
/// Sets an associated value to this `AnyUserData`.
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`get_user_value`].
/// As Lua < 5.3 allows to store only tables, the value will be stored in a table at index 1.
///
/// [`get_user_value`]: #method.get_user_value
pub fn set_user_value<V: ToLua<'lua>>(&self, v: V) -> Result<()> {
let lua = self.0.lua;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
let v = {
// Lua 5.2/5.1 allows to store only table. Then we will wrap the value.
let t = lua.create_table()?;
// Lua <= 5.2 allows to store only a table. Then we will wrap the value.
let t = lua.create_table_with_capacity(1, 0)?;
t.raw_set(1, v)?;
crate::Value::Table(t)
Value::Table(t)
};
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
let v = v.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 2);
lua.push_ref(&self.0);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0)?;
lua.push_value(v)?;
ffi::lua_setuservalue(lua.state, -2);
Ok(())
}
}
/// Returns an associated value set by [`set_user_value`].
///
/// For Lua < 5.3 the value will be automatically extracted from the table wrapper from index 1.
///
/// [`set_user_value`]: #method.set_user_value
pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> {
let lua = self.0.lua;
let res = unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&self.0);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0)?;
ffi::lua_getuservalue(lua.state, -1);
lua.pop_value()
};
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
return crate::Table::from_lua(res, lua)?.get(1);
#[cfg(feature = "lua53")]
return match <Option<Table>>::from_lua(res, lua)? {
Some(t) => t.get(1),
None => V::from_lua(Value::Nil, lua),
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
V::from_lua(res, lua)
}
fn get_metatable(&self) -> Result<Table<'lua>> {
/// Returns a metatable of this `UserData`.
///
/// Returned [`UserDataMetatable`] object wraps the original metatable and
/// provides safe access to it methods.
///
/// For `T: UserData + 'static` returned metatable is shared among all instances of type `T`.
///
/// [`UserDataMetatable`]: struct.UserDataMetatable.html
pub fn get_metatable(&self) -> Result<UserDataMetatable<'lua>> {
self.get_raw_metatable().map(UserDataMetatable)
}
/// Checks for a metamethod in this `AnyUserData`.
///
/// This function is deprecated and will be removed in v0.7.
/// Please use [`get_metatable`] function instead.
///
/// [`get_metatable`]: #method.get_metatable
#[deprecated(
since = "0.6.0",
note = "Please use the get_metatable function instead"
)]
pub fn has_metamethod(&self, method: MetaMethod) -> Result<bool> {
match self.get_raw_metatable() {
Ok(mt) => {
let name = self.0.lua.create_string(method.validate()?.name())?;
if let Value::Nil = mt.raw_get(name)? {
Ok(false)
} else {
Ok(true)
}
}
Err(Error::UserDataTypeMismatch) => Ok(false),
Err(e) => Err(e),
}
}
fn get_raw_metatable(&self) -> Result<Table<'lua>> {
unsafe {
let lua = self.0.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
return Err(Error::UserDataTypeMismatch);
}
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0)?;
ffi::lua_getmetatable(lua.state, -1); // Checked that non-empty on the previous call
Ok(Table(lua.pop_ref()))
}
}
pub(crate) fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
let other = other.as_ref();
// Uses lua_rawequal() under the hood
if self == other {
return Ok(true);
}
let mt = self.get_metatable()?;
if mt != other.get_metatable()? {
let mt = self.get_raw_metatable()?;
if mt != other.get_raw_metatable()? {
return Ok(false);
}
@@ -432,29 +828,30 @@ impl<'lua> AnyUserData<'lua> {
fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R>
where
T: 'static + UserData,
F: FnOnce(&'a RefCell<T>) -> Result<R>,
F: FnOnce(&'a UserDataCell<T>) -> Result<R>,
{
let lua = self.0.lua;
unsafe {
let lua = self.0.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
check_stack(lua.state, 3)?;
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
Err(Error::UserDataTypeMismatch)
} else {
ffi::lua_rawgeti(
lua.state,
ffi::LUA_REGISTRYINDEX,
lua.userdata_metatable::<T>()? as ffi::lua_Integer,
);
return Err(Error::UserDataTypeMismatch);
}
lua.push_userdata_metatable::<T>()?;
if ffi::lua_rawequal(lua.state, -1, -2) == 0 {
Err(Error::UserDataTypeMismatch)
if ffi::lua_rawequal(lua.state, -1, -2) == 0 {
// Maybe UserData destructed?
ffi::lua_pop(lua.state, 1);
get_destructed_userdata_metatable(lua.state);
if ffi::lua_rawequal(lua.state, -1, -2) == 1 {
Err(Error::UserDataDestructed)
} else {
func(&*get_userdata::<RefCell<T>>(lua.state, -3))
Err(Error::UserDataTypeMismatch)
}
} else {
func(&*get_userdata::<UserDataCell<T>>(lua.state, -3))
}
}
}
@@ -472,3 +869,98 @@ impl<'lua> AsRef<AnyUserData<'lua>> for AnyUserData<'lua> {
self
}
}
/// Handle to a `UserData` metatable.
#[derive(Clone, Debug)]
pub struct UserDataMetatable<'lua>(pub(crate) Table<'lua>);
impl<'lua> UserDataMetatable<'lua> {
/// Gets the value associated to `key` from the metatable.
///
/// If no value is associated to `key`, returns the `Nil` value.
/// Access to restricted metamethods such as `__gc` or `__metatable` will cause an error.
pub fn get<K: Into<MetaMethod>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
self.0.raw_get(key.into().validate()?.name())
}
/// Sets a key-value pair in the metatable.
///
/// If the value is `Nil`, this will effectively remove the `key`.
/// Access to restricted metamethods such as `__gc` or `__metatable` will cause an error.
/// Setting `__index` or `__newindex` metamethods is also restricted because their values are cached
/// for `mlua` internal usage.
pub fn set<K: Into<MetaMethod>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
let key = key.into().validate()?;
// `__index` and `__newindex` cannot be changed in runtime, because values are cached
if key == MetaMethod::Index || key == MetaMethod::NewIndex {
return Err(Error::MetaMethodRestricted(key.to_string()));
}
self.0.raw_set(key.name(), value)
}
/// Checks whether the metatable contains a non-nil value for `key`.
pub fn contains<K: Into<MetaMethod>>(&self, key: K) -> Result<bool> {
self.0.contains_key(key.into().validate()?.name())
}
/// Consumes this metatable and returns an iterator over the pairs of the metatable.
///
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
///
/// [`Result`]: type.Result.html
pub fn pairs<V: FromLua<'lua>>(self) -> UserDataMetatablePairs<'lua, V> {
UserDataMetatablePairs(self.0.pairs())
}
}
/// An iterator over the pairs of a [`UserData`] metatable.
///
/// It skips restricted metamethods, such as `__gc` or `__metatable`.
///
/// This struct is created by the [`UserDataMetatable::pairs`] method.
///
/// [`UserData`]: trait.UserData.html
/// [`UserDataMetatable::pairs`]: struct.UserDataMetatable.html#method.pairs
pub struct UserDataMetatablePairs<'lua, V>(TablePairs<'lua, StdString, V>);
impl<'lua, V> Iterator for UserDataMetatablePairs<'lua, V>
where
V: FromLua<'lua>,
{
type Item = Result<(MetaMethod, V)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.0.next()? {
Ok((key, value)) => {
// Skip restricted metamethods
if let Ok(metamethod) = MetaMethod::from(key).validate() {
break Some(Ok((metamethod, value)));
}
}
Err(e) => break Some(Err(e)),
}
}
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for AnyUserData<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let res = (|| unsafe {
let lua = self.0.lua;
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
(*ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?.ser)
.serialize(serializer)
.map_err(|err| Error::SerializeError(err.to_string()))
})();
res.map_err(ser::Error::custom)
}
}
+347 -467
View File
File diff suppressed because it is too large Load Diff
+42 -12
View File
@@ -1,8 +1,13 @@
use std::iter::{self, FromIterator};
use std::{slice, str, vec};
#[cfg(feature = "serialize")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
@@ -11,10 +16,9 @@ use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number};
use crate::userdata::AnyUserData;
/// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData`
/// variants contain handle types into the internal Lua state. It is a logic error to mix handle
/// types between separate `Lua` instances, or between a parent `Lua` instance and one received as a
/// parameter in a Rust callback, and doing so will result in a panic.
/// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData`
/// variants 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(Debug, Clone)]
pub enum Value<'lua> {
/// The Lua value `nil`.
@@ -42,24 +46,25 @@ pub enum Value<'lua> {
/// Reference to a userdata object that holds a custom type which implements `UserData`.
/// Special builtin userdata types will be represented as other `Value` variants.
UserData(AnyUserData<'lua>),
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Error),
}
pub use self::Value::Nil;
impl<'lua> Value<'lua> {
pub(crate) fn type_name(&self) -> &'static str {
pub fn type_name(&self) -> &'static str {
match *self {
Value::Nil => "nil",
Value::Boolean(_) => "boolean",
Value::LightUserData(_) => "light userdata",
Value::LightUserData(_) => "lightuserdata",
Value::Integer(_) => "integer",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Table(_) => "table",
Value::Function(_) => "function",
Value::Thread(_) => "thread",
Value::UserData(_) | Value::Error(_) => "userdata",
Value::UserData(_) => "userdata",
Value::Error(_) => "error",
}
}
@@ -89,8 +94,8 @@ impl<'lua> PartialEq for Value<'lua> {
(Value::Boolean(a), Value::Boolean(b)) => a == b,
(Value::LightUserData(a), Value::LightUserData(b)) => a == b,
(Value::Integer(a), Value::Integer(b)) => *a == *b,
(Value::Integer(a), Value::Number(b)) => *a as ffi::lua_Number == *b,
(Value::Number(a), Value::Integer(b)) => *a == *b as ffi::lua_Number,
(Value::Integer(a), Value::Number(b)) => *a as Number == *b,
(Value::Number(a), Value::Integer(b)) => *a == *b as Number,
(Value::Number(a), Value::Number(b)) => *a == *b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Table(a), Value::Table(b)) => a == b,
@@ -109,6 +114,31 @@ impl<'lua> AsRef<Value<'lua>> for Value<'lua> {
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Value<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Nil => serializer.serialize_unit(),
Value::Boolean(b) => serializer.serialize_bool(*b),
#[allow(clippy::useless_conversion)]
Value::Integer(i) => serializer.serialize_i64((*i).into()),
#[allow(clippy::useless_conversion)]
Value::Number(n) => serializer.serialize_f64((*n).into()),
Value::String(s) => s.serialize(serializer),
Value::Table(t) => t.serialize(serializer),
Value::UserData(ud) => ud.serialize(serializer),
Value::LightUserData(ud) if ud.0.is_null() => serializer.serialize_none(),
Value::Error(_) | Value::LightUserData(_) | Value::Function(_) | Value::Thread(_) => {
let msg = format!("cannot serialize <{}>", self.type_name());
Err(ser::Error::custom(msg))
}
}
}
}
/// Trait for types convertible to `Value`.
pub trait ToLua<'lua> {
/// Performs the conversion.
@@ -158,7 +188,7 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
type IntoIter = iter::Rev<slice::Iter<'a, Value<'lua>>>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).into_iter().rev()
(&self.0).iter().rev()
}
}
+437
View File
@@ -0,0 +1,437 @@
#![cfg(feature = "async")]
use std::cell::Cell;
use std::rc::Rc;
use std::sync::{
atomic::{AtomicI64, Ordering},
Arc,
};
use std::time::Duration;
use futures_timer::Delay;
use futures_util::stream::TryStreamExt;
use mlua::{
Error, Function, Lua, Result, Table, TableExt, Thread, UserData, UserDataMethods, Value,
};
#[tokio::test]
async fn test_async_function() -> Result<()> {
let lua = Lua::new();
let f = lua
.create_async_function(|_lua, (a, b, c): (i64, i64, i64)| async move { Ok((a + b) * c) })?;
lua.globals().set("f", f)?;
let res: i64 = lua.load("f(1, 2, 3)").eval_async().await?;
assert_eq!(res, 9);
Ok(())
}
#[tokio::test]
async fn test_async_sleep() -> Result<()> {
let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
lua.globals().set("sleep", sleep)?;
let res: String = lua.load(r"return sleep(...)").call_async(100).await?;
assert_eq!(res, "elapsed:100ms");
Ok(())
}
#[tokio::test]
async fn test_async_call() -> Result<()> {
let lua = Lua::new();
let hello = lua.create_async_function(|_lua, name: String| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(format!("hello, {}!", name))
})?;
match hello.call::<_, ()>("alex") {
Err(Error::RuntimeError(_)) => {}
_ => panic!(
"non-async executing async function must fail on the yield stage with RuntimeError"
),
};
assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!");
// Executing non-async functions using async call is allowed
let sum = lua.create_function(|_lua, (a, b): (i64, i64)| return Ok(a + b))?;
assert_eq!(sum.call_async::<_, i64>((5, 1)).await?, 6);
Ok(())
}
#[tokio::test]
async fn test_async_bind_call() -> Result<()> {
let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move { Ok(a + b) })?;
let plus_10 = sum.bind(10)?;
lua.globals().set("plus_10", plus_10)?;
assert_eq!(lua.load("plus_10(-1)").eval_async::<i64>().await?, 9);
assert_eq!(lua.load("plus_10(1)").eval_async::<i64>().await?, 11);
Ok(())
}
#[tokio::test]
async fn test_async_handle_yield() -> Result<()> {
let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(a + b)
})?;
lua.globals().set("sleep_sum", sum)?;
let res: String = lua
.load(
r#"
sum = sleep_sum(6, 7)
assert(sum == 13)
coroutine.yield("in progress")
return "done"
"#,
)
.call_async(())
.await?;
assert_eq!(res, "done");
let min = lua
.load(
r#"
function (a, b)
coroutine.yield("ignore me")
if a < b then return a else return b end
end
"#,
)
.eval::<Function>()?;
assert_eq!(min.call_async::<_, i64>((-1, 1)).await?, -1);
Ok(())
}
#[tokio::test]
async fn test_async_multi_return_nil() -> Result<()> {
let lua = Lua::new();
lua.globals().set(
"func",
lua.create_async_function(|_, _: ()| async { Ok((Option::<String>::None, "error")) })?,
)?;
lua.load(
r#"
local ok, err = func()
assert(err == "error")
"#,
)
.exec_async()
.await
}
#[tokio::test]
async fn test_async_return_async_closure() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(|lua, a: i64| async move {
Delay::new(Duration::from_millis(10)).await;
let g = lua.create_async_function(move |_, b: i64| async move {
Delay::new(Duration::from_millis(10)).await;
return Ok(a + b);
})?;
Ok(g)
})?;
lua.globals().set("f", f)?;
let res: i64 = lua
.load("local g = f(1); return g(2) + g(3)")
.call_async(())
.await?;
assert_eq!(res, 7);
Ok(())
}
#[tokio::test]
async fn test_async_thread_stream() -> Result<()> {
let lua = Lua::new();
let thread = lua.create_thread(
lua.load(
r#"
function (sum)
for i = 1,10 do
sum = sum + i
coroutine.yield(sum)
end
return sum
end
"#,
)
.eval()?,
)?;
let mut stream = thread.into_async::<_, i64>(1);
let mut sum = 0;
while let Some(n) = stream.try_next().await? {
sum += n;
}
assert_eq!(sum, 286);
Ok(())
}
#[tokio::test]
async fn test_async_thread() -> Result<()> {
let lua = Lua::new();
let cnt = Arc::new(10); // sleep 10ms
let cnt2 = cnt.clone();
let f = lua.create_async_function(move |_lua, ()| {
let cnt3 = cnt2.clone();
async move {
Delay::new(Duration::from_millis(*cnt3.as_ref())).await;
Ok("done")
}
})?;
let res: String = lua.create_thread(f)?.into_async(()).await?;
assert_eq!(res, "done");
assert_eq!(Arc::strong_count(&cnt), 2);
lua.gc_collect()?; // thread_s is non-resumable and subject to garbage collection
assert_eq!(Arc::strong_count(&cnt), 1);
Ok(())
}
#[tokio::test]
async fn test_async_table() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
table.set("val", 10)?;
let get_value = lua.create_async_function(|_, table: Table| async move {
Delay::new(Duration::from_millis(10)).await;
table.get::<_, i64>("val")
})?;
table.set("get_value", get_value)?;
let set_value = lua.create_async_function(|_, (table, n): (Table, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
table.set("val", n)
})?;
table.set("set_value", set_value)?;
let sleep = lua.create_async_function(|_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
table.set("sleep", sleep)?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
10
);
table.call_async_method("set_value", 15).await?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
15
);
assert_eq!(
table
.call_async_function::<_, _, String>("sleep", 7)
.await?,
"elapsed:7ms"
);
Ok(())
}
#[tokio::test]
async fn test_async_userdata() -> Result<()> {
#[derive(Clone)]
struct MyUserData(Arc<AtomicI64>);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(data.0.load(Ordering::Relaxed))
});
methods.add_async_method("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await;
data.0.store(n, Ordering::Relaxed);
Ok(())
});
methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
});
}
}
let lua = Lua::new();
let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(Arc::new(AtomicI64::new(11))))?;
globals.set("userdata", userdata.clone())?;
lua.load(
r#"
assert(userdata:get_value() == 11)
userdata:set_value(12)
assert(userdata.sleep(5) == "elapsed:5ms")
assert(userdata:get_value() == 12)
"#,
)
.exec_async()
.await?;
Ok(())
}
#[tokio::test]
async fn test_async_scope() -> Result<()> {
let ref lua = Lua::new();
let ref rc = Rc::new(Cell::new(0));
let fut = lua.async_scope(|scope| async move {
let f = scope.create_async_function(move |_, n: u64| {
let rc2 = rc.clone();
async move {
rc2.set(42);
Delay::new(Duration::from_millis(n)).await;
assert_eq!(Rc::strong_count(&rc2), 2);
Ok(())
}
})?;
lua.globals().set("f", f.clone())?;
assert_eq!(Rc::strong_count(rc), 1);
let _ = f.call_async::<u64, ()>(10).await?;
assert_eq!(Rc::strong_count(rc), 1);
// Create future in partialy polled state (Poll::Pending)
let g = lua.create_thread(f)?;
g.resume::<u64, ()>(10)?;
lua.globals().set("g", g)?;
assert_eq!(Rc::strong_count(rc), 2);
Ok(())
});
assert_eq!(Rc::strong_count(rc), 1);
let _ = fut.await?;
assert_eq!(Rc::strong_count(rc), 1);
match lua
.globals()
.get::<_, Function>("f")?
.call_async::<_, ()>(10)
.await
{
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed function: {:?}", r),
};
match lua.globals().get::<_, Thread>("g")?.resume::<_, Value>(()) {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed function: {:?}", r),
};
Ok(())
}
#[tokio::test]
async fn test_async_scope_userdata() -> Result<()> {
#[derive(Clone)]
struct MyUserData(Arc<AtomicI64>);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(data.0.load(Ordering::Relaxed))
});
methods.add_async_method("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await;
data.0.store(n, Ordering::Relaxed);
Ok(())
});
methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
});
}
}
let ref lua = Lua::new();
let ref arc = Arc::new(AtomicI64::new(11));
lua.async_scope(|scope| async move {
let ud = scope.create_userdata(MyUserData(arc.clone()))?;
lua.globals().set("userdata", ud)?;
lua.load(
r#"
assert(userdata:get_value() == 11)
userdata:set_value(12)
assert(userdata.sleep(5) == "elapsed:5ms")
assert(userdata:get_value() == 12)
"#,
)
.exec_async()
.await
})
.await?;
assert_eq!(Arc::strong_count(arc), 1);
match lua.load("userdata:get_value()").exec_async().await {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed userdata: {:?}", r),
};
Ok(())
}
+24
View File
@@ -0,0 +1,24 @@
#[test]
#[ignore]
fn test_compilation() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile/function_borrow.rs");
t.compile_fail("tests/compile/lua_norefunwindsafe.rs");
t.compile_fail("tests/compile/ref_nounwindsafe.rs");
t.compile_fail("tests/compile/scope_callback_capture.rs");
t.compile_fail("tests/compile/scope_callback_inner.rs");
t.compile_fail("tests/compile/scope_callback_outer.rs");
t.compile_fail("tests/compile/scope_invariance.rs");
t.compile_fail("tests/compile/scope_mutable_aliasing.rs");
t.compile_fail("tests/compile/scope_userdata_borrow.rs");
t.compile_fail("tests/compile/static_callback_args.rs");
#[cfg(feature = "async")]
t.compile_fail("tests/compile/async_nonstatic_userdata.rs");
#[cfg(feature = "send")]
t.compile_fail("tests/compile/non_send.rs");
#[cfg(not(feature = "send"))]
t.pass("tests/compile/non_send.rs");
}
+17
View File
@@ -0,0 +1,17 @@
use mlua::{Lua, UserData, UserDataMethods};
fn main() {
let ref lua = Lua::new();
#[derive(Clone)]
struct MyUserData<'a>(&'a i64);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("print", |_, data, ()| async move {
println!("{}", data.0);
Ok(())
});
}
}
}
@@ -0,0 +1,36 @@
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> $DIR/async_nonstatic_userdata.rs:11:72
|
11 | methods.add_async_method("print", |_, data, ()| async move {
| ________________________________________________________________________^
12 | | println!("{}", data.0);
13 | | Ok(())
14 | | });
| |_____________^
|
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 9:10...
--> $DIR/async_nonstatic_userdata.rs:9:10
|
9 | impl<'a> UserData for MyUserData<'a> {
| ^^
note: ...so that the types are compatible
--> $DIR/async_nonstatic_userdata.rs:11:72
|
11 | methods.add_async_method("print", |_, data, ()| async move {
| ________________________________________________________________________^
12 | | println!("{}", data.0);
13 | | Ok(())
14 | | });
| |_____________^
= note: expected `(MyUserData<'_>,)`
found `(MyUserData<'a>,)`
note: but, the lifetime must be valid for the lifetime `'lua` as defined on the method body at 10:24...
--> $DIR/async_nonstatic_userdata.rs:10:24
|
10 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ^^^^
note: ...so that the type `impl Future` will meet its required lifetime bounds
--> $DIR/async_nonstatic_userdata.rs:11:21
|
11 | methods.add_async_method("print", |_, data, ()| async move {
| ^^^^^^^^^^^^^^^^
+16
View File
@@ -0,0 +1,16 @@
error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<()>`
= note: required because it appears within the type `PhantomData<UnsafeCell<()>>`
= note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:48]`
+17
View File
@@ -0,0 +1,17 @@
use std::cell::Cell;
use std::rc::Rc;
use mlua::{Lua, Result};
fn main() -> Result<()> {
let lua = Lua::new();
let data = Rc::new(Cell::new(0));
lua.create_function(move |_, ()| {
Ok(data.get())
})?
.call::<_, i32>(())?;
Ok(())
}
+14
View File
@@ -0,0 +1,14 @@
error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
--> $DIR/non_send.rs:11:9
|
11 | lua.create_function(move |_, ()| {
| _________^^^^^^^^^^^^^^^_-
| | |
| | `Rc<Cell<i32>>` cannot be sent between threads safely
12 | | Ok(data.get())
13 | | })?
| |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
|
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
= note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
+18
View File
@@ -0,0 +1,18 @@
error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<()>`
= note: required because it appears within the type `PhantomData<UnsafeCell<()>>`
= note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `LuaTable<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:54]`
@@ -1,15 +1,11 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
use mlua::{Lua, Table};
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
lua.scope(|scope| {
let mut inner: Option<Table> = None;
let f = scope
.create_function_mut(move |lua, t: Table| {
.create_function_mut(move |_, t: Table| {
if let Some(old) = inner.take() {
// Access old callback `Lua`.
}
@@ -0,0 +1,26 @@
warning: unused variable: `old`
--> $DIR/scope_callback_capture.rs:9:29
|
9 | if let Some(old) = inner.take() {
| ^^^ help: if this is intentional, prefix it with an underscore: `_old`
|
= note: `#[warn(unused_variables)]` on by default
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_capture.rs:7:17
|
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(move |_, t: Table| {
9 | | if let Some(old) = inner.take() {
10 | | // Access old callback `Lua`.
... |
13 | | Ok(())
14 | | })?;
| |______________^ `scope` escapes the closure body here
@@ -1,12 +1,8 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
use mlua::{Lua, Table};
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
lua.scope(|scope| {
let mut inner: Option<Table> = None;
let f = scope
.create_function_mut(|_, t: Table| {
+42
View File
@@ -0,0 +1,42 @@
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_inner.rs:7:17
|
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function
--> $DIR/scope_callback_inner.rs:8:34
|
5 | lua.scope(|scope| {
| ----- has type `&Scope<'_, '2>`
...
8 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
9 | inner = Some(t);
| ----- `inner` is borrowed here
|
note: function requires argument type to outlive `'2`
--> $DIR/scope_callback_inner.rs:7:17
|
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^
help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword
|
8 | .create_function_mut(move |_, t: Table| {
| ^^^^^^^^^^^^^^^^^^
@@ -1,13 +1,9 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
use mlua::{Lua, Table};
fn main() {
let lua = Lua::new();
let mut outer: Option<Table> = None;
lua.scope(|scope| -> Result<()> {
lua.scope(|scope| {
let f = scope
.create_function_mut(|_, t: Table| {
outer = Some(t);
+30
View File
@@ -0,0 +1,30 @@
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_outer.rs:7:17
|
6 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
9 | | outer = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0597]: `outer` does not live long enough
--> $DIR/scope_callback_outer.rs:9:17
|
6 | lua.scope(|scope| {
| ------- value captured here
...
9 | outer = Some(t);
| ^^^^^ borrowed value does not live long enough
...
15 | }
| -
| |
| `outer` dropped here while still borrowed
| borrow might be used here, when `outer` is dropped and runs the destructor for type `Option<LuaTable<'_>>`
@@ -1,4 +1,4 @@
use mlua::{Lua, Result};
use mlua::Lua;
struct Test {
field: i32,
@@ -6,7 +6,7 @@ struct Test {
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
lua.scope(|scope| {
let f = {
let mut test = Test { field: 0 };
@@ -1,8 +1,8 @@
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function
--> $DIR/scope_invariance.rs:14:38
|
9 | lua.scope(|scope| -> Result<()> {
| ----- has type `&mlua::scope::Scope<'_, '1>`
9 | lua.scope(|scope| {
| ----- has type `&Scope<'_, '1>`
...
14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test`
@@ -1,15 +1,15 @@
use mlua::{Lua, UserData, Result};
struct MyUserData<'a>(&'a mut i32);
impl<'a> UserData for MyUserData<'a> {}
use mlua::{Lua, UserData};
fn main() {
struct MyUserData<'a>(&'a mut i32);
impl<'a> UserData for MyUserData<'a> {};
let mut i = 1;
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
lua.scope(|scope| {
let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
Ok(())
});
}
@@ -1,9 +1,9 @@
error[E0499]: cannot borrow `i` as mutable more than once at a time
--> $DIR/scope_mutable_aliasing.rs:12:61
|
11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
| ------ first mutable borrow occurs here
12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
| ------------------------- ^^^^^^ second mutable borrow occurs here
| |
| first borrow later used by call
@@ -1,19 +1,18 @@
use mlua::{Lua, UserData, Result};
struct MyUserData<'a>(&'a i32);
impl<'a> UserData for MyUserData<'a> {}
use mlua::{Lua, UserData};
fn main() {
// Should not allow userdata borrow to outlive lifetime of AnyUserData handle
struct MyUserData<'a>(&'a i32);
impl<'a> UserData for MyUserData<'a> {};
let igood = 1;
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood))?;
lua.scope(|scope| {
let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood)).unwrap();
let _ubad = {
let ibad = 42;
scope.create_nonstatic_userdata(MyUserData(&ibad))?;
scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap();
};
Ok(())
});
@@ -1,13 +1,13 @@
error[E0597]: `ibad` does not live long enough
--> $DIR/scope_userdata_borrow.rs:16:56
--> $DIR/scope_userdata_borrow.rs:15:56
|
12 | lua.scope(|scope| -> Result<()> {
| ----- has type `&mlua::scope::Scope<'_, '1>`
11 | lua.scope(|scope| {
| ----- has type `&Scope<'_, '1>`
...
16 | scope.create_nonstatic_userdata(MyUserData(&ibad))?;
15 | scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap();
| -------------------------------------------^^^^^--
| | |
| | borrowed value does not live long enough
| argument requires that `ibad` is borrowed for `'1`
17 | };
16 | };
| - `ibad` dropped here while still borrowed
+32
View File
@@ -0,0 +1,32 @@
use std::cell::RefCell;
use mlua::{Lua, Result, Table};
fn main() -> Result<()> {
thread_local! {
static BAD_TIME: RefCell<Option<Table<'static>>> = RefCell::new(None);
}
let lua = Lua::new();
lua.create_function(|_, table: Table| {
BAD_TIME.with(|bt| {
*bt.borrow_mut() = Some(table);
});
Ok(())
})?
.call::<_, ()>(lua.create_table()?)?;
// In debug, this will panic with a reference leak before getting to the next part but
// it segfaults anyway.
drop(lua);
BAD_TIME.with(|bt| {
println!(
"you're gonna have a bad time: {}",
bt.borrow().as_ref().unwrap().len().unwrap()
);
});
Ok(())
}
+35
View File
@@ -0,0 +1,35 @@
error[E0597]: `lua` does not live long enough
--> $DIR/static_callback_args.rs:12:5
|
12 | lua.create_function(|_, table: Table| {
| -^^
| |
| _____borrowed value does not live long enough
| |
13 | | BAD_TIME.with(|bt| {
14 | | *bt.borrow_mut() = Some(table);
15 | | });
16 | | Ok(())
17 | | })?
| |______- argument requires that `lua` is borrowed for `'static`
...
32 | }
| - `lua` dropped here while still borrowed
error[E0505]: cannot move out of `lua` because it is borrowed
--> $DIR/static_callback_args.rs:22:10
|
12 | lua.create_function(|_, table: Table| {
| ---
| |
| _____borrow of `lua` occurs here
| |
13 | | BAD_TIME.with(|bt| {
14 | | *bt.borrow_mut() = Some(table);
15 | | });
16 | | Ok(())
17 | | })?
| |______- argument requires that `lua` is borrowed for `'static`
...
22 | drop(lua);
| ^^^ move out of `lua` occurs here
-6
View File
@@ -1,6 +0,0 @@
#[test]
#[ignore]
fn test_compile_fail() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/*.rs");
}
@@ -1,42 +0,0 @@
error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<()>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::UnsafeCell<()>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
= note: required because it appears within the type `std::cell::Cell<isize>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
@@ -1,48 +0,0 @@
error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<()>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::UnsafeCell<()>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
= note: required because it appears within the type `std::cell::Cell<isize>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
@@ -1,45 +0,0 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_capture.rs:12:14
|
12 | .create_function_mut(move |lua, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 9:15...
--> $DIR/scope_callback_capture.rs:9:15
|
9 | lua.scope(|scope| -> Result<()> {
| _______________^
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
--> $DIR/scope_callback_capture.rs:11:17
|
11 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 9:5...
--> $DIR/scope_callback_capture.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_capture.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |______^
@@ -1,45 +0,0 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_inner.rs:12:14
|
12 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 9:15...
--> $DIR/scope_callback_inner.rs:9:15
|
9 | lua.scope(|scope| -> Result<()> {
| _______________^
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
--> $DIR/scope_callback_inner.rs:11:17
|
11 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 9:5...
--> $DIR/scope_callback_inner.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_inner.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |______^
@@ -1,11 +0,0 @@
error: borrowed data cannot be stored outside of its closure
--> $DIR/scope_callback_outer.rs:11:17
|
9 | let mut outer: Option<Table> = None;
| --------- ...so that variable is valid at time of its declaration
10 | lua.scope(|scope| -> Result<()> {
| --------------------- borrowed data cannot outlive this closure
11 | let f = scope
| ^^^^^ cannot be stored outside of its closure
12 | .create_function_mut(|_, t: Table| {
| ------------------- cannot infer an appropriate lifetime...
+114
View File
@@ -0,0 +1,114 @@
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::CString;
use maplit::{btreemap, btreeset, hashmap, hashset};
use mlua::{Lua, Result};
#[test]
fn test_conv_vec() -> Result<()> {
let lua = Lua::new();
let v = vec![1, 2, 3];
lua.globals().set("v", v.clone())?;
let v2: Vec<i32> = lua.globals().get("v")?;
assert!(v == v2);
Ok(())
}
#[test]
fn test_conv_hashmap() -> Result<()> {
let lua = Lua::new();
let map = hashmap! {"hello".to_string() => "world".to_string()};
lua.globals().set("map", map.clone())?;
let map2: HashMap<String, String> = lua.globals().get("map")?;
assert!(map == map2);
Ok(())
}
#[test]
fn test_conv_hashset() -> Result<()> {
let lua = Lua::new();
let set = hashset! {"hello".to_string(), "world".to_string()};
lua.globals().set("set", set.clone())?;
let set2: HashSet<String> = lua.globals().get("set")?;
assert!(set == set2);
Ok(())
}
#[test]
fn test_conv_btreemap() -> Result<()> {
let lua = Lua::new();
let map = btreemap! {"hello".to_string() => "world".to_string()};
lua.globals().set("map", map.clone())?;
let map2: BTreeMap<String, String> = lua.globals().get("map")?;
assert!(map == map2);
Ok(())
}
#[test]
fn test_conv_btreeset() -> Result<()> {
let lua = Lua::new();
let set = btreeset! {"hello".to_string(), "world".to_string()};
lua.globals().set("set", set.clone())?;
let set2: BTreeSet<String> = lua.globals().get("set")?;
assert!(set == set2);
Ok(())
}
#[test]
fn test_conv_cstring() -> Result<()> {
let lua = Lua::new();
let s = CString::new(b"hello".to_vec()).unwrap();
lua.globals().set("s", s.clone())?;
let s2: CString = lua.globals().get("s")?;
assert!(s == s2);
Ok(())
}
#[test]
fn test_conv_cow() -> Result<()> {
let lua = Lua::new();
let s = Cow::from("hello");
lua.globals().set("s", s.clone())?;
let s2: String = lua.globals().get("s")?;
assert!(s == s2);
Ok(())
}
#[test]
fn test_conv_boxed_str() -> Result<()> {
let lua = Lua::new();
let s = String::from("hello").into_boxed_str();
lua.globals().set("s", s.clone())?;
let s2: Box<str> = lua.globals().get("s")?;
assert!(s == s2);
Ok(())
}
#[test]
fn test_conv_boxed_slice() -> Result<()> {
let lua = Lua::new();
let v = vec![1, 2, 3].into_boxed_slice();
lua.globals().set("v", v.clone())?;
let v2: Box<[i32]> = lua.globals().get("v")?;
assert!(v == v2);
Ok(())
}
+14
View File
@@ -75,3 +75,17 @@ fn test_rust_function() -> Result<()> {
Ok(())
}
#[test]
fn test_dump() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let concat_lua = lua
.load(r#"function(arg1, arg2) return arg1 .. arg2 end"#)
.eval::<Function>()?;
let concat = lua.load(&concat_lua.dump(false)).into_function()?;
assert_eq!(concat.call::<_, String>(("foo", "bar"))?, "foobar");
Ok(())
}
+249
View File
@@ -0,0 +1,249 @@
use std::cell::RefCell;
use std::ops::Deref;
use std::str;
use std::sync::{Arc, Mutex};
use mlua::{Error, HookTriggers, Lua, Result, Value};
#[test]
fn line_counts() -> Result<()> {
let output = Arc::new(Mutex::new(Vec::new()));
let hook_output = output.clone();
let lua = Lua::new();
lua.set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
move |_lua, debug| {
hook_output.lock().unwrap().push(debug.curr_line());
Ok(())
},
)?;
lua.load(
r#"
local x = 2 + 3
local y = x * 63
local z = string.len(x..", "..y)
"#,
)
.exec()?;
lua.remove_hook();
let output = output.lock().unwrap();
if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 {
assert_eq!(*output, vec![2, 3, 4, 0, 4]);
} else {
assert_eq!(*output, vec![2, 3, 4]);
}
Ok(())
}
#[test]
fn function_calls() -> Result<()> {
let output = Arc::new(Mutex::new(Vec::new()));
let hook_output = output.clone();
let lua = Lua::new();
lua.set_hook(
HookTriggers {
on_calls: true,
..Default::default()
},
move |_lua, debug| {
let names = debug.names();
let source = debug.source();
let name = names.name.map(|s| str::from_utf8(s).unwrap().to_owned());
let what = source.what.map(|s| str::from_utf8(s).unwrap().to_owned());
hook_output.lock().unwrap().push((name, what));
Ok(())
},
)?;
lua.load(
r#"
local v = string.len("Hello World")
"#,
)
.exec()?;
lua.remove_hook();
let output = output.lock().unwrap();
if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 {
assert_eq!(
*output,
vec![
(None, Some("main".to_string())),
(Some("len".to_string()), Some("Lua".to_string()))
]
);
} else {
assert_eq!(
*output,
vec![
(None, Some("main".to_string())),
(Some("len".to_string()), Some("C".to_string()))
]
);
}
Ok(())
}
#[test]
fn error_within_hook() -> Result<()> {
let lua = Lua::new();
lua.set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
|_lua, _debug| {
Err(Error::RuntimeError(
"Something happened in there!".to_string(),
))
},
)?;
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"),
};
Ok(())
}
#[test]
fn limit_execution_instructions() -> Result<()> {
let lua = Lua::new();
let mut max_instructions = 10000;
#[cfg(feature = "luajit")]
// For LuaJIT disable JIT, as compiled code does not trigger hooks
lua.load("jit.off()").exec()?;
lua.set_hook(
HookTriggers {
every_nth_instruction: Some(30),
..Default::default()
},
move |_lua, _debug| {
max_instructions -= 30;
if max_instructions < 0 {
Err(Error::RuntimeError("time's up".to_string()))
} else {
Ok(())
}
},
)?;
lua.globals().set("x", Value::Integer(0))?;
let _ = lua
.load(
r#"
for i = 1, 10000 do
x = x + 1
end
"#,
)
.exec()
.expect_err("instruction limit didn't occur");
Ok(())
}
#[test]
fn hook_removal() -> Result<()> {
let lua = Lua::new();
lua.set_hook(
HookTriggers {
every_nth_instruction: Some(1),
..Default::default()
},
|_lua, _debug| {
Err(Error::RuntimeError(
"this hook should've been removed by this time".to_string(),
))
},
)?;
assert!(lua.load("local x = 1").exec().is_err());
lua.remove_hook();
assert!(lua.load("local x = 1").exec().is_ok());
Ok(())
}
#[test]
fn hook_swap_within_hook() -> Result<()> {
thread_local! {
static TL_LUA: RefCell<Option<Lua>> = RefCell::new(None);
}
TL_LUA.with(|tl| {
*tl.borrow_mut() = Some(Lua::new());
});
TL_LUA.with(|tl| {
tl.borrow().as_ref().unwrap().set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
move |lua, _debug| {
lua.globals().set("ok", 1i64)?;
TL_LUA.with(|tl| {
tl.borrow().as_ref().unwrap().set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
move |lua, _debug| {
lua.load(
r#"
if ok ~= nil then
ok = ok + 1
end
"#,
)
.exec()
.expect("exec failure within hook");
TL_LUA.with(|tl| {
tl.borrow().as_ref().unwrap().remove_hook();
});
Ok(())
},
)
})
},
)
})?;
TL_LUA.with(|tl| {
let tl = tl.borrow();
let lua = tl.as_ref().unwrap();
lua.load(
r#"
local x = 1
x = 2
local y = 3
"#,
)
.exec()?;
assert_eq!(lua.globals().get::<_, i64>("ok")?, 2);
Ok(())
})
}
+32 -1
View File
@@ -2,12 +2,43 @@ use std::sync::Arc;
use mlua::{Lua, Result, UserData};
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
use mlua::Error;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[test]
fn test_memory_limit() -> Result<()> {
let lua = Lua::new();
let initial_memory = lua.used_memory();
assert!(
initial_memory > 0,
"used_memory reporting is wrong, lua uses memory for stdlib"
);
let f = lua
.load("local t = {}; for i = 1,10000 do t[i] = i end")
.into_function()?;
f.call::<_, ()>(()).expect("should trigger no memory limit");
lua.set_memory_limit(initial_memory + 10000)?;
match f.call::<_, ()>(()) {
Err(Error::MemoryError(_)) => {}
something_else => panic!("did not trigger memory error: {:?}", something_else),
};
lua.set_memory_limit(0)?;
f.call::<_, ()>(()).expect("should trigger no memory limit");
Ok(())
}
#[test]
fn test_gc_control() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
assert!(lua.gc_is_running());
lua.gc_stop();
+5
View File
@@ -0,0 +1,5 @@
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-args=-rdynamic"]
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-args=-rdynamic"]
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "module_runner"
version = "0.0.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
[features]
lua54 = ["mlua/lua54"]
lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
lua51 = ["mlua/lua51"]
luajit = ["mlua/luajit"]
vendored = ["mlua/vendored"]
[dependencies]
mlua = { path = "../.." }
+82
View File
@@ -0,0 +1,82 @@
use std::env;
use std::path::PathBuf;
use mlua::{Lua, Result};
#[test]
fn test_module() -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
local mod = require("rust_module")
assert(mod.sum(2,2) == 4)
"#,
)
.exec()
}
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51"
))]
#[test]
fn test_module_from_thread() -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
local mod
local co = coroutine.create(function(a, b)
mod = require("rust_module")
assert(mod.sum(a, b) == a + b)
end)
coroutine.resume(co, 3, 5)
collectgarbage()
assert(mod.used_memory() > 0)
"#,
)
.exec()
}
fn make_lua() -> Result<Lua> {
let (dylib_path, dylib_ext, separator);
if cfg!(target_os = "macos") {
dylib_path = env::var("DYLD_FALLBACK_LIBRARY_PATH").unwrap();
dylib_ext = "dylib";
separator = ":";
} else if cfg!(target_os = "linux") {
dylib_path = env::var("LD_LIBRARY_PATH").unwrap();
dylib_ext = "so";
separator = ":";
} else if cfg!(target_os = "windows") {
dylib_path = env::var("PATH").unwrap();
dylib_ext = "dll";
separator = ";";
} else {
panic!("unknown target os");
};
let mut cpath = dylib_path
.split(separator)
.take(3)
.map(|p| {
let mut path = PathBuf::from(p);
path.push(format!("lib?.{}", dylib_ext));
path.to_str().unwrap().to_owned()
})
.collect::<Vec<_>>()
.join(";");
if cfg!(target_os = "windows") {
cpath = cpath.replace("\\", "\\\\");
cpath = cpath.replace("lib?.", "?.");
}
let lua = unsafe { Lua::unsafe_new() }; // To be able to load C modules
lua.load(&format!("package.cpath = \"{}\"", cpath)).exec()?;
Ok(lua)
}
+162 -35
View File
@@ -1,7 +1,11 @@
use std::cell::Cell;
use std::rc::Rc;
use std::sync::Arc;
use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods};
use mlua::{
AnyUserData, Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataFields,
UserDataMethods,
};
#[test]
fn scope_func() -> Result<()> {
@@ -23,44 +27,16 @@ fn scope_func() -> Result<()> {
assert_eq!(Rc::strong_count(&rc), 1);
match lua.globals().get::<_, Function>("bad")?.call::<_, ()>(()) {
Err(Error::CallbackError { .. }) => {}
Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
Error::CallbackDestructed => {}
ref err => panic!("wrong error type {:?}", err),
},
r => panic!("improper return for destructed function: {:?}", r),
};
Ok(())
}
#[test]
fn scope_drop() -> Result<()> {
let lua = Lua::new();
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, _, ()| Ok(()));
}
}
let rc = Rc::new(());
lua.scope(|scope| {
lua.globals().set(
"test",
scope.create_static_userdata(MyUserdata(rc.clone()))?,
)?;
assert_eq!(Rc::strong_count(&rc), 2);
Ok(())
})?;
assert_eq!(Rc::strong_count(&rc), 1);
match lua.load("test:method()").exec() {
Err(Error::CallbackError { .. }) => {}
r => panic!("improper return for destructed userdata: {:?}", r),
};
Ok(())
}
#[test]
fn scope_capture() -> Result<()> {
let lua = Lua::new();
@@ -80,7 +56,7 @@ fn scope_capture() -> Result<()> {
}
#[test]
fn outer_lua_access() -> Result<()> {
fn scope_outer_lua_access() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
@@ -94,6 +70,41 @@ fn outer_lua_access() -> Result<()> {
Ok(())
}
#[test]
fn scope_userdata_fields() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserData<'a> {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("val", |_, data| Ok(data.0.get()));
fields.add_field_method_set("val", |_, data, val| {
data.0.set(val);
Ok(())
});
}
}
let lua = Lua::new();
let i = Cell::new(42);
let f: Function = lua
.load(
r#"
function(u)
assert(u.val == 42)
u.val = 44
end
"#,
)
.eval()?;
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?;
assert_eq!(i.get(), 44);
Ok(())
}
#[test]
fn scope_userdata_methods() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
@@ -198,7 +209,6 @@ fn scope_userdata_mismatch() -> Result<()> {
a.inc(a)
b.inc(b)
end
function bad(a, b)
a.inc(b)
end
@@ -229,3 +239,120 @@ fn scope_userdata_mismatch() -> Result<()> {
Ok(())
}
#[test]
fn scope_userdata_drop() -> Result<()> {
let lua = Lua::new();
struct MyUserData(Rc<()>);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, _, ()| Ok(()));
}
}
struct MyUserDataArc(Arc<()>);
impl UserData for MyUserDataArc {}
let rc = Rc::new(());
let arc = Arc::new(());
lua.scope(|scope| {
let ud = scope.create_userdata(MyUserData(rc.clone()))?;
ud.set_user_value(MyUserDataArc(arc.clone()))?;
lua.globals().set("ud", ud)?;
assert_eq!(Rc::strong_count(&rc), 2);
assert_eq!(Arc::strong_count(&arc), 2);
Ok(())
})?;
lua.gc_collect()?;
assert_eq!(Rc::strong_count(&rc), 1);
assert_eq!(Arc::strong_count(&arc), 1);
match lua.load("ud:method()").exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
err => panic!("expected CallbackDestructed, got {:?}", err),
},
r => panic!("improper return for destructed userdata: {:?}", r),
};
let ud = lua.globals().get::<_, AnyUserData>("ud")?;
match ud.borrow::<MyUserData>() {
Ok(_) => panic!("succesfull borrow for destructed userdata"),
Err(Error::UserDataDestructed) => {}
Err(err) => panic!("improper borrow error for destructed userdata: {:?}", err),
}
match ud.get_metatable() {
Ok(_) => panic!("successful metatable retrieval of destructed userdata"),
Err(Error::UserDataDestructed) => {}
Err(err) => panic!(
"improper metatable error for destructed userdata: {:?}",
err
),
}
Ok(())
}
#[test]
fn scope_nonstatic_userdata_drop() -> Result<()> {
let lua = Lua::new();
struct MyUserData<'a>(&'a Cell<i64>, Arc<()>);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
}
}
struct MyUserDataArc(Arc<()>);
impl UserData for MyUserDataArc {}
let i = Cell::new(1);
let arc = Arc::new(());
lua.scope(|scope| {
let ud = scope.create_nonstatic_userdata(MyUserData(&i, arc.clone()))?;
ud.set_user_value(MyUserDataArc(arc.clone()))?;
lua.globals().set("ud", ud)?;
lua.load("ud:inc()").exec()?;
assert_eq!(Arc::strong_count(&arc), 3);
Ok(())
})?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&arc), 1);
match lua.load("ud:inc()").exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
err => panic!("expected CallbackDestructed, got {:?}", err),
},
r => panic!("improper return for destructed userdata: {:?}", r),
};
let ud = lua.globals().get::<_, AnyUserData>("ud")?;
match ud.borrow::<MyUserData>() {
Ok(_) => panic!("succesfull borrow for destructed userdata"),
Err(Error::UserDataDestructed) => {}
Err(err) => panic!("improper borrow error for destructed userdata: {:?}", err),
}
match ud.get_metatable() {
Ok(_) => panic!("successful metatable retrieval of destructed userdata"),
Err(Error::UserDataDestructed) => {}
Err(err) => panic!(
"improper metatable error for destructed userdata: {:?}",
err
),
}
Ok(())
}
+415
View File
@@ -0,0 +1,415 @@
#![cfg(feature = "serialize")]
use std::collections::HashMap;
use mlua::{Error, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData, Value};
use serde::{Deserialize, Serialize};
#[test]
fn test_serialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize)]
struct MyUserData(i64, String);
impl UserData for MyUserData {}
let lua = Lua::new();
let globals = lua.globals();
let ud = lua.create_ser_userdata(MyUserData(123, "test userdata".into()))?;
globals.set("ud", ud)?;
globals.set("null", lua.null())?;
let empty_array = lua.create_table()?;
empty_array.set_metatable(Some(lua.array_metatable()));
globals.set("empty_array", empty_array)?;
let val = lua
.load(
r#"
{
_bool = true,
_integer = 123,
_number = 321.99,
_string = "test string serialization",
_table_arr = {nil, "value 1", nil, "value 2", {}},
_table_map = {["table"] = "map", ["null"] = null},
_bytes = "\240\040\140\040",
_userdata = ud,
_null = null,
_empty_map = {},
_empty_array = empty_array,
}
"#,
)
.eval::<Value>()?;
let json = serde_json::json!({
"_bool": true,
"_integer": 123,
"_number": 321.99,
"_string": "test string serialization",
"_table_arr": [null, "value 1", null, "value 2", {}],
"_table_map": {"table": "map", "null": null},
"_bytes": [240, 40, 140, 40],
"_userdata": [123, "test userdata"],
"_null": null,
"_empty_map": {},
"_empty_array": [],
});
assert_eq!(serde_json::to_value(&val)?, json);
// Test to-from loop
let val = lua.to_value(&json)?;
let expected_json = lua.from_value::<serde_json::Value>(val)?;
assert_eq!(expected_json, json);
Ok(())
}
#[test]
fn test_serialize_in_scope() -> LuaResult<()> {
#[derive(Serialize, Clone)]
struct MyUserData(i64, String);
impl UserData for MyUserData {}
let lua = Lua::new();
lua.scope(|scope| {
let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?;
assert_eq!(
serde_json::to_value(&ud).unwrap(),
serde_json::json!((-5, "test userdata"))
);
Ok(())
})?;
lua.scope(|scope| {
let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?;
lua.globals().set("ud", ud)
})?;
let val = lua.load("ud").eval::<Value>()?;
match serde_json::to_value(&val) {
Ok(v) => panic!("expected destructed error, got {}", v),
Err(e) if e.to_string().contains("destructed") => {}
Err(e) => panic!("expected destructed error, got {}", e),
}
struct MyUserDataRef<'a>(&'a ());
impl<'a> UserData for MyUserDataRef<'a> {}
lua.scope(|scope| {
let ud = scope.create_nonstatic_userdata(MyUserDataRef(&()))?;
match serde_json::to_value(&ud) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
};
Ok(())
})?;
Ok(())
}
#[test]
fn test_serialize_failure() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize)]
struct MyUserData(i64);
impl UserData for MyUserData {}
let lua = Lua::new();
let ud = Value::UserData(lua.create_userdata(MyUserData(123))?);
match serde_json::to_value(&ud) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
let func = lua.create_function(|_, _: ()| Ok(()))?;
match serde_json::to_value(&Value::Function(func.clone())) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
let thr = lua.create_thread(func)?;
match serde_json::to_value(&Value::Thread(thr)) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
Ok(())
}
#[test]
fn test_to_value_struct() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null())?;
#[derive(Serialize)]
struct Test {
name: String,
key: i64,
data: Option<bool>,
}
let test = Test {
name: "alex".to_string(),
key: -16,
data: None,
};
globals.set("value", lua.to_value(&test)?)?;
lua.load(
r#"
assert(value["name"] == "alex")
assert(value["key"] == -16)
assert(value["data"] == null)
"#,
)
.exec()
}
#[test]
fn test_to_value_enum() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null())?;
#[derive(Serialize)]
struct Test {
name: String,
key: i64,
data: Option<bool>,
}
let test = Test {
name: "alex".to_string(),
key: -16,
data: None,
};
globals.set("value", lua.to_value(&test)?)?;
lua.load(
r#"
assert(value["name"] == "alex")
assert(value["key"] == -16)
assert(value["data"] == null)
"#,
)
.exec()?;
#[derive(Serialize)]
enum E {
Unit,
Integer(u32),
Tuple(u32, u32),
Struct { a: u32 },
}
let u = E::Unit;
globals.set("value", lua.to_value(&u)?)?;
lua.load(r#"assert(value == "Unit")"#).exec()?;
let n = E::Integer(1);
globals.set("value", lua.to_value(&n)?)?;
lua.load(r#"assert(value["Integer"] == 1)"#).exec()?;
let t = E::Tuple(1, 2);
globals.set("value", lua.to_value(&t)?)?;
lua.load(
r#"
assert(value["Tuple"][1] == 1)
assert(value["Tuple"][2] == 2)
"#,
)
.exec()?;
let s = E::Struct { a: 1 };
globals.set("value", lua.to_value(&s)?)?;
lua.load(r#"assert(value["Struct"]["a"] == 1)"#).exec()?;
Ok(())
}
#[test]
fn test_to_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null())?;
// set_array_metatable
let data = lua.to_value_with(
&Vec::<i32>::new(),
SerializeOptions::new().set_array_metatable(false),
)?;
globals.set("data", data)?;
lua.load(
r#"
assert(type(data) == "table" and #data == 0)
assert(getmetatable(data) == nil)
"#,
)
.exec()?;
#[derive(Serialize)]
struct UnitStruct;
#[derive(Serialize)]
struct MyData {
map: HashMap<&'static str, Option<i32>>,
unit: (),
unitstruct: UnitStruct,
}
// serialize_none_to_null
let mut map = HashMap::new();
map.insert("key", None);
let mydata = MyData {
map,
unit: (),
unitstruct: UnitStruct,
};
let data2 = lua.to_value_with(
&mydata,
SerializeOptions::new().serialize_none_to_null(false),
)?;
globals.set("data2", data2)?;
lua.load(
r#"
assert(data2.map.key == nil)
assert(data2.unit == null)
assert(data2.unitstruct == null)
"#,
)
.exec()?;
// serialize_unit_to_null
let data3 = lua.to_value_with(
&mydata,
SerializeOptions::new().serialize_unit_to_null(false),
)?;
globals.set("data3", data3)?;
lua.load(
r#"
assert(data3.map.key == null)
assert(data3.unit == nil)
assert(data3.unitstruct == nil)
"#,
)
.exec()?;
Ok(())
}
#[test]
fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
struct Test {
int: u32,
seq: Vec<String>,
map: std::collections::HashMap<i32, i32>,
empty: Vec<()>,
tuple: (u8, u8, u8),
}
let value = lua
.load(
r#"
{
int = 1,
seq = {"a", "b"},
map = {2, [4] = 1},
empty = {},
tuple = {10, 20, 30},
}
"#,
)
.eval::<Value>()?;
let got = lua.from_value(value)?;
assert_eq!(
Test {
int: 1,
seq: vec!["a".into(), "b".into()],
map: vec![(1, 2), (4, 1)].into_iter().collect(),
empty: vec![],
tuple: (10, 20, 30),
},
got
);
Ok(())
}
#[test]
fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
enum E {
Unit,
Integer(u32),
Tuple(u32, u32),
Struct { a: u32 },
}
let value = lua.load(r#""Unit""#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Unit, got);
let value = lua.load(r#"{Integer = 1}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Integer(1), got);
let value = lua.load(r#"{Tuple = {1, 2}}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Tuple(1, 2), got);
let value = lua.load(r#"{Struct = {a = 3}}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Struct { a: 3 }, got);
Ok(())
}
#[test]
fn test_from_value_enum_untagged() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
lua.globals().set("null", lua.null())?;
#[derive(Deserialize, PartialEq, Debug)]
#[serde(untagged)]
enum Eut {
Unit,
Integer(u64),
Tuple(u32, u32),
Struct { a: u32 },
}
let value = lua.load(r#"null"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Unit, got);
let value = lua.load(r#"1"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Integer(1), got);
let value = lua.load(r#"{3, 1}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Tuple(3, 1), got);
let value = lua.load(r#"{a = 10}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Struct { a: 10 }, got);
let value = lua.load(r#"{b = 12}"#).eval()?;
match lua.from_value::<Eut>(value) {
Ok(v) => panic!("expected Error::DeserializeError, got {:?}", v),
Err(Error::DeserializeError(_)) => {}
Err(e) => panic!("expected Error::DeserializeError, got {}", e),
}
Ok(())
}
+72 -1
View File
@@ -1,4 +1,4 @@
use mlua::{Lua, Nil, Result, Table, Value};
use mlua::{Lua, Nil, Result, Table, TableExt, Value};
#[test]
fn test_set_get() -> Result<()> {
@@ -109,6 +109,47 @@ fn test_table() -> Result<()> {
Ok(())
}
#[test]
fn test_table_sequence_from() -> Result<()> {
let lua = Lua::new();
let get_table = lua.create_function(|_, t: Table| Ok(t))?;
assert_eq!(
get_table
.call::<_, Table>(vec![1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
assert_eq!(
get_table
.call::<_, Table>([1, 2, 3].as_ref())?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
assert_eq!(
get_table
.call::<_, Table>([1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
assert_eq!(
get_table
.call::<_, Table>(&[1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
Ok(())
}
#[test]
fn test_table_scope() -> Result<()> {
let lua = Lua::new();
@@ -226,3 +267,33 @@ fn test_table_error() -> Result<()> {
Ok(())
}
#[test]
fn test_table_call() -> Result<()> {
let lua = Lua::new();
lua.load(
r#"
table = {a = 1}
function table.func(key)
return "func_"..key
end
function table:method(key)
return "method_"..self[key]
end
"#,
)
.exec()?;
let table: Table = lua.globals().get("table")?;
assert_eq!(table.call_function::<_, _, String>("func", "a")?, "func_a");
assert_eq!(
table.call_method::<_, _, String>("method", "a")?,
"method_1"
);
Ok(())
}
+276 -53
View File
@@ -1,12 +1,84 @@
use std::iter::FromIterator;
use std::panic::catch_unwind;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::string::String as StdString;
use std::sync::Arc;
use std::{error, f32, f64, fmt};
use mlua::{
Error, ExternalError, Function, Lua, Nil, Result, String, Table, UserData, Value, Variadic,
ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table,
UserData, Value, Variadic,
};
#[test]
fn test_safety() -> Result<()> {
let lua = Lua::new();
assert!(lua.load(r#"require "debug""#).exec().is_err());
match lua.load_from_std_lib(StdLib::DEBUG) {
Err(Error::SafetyError(_)) => {}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
drop(lua);
let lua = unsafe { Lua::unsafe_new() };
assert!(lua.load(r#"require "debug""#).exec().is_ok());
drop(lua);
match Lua::new_with(StdLib::DEBUG, LuaOptions::default()) {
Err(Error::SafetyError(_)) => {}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got new Lua state"),
}
let lua = Lua::new();
match lua.load(r#"package.loadlib()"#).exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::SafetyError(_) => {}
e => panic!("expected SafetyError cause, got {:?}", e),
},
Err(e) => panic!("expected CallbackError, got {:?}", e),
Ok(_) => panic!("expected CallbackError, got no error"),
};
match lua.load(r#"require "fake_ffi""#).exec() {
Err(Error::RuntimeError(msg)) => assert!(msg.contains("can't load C modules in safe mode")),
Err(e) => panic!("expected RuntimeError, got {:?}", e),
Ok(_) => panic!("expected RuntimeError, got no error"),
}
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true);
match lua.load(&bytecode).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
drop(lua);
// Test safety rules after dynamically loading `package` library
let lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?;
assert!(lua.globals().get::<_, Option<Value>>("require")?.is_none());
lua.load_from_std_lib(StdLib::PACKAGE)?;
match lua.load(r#"package.loadlib()"#).exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::SafetyError(_) => {}
e => panic!("expected SafetyError cause, got {:?}", e),
},
Err(e) => panic!("expected CallbackError, got {:?}", e),
Ok(_) => panic!("expected CallbackError, got no error"),
};
Ok(())
}
#[test]
fn test_load() -> Result<()> {
let lua = Lua::new();
@@ -75,6 +147,41 @@ fn test_eval() -> Result<()> {
Ok(())
}
#[test]
fn test_load_mode() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
assert_eq!(
lua.load("1 + 1").set_mode(ChunkMode::Text).eval::<i32>()?,
2
);
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a text chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true);
assert_eq!(lua.load(&bytecode).eval::<i32>()?, 2);
assert_eq!(
lua.load(&bytecode)
.set_mode(ChunkMode::Binary)
.eval::<i32>()?,
2
);
match lua.load(&bytecode).set_mode(ChunkMode::Text).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a binary chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
Ok(())
}
#[test]
fn test_lua_multi() -> Result<()> {
let lua = Lua::new();
@@ -115,6 +222,7 @@ fn test_coercion() -> Result<()> {
int = 123
str = "123"
num = 123.0
func = function() end
"#,
)
.exec()?;
@@ -123,6 +231,7 @@ fn test_coercion() -> Result<()> {
assert_eq!(globals.get::<_, String>("int")?, "123");
assert_eq!(globals.get::<_, i32>("str")?, 123);
assert_eq!(globals.get::<_, i32>("num")?, 123);
assert!(globals.get::<_, String>("func").is_err());
Ok(())
}
@@ -266,62 +375,139 @@ fn test_error() -> Result<()> {
assert!(understand_recursion.call::<_, ()>(()).is_err());
match catch_unwind(|| -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
Ok(())
}
#[test]
fn test_panic() -> Result<()> {
fn make_lua(options: LuaOptions) -> Result<Lua> {
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let rust_panic_function =
lua.create_function(|_, msg: Option<StdString>| -> Result<()> {
if let Some(msg) = msg {
panic!("{}", msg)
}
panic!("rust panic")
})?;
lua.globals()
.set("rust_panic_function", rust_panic_function)?;
Ok(lua)
}
// Test triggerting Lua error passing Rust panic (must be resumed)
{
let lua = make_lua(LuaOptions::default())?;
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
lua.load(
r#"
_, err = pcall(rust_panic_function)
error(err)
"#,
)
.exec()
})) {
Ok(Ok(_)) => panic!("no panic was detected"),
Ok(Err(e)) => panic!("error during panic test {:?}", e),
Err(p) => assert!(*p.downcast::<&str>().unwrap() == "rust panic"),
};
// Trigger same panic again
match lua.load("error(err)").exec() {
Ok(_) => panic!("no error was detected"),
Err(Error::PreviouslyResumedPanic) => {}
Err(e) => panic!("expected PreviouslyResumedPanic, got {:?}", e),
}
}
// Test returning Rust panic (must be resumed)
{
let lua = make_lua(LuaOptions::default())?;
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
let _catched_panic = lua
.load(
r#"
-- Set global
_, err = pcall(rust_panic_function)
return err
"#,
)
.eval::<Value>()?;
Ok(())
})) {
Ok(_) => panic!("no panic was detected"),
Err(_) => {}
};
assert!(lua.globals().get::<_, Value>("err")? == Value::Nil);
match lua.load("tostring(err)").exec() {
Ok(_) => panic!("no error was detected"),
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::PreviouslyResumedPanic => {}
e => panic!("expected PreviouslyResumedPanic, got {:?}", e),
},
Err(e) => panic!("expected CallbackError, got {:?}", e),
}
}
// Test representing rust panic as a string
match catch_unwind(|| -> Result<()> {
let lua = make_lua(LuaOptions::default())?;
lua.load(
r#"
function rust_panic()
local _, err = pcall(function () rust_panic_function() end)
if err ~= nil then
error(err)
end
end
local _, err = pcall(rust_panic_function)
error(tostring(err))
"#,
)
.exec()?;
let rust_panic_function =
lua.create_function(|_, ()| -> Result<()> { panic!("test_panic") })?;
globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, Function>("rust_panic")?;
rust_panic.call::<_, ()>(())
}) {
Ok(Ok(_)) => panic!("no panic was detected"),
Ok(Err(e)) => panic!("error during panic test {:?}", e),
Err(p) => assert!(*p.downcast::<&str>().unwrap() == "test_panic"),
};
match catch_unwind(|| -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
function rust_panic()
local _, err = pcall(function () rust_panic_function() end)
if err ~= nil then
error(tostring(err))
end
end
"#,
)
.exec()?;
let rust_panic_function =
lua.create_function(|_, ()| -> Result<()> { panic!("test_panic") })?;
globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, Function>("rust_panic")?;
rust_panic.call::<_, ()>(())
.exec()
}) {
Ok(Ok(_)) => panic!("no error was detected"),
Ok(Err(Error::RuntimeError(_))) => {}
Ok(Err(e)) => panic!("unexpected error during panic test {:?}", e),
Ok(Err(e)) => panic!("expected RuntimeError, got {:?}", e),
Err(_) => panic!("panic was detected"),
};
}
// Test disabling `catch_rust_panics` option / pcall correctness
match catch_unwind(|| -> Result<()> {
let lua = make_lua(LuaOptions::new().catch_rust_panics(false))?;
lua.load(
r#"
local ok, err = pcall(function(msg) error(msg) end, "hello")
assert(not ok and err:find("hello") ~= nil)
ok, err = pcall(rust_panic_function, "rust panic from lua")
-- Nothing to return, panic should be automatically resumed
"#,
)
.exec()
}) {
Ok(r) => panic!("no panic was detected: {:?}", r),
Err(p) => assert!(*p.downcast::<StdString>().unwrap() == "rust panic from lua"),
}
// Test enabling `catch_rust_panics` option / xpcall correctness
match catch_unwind(|| -> Result<()> {
let lua = make_lua(LuaOptions::new().catch_rust_panics(false))?;
lua.load(
r#"
local msgh_ok = false
local msgh = function(err)
msgh_ok = err ~= nil and err:find("hello") ~= nil
return err
end
local ok, err = xpcall(function(msg) error(msg) end, msgh, "hello")
assert(not ok and err:find("hello") ~= nil)
assert(msgh_ok)
ok, err = xpcall(rust_panic_function, msgh, "rust panic from lua")
-- Nothing to return, panic should be automatically resumed
"#,
)
.exec()
}) {
Ok(r) => panic!("no panic was detected: {:?}", r),
Err(p) => assert!(*p.downcast::<StdString>().unwrap() == "rust panic from lua"),
}
Ok(())
}
@@ -389,7 +575,7 @@ fn test_num_conversion() -> Result<()> {
assert_eq!(lua.load("1.0").eval::<i64>()?, 1);
assert_eq!(lua.load("1.0").eval::<f64>()?, 1.0);
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua54", feature = "lua53"))]
assert_eq!(lua.load("1.0").eval::<String>()?, "1.0");
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
assert_eq!(lua.load("1.0").eval::<String>()?, "1");
@@ -405,7 +591,9 @@ fn test_num_conversion() -> Result<()> {
assert!(lua.load("math.huge").eval::<i64>().is_err());
assert_eq!(lua.unpack::<f64>(lua.pack(f32::MAX)?)?, f32::MAX as f64);
assert!(lua.unpack::<f32>(lua.pack(f64::MAX)?).is_err());
assert_eq!(lua.unpack::<f64>(lua.pack(f32::MIN)?)?, f32::MIN as f64);
assert_eq!(lua.unpack::<f32>(lua.pack(f64::MAX)?)?, f32::INFINITY);
assert_eq!(lua.unpack::<f32>(lua.pack(f64::MIN)?)?, f32::NEG_INFINITY);
assert_eq!(lua.unpack::<i128>(lua.pack(1i128 << 64)?)?, 1i128 << 64);
@@ -466,7 +654,12 @@ fn test_pcall_xpcall() -> Result<()> {
assert_eq!(globals.get::<_, String>("pcall_error")?, "testerror");
assert_eq!(globals.get::<_, bool>("xpcall_statusr")?, false);
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit"))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit"
))]
assert_eq!(
globals.get::<_, std::string::String>("xpcall_error")?,
"testerror"
@@ -693,6 +886,24 @@ fn too_many_binds() -> Result<()> {
Ok(())
}
#[test]
fn test_ref_stack_exhaustion() {
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
let lua = Lua::new();
let mut vals = Vec::new();
for _ in 0..1000000 {
vals.push(lua.create_table()?);
}
Ok(())
})) {
Ok(_) => panic!("no panic was detected"),
Err(p) => assert!(p
.downcast::<StdString>()
.unwrap()
.starts_with("cannot create a Lua reference, out of auxiliary stack space")),
}
}
#[test]
fn large_args() -> Result<()> {
let lua = Lua::new();
@@ -799,7 +1010,7 @@ fn context_thread() -> Result<()> {
)
.into_function()?;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
f.call::<_, ()>(lua.current_thread())?;
#[cfg(any(feature = "lua51", feature = "luajit"))]
@@ -828,3 +1039,15 @@ fn context_thread_51() -> Result<()> {
Ok(())
}
#[test]
#[cfg(feature = "luajit")]
fn test_jit_version() -> Result<()> {
let lua = Lua::new();
let jit: Table = lua.globals().get("jit")?;
assert!(jit
.get::<_, String>("version")?
.to_str()?
.contains("LuaJIT"));
Ok(())
}
+6 -1
View File
@@ -100,7 +100,12 @@ fn coroutine_from_closure() -> Result<()> {
let thrd_main = lua.create_function(|_, ()| Ok(()))?;
lua.globals().set("main", thrd_main)?;
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit"))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit"
))]
let thrd: Thread = lua.load("coroutine.create(main)").eval()?;
#[cfg(feature = "lua51")]
let thrd: Thread = lua

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